@fradser/pi-agent-teams 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.memory/MEMORY.md CHANGED
@@ -3,4 +3,4 @@
3
3
  - `openai_teammate_api_reference.md` — OpenAI external Teammate / Workspace Agents API reference (Workspace Agents API, Files API, file search, vector stores, Threads API). Reference only, not for Pi extension usage guidance.
4
4
 
5
5
  - `team-centric-design.md` — Agent Teams current architecture: named resident RPC teammates, shared task board with atomic self-claim, direct P2P inboxes, verify-gated completion, harness-driven wake-ups. Supersedes the deleted run-centric DAG design.
6
- - `follow-up-queue.md` — Leader-report FIFO handoff: dispatch the first report to Pi's native follow-up queue while the leader is active; native follow-up does not interrupt tool loops.
6
+ - `follow-up-queue.md` — Teammate report steering handoff: hand reports immediately to Pi's steering channel without an internal serialized follow-up queue; delivers at safe tool boundaries.
@@ -1,21 +1,18 @@
1
1
  ---
2
2
  name: follow-up-queue
3
- description: Agent Teams serializes automatic teammate reports through Pi custom-message follow-ups, including reports arriving while the leader is active
3
+ description: Agent Teams hands teammate reports immediately to Pi's steering channel without an internal serialized follow-up queue
4
4
  type: project
5
5
  ---
6
6
 
7
7
  ## Why
8
8
 
9
- Pi accepts a custom `sendMessage(..., { triggerTurn: true, deliverAs: "followUp" })` while the leader is streaming and retains it in its native follow-up queue. Agent Teams must not add an idle-only gate that delays that handoff until the entire leader run settles. Doing so leaves reports in the package queue during a leader tool-call loop instead of at least making them visible to Pi's proper queue.
10
-
11
- A native follow-up still runs only after the current tool/steering loop can naturally finish; it does not interrupt an in-flight tool or an unbounded leader loop. The leader guidance therefore forbids sleep-based polling, and urgent interruption needs an explicit steer/abort design rather than changing ordinary reports.
9
+ Using `deliverAs: "followUp"` delayed teammate reports until the entire leader tool-calling turn finished, and an internal serialized queue holding subsequent reports until `agent_settled` meant multiple reports backlogged during active leader work and then flooded the leader across subsequent turns. Additionally, worker reports need to arrive promptly when the leader is working, at the next safe tool boundary, rather than waiting for the entire run to settle.
12
10
 
13
11
  ## How to apply
14
12
 
15
- - `packages/agent-teams/src/follow-up-queue.ts` owns a FIFO report queue and one active dispatch.
16
- - `pump()` dispatches the first pending report whether the leader is idle or active. Active leaders receive it through Pi's native `followUp` channel; idle leaders begin a new turn.
17
- - Keep only one package dispatch active. `agent_settled` releases it and schedules the next pending report, preserving FIFO and one-report-per-leader-turn semantics.
18
- - Match idle-started dispatches in `before_agent_start`/`agent_start`; a watchdog restores a dispatch when no start occurs. Active-run dispatches are already accepted by Pi's follow-up queue and wait for settlement.
19
- - `sendMainSessionFollowUp` only enqueues. Do not call Pi's void `sendMessage` directly from worker completion paths.
20
- - The team machine suppresses every report after the first accepted terminal status in a wake-up sequence; a new wake-up resets that boundary. Distinct intermediate reports, including identical bodies before terminal status, remain deliverable, and the same content is not suppressed across assignments.
21
- - Keep BDD coverage in `features/agent-teams.feature` and the busy-leader serialization regression in `tests/test_teammate_package.py`.
13
+ - `packages/agent-teams/src/leader-reports.ts` provides report types, envelopes, and display grouping; the internal `FollowUpQueue` is removed.
14
+ - Reports from `team-machine.ts` are handed directly to `leaderPi.sendMessage(..., { triggerTurn: true, deliverAs: "steer" })`.
15
+ - Working leaders receive reports at the next safe tool boundary without interrupting in-flight tools; idle leaders wake immediately.
16
+ - The leader guidance emphasizes that workers execute assignments autonomously without progress nagging; leader steering is reserved for newly discovered information that affects the worker's assignment.
17
+ - Silence is console telemetry only; no heartbeat or stall notices reach the leader. Provider hangs surface through the terminal close-path diagnostic (repo ADR 0002).
18
+ - Keep BDD coverage in `features/agent-teams.feature` and native delivery regressions in `tests/test_immediate_reports.py`.
@@ -1,21 +1,25 @@
1
1
  ---
2
- name: teammate-resident-teams
3
- description: Agent Teams (packages/agent-teams) — team-centric: named resident RPC teammates, shared task board with atomic self-claim, unified addressed messaging, verify-gated completion, harness-driven wake-ups
2
+ name: agent-teams-final-surface
3
+ description: Final Agent Teams public surface and lifecycle rules
4
4
  type: project
5
5
  ---
6
6
 
7
- `packages/agent-teams` is a Claude-Code-style collaborative organization layer for Pi: the Pi session is the team leader; teammates are named resident child Pi processes in RPC mode; coordination runs through a shared local task board plus direct peer inbox messaging. It replaces the former run-centric DAG design (`teammate_run`/fanout/retry are deleted).
8
-
9
- **Why:**
10
- Exploratory / competitive-hypothesis work (multi-hypothesis debugging, three-way review with cross-challenge) cannot be represented cleanly by a static DAG because workers must exchange findings mid-task. One-step delegation avoids the old registry ceremony: `teammate_spawn(name, agent, prompt?)` carries the kickoff task and the board is optional. A later grilling decision identified tool count, conceptual overlap, and parameter breadth as separate problems: the surface was reduced from ten names to seven by making `send_message` the one addressed message primitive, registering `task_list` once for both sides, and moving model/worktree from spawn overrides into agent frontmatter.
7
+ **Why:** Agent Teams uses one explicit coordination surface so Work authority,
8
+ communication, and resident lifecycle cannot be confused.
11
9
 
