Repository navigation
AUDIT-13 — Medium — Tool JSON schemas drop $defs (unresolvable $ref) #70
Description
Summary
Severity: Medium
Area: Tools / schema
Affected files: agentkit/tools/schema.py:63-78
Suggested branch: fix/tool-schema-defs
Suggested PR title: fix(tools): preserve $defs in generated tool schemas
This is finding AUDIT-13 of 18 from a consolidated audit of agentkit/. It is deliberately self-contained so it can be fixed on its own branch with its own PR.
Related findings: Independent of the other findings.
AUDIT-13 — Medium — Tool JSON schemas drop $defs (unresolvable $ref)
File — agentkit/tools/schema.py:63-78
raw_json_schema = args_model.model_json_schema()
properties: dict[str, Any] = raw_json_schema.get("properties", {})
required: list[str] = raw_json_schema.get("required", [])
parameters: dict[str, Any] = {
"type": "object",
"properties": properties,
}
if required:
parameters["required"] = requiredWhy this is a bug
Pydantic emits nested models and enums as $ref: "#/$defs/…", with the definitions in a sibling $defs key. The schema builder keeps only type/properties/required, so the $defs section is dropped and the emitted schema contains unresolvable references, which providers reject or mis-handle.
Reproduced
tool param typed as an Enum or a nested BaseModel
-> parameters = {"type":"object","properties":{"mode":{"$ref":"#/$defs/Mode", ...}}}
-> no "$defs" key in the emitted schema
Pass cases
- A tool whose parameters contain an
Enumor a nestedBaseModelproduces a schema in which every$refresolves inside the emitted document. -
$defs(andadditionalProperties,title,defaulthandling) are preserved in the emittedparameters. - The emitted schema is valid JSON Schema: a test resolves all
$refs (or validates it with a JSON-Schema validator) and asserts no dangling references. - Existing simple tools (calculator, read_file, http_fetch, sql_readonly, web_search) produce byte-identical schemas to today (no regression for the built-ins).
Definition of done (per AGENTS.md)
- New/updated unit and integration tests covering the pass cases above, all passing (
pytest). -
ruff check .,ruff format --check .andmypy agentkitclean. - Docstrings updated where behaviour changed;
docs/PROGRESS.mdupdated (status, PR link, commit count, notes). - A new ADR in
docs/DECISIONS.mdif an architectural decision was made. - Branch pushed and PR opened (never commit directly to
main).
Baseline at the time of writing: pytest is green on main (all tests pass; tests/real/* skipped unless AGENTKIT_REAL_INFRA=1).
Activity
getdownandcode commentedon Oct 20, 2026
Audit index (18 separate issues, fix one by one)
Each issue is self-contained: current code, reproduction, pass cases, suggested branch and PR title. Reference the relevant one with
Fixes #NNin your PR.HamzaKhanBUIC commentedon Oct 20, 2026
Opened PR #81 with the fix and regression test suite:
ead_file, http_fetch, sql_readonly, web_search) produce byte-identical schemas without extra keys.
uff check,
uff format --check, and mypy agentkit.