fix(agent): unwrap pre-wrapped context-engine tool schemas - #48065
Closed
arminanton wants to merge 1 commit into
Closed
fix(agent): unwrap pre-wrapped context-engine tool schemas#48065arminanton wants to merge 1 commit into
arminanton wants to merge 1 commit into
Conversation
ContextEngine.get_tool_schemas() is contracted to return BARE schemas
({name, description, parameters}); the host wraps each in the OpenAI envelope
before adding it to agent.tools. If an engine mistakenly pre-wraps its schemas,
wrapping again yields {"function": {"function": {...}}} whose OUTER
function.name is empty, and the provider rejects the whole request with HTTP 400
"tools[N].function.name: empty string" — breaking every turn for the session.
Add _normalize_context_engine_schema() to detect the envelope and unwrap to the
inner bare schema before processing, so both bare and pre-wrapped inputs
register with a correct name. Same error signature as NousResearch#11411 but a different
root cause (that one is the Responses-API input path; this is the outgoing
tool-schema registration in agent_init).
arminanton
marked this pull request as ready for review
June 17, 2026 21:46
arminanton
added a commit
to arminanton/hermes-agent
that referenced
this pull request
Jun 22, 2026
…olution semantic review Council: (3) per-hunk justification — every unmapped hunk blamed via git log -S to its origin commit, mapped to a standing user instruction (exclusion) OR a shipped PR. 216/216 accounted, 0 uncovered. Found+resolved 11 initially-uncovered (all in shipped test-cluster PRs NousResearch#48065/NousResearch#48101/NousResearch#49644/NousResearch#50032/NousResearch#50080/NousResearch#50078). (4) semantic-equivalence review of the resolution patches: all 6 active ones re-anchor their PR's exact intent onto v0.17.0, no silent behavior change; removed the DEAD agent_gemini_cloudcode_adapter patch (never invoked — withdrawn file — and imported withdrawn google_user_agent).
Contributor
|
Thanks for the focused regression analysis. Automated hermes-sweeper review found that current
Closing as implemented on main. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
agent_initdefensively unwraps an already-enveloped context-engine tool schema before re-wrapping it, so a misbehaving engine can't brick every turn with an HTTP 400.A
ContextEngine.get_tool_schemas()is contracted to return bare schemas ({name, description, parameters}); the host wraps each one in the OpenAI envelope ({"type": "function", "function": {...}}) before adding it toagent.tools. If an engine mistakenly pre-wraps its schemas in that envelope, wrapping again yields{"function": {"function": {...}}}whose outerfunction.nameis empty. The provider then rejects the entire request withtools[N].function.name: empty string, so every turn fails for the whole session once that engine is active.The fix detects the envelope and unwraps to the inner bare schema before processing, so both bare and pre-wrapped inputs register with a correct name.
Related Issue
Same error signature as #11411 (
empty stringname → provider 400), but a different root cause and code path: #11411 is about empty-namefunction_call_outputon the Responses-API input side; this is the outgoing tool-schema registration inagent_init. The empty-name 400 has multiple distinct sources; this PR closes the context-engine one.Type of Change
Changes Made
agent/agent_init.py— new module-level helper_normalize_context_engine_schema()that returns the inner bare schema when the input is an OpenAI envelope (type == "function"+ afunctiondict + no top-levelname), and returns bare/non-dict inputs unchanged. The context-engine tool-collection loop now calls it before wrapping, replacing the previous inline_schema.get("name", "")with a normalize-then-read.tests/agent/test_context_engine_tool_schema_unwrap.py— 8 new tests exercising the real helper fromagent_init(not a re-implementation): bare passthrough, envelope unwrap, non-dict input, the name+type-but-not-envelope edge case, and the resulting registration shape (bare / pre-wrapped / mixed / no-empty-names-emitted).How to Test
{"type": "function", "function": {"name": "grep", ...}}.{"function": {"function": {...}}}with an empty outer name → the next request 400s withtools[N].function.name: empty string. After: the schema is unwrapped, the tool registers withfunction.name == "grep", and the request is valid.Checklist
Code
fix(agent):)pytest tests/ -qand all tests pass (8 new + the existing 29 context-engine tests intests/agent/test_context_engine.py+test_context_engine_host_contract.py, no regression)Documentation & Housekeeping
cli-config.yaml.exampleif I added/changed config keys — N/A (no config keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A