Skip to content

[fix] Expand x-ag-type-ref in reference-tool input schemas so harnesses can invoke them#4890

Merged
mmabrouk merged 2 commits into
big-agentsfrom
fix/reference-tool-schema-expand
Jun 28, 2026
Merged

[fix] Expand x-ag-type-ref in reference-tool input schemas so harnesses can invoke them#4890
mmabrouk merged 2 commits into
big-agentsfrom
fix/reference-tool-schema-expand

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Context

A type:"reference" workflow tool ships its messages input as
{"type":"array","x-ag-type-ref":"messages"} — an array with no items. x-ag-type-ref is an
Agenta-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.

  • New pure helper expand_type_refs (sdks/python/agenta/sdk/agents/platform/_schema.py) walks
    the schema and resolves each x-ag-type-ref against the in-process CATALOG_TYPES.
  • AgentaWorkflowToolResolver.resolve (platform/workflow.py) wraps the reference's
    input_schema through it.

Merge policy: the catalog supplies the structure (type, items, ...); the author's annotations
(description, title, ...) are preserved; the x-ag-type-ref marker is dropped. An unknown key
is left exactly as authored, with no crash. Nested and transitive pointers expand too.

The helper lives under platform/ on purpose. CATALOG_TYPES is defined in utils.types, which
imports agents.tools, so importing the catalog from a tools module is circular. The platform
package is imported lazily and sits below that cycle.

Scope / risk

  • SDK reference-resolver only. Reference tools are the one tool kind whose schema comes from a
    workflow's inputs, so they are the only kind that can carry these pointers. code and client
    tools author plain JSON Schema and are unaffected.
  • Content-only. This changes the content of the emitted inputSchema (concrete now), not the wire
    shape. No golden fixture carries a reference tool with x-ag-type-ref, so there is no golden
    update and the wire-contract tests stay green.
  • A ref-free schema returns unchanged. An unknown ref key is left as-is. The input is never
    mutated.

How to QA

Prerequisites: the SDK virtualenv (cd sdks/python && uv sync).

Unit:

  1. cd sdks/python
  2. uv 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 -q
  3. Expected: all pass. The new test asserts a messages ref expands to an array with items
    (role + content), the author's description survives, a ref-free schema is unchanged, an
    unknown 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

  • Helper: expand_type_refs(schema, catalog=CATALOG_TYPES) at
    sdks/python/agenta/sdk/agents/platform/_schema.py.
  • Call site: sdks/python/agenta/sdk/agents/platform/workflow.py,
    AgentaWorkflowToolResolver.resolveinput_schema=expand_type_refs(tool_config.input_schema).
  • Catalog: CATALOG_TYPES at sdks/python/agenta/sdk/utils/types.py. CATALOG_TYPES["messages"]
    is {"type":"array","items":{...role/content...},"title":"Messages","x-ag-type":"messages"}.
  • No wire field changed; inputSchema content only.

Docs

The reference-tool schema-delivery docs (the "Reference" bullet in documentation/tools.md and
interfaces/in-service/tool-models-and-resolution.md) should note that x-ag-type-ref is now
expanded 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

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
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. python Pull requests that update Python code labels Jun 27, 2026
@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jun 27, 2026 10:39pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d27a5f3-a6c6-40d9-a2b3-f6881d2d0e8a

📥 Commits

Reviewing files that changed from the base of the PR and between 5aaf4d9 and 0a84f68.

📒 Files selected for processing (2)
  • sdks/python/agenta/sdk/agents/platform/_schema.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_schema_expand.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • sdks/python/agenta/sdk/agents/platform/_schema.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_schema_expand.py

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for expanding Agenta type-reference pointers into standard JSON Schema before workflow tools are used.
    • Workflow tool inputs now resolve referenced catalog types into concrete schemas automatically.
  • Bug Fixes

    • Preserves authored descriptions and titles during expansion.
    • Leaves unknown references unchanged while still expanding any nested valid references.
    • Ensures schema expansion does not mutate the original input.

Walkthrough

A new _schema.py module adds expand_type_refs, which recursively replaces x-ag-type-ref pointer nodes with concrete JSON Schema from the SDK catalog. workflow.py now applies this expansion when building CallbackToolSpec input schemas. Tests cover expansion rules and workflow resolver behavior.

Changes

x-ag-type-ref Schema Expansion

Layer / File(s) Summary
expand_type_refs implementation
sdks/python/agenta/sdk/agents/platform/_schema.py
Defines _TYPE_REF_KEY, _STRUCTURAL_KEYS, the public expand_type_refs wrapper, and recursive expansion logic that resolves catalog pointers, preserves authored annotations, and avoids mutating inputs.
Workflow tool spec wiring
sdks/python/agenta/sdk/agents/platform/workflow.py
Imports expand_type_refs and applies it to tool_config.input_schema when constructing CallbackToolSpec entries for type:"reference" workflow tools.
Unit and integration tests
sdks/python/oss/tests/pytest/unit/agents/platform/test_schema_expand.py
Covers core expansion, annotation preservation, unknown-ref passthrough, nested/transitive expansion, merge precedence, input purity, and an async AgentaWorkflowToolResolver check.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Agenta-AI/agenta#4835: Also centers on x-ag-type-ref catalog pointers and their relationship to CATALOG_TYPES resolution.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: expanding x-ag-type-ref for reference tool input schemas.
Description check ✅ Passed The description matches the code changes and explains the schema expansion fix and resolver update.
Docstring Coverage ✅ Passed Docstring coverage is 64.29% which is sufficient. The required threshold is 60.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reference-tool-schema-expand

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mmabrouk mmabrouk added the needs-review Agent updated; awaiting Mahmoud's review label Jun 27, 2026
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Changes made

