You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A research assistant with long threads. Over 30+ turns the user pulls in documents, runs searches and narrows a list of vendors. Many of those turns carry large tool results (search results, document extracts).
Then the user asks for something that suits a focused child agent, e.g. "compare pricing for the three vendors we shortlisted". We want the parent to delegate to a researcher subagent with a short, self-contained brief it writes itself:
Find current list pricing for the team plans of Vendor A, Vendor B and Vendor C. Return plan name, price per seat, billing period and source URL.
This is how Claude Code's Task tool and the OpenAI Agents SDK's Agent.as_tool() work: the parent writes the child's input.
How defineAgent falls short (0.61.0)
The parent can't write a brief. The tool generated for each agent has no inputSchema (spawn.ts#L720-L725), so the model sees a tool with no parameters. execute ignores its input anyway (execute: async (_input, context)).
The child gets the whole transcript as its brief.ctx.messages is the parent conversation up to the tool call (messagesBeforeCall, spawn.ts#L662). In our example that's 30+ turns and every earlier tool result. The child has to work out which request is its job.
It costs a lot of tokens.
Parallel children each get a full copy.
The child has its own system prompt and tools, so the prompt cache prefix from the parent doesn't carry over.
With order: 'sequence' each child also gets the text of the children before it.
Workarounds we looked at
Compact ctx.messages inside run (trim, or summarise with a cheap model). The child still has to guess its task, and the summary can drop the one detail it needs. It also breaks resume: on a resume ctx.messages is [...parent, ...child's own messages] (spawn.ts#L243), so trimming can cut the child's own tool-call history.
Read the parent's lead-in text.messagesBeforeCall keeps the text of the message that carries the call, so run could treat it as the brief. That only works if the model writes the text every time, and the user sees it streamed.
An optional inputSchema on defineAgent. When set, the generated tool exposes it, and execute passes the validated input to run as ctx.input:
constresearcher=defineAgent({name: 'researcher',description: 'Researches a focused question and returns sourced findings',inputSchema: z.object({task: z.string().describe('Self-contained brief: what to find and what to return'),}),run: (ctx)=>chat({
adapter,messages: [{role: 'user',content: ctx.input.task}],threadId: ctx.threadId,runId: ctx.runId,parentRunId: ctx.parentRunId,subagentRunId: ctx.subagentRunId,resume: ctx.resume,}),})
Agents without inputSchema behave as they do now.
ctx.messages stays, so run can still add context from the parent if it wants.
Resume already runs the same open tool call again, so the same input is available on the resumed run.
Open question: router mode has no tool call, so ctx.input would be undefined there unless the router can return input with its pick (e.g. { names, input }).
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Use case
A research assistant with long threads. Over 30+ turns the user pulls in documents, runs searches and narrows a list of vendors. Many of those turns carry large tool results (search results, document extracts).
Then the user asks for something that suits a focused child agent, e.g. "compare pricing for the three vendors we shortlisted". We want the parent to delegate to a
researchersubagent with a short, self-contained brief it writes itself:This is how Claude Code's Task tool and the OpenAI Agents SDK's
Agent.as_tool()work: the parent writes the child's input.How
defineAgentfalls short (0.61.0)inputSchema(spawn.ts#L720-L725), so the model sees a tool with no parameters.executeignores its input anyway (execute: async (_input, context)).ctx.messagesis the parent conversation up to the tool call (messagesBeforeCall, spawn.ts#L662). In our example that's 30+ turns and every earlier tool result. The child has to work out which request is its job.order: 'sequence'each child also gets the text of the children before it.Workarounds we looked at
ctx.messagesinsiderun(trim, or summarise with a cheap model). The child still has to guess its task, and the summary can drop the one detail it needs. It also breaks resume: on a resumectx.messagesis[...parent, ...child's own messages](spawn.ts#L243), so trimming can cut the child's own tool-call history.messagesBeforeCallkeeps the text of the message that carries the call, soruncould treat it as the brief. That only works if the model writes the text every time, and the user sees it streamed.defineAgentand use a plain server tool with ataskinput that runs a childchat(). This gives us the brief but loses what feat: add AG-UI subagents to chat() and useChat #1438 added: subagent cards, live child streaming, child interrupt resume and usage roll-up. Discussion Support AG-UI projection for Agent-as-Tool / nested agent runs #1335 describes the same trade-off.Proposal
An optional
inputSchemaondefineAgent. When set, the generated tool exposes it, andexecutepasses the validated input torunasctx.input:inputSchemabehave as they do now.ctx.messagesstays, soruncan still add context from the parent if it wants.Open question: router mode has no tool call, so
ctx.inputwould beundefinedthere unless the router can return input with its pick (e.g.{ names, input }).All reactions