fix(delegate): accept JSON string batch tasks

Recover delegate_task batch inputs when open-weight models emit tasks as a JSON-encoded array string, and return clear errors for malformed task lists.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Bartok 2026-05-08 12:30:08 -04:00 committed by kshitij
parent 4632be123d
commit 326ca754ad
2 changed files with 90 additions and 0 deletions

View file

@ -1867,6 +1867,29 @@ def _run_single_child(
logger.debug("Failed to close child agent after delegation")
def _recover_tasks_from_json_string(
tasks: Any,
) -> tuple[Optional[List[Dict[str, Any]]], Optional[str]]:
if not isinstance(tasks, str):
return None, None
raw = tasks.strip()
if not raw:
return None, "Provide either 'goal' (single task) or 'tasks' (batch)."
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
return None, (
"tasks must be a JSON array of task objects; received a string "
f"that could not be parsed as JSON ({exc.msg})."
)
if not isinstance(parsed, list):
return None, (
f"tasks must be a JSON array of task objects; parsed "
f"{type(parsed).__name__} instead."
)
return parsed, None
def delegate_task(
goal: Optional[str] = None,
context: Optional[str] = None,
@ -1951,6 +1974,12 @@ def delegate_task(
# Normalize to task list
max_children = _get_max_concurrent_children()
recovered_tasks, tasks_error = _recover_tasks_from_json_string(tasks)
if tasks_error:
return tool_error(tasks_error)
if recovered_tasks is not None:
tasks = recovered_tasks
if tasks and isinstance(tasks, list):
if len(tasks) > max_children:
return tool_error(
@ -1973,6 +2002,10 @@ def delegate_task(
# Validate each task has a goal
for i, task in enumerate(task_list):
if not isinstance(task, dict):
return tool_error(
f"Task {i} must be an object, got {type(task).__name__}."
)
if not task.get("goal", "").strip():
return tool_error(f"Task {i} is missing a 'goal'.")