Files
LLeMbas-CLI/harness/team.md
T
HomerandClaude Opus 5.5 880289970b Harness spec 3.2.0: teams — subagents that work together
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>
2026-11-09 08:17:48 +00:00

15 KiB
Raw Permalink Blame History

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 min is 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, or all), 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. cap is the instance's (LLeMbas) or the global configuration's (the CLI), default loop.json team.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 as max = min(max, cap), min = min(min, max).
  • Defaults for a new chat: min 1, max 4 (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:

  1. 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.
  2. The chat's model serves at least two requests at once: capacity.parallel ≥ 2, or neither parallel nor single_session is 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: task answers one_agent too (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 least min − 1 members.
  • 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_add is refused past max — when the size would become more than max, with team.texts.full. So is task, while a team is offered: with full, or with one_agent when max is 1 and the lead has no member. A job_send that would start a finished background task helper again is refused the same way.
  • Lowering max stops nobody. Members already there stay; nothing new is added until the size is under the new max.
  • The size is across the whole tree: a member that leads adds within the same max as the lead.

4. Members

  • team_add starts the member at once, in the background, under the caller: the existing job machinery (a hidden chat on LLeMbas with its chat_jobs row, a subagent job in the CLI), running unattended — no ask_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) or explore (read-only); model is as task's — a model the person may use, the default being the caller's.
  • Its tools are those its agent gives a subagent, plus team_send and team_read; one added with lead: true also has team_add and team_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.md in place of blocks/subagent.md — its name, role, manager, the team's goal, the agent's own instructions, and for one with lead: true the whole of blocks/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 goal the caller gave in team_add, else the caller's own goal. The lead's own goal is the last goal it gave; the lead's first team_add must 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 name all); bad_name (not team.names.pattern); taken (the name of an agent on the team, lead included); no_goal; full. Then the member is added: added, or added_waiting when it has to wait for a slot (§8). An unknown agent or model answers as task does.
  • Members are not jobs to the model. job_list does not show them and the job_* 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 is working, waiting or idle.
  • A member's background commands stop when its turn ends (loop.json jobs.lifecycle). Deleting the lead's session stops the whole team.

5. The room: who receives what

  • team_send posts one message: to a list of names, or ["all"] (a lone string is read as a one-name list). Names are deduplicated, kept in the order given. all anywhere 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: all when 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, to as 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 with team_read, on demand, so nothing floods its context.
  • A report: a member's final reply, when its turn ends, is posted to its manager (report true) and delivered to it. An empty reply reads team.texts.no_report; a turn that ended otherwise than by the model stopping adds team.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_read answers the tree — every agent, the whole team, in tree order: name, role, its manager, state, model, last activity — and the room's messages numbered above since (default 0) that the caller may see, oldest first, at most team.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 bash call the default rules alone allow in manual mode (permission/defaults.json, the permission conformance 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}}:] — to as addressed: the names joined with , , or all; the same for every recipient.
  • report: [Team report from {{from}} to {{to}}:] — to the 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), with team_lead after it when min ≥ 2 (members = min − 1); when max is 1, team_one instead. 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.

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.