Files
LLeMbas-CLI/harness/subagents.md
T
HomerandClaude Opus 5.5 b66891e186
ci / check (push) Waiting to run
Harness spec 3.4.0: an agent chat's subagents are the device's; the tree's cases
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>
2026-11-10 08:19:17 +00:00

23 KiB
Raw Blame History

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. So coder-30b. at the end of a sentence names coder-30b, and coder-30b-instruct does not. Runs of white space in a display name match any run of white space.

  • The model parameter of task and of team_add takes 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, with subagents.texts.not_named:

    Use another model for subagents only when the person names it.

    The session's own model given as model is 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_add in 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:

  1. M is another model on S's one-model server (both have the same group): refused with subagents.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.

  2. M is S, and S serves one request at a time (parallel 1): refused with subagents.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:

  1. Subagents are enabled for the session (team.md §2, rule 1).
  2. 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 (no group, or another one) — whatever its own parallel.

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, all and parent reserved;
    • position — its designation, one line, given by the main agent (team_add's role: 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 by team_assign);
    • mode and request — 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.md meaning.

6. Only the main agent makes subagents

  • team_add, team_assign, team_replace, team_stop and team_decide are the main agent's. A subagent's call to any of them is refused, word for word, with team.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_add takes, beside 3.2.0's name, role (the position), task, goal, lead, agent, model: parent — the name of a sub-manager to place it under (default: the main agent; refused with team.texts.not_manager for a subagent that is not a sub-manager, and unknown for 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), then unknown and not_manager for parent, then the mode's (wider, request_not_narrower, request_kind), then the model's (§2–§3). parent and the reserved names are refused as taken.

  • 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 − 1 slots 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) and team_read lists it, it is not an agent and does not count toward the size, and each subagent added fills one. Open slots are max(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 and team_assign's mode, looser than the parent's, is refused, word for word, with team.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 classes read, write, execute) may be given only with a mode stricter than the parent's — otherwise refused with team.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 with team.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:

  1. 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.

  2. 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 with team.texts.requestable — it asks with team_request, the main agent decides with team_decide, and once allowed the same call runs. Otherwise refused with team.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.json unattended says. So in an all-Edit tree a subagent's bash reaches 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 message to names, all, or parent (3.4.0): the caller's parent (refused for the main agent with team.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 — as team.findings writes them. A name that has been used more than once means its latest holder; an id is exact. Otherwise refused with team.texts.not_yours_findings. The person reads every agent's (§12).
  • team_assign — name, task, and optionally role (a new position), parent (another sub-manager, or lead), mode (§7): a new task for a subagent that has not ended. Its task becomes this one; it is told under team.marks.assignment, and a finished one is woken. Refused: unknown, team.texts.ended for one that has ended, not_manager, wider; a move under its own subtree with team.texts.cycle.
  • team_replace — name, and optionally task: 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 with task (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:

  1. it is paused: nothing new reaches it — a message to it is posted in the room and not delivered; whoever replaces it reads it with team_read;
  2. it is given one final turn, at most team.context.final_steps steps (8), whose user message is team.texts.context_full, under its own mark: save what it found with team_findings, and tell its parent with team_send that it is gone and what is left;
  3. when that turn ends it is out_of_context, and its parent is sent team.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 of team.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.md in 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).