Skip to content

fix(agent): validate context/memory tool schemas before wrapping (#47707) - #47712

Closed
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:fix/47707-validate-context-tool-schema
Closed

fix(agent): validate context/memory tool schemas before wrapping (#47707)#47712
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:fix/47707-validate-context-tool-schema

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Validate context-engine and memory-provider tool schemas before wrapping them into OpenAI tool form.
  • One malformed/already-wrapped schema no longer poisons the entire request (HTTP 400 → whole toolset disabled).

Motivation

Closes #47707.

In agent/agent_init.py, schemas returned by a context engine's get_tool_schemas() were wrapped as {"type": "function", "function": _schema} without validating that _schema has a top-level name. If a provider returns an entry already in OpenAI tool form ({"type":"function","function":{...}}), the result is double-wrapped into a tool whose function has no top-level name. Strict providers (e.g. DeepSeek) reject the entire request with tools[N].function: missing field name (HTTP 400), so a single bad tool silently disables the whole toolset and breaks every turn. The tool was also never added to valid_tool_names, so even lenient providers couldn't call it. The same unguarded pattern lived in agent/memory_manager.py.

Fix

Add a shared normalize_tool_schema() helper in memory_manager.py that:

  • unwraps an already-wrapped OpenAI tool entry to its inner function schema, and
  • returns None for anything lacking a resolvable top-level string name.

Wire it into:

  • the agent_init.py context-engine injection loop (skip-with-warning instead of appending a nameless tool),
  • inject_memory_provider_tools(),
  • MemoryManager.add_provider() routing index, and
  • MemoryManager.get_all_tool_schemas().

So a single bad plugin schema is skipped (or, if merely double-wrapped but otherwise valid, correctly unwrapped) instead of poisoning the request, with a clear log line pointing at the offending provider.

Verification

  • python3 -m pytest tests/agent/test_memory_provider.py — 100 passed (incl. 9 new)
  • python3 -m pytest tests/agent/ -k "memory or tool_schema or context_engine or inject" — 209 passed
  • ruff check agent/memory_manager.py agent/agent_init.py — clean
  • New regression tests assert the unwrap + skip-nameless behavior and fail without the fix (the old blind-wrap path produced a nameless function).

Real behavior proof

  • Behavior addressed: an already-wrapped schema {"type":"function","function":{"name":"x_grep",...}} was blind-wrapped a second time into {"type":"function","function":{"type":"function","function":{...}}} — a tool whose function has no top-level name → DeepSeek HTTP 400.
  • Real environment: Python 3.14, repo checkout on fix/47707-validate-context-tool-schema.
  • Captured output (real run on this branch):
=== BEFORE fix: blind wrap of already-wrapped schema (DeepSeek 400 trigger) ===
{
"type": "function",
"function": {
"type": "function",
"function": {
"name": "x_grep",
...
}
}
}
-> tool.function has top-level 'name'?: False

=== AFTER fix: normalize then wrap ===
{
"type": "function",
"function": {
"name": "x_grep",
"description": "grep X",
"parameters": {"type": "object", "properties": {}}
}
}
-> tool.function has top-level 'name'?: True
  • Regression test: tests/agent/test_memory_provider.py::TestMemoryInjectionRejectsMalformedSchema::test_already_wrapped_schema_is_unwrapped_not_poisoned (+ test_nameless_schema_is_skipped, test_good_schema_still_injected_alongside_bad, and TestNormalizeToolSchema).
  • What was NOT tested: live end-to-end against a real DeepSeek endpoint (the 400 is reproduced structurally via the wrapped-schema shape, not a network call).

Closes NousResearch#47707

Context engines and memory providers expose tool schemas via
get_tool_schemas(). agent_init.py wrapped each as
{"type":"function","function":_schema} without validating that
_schema carries a top-level name. A provider returning an entry already
in OpenAI tool form ({"type":"function","function":{...}}) was then
double-wrapped into a tool whose function has no name. Strict providers
(e.g. DeepSeek) reject the entire request with HTTP 400
'tools[N].function: missing field name', so one malformed schema
silently disables the whole toolset and breaks every turn. The schema
was also never added to valid_tool_names, so even lenient providers
could not call it.

Add a shared normalize_tool_schema() helper that unwraps an
already-wrapped entry and returns None for anything lacking a resolvable
string name. Wire it into the agent_init context-engine loop and all
three memory_manager surfaces (inject_memory_provider_tools,
add_provider routing index, get_all_tool_schemas), so a single bad
plugin schema is skipped with a warning instead of poisoning the
request.

Verification: 209 targeted agent/memory tests pass (incl. 9 new).
New tests assert the unwrap + skip-nameless behavior and fail without
the fix.
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/memory Memory tool and memory providers P2 Medium — degraded but workaround exists labels Jun 17, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the thorough fix. This is an automated hermes-sweeper review: the exact implementation has already landed on main via the salvaged PR #52140, retaining the original author credit.

@teknium1 teknium1 closed this Jul 14, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:implemented-on-main Sweeper: behavior already present on current main tool/memory Memory tool and memory providers type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Context-engine/memory-provider tool schemas are wrapped without validating a top-level name — one malformed schema 400s the entire request

3 participants