12
10
  **How to apply:**
13
- 1. **Seven tool names**: leader gets `teammate_spawn(name, agent, prompt?)`, `teammate_shutdown(name)`, `send_message(to, message, status?)`, `task_create`, and shared `task_list`; workers get the same `send_message`, shared `task_list`, `task_claim`, and `task_submit`. There is no `teammate_message`, cancel/retry/run/fanout, or polling tool.
14
- 2. **Unified messaging**: `send_message(to="leader", message, status?)` is a worker report written to the validated outbox; `send_message(to="<peer>", message)` writes the recipient inbox directly; leader-addressed teammate messages route through a running control stream or idle wake queue. Synchronous routing outcomes are deliberately narrow: `steered` means the active control stream accepted the write, while `queued` means the harness still owns delivery into a recipient turn. Peer inbox and leader outbox writes are therefore queued, not delivered; no routing label claims the recipient read or processed the message. `status` is invalid except for `to="leader"`. The first non-empty message line is the console title.
15
- 3. **Declarative role attributes**: agent frontmatter carries `model`, optional `verify`, and boolean `worktree`.Definitions live in one project directory with filename-based scopes: `<name>.local.md` is project-local (personal, never committed) and dedupes against its `<name>.md` counterpart (project scope, git-managed, team-shared); user-level sits in `~/.pi/agent/agents`; precedence is project-local > project > user > bundled and `resolveAgent` exposes scope + gitManaged for provenance. Spawn has no cwd/model/worktree override; create a role variant instead. `worktree: true` gives every instance of that role a dedicated Git worktree and diff capture at shutdown.
16
- 4. **Residents/wake-ups (constitution: detect and notify, never terminate)**: long-lived RPC children, 8-process session cap; wake-up sequences have NO turn-count or duration caps — no configuration may automatically terminate a working teammate. Turn counts and silence durations are telemetry only. The 500ms harness poll drains outboxes, applies intents, routes inboxes, wakes only for queued deliveries or a paced claimable-task notice, and runs the stall heartbeat: 30 minutes without any RPC output (`PI_TEAMMATE_STALL_NOTICE_MS`, 0 disables) sends the leader one actionable notice per silence episode — that notice is the LAST automatic action; continuing, steering, shutting down, or respawning a context-carrying successor (original kickoff + mailbox reports + board claims + liveText tail) belongs to the leader model alone. Any stream event or prompt delivery resets `lastOutputAt` and clears `stallNoticeSentAt`. Stall diagnostics render independently as `[agent] event` health notices and never decorate a `[message]` routing row.
17
- 5. **Board**: only leader writes `~/.pi/agent/tasks/<sessionKey>/board.json`; workers use exclusive-create claim/submission marker files. Task completion uses effective task verify or agent verify; failed verification returns the reviewer's findings to the claimer. The board persists across restarts; runtime state does not. `task_create` never spawns a teammate: it immediately offers newly claimable work to existing idle teammates and reports whether anyone was notified; with no living teammates it tells the leader to call `teammate_spawn`. Boards are keyed by the Pi session file (or cwd without one), so fresh sessions do not import another session's tasks unless they resume the same board directory.
18
- 6. **Reliability/UI**: per-spawn identity validation, event/message dedup, crash releases claimed tasks, role worktree diff at shutdown. Widget above the input box shows ONLY working/starting teammates as spinner rows with live activity and hides when nobody is working; idle/stopped teammates live only in the /teammate console roster; spawn prints a legacy-style `[agent] started · @name · task` transcript line; `/teammate` offers roster/board pages and message/task detail.
19
- 7. **Node-native TS**: relative imports use explicit `.ts` extensions (root tsconfig permits them), allowing node to strip-types modules in package tests.
20
11
 
21
- **Related:** [[pi-cli-print-json-usage]] [[no-global-input-interception]] [[pi-package-conventions]]
12
+ 1. Public model tools are `agent`, `work`, and `agent_event`; `/agent-teams`
13
+ is the human management surface.
14
+ 2. `agent` uses strict `delegate`, `start`, `inspect`, and exact-session `stop`
15
+ actions. Session handles include the resident and spawn incarnation.
16
+ 3. `work` owns create/list/assign/claim/submit/release/completed-only reopen/
17
+ supersede. Claim and submit are marker intents; the single-writer harness owns
18
+ acceptance and verification.
19
+ 4. `agent_event` is communication-only with `inform` or `request`. It never
20
+ completes, releases, reopens, or reassigns Work.
21
+ 5. Worker capability grants contain only `agent_event` and `work` plus requested
22
+ Pi built-ins. Verification freezes execution and archives deferred mail as Work
23
+ history.
24
+ 6. Incompatible persisted runtime snapshots fail explicitly; no silent migration
25
+ or legacy-tool fallback is allowed.
package/README.md CHANGED
@@ -1,179 +1,137 @@
1
- # Agent Teams Pi Package
1
+ # @fradser/pi-agent-teams
2
2
 
3
- Claude-Code-style collaborative agent teams for Pi: named resident teammates, a shared local task board with self-claim, and direct peer-to-peer messaging.
3
+ Agent Teams provides one final coordination surface for Pi:
4
4
 