A type:"reference" workflow tool shipped messages as {"type":"array","x-ag-type-ref":"messages"} — an array with no items. x-ag-type-ref is an Agenta-internal pointer, not standard JSON Schema, so Claude / an MCP client could not build the call and returned null. This PR expands those pointers into concrete JSON Schema at the reference resolver, before the spec is built.

  • New helper expand_type_refs in sdks/python/agenta/sdk/agents/platform/_schema.py. It walks a schema and resolves each x-ag-type-ref against the in-process CATALOG_TYPES. Pure, no I/O. It lives under platform/ because importing CATALOG_TYPES from a tools module is circular (CATALOG_TYPES is in utils.types, which imports agents.tools); platform/ is imported lazily and sits below that cycle.
  • Call site sdks/python/agenta/sdk/agents/platform/workflow.py: AgentaWorkflowToolResolver.resolve now wraps the reference's input_schema through the helper. Reference tools are the only tool kind whose schema comes from a workflow's inputs, so they are the only kind that can carry these pointers; code/client tools author plain JSON Schema and are untouched.
  • Merge policy: the catalog supplies the structure (type, items, ...); the author's annotations (description, title, ...) are preserved; the marker is dropped. An unknown key is left exactly as authored, with no crash. Nested and transitive pointers expand too.
  • New test oss/tests/pytest/unit/agents/platform/test_schema_expand.py (10 cases): the messages ref expands to an array with items, the author's description survives, a ref-free schema is unchanged, an unknown key is left as-is, a nested ref expands, plus transitivity, purity, and the merge precedence; one test drives the full resolver call site.

No wire change. This changes the content of the emitted inputSchema (concrete now), not the wire shape. No golden fixture carries a reference tool with x-ag-type-ref, so there is no golden update and the wire-contract tests stay green.

Tests: ruff format + ruff check clean; the full SDK agents suite (414) passes, including test_schema_expand.py, test_workflow_resolver.py, and test_wire_contract.py.

Docs: the keep-docs-in-sync note (the "Reference" bullet in documentation/tools.md and interfaces/in-service/tool-models-and-resolution.md should say x-ag-type-ref is now expanded at the reference resolver) is not in this PR. Those files are owned and committed by the tool-discovery lane (#4884), so I left the note out to avoid a cross-lane ownership conflict; it should be folded into that lane or applied after it merges.

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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()}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

What I need from you

This is a code review, with two decisions I want you to confirm:

  1. The expansion / merge policy. When a node carries x-ag-type-ref, the catalog supplies the structure (type, items, ...) and the author's annotations (description, title, ...) are preserved on top. Catalog wins for structural keywords; author wins for annotations. Is that the split you want? (See the inline comment on _schema.py.)
  2. The unknown-key choice. If a x-ag-type-ref names a key that is not in CATALOG_TYPES, the node is left exactly as authored — no crash, no expansion. I picked leave-as-is over failing loud. Confirm that is right, or say if you'd rather it fail.

Everything else is mechanical: same wire shape, golden untouched, code/client/gateway tools unaffected.

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.

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b9b4adf and 5aaf4d9.

📒 Files selected for processing (3)
  • sdks/python/agenta/sdk/agents/platform/_schema.py
  • sdks/python/agenta/sdk/agents/platform/workflow.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_schema_expand.py

Comment thread sdks/python/agenta/sdk/agents/platform/_schema.py

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 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. messages lives in the SDK as CATALOG_TYPES["messages"] (sdks/python/agenta/sdk/utils/types.py), with the concrete items (role + content). The backend catalog endpoint /workflows/catalog/types/ serves from that same dict (api/oss/src/resources/workflows/catalog.py). The expander walks the schema and resolves each x-ag-type-ref against that catalog. It invents no schema.

2. /inspect returns the pointer, not the shape. The agent's /inspect emits messages as {"type":"array","x-ag-type-ref":"messages"} — an array with no items (services/oss/src/agent/schemas.py, AGENT_INPUTS_SCHEMA). So "pull it from inspect" hands you the same pointer; something still has to resolve it against the catalog. The playground resolves x-ag-type-ref. A harness (Claude, an MCP client) does not. That gap is the bug: the harness sees a bare array, cannot build the call, and returns null.

3. It's generic, not messages-only. Completion and LLM workflows reference model the same way (and model sits transitively under agent_config/llm). One helper resolves every catalog pointer in any schema, so it covers chat AND completion — the two cases you named — instead of special-casing messages.

On your instinct: sourcing the schema from a live /inspect of the referenced workflow, instead of from the stored config, is a separate and larger change. And it would STILL hand back x-ag-type-ref pointers, so it would STILL need this expander. The PR isn't wasted either way.

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mmabrouk mmabrouk merged commit c8de20a into big-agents Jun 28, 2026
19 checks passed
@mmabrouk mmabrouk mentioned this pull request Jun 28, 2026
12 tasks
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says: Logging a TODO for test coverage on the tool schema shapes this PR introduces. Recorded in docs/design/agent-workflows/scratch/open-issues.md (entry: "Add test coverage for agent tool schema delivery (reference + platform tools)").

The TODO covers two shapes to lock down:

  1. /inspect response for an agent with reference/platform tools — the harness_capabilities block and tool defs, including the expansion state of x-ag-type-ref.
  2. Resolved tool shape after x-ag-type-ref expansion — concrete input_schema, the call descriptor fields, ref_by/slug/version for reference tools, op for platform tools.

Goal: snapshot or assertion-lock these so future schema changes don't silently drift.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review Agent updated; awaiting Mahmoud's review python Pull requests that update Python code size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant