[fix] Expand x-ag-type-ref in reference-tool input schemas so harnesses can invoke them#4890
Conversation
A type:"reference" workflow tool ships its messages input as
{"type":"array","x-ag-type-ref":"messages"} with no items. x-ag-type-ref
is an Agenta-internal catalog pointer, not standard JSON Schema, so a harness
(Claude / an MCP client) sees an array with no item shape and cannot build a
valid call, returning null.
Expand x-ag-type-ref pointers into concrete JSON Schema at the reference
resolver, before the spec is built. New pure helper expand_type_refs resolves
each pointer against the in-process CATALOG_TYPES: the catalog supplies the
structure, the author's annotations are preserved, and an unknown key is left
as-is. AgentaWorkflowToolResolver wraps the input schema through it.
SDK-only and content-only: the emitted inputSchema is concrete now, the wire
shape is unchanged, and the golden fixtures are untouched.
Claude-Session: https://claude.ai/code/session_01GYo3UEfvsZpncagqb28Mbc
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughA new Changesx-ag-type-ref Schema Expansion
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🤖 The AI agent says: Changes made A
No wire change. This changes the content of the emitted Tests: Docs: the keep-docs-in-sync note (the "Reference" bullet in |
mmabrouk
left a comment
There was a problem hiding this comment.
🤖 The AI agent says: Inline pointers to the expansion, the unknown-key fallback, and the call site — the spots worth a close read.
| return node | ||
|
|
||
| ref = node.get(_TYPE_REF_KEY) | ||
| if isinstance(ref, str) and ref in catalog: |
There was a problem hiding this comment.
🤖 The AI agent says: This is the expansion and the merge policy. When a node carries x-ag-type-ref: <key> and <key> is in the catalog, the catalog entry (deep-copied) is the base. The author's keys then overlay it with one rule: a structural keyword the catalog already defines (type, items, ...) always comes from the catalog (line 99), while annotations like description/title are kept from the author (line 100). The marker itself is dropped (line 97). The recursion at line 102 lets a catalog type that itself references another (e.g. llm -> model) expand fully. Please sanity-check this policy: catalog wins for structure, author wins for annotations.
|
|
||
| # No resolvable ref here. Rebuild the node so nested refs inside it expand; an unknown ref key | ||
| # falls through to here and is left untouched (its scalar marker value is returned as-is). | ||
| return {key: _expand(value, catalog) for key, value in node.items()} |
There was a problem hiding this comment.
🤖 The AI agent says: This is the unknown-key fallback. If x-ag-type-ref names a key that is not in the catalog (or the ref is not a string), we do not expand: the node is rebuilt as-is — marker and all — and nothing crashes. Is leave-as-is the behavior you want for an unknown ref? I chose it over failing loud so a stray marker surfaces to a human instead of breaking a run; the harness still gets whatever the author wrote.
| # not a bare ``x-ag-type-ref``) and can construct the call. Reference tools are | ||
| # the only tool kind whose schema comes from a workflow's inputs and so can | ||
| # carry these pointers; code/client tools author plain JSON Schema. | ||
| input_schema=expand_type_refs(tool_config.input_schema), |
There was a problem hiding this comment.
🤖 The AI agent says: The call site. The reference's input_schema is the workflow's authored input schema — the only tool schema that can carry x-ag-type-ref. Wrapping it here keeps the expansion at one chokepoint and off the wire; gateway, code, and client specs are untouched.
|
🤖 The AI agent says: What I need from you This is a code review, with two decisions I want you to confirm:
Everything else is mechanical: same wire shape, golden untouched, Note: live verification (a Claude agent actually invoking a reference workflow) needs an SDK redeploy — the orchestrator will redeploy and re-run that cell. The unit tests prove the schema is now concrete. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c2066186-c629-49d4-9cd1-9067e08e7b26
📒 Files selected for processing (3)
sdks/python/agenta/sdk/agents/platform/_schema.pysdks/python/agenta/sdk/agents/platform/workflow.pysdks/python/oss/tests/pytest/unit/agents/platform/test_schema_expand.py
mmabrouk
left a comment
There was a problem hiding this comment.
I'm deeply confused by this PR. The schema for agents for completion and for chat is very well known. We know exactly what is the schema for messages.
So I'm not sure what we're trying to do here.
Why aren't we extracting that schema from the inspect endpoint for that workflow? Don't we have that schema? And if not, let's make sure that the InspectFor agent has the clear schema for messages. The same goes for chat and completion. The only issue I see is that, for chat and completion, they have inputs, and these inputs have some parameters, and these parameters are specified as part of the config. That's pretty tricky, but if I understand, this is not the scope of this PR. Therefore, if the problem is about messages, why are we doing all of this?
…pe-ref expansion CodeRabbit flagged that the structural denylist omitted validation constraints (minItems, maxItems, minLength, maxLength, pattern, minimum, maximum, uniqueItems, multipleOf, exclusiveMinimum/Maximum, and the object-size siblings). An author annotation on a referenced node could therefore override the catalog's canonical constraint and let the emitted schema drift from the type's required shape. Add those constraint keywords to _STRUCTURAL_KEYS so the catalog wins for a referenced type; the author keeps only true annotations (description, title, examples). Adds a test asserting an author-supplied minItems and pattern do not override the catalog.
|
🤖 The AI agent says: You're right that the messages schema is well known. One fact is off, and that one fact is the whole point of the PR. Three points. 1. The messages schema is well known — and this PR resolves against exactly that schema. 2. 3. It's generic, not messages-only. Completion and LLM workflows reference On your instinct: sourcing the schema from a live |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
🤖 The AI agent says: Logging a TODO for test coverage on the tool schema shapes this PR introduces. Recorded in The TODO covers two shapes to lock down:
Goal: snapshot or assertion-lock these so future schema changes don't silently drift. |
Context
A
type:"reference"workflow tool ships itsmessagesinput as{"type":"array","x-ag-type-ref":"messages"}— an array with noitems.x-ag-type-refis anAgenta-internal catalog pointer, not standard JSON Schema. The harness (Claude, an MCP client)
sees an array with no item shape, cannot build a valid call, and returns null. Mahmoud hit this: a
Claude agent with a reference tool could not invoke the referenced workflow.
The fix
Expand the pointers into concrete JSON Schema at the reference resolver, before the spec is built.
expand_type_refs(sdks/python/agenta/sdk/agents/platform/_schema.py) walksthe schema and resolves each
x-ag-type-refagainst the in-processCATALOG_TYPES.AgentaWorkflowToolResolver.resolve(platform/workflow.py) wraps the reference'sinput_schemathrough it.Merge policy: the catalog supplies the structure (
type,items, ...); the author's annotations(
description,title, ...) are preserved; thex-ag-type-refmarker is dropped. An unknown keyis left exactly as authored, with no crash. Nested and transitive pointers expand too.
The helper lives under
platform/on purpose.CATALOG_TYPESis defined inutils.types, whichimports
agents.tools, so importing the catalog from atoolsmodule is circular. The platformpackage is imported lazily and sits below that cycle.
Scope / risk
workflow's inputs, so they are the only kind that can carry these pointers.
codeandclienttools author plain JSON Schema and are unaffected.
inputSchema(concrete now), not the wireshape. No golden fixture carries a reference tool with
x-ag-type-ref, so there is no goldenupdate and the wire-contract tests stay green.
mutated.
How to QA
Prerequisites: the SDK virtualenv (
cd sdks/python && uv sync).Unit:
cd sdks/pythonuv run --no-sync python -m pytest oss/tests/pytest/unit/agents/platform/test_schema_expand.py oss/tests/pytest/unit/agents/platform/test_workflow_resolver.py oss/tests/pytest/unit/agents/test_wire_contract.py -n0 -qmessagesref expands to an array with items(role + content), the author's
descriptionsurvives, a ref-free schema is unchanged, anunknown key is left as-is, and a nested ref expands.
Edge cases covered: unknown catalog key (left as-is, no crash), transitive ref (
llm->model),purity (input not mutated), structural-vs-annotation precedence (catalog wins for
type/items,author wins for
description/title).Live (needs an SDK redeploy): a Claude agent with a reference tool can now invoke the referenced
workflow instead of returning null. The orchestrator will redeploy the SDK and re-run this cell.
Interface reference
expand_type_refs(schema, catalog=CATALOG_TYPES)atsdks/python/agenta/sdk/agents/platform/_schema.py.sdks/python/agenta/sdk/agents/platform/workflow.py,AgentaWorkflowToolResolver.resolve—input_schema=expand_type_refs(tool_config.input_schema).CATALOG_TYPESatsdks/python/agenta/sdk/utils/types.py.CATALOG_TYPES["messages"]is
{"type":"array","items":{...role/content...},"title":"Messages","x-ag-type":"messages"}.inputSchemacontent only.Docs
The reference-tool schema-delivery docs (the "Reference" bullet in
documentation/tools.mdandinterfaces/in-service/tool-models-and-resolution.md) should note thatx-ag-type-refis nowexpanded at the reference resolver. Those files are owned and committed by the tool-discovery lane
(#4884), so the note is left out of this PR to avoid a cross-lane ownership conflict; it should be
folded into that lane or applied after it merges.
https://claude.ai/code/session_01GYo3UEfvsZpncagqb28Mbc