5
- **Display Name**: Agent Teams
6
-
7
- ## What This Package Does
8
-
9
- The team leader (your Pi session) spawns named teammates (isolated resident agents / sub-agents) as long-lived child Pi processes in RPC mode. Each teammate has an isolated context, a peer inbox, and access to a shared task board. When third-party skills or engineering workflows instruct to spawn an agent, call a subagent, or delegate work to a worker agent, `teammate_spawn` is the tool to invoke. The harness—not the leader model—polls for activity: it drains leader reports, routes peer mail, applies task claims and submissions, judges verify prompts with fresh one-shot reviewers, and wakes idle teammates only when work or mail exists.
10
-
11
- The message surface is deliberately singular: `send_message` is the only messaging primitive. Workers report with `to: "leader"`; teammates address peers by name; the leader addresses teammates by name. The routing implementation varies by destination (outbox, inbox, or control stream), but callers use one schema.
12
-
13
- ## Install
14
-
15
- ```bash
16
- pi install /path/to/pi-packages/packages/agent-teams
5
+ ```text
6
+ agent
7
+ work
8
+ agent_event
9
+ /agent-teams
17
10
  ```
18
11
 
19
- Then run `/reload` in Pi.
20
-
21
- ## Agents Are Declarative Files
12
+ `agent` manages Agent sessions through strict actions:
22
13
 
23
- Agent definitions are Markdown files with frontmatter; the body is the role prompt.
24
-
25
- ```markdown
26
- ---
27
- name: isolated-security-auditor
28
- description: Read-only security reviewer in an isolated worktree
29
- tools: read,bash
30
- model: provider/model # optional; "inherit" = the leader's current model at spawn time
31
- verify: review prompt # optional role-default gate; a fresh reviewer answers VERDICT: PASS/FAIL
32
- worktree: true # optional role-default Git isolation
33
- ---
34
- Review the assigned scope for exploitable security problems. Do not edit files.
14
+ ```ts
15
+ agent({ action: "delegate", name: "reviewer", prompt: "Audit authentication" })
16
+ agent({ action: "start", name: "reviewer" })
17
+ agent({ action: "inspect", name: "reviewer", session: "session:reviewer-...:spawn-..." })
18
+ agent({ action: "stop", session: "session:reviewer-...:spawn-..." })
35
19
  ```
36
20
 
37
- Teammate model resolution at spawn time, in precedence order: an explicit
38
- `provider/model` pin wins; `inherit` pins the leader session's current model;
39
- without a pin, the unified teammate model set from `/agent-teams` (press `m`
40
- in the roster page for a type-to-filter picker) applies to this session;
41
- otherwise Pi picks its own default.
42
-
43
- Discovery precedence per name (later overrides earlier): user < project <
44
- project-local, with generated session roles filling only the names no file
45
- defines. A re-spawn that supplies an explicit inline definition replaces a
46
- previously generated session role of the same name; definition files are never
47
- overwritten by inline input.
48
-
49
- There are no built-in roles. When a needed role has no definition, the
50
- leader creates it in memory for the current session: it derives the definition
51
- from the task using the abstract role reference in `references/agent-roles.md`
52
- (definition anatomy, archetype axes, invariants), registers it, and then spawns.
53
- The role is not written to disk or reused in a later session unless the user
54
- explicitly requests persistence; only then may the leader set `definition.persist`
55
- and choose a project or project-local scope.
56
-
57
- | Scope | Location | Git semantics |
58
- |---|---|---|
59
- | user | `~/.pi/agent/agents/*.md` | system-level, never committed |
60
- | project | `<cwd>/.pi/agents/<name>.md` | **git-managed** — commit for the team |
61
- | project-local | `<cwd>/.pi/agents/<name>.local.md` | **local** — personal override, gitignore by convention |
62
- | session | in-memory registry | **ephemeral** — disappears at session start, no source file |
63
-
64
- A teammate definition is just a Markdown file you own. Shared roles live in `.pi/agents/<name>.md`; a personal tweak to that exact role is `<name>.local.md` in the SAME directory — same teammate name, local scope wins, and discovery deduplicates the pair into one entry (never two). `resolveAgent` reports each definition's `scope` and `gitManaged`; guidance lists both so the leader knows provenance.
65
-
66
- ## Build a Team
67
-
68
- ```text
69
- teammate_spawn({
70
- name: "security",
71
- agent: "reviewer",
72
- prompt: "Review the auth middleware",
73
- resources: ["packages/api/auth"]
74
- })
75
-
76
- teammate_spawn({ name: "backend", agent: "worker" })
21
+ A delegate action can include an inline definition. It is session-local unless
22
+ `persist: true` is explicitly supplied in that definition. Choose explicit minimal
23
+ canonical tool IDs (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`,
24
+ `powershell`); extension aliases such as `ffgrep` are not inherited. Omitted
25
+ `tools` or `tools: []` grants only `agent_event` and `work`, never default shell
26
+ or file access. Delegate/start results and inspection expose the recorded
27
+ `session.tools` grant, with a coordination-only warning when applicable.
28
+ Inspection uses the session's recorded grant, not a changed role definition;
29
+ legacy records without a grant leave it unknown rather than claiming no tools.
30
+
31
+ For an unknown role, provide a compact inline definition:
32
+
33
+ ```ts
34
+ agent({ action: "delegate", name: "reader", prompt: "Read README.md and cite its install command",
35
+ definition: { description: "Read evidence", prompt: "Read the assigned file and cite evidence", tools: ["read"] } })
77
36
  ```
78
37
 
