Skip to content

Supervisor delegation

Supervisor delegation is for “ask a specialist once.” The user keeps talking only to the supervisor. When useful, the supervisor calls a temporary child agent through a tool, gives it one clear task, and receives the child agent’s final answer as the tool_result.

Use this for short-lived child agents such as:

  • an explore agent that reads the codebase and reports findings
  • a planner that proposes an implementation path
  • a reviewer that checks a patch
  • an implementer that writes one artifact and reports what changed

This pattern does not require a team mailbox. The child AgentLike runs for one request, returns one result, and the supervisor decides what to do next.

import {
agentTool,
createAgent,
} from "agent-lattice";
const explorer = createAgent({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
model: "deepseek-v4-flash",
name: "explorer",
systemPrompt: [
"You are an explore subagent.",
"Inspect the repository, identify relevant files, and report concise findings.",
"Do not make code changes.",
].join("\n"),
workspace: ".agent-workspaces/explorer",
});
const supervisor = createAgent({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
model: "deepseek-v4-flash",
name: "supervisor",
systemPrompt: "You supervise the task and call specialist subagents when useful.",
tools: [
agentTool("explore", explorer, {
description: "Ask a temporary explore subagent to inspect the codebase and report findings.",
}),
],
});
const result = await supervisor.prompt("Find the modules involved in SDK team delegation.");
console.log(result.result);

When the supervisor agent calls the explore tool with mode: "ask", the SDK calls explorer.prompt(...). The explorer’s final result becomes the supervisor’s tool_result, and then the supervisor continues its own agent loop.

agentTool() wraps a child agent into a plain tool — it returns a ToolDefinition you drop into createAgent’s tools just like any custom tool. To the supervisor model it’s indistinguishable from any other tool; the only difference is internal: when the model calls it, the SDK runs the wrapped child agent instead of running code, and returns that as the tool result.

That tool exposes a standard model-facing input:

{
mode: "ask",
task: "Inspect the SDK team runtime and summarize the relevant files.",
expectedOutput: "A concise file map and implementation notes",
acceptanceCriteria: [
"Mention the runner entry point",
"Mention mailbox state transitions"
]
}

For one-shot supervisor delegation, prefer mode: "ask".

Mode Meaning in supervisor delegation
ask Run the child AgentLike now and return its final answer as this tool result.
handoff Requires a team runtime. Without a runtime, the tool returns a clear error. Use mailbox teams for async handoff.
observe Reserved for observable long-running work. It currently reports unsupported unless a host runtime provides it.

The team runtime here is the runtime that backs a persistent mailbox team — it owns the message queue, holds task state, and drives member lifecycle after a handoff. This one-shot pattern has no runtime by default, so handoff / observe are rarely useful here; when you need them, use a persistent mailbox team.

AgentToolOptions also accepts metadata, a host-owned Record<string, unknown> passed through to the generated ToolDefinition.metadata (for example a contract version). The SDK never reads or interprets it and never shows it to the model. Requires 0.22.0 or later.

Requires 0.15.0 or later.

agentTool() accepts either a live AgentLike session (as above) or an AgentSpec template created by defineAgent():

import { agentTool, defineAgent } from "agent-lattice";
const explorerSpec = defineAgent({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
model: "deepseek-v4-flash",
name: "explorer",
systemPrompt: "You are an explore subagent. Inspect and report; do not make code changes.",
});
const supervisor = createAgent({
// ...
tools: [
agentTool("explore", explorerSpec, {
description: "Ask a temporary explore subagent to inspect the codebase and report findings.",
}),
],
});

A spec target spawns a fresh session on every tool call — no history leaks between unrelated tasks, and concurrent calls never collide on a shared conversation. A session target keeps its history across calls, so earlier tasks can influence later answers. Prefer a spec for supervisor delegation; register spec.spawn() only when the specialist should remember previous tasks. The generated tool description states which semantics the target has, so the supervisor model knows whether each task must be self-contained.

Requires 0.20.0 or later.

When the child’s deliverable should be typed data rather than prose, declare an OutputSchema on the child (a zod schema works directly): the child agent must then submit its answer through the injected submit_output tool, and the parent side validates what comes back. Since 0.21.0 agentTool() inherits the target’s declared schema — from spec.options.outputSchema for an AgentSpec, or from the live Agent’s outputSchema getter — so the parent-side copy can be omitted:

import { agentTool, createAgent } from "agent-lattice";
import { z } from "zod/v4";
const findingsSchema = z.object({
files: z.array(z.string()),
notes: z.string(),
});
const explorer = createAgent({
model: "deepseek-v4-flash",
name: "explorer",
systemPrompt: "You are an explore subagent. Inspect and report; do not make code changes.",
outputSchema: findingsSchema,
});
const supervisor = createAgent({
model: "deepseek-v4-flash",
name: "supervisor",
tools: [
agentTool("explore", explorer, {
description: "Ask a temporary explore subagent to inspect the codebase and report findings.",
// outputSchema is inherited from the target (0.21.0+).
}),
],
});

With mode: "ask" the tool result is the child’s validated structured output as a JSON string, and the generated tool description tells the supervisor model to expect that. If the child ends without submitting, or its submission fails the schema, the tool returns an is_error tool_result starting with child_output_invalid: — a normal tool error in the supervisor’s loop, so the supervisor can retry or rephrase the task. The same applies on the runtime delegate path: a failed child surfaces as child_output_invalid carrying the original failure code and message.

The structure is enforced by the harness rather than by prompt discipline, and unlike outputFormat it does not depend on provider response_format/json_schema support — any tool-capable provider works (see Provider Compatibility).

Contract inheritance and assembly-time checks

Section titled “Contract inheritance and assembly-time checks”

Requires 0.21.0 or later.

Passing AgentToolOptions.outputSchema explicitly is still allowed, but it must match the target’s declaration — compared by reference first, then by derived JSON Schema structure. A mismatch throws from agentTool() at assembly time; the error suggests sharing one schema instance between createAgent/defineAgent and agentTool(), or omitting the agentTool() copy to inherit it. Host-defined AgentLike adapters expose no readable declaration, so nothing is inherited or cross-checked for them — an explicit outputSchema still applies.

One combination is pinned down since 0.23.0: when the target declares no schema and agentTool() declares one explicitly, assembly allows it (there is nothing to cross-check against), and an ask call validates the child’s structuredResult against the parent-side schema and returns it as JSON. This fits children that end through a custom endTurn + structuredResult tool performing domain validation beyond the schema (for example reference truthfulness), with the contract declared by the parent alone.

Behavior change in 0.21.0: where the child declares an outputSchema and the parent does not, the ask tool result changed from the fixed text "Structured output submitted." to the validated JSON. That is the intended fix and ships in a minor under 0.x.

Structured result passthrough without a schema

Section titled “Structured result passthrough without a schema”

Requires 0.23.0 or later.

When neither side declares an outputSchema and the child ends with a structuredResult (via a custom endTurn tool), the ask tool result is that payload’s JSON as-is — unvalidated — instead of falling back to the text content and dropping it. The trust level is the same as the text result; the schema’s job is validation only, not gating the structured channel. This applies on both the direct path and the team runtime delegate path.

Behavior change in 0.23.0: existing code where the child ends with endTurn + structuredResult and the parent declares no schema now receives the structured JSON from ask instead of the text content.

Requires 0.21.0 or later.

AgentToolOptions.inputSchema replaces the default {mode, task, expectedOutput, acceptanceCriteria, workspaceGrants} input shape with your own schema, and mapInput projects the validated input into the child prompt:

const judge = createAgent({
model: "deepseek-v4-flash",
name: "judge",
systemPrompt: "You judge a case and submit a structured verdict.",
outputSchema: verdictSchema,
});
const judgeTool = agentTool("judge", judge, {
description: "Judge a case from its summary and documents.",
inputSchema: z.object({
caseSummary: z.string(),
documents: z.array(z.object({ title: z.string(), content: z.string() })),
}),
mapInput: input => renderJudgeTask(input.caseSummary, input.documents),
});

The parent’s arguments are parsed against inputSchema before anything runs; invalid input is rejected as an error tool_result in the supervisor’s loop — the same semantics as a plain tool() call — and the child is never invoked. inputSchema and mapInput must come as a pair: agentTool() throws at assembly time when one is missing. Typed delegation is ask-only — there is no mode field and no workspaceGrants. mapInput may return a string or ContentBlock[], but ContentBlock[] is only supported for direct ask calls; inside a team runtime the projected prompt must be a string, or the call fails at runtime.

Each child agent has its own workspace. Ask child agents to write durable reports, logs, generated files, or code under their workspace and mention important paths in their final text.

workspace is optional. When omitted, the SDK assigns a default workspace keyed by the agent’s name at ~/.agent/workspaces/<name>/ (agent-<sessionId> if there’s no name) and creates it on first run. The default is fine for most cases — it’s already isolated per agent and lives outside your repo, so it won’t clutter the project. Pass workspace explicitly only when you want artifacts in a specific location (e.g. a directory inside the repo), as below:

const reviewer = createAgent({
model: "deepseek-v4-flash",
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
systemPrompt: "Review the patch and write notes under your workspace.",
workspace: ".agent-workspaces/reviewer",
});

In this pattern the child’s deliverable is just a natural-language tool_result (what it did, where artifacts live under its workspace, how to verify) — no durable state, message queue, or structured-event protocol.

Use a mailbox team when the work needs to stay in a team: durable status, named member inboxes, replies, follow-ups, persistence, or nested teams. In one line: supervisor delegation is “call this helper now and get a result”; a mailbox team is “put the task in the mailbox and let members complete it through the message flow,” emitting team_message / team_agent structured events for tracing and UI along the way.