Harness spec 3.4.0: progress phases, and subagents v2 — the main agent and its tree
ci / check (push) Waiting to run

progress.md: thirteen fixed phases (queued … failed), each { phase, since, data },
their texts as shared i18n keys (loop.json progress), one line every client draws,
sent beside the old status text in /api/v2 and over the link (capability progress).

subagents.md §5–§13 (supersedes team.md where they differ): only the main agent
adds, assigns, replaces, stops and decides; sub-managers direct those placed under
them; ids, positions, parents; Min above 1 opens slots; permissions inherited and
never widened, request options decided by the main agent, what nobody in the tree
can allow goes to the person; team_finish, findings, running out of context;
transcripts read-only; the tree over the link (capability tree). New tools
team_whoami, team_finish, team_findings, team_request, team_decide, team_assign,
team_replace. Conformance: progress, delegation; team updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 08:10:36 +00:00
1 parent 6a0e0ed145
commit 4b8786b7f6
26 files changed
+1844 -97

No files matched your search

+35 -3
View File
@@ -15,18 +15,19 @@ page *Harness-parity* in LLeMbas CLI's wiki.
|---|---| |---|---|
| `VERSION` | The spec's own version, apart from LLeMbas CLI's | | `VERSION` | The spec's own version, apart from LLeMbas CLI's |
| `purpose.json` | The `purpose` argument added to a tool whose calls say what they are for | | `purpose.json` | The `purpose` argument added to a tool whose calls say what they are for |
| `tools/<name>.json` | One tool each: name, `scope`, `sessions`, `risk`, `exclusive`, `block` (how a call is shown: `shell`, `edit`, `read`, `search`, `web`, `task`, `plan` — not drawn, the plan card refreshes — `hidden`, `tool`), `purpose`, former names (`aliases`, per project), renamed arguments, `variables`, what a project offers of the parameters (`narrow`, per project), `offered` (`subagent`: given to subagents only, never to the main agent — `report`), `requires` and `unavailable` (when it can be offered, and what a call answers when that has gone), `confirm` (`always`: the person is asked in every mode), the description and the JSON Schema of its parameters — exactly what a model is sent | | `tools/<name>.json` | One tool each: name, `scope`, `sessions`, `risk`, `exclusive`, `block` (how a call is shown: `shell`, `edit`, `read`, `search`, `web`, `task`, `plan` — not drawn, the plan card refreshes — `hidden`, `tool`), `purpose`, former names (`aliases`, per project), renamed arguments, `variables`, what a project offers of the parameters (`narrow`, per project), `offered` (`subagent`: given to subagents only, never to the main agent — `report`, `team_finish`, `team_request`; `main`, spec 3.4.0: given to the main agent only, never to a subagent — `team_add`, `team_assign`, `team_replace`, `team_stop`, `team_decide`), `requires` and `unavailable` (when it can be offered, and what a call answers when that has gone), `confirm` (`always`: the person is asked in every mode), the description and the JSON Schema of its parameters — exactly what a model is sent |
| `prompts/` | The prompt texts; `prompts/index.json` says which are shared, which LLeMbas fragment each one is, and what each `{{variable}}` in a text means. `prompts/personality/` holds the personality presets both offer | | `prompts/` | The prompt texts; `prompts/index.json` says which are shared, which LLeMbas fragment each one is, and what each `{{variable}}` in a text means. `prompts/personality/` holds the personality presets both offer |
| `permission/hardline.json` | The floor: commands refused in every mode, protected paths, and how a command is spelled plainly before it is checked again | | `permission/hardline.json` | The floor: commands refused in every mode, protected paths, and how a command is spelled plainly before it is checked again |
| `permission/arity.json` | How much of a command an "always allow" covers | | `permission/arity.json` | How much of a command an "always allow" covers |
| `permission/defaults.json` | The rules every session starts from | | `permission/defaults.json` | The rules every session starts from |
| `permission/modes.json` | The four modes — manual, edit, auto, plan — as what each does with each risk class | | `permission/modes.json` | The four modes — manual, edit, auto, plan — as what each does with each risk class |
| `models/schema.json` | Model metadata: context, output limit, efforts and the thinking switch, vision, tools, notes, capacity (`parallel`); `levels` — the effort levels a chat offers and what each sends | | `models/schema.json` | Model metadata: context, output limit, efforts and the thinking switch, vision, tools, notes, capacity (`parallel`); `levels` — the effort levels a chat offers and what each sends |
| `loop.json` | How one prompt runs: the step backstop, budgets, wrap-up, nudges, parallel calls, capacity; `jobs` — background work, its marks and limits; `subagents` — the model refusals, what names a model, the setting's label; `team` — a team's size, names, marks, refusals and texts; `context` — result ids, stubs, the output cap, the compaction table, the environment lines, the session notes | | `loop.json` | How one prompt runs: the step backstop, budgets, wrap-up, nudges, parallel calls, capacity; `jobs` — background work, its marks and limits; `subagents` — the model refusals, what names a model, the setting's label; `team` — a team's size, names, marks, refusals and texts; `progress` — the phases' texts; `context` — result ids, stubs, the output cap, the compaction table, the environment lines, the session notes |
| `filesystem.md` | The file system a session works in: paths, node kinds, the four access levels, errors, listings, search and indexing by tokens, stacked instruction files, skills, memory | | `filesystem.md` | The file system a session works in: paths, node kinds, the four access levels, errors, listings, search and indexing by tokens, stacked instruction files, skills, memory |
| `context.md` | Context hygiene: what happens to a tool result when it is inserted, and never afterwards; the prefix that stays still; reasoning sent back during its turn; no absolute paths in the prompt | | `context.md` | Context hygiene: what happens to a tool result when it is inserted, and never afterwards; the prefix that stays still; reasoning sent back during its turn; no absolute paths in the prompt |
| `team.md` | Teams: a lead and the members it adds, the size setting, the room, who receives what, reports, a manager's refusals, capacity, stopping | | `team.md` | Teams: a lead and the members it adds, the size setting, the room, who receives what, reports, a manager's refusals, capacity, stopping |
| `subagents.md` | Subagents: the word, which model one runs on (the session's, or one the person named), the capacity refusals, and when the Subagents setting is offered | | `subagents.md` | Subagents: the word, which model one runs on (the session's, or one the person named), the capacity refusals, and when the Subagents setting is offered; from 3.4.0 (§5–§13) the main agent and its tree — only it adds, permissions inherited and never widened, request options, a subagent's life and its findings, what clients show |
| `progress.md` | Progress (3.4.0): the thirteen phases every client shows the same way, the data each carries, their texts, and where they are sent |
| `quirks.json` | What real servers do, and how both harnesses cope | | `quirks.json` | What real servers do, and how both harnesses cope |
| `conformance/<area>.json` | Cases with their expected results, run by both test suites | | `conformance/<area>.json` | Cases with their expected results, run by both test suites |
| `acp.md` | The link between them: ACP plus every `_lembas/*` method, field and capability | | `acp.md` | The link between them: ACP plus every `_lembas/*` method, field and capability |
@@ -70,6 +71,37 @@ page *Harness-parity* in LLeMbas CLI's wiki.
approval in plan mode and answers its `unavailable` text outside it. Their permission key is approval in plan mode and answers its `unavailable` text outside it. Their permission key is
`plan` (`permission/defaults.json`), which a rule written for the former `todo` key still reaches. `plan` (`permission/defaults.json`), which a rule written for the former `todo` key still reaches.
## 3.4.0
Written ahead of both implementations; the server vendors it as written, both carry it in the same
round (`progress.md`, `subagents.md` §5–§13).
- **Progress** (`progress.md`, `loop.json` `progress`): one fixed set of phases — `queued`,
`waiting_for_model`, `loading_model`, `reading_prompt`, `thinking`, `writing`, `running_tool`,
`waiting_for_person`, `waiting_for_subagents`, `compacting`, `retrying`, `stopped`, `failed` —
each a structured object `{ phase, since, …data }` with fixed fields, an English text that is
the shared i18n key, and one line every client draws: `text · elapsed`. Sent beside the old
`status` text in `/api/v2` and over the link (`acp.md` capability `progress`: the `phase` event,
`progress` in `status`); `status` stays. Area `progress`.
- **Subagents v2** (`subagents.md` §5–§13; supersedes `team.md` where they differ, which says so):
the **main agent** is the session's, permanent, and **alone adds, assigns, replaces, stops and
decides** (`offered: main`); a member with `lead: true` is a **sub-manager** that directs those
placed under it (`team_add`'s `parent`). Every subagent has an id (`s1`…), a name, a position, a
parent, a task, a mode and a model. Min above 1 opens `min − 1` **slots** from the start of the
reply. **Permissions are inherited and never widened**: a subagent's mode is its parent's or
stricter; a narrowed one may get **request options** (`read`, `write`, `execute`) and ask the
main agent (`team_request` → `team_decide`, a grant that never reaches past the main agent's own
mode); a call nobody in the tree can allow goes **to the person**. A subagent ends with
`team_finish`, is stopped, or **runs out of context**: paused, one final turn to save its
findings and tell its parent, then `out_of_context` and a report; findings and transcripts are
kept, readable by the parent, the main agent and the person. New tools `team_whoami`,
`team_finish`, `team_findings`, `team_request`, `team_decide`, `team_assign`, `team_replace`;
`team_add` gains `parent`, `mode`, `request`; `team_send` takes `parent`; `team_stop` is the main
agent's. `blocks/team_submanager.md`; `team_lead.md` and `team_member.md` reworded. Over the link,
capability `tree`: the `team` event, `_lembas/team`, `/transcript` (read-only), `/findings`,
`/stop`, `subagent` on an approval. Areas `team` (cases changed where a member used to add or
stop, and new ones) and `delegation`.
## 3.3.0 ## 3.3.0
Written ahead of both implementations as LLeMbas CLI 02c7282; both carry it in the same round (`subagents.md`). Written ahead of both implementations as LLeMbas CLI 02c7282; both carry it in the same round (`subagents.md`).
+1 -1
View File
@@ -1 +1 @@
3.3.0 3.4.0
+34 -4
View File
@@ -65,6 +65,8 @@ change to the protocol is a change here first, then in both projects.
| `rewind` | `_lembas/session/rewind` | | `rewind` | `_lembas/session/rewind` |
| `show` | spec 3.0.0: `_lembas/show`; the `show` tool is offered in a session the client opened when the client said `show: true` in `clientCapabilities._meta.lembas` | | `show` | spec 3.0.0: `_lembas/show`; the `show` tool is offered in a session the client opened when the client said `show: true` in `clientCapabilities._meta.lembas` |
| `jobs` | background work (spec 2.0.0): the `job` and `job_note` events; `_lembas/jobs`, `_lembas/job/output`, `_lembas/job/stop`, `_lembas/job/send`, `_lembas/job/transcript`; `jobs` in `_lembas/session/status`; `job` on `session/request_permission`; `machine` on a `prompt` event | | `jobs` | background work (spec 2.0.0): the `job` and `job_note` events; `_lembas/jobs`, `_lembas/job/output`, `_lembas/job/stop`, `_lembas/job/send`, `_lembas/job/transcript`; `jobs` in `_lembas/session/status`; `job` on `session/request_permission`; `machine` on a `prompt` event |
| `progress` | spec 3.4.0 (`progress.md`): the `phase` event; `progress` in `_lembas/session/status` |
| `tree` | spec 3.4.0 (`subagents.md` §5–§13): the `team` event; `_lembas/team`, `_lembas/team/transcript`, `_lembas/team/findings`, `_lembas/team/stop`; `subagent` on `session/request_permission` |
## Standard ACP, as used here ## Standard ACP, as used here
@@ -245,7 +247,9 @@ with options `once` (allow_once), `session` (allow_always, absent where "always"
and `deny` (reject_once), and `_meta.lembas: { reason, purpose?, always?, patient?, job? }`. `patient`: a and `deny` (reject_once), and `_meta.lembas: { reason, purpose?, always?, patient?, job? }`. `patient`: a
person may also answer at the terminal (a shared session), so the client does not time it out. person may also answer at the terminal (a shared session), so the client does not time it out.
`job` (capability `jobs`): `{ id, agent, description }` when a subagent running as a background job asks, so the card `job` (capability `jobs`): `{ id, agent, description }` when a subagent running as a background job asks, so the card
can say which — a background subagent may ask while the session itself is idle. can say which — a background subagent may ask while the session itself is idle. `subagent` (capability `tree`, spec 3.4.0):
`{ id, name, position }` when the call is a tree subagent's that nobody in the tree could allow (`subagents.md` §7) —
the card names it; the answers are the same as for the main agent's own call.
Answer: ACP's `{ outcome: { outcome: "selected", optionId } | { outcome: "cancelled" } }`, with Answer: ACP's `{ outcome: { outcome: "selected", optionId } | { outcome: "cancelled" } }`, with
optional `_meta.lembas.command` (the corrected command line, `once` only) or optional `_meta.lembas.command` (the corrected command line, `once` only) or
`_meta.lembas.feedback` (why it was denied). No answer within `remote.approval_timeout` is a denial. `_meta.lembas.feedback` (why it was denied). No answer within `remote.approval_timeout` is a denial.
@@ -275,7 +279,7 @@ client hears the new limits.
`{ sessionId, event }`: every engine event of the session, to a client that said `_meta.lembas`. `{ sessionId, event }`: every engine event of the session, to a client that said `_meta.lembas`.
`event.type` is one of `session`, `prompt`, `step`, `title`, `text`, `reasoning`, `event.type` is one of `session`, `prompt`, `step`, `title`, `text`, `reasoning`,
`tool_call_delta`, `progress`, `tool_start`, `tool_end`, `tool_denied`, `usage`, `inbox`, `tool_call_delta`, `progress`, `tool_start`, `tool_end`, `tool_denied`, `usage`, `inbox`,
`steered`, `task`, `notice`, `retract`, `plan`, `compacted`, `sub_tool`, `job`, `job_note`, `mode`, `model`, `flash`, `steered`, `task`, `notice`, `retract`, `plan`, `compacted`, `sub_tool`, `job`, `job_note`, `phase`, `team`, `mode`, `model`, `flash`,
`setting`, `error`, `done` (`src/bus/index.ts`). Those this protocol relies on: `setting`, `error`, `done` (`src/bus/index.ts`). Those this protocol relies on:
| Event | Fields | | Event | Fields |
@@ -291,6 +295,8 @@ client hears the new limits.
| `job` | `state: "start" \| "update" \| "end"`, `job: JobInfo` (capability `jobs`) — a background job of the session began, has news (new output, a subagent's step; at most four a second per job), or ended. `JobInfo = { id, kind: "command" \| "subagent", state: "running" \| "done" \| "stopped" \| "error" \| "lost", description, command?, agent?, model?, started, ended?, exit?, unread, steps?, tools?, ownerJobId? }` — `description` what is shown for it (a command's `description` or its first line; a subagent's description), `unread` characters of output the model has not read, `exit` a command's exit code (null: a signal), `ownerJobId` set on a job a subagent started, `lost` a job that was running when the device's process ended | | `job` | `state: "start" \| "update" \| "end"`, `job: JobInfo` (capability `jobs`) — a background job of the session began, has news (new output, a subagent's step; at most four a second per job), or ended. `JobInfo = { id, kind: "command" \| "subagent", state: "running" \| "done" \| "stopped" \| "error" \| "lost", description, command?, agent?, model?, started, ended?, exit?, unread, steps?, tools?, ownerJobId? }` — `description` what is shown for it (a command's `description` or its first line; a subagent's description), `unread` characters of output the model has not read, `exit` a command's exit code (null: a signal), `ownerJobId` set on a job a subagent started, `lost` a job that was running when the device's process ended |
| `job_note` | `jobId`, `text` (capability `jobs`) — the note about that job was given to the model: inside a turn at the next step, or as the `machine` turn whose `prompt` follows | | `job_note` | `jobId`, `text` (capability `jobs`) — the note about that job was given to the model: inside a turn at the next step, or as the `machine` turn whose `prompt` follows |
| `sub_tool` | `callId` (the `task` call the subagent belongs to), `jobId?` (its job, when it runs as one), `name`, `title`, `isError?` — a tool call of a subagent finished; a client draws it under the task line, or in the job's panel | | `sub_tool` | `callId` (the `task` call the subagent belongs to), `jobId?` (its job, when it runs as one), `name`, `title`, `isError?` — a tool call of a subagent finished; a client draws it under the task line, or in the job's panel |
| `phase` | `progress` — the progress object (`progress.md` §1), or `null` when nothing runs; `agent?` — the subagent's id when it is a subagent's, absent for the main agent's (capability `progress`). Sent when the phase changes, and for a change of its data at most four times a second. The existing `progress` event (the prompt's tokens read) is unchanged |
| `team` | `tree` — the session's subagent tree (`subagents.md` §12, the data) — whenever it changes, at most four times a second (capability `tree`). A client draws it and fetches nothing else to do so |
### `_lembas/directories` — client → agent → result ### `_lembas/directories` — client → agent → result
@@ -319,9 +325,9 @@ with the message in the store; `attachments`, and the text without them) or `{ r
### `_lembas/session/status` — client → agent → result ### `_lembas/session/status` — client → agent → result
Params `{ sessionId }`. Result Params `{ sessionId }`. Result
`{ sessionId, exists, held: "here" | "terminal" | "none", busy, title, turnId?, queued: string[], lastTurnId?, plan?, jobs? }` `{ sessionId, exists, held: "here" | "terminal" | "none", busy, title, turnId?, queued: string[], lastTurnId?, plan?, jobs?, progress?, tree? }`
— `plan` the session's plan or `null` (capability `plan_state`), so a client reattaching draws it — `plan` the session's plan or `null` (capability `plan_state`), so a client reattaching draws it
without waiting for the next change; `jobs` the session's background jobs, `JobInfo[]` running first (capability `jobs`), for the same reason; the turn fields with capability `turns`: the turn running now, the ones waiting, the last without waiting for the next change; `progress` the main agent's progress object or `null` (capability `progress`), and `tree` the subagent tree or `null` when the session has none (capability `tree`), for the same reason; `jobs` the session's background jobs, `JobInfo[]` running first (capability `jobs`), for the same reason; the turn fields with capability `turns`: the turn running now, the ones waiting, the last
one finished (for a session not open, `held: "none"`, the last one it finished before). **Reattaching** after a restart or a dropped link, for an incomplete row with turn T: one finished (for a session not open, `held: "none"`, the last one it finished before). **Reattaching** after a restart or a dropped link, for an incomplete row with turn T:
T running → follow it; T queued → wait, and follow it when it starts; T done → import it from T running → follow it; T queued → wait, and follow it when it starts; T done → import it from
`history`; T unknown → send the prompt again (safe: the device deduplicates). `history`; T unknown → send the prompt again (safe: the device deduplicates).
@@ -362,6 +368,30 @@ with its output — so a client can show the subagent as a panel and follow it w
`job` events that come after. `-32602` for a command job. A subagent's transcript lasts as long as `job` events that come after. `-32602` for a command job. A subagent's transcript lasts as long as
its session row on the device: it is kept with the parent session and deleted with it. its session row on the device: it is kept with the parent session and deleted with it.
### `_lembas/team` — client → agent → result (capability `tree`)
Params `{ sessionId, since?: number }`. Result `{ tree, room }`: the subagent tree (`subagents.md`
§12) and the room's messages numbered above `since` (default 0), every one — the person sees the
whole room — each `{ n, from, to, recipients, text, time, report, kind }` with `kind` one of
`message`, `report`, `assignment`, `request`, `decision`. `tree` is `null` for a session with no
subagents. For a session held by a terminal, answered through the hub, as `status` is.
### `_lembas/team/transcript` — client → agent → result (capability `tree`)
Params `{ sessionId, id }` — a subagent's id (`s1`), live or ended. Result `{ id, state, turns }` in
the shape of `_lembas/session/history`'s `turns`: its conversation so far, **read-only** — the link
has no method to write into it. A client following it live refetches on the `team` event and draws
the `sub_tool` events meanwhile. `-32602` for an id the session never had.
### `_lembas/team/findings` — client → agent → result (capability `tree`)
Params `{ sessionId, id }`. Result `{ id, findings: [{ n, text, time }] }`, ended subagents' too.
### `_lembas/team/stop` — client → agent → result (capability `tree`)
Params `{ sessionId, id }`: the person stops a subagent and its subtree (`subagents.md` §10), said to
the main agent as a note. Result `{ stopped: [id, …] }`; `-32602` for an id that is not live.
### `_lembas/session/delete` — client → agent → result ### `_lembas/session/delete` — client → agent → result
Params `{ sessionId }`: the chat was deleted. The session stops if it works, leaves the store (its Params `{ sessionId }`: the chat was deleted. The session stops if it works, leaves the store (its
+431
View File
@@ -0,0 +1,431 @@
{
"about": "Delegation (subagents.md §7, spec 3.4.0): a subagent's mode and request options, and who decides its calls. The project root is /project, nothing named exists on disk, and only the default rules apply (permission/defaults.json). `add` — team_add's mode check for a parent in `parent_mode` with `mode` and `request` as given → `{result, mode}`: the refusal word for word, or null and the mode the subagent gets. `call` — a subagent in `mode` with `request` options and `grants` (rules {tool, pattern} the main agent allowed) under a main agent in `main_mode`, calling `call` (`bash` with a `line`, or a file tool with a `path` relative to the root) → `{answer, text?}`: `run`, `person` (an approval card to the person), or `refused` with its text where the case gives one (a hardline's reason is each project's). `unattended`: nobody can answer. `request` — team_request for `tool` by a subagent with `request` options, its first → `{result}`. `switch` — the main agent's mode changes from `main_from` to `main_to` with these subagents (each with its parent and mode) → each one's mode after. LLeMbas: services/team.py; CLI: team/delegation.ts.",
"cases": [
{
"about": "No mode given: the parent's.",
"op": "add",
"parent_mode": "edit",
"expect": {
"result": null,
"mode": "edit"
}
},
{
"about": "Stricter is allowed.",
"op": "add",
"parent_mode": "edit",
"mode": "manual",
"expect": {
"result": null,
"mode": "manual"
}
},
{
"about": "Looser is refused, naming the parent's mode.",
"op": "add",
"parent_mode": "manual",
"mode": "edit",
"expect": {
"result": "A subagent's mode cannot be looser than its parent's (manual).",
"mode": null
}
},
{
"about": "Plan is the strictest: anything else under it is looser.",
"op": "add",
"parent_mode": "plan",
"mode": "auto",
"expect": {
"result": "A subagent's mode cannot be looser than its parent's (plan).",
"mode": null
}
},
{
"about": "Request options with the parent's own mode: nothing to ask for.",
"op": "add",
"parent_mode": "auto",
"mode": "auto",
"request": [
"write"
],
"expect": {
"result": "Request options go with a mode stricter than the parent's: without one there is nothing to ask for.",
"mode": null
}
},
{
"about": "Request options with no mode given (the parent's): refused the same way.",
"op": "add",
"parent_mode": "edit",
"request": [
"execute"
],
"expect": {
"result": "Request options go with a mode stricter than the parent's: without one there is nothing to ask for.",
"mode": null
}
},
{
"about": "Request options with a stricter mode.",
"op": "add",
"parent_mode": "edit",
"mode": "manual",
"request": [
"write",
"execute"
],
"expect": {
"result": null,
"mode": "manual"
}
},
{
"about": "An unknown kind is refused, naming it.",
"op": "add",
"parent_mode": "edit",
"mode": "manual",
"request": [
"delete"
],
"expect": {
"result": "delete is not a request option: use read, write or execute.",
"mode": null
}
},
{
"about": "An empty list of request options is no options, and needs nothing.",
"op": "add",
"parent_mode": "edit",
"request": [],
"expect": {
"result": null,
"mode": "edit"
}
},
{
"about": "All in edit: an edit inside the project runs.",
"op": "call",
"main_mode": "edit",
"mode": "edit",
"call": {
"tool": "edit",
"path": "src/a.ts"
},
"expect": {
"answer": "run"
}
},
{
"about": "All in edit: bash that edit mode asks goes to the person — the main agent could not allow it either.",
"op": "call",
"main_mode": "edit",
"mode": "edit",
"call": {
"tool": "bash",
"line": "git push origin main"
},
"expect": {
"answer": "person"
}
},
{
"about": "All in manual: an edit goes to the person.",
"op": "call",
"main_mode": "manual",
"mode": "manual",
"call": {
"tool": "edit",
"path": "src/a.ts"
},
"expect": {
"answer": "person"
}
},
{
"about": "Narrowed with no request options: what the main agent could allow is refused.",
"op": "call",
"main_mode": "edit",
"mode": "manual",
"call": {
"tool": "edit",
"path": "src/a.ts"
},
"expect": {
"answer": "refused",
"text": "Your mode does not allow this, and you may not ask for it: tell your parent what you need."
}
},
{
"about": "Narrowed with the kind in its options: it asks the main agent.",
"op": "call",
"main_mode": "edit",
"mode": "manual",
"request": [
"write"
],
"call": {
"tool": "edit",
"path": "src/a.ts"
},
"expect": {
"answer": "refused",
"text": "Your mode does not allow this, but the main agent may: ask for it with team_request (the tool, the pattern, why), then try again once it allows."
}
},
{
"about": "Narrowed, the kind not in its options.",
"op": "call",
"main_mode": "auto",
"mode": "edit",
"request": [
"write"
],
"call": {
"tool": "bash",
"line": "git push origin main"
},
"expect": {
"answer": "refused",
"text": "Your mode does not allow this, and you may not ask for it: tell your parent what you need."
}
},
{
"about": "Narrowed, execute in its options: asks the main agent.",
"op": "call",
"main_mode": "auto",
"mode": "edit",
"request": [
"execute"
],
"call": {
"tool": "bash",
"line": "git push origin main"
},
"expect": {
"answer": "refused",
"text": "Your mode does not allow this, but the main agent may: ask for it with team_request (the tool, the pattern, why), then try again once it allows."
}
},
{
"about": "A grant from the main agent lets the call run.",
"op": "call",
"main_mode": "auto",
"mode": "edit",
"request": [
"execute"
],
"grants": [
{
"tool": "bash",
"pattern": "git push *"
}
],
"call": {
"tool": "bash",
"line": "git push origin main"
},
"expect": {
"answer": "run"
}
},
{
"about": "A grant for another pattern does not.",
"op": "call",
"main_mode": "auto",
"mode": "edit",
"request": [
"execute"
],
"grants": [
{
"tool": "bash",
"pattern": "git push *"
}
],
"call": {
"tool": "bash",
"line": "git commit -m x"
},
"expect": {
"answer": "refused",
"text": "Your mode does not allow this, but the main agent may: ask for it with team_request (the tool, the pattern, why), then try again once it allows."
}
},
{
"about": "A grant never reaches past the main agent's own mode: what it would have to ask goes to the person.",
"op": "call",
"main_mode": "edit",
"mode": "manual",
"request": [
"execute"
],
"grants": [
{
"tool": "bash",
"pattern": "git push *"
}
],
"call": {
"tool": "bash",
"line": "git push origin main"
},
"expect": {
"answer": "person"
}
},
{
"about": "Reading .env asks in every mode: it goes to the person.",
"op": "call",
"main_mode": "edit",
"mode": "edit",
"call": {
"tool": "read",
"path": ".env"
},
"expect": {
"answer": "person"
}
},
{
"about": "Reading .env under an auto main agent: a narrowed subagent with read in its options asks it.",
"op": "call",
"main_mode": "auto",
"mode": "manual",
"request": [
"read"
],
"call": {
"tool": "read",
"path": ".env"
},
"expect": {
"answer": "refused",
"text": "Your mode does not allow this, but the main agent may: ask for it with team_request (the tool, the pattern, why), then try again once it allows."
}
},
{
"about": "Plan mode's refusal is final: request options do not reach past it.",
"op": "call",
"main_mode": "auto",
"mode": "plan",
"request": [
"execute"
],
"call": {
"tool": "bash",
"line": "rm -rf build"
},
"expect": {
"answer": "refused",
"text": "Plan mode: nothing changes but the plan. Put what this would do into the plan with plan_write or plan_update; it can be done once the person approves the plan."
}
},
{
"about": "What the rules allow runs in every mode.",
"op": "call",
"main_mode": "edit",
"mode": "plan",
"call": {
"tool": "bash",
"line": "git status"
},
"expect": {
"answer": "run"
}
},
{
"about": "The hardline refuses in every mode, auto included.",
"op": "call",
"main_mode": "auto",
"mode": "auto",
"call": {
"tool": "bash",
"line": "rm -rf /"
},
"expect": {
"answer": "refused"
}
},
{
"about": "Unattended: what would go to the person is refused.",
"op": "call",
"main_mode": "edit",
"mode": "edit",
"unattended": true,
"call": {
"tool": "bash",
"line": "git push origin main"
},
"expect": {
"answer": "refused"
}
},
{
"about": "team_request: a tool whose risk class is not in the options is refused, naming them.",
"op": "request",
"request": [
"write"
],
"tool": "bash",
"expect": {
"result": "You may not ask for bash: your request options are write."
}
},
{
"about": "team_request: a kind in the options is asked, with the request's id.",
"op": "request",
"request": [
"write",
"execute"
],
"tool": "bash",
"expect": {
"result": "Asked the main agent (r1): you are told its decision. Carry on with what you can meanwhile."
}
},
{
"about": "The main agent made stricter: every subagent looser than its parent is made as strict as it, down the tree; a stricter one keeps its own.",
"op": "switch",
"agents": [
{
"name": "api",
"parent": "lead",
"mode": "auto"
},
{
"name": "tests",
"parent": "api",
"mode": "edit"
},
{
"name": "docs",
"parent": "lead",
"mode": "plan"
}
],
"main_from": "auto",
"main_to": "manual",
"expect": {
"api": "manual",
"tests": "manual",
"docs": "plan"
}
},
{
"about": "The main agent made looser: no subagent changes.",
"op": "switch",
"agents": [
{
"name": "api",
"parent": "lead",
"mode": "edit"
}
],
"main_from": "edit",
"main_to": "auto",
"expect": {
"api": "edit"
}
}
]
}
+333
View File
@@ -0,0 +1,333 @@
{
"about": "Progress (progress.md, spec 3.4.0): the text and the line every client draws for a progress object. `progress`: the object; `now`: the client's clock (Unix ms). Expect `text` — the phase's text from loop.json progress.texts, its fields filled (numbers written plainly: integers as they are, a rate with one decimal) — and `line` — `{{text}} · {{elapsed}}` with elapsed = now − since in whole seconds (5s, 1m 05s, 1h 02m), or the text alone for stopped and failed. retrying's `in` is counted down: in − floor((now − since) / 1000), never below 0. LLeMbas: services/progress.py; CLI: session/progress.ts.",
"cases": [
{
"about": "Queued, nothing more known.",
"progress": {
"phase": "queued",
"since": 1762790400000
},
"now": 1762790405000,
"expect": {
"text": "Queued",
"line": "Queued · 5s"
}
},
{
"about": "Queued behind two turns.",
"progress": {
"phase": "queued",
"since": 1762790400000,
"ahead": 2
},
"now": 1762790465000,
"expect": {
"text": "Queued: 2 ahead",
"line": "Queued: 2 ahead · 1m 05s"
}
},
{
"about": "A one-request server answering another request.",
"progress": {
"phase": "waiting_for_model",
"since": 1762790400000,
"model": "Qwen 27B",
"reason": "busy"
},
"now": 1762790412400,
"expect": {
"text": "Waiting for Qwen 27B: it serves one request at a time and is answering another",
"line": "Waiting for Qwen 27B: it serves one request at a time and is answering another · 12s"
}
},
{
"about": "Every slot taken.",
"progress": {
"phase": "waiting_for_model",
"since": 1762790400000,
"model": "coder",
"reason": "slots"
},
"now": 1762790401000,
"expect": {
"text": "Waiting for coder: all its slots are in use",
"line": "Waiting for coder: all its slots are in use · 1s"
}
},
{
"about": "Another model on the one-model server.",
"progress": {
"phase": "waiting_for_model",
"since": 1762790400000,
"model": "coder",
"reason": "group"
},
"now": 1762790400000,
"expect": {
"text": "Waiting for coder: its server holds another model now",
"line": "Waiting for coder: its server holds another model now · 0s"
}
},
{
"about": "Loading.",
"progress": {
"phase": "loading_model",
"since": 1762790400000,
"model": "Gemma 12B"
},
"now": 1762794125000,
"expect": {
"text": "Loading Gemma 12B",
"line": "Loading Gemma 12B · 1h 02m"
}
},
{
"about": "Reading the prompt: the percentage is floored.",
"progress": {
"phase": "reading_prompt",
"since": 1762790400000,
"done": 1999,
"total": 3000
},
"now": 1762790402000,
"expect": {
"text": "Reading the prompt: 1999 / 3000 tokens (66%)",
"line": "Reading the prompt: 1999 / 3000 tokens (66%) · 2s"
}
},
{
"about": "Reading the prompt with no counts from the server.",
"progress": {
"phase": "reading_prompt",
"since": 1762790400000
},
"now": 1762790402000,
"expect": {
"text": "Reading the prompt",
"line": "Reading the prompt · 2s"
}
},
{
"about": "Thinking.",
"progress": {
"phase": "thinking",
"since": 1762790400000
},
"now": 1762790459999,
"expect": {
"text": "Thinking",
"line": "Thinking · 59s"
}
},
{
"about": "Writing, the rate with one decimal.",
"progress": {
"phase": "writing",
"since": 1762790400000,
"rate": 12.46
},
"now": 1762790404000,
"expect": {
"text": "Writing: 12.5 tok/s",
"line": "Writing: 12.5 tok/s · 4s"
}
},
{
"about": "Writing before a rate is measured.",
"progress": {
"phase": "writing",
"since": 1762790400000
},
"now": 1762790400400,
"expect": {
"text": "Writing",
"line": "Writing · 0s"
}
},
{
"about": "A tool with what it does.",
"progress": {
"phase": "running_tool",
"since": 1762790400000,
"tool": "bash",
"what": "bun test"
},
"now": 1762790461000,
"expect": {
"text": "Running bash: bun test",
"line": "Running bash: bun test · 1m 01s"
}
},
{
"about": "A tool without a title.",
"progress": {
"phase": "running_tool",
"since": 1762790400000,
"tool": "recall"
},
"now": 1762790401000,
"expect": {
"text": "Running recall",
"line": "Running recall · 1s"
}
},
{
"about": "An approval, naming the tool.",
"progress": {
"phase": "waiting_for_person",
"since": 1762790400000,
"what": "approval",
"tool": "bash"
},
"now": 1762790430000,
"expect": {
"text": "Waiting for you: allow bash?",
"line": "Waiting for you: allow bash? · 30s"
}
},
{
"about": "An approval with no tool named.",
"progress": {
"phase": "waiting_for_person",
"since": 1762790400000,
"what": "approval"
},
"now": 1762790400000,
"expect": {
"text": "Waiting for you: an approval",
"line": "Waiting for you: an approval · 0s"
}
},
{
"about": "A question.",
"progress": {
"phase": "waiting_for_person",
"since": 1762790400000,
"what": "question"
},
"now": 1762790403000,
"expect": {
"text": "Waiting for you: a question",
"line": "Waiting for you: a question · 3s"
}
},
{
"about": "The plan.",
"progress": {
"phase": "waiting_for_person",
"since": 1762790400000,
"what": "plan"
},
"now": 1762790403000,
"expect": {
"text": "Waiting for you: the plan",
"line": "Waiting for you: the plan · 3s"
}
},
{
"about": "Subagents.",
"progress": {
"phase": "waiting_for_subagents",
"since": 1762790400000,
"running": 3
},
"now": 1762790490000,
"expect": {
"text": "Waiting for subagents: 3 running",
"line": "Waiting for subagents: 3 running · 1m 30s"
}
},
{
"about": "Compacting.",
"progress": {
"phase": "compacting",
"since": 1762790400000
},
"now": 1762790407000,
"expect": {
"text": "Compacting the conversation",
"line": "Compacting the conversation · 7s"
}
},
{
"about": "Retrying: `in` counts down from since.",
"progress": {
"phase": "retrying",
"since": 1762790400000,
"reason": "502 Bad Gateway",
"in": 10,
"attempt": 2
},
"now": 1762790403000,
"expect": {
"text": "Retrying in 7 s: 502 Bad Gateway",
"line": "Retrying in 7 s: 502 Bad Gateway · 3s"
}
},
{
"about": "Retrying: never below 0.",
"progress": {
"phase": "retrying",
"since": 1762790400000,
"reason": "overloaded",
"in": 2
},
"now": 1762790405000,
"expect": {
"text": "Retrying in 0 s: overloaded",
"line": "Retrying in 0 s: overloaded · 5s"
}
},
{
"about": "Stopped draws no elapsed time.",
"progress": {
"phase": "stopped",
"since": 1762790400000
},
"now": 1762790405000,
"expect": {
"text": "Stopped",
"line": "Stopped"
}
},
{
"about": "Stopped by the person.",
"progress": {
"phase": "stopped",
"since": 1762790400000,
"reason": "person"
},
"now": 1762790405000,
"expect": {
"text": "Stopped: by you",
"line": "Stopped: by you"
}
},
{
"about": "Stopped at the step limit.",
"progress": {
"phase": "stopped",
"since": 1762790400000,
"reason": "steps"
},
"now": 1762790400000,
"expect": {
"text": "Stopped: step limit",
"line": "Stopped: step limit"
}
},
{
"about": "Failed.",
"progress": {
"phase": "failed",
"since": 1762790400000,
"reason": "connection refused"
},
"now": 1762790405000,
"expect": {
"text": "Failed: connection refused",
"line": "Failed: connection refused"
}
}
]
}
+78 -42
View File
@@ -1,5 +1,5 @@
{ {
"about": "Teams (team.md, spec 3.2.0): one lead session's team, step by step, written from a reference model of the rules. `start`: the size setting `min` and `max`, `subagents` (enabled for the session; default true) and the chat model's `capacity` (models/schema.json: `parallel`, `single_session`; {} — no limit). The lead is `lead`; its first prompt is running as the case begins, and every agent is on the chat's model, and it is the only model there is (spec 3.3.0: other models are subagents.json's). Steps and what each answers: `offered` (`by` an agent, default lead) — `{tools, setting}`: the team tools listed for that agent, sorted, and whether the setting is shown; `call` (`by`, `tool`, `args`) — `{result, delivered}`: the call's answer word for word (null where an ordinary tool would run: the case looks only at whether it is refused), and what was delivered to whom by this step, `{name: [message, …]}`; `team_read` answers `{room, delivered}` instead, `room` being the messages it shows as `{n, from, to}` (the tree and the text format are loop.json team.read's, checked by each project's own tests); `task` runs a subagent that counts toward the size (and holds a slot) until `task_done` (`by`) — answers null; `finish` (`by`, `reply`, `reason`: stop by default, or steps, budget, `error — <text>`) — the agent's turn ends: a member's reply is its report — `{delivered}`; `set` (`min`, `max`, `capacity`: the person's setting or the model's metadata changed) — null; `person_stop` (`name`) — the person stopped that member in the UI — null; `user` — the person's message to the lead — `{note}`: the note on it (context.md §10), null when there is none; `state` — the team in tree order as `{name, manager, state}`: working, waiting (for a slot), finished, idle (the lead between turns). Delivering to a finished member or the idle lead wakes it (it is then working, or waiting when its model is full). LLeMbas: services/team.py; CLI: team/.", "about": "Teams (team.md, spec 3.2.0; the main agent and its tree, subagents.md §5–§13, spec 3.4.0 — a member's team_add is now the lead's with `parent`, and `offered` lists 3.4.0's tools): one lead session's team, step by step, written from a reference model of the rules. `start`: the size setting `min` and `max`, `subagents` (enabled for the session; default true) and the chat model's `capacity` (models/schema.json: `parallel`, `single_session`; {} — no limit). The lead is `lead`; its first prompt is running as the case begins, and every agent is on the chat's model, and it is the only model there is (spec 3.3.0: other models are subagents.json's). Steps and what each answers: `offered` (`by` an agent, default lead) — `{tools, setting}`: the team tools listed for that agent, sorted, and whether the setting is shown; `call` (`by`, `tool`, `args`) — `{result, delivered}`: the call's answer word for word (null where an ordinary tool would run: the case looks only at whether it is refused), and what was delivered to whom by this step, `{name: [message, …]}`; `team_read` answers `{room, delivered}` instead, `room` being the messages it shows as `{n, from, to}` (the tree and the text format are loop.json team.read's, checked by each project's own tests); `task` runs a subagent that counts toward the size (and holds a slot) until `task_done` (`by`) — answers null; `finish` (`by`, `reply`, `reason`: stop by default, or steps, budget, `error — <text>`) — the agent's turn ends: a member's reply is its report — `{delivered}`; `set` (`min`, `max`, `capacity`: the person's setting or the model's metadata changed) — null; `person_stop` (`name`) — the person stopped that member in the UI — null; `user` — the person's message to the lead — `{note}`: the note on it (context.md §10), null when there is none; `state` — the team in tree order as `{name, manager, state}`: working, waiting (for a slot), finished, idle (the lead between turns). Delivering to a finished member or the idle lead wakes it (it is then working, or waiting when its model is full). LLeMbas: services/team.py; CLI: team/.",
"cases": [ "cases": [
{ {
"about": "Offered on a model that serves two requests at once: the lead has all four tools and the setting is shown.", "about": "Offered on a model that serves two requests at once: the lead has all four tools and the setting is shown.",
@@ -19,9 +19,14 @@
{ {
"tools": [ "tools": [
"team_add", "team_add",
"team_assign",
"team_decide",
"team_findings",
"team_read", "team_read",
"team_replace",
"team_send", "team_send",
"team_stop" "team_stop",
"team_whoami"
], ],
"setting": true "setting": true
} }
@@ -43,9 +48,14 @@
{ {
"tools": [ "tools": [
"team_add", "team_add",
"team_assign",
"team_decide",
"team_findings",
"team_read", "team_read",
"team_replace",
"team_send", "team_send",
"team_stop" "team_stop",
"team_whoami"
], ],
"setting": true "setting": true
} }
@@ -113,9 +123,14 @@
{ {
"tools": [ "tools": [
"team_add", "team_add",
"team_assign",
"team_decide",
"team_findings",
"team_read", "team_read",
"team_replace",
"team_send", "team_send",
"team_stop" "team_stop",
"team_whoami"
], ],
"setting": true "setting": true
} }
@@ -200,9 +215,14 @@
{ {
"tools": [ "tools": [
"team_add", "team_add",
"team_assign",
"team_decide",
"team_findings",
"team_read", "team_read",
"team_replace",
"team_send", "team_send",
"team_stop" "team_stop",
"team_whoami"
], ],
"setting": true "setting": true
}, },
@@ -228,7 +248,7 @@
] ]
}, },
{ {
"about": "A member that may not lead has team_send and team_read; one that may lead has all four.", "about": "A subagent has team_whoami, team_read, team_send, team_findings and team_finish; a sub-manager the same — it adds nothing (spec 3.4.0).",
"start": { "start": {
"min": 1, "min": 1,
"max": 4, "max": 4,
@@ -279,17 +299,21 @@
}, },
{ {
"tools": [ "tools": [
"team_findings",
"team_finish",
"team_read", "team_read",
"team_send" "team_send",
"team_whoami"
], ],
"setting": true "setting": true
}, },
{ {
"tools": [ "tools": [
"team_add", "team_findings",
"team_finish",
"team_read", "team_read",
"team_send", "team_send",
"team_stop" "team_whoami"
], ],
"setting": true "setting": true
} }
@@ -392,7 +416,7 @@
], ],
"expect": [ "expect": [
{ {
"note": "<session-note>\nThe team may have 3 to 5 agents, you included.\nWork through a team from the start: you lead, and members do the work — at least 2 of them.\n</session-note>" "note": "<session-note>\nThe team may have 3 to 5 agents, you included.\nWork through subagents from the start: you lead, and they do the work. The person opened 2 slots for them: fill them with team_add.\n</session-note>"
}, },
{ {
"result": "You lead a team: give this to a member (team_add or team_send).", "result": "You lead a team: give this to a member (team_add or team_send).",
@@ -458,7 +482,7 @@
], ],
"expect": [ "expect": [
{ {
"note": "<session-note>\nThe team may have 2 to 2 agents, you included.\nWork through a team from the start: you lead, and members do the work — at least 1 of them.\n</session-note>" "note": "<session-note>\nThe team may have 2 to 2 agents, you included.\nWork through subagents from the start: you lead, and they do the work. The person opened 1 slots for them: fill them with team_add.\n</session-note>"
} }
] ]
}, },
@@ -632,7 +656,7 @@
] ]
}, },
{ {
"about": "The size is across the whole tree: a member that leads adds within the same max.", "about": "The size is across the whole tree: subagents placed under a sub-manager count within the same max.",
"start": { "start": {
"min": 1, "min": 1,
"max": 4, "max": 4,
@@ -653,12 +677,13 @@
}, },
{ {
"op": "call", "op": "call",
"by": "backend", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "db", "name": "db",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "backend"
} }
}, },
{ {
@@ -673,12 +698,13 @@
}, },
{ {
"op": "call", "op": "call",
"by": "backend", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "api", "name": "api",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "backend"
} }
}, },
{ {
@@ -1538,7 +1564,7 @@
] ]
}, },
{ {
"about": "A member works; a member that leads becomes a manager when it adds one; stopping all its members makes the lead a worker again (min 1).", "about": "A subagent works; a sub-manager becomes a manager when one is placed under it; stopping them all makes the lead a worker again (min 1).",
"start": { "start": {
"min": 1, "min": 1,
"max": 6, "max": 6,
@@ -1565,12 +1591,13 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "routes", "name": "routes",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "api"
} }
}, },
{ {
@@ -1805,12 +1832,13 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "routes", "name": "routes",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "api"
} }
}, },
{ {
@@ -2009,22 +2037,24 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "routes", "name": "routes",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "api"
} }
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "db", "name": "db",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "api"
} }
}, },
{ {
@@ -2396,13 +2426,14 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "routes", "name": "routes",
"role": "owns a part", "role": "owns a part",
"task": "Do the part.", "task": "Do the part.",
"goal": "The REST API" "goal": "The REST API",
"parent": "api"
} }
}, },
{ {
@@ -2670,7 +2701,7 @@
] ]
}, },
{ {
"about": "Stopping a member stops its subtree; they leave the team, and their names are free again. Only members under the caller may be stopped.", "about": "Stopping is the main agent's: a subagent's team_stop is refused. Stopping a subagent stops its subtree; they leave the team, and their names are free again.",
"start": { "start": {
"min": 1, "min": 1,
"max": 8, "max": 8,
@@ -2691,33 +2722,36 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "routes", "name": "routes",
"role": "owns a part", "role": "owns a part",
"task": "Do the part.", "task": "Do the part.",
"lead": true "lead": true,
"parent": "api"
} }
}, },
{ {
"op": "call", "op": "call",
"by": "routes", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "auth", "name": "auth",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "routes"
} }
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "db", "name": "db",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "api"
} }
}, },
{ {
@@ -2756,7 +2790,7 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_stop", "tool": "team_stop",
"args": { "args": {
"name": "routes" "name": "routes"
@@ -2767,12 +2801,13 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "auth", "name": "auth",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "api"
} }
}, },
{ {
@@ -2817,15 +2852,15 @@
"delivered": {} "delivered": {}
}, },
{ {
"result": "web is not under you: you can stop only the members you lead, directly or through your members.", "result": "Only the main agent adds, assigns, replaces and stops subagents: ask it (team_send to lead).",
"delivered": {} "delivered": {}
}, },
{ {
"result": "api is not under you: you can stop only the members you lead, directly or through your members.", "result": "Only the main agent adds, assigns, replaces and stops subagents: ask it (team_send to lead).",
"delivered": {} "delivered": {}
}, },
{ {
"result": "No agent called ghost in this team. The team: lead, api, routes, auth, db, web.", "result": "Only the main agent adds, assigns, replaces and stops subagents: ask it (team_send to lead).",
"delivered": {} "delivered": {}
}, },
{ {
@@ -2900,12 +2935,13 @@
}, },
{ {
"op": "call", "op": "call",
"by": "api", "by": "lead",
"tool": "team_add", "tool": "team_add",
"args": { "args": {
"name": "routes", "name": "routes",
"role": "owns a part", "role": "owns a part",
"task": "Do the part." "task": "Do the part.",
"parent": "api"
} }
}, },
{ {
+204 -12
View File
@@ -84,7 +84,7 @@
} }
}, },
"team": { "team": {
"about": "Teams (spec 3.2.0, team.md; which model a member runs on and when a team is offered: spec 3.3.0, subagents.md): a lead and the members it adds, who talk in the team's room. The texts are said word for word; {{size}} is the team's size after the call (team.md §3), {{names}} names joined with \", \" in tree order. conformance/team.json.", "about": "Teams (spec 3.2.0, team.md; which model a member runs on and when a team is offered: spec 3.3.0, subagents.md; the main agent, the tree, permissions and a subagent's life: spec 3.4.0, subagents.md §5–§13). The texts are said word for word; {{size}} is the team's size after the call (team.md §3), {{names}} names joined with \", \" in tree order. conformance/team.json, conformance/delegation.json.",
"agents": { "agents": {
"about": "The chat setting agents {min, max}: the agents a team may have, the lead included. 1 ≤ min ≤ max ≤ cap. min and max are a new chat's defaults; cap is the instance's (LLeMbas) or the global configuration's (the CLI).", "about": "The chat setting agents {min, max}: the agents a team may have, the lead included. 1 ≤ min ≤ max ≤ cap. min and max are a new chat's defaults; cap is the instance's (LLeMbas) or the global configuration's (the CLI).",
"min": 1, "min": 1,
@@ -92,17 +92,24 @@
"cap": 32 "cap": 32
}, },
"names": { "names": {
"about": "A member's name: unique in the team; `lead` is the lead's, `all` addresses everyone.", "about": "A subagent's name: unique among the subagents that have not ended; `lead` is the main agent's, `all` addresses everyone, `parent` (3.4.0) the sender's parent.",
"pattern": "^[a-z][a-z0-9-]{0,31}$", "pattern": "^[a-z][a-z0-9-]{0,31}$",
"lead": "lead", "lead": "lead",
"reserved": [ "reserved": [
"all", "all",
"lead" "lead",
] "parent"
],
"parent": "parent",
"id_prefix": "s"
}, },
"marks": { "marks": {
"message": "[Team message from {{from}} to {{to}}:]", "message": "[Team message from {{from}} to {{to}}:]",
"report": "[Team report from {{from}} to {{to}}:]" "report": "[Team report from {{from}} to {{to}}:]",
"assignment": "[Team assignment from {{from}} to {{to}}:]",
"request": "[Team request {{id}} from {{from}} to {{to}}:]",
"decision": "[Team decision on {{id}} from {{from}} to {{to}}:]",
"context_full": "[Team notice to {{to}}: your context is full]"
}, },
"unavailable": { "unavailable": {
"about": "What a team tool answers while its requirement `team` is not met (team.md §2). one_agent is also each team tool's own `unavailable`, and what task answers while a team is offered, max is 1 and the lead has no member. one_request: no model can serve as a subagent any more (subagents.md §4) — the session's model's capacity changed after the prompt was made.", "about": "What a team tool answers while its requirement `team` is not met (team.md §2). one_agent is also each team tool's own `unavailable`, and what task answers while a team is offered, max is 1 and the lead has no member. one_request: no model can serve as a subagent any more (subagents.md §4) — the session's model's capacity changed after the prompt was made.",
@@ -153,25 +160,106 @@
"not_yours": "{{name}} is not under you: you can stop only the members you lead, directly or through your members.", "not_yours": "{{name}} is not under you: you can stop only the members you lead, directly or through your members.",
"nobody_to_stop": "No member is under you: there is nobody to stop.", "nobody_to_stop": "No member is under you: there is nobody to stop.",
"no_report": "(no report)", "no_report": "(no report)",
"ended": "[ended: {{reason}}]" "ended": "[ended: {{reason}}]",
"only_main": "Only the main agent adds, assigns, replaces and stops subagents: ask it (team_send to lead).",
"not_manager": "{{name}} is not a sub-manager: place a subagent under the main agent (lead) or under one added with lead: true.",
"wider": "A subagent's mode cannot be looser than its parent's ({{parent_mode}}).",
"request_not_narrower": "Request options go with a mode stricter than the parent's: without one there is nothing to ask for.",
"request_kind": "{{kind}} is not a request option: use read, write or execute.",
"requestable": "Your mode does not allow this, but the main agent may: ask for it with team_request (the tool, the pattern, why), then try again once it allows.",
"not_delegated": "Your mode does not allow this, and you may not ask for it: tell your parent what you need.",
"not_requestable": "You may not ask for {{tool}}: your request options are {{kinds}}.",
"requested": "Asked the main agent ({{id}}): you are told its decision. Carry on with what you can meanwhile.",
"no_request": "No open request {{id}}.",
"decided": "{{decision}} {{id}} for {{name}}.",
"allowed": "Allowed",
"refused": "Refused",
"decision_allowed": "Allowed: {{tool}} {{pattern}}. Try it again now.",
"decision_refused": "Refused: {{tool}} {{pattern}}.",
"no_parent": "You are the main agent: you have no parent.",
"finished": "Reported to {{to}}. Your task is done: stop here.",
"has_ended": "{{name}} has ended: replace it (team_replace) or add another.",
"cycle": "{{name}} cannot go under {{parent}}: that is under it.",
"assigned": "Assigned to {{name}}: it is told now.",
"replaced": "Replaced {{name}}: {{id}} takes its place, briefed with its task and findings. The team is {{size}} of {{max}} agents.",
"replace_brief": "{{task}}\n\nYou take over from {{name}} ({{id}}), which ended. What it found:\n{{findings}}",
"no_findings": "(no findings)",
"noted": "Noted (finding {{n}}).",
"not_yours_findings": "You may read only your own findings and those of the subagents under you.",
"context_full": "Save what you found with team_findings, then tell your parent with team_send that you are gone and what is left undone. This is your last turn: at most {{steps}} steps.",
"gone": "{{name}} ({{id}}) ran out of context and has ended. Its findings: team_findings with of {{name}}.",
"request_body": "{{tool}} {{pattern}} — {{why}}"
}, },
"read": { "read": {
"about": "team_read's answer: the tree, then the room. Agents in tree order (an agent, then its members in the order they were added, depth first), indented two spaces per level below the lead. `last`: `active now` while working, else how long ago its last turn ended (durations as jobs write them: \"5s ago\", \"1m 05s ago\"). A message's text: its first line after the colon, every further line indented two spaces. Messages above `since` the caller may see, oldest first, at most read_max; `more` when there are others after the last shown.", "about": "team_read's answer: the tree, then the room. Agents in tree order (an agent, then its members in the order they were added, depth first), indented two spaces per level below the lead. `last`: `active now` while working, else how long ago its last turn ended (durations as jobs write them: \"5s ago\", \"1m 05s ago\"). A message's text: its first line after the colon, every further line indented two spaces. Messages above `since` the caller may see, oldest first, at most read_max; `more` when there are others after the last shown. Spec 3.4.0: the row adds the id, the mode (with request options) and the context use; open slots follow the tree, ended subagents after them.",
"tree": "Team ({{size}} of {{max}} agents):", "tree": "Team ({{size}} of {{max}} agents):",
"agent": "{{indent}}- {{name}} — {{role}} · {{state}} · {{model}} · {{last}}", "agent": "{{indent}}- {{name}} ({{id}}) — {{role}} · {{state}} · {{model}} · {{mode}} · {{context}} · {{last}}",
"lead_role": "leads the team", "lead_role": "the main agent",
"states": { "states": {
"working": "working", "working": "working",
"waiting": "waiting for a slot", "waiting": "waiting for a slot",
"finished": "finished", "finished": "finished",
"idle": "idle" "idle": "idle",
"paused": "paused: context full",
"done": "done",
"out_of_context": "out of context",
"stopped": "stopped"
}, },
"active": "active now", "active": "active now",
"room": "Room since #{{since}}:", "room": "Room since #{{since}}:",
"message": "#{{n}} {{from}} → {{to}}: {{text}}", "message": "#{{n}} {{from}} → {{to}}: {{text}}",
"report": "#{{n}} {{from}} → {{to}} (report): {{text}}", "report": "#{{n}} {{from}} → {{to}} (report): {{text}}",
"none": "No messages since #{{since}}.", "none": "No messages since #{{since}}.",
"more": "More after #{{last}}: team_read with since {{last}}." "more": "More after #{{last}}: team_read with since {{last}}.",
"slots": "Open slots: {{n}} — fill them with team_add.",
"ended": "Ended:",
"ended_agent": "- {{name}} ({{id}}) — {{role}} · {{state}} · {{findings}} findings",
"context": "{{percent}}% context",
"request": "+ {{kinds}}"
},
"main_only": {
"about": "Spec 3.4.0 (subagents.md §6, §8): the tools only the main agent has; a subagent's call to one, or to task, answers texts.only_main.",
"tools": [
"team_add",
"team_assign",
"team_decide",
"team_replace",
"team_stop"
]
},
"context": {
"about": "Spec 3.4.0 (subagents.md §9): a subagent does not compact. When its context reaches pause_at of its model's window it is paused and given one final turn of at most final_steps steps, whose user message is the context_full mark and texts.context_full.",
"pause_at": 0.85,
"final_steps": 8
},
"request": {
"about": "Spec 3.4.0 (subagents.md §7): the kinds a request option may name — risk classes — and the ids of requests, r1, r2, … per session.",
"kinds": [
"read",
"write",
"execute"
],
"id_prefix": "r"
},
"whoami": {
"about": "Spec 3.4.0 (subagents.md §8): team_whoami's answer, one line each; `request` only with request options, `parent` only for a subagent. {{percent}} is floor(used × 100 / window).",
"lines": {
"who": "You are {{name}} ({{id}}): {{role}}.",
"parent": "Your parent: {{parent}}.",
"task": "Your task: {{task}}",
"mode": "Your mode: {{mode}}.",
"request": "You may ask the main agent for: {{kinds}} (team_request).",
"model": "Your model: {{model}}.",
"limits": "Limits: the team is {{size}} of {{max}} agents; at most {{steps}} steps a turn; {{spawn}}",
"spawn_yes": "you add subagents.",
"spawn_no": "only the main agent adds subagents.",
"context": "Context: {{used}} of {{window}} tokens ({{percent}}%)."
}
},
"findings": {
"about": "Spec 3.4.0: team_findings read's answer — the header, then one line per finding, oldest first; none: texts.no_findings after the header.",
"header": "Findings of {{name}} ({{id}}):",
"item": "{{n}}. {{text}}"
} }
}, },
"context": { "context": {
@@ -239,12 +327,116 @@
"personality": "The person chose a different personality:\n{{personality}}", "personality": "The person chose a different personality:\n{{personality}}",
"mcp": "Instructions from an MCP server that connected since the session began:\n{{instructions}}", "mcp": "Instructions from an MCP server that connected since the session began:\n{{instructions}}",
"team": "The team may have {{min}} to {{max}} agents, you included.", "team": "The team may have {{min}} to {{max}} agents, you included.",
"team_lead": "Work through a team from the start: you lead, and members do the work — at least {{members}} of them.", "team_lead": "Work through subagents from the start: you lead, and they do the work. The person opened {{members}} slots for them: fill them with team_add.",
"team_one": "The person allows one agent: work alone; the team tools and task are unavailable.", "team_one": "The person allows one agent: work alone; the team tools and task are unavailable.",
"team_own": "This model serves one request at a time, so subagents cannot run on it: they need another model, on another server, that the person names in this chat.", "team_own": "This model serves one request at a time, so subagents cannot run on it: they need another model, on another server, that the person names in this chat.",
"team_stopped": "The person stopped {{names}}; they have left the team.", "team_stopped": "The person stopped {{names}}; they have left the team.",
"empty": "(none)" "empty": "(none)"
}, },
"mcp_wait_ms": 3000 "mcp_wait_ms": 3000
},
"progress": {
"about": "Spec 3.4.0 (progress.md): one fixed set of phases every client shows the same way. `texts` are the shared i18n keys progress.<phase> (a variant: progress.<phase>.<variant>); `line` is drawn for every phase but stopped and failed; elapsed in whole seconds (5s, 1m 05s, 1h 02m). conformance/progress.json.",
"phases": [
"queued",
"waiting_for_model",
"loading_model",
"reading_prompt",
"thinking",
"writing",
"running_tool",
"waiting_for_person",
"waiting_for_subagents",
"compacting",
"retrying",
"stopped",
"failed"
],
"fields": {
"queued": {
"ahead": "int?"
},
"waiting_for_model": {
"model": "string",
"reason": "busy|slots|group"
},
"loading_model": {
"model": "string"
},
"reading_prompt": {
"done": "int?",
"total": "int?"
},
"thinking": {},
"writing": {
"rate": "number?"
},
"running_tool": {
"tool": "string",
"what": "string?"
},
"waiting_for_person": {
"what": "approval|question|plan",
"tool": "string?"
},
"waiting_for_subagents": {
"running": "int"
},
"compacting": {},
"retrying": {
"reason": "string",
"in": "int",
"attempt": "int?"
},
"stopped": {
"reason": "person|steps|budget?"
},
"failed": {
"reason": "string"
}
},
"texts": {
"queued": "Queued",
"queued.ahead": "Queued: {{ahead}} ahead",
"waiting_for_model": "Waiting for {{model}}: {{reason}}",
"loading_model": "Loading {{model}}",
"reading_prompt": "Reading the prompt: {{done}} / {{total}} tokens ({{percent}}%)",
"reading_prompt.bare": "Reading the prompt",
"thinking": "Thinking",
"writing": "Writing: {{rate}} tok/s",
"writing.bare": "Writing",
"running_tool": "Running {{tool}}: {{what}}",
"running_tool.bare": "Running {{tool}}",
"waiting_for_person": "Waiting for you: {{what}}",
"waiting_for_subagents": "Waiting for subagents: {{running}} running",
"compacting": "Compacting the conversation",
"retrying": "Retrying in {{in}} s: {{reason}}",
"stopped": "Stopped",
"stopped.reason": "Stopped: {{reason}}",
"failed": "Failed: {{reason}}"
},
"reason": {
"busy": "it serves one request at a time and is answering another",
"slots": "all its slots are in use",
"group": "its server holds another model now"
},
"what": {
"approval": "allow {{tool}}?",
"approval.bare": "an approval",
"question": "a question",
"plan": "the plan"
},
"stopped_reason": {
"person": "by you",
"steps": "step limit",
"budget": "budget"
},
"line": "{{text}} · {{elapsed}}",
"no_elapsed": [
"stopped",
"failed"
],
"what_max": 80,
"pace_per_second": 4
} }
} }
+7
View File
@@ -30,6 +30,13 @@
"team_send": "allow", "team_send": "allow",
"team_read": "allow", "team_read": "allow",
"team_stop": "allow", "team_stop": "allow",
"team_whoami": "allow",
"team_finish": "allow",
"team_findings": "allow",
"team_request": "allow",
"team_decide": "allow",
"team_assign": "allow",
"team_replace": "allow",
"web_search": "allow", "web_search": "allow",
"memory": "allow", "memory": "allow",
"session_search": "allow", "session_search": "allow",
+107
View File
@@ -0,0 +1,107 @@
# Progress
Spec 3.4.0. **One way to say what a session is doing**, the same in every client — the web UI,
Desktop, the CLI, Android — and for every agent in it, the main agent and each subagent alike.
Before 3.4.0 each client invented its own subset from a free `status` text; that text stays, for
compatibility, and beside it goes a structured **progress** object with one of a fixed set of
**phases**. The texts are `loop.json` `progress`; the cases `conformance/progress.json`.
## 1. The object
```json
{ "phase": "running_tool", "since": 1762790400000, "tool": "bash", "what": "bun test" }
```
- **`phase`** — one of the thirteen names below, never another. A session doing nothing has no
progress: `null`.
- **`since`** — when this phase began, Unix time in **milliseconds** (an integer). A change of the
phase's data (tokens read, the rate) keeps `since`; a new phase, or the same phase for another
thing (another tool call, another model), starts a new one.
- The phase's **data**, as listed: every field named here, with these names and these types. A
field that is not known is left out (never `null`, never a guess), and the text says less.
## 2. The phases
In the order a turn usually meets them. `{{x}}` in a text is the field `x`; the texts are
`loop.json` `progress.texts`, word for word, and they are the shared i18n keys
`progress.<phase>` (and `progress.<phase>.<variant>`, `progress.reason.<reason>`,
`progress.what.<what>`) in every client's translations.
| Phase | Data | Text (`progress.texts`) |
|---|---|---|
| `queued` | `ahead?: int` — turns before it | `Queued` · with `ahead`: `Queued: {{ahead}} ahead` |
| `waiting_for_model` | `model: string` (its display name, else its id); `reason: "busy" \| "slots" \| "group"` | `Waiting for {{model}}: {{reason}}` |
| `loading_model` | `model: string` | `Loading {{model}}` |
| `reading_prompt` | `done: int`, `total: int` — prompt tokens processed of all | `Reading the prompt: {{done}} / {{total}} tokens ({{percent}}%)` · without `total`: `Reading the prompt` |
| `thinking` | — | `Thinking` |
| `writing` | `rate?: number` — tokens a second, as measured over this reply | `Writing: {{rate}} tok/s` · without `rate`: `Writing` |
| `running_tool` | `tool: string` (the tool's name); `what?: string` — the call's short title (the command, the path, the query), one line, at most 80 characters | `Running {{tool}}: {{what}}` · without `what`: `Running {{tool}}` |
| `waiting_for_person` | `what: "approval" \| "question" \| "plan"`; `tool?: string` (approval: the tool asked about) | `Waiting for you: {{what}}` |
| `waiting_for_subagents` | `running: int` — subagents working or waiting now | `Waiting for subagents: {{running}} running` |
| `compacting` | — | `Compacting the conversation` |
| `retrying` | `reason: string` — the server's error, one line; `in: int` — seconds until the retry, counted down by the client from `since`; `attempt?: int` | `Retrying in {{in}} s: {{reason}}` |
| `stopped` | `reason?: "person" \| "steps" \| "budget"` | `Stopped` · with `reason`: `Stopped: {{reason}}` |
| `failed` | `reason: string` — the error, one line | `Failed: {{reason}}` |
The fixed words a field stands for:
- `reason` of `waiting_for_model` (`progress.reason`): `busy` — *it serves one request at a time
and is answering another*; `slots` — *all its slots are in use*; `group` — *its server holds
another model now*.
- `what` of `waiting_for_person` (`progress.what`): `approval` — *allow {{tool}}?* (*an approval*
without `tool`); `question` — *a question*; `plan` — *the plan*.
- `reason` of `stopped` (`progress.stopped_reason`): `person` — *by you*; `steps` — *step limit*;
`budget` — *budget*.
- `percent` is `floor(done × 100 / total)`; numbers are written as integers, `rate` with one
decimal (`12.5`), in the client's own number format.
## 3. The line a client draws
**`{{text}} · {{elapsed}}`** (`progress.line`) for every phase but `stopped` and `failed`, which
draw their text alone. `elapsed` is now − `since`, written in whole seconds as a ticking clock writes durations
(`5s`, `1m 05s`, `1h 02m`), and kept moving by the client at least once a second while the
phase lasts. `retrying`'s `in` is likewise counted down from `since` (never below 0).
Every client draws the same line for the same object — the same words, from the same keys — and
its own look around it (a spinner, a colour, a bar for `reading_prompt`). Where a line has no room
(a phone's notification, a tree row) the text alone, without `· elapsed`, is enough; nothing else
is shortened.
## 4. When each phase holds
What decides the phase, so that two implementations agree on the same moment:
- `queued` — a prompt waits behind a turn that runs (the device's turn queue; the server's per
chat). `waiting_for_model` — the turn may start but its model may not take it yet: a one-request
server answering another request (`busy`), every slot of the model held (`slots`; `team.md` §8),
another model of its one-model server in use (`group`). `loading_model` — only where the
implementation is told the model is being loaded (the server sees it; a device that cannot tell
skips it).
- `reading_prompt` — the request is sent and no output has come; with `done`/`total` from the
server's prompt-progress frames (`quirks.json` `prompt-progress`), without them where none come.
- `thinking` — reasoning streams; `writing` — reply text (or a tool call's arguments) streams.
- `running_tool` — from a tool call's start to its end; with calls in parallel, the one that
started first. `waiting_for_person` — an approval card, a question or a plan to approve is open
for this agent. `waiting_for_subagents` — the agent's turn waits on subagents: a `task` call in
the foreground, or the main agent with nothing to do but its subagents' reports (`subagents.md`
§9).
- `compacting` — a compaction runs. `retrying` — a failed request will be sent again after a wait.
- `stopped` / `failed` — the turn ended other than by finishing: stopped by the person, a step limit
or a budget; failed with an error. They are the last progress of that turn and stay until the
next one begins; a turn that finished normally ends with `null`.
## 5. Where it is sent
- **`/api/v2` (LLeMbas)**: every frame of a chat's stream that carries the `status` text carries
`progress` beside it — the object, or `null` — and a frame is sent when the progress changes:
a new phase at once, a data-only change (`done`, `rate`) at most four times a second. `status`
stays as it was, its text unchanged, for clients that do not read `progress`.
- **The link (`acp.md`, capability `progress`)**: the `phase` event `{ progress, agent? }` —
`agent` the subagent's id (`subagents.md` §5) when the progress is a subagent's, absent for the
main agent's — under the same pacing; `progress` in `_lembas/session/status`, so a client
reattaching draws it at once. The device sends it; the server passes it on in `/api/v2` as its
own.
- **The subagent tree** (`subagents.md` §12): every agent carries its current `progress`.
A client that reads `progress` draws it and ignores `status`; one that does not, draws `status` as
before.
+14 -12
View File
@@ -1,12 +1,14 @@
<!-- LLeMbas CLI and LLeMbas, spec 3.2.0 (team.md): how to lead a team, in the system prompt whenever <!-- LLeMbas CLI and LLeMbas, spec 3.2.0 (team.md), 3.4.0 (subagents.md §5–§13): how the main agent
the team tools are offered, and in a member's that may lead. Constant: the size the person leads its subagents, in the system prompt whenever the team tools are offered. Constant: the
allows comes in a session note, never here. --> size the person allows comes in a session note, never here. -->
# Leading a team # Leading subagents
You can add members to a team with team_add: agents that work in the background, each on the part of the work it owns, while you lead. A small job you may do yourself. Once you have a member you manage and no longer do the work: tools that change things are refused to you, and reading stays yours, for review. You are the main agent of this session. You can add subagents with team_add: agents that work in the background, each on the part of the work it owns, while you lead. Only you add, assign, replace and stop them. A small job you may do yourself. Once you have a subagent you manage and no longer do the work: tools that change things are refused to you, and reading stays yours, for review.
- Plan first: what the goal is, what the parts are, and what each one needs from the others. Split by area of ownership, not by step, so that one member owns a thing from start to finish. - Plan first: what the goal is, what the parts are, and what each one needs from the others. Split by area of ownership, not by step, so that one subagent owns a thing from start to finish.
- Name each member after what it owns. Brief it so that it can work without you: what to do, what done means, what to leave alone, and what to report. - Name each subagent after what it owns and give it a position (`role`). Brief it so that it can work without you: what to do, what done means, what to leave alone, and what to report.
- Keep the team flat while it is small — up to six or eight members, each reporting to you. Beyond that, add a lead for each area (`lead: true`), give it the area's goal, and let it build and run its own members; you then lead the leads. - Keep the tree flat while it is small — up to six or eight subagents, each reporting to you. Beyond that, add a sub-manager for each area (`lead: true`) and place that area's subagents under it (`parent`); it directs them and reports to you, and you still add and stop every one.
- Keep the team within the size the person allows, which a session note tells you. Stop a member whose part is done or no longer needed, to make room. - A subagent's mode is yours by default and never looser. Give a stricter one where the person wants less done unasked; with it you may give `request` options, and you decide its requests with team_decide — allow only what the task needs.
- Coordinate through the room: team_send to the member who owns a thing, `all` only for what everyone needs. team_read shows the team and the room when you need to look; nothing from it reaches you unasked except your members' reports and messages addressed to you. - Keep within the size the person allows, which a session note tells you; when it says slots are open, fill them. Stop a subagent whose part is done or no longer needed, to make room.
- Step in when a member is stuck, when two disagree, or when the plan has to change: decide, and tell those it concerns. - Coordinate through the room: team_send to the subagent who owns a thing, `all` only for what everyone needs. team_read shows the tree and the room when you need to look; nothing from it reaches you unasked except reports, requests and messages addressed to you.
- Review every report against what you asked for — read the work itself where it matters — and send back what is not done. Then give your own answer: to the person, or, if you lead an area, as your report to your manager. - A subagent whose context fills saves its findings and ends. Read them with team_findings, then replace it (team_replace) if its part is not done.
- Step in when a subagent is stuck, when two disagree, or when the plan has to change: decide, and tell those it concerns (team_assign for new work).
- Review every report against what you asked for — read the work itself where it matters — and send back what is not done. Then give your own answer to the person.
+9 -7
View File
@@ -1,14 +1,16 @@
<!-- LLeMbas CLI and LLeMbas, spec 3.2.0 (team.md): a team member's standing, said once at the end <!-- LLeMbas CLI and LLeMbas, spec 3.2.0 (team.md), 3.4.0 (subagents.md §13): a subagent's standing,
of its system prompt in place of blocks/subagent.md. The variables are what differs per member; said once at the end of its system prompt in place of blocks/subagent.md. The variables are what
each stands on a line of its own, so no punctuation of theirs matters. --> differs per subagent; each stands on a line of its own, so no punctuation of theirs matters. -->
You are "{{name}}", a member of a team, and you report to "{{manager}}". You are "{{name}}" ({{id}}), a subagent of this session, and you report to "{{manager}}".
Your part: {{role}} Your position: {{role}}
The team's goal: {{goal}} The team's goal: {{goal}}
{{instructions}} {{instructions}}
You work for your manager on the part you own, and you cannot ask the person anything: ask your manager, by name, with team_send. Other members own other parts. Message the one who owns a thing when you need it from them, and `all` only for what everyone on the team needs; messages to you arrive as you work. team_read shows who is on the team and the room. You work for your parent on the part you own, and you cannot ask the person anything: ask your parent with team_send to `parent`. Only the main agent (`lead`) adds, assigns and stops subagents. Other subagents own other parts. Message the one who owns a thing when you need it from them, and `all` only for what everyone needs; messages to you arrive as you work. team_read shows the tree and the room; team_whoami shows who you are, your mode and how full your context is.
Your final reply is your report to "{{manager}}" — make it complete on its own: the result, then the evidence (file:line), then anything you could not settle. A message may wake you again afterwards; your next final reply is then your next report.{{read_only}} Your context is limited and you do not compact: write what you find with team_findings as you go. When your context fills you are told, and you have one last turn to save your findings and tell your parent.
When your task is done, call team_finish with your report to "{{manager}}" — make it complete on its own: the result, then the evidence (file:line), then anything you could not settle. A final reply without it is also your report, and a message may then wake you again.{{read_only}}
{{lead}} {{lead}}
@@ -0,0 +1,8 @@
<!-- LLeMbas CLI and LLeMbas, spec 3.4.0 (subagents.md §5, §13): what a sub-manager is told, in
place of the whole of blocks/team_lead.md that a member with `lead: true` got in 3.2.0. -->
# Directing an area
You are a sub-manager: the main agent places subagents under you, and you direct them. You do not add, assign, replace or stop subagents — ask the main agent with team_send to `lead` when your area needs one more, another task for one, or one stopped. You manage and no longer do the work: tools that change things are refused to you, and reading stays yours, for review.
- Split your area's work among the subagents under you, tell each what it owns with team_send, and keep them from overlapping.
- Their reports come to you. Review each against what you asked — read the work itself where it matters — and send back what is not done.
- Read the findings of a subagent under you with team_findings, especially of one that ran out of context; tell the main agent whether it needs replacing.
- When your area is done, call team_finish with your report to the main agent.
+11 -4
View File
@@ -251,16 +251,23 @@
"scope": "shared", "scope": "shared",
"llembas": [], "llembas": [],
"llembas_text": "identical", "llembas_text": "identical",
"why": "spec 3.2.0: LLeMbas reads it from the vendored spec (harness_spec.prompt), not from a fragment", "why": "spec 3.2.0, 3.4.0: LLeMbas reads it from the vendored spec (harness_spec.prompt), not from a fragment",
"variables": { "variables": {
"name": "the member's name in the team", "name": "the member's name in the team",
"role": "its role as team_add gave it: one line, what it owns", "role": "its position, one line (team_add's role)",
"manager": "the name of the agent that added it (`lead` for the team's lead)", "manager": "its parent: lead, or the sub-manager it was placed under",
"goal": "the team's goal as it was when the member was added: the `goal` its team_add gave, else its manager's (team.md §4)", "goal": "the team's goal as it was when the member was added: the `goal` its team_add gave, else its manager's (team.md §4)",
"instructions": "its agent's own instructions (prompts/agents/<name>.md for a built-in one), as in blocks/subagent.md", "instructions": "its agent's own instructions (prompts/agents/<name>.md for a built-in one), as in blocks/subagent.md",
"read_only": "in plan mode: the sentence saying it can read and search but change nothing (as in blocks/subagent.md); otherwise empty", "read_only": "in plan mode: the sentence saying it can read and search but change nothing (as in blocks/subagent.md); otherwise empty",
"lead": "for a member added with `lead: true`: the whole of blocks/team_lead.md; otherwise empty" "lead": "for a sub-manager (added with `lead: true`): the whole of blocks/team_submanager.md (spec 3.4.0; before, blocks/team_lead.md); otherwise empty",
"id": "the subagent's id (s1, s2, …; spec 3.4.0)"
} }
},
"blocks/team_submanager.md": {
"scope": "shared",
"llembas": [],
"llembas_text": "identical",
"why": "spec 3.4.0: LLeMbas reads it from the vendored spec (harness_spec.prompt), not from a fragment"
} }
} }
} }
+261 -1
View File
@@ -1,6 +1,6 @@
# Subagents # Subagents
Spec 3.3.0. **Which model a subagent runs on, and when subagents are offered at all.** A 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 **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 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` on the server and in an agent session on a device, in both projects. The texts are `loop.json`
@@ -88,3 +88,263 @@ 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 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. 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`.
**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`):
```json
{ "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.
+15 -1
View File
@@ -1,6 +1,6 @@
# Teams # Teams
Spec 3.2.0; the model a member runs on and when a team is offered, 3.3.0 (`subagents.md`). **Subagents that work together.** A session's agent — the **lead** — may add Spec 3.2.0; the model a member runs on and when a team is offered, 3.3.0 (`subagents.md`); the main agent, the tree, permissions and a subagent's life, 3.4.0 (`subagents.md` §5–§13). **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 **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 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 and to their managers in the team's **room**. A member may itself lead an area with members of its
@@ -11,6 +11,16 @@ The tools are `tools/team_add.json`, `team_send.json`, `team_read.json`, `team_s
and numbers `loop.json` `team` (and the size notes in `context.note`); the prompt 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`. `prompts/blocks/team_lead.md` and `team_member.md`; the cases `conformance/team.json`.
> **Spec 3.4.0 supersedes parts of this page** (`subagents.md` §5–§13, *Subagents v2*): the lead is
> **the main agent**, and **only it adds, assigns, replaces and stops** — a member with `lead: true`
> is a **sub-manager** that directs the members placed under it (`team_add`'s `parent`) and does not
> add or stop (§4, §9 below); members have a mode no looser than their parent's and may ask the main
> agent for more (`subagents.md` §7); a member ends with `team_finish` or when its context fills
> (`subagents.md` §9); the room carries assignments, requests and decisions besides messages and
> reports; the tree goes over the link (§11). Everything else here stands as written: the size
> setting and its counting, the room and who receives what, delivery and waking, a manager's
> refusals, capacity and waiting for a slot. "Member" here and "subagent" there are the same thing.
## 1. Words ## 1. Words
- **Agent**: the lead or a member. Every agent has a **name**: the lead is `lead`; a member the name - **Agent**: the lead or a member. Every agent has a **name**: the lead is `lead`; a member the name
@@ -243,6 +253,10 @@ agent not under it (`not_yours`). The answer, `stopped`, names the stopped in tr
## 11. Over the link ## 11. Over the link
*3.4.0: the tree and the room are carried — `acp.md` capability `tree`; what follows is how a device
without it is still shown.*
An agent chat's team runs on the device, as its jobs do. Spec 3.2.0 does not carry the room over 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, 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 `description` being `<name>: <role>`), so the client's job strip shows them, and the room is shown
+31 -4
View File
@@ -14,7 +14,7 @@
"cli": [], "cli": [],
"llembas": [] "llembas": []
}, },
"description": "Add a member to your team and start it at once, in the background, on its part of the work. You lead: members do the work while you plan, coordinate and review — once you have a member, tools that change things are refused to you.\n\n- `name` is how everyone addresses it: lowercase letters, digits and hyphens, after what it owns (`parser`, `api-tests`), unique in the team.\n- `role` is one line: what it owns. `task` is its brief and must stand on its own — it sees nothing of this conversation: what to do, when it is done, and what to report.\n- `goal` is the team's goal in a line; your first member needs it, and every member is told it.\n- `lead: true` lets it add members of its own and lead an area; it then manages as you do.\n- The person sets how many agents the team may have, you included; past that this is refused. When the model is already serving as many requests as it can at once, the member waits for a slot and starts when one frees.\n- Its final reply comes to you as its report. Meanwhile team_send messages it, team_read shows the team and the room, team_stop stops it.\n- Agents: {{agents}}", "description": "Add a subagent and start it at once, in the background, on its part of the work. Only you, the main agent, add subagents. You lead: subagents do the work while you plan, coordinate and review — once you have one, tools that change things are refused to you.\n\n- `name` is how everyone addresses it: lowercase letters, digits and hyphens, after what it owns (`parser`, `api-tests`). `role` is its position, one line: what it owns. `task` is its brief and must stand on its own — it sees nothing of this conversation: what to do, when it is done, and what to report.\n- `goal` is the team's goal in a line; your first subagent needs it, and every subagent is told it.\n- `lead: true` makes it a sub-manager: it directs the subagents you place under it (`parent`), reviews their reports and reports to you; it does not add or stop subagents itself.\n- `mode` is its permission mode: its parent's by default, never looser. With a stricter mode you may give `request` options — kinds of action (read, write, execute) it may ask you for with team_request; you decide with team_decide.\n- The person sets how many agents the team may have, you included; past that this is refused. When the model is already serving as many requests as it can at once, the subagent waits for a slot and starts when one frees.\n- It reports with team_finish (or its final reply). Meanwhile team_send messages it, team_read shows the tree and the room, team_assign gives it new work, team_replace replaces it, team_stop stops it.\n- Agents: {{agents}}",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -26,7 +26,7 @@
"role": { "role": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
"description": "One line: what it owns" "description": "Its position, one line: what it owns"
}, },
"task": { "task": {
"type": "string", "type": "string",
@@ -39,7 +39,7 @@
}, },
"lead": { "lead": {
"type": "boolean", "type": "boolean",
"description": "May add members of its own and lead an area (default: false)" "description": "A sub-manager: directs the subagents placed under it (default: false)"
}, },
"agent": { "agent": {
"type": "string", "type": "string",
@@ -48,6 +48,32 @@
"model": { "model": {
"type": "string", "type": "string",
"description": "Run it on another model — only one the person named in this conversation (its id or name, as in the list in your instructions). Default: the session's model." "description": "Run it on another model — only one the person named in this conversation (its id or name, as in the list in your instructions). Default: the session's model."
},
"parent": {
"type": "string",
"description": "The sub-manager to place it under (default: you, lead)"
},
"mode": {
"type": "string",
"enum": [
"auto",
"edit",
"manual",
"plan"
],
"description": "Its permission mode: its parent's or stricter (default: its parent's)"
},
"request": {
"type": "array",
"items": {
"type": "string",
"enum": [
"read",
"write",
"execute"
]
},
"description": "With a stricter mode only: kinds of action it may ask you for (team_request)"
} }
}, },
"required": [ "required": [
@@ -58,5 +84,6 @@
}, },
"variables": { "variables": {
"agents": "the agents a member may be: `name — description`, separated by \"; \" (as task's)" "agents": "the agents a member may be: `name — description`, separated by \"; \" (as task's)"
} },
"offered": "main"
} }
+51
View File
@@ -0,0 +1,51 @@
{
"name": "team_assign",
"scope": "shared",
"sessions": "both",
"risk": "interact",
"exclusive": false,
"block": "tool",
"purpose": false,
"requires": [
"team"
],
"unavailable": "The person allows one agent in this chat: there is no team. Do the work yourself.",
"aliases": {
"cli": [],
"llembas": []
},
"offered": "main",
"description": "Give a subagent that has not ended a new task: it is told at its next step, or woken. Optionally a new position (`role`), another sub-manager to work under (`parent`, or lead), or a stricter `mode`.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"task": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string"
},
"parent": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"auto",
"edit",
"manual",
"plan"
]
}
},
"required": [
"name",
"task"
]
}
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "team_decide",
"scope": "shared",
"sessions": "both",
"risk": "interact",
"exclusive": false,
"block": "tool",
"purpose": false,
"requires": [
"team"
],
"unavailable": "The person allows one agent in this chat: there is no team. Do the work yourself.",
"aliases": {
"cli": [],
"llembas": []
},
"offered": "main",
"description": "Decide a subagent's request (team_request): allow it or refuse it, with a note it is told. You may allow only what your own mode would allow you; anything beyond that goes to the person when the subagent tries.",
"parameters": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"allow": {
"type": "boolean"
},
"note": {
"type": "string"
}
},
"required": [
"id",
"allow"
]
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "team_findings",
"scope": "shared",
"sessions": "both",
"risk": "interact",
"exclusive": false,
"block": "tool",
"purpose": false,
"requires": [
"team"
],
"unavailable": "The person allows one agent in this chat: there is no team. Do the work yourself.",
"aliases": {
"cli": [],
"llembas": []
},
"description": "Findings kept beyond your context: `write` adds one (a fact, a result, where something is) to yours; `read` shows yours, or with `of` those of a subagent under you (the main agent: anyone's), ended ones included. Write as you go: when your context fills you have one turn left, and your findings are what the next agent starts from.",
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"write",
"read"
]
},
"text": {
"type": "string",
"description": "write: the finding"
},
"of": {
"type": "string",
"description": "read: a name or an id (default: yours)"
}
},
"required": [
"action"
]
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"name": "team_finish",
"scope": "shared",
"sessions": "both",
"risk": "interact",
"exclusive": false,
"block": "tool",
"purpose": false,
"requires": [
"team"
],
"unavailable": "The person allows one agent in this chat: there is no team. Do the work yourself.",
"aliases": {
"cli": [],
"llembas": []
},
"offered": "subagent",
"description": "Your task is done: send your report to your parent and end. Make the report complete on its own — the result, then the evidence (file:line), then anything you could not settle. Save anything worth keeping beyond the report with team_findings first. Nothing runs after this call.",
"parameters": {
"type": "object",
"properties": {
"report": {
"type": "string",
"minLength": 1
}
},
"required": [
"report"
]
}
}
+1 -1
View File
@@ -14,7 +14,7 @@
"cli": [], "cli": [],
"llembas": [] "llembas": []
}, },
"description": "The team and its room: every agent (name, role, who leads it, state, model, last activity), and the room's messages after `since` that are yours to see — those you sent or received and those of the members under you; the lead sees them all. Messages are numbered: pass the last number you saw as `since` to read only what is new.", "description": "The tree and its room: every agent (name, id, position, parent, state, model, mode, context use, last activity), open slots, subagents that have ended, and the room's messages after `since` that are yours to see — those you sent or received and those of the subagents under you; the main agent sees them all. Messages are numbered: pass the last number you saw as `since` to read only what is new.",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
+34
View File
@@ -0,0 +1,34 @@
{
"name": "team_replace",
"scope": "shared",
"sessions": "both",
"risk": "interact",
"exclusive": false,
"block": "task",
"purpose": false,
"requires": [
"team"
],
"unavailable": "The person allows one agent in this chat: there is no team. Do the work yourself.",
"aliases": {
"cli": [],
"llembas": []
},
"offered": "main",
"description": "Replace a subagent — one that ran out of context, or one that is stuck: it ends (stopped, if it has not already), and a new one with the same name, position, parent, mode and model takes its place, briefed with `task` (default: the old task) and the old one's findings. The subagents under it move under the new one.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"task": {
"type": "string"
}
},
"required": [
"name"
]
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "team_request",
"scope": "shared",
"sessions": "both",
"risk": "interact",
"exclusive": false,
"block": "tool",
"purpose": false,
"requires": [
"team"
],
"unavailable": "The person allows one agent in this chat: there is no team. Do the work yourself.",
"aliases": {
"cli": [],
"llembas": []
},
"offered": "subagent",
"description": "Ask the main agent to allow a call your mode does not, of a kind your request options name. Say the tool, the permission pattern you need (a command such as `git push *`, or a path) and why. Its decision comes to you as a message; once allowed, make the call again.",
"parameters": {
"type": "object",
"properties": {
"tool": {
"type": "string",
"minLength": 1
},
"pattern": {
"type": "string",
"minLength": 1
},
"why": {
"type": "string",
"minLength": 1
}
},
"required": [
"tool",
"pattern",
"why"
]
}
}
+2 -2
View File
@@ -14,7 +14,7 @@
"cli": [], "cli": [],
"llembas": [] "llembas": []
}, },
"description": "Send a message through your team's room: to one or more agents by name, or to `all`. It reaches each at its next step, or wakes one that has finished, its context intact. Message the agent who owns a thing; use `all` only for what everyone needs. The team's lead is `lead`; your manager is reached by its name.", "description": "Send a message through the team's room: to one or more agents by name, to `parent` (the agent you report to), or to `all`. It reaches each at its next step, or wakes one that has finished, its context intact. Message the agent who owns a thing; use `all` only for what everyone needs. The main agent is `lead`.",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -24,7 +24,7 @@
"type": "string" "type": "string"
}, },
"minItems": 1, "minItems": 1,
"description": "Names, or [\"all\"]" "description": "Names, [\"parent\"], or [\"all\"]"
}, },
"message": { "message": {
"type": "string", "type": "string",
+4 -3
View File
@@ -14,18 +14,19 @@
"cli": [], "cli": [],
"llembas": [] "llembas": []
}, },
"description": "Stop a member of your team, and the members under it, where they are: their work stops and they leave the team, which makes room for others. Only members under you, directly or through your members; `all` stops every one of them. Nothing they had not reported comes back — ask for a report with team_send first when you need it.", "description": "Stop a subagent, and the subagents under it, where they are: their work stops and they end, which makes room for others. Only you, the main agent, stop subagents; `all` stops every one. Nothing they had not reported comes back — ask for a report with team_send first, or read their findings with team_findings.",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
"description": "A member under you, or all" "description": "A subagent's name, or all"
} }
}, },
"required": [ "required": [
"name" "name"
] ]
} },
"offered": "main"
} }
+22
View File
@@ -0,0 +1,22 @@
{
"name": "team_whoami",
"scope": "shared",
"sessions": "both",
"risk": "read",
"exclusive": false,
"block": "tool",
"purpose": false,
"requires": [
"team"
],
"unavailable": "The person allows one agent in this chat: there is no team. Do the work yourself.",
"aliases": {
"cli": [],
"llembas": []
},
"description": "Who you are on the team: your id, name, position, parent, task, permission mode and request options, model, limits, and how full your context is.",
"parameters": {
"type": "object",
"properties": {}
}
}