79
- - Spawn accepts `name`, `agent`, an optional kickoff `prompt`, optional `resources`, and optional `handoffFrom`. Resources are stable path-like tags used to prevent overlapping mutating assignments; they are advisory to the filesystem but enforced by the task/assignment state machine.
80
- - A kickoff creates one direct assignment. After its terminal report, that worker cannot claim board work until the leader explicitly sends `reopen: true` with a distinct next assignment.
81
- - `handoffFrom` builds a successor kickoff from the predecessor's latest assignment, board claim, and recent leader reports; it never silently transfers a board claim.
82
- - Without a kickoff prompt, the teammate idles until messaged or until eligible claimable work appears.
83
- - Teammates stay alive between tasks and consume no model tokens while idle.
84
- - A session-wide cap of 8 living teammates applies.
85
- - An agent declaring `worktree: true` gets its own Git worktree; at shutdown its changes are committed onto the worktree branch, the directory is removed, and the branch is kept so captured work stays retrievable (`git diff <base>..<branch>`).
86
- - `teammate_shutdown({ name })` stops one teammate and releases its claimed task back to the board.
87
-
88
- ## Coordinate Through One Message Primitive and the Board
89
-
90
- ```text
91
- send_message({
92
- to: "security",
93
- message: "Please challenge the backend finding against the current middleware."
94
- })
95
-
96
- task_create({
97
- subject: "Fix auth middleware findings",
98
- description: "Address the confirmed security findings in packages/api/src/auth.ts",
99
- dependsOn: ["t_1"],
100
- resources: ["packages/api/auth"],
101
- supersedes: ["old-auth-patch"],
102
- verify: "Every scenario in features/gallery.feature holds in the built gallery; no horizontal overflow at 400px."
103
- })
38
+ `agent start` creates an unassigned resident without a synthetic Work ID or an
39
+ initial model turn. Presence stays `starting` until a one-shot native readiness
40
+ acknowledgement, then becomes `idle` and eligible for exact-session assignment.
41
+ The public start call awaits that bounded acknowledgement before returning, so
42
+ its exact returned session accepts an immediate assignment without a caller wait.
43
+ Rejected readiness, timeout, or early exit fails the start call; a process ID is
44
+ not readiness. Reassigning the same still-claimed Work to its exact current owner
45
+ returns the existing Assignment Attempt without a second delivery. Later claimable-board notices can still wake
46
+ the resident and activate deliberate autonomous claims. Start is not a permanent
47
+ assignment-only scheduling mode.
48
+
49
+ `work` is activated only in valid assignment/board contexts.
50
+
51
+ `work` owns Work lifecycle. Leaders create/list/assign/release/reopen completed
52
+ Work/supersede; Workers list/claim/submit/release their current Work.
53
+
54
+ ```ts
55
+ work({ action: "create", subject: "Fix storage", resources: ["firmware/storage"] })
56
+ work({ action: "assign", id: "fix-storage", target: { session: "session:worker-...:spawn-..." } })
57
+ work({ action: "claim" })
58
+ work({ action: "submit", outcome: "success", result: "Verification evidence" })
104
59
  ```
105
60
 
106
- `send_message` has one schema everywhere:
61
+ `agent_event` is communication-only. It accepts a message, a recipient or bound
62
+ reply route, and optional `inform`/`request` intent. Work lifecycle authority is
63
+ never inferred from message text.
107
64
 
108
- ```text
109
- send_message({ to, message, status? })
65
+ ```ts
66
+ agent_event({ to: "session:reviewer-...:spawn-...", message: "The policy changed", intent: "inform" })
110
67
  ```
111
68
 
