Skip to content

fix(delegate-task): recover batch tasks from JSON-encoded string coer…#22092

Closed
uzunkuyruk wants to merge 1 commit into
NousResearch:mainfrom
uzunkuyruk:fix/delegate-task-json-string-coercion
Closed

fix(delegate-task): recover batch tasks from JSON-encoded string coer…#22092
uzunkuyruk wants to merge 1 commit into
NousResearch:mainfrom
uzunkuyruk:fix/delegate-task-json-string-coercion

Conversation

@uzunkuyruk

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes silent batch delegation failures when open-weight models (Qwen, DeepSeek, GLM) emit the tasks array as a JSON-encoded string instead of a native array.

When this happens, coerce_tool_args silently wraps the string into a single-element list ["[{...}]"]. The delegate_task function then receives a list containing one string, passes the isinstance(tasks, list) check, and crashes on task.get("goal") — or falls through to the unhelpful "Provide either 'goal' or 'tasks'" error.

Three-layer fix:

  • _coerce_json: now logs a WARNING when JSON parsing fails or yields the wrong type, instead of returning the original string silently
  • coerce_tool_args: detects strings that look like JSON arrays (start with [) before wrapping, and emits a clear warning to aid debugging
  • delegate_task: adds post-coercion recovery — if tasks arrives as a single-element list containing a JSON string, attempt to parse and unwrap it before validation, restoring batch mode for affected models

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

References

Fixes #21933

Checklist

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • My PR contains only changes related to this fix

…cion

Open-weight models (Qwen, DeepSeek, GLM) sometimes emit the tasks array
as a JSON-encoded string instead of a native array. The existing
coerce_tool_args fallback silently wrapped this into a single-element
list, causing batch delegation to fail with a confusing error.

Three-layer fix:
- _coerce_json: log a warning when JSON parsing fails or yields the
  wrong type, instead of silently returning the original string
- coerce_tool_args: detect strings that look like JSON arrays before
  wrapping, and emit a clear warning to aid debugging
- delegate_task: add post-coercion recovery — if tasks arrives as a
  single-element list containing a JSON string, attempt to parse and
  unwrap it before validation, restoring batch mode for affected models

Fixes NousResearch#21933
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder tool/delegate Subagent delegation labels May 8, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: #21966 and #21957 — all three PRs fix the same issue (#21933: delegate_task batch mode fails when model emits tasks as JSON string). This PR has the most comprehensive fix with three-layer recovery.

kshitijk4poor pushed a commit that referenced this pull request May 9, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in #21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for #21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR #22092.
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #22436. Your model_tools.py diagnostic logging changes were cherry-picked with original authorship preserved (7330183d0 on main) — thanks for catching the silent-failure paths in _coerce_json and coerce_tool_args.

The delegate_task recovery from this PR was dropped from the salvage. After verifying empirically against #21933, delegate_task is in _AGENT_LOOP_TOOLS (model_tools.py:495) and is dispatched directly from run_agent.py via _dispatch_delegate_task() with raw function_args from json.loads — it never goes through coerce_tool_args. So tasks arrives at delegate_task as a raw string when the model emits it as a JSON-encoded string, never as a wrapped [json_string] form. The recovery in this PR targeted the wrapped form and didn't trigger for the actual bug.

The fix that does work (handling the raw string case) was salvaged from #21966 by @Bartok9 in commit 326ca754a. Closes #21933 properly. Thanks for the careful diagnosis and the logging improvements!

JZKK720 pushed a commit to JZKK720/hermes-agent that referenced this pull request May 11, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in NousResearch#21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for NousResearch#21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR NousResearch#22092.
rmulligan pushed a commit to rmulligan/hermes-agent that referenced this pull request May 11, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in NousResearch#21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for NousResearch#21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR NousResearch#22092.
JinyuID pushed a commit to JinyuID/hermes-agent that referenced this pull request May 11, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in NousResearch#21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for NousResearch#21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR NousResearch#22092.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in NousResearch#21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for NousResearch#21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR NousResearch#22092.
@Bartok9

Bartok9 commented May 14, 2026

Copy link
Copy Markdown
Contributor

Appreciate the careful diagnosis. You're right — I'd targeted the wrapped [json_string] form because that's what coerce_tool_args produces, but delegate_task bypasses that path entirely via _dispatch_delegate_task() so it never sees the wrapping. That's a subtle dispatch quirk worth knowing.

Good salvage call taking the diagnostic logging from model_tools.py (the silent-failure paths in _coerce_json / coerce_tool_args were the bigger long-term win anyway) and pulling the actual delegate_task recovery from #21966. Closing this one as superseded by the salvage.

Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request May 25, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in NousResearch#21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for NousResearch#21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR NousResearch#22092.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in NousResearch#21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for NousResearch#21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR NousResearch#22092.
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in NousResearch#21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for NousResearch#21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR NousResearch#22092.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: delegate_task batch mode fails silently when model emits tasks as JSON string

4 participants