4. Run a team
Two members working in parallel, a lead that waits for both, and one log per thread.
Two questions come in at once: when did order 42 arrive, and how long is the return window? A subagent would answer them one after the other. A team answers them at the same time.
Give an agent a team and it becomes a lead. Its model can start members from the agents the team
lists, send them messages, ask them questions, wait for them and cancel them. Each member runs in
its own thread. When a member finishes, the lead wakes up with the result.
Two desks, one lead
The lead below starts both desks, calls wait until they are done, and answers. start, wait and
the other team tools are given to the lead automatically — you don't define them.
import { agent, scriptedModel, sqlite, tool } from "@threads/core";
import { z } from "zod";
const usage = { input_tokens: 10, output_tokens: 2 };
const say = (text: string) => ({
content: [{ type: "text", text }],
stop_reason: "end_turn",
usage,
});
const call = (id: string, name: string, input: unknown) => ({
content: [{ type: "tool_use", call_id: id, name, input }],
stop_reason: "tool_use",
usage,
});
const lookupOrder = tool({
name: "lookup_order",
description: "Look up an order by id.",
input: z.object({ id: z.string() }),
effect: "read_only",
execute: async ({ id }) => `order ${id}: delivered 12 days ago`,
});
const orderDesk = agent({
name: "order_desk",
instructions: "Look the order up and answer in one sentence.",
model: scriptedModel({
responses: [
call("o1", "lookup_order", { id: "42" }),
say("Order 42 was delivered 12 days ago."),
],
}),
tools: [lookupOrder],
});
const policyDesk = agent({
name: "policy_desk",
instructions: "Answer policy questions in one sentence.",
model: scriptedModel({
responses: [say("Returns are accepted within 30 days of delivery.")],
}),
});
const support = agent({
name: "support",
instructions: "Start the order desk and the policy desk, wait for both, then answer.",
model: scriptedModel({
responses: [
call("c1", "start", { agent: "order_desk", task: "When was order 42 delivered?" }),
call("c2", "start", { agent: "policy_desk", task: "How long is the return window?" }),
call("c3", "wait", { members: ["order_desk-1", "policy_desk-1"] }),
say("Order 42 arrived 12 days ago, inside the 30-day return window."),
say("Both desks replied: order 42 arrived 12 days ago, inside the 30-day return window."),
],
}),
team: [orderDesk, policyDesk],
});
const r = await support.run("Can I still return order 42?", { store: sqlite(":memory:") });
if (r.status === "completed") console.log(r.output);
else console.log(r.status);
for (const m of await r.team.members()) console.log(m.name, m.state);It prints:
Both desks replied: order 42 arrived 12 days ago, inside the 30-day return window.
order_desk-1 idle
policy_desk-1 idle
support idleThree things in that output are worth reading twice.
- The member names are
order_desk-1andpolicy_desk-1. A member's name is the agent's name plus a count, so one definition can be started several times. That is the name the lead passes towait,sendorcancel. - The lead answered twice.
Order 42 arrived…was its answer afterwaitreturned; the line that got printed is its last one, given after both members' results had reported in. Every answer before it is still in the lead's timeline. members()lists the lead too.supportis in the team alongside the desks it started.
The team handle
r.team is on every result, whatever the status. Your own code can drive the team the same way the
lead's model does — start a member, ask it something, wait for it:
const started = await r.team.start("order_desk", "Find every order from last week.");
if (started.status === "started") {
const answer = await r.team.ask(started.member, "Which one is oldest?", { timeoutMs: 60_000 });
if (answer.status === "answered") console.log(answer.text);
}Nothing here raises. A call that can't go ahead comes back as { status: "refused", code } — an
unknown member, a member that has ended, a full mailbox, a closed team — and the model reads the
same refusals as text it can act on.
What you have now
Four steps ago this was one agent repeating its instructions. It now has a tool, an approval rule, a subagent and a team, and every one of those steps wrote to an append-only log: the lead's thread, one thread per member, and a team log. You can replay any of them, fork any step, or turn a run you liked into a test.