112
- - Worker report: `to: "leader"`; use optional `status: "completed" | "failed"` for a terminal report. Every accepted leader-bound report — intermediate or terminal — enters Pi's follow-up queue as its own turn, even while the leader is active; Pi processes it when the current run can naturally end. After the first accepted terminal report in a wake-up sequence, later reports are suppressed until a new wake-up; distinct intermediate reports, including identical bodies before terminal status, remain deliverable, and the same content is not suppressed across assignments. For bounded reviewer assignments, put findings, the recommendation, verification evidence, and remaining risks in one concise terminal report. Use earlier reports only for genuinely new blockers, plan-changing facts, or evidence that changes the conclusion; do not send a separate status-only assignment-complete message or repeat unchanged findings. A terminal report ends the current worker turn. After a terminal report, report to the leader again only for a new assignment or decision-useful fact.
113
- - Harness lifecycle events use a separate `<harness-event>` envelope. A requested `teammate_shutdown` stays in the tool lifecycle row and console mailbox; it does not start a leader follow-up turn. Unexpected stops and actionable harness events may still wake the leader.
114
- - Peer mail: `to: "<teammate-name>"`; `status` is invalid.
115
- - Leader steering or direct assignment: `to: "<teammate-name>"`; the synchronous routing result is `steered` only when the active control stream accepts the message, otherwise `queued` while the harness owns delivery on the next wake-up.
116
- - Peer mail and reports to the leader are written to an inbox or outbox first, so their synchronous routing result is `queued`; none of these labels claims the recipient has read, understood, or processed the message.
117
- - Teammate health is reported separately as an `[agent] event` such as `@name stalled · silent 5m`; health never appears as a message-routing suffix.
118
- - The first non-empty line of `message` becomes the console title.
119
- - Peer traffic never enters the leader model context; inspect it in `/agent-teams` instead.
120
-
121
- Only the leader creates tasks. `task_create` adds pending work to the current session's board; it never spawns a teammate. An idle teammate receives a board notice only when it owns no assignment and the task's resources do not overlap another active assignment. A worker that claims a task owns it until `task_submit` completes or releases it; a terminal leader report alone never completes a board task or authorizes more claims. `supersedes` marks obsolete pending/claimed tasks as `superseded`, retargets pending downstream dependencies to the replacement, and permanently removes obsolete work from notices, claims, and completion. A live holder retains its resource lock until it acknowledges cancellation with `task_submit(status="failed")` or stops; a resumed board clears dead holders. With no living teammates, the result explicitly tells the leader to call `teammate_spawn`; with no eligible idle recipient, the task remains pending until the leader creates/opens a compatible assignment. Boards are session-keyed, so a task created in another Pi session is not automatically imported into the current session. A task-level `verify` prompt overrides the claiming agent's frontmatter `verify`. The harness runs it as a fresh one-shot reviewer: explicit `VERDICT: PASS` completes; explicit `VERDICT: FAIL - <reasons>` returns findings to the claimer. A missing verdict is **inconclusive**: the harness requests one verdict-only clarification, then escalates without counting it as a verification failure. Write acceptance criteria a reviewer can check, not shell commands.
122
-
123
- ## Tools
124
-
125
- | Tool / command | Side | Description |
126
- |---|---|---|
127
- | `teammate_spawn(name, agent, prompt?, resources?, handoffFrom?)` | Leader | Start one named resident teammate; model/worktree come from agent frontmatter |
128
- | `teammate_shutdown(name)` | Leader | Stop one teammate and release its claimed work |
129
- | `send_message(to, message, reopen?, resources?, status?)` | Both | The only messaging primitive; `to: "leader"` is reserved for worker reports |
130
- | `task_create(subject, description?, dependsOn?, verify?, resources?, supersedes?)` | Leader | Add a resource-scoped shared board task |
131
- | `task_list()` | Both | One shared board-view definition; leader view also includes the roster |
132
- | `task_claim(taskId?)` | Worker | Atomically self-claim a pending, unblocked task |
133
- | `task_submit(taskId, status, result?)` | Worker | Submit a claimed task outcome; completion passes through verify |
134
- | `/agent-teams` | User | Management console: session teammates, persistent agent roles, board, details, shutdown |
135
-
136
- That is **7 unique tool names**. There are no `teammate_run`, `teammate_fanout`, `teammate_cancel`, `teammate_retry`, or `teammate_message` tools.
137
-
138
- ## Reliability Protocol
69
+ Automatic final answers (including autonomously claimed Work) and explicit Work
70
+ submission use the same attempt-bound acceptance pipeline. Explicit submission
71
+ ends the worker turn without a second automatic submission. Successful results
72
+ reach the Leader once, after execution settles and any verification gate passes.
73
+ Execution failures are reported after settlement without claiming acceptance.
74
+ `work list` includes bounded result evidence for deliberate recovery.
75
+ Verification freezes execution and retains deferred mail as Work history.
76
+ An ordinary final answer is a successful candidate, not independent verification;
77
+ ungated acceptance does not independently verify its claims. If requested work
78
+ cannot be performed, including missing tools, workers must explicitly submit
79
+ `work({ action: "submit", outcome: "failed", result: "Blocker and unverified work" })`.
80
+ Blocker prose is not classified as failure. Failed submission produces one failed
81
+ report, not completion or dependent unlock; a rejecting verification gate also
82
+ prevents acceptance. Leaders should delegate concrete acceptance criteria and an
83
+ explicit verification gate where appropriate. Informational reports need no
84
+ acknowledgment unless a decision or action changes.
85
+ Do not narrate repeated reports. This guidance does not suppress event transport
86
+ or infer lifecycle changes from communication.
87
+
88
+ After delegation, continue independent work or end the turn: results resume the
89
+ session automatically. Use `agent inspect` for deliberate diagnosis, not repeated
90
+ polling or sleep loops. `agent_event` never creates or retries Work; messaging a
91
+ completed assignment returns its recorded result without waking the worker.
92
+ Recovery reuses the existing Work ID. Request explicit `work release` and await
93
+ authoritative release before `work assign` to the chosen exact session; a queued
94
+ release is not yet released authority. Do not create competing recovery Work via
95
+ `agent delegate`. Read-only/no-write messages are steering, not cancellation or
96
+ proof that the current tool batch stopped. Native steering takes effect at safe
97
+ tool boundaries, before peer follow-up; it cannot undo already executed writes.
98
+ Parked verification requires explicit Work release and reassignment; ordinary
99
+ messages remain deferred. Exact session handles are incarnation-bound, so a stale
100
+ handle cannot stop or inspect a replacement resident. Work persists for later inspection,
101
+ but a different session file does not import another session's tasks automatically.
102
+
103
+ ## Coordination and delivery
104
+
105
+ Leader guidance uses the same delivery contract before and after a team starts:
106
+
107
+ - Delegate separable work with a task-scoped baseline, acceptance criteria and safe local checks. Use `work create` with `dependsOn` for prerequisites and shared `resources` tags for conflicting writes; then assign eligible Work. Independent delegation is not a substitute for dependencies.
108
+ - Implementers own assigned local checks. One integration owner checks shared behavior against the integrated candidate. Determine isolation needs before verification; commands running locally may still access user data or production credentials.
109
+ - Record a revision or scoped snapshot for the candidate and exclude unrelated dirty changes. Review and integration checks judge that same candidate; later edits invalidate affected evidence rather than automatically requiring every Agent to rerun every suite.
110
+ - A completed review Work Item can report REWORK. It is not a PASS on the implementation. Review-only work ends with that report, without editing the implementation or waiting for repairs; the implementation owner owns remediation. Implementation delivery stays pending until required Work and blocking reviews have returned, findings are resolved, and applicable evidence matches the final candidate. If only results remain outstanding, yield without announcing completion.
111
+ - Group related findings by root cause. Before a bounded recheck, preserve the prior report outside the result that reopen clears, and prepare the retained baseline, new candidate fingerprint, correction delta, prior findings and safe checks. If the description already references an authoritative current-attempt brief, update that brief before reopening and assigning, then keep it stable. Reopen/assign and the reopen reason do not change the old description or forward prior findings. A fixed conflicting description needs bounded follow-up Work with a refreshed description and `dependsOn` on the completed review. Messages to a closed assignment do not restart it; use a new broad review only when scope or risk changes.
112
+
113
+ `verify` is an independent reviewer's acceptance prompt, **not a shell command**. It adds a model-based acceptance gate when appropriate; it does not replace tests or make ungated reports independently verified. The contract above is prompt guidance, not a new scheduler, automatic snapshot system, or cross-extension completion lock.
114
+
115
+ Read [the role reference](references/agent-roles.md#assignment-brief) when preparing an assignment brief.
116
+
117
+ ## Message display
118
+
119
+ Collapsed `[message]` rows use the current terminal width for their preview and
120
+ reserve the complete configured expansion-key hint. Expanded rows show the full
121
+ message once, inline after the recipient and delivery state, with natural wrapping
122
+ and preserved line breaks. Literal JSON quotes and punctuation spacing remain
123
+ unchanged; message content bypasses prose cleanup. Routing identifiers remain
124
+ model-facing, not transcript copy; real extra notes are separate from the message
125
+ and are not repeated previews.
126
+ The renderer uses the same pi-kit lifecycle abstraction as `[context] researched`;
127
+ only the content model differs: research has a distinct query and answer, while a
128
+ sent message expands its inline text.
139
129
 
140
- - **Per-spawn identity validation**: every leader report must match the teammate's current spawn id; stale callbacks and events cannot affect a replacement with the same name.
141
- - **Queued means written**: peer `send_message` succeeds only after the recipient inbox write succeeds. The harness still owns delivery into a recipient turn; only an accepted active control-stream steer is labeled `steered`.
142
- - **One writer per state file**: only the leader process writes runtime and board snapshots (atomic tmp+rename). Workers append leader reports to their outbox, peer mail to recipient inboxes, and task intent via exclusive-create marker files.
143
- - **One assignment at a time**: direct assignments and board claims are mutually exclusive. A terminal direct report closes that assignment until an explicit `reopen`; a board claim stays open until `task_submit`.
144
- - **Resource-safe board work**: overlapping path-like resource tags cannot run concurrently. Superseded tasks stay visible for audit but cannot be claimed, completed, or re-noticed.
145
- - **Completion is gated, not self-reported**: a task completes only after its effective verify gate passes when one exists; no gate means the submission itself completes it. Explicit FAIL twice parks the task with its holder and escalates once. Missing verdict text is inconclusive, gets one clarification, then escalates without incrementing the failure count.
146
- - **No caps, heartbeat only**: teammates run without turn-count or duration ceilings. The harness heartbeat tracks silence per working teammate and — after 30 minutes without any RPC output (`PI_TEAMMATE_STALL_NOTICE_MS`, 0 disables) — sends the leader one actionable health event per silence episode. The notice is the last automatic action: continuing, steering, shutting down, or respawning a context-carrying successor belongs to the leader alone. Any output or prompt delivery re-arms it. Health events carry diagnostics separately from message routing: silence duration, spawn age, and lifetime token/cost usage. A teammate with zero lifetime model output and no tool running gets flagged much earlier (5 minutes, `PI_TEAMMATE_SILENT_STALL_MS`, 0 disables): an in-flight request stuck on the provider will not recover by steering, so that notice names shutdown plus respawn as the effective remedy.
147
- - **One-shot board notices**: an idle teammate is told about a claimable task exactly once; declined tasks never re-wake it, and released tasks re-arm. Notices are paced at least five minutes apart per teammate (`PI_TEAMMATE_NOTICE_PACE_MS` overrides in milliseconds).
148
- - **One end-of-life line per teammate**: the first terminal report of a spawn incarnation renders the finish entry; shutting that incarnation down afterwards adds no second event row. Requested shutdown is process cleanup only, not proof of assignment completion; wait for the worker's terminal status report when possible. After the first accepted terminal report, later reports are suppressed until a new assignment prompt; distinct intermediate reports remain deliverable.
149
- - **Failure semantics**: an unexpected crash marks the teammate stopped, reports a diagnostic, and releases its claimed tasks.
150
-
151
- ## State and Sessions
152
-
153
- Runtime state lives in `~/.pi/agent/teammate/<sessionKey>/` and dies with the session (`state.json`, report outboxes, peer inboxes, roster). The task board lives in `~/.pi/agent/tasks/<sessionKey>/board.json` and persists for later inspection; nothing auto-deletes it. The board key is derived from the Pi session file (or cwd when no session file exists), so a fresh session with a different session file does not import another session's tasks automatically; resume the same board directory to continue them. Claims held by dead teammates return to pending on reload.
154
-
155
- ## Structure
130
+ ## Install
156
131
 
132
+ ```bash
133
+ pi install npm:@fradser/pi-agent-teams
157
134
  ```
158
- agent-teams/
159
- ├── index.ts — package-root extension entry point
160
- ├── references/ — role templates consulted when generating new agents
161
- ├── src/
162
- │ ├── index.ts — composition root and session lifecycle
163
- │ ├── tools.ts — leader tools and /agent-teams command
164
- │ ├── worker.ts — worker tools plus the shared task_list registration
165
- │ ├── team-machine.ts — resident lifecycle, mail routing, intents, verify, wake-ups
166
- │ ├── state.ts — roster, board, leader inbox state machine
167
- │ ├── spawner.ts — resident RPC process spawner (uncapped sequences)
168
- │ ├── agents.ts — agent discovery and frontmatter parsing
169
- │ ├── statefile.ts — runtime/board/mail IO and marker-file intents
170
- │ ├── ui.ts — passive widget and /agent-teams management console
171
- │ ├── guidance.ts — static leader and worker protocols
172
- │ └── follow-up-queue.ts — serialized automatic report delivery
173
- ├── features/ — BDD contract
174
- └── tests/ — package tests
175
- ```
176
-
177
- ## License
178
135
 
179
- MIT
136
+ Run `/reload` after installation. `/agent-teams` is the human management
137
+ surface for Presence, Work, diagnostics, and exact-session stop controls.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fradser/pi-agent-teams",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Multi-agent team system for Pi — resident teammates, shared task board, and peer-to-peer messaging",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -39,6 +39,6 @@
39
39
  ]
40
40
  },
41
41
  "dependencies": {
42
- "@fradser/pi-kit": "0.4.2"
42
+ "@fradser/pi-kit": "0.5.0"
43
43
  }
44
44
  }
@@ -13,8 +13,14 @@ Frontmatter:
13
13
  - `name` — unique id among discovered roles.
14
14
  - `description` — the routing contract: when the leader should choose this
15
15
  role, phrased as "use when ...". This is the most load-bearing field.
16
- - `tools` — the minimal set; follows the mutability axis (see below).
17
- - optional `model`, `verify`, `worktree`.
16
+ - `tools` — the explicit minimal set; follows the mutability axis (see below).
17
+ Canonical built-ins are `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`,
18
+ `powershell` (kept in sync with `src/worker-tools.ts`).
19
+ Omitted tools or `[]` grant only coordination tools (`agent_event`, `work`),
20
+ not file or shell access. No aliases or implicit default `bash`.
21
+ - optional `model`, `verify`, `worktree`. `verify` is an independent completion-gate
22
+ reviewer prompt, not a shell command. State the evidence and acceptance criteria
23
+ the gate should inspect; a concrete local gate is not a second broad project review.
18
24
 
19
25
  The Markdown body is the role prompt, built from five parts:
20
26
 
@@ -23,10 +29,37 @@ The Markdown body is the role prompt, built from five parts:
23
29
  observation targets, depending on the judgment axis.
24
30
  3. Boundaries — what must never be touched or changed.
25
31
  4. Evidence — what every claim or finding must carry.
26
- 5. Terminal report — delivery via `send_message(to="leader", message=...)`;
27
- the last message to the leader MUST
28
- carry status="completed" (or status="failed"); work without a terminal
29
- status counts as unfinished work.
32
+ 5. Final result — an ordinary final answer is submitted automatically for the
33
+ current Work Item after execution settles. A Worker may explicitly use
34
+ `work({ action: "submit", outcome, result? })`; communication events never
35
+ carry completion authority.
36
+
37
+ ## Assignment brief
38
+
39
+ Keep task-specific coordination in the Work description, not a permanently growing role:
40
+
41
+ - **Scope and baseline** — owned files or resources, originating requirements, and
42
+ the exact task delta, including untracked files and pre-existing dirty changes to exclude.
43
+ - **Dependencies** — required predecessor Work and any shared resource ownership.
44
+ Use `work create` with `dependsOn` for dependent work, then assign when eligible;
45
+ `agent delegate` is for independent work.
46
+ - **Verification owner** — assigned safe local checks for this Worker; one owner
47
+ integrates and checks shared behavior. Determine isolation needs before commands run.
48
+ - **Candidate and result** — identify the revision or scoped snapshot checked, commands
49
+ and outcomes, findings and limitations. Reviewer output keeps Standards and Spec
50
+ verdicts separate when both apply; REWORK is a valid completed review, not acceptance
51
+ of the implementation. Review-only assignments finish with that report, without
52
+ editing the implementation or waiting for repairs. Missing access or tools is failed
53
+ execution, not REWORK evidence.
54
+ - **Recheck handoff** — preserve the prior report outside the Work result before reopen
55
+ clears it. A reusable description should point to an authoritative brief for the current
56
+ attempt: refresh its candidate fingerprint, correction delta, baseline, findings and safe
57
+ checks before reopening/assignment, then keep it stable. A fixed conflicting description
58
+ requires bounded follow-up Work with the refreshed description and `dependsOn` on the
59
+ completed review. Reopen/assign and the reopen reason do not update the old description.
60
+
61
+ Leader delivery and pending-review rules are injected by the shared Leader guidance;
62
+ these brief fields make those rules actionable without redefining Work lifecycle here.
30
63
 
31
64
  ## Archetype axes
32
65
 
@@ -55,12 +88,18 @@ as long as every invariant below holds.
55
88
  Non-negotiable in every generated definition:
56
89
 
57
90
  - Tool sets match the mutability axis: read-only roles never receive
58
- `edit` or `write`.
91
+ `edit` or `write`. A `bash` grant is not read-only — a shell can still write,
92
+ so "read-only" here bounds the tools, not the file system, and the completion
93
+ gate relies on the reviewer's instruction rather than enforcement.
59
94
  - Every finding or claim carries evidence: exact paths, command output,
60
95
  timestamps — never bare assertions.
61
96
  - Scope is bounded: mutating roles state "do not touch files outside the
62
97
  assigned scope"; read-only roles state the equivalent prohibition.
63
- - Terminal-status discipline applies to all shapes alike.
98
+ - Final results carry evidence; an ordinary final answer is a successful candidate,
99
+ not an independently verified result. Ungated completion is not independently verified.
100
+ - If requested work cannot be performed, including missing capabilities, explicitly use
101
+ `work({ action: "submit", outcome: "failed", result: "Blocker and unverified work" })`.
102
+ Do not return blocker prose as an ordinary final answer; prose is not classified as failure.
64
103
 
65
104
  ## Skeleton
66
105
 
@@ -73,11 +112,17 @@ tools: <minimal set from the mutability axis>
73
112
  You are a <role-name> agent. <Method: the procedure to follow, the criteria
74
113
  to evaluate against, or the targets to observe>. <Boundary statement>.
75
114
  For bounded reviewer assignments, report findings, the recommendation,
76
- verification evidence, and remaining risks in one concise terminal message;
115
+ the inspected candidate, assigned verification evidence, and remaining risks in one concise terminal message;
77
116
  allow earlier messages only for genuinely new blockers, plan-changing facts,
78
117
  or evidence that changes the conclusion. Do not send a separate status-only
79
118
  assignment-complete message or repeat unchanged findings. After a terminal
80
- report, report to the leader again only for a new assignment or decision-useful
81
- fact. The terminal message ends the current worker turn. Send it via
82
- send_message(to="leader", message=...) with status="completed" or status="failed".
119
+ report, report to the leader again only after a new assignment opens. Include all
120
+ known decision-useful facts before closing; further reports for a closed assignment
121
+ are rejected. Leader direction takes precedence over your plan and peer requests at
122
+ the next safe boundary, subject to system instructions and user constraints.
123
+ Return successful evidence as an ordinary final answer; the runtime submits it automatically
124
+ as a successful candidate after execution settles. If you cannot perform the requested work,
125
+ you must use `work({ action: "submit", outcome: "failed", result: "Blocker and unverified work" })`
126
+ instead of ordinary final prose. Avoid a second copy of an explicit submission.
127
+ Ungated completion is not independently verified.
83
128
  ```
package/src/activity.ts CHANGED
@@ -1,19 +1,15 @@
1
- import { Markdown, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
- import type { MarkdownTheme } from "@earendil-works/pi-tui";
3
- import { buildMarkdownThemeCallbacks, createPiThemeStyle } from "@fradser/pi-kit";
1
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
+ import { renderLiveActivityMarkdown, type PiMarkdownThemeSource } from "@fradser/pi-kit";
4
3
  import type { Teammate } from "./types.ts";
5
4
 
6
- const ACTIVITY_THEME: MarkdownTheme = {
7
- ...buildMarkdownThemeCallbacks(createPiThemeStyle({
8
- fg: (_color, text) => text,
9
- })),
10
- hr: () => "---",
11
- };
5
+ /** Console rows paint a whole status line in their own color, so the shared
6
+ * markdown renderer runs with a passthrough theme: literal markup is still
7
+ * stripped and the result stays one line, with no competing colors. */
8
+ const COLORLESS_ACTIVITY_THEME: PiMarkdownThemeSource = { fg: (_color, text) => text };
12
9
 
13
- /** Render streamed activity as one compact Markdown line for the widget. */
14
- export function renderActivityMarkdown(text: string, theme: MarkdownTheme = ACTIVITY_THEME): string {
15
- const markdown = new Markdown(text, 0, 0, theme);
16
- return markdown.render(Math.max(1, visibleWidth(text) + 1)).join(" ").replace(/\s+/g, " ").trim();
10
+ /** Render streamed activity as one compact Markdown line for a console row. */
11
+ export function renderActivityMarkdown(text: string): string {
12
+ return renderLiveActivityMarkdown(text, COLORLESS_ACTIVITY_THEME);
17
13
  }
18
14
 
19
15
  function extractLatestLine(text: string | undefined): string | undefined {
@@ -47,7 +43,7 @@ export interface TeammateRowWidths {
47
43
  /** Reserve enough width for the live activity before truncating the identity. */
48
44
  export function teammateRowWidths(spinner: string, agent: string, width: number): TeammateRowWidths {
49
45
  const lineWidth = Math.max(10, width - 1);
50
- const fixedWidth = visibleWidth(` ${spinner} · `);
46
+ const fixedWidth = visibleWidth(`${spinner} · `);
51
47
  const availableWidth = Math.max(2, lineWidth - fixedWidth);
52
48
  const nameWidth = Math.max(1, Math.min(visibleWidth(agent), Math.floor(availableWidth * 0.4)));
53
49
  return {
@@ -57,30 +53,13 @@ export function teammateRowWidths(spinner: string, agent: string, width: number)
57
53
  };
58
54
  }
59
55
 
60
- /** Fit one widget row to its terminal width while keeping it on one line. */
61
- export function fitTeammateRow(
62
- spinner: string,
63
- agent: string,
64
- activity: string,
65
- width: number,
66
- formatActivity: (text: string) => string = (text) => text,
67
- markdownTheme: MarkdownTheme = ACTIVITY_THEME,
68
- ): string {
69
- const sizes = teammateRowWidths(spinner, agent, width);
70
- const nameText = truncateToWidth(agent, sizes.nameWidth);
71
- const activityText = formatActivity(truncateToWidth(renderActivityMarkdown(activity, markdownTheme), sizes.activityWidth));
72
- const line = ` ${spinner} ${nameText} · ${activityText}`;
73
- return truncateToWidth(line, sizes.lineWidth);
74
- }
75
-
76
56
  /** Fit a console status label to the requested activity width. */
77
57
  export function formatTeammateLabel(
78
58
  spinner: string,
79
59
  activity: string,
80
60
  maxActivityWidth?: number,
81
- markdownTheme: MarkdownTheme = ACTIVITY_THEME,
82
61
  ): string {
83
- if (maxActivityWidth === undefined) return `${spinner} ${renderActivityMarkdown(activity, markdownTheme)}`;
62
+ if (maxActivityWidth === undefined) return `${spinner} ${renderActivityMarkdown(activity)}`;
84
63
  if (maxActivityWidth <= 0) return spinner;
85
- return `${spinner} ${truncateToWidth(renderActivityMarkdown(activity, markdownTheme), maxActivityWidth)}`;
64
+ return `${spinner} ${truncateToWidth(renderActivityMarkdown(activity), maxActivityWidth)}`;
86
65
  }