team.md: a lead adds members (team_add) that work in the background on
the parts they own; members may lead areas of their own. The chat
setting agents {min, max} (defaults 1/4, an instance cap of 32) bounds
the whole tree, task helpers included; a team is offered only where
subagents are enabled and the model serves two requests at once
(capacity.parallel). A manager's work calls — risk write or execute,
bar job_stop and reading bash — answer a fixed refusal while the tool
list stays unchanged. The room: team_send to names or all, delivered
under "[Team message from X to Y:]" at the next step or waking a
finished member; a member's final reply is its report to its manager;
team_read shows the tree and the visible part of the room; team_stop
stops a subtree. Past the model's capacity a member waits for a slot,
the lead first. Size notes in context.note; texts in loop.json team;
prompts blocks/team_lead.md and team_member.md; tools allowed by
default. Conformance area team, written from a reference model.
The CLI embeds the new tool and prompt files; its tools are pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
15 KiB
Teams
Spec 3.2.0. Subagents that work together. A session's agent — the lead — may add members, background agents that each own a part of the work, and lead them as a manager: it plans, splits the work, coordinates and reviews, and the members do it. Members talk to each other and to their managers in the team's room. A member may itself lead an area with members of its own, so a team grows from a few agents side by side into a small company with one goal. The same in a chat on the server and in an agent session on a device, in both projects.
The tools are tools/team_add.json, team_send.json, team_read.json, team_stop.json; the texts
and numbers loop.json team (and the size notes in context.note); the prompt texts
prompts/blocks/team_lead.md and team_member.md; the cases conformance/team.json.
1. Words
- Agent: the lead or a member. Every agent has a name: the lead is
lead; a member the name it was added under. Names are unique in the team. - Member: an agent added with
team_add, by the lead or by a member that leads. Its manager is the agent that added it. The members an agent added, and theirs, down the tree, are under it; with the agent itself they are its subtree. - Manager: an agent with at least one member, and the lead from the start when the team's
minis 2 or more (§2). A manager does not do the work (§6). - Team: the lead and every member, one per lead session; its size is how many agents it has now (§3).
- Room: the team's messages, numbered from 1 in the order they were posted, each with
n,from(a name),to(as addressed: names, orall), its recipients (resolved when it was posted), the text, the time, and whether it is a report.
2. The size setting, and when a team is offered
A chat has the setting agents: {min, max} — the agents the team may have, the lead included.
1 ≤ min ≤ max ≤ cap.capis the instance's (LLeMbas) or the global configuration's (the CLI), defaultloop.jsonteam.agents.cap(32); a project's configuration never raises it. A value outside is refused where it is set, with what the bounds are, never clamped silently; a stored setting the cap was lowered under is read asmax = min(max, cap),min = min(min, max).- Defaults for a new chat:
min1,max4 (team.agents) — the lead and up to three members. LLeMbas keeps a per-person default for new chats, seeded with those; the CLI reads them from its configuration. - The setting is kept with the chat. A change during a session is told to the lead as a note on its
next user message (
context.md§10;context.note.team, below), never by changing the system prompt.
A team is offered — the setting shown, the team tools listed, blocks/team_lead.md in the
system prompt — when both hold, and only then:
- Subagents are enabled for the session: LLeMbas — the instance allows them and the chat's model has its subagent tool; the CLI — its subagents are not switched off in the configuration.
- The chat's model serves at least two requests at once:
capacity.parallel ≥ 2, or neitherparallelnorsingle_sessionis set (a hosted API;models/schema.json).
Both are fixed for as long as the system prompt is (context.md §10, rule 4: a model switch makes
a new prompt and a new list). Otherwise the setting is not shown and none of the four tools is
listed, and the session's task works as before.
While the tools are listed they need team (requires): met when max ≥ 2 and the model still
serves two at once — or, for the lead, when its team still has a member (so a lead whose person
lowered max to 1 can still talk to, read and stop the members it has). A call while it is not
met answers, word for word, team.unavailable.one_request when the model's capacity no longer
allows a team (its metadata changed after the prompt was made), else team.unavailable.one_agent
— which is also the tools' own unavailable text.
The meaning of the setting:
max = 1: one agent, no team, and no helper of any kind:taskanswersone_agenttoo (while the lead has no member; with members left from a larger setting,full).min ≥ 2: the lead works through a team from the start — it is a manager before its first member (§6), and the note asks for at leastmin − 1members.min = 1,max ≥ 2: the lead decides; a small job it may do alone. Once it has a member it is a manager.
3. Size, and what counts
The size of a team is: the lead, every member (whatever its state — a finished member can be
woken by a message, so it keeps its place), and every task helper running in the team, started by
any agent in it (the CLI's background task jobs and foreground ones alike, while they run).
A stopped member does not count: stopping is how room is made.
team_addis refused pastmax— when the size would become more thanmax, withteam.texts.full. So istask, while a team is offered: withfull, or withone_agentwhenmaxis 1 and the lead has no member. Ajob_sendthat would start a finished backgroundtaskhelper again is refused the same way.- Lowering
maxstops nobody. Members already there stay; nothing new is added until the size is under the newmax. - The size is across the whole tree: a member that leads adds within the same
maxas the lead.
4. Members
team_addstarts the member at once, in the background, under the caller: the existing job machinery (a hidden chat on LLeMbas with itschat_jobsrow, a subagent job in the CLI), running unattended — noask_user, the chat's own permission ceiling, its mode no looser than its manager's (permission/modes.json).- Its agent is
general(the default: it does the work) orexplore(read-only);modelis astask's — a model the person may use, the default being the caller's. - Its tools are those its agent gives a subagent, plus
team_sendandteam_read; one added withlead: truealso hasteam_addandteam_stop. They are fixed when it is made (§10 rule 4: a subagent's narrowing).report(offered: subagent) sends its manager a message before its final reply: posted in the room to the manager, delivered with the message mark. - Its system prompt is built once, when it is added: what a subagent of that agent gets, with
blocks/team_member.mdin place ofblocks/subagent.md— its name, role, manager, the team's goal, the agent's own instructions, and for one withlead: truethe whole ofblocks/team_lead.md. Its first user message is its brief (task), as given. - The team's goal is what a member is told the team is for: the
goalthe caller gave inteam_add, else the caller's own goal. The lead's own goal is the lastgoalit gave; the lead's firstteam_addmust give one (team.texts.no_goal). A member already working keeps the goal it was told; a new goal reaches it only as a message. team_add's refusals are checked in this order, the first that applies answering:unavailable(§2);all_name(the nameall);bad_name(notteam.names.pattern);taken(the name of an agent on the team,leadincluded);no_goal;full. Then the member is added:added, oradded_waitingwhen it has to wait for a slot (§8). An unknownagentormodelanswers astaskdoes.- Members are not jobs to the model.
job_listdoes not show them and thejob_*tools do not take them; the team tools do. (An implementation may run them on its job machinery.) - States:
working(a turn is running),waiting(it has work, and waits for a slot, §8),finished(its last turn ended; it can be woken). The lead isworking,waitingoridle. - A member's background commands stop when its turn ends (
loop.jsonjobs.lifecycle). Deleting the lead's session stops the whole team.
5. The room: who receives what
team_sendposts one message:toa list of names, or["all"](a lone string is read as a one-name list). Names are deduplicated, kept in the order given.allanywhere in the list means all, and the other names in it are ignored.- Recipients: for named agents, exactly those named; for
all, every agent of the team but the sender — the lead and every member, whoever manages them (the room is one channel for the whole company). Resolved when the message is posted: an agent added later does not get it. - Refused, and nothing posted, checked in this order:
allwhen nobody else is on the team (nobody); a name that is not on the team (unknown, the first such name in the order given); the sender's own name among them (self). The answer when posted:team.texts.sent,toas addressed. - What an agent receives is only what is addressed to it, by name or as
all, and — a manager — the reports of its own members. A manager is not sent its members' messages to each other; it reads them withteam_read, on demand, so nothing floods its context. - A report: a member's final reply, when its turn ends, is posted to its manager (
reporttrue) and delivered to it. An empty reply readsteam.texts.no_report; a turn that ended otherwise than by the model stopping addsteam.texts.ended(steps,budget,error — <text>), after a blank line. A sub-manager's final reply is likewise its report to its manager. - The person's messages go to the lead as its user messages, not into the room; the lead's
answers to the person are not room messages either. The person sees the whole room and the tree
in the UI (LLeMbas: the Team panel; the CLI:
/team), and can stop a member there — said to the lead as a note (context.note.team_stopped). team_readanswers the tree — every agent, the whole team, in tree order: name, role, its manager, state, model, last activity — and the room's messages numbered abovesince(default 0) that the caller may see, oldest first, at mostteam.read_max(50), and how to read on. Who may see a message: the lead every one; any other agent those whose sender or one of whose recipients is in its subtree — for a member that leads nobody, what it sent and what it received.
6. A manager does not do the work
A manager's work calls are refused, before the permission check — nothing is asked — with
team.texts.manager, word for word:
You lead a team: give this to a member (team_add or team_send).
Work is a call whose risk class (risk; an MCP tool's: read when it says readOnlyHint, else
execute) is write or execute, with two exceptions that stay a manager's:
job_stop— managing what runs is not work;- a
bashcall the default rules alone allow in manual mode (permission/defaults.json, thepermissionconformance area's evaluation with no rules of the person's): reading commands —git status,git diff,cat,ls,grep— for review.
So with the spec's tools: refused — write, edit, multiedit, apply_patch, mkdir, mv,
rm, skill_manage (write); bash beyond reading, python, commit, branch, pr, merge,
tag, release (execute); an MCP tool that does not say it only reads. Kept — every read
tool (read, glob, grep, list, search, recall, web_search, web_fetch, …), every
interact tool (ask_user for the lead, plan_*, task, memory, show, job_send, report,
the team tools, …) and job_stop. team.work lists the same, and a new tool is placed by its risk
class.
The tool list does not change when an agent becomes a manager (context.md §10, rule 4): the
same tools are listed and the refused ones answer the refusal. An agent is a manager while it has
a member — a finished one included; one whose members were all stopped is not, unless it is the
lead and min ≥ 2 with the team offered. task stays: a one-off helper is delegation, and counts
toward the size (§3).
7. Delivery, and waking
A message or a report reaches its recipient as a marked message — the jobs mechanism
(loop.json jobs): while the recipient's turn runs, as a user message at its next step boundary
(which does not start a turn: context.md §8); otherwise it wakes the recipient — a finished
member, or the idle lead — as a new turn of its own, marked machine, with its context intact, as
job_send restarts a finished helper. A woken agent needs a slot (§8). Several waiting at one step
boundary go in one user message, in room order, separated by a blank line.
Every one begins with its mark, on a line of its own, then the text:
[Team message from parser to lead:]
The grammar is done; the tests are next.
- message:
[Team message from {{from}} to {{to}}:]—toas addressed: the names joined with,, orall; the same for every recipient. - report:
[Team report from {{from}} to {{to}}:]—tothe manager.
A session that is not open keeps them until it is resumed, as a job's note.
8. Capacity: a member waits for a slot
A model serves parallel requests at once (models/schema.json). An agent holds a slot of its
model while its turn runs — the lead, a member, a task helper. An agent with work (added, woken,
given a message while finished, the lead given the person's message) starts when its model has a
free slot and none of its capacity.group's other models is busy; otherwise it is waiting.
team_add is never refused for capacity (only for size): its answer says the member waits.
Waiting agents start in order: the lead first (the person waits on it), then the others in the
order they began waiting. A slot frees when a turn ends or an agent is stopped. task keeps its own
rule (loop.json capacity: refused where its model is full), and its helper holds a slot.
9. Stopping
team_stop stops one member and its whole subtree, or with all every member under the caller:
their turns stop where they are, their background commands with them, messages waiting for them
are dropped, and they leave the team — the size drops, their slots free, their names may be
used again. No report comes back from a stopped member. Refused, in this order: all with nobody
under the caller (nobody_to_stop); a name not on the team (unknown); the caller itself or an
agent not under it (not_yours). The answer, stopped, names the stopped in tree order.
10. What the lead is told
blocks/team_lead.md— constant, no numbers — is in the system prompt whenever the team tools are listed: how to lead.- The size is in a note (
context.md§10,context.note): on the first user message of the session, and on the next one after the setting changed —team(min,max), withteam_leadafter it whenmin ≥ 2(members=min − 1); whenmaxis 1,team_oneinstead. One block. A member the person stopped is its own block,team_stopped, with the names. Members are told nothing of the size:team_add's answer says how big the team is.
11. Over the link
An agent chat's team runs on the device, as its jobs do. Spec 3.2.0 does not carry the room over
ACP yet: a device with the jobs capability reports each member as a subagent job (job events,
description being <name>: <role>), so the client's job strip shows them, and the room is shown
on the device (/team). Carrying the room is a later spec's.