Orchestrate
Subagents
Let an agent hand a focused task to a specialist agent with its own tools.
A subagent is a specialist that a lead agent hands one focused task to. It has its own tools, starts with a fresh context, and only its answer comes back to the lead. A tool on the lead calls it by key, like any other Actor.
import { pi } from "@rivet-dev/pi";
import { setup } from "rivetkit";
import { askSpecialist, engineering, orders } from "./specialists";
const support = pi({ model: "anthropic/claude-opus-5-5", customTools: [askSpecialist] });
export const registry = setup({ use: { support, orders, engineering } });
registry.start();
import { Type } from "@earendil-works/pi-ai";
import { defineTool } from "@earendil-works/pi-coding-agent";
import { pi } from "@rivet-dev/pi";
import { e2bProvider } from "@rivet-dev/sandbox-adapter/e2b";
import type { Registry } from "rivetkit";
import { createClient } from "rivetkit/client";
import { getOrder } from "../custom-tools/get-order";
export const orders = pi({ model: "openai/gpt-5.4-mini", customTools: [getOrder] });
export const engineering = pi({ model: "anthropic/claude-opus-5-5", sandbox: e2bProvider() });
export const askSpecialist = defineTool({
name: "ask_specialist",
label: "Ask a specialist",
description:
"Hand one focused question to a specialist and get its answer. Use orders for order status, refunds, and shipping. Use engineering for bugs that need someone to read or run the code.",
parameters: Type.Object({
specialist: Type.Union([Type.Literal("orders"), Type.Literal("engineering")]),
question: Type.String({ description: "Everything the specialist needs. It can't see this conversation." }),
}),
async execute(_toolCallId, { specialist, question }, _signal, _onUpdate, ctx) {
const agent = client[specialist].getOrCreate([ctx.sessionManager.getSessionId()]);
await agent.prompt(question);
const answer = (await agent.getLastAssistantText()) ?? "The specialist finished without an answer.";
return { content: [{ type: "text", text: answer }], details: { specialist } };
},
});
const client = createClient<Registry<{ orders: typeof orders; engineering: typeof engineering }>>();
import { createClient } from "rivetkit/client";
import type { registry } from "./server";
const client = createClient<typeof registry>();
const support = client.support.getOrCreate(["ticket-4821"]);
await support.prompt(
"Order 1042 shows as delivered but never arrived, and the tracking page throws a 500 error. Repo: https://github.com/acme/storefront",
);
console.log(await support.getLastAssistantText());
- The model picks the specialist and writes the question. The question has to carry everything the specialist needs, because the specialist can’t see the lead’s conversation.
- Each specialist has only the tools for its job:
ordershasget_order, andengineeringhas a sandbox to read and run code. The support agent has neither. - Specialists are keyed by the lead’s session id, so a follow-up question on the same ticket continues the same specialist session.
- The tool waits for the answer and returns it as the tool result. To hand work off without waiting, see Agent-to-Agent Messages.