acp.md *Subagents in an agent chat*: capability agents carries the chat's {min, max} (and
subagents: false) down on every turn and _lembas/configure, said (the person's earlier words)
on session/new; the tree, progress, approvals, findings and transcripts come up; what the server
keeps. subagents.md §14. team.json: ten 3.4.0 cases (only the main agent, sub-managers and parent,
slots, modes and request options, whoami, finish, findings, running out of context and replacing,
requests and decisions, assign); a member's own task is refused only_main. subagents.md: the
subagents under a sub-manager that ended by itself move under its parent until it is replaced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 KiB
Subagents
Spec 3.3.0; §5–§13 spec 3.4.0. Which model a subagent runs on, and when subagents are offered at all. A
subagent is any agent a session's agent starts to work for it: a task subagent (in the
foreground or as a background job) and a team's member (team.md) alike. The same rules in a chat
on the server and in an agent session on a device, in both projects. The texts are loop.json
subagents; the cases conformance/subagents.json.
1. The word
Subagent is the word in every text a person or a model reads: the UI (the web UI, Desktop,
Android, the CLI, the admin pages), the prompt texts and the tool texts. A team's members are
subagents too. The word helper is retired. Tool names, their former names (aliases) and
stored identifiers do not change.
2. The model a subagent runs on
The session's own model, always — unless the person has named another model in this
session. Nothing else chooses one: there is no setting for a subagent's model anywhere — not
in a chat, not in a person's or an instance's settings, not in the configuration, and an agent
definition's own model (the CLI's agent files) is not used for a subagent either.
-
Named means: in a message the person typed in this session, the model's served id, its id at its connection, or its display name occurs as a whole word, letters compared without case. Only the person's own words count — never the model's text, a tool's output, a file, a note, a job's or a team's marked message, or a subagent's report. A message that was compacted away still counts: what the person named stays named for the whole session.
-
A whole word: the character before the name is not a letter, a digit,
_,-,/,.or:; the character after it is not a letter, a digit,_,-or/, nor a.or:followed by a letter or a digit. Socoder-30b.at the end of a sentence namescoder-30b, andcoder-30b-instructdoes not. Runs of white space in a display name match any run of white space. -
The
modelparameter oftaskand ofteam_addtakes a model the person may use, by the same names. When it is not the session's model and the person has not named it, the call is refused, word for word, withsubagents.texts.not_named:Use another model for subagents only when the person names it.
The session's own model given as
modelis the default spelled out, and is never refused for not being named. -
A team's members are of the lead's session: what the person named there counts for every
team_addin the team, a sub-manager's included. A member's default is the session's model, not its manager's.
3. Capacity: the logical restrictions, and no others
What a model's server can do at once is models/schema.json capacity: parallel (single_session
being parallel: 1; neither said — no limit), and group, the server that holds one model at a
time. With S the session's model and M the subagent's, checked in this order, after
not_named:
-
M is another model on S's one-model server (both have the same
group): refused withsubagents.texts.same_group— starting it would unload the lead's model:That model is on the same one-model server as this chat's model; name a model on another server.
-
M is S, and S serves one request at a time (
parallel1): refused withsubagents.texts.one_request— the subagent would take the only slot from the lead:This model serves one request at a time: name another model, on another server, for subagents.
There is no fallback: nothing else is picked for the person, ever. A model the person named, on another server, is the way out — and a model that serves one request at a time is fine there.
These apply to task and to team_add alike, and before anything is started. Beyond them nothing
is refused for being the wrong model: a member past the free slots waits for one (team.md §8),
and task keeps its rule for a model that is already full (loop.json capacity).
4. When subagents are offered
The size setting agents {min, max} (team.md §2) is shown as Subagents — subagents.setting:
the label, and Min and Max for min and max (defaults 1 and 4, at most cap 32, kept
with the chat). The numbers count the session's own agent: Min 1 / Max 1 is the agent alone,
with no subagents; Max 4 is the agent and up to three subagents (subagents.setting.hint). A UI
shows the setting as one range from 1 to cap with two handles, Min and Max (Min never past
Max): a slider in a graphical UI, two numbers on one line in a terminal, each moved on its own. It, the team tools and blocks/team_lead.md are offered when both hold:
- Subagents are enabled for the session (
team.md§2, rule 1). - At least one model could serve as a subagent: S itself, when it serves two or more requests
at once (
parallel ≥ 2, or no limit); or any other model the person may use that is not on S's one-model server (nogroup, or another one) — whatever its ownparallel.
So a session on a one-request model is offered subagents when another server has a model, and
its first note says so (context.note.team_own, after team): the session's model cannot
serve subagents itself, and they need a model the person names. A UI showing the setting says the
same beside it (subagents.setting.own). Nothing is offered when neither holds — the same as
before 3.3.0 for a single model.
What counts as usable is each project's: LLeMbas — the models the person may use in the chat's picker; the CLI — the models of the server it is logged in to.
Subagents v2 — the main agent and its tree
Spec 3.4.0. How subagents are organised, what each may do, and how each ends. The owner's model:
one permanent main agent per session, which alone makes subagents and places them in a tree;
permissions are inherited and never widened; a subagent is temporary and ends with its task or its
context. Sections 5–13 supersede team.md where they say something different — who adds a
subagent (§6), who stops one (§10), the states (§9), what a member is told (§13) — and keep the
rest of it: the size setting and its counting (§2–§3), the room and who receives what (§5),
delivery and waking (§7), a manager's refusals (§6 there), capacity and waiting for a slot (§8).
The tool names team_* stay (nothing stored or trained breaks); task and report are unchanged
for a one-off subagent. The texts are loop.json team (3.4.0 additions marked there); the cases
conformance/team.json (the tree, the tools, the lifecycle) and conformance/delegation.json
(permissions).
5. Words
- The main agent is the session's own agent: the chat's agent on LLeMbas, the session's in the
CLI. It is permanent: it compacts and never ends. In the room and in every tool it is called
lead(the name is kept from 3.2.0); a person reads it as the main agent. - A subagent is an agent the main agent added (
team_add). It has:id—s1,s2, … in the order the session's subagents were added, never reused in the session (a replacement gets a new one);name— the main agent's choice (team.names.pattern), unique among the subagents that have not ended,lead,allandparentreserved;position— its designation, one line, given by the main agent (team_add'srole: the API's tests, reviews the parser);parent— the main agent, or a sub-manager the main agent placed it under;task— its brief, as given (and as replaced byteam_assign);modeandrequest— its permission mode and its request options (§7);model—subagents.md§2–§3, unchanged.
- A sub-manager is a subagent added with
lead: true. It directs the subagents the main agent places under it — they report to it, it messages them, it reviews their work — and it does not add, assign, replace or stop subagents: only the main agent does (§6). Like every manager it does not do the work itself (team.md§6, unchanged). - The tree: the main agent at the root, every subagent under its parent, in the order added.
Under an agent and its subtree keep their
team.mdmeaning.
6. Only the main agent makes subagents
-
team_add,team_assign,team_replace,team_stopandteam_decideare the main agent's. A subagent's call to any of them is refused, word for word, withteam.texts.only_main:Only the main agent adds, assigns, replaces and stops subagents: ask it (team_send to lead).
A subagent's tool list never has
task(a one-off subagent is the main agent's too), and a call to it by a former name is refused with the same text. -
team_addtakes, beside 3.2.0'sname,role(the position),task,goal,lead,agent,model:parent— the name of a sub-manager to place it under (default: the main agent; refused withteam.texts.not_managerfor a subagent that is not a sub-manager, andunknownfor no such name),mode— its mode (default: its parent's; §7),request— its request options (§7). Its refusals, in order:unavailable,only_main, then 3.2.0's (all_name,bad_name,taken,no_goal,full), thenunknownandnot_managerforparent, then the mode's (wider,request_not_narrower,request_kind), then the model's (§2–§3).parentand the reserved names are refused astaken. -
The person can tell the main agent to spawn — it is the main agent's call, as any — or set Min above 1: then
min − 1slots exist from the start of the main agent's reply (the first one after the setting was told,team.md§10). A slot is a place in the tree for a subagent not added yet: it is shown (§12) andteam_readlists it, it is not an agent and does not count toward the size, and each subagent added fills one. Open slots aremax(0, min − 1 − subagents that have not ended). The main agent is told to use them in its note (context.note.team_lead, which 3.4.0 words as slots), and is a manager from the start (team.md§6). -
Min/Max (3.3.0) are unchanged: they count the main agent; 1 is the main agent alone. The model rules (§2–§3) are unchanged.
7. Permissions: inherited, never widened
A subagent's mode is its parent's or stricter — permission/modes.json order, loosest first:
auto, edit, manual, plan. The main agent's mode is the session's.
-
team_add's andteam_assign'smode, looser than the parent's, is refused, word for word, withteam.texts.wider:A subagent's mode cannot be looser than its parent's ({{parent_mode}}).
When the parent's own mode becomes stricter during the session (the person switches the main agent to manual), every subagent looser than its parent is made as strict as it, down the tree, at once; a looser switch changes no subagent.
-
Request options (
request: a list of the risk classesread,write,execute) may be given only with a mode stricter than the parent's — otherwise refused withteam.texts.request_not_narrower("Request options go with a mode stricter than the parent's: without one there is nothing to ask for."); an unknown kind withteam.texts.request_kind. They let the subagent ask the main agent for a call of that class that its own mode does not allow; the main agent is the check.
Who decides a subagent's call. The hardline and the deny rules first, as for any call. Then:
-
The call is evaluated in the subagent's mode, with the session's rules and approvals and the subagent's own grants (below). Allow — it runs. Deny (a rule, the hardline, plan mode's refusal) — refused, as always.
-
Ask — the call is evaluated in the main agent's mode (the session's rules and approvals, no grants):
-
Allow — the main agent could allow it: the subagent was narrowed. If the call's risk class is in its
request, refused withteam.texts.requestable— it asks withteam_request, the main agent decides withteam_decide, and once allowed the same call runs. Otherwise refused withteam.texts.not_delegated:Your mode does not allow this, and you may not ask for it: tell your parent what you need.
-
Ask — nobody in the tree can allow it: it goes to the person, as an approval card that names the subagent (its name and position), the same card and the same answers as the main agent's own; unattended, refused as
modes.jsonunattendedsays. So in an all-Edit tree a subagent'sbashreaches the person — not the main agent, which could not allow it either. -
Deny — refused with that reason.
A sub-manager is never asked: it may not allow more than the main agent, and the main agent is the check.
-
team_request (a subagent with request options; its tool list has it only then): tool, the
permission pattern it needs (as a rule writes it: git push *, a path) and why. Refused with
team.texts.not_requestable when the tool's risk class is not in its options. Posted to the main
agent as a request, under its own mark (team.marks.request), the body team.texts.request_body,
with an id (r1, r2, … per session); the answer team.texts.requested says it waits for the main agent's decision.
team_decide (the main agent): id, allow (a boolean), note. Allowed — the subagent gets
a grant: the rule { permission: <tool's key>, pattern, action: allow } for itself alone, for
the rest of its life; a grant only ever applies where the main agent's own mode would allow the
call (step 2's evaluation, done again at the call), so it can never widen past the main agent.
Either way the subagent is told, under team.marks.decision: team.texts.decision_allowed or
decision_refused, then the note after a blank line. The answer to the main agent is
team.texts.decided (decision being allowed or refused). Refused:
team.texts.no_request for an id that is not an open request.
8. What each agent has
| Tools | Main agent | Sub-manager | Subagent |
|---|---|---|---|
team_whoami, team_read, team_send, team_findings |
✓ | ✓ | ✓ |
team_finish |
— | ✓ | ✓ |
team_request |
— | with request options | with request options |
team_add, team_assign, team_replace, team_stop, team_decide |
✓ | — | — |
Fixed when the prompt is made (the main agent's) or when the subagent is added (context.md §10,
rule 4). What each does:
team_whoami— who the caller is:team.whoami, one line each, in its order — id, name, position, parent, task, mode and request options, model, the limits (the size and Max, the steps a turn may take, whether it may add subagents) and its context use (tokens in its context now, of its model's window, and the percentage). For the main agent: the same, its position the main agent, without the parent and task lines.team_read— the roster: the tree (every agent and open slot, §12's fields) and the room, as in 3.2.0 (team.read; 3.4.0's row adds the id, the mode and the context use).team_send— a messagetonames,all, orparent(3.4.0): the caller's parent (refused for the main agent withteam.texts.no_parent). Otherwise 3.2.0.team_finish—report: the subagent's task is done. The report goes to its parent (the report mark, as a final reply's), and the subagent ends (§9): its turn stops after this call, it leaves the size, and its findings and transcript stay.team_findings—write(text): a finding kept for the caller, numbered from 1;read(of: a name or an id, default the caller): the findings of the caller, or of an agent in its subtree (the main agent: anybody's), ended ones included — asteam.findingswrites them. A name that has been used more than once means its latest holder; an id is exact. Otherwise refused withteam.texts.not_yours_findings. The person reads every agent's (§12).team_assign—name,task, and optionallyrole(a new position),parent(another sub-manager, orlead),mode(§7): a new task for a subagent that has not ended. Its task becomes this one; it is told underteam.marks.assignment, and a finished one is woken. Refused:unknown,team.texts.endedfor one that has ended,not_manager,wider; a move under its own subtree withteam.texts.cycle.team_replace—name, and optionallytask: the subagent ends (stopped, if it has not already), and a new one takes its place — the same name, position, parent, mode, request options and model, a new id — briefed withtask(default: the old task) followed by the old one's findings (team.texts.replace_brief). Its subagents move under the new one. Refused:unknown.team_stop— the main agent's only (3.2.0 let a manager stop its subtree); otherwise 3.2.0.team_decide— §7.
9. A subagent's life
States (team.read.states): working (a turn runs), waiting (it has work and waits for
a slot, team.md §8), finished (its last turn ended with a final reply, which was its report;
a message wakes it — 3.2.0), paused (its context is full; its last turn runs), and the ends:
done (team_finish), out_of_context (its last turn after paused ended), stopped
(the main agent's team_stop, a replacement, or the person). The main agent is working,
waiting or idle.
An ended subagent — done, out_of_context, stopped — leaves the size and its slot frees;
its name may be used again; it keeps its id, and the tree keeps it (§12) with its findings and
transcript for the rest of the session. Messages to it by name are refused as unknown.
The subagents under a sub-manager that ended by itself (done, out_of_context) move under its
parent until the main agent replaces it, and then back under the replacement; a stopped one's
subtree is stopped with it (§10).
When its context fills. A subagent does not compact. When its context reaches
team.context.pause_at of its model's window (default 0.85, the fraction compaction would start
at), after the step that took it there:
- it is
paused: nothing new reaches it — a message to it is posted in the room and not delivered; whoever replaces it reads it withteam_read; - it is given one final turn, at most
team.context.final_stepssteps (8), whose user message isteam.texts.context_full, under its own mark: save what it found withteam_findings, and tell its parent withteam_sendthat it is gone and what is left; - when that turn ends it is
out_of_context, and its parent is sentteam.texts.gone— then, after a blank line, that turn's final reply if it gave one — as its report (the report mark): the name, that it ran out of context, and how to read its findings. The parent decides — the main agent replaces it (team_replace) or uses the findings; a sub-manager tells the main agent.
The main agent is permanent: it compacts as every session does (context.md §7) and never
ends; deleting the session ends every subagent.
Waiting for subagents. The main agent's turn may end while subagents work; their reports wake
it (3.2.0 §7). Its progress is waiting_for_subagents (progress.md) while its turn waits on
them — a foreground task — and the session shows the subagents' own phases in the tree.
10. Stopping
Only the main agent stops (team_stop, 3.2.0's refusals but not_yours, which no longer arises),
and the person (§12) — said to the main agent as a note (context.note.team_stopped). A
stopped subagent's subtree is stopped with it, unless the main agent moved it first. A sub-manager
that wants a subagent of its own stopped asks the main agent.
11. Transcripts and findings
Every subagent's conversation is kept — its brief, what it wrote, every tool call with its output — for as long as the session: viewable read-only by the person, live while it runs and after it ended. Nobody types into it; the person speaks to a subagent only through the main agent (or by stopping it). Findings (§8) are kept the same way, readable by its parent, the main agent, and the person.
12. What clients show
The tree — the main agent, every subagent under its parent, open slots, and ended subagents (after the live ones, dimmed, with how each ended):
| Field | Shown as |
|---|---|
name, id, position |
parser (s2) — reviews the parser |
state |
team.read.states, its word |
model |
display name |
mode |
the mode's name, and request options as + write etc. |
context |
{used, window} → 42% (a bar where there is room) |
progress |
its current phase line (progress.md §3) |
| messages | the room's messages to and from it: how many unread by the person, the last one's first line |
- Web UI, Desktop and the CLI show the whole tree, the room, each subagent's findings and its
context (the CLI:
/context <name>), and open any subagent's transcript read-only, live and after it ended; the person can stop any subagent there. - Android shows a reduced list — name, position, state, progress line, context % — and opens a transcript read-only.
The data, the same in /api/v2 (LLeMbas: a chat's stream carries the tree as it changes) and on
the link (acp.md, capability tree):
{ "min": 1, "max": 4, "size": 2, "open_slots": 0,
"agents": [
{ "id": "lead", "name": "lead", "position": "the main agent", "parent": null, "state": "working",
"model": "qwen-27b", "mode": "edit", "request": [], "manager": true,
"context": { "used": 41200, "window": 131072 }, "progress": { "phase": "thinking", "since": 1762790400000 } },
{ "id": "s1", "name": "parser", "position": "reviews the parser", "parent": "lead", "state": "working",
"model": "qwen-27b", "mode": "manual", "request": ["write"], "manager": false, "task": "…",
"context": { "used": 9100, "window": 131072 }, "progress": { "phase": "running_tool", "since": 1762790401000, "tool": "read", "what": "src/parse.ts" },
"findings": 2, "messages": 3 }
],
"ended": [ { "id": "s0", "name": "scout", "position": "…", "parent": "lead", "state": "done", "ended": 1762790300000, "findings": 1 } ] }
13. What each is told
- The main agent:
blocks/team_lead.md(3.4.0: it alone adds; slots; positions; modes and request options; replacing a subagent that ran out of context) and the notes ofteam.md§10. - A subagent:
blocks/team_member.md(3.4.0: its id and position,team_whoami,team_findings,team_finish, that its context is limited and it will be told when it fills); a sub-manager,blocks/team_submanager.mdin place of the whole lead block it got in 3.2.0.
14. In an agent chat
The main agent of an agent chat is the CLI on the linked device: it starts and runs the
subagents there, under every rule above. The server carries the chat's Subagents setting down with
every turn and the tree, approvals, findings and transcripts up, and offers no subagents of its own
to an agent chat. The protocol: acp.md, Subagents in an agent chat (capabilities agents and
tree).