@tmustier/pi-agent-teams 0.4.0 → 0.5.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ ## 0.5.1
4
+
5
+ ### Features
6
+
7
+ - **Automatic startup GC** — on session start, silently removes stale team directories older than 24h (fire-and-forget, never blocks). Reuses the existing `gcStaleTeamDirs()` with age + state checks. (Thanks **@RensTillmann** — #8, #30)
8
+ - **Exit cleanup of empty team dirs** — on session shutdown, deletes the session's own team directory if it has no tasks in any namespace, no active teammates (RPC or manual), and no attach claim from another session. (Thanks **@RensTillmann** — #8, #30)
9
+
10
+ ### Fixes
11
+
12
+ - Added `excludeTeamIds` parameter to `gcStaleTeamDirs()` to prevent startup GC from removing the current session's team (important for resumed sessions older than 24h).
13
+
14
+ ## 0.5.0
15
+
16
+ ### Features
17
+
18
+ - **DM routing to leader LLM context** — Teammate DMs are now injected into the leader's conversation via `sendLeaderLlmMessage` instead of only showing in the TUI. The leader can now act on DM requests autonomously. (Thanks **@davidsu** — #6, #29)
19
+ - **Batch-complete auto-wake** — `DelegationTracker` tracks task ID batches from `delegate()` calls. When all tasks in a batch complete, the idle leader is automatically woken to review results and continue orchestrating. Quality-gate aware. (Thanks **@RensTillmann** — #7, #29)
20
+ - **Worker completion messages in leader context** — Per-task completion/failure notifications injected into the leader LLM conversation with task subject, result summary, and progress counters. All-done detection warns when quality gates are still running. (#13)
21
+ - **Ergonomic worker status** — Real-time time-in-state, stall detection (configurable via `PI_TEAMS_STALL_THRESHOLD_MS`), last message summary, and model-per-worker in panel detail view. `member_status` tool action for agent-driven orchestration. (#10)
22
+ - **Tool call content in transcript** — Worker transcript view shows tool arguments inline: file paths, commands, patterns. Errors marked with ✗. (#18, #21, #23)
23
+ - **`/team done` + auto-done detection** — `/team done` ends a run (stops teammates, hides widget, notifies with summary). Widget auto-detects when all tasks complete. (#16)
24
+ - **Hook/model policy in panel** — Compact policy summary (hooks status, failure action, reopens, model inheritance) shown in widget and panel. (#20)
25
+ - **Model, thinking, task in spawn/panel** — Visible in spawn output, panel detail, and transcript header. (#19)
26
+ - **Urgent message interrupts** — `--urgent` flag on `/team dm` and `/team broadcast` interrupts active worker turns via steering. (#15)
27
+ - **Hook contract versioning** — Formal versioning and compatibility policy for quality-gate hooks. (#24)
28
+
29
+ ### Fixes
30
+
31
+ - **Worktree/branch auto-cleanup** — Stale team dirs, worktrees, and branches cleaned up on shutdown and session switch. (#14)
32
+ - **`/team status` in README** — Added missing command to docs table. (#27)
33
+ - **Activity tracker** — Added `extractStartSummary`/`extractEndSummary` for tool transcript display.
34
+
35
+ ## 0.4.0
36
+
37
+ Initial public release.
package/README.md CHANGED
@@ -10,15 +10,16 @@ Core agent-teams primitives, matching Claude's design:
10
10
 
11
11
  - **Shared task list** — file-per-task on disk with three states (pending / in-progress / completed) and dependency tracking so blocked tasks stay blocked until their prerequisites finish.
12
12
  - **Auto-claim** — idle teammates automatically pick up the next unassigned, unblocked task. No manual dispatching required (disable with `PI_TEAMS_DEFAULT_AUTO_CLAIM=0`).
13
- - **Direct messages and broadcast** — send a message to one teammate or all of them at once, via file-based mailboxes.
13
+ - **Direct messages and broadcast** — send a message to one teammate or all of them at once, via file-based mailboxes. Urgent messages can interrupt active turns via steering.
14
14
  - **Graceful lifecycle** — spawn, stop, shutdown (with handshake), or kill teammates. The leader tracks who's online, idle, or streaming.
15
15
  - **LLM-callable teams tool** — the model can spawn teammates, delegate tasks, mutate task assignment/status/dependencies, message teammates, and run lifecycle actions in tool calls (no slash commands needed).
16
- - **Team cleanup** — tear down all team artifacts (tasks, mailboxes, sessions, worktrees) when you're done.
16
+ - **Team done + cleanup** — `/team done` ends a run (stops teammates, hides the widget, notifies with a summary); the widget auto-detects when all tasks are complete and shows a hint. `/team cleanup` tears down artifacts afterward.
17
17
 
18
18
  Additional Pi-specific capabilities:
19
19
 
20
20
  - **Git worktrees** — optionally give each teammate its own worktree so they work on isolated branches without conflicting edits.
21
21
  - **Session branching** — clone the leader's conversation context into a teammate so it starts with full awareness of the work so far, instead of from scratch.
22
+ - **Completion notifications** — when a teammate finishes or fails a task, the leader LLM receives a structured `[Team]` message with task ID, subject, result summary, and progress counters so it can orchestrate autonomously without human intervention. When quality-gate hooks are active, the message warns that task states may still change.
22
23
  - **Hooks / quality gates** — optional leader-side hooks on idle / task completion to run scripts (opt-in).
23
24
 
24
25
  ## UI style (terminology + naming)
@@ -110,9 +111,13 @@ Or drive it manually:
110
111
 
111
112
  /tw # open the interactive widget panel
112
113
 
114
+ /team done # end run: stop teammates + hide widget
115
+ /team done --force # end run even with in-progress tasks
116
+
113
117
  /team shutdown alice # graceful shutdown (handshake)
114
118
  /team shutdown # stop all teammates (leader session remains active)
115
- /team cleanup # remove team artifacts when done
119
+ /team cleanup # remove team artifacts (worktrees, branches, team dir)
120
+ /team gc --dry-run # preview stale team dirs that would be removed
116
121
  ```
117
122
 
118
123
  Or let the model drive it with the delegate tool:
@@ -143,13 +148,15 @@ Or let the model drive it with the delegate tool:
143
148
  | `task_dep_add` | `taskId`, `depId` | Add dependency edge (`taskId` depends on `depId`). |
144
149
  | `task_dep_rm` | `taskId`, `depId` | Remove dependency edge. |
145
150
  | `task_dep_ls` | `taskId` | Inspect dependency/block graph for one task. |
146
- | `message_dm` | `name`, `message` | Send mailbox DM to one teammate. |
147
- | `message_broadcast` | `message` | Send mailbox message to all discovered workers. |
151
+ | `message_dm` | `name`, `message` | Send mailbox DM to one teammate. Set `urgent=true` to interrupt their active turn. |
152
+ | `message_broadcast` | `message` | Send mailbox message to all discovered workers. Set `urgent=true` to interrupt active turns. |
148
153
  | `message_steer` | `name`, `message` | Send steer instruction to a running RPC teammate. |
149
154
  | `member_spawn` | `name` | Spawn one teammate (supports context/workspace/model/thinking/plan options). |
155
+ | `member_status` | optional `name` | Real-time worker status: activity, time in state, stall detection, tool use, tokens, last message. Omit name for all-worker summary. |
150
156
  | `member_shutdown` | `name` or `all=true` | Request graceful shutdown via mailbox handshake. |
151
157
  | `member_kill` | `name` | Force-stop one RPC teammate and unassign active tasks. |
152
158
  | `member_prune` | _(none)_ | Mark stale non-RPC workers offline (`all=true` to force). |
159
+ | `team_done` | _(none)_ | End team run: stop all teammates, mark workers offline, hide widget (`all=true` to force with in-progress tasks). |
153
160
  | `plan_approve` | `name` | Approve pending plan for a plan-required teammate. |
154
161
  | `plan_reject` | `name` | Reject pending plan (`feedback` optional). |
155
162
  | `hooks_policy_get` | _(none)_ | Read team hooks policy (configured + effective with env fallback). |
@@ -163,11 +170,13 @@ Example calls:
163
170
  { "action": "task_assign", "taskId": "12", "assignee": "alice" }
164
171
  { "action": "task_dep_add", "taskId": "12", "depId": "7" }
165
172
  { "action": "message_broadcast", "message": "Sync: finishing this milestone" }
173
+ { "action": "message_dm", "name": "alice", "message": "Stop using lib X, use Y instead", "urgent": true }
166
174
  { "action": "member_kill", "name": "alice" }
167
175
  { "action": "plan_approve", "name": "alice" }
168
176
  { "action": "hooks_policy_get" }
169
177
  { "action": "hooks_policy_set", "hookFailureAction": "reopen_followup", "hookMaxReopensPerTask": 2, "hookFollowupOwner": "member" }
170
178
  { "action": "model_policy_get" }
179
+ { "action": "team_done" }
171
180
  { "action": "model_policy_check", "model": "openai-codex/gpt-5.1-codex-mini" }
172
181
  ```
173
182
 
@@ -189,6 +198,7 @@ All management commands live under `/team`.
189
198
  | --- | --- |
190
199
  | `/team spawn <name> [fresh\|branch] [shared\|worktree] [plan] [--model <provider>/<modelId>] [--thinking <level>]` | Start a teammate |
191
200
  | `/team list` | List teammates and their status |
201
+ | `/team status [name]` | Real-time worker state: stall detection, time in state, activity (omit name for summary) |
192
202
  | `/team panel` | Interactive widget panel (same as `/tw`) |
193
203
  | `/team attach list` | Discover existing team workspaces under `<teamsRoot>` |
194
204
  | `/team attach <teamId> [--claim]` | Attach this session to an existing team workspace (`--claim` force-takes over an active claim) |
@@ -199,14 +209,16 @@ All management commands live under `/team`.
199
209
  | `/team style <name>` | Set style (built-in or custom) |
200
210
  | `/team send <name> <msg>` | Send a prompt over RPC |
201
211
  | `/team steer <name> <msg>` | Redirect an in-flight run |
202
- | `/team dm <name> <msg>` | Send a mailbox message |
203
- | `/team broadcast <msg>` | Message all teammates |
212
+ | `/team dm <name> [--urgent] <msg>` | Send a mailbox message (`--urgent` interrupts active turns) |
213
+ | `/team broadcast [--urgent] <msg>` | Message all teammates (`--urgent` interrupts active turns) |
204
214
  | `/team stop <name> [reason]` | Abort current work (resets task to pending) |
205
215
  | `/team shutdown <name> [reason]` | Graceful shutdown (handshake) |
206
216
  | `/team shutdown` | Stop all teammates (RPC + best-effort manual) (leader session remains active) |
207
217
  | `/team prune [--all]` | Mark stale manual teammates offline (hides them in widget) |
208
218
  | `/team kill <name>` | Force-terminate |
209
- | `/team cleanup [--force]` | Delete team artifacts |
219
+ | `/team done [--force]` | End run: stop teammates + hide widget (auto-detects when all tasks complete) |
220
+ | `/team cleanup [--force]` | Delete team artifacts (worktrees, branches, team dir) |
221
+ | `/team gc [--dry-run] [--force] [--max-age-hours=N]` | Garbage-collect stale team dirs older than N hours (default: 24) |
210
222
  | `/team id` | Print team/task-list IDs and paths |
211
223
  | `/team env <name>` | Print env vars to start a manual teammate |
212
224
 
@@ -216,11 +228,34 @@ Model inheritance note:
216
228
  - Explicit deprecated `--model` overrides are rejected.
217
229
  - Agents can introspect/check this at runtime via `teams` actions: `{ "action": "model_policy_get" }` and `{ "action": "model_policy_check", "model": "..." }`.
218
230
 
231
+ ### Policy visibility
232
+
233
+ Both the persistent widget and the interactive panel (`/tw`) show a compact policy
234
+ summary below the header:
235
+
236
+ - **Hooks**: on/off status, `failureAction`, `maxReopensPerTask`, `followupOwner` (effective values from team config + env fallback)
237
+ - **Model**: leader model with deprecation warning when applicable, teammate selection source (`inherit`/`override`/`default`)
238
+
239
+ These values reflect the same resolution as the `hooks_policy_get` and `model_policy_get`
240
+ tool actions, but are continuously visible — no extra tool calls needed.
241
+
242
+ ### Ergonomic worker status
243
+
244
+ The widget and panel show real-time worker state at a glance:
245
+
246
+ - **Time in state**: how long a worker has been in its current status (e.g. `3m12s`)
247
+ - **Stall detection**: when a streaming worker hasn't emitted any agent event for > 5 minutes, status changes to `⚠ stalled` (configurable via `PI_TEAMS_STALL_THRESHOLD_MS`)
248
+ - **Last message summary**: most recent assistant text (first 80–100 chars) visible in the panel's selected-worker detail section
249
+ - **Model per worker**: shown in the panel detail view when available
250
+ - **Current activity**: tool verb (e.g. `running…`, `editing…`) displayed inline
251
+
252
+ The `member_status` tool action provides the same information programmatically for agent-driven orchestration — no need to parse JSONL files or check file modification times.
253
+
219
254
  ### Panel shortcuts (`/tw` / `/team panel`)
220
255
 
221
256
  - `↑/↓` or `w/s`: select teammate / scroll transcript
222
257
  - `1..9`: jump directly to teammate in overview
223
- - `enter`: open selected teammate transcript
258
+ - `enter`: open selected teammate transcript (shows tool args inline: file paths, commands, patterns; errors marked with ✗)
224
259
  - `t` or `shift+t`: open selected teammate task list (task-centric view with deps/blocks); in task view, toggle back (`esc`/`t`/`shift+t`)
225
260
  - task view: `c` complete, `p` pending, `i` in-progress, `u` unassign, `r` reassign selected task
226
261
  - `m` or `d`: compose message to selected teammate
@@ -257,6 +292,7 @@ Model inheritance note:
257
292
  | `PI_TEAMS_HOOKS_MAX_REOPENS_PER_TASK` | Reopen cap per task when failure action includes `reopen` (`0` disables auto-reopen) | `3` |
258
293
  | `PI_TEAMS_HOOKS_FOLLOWUP_OWNER` | Follow-up owner policy: `member`, `lead`, `none` | `member` |
259
294
  | `PI_TEAMS_HOOKS_CREATE_TASK_ON_FAILURE` | Legacy shortcut for `PI_TEAMS_HOOKS_FAILURE_ACTION=followup` | `0` (off) |
295
+ | `PI_TEAMS_STALL_THRESHOLD_MS` | Threshold (ms) before a streaming worker with no events is flagged as "stalled" | `300000` (5 min) |
260
296
 
261
297
  ## Storage layout
262
298
 
@@ -305,6 +341,8 @@ Hooks run with working directory = the **leader session cwd** and receive contex
305
341
  - `PI_TEAMS_MEMBER`
306
342
  - `PI_TEAMS_TASK_ID`, `PI_TEAMS_TASK_SUBJECT`, `PI_TEAMS_TASK_OWNER`, `PI_TEAMS_TASK_STATUS`
307
343
 
344
+ See [`docs/hook-contract.md`](docs/hook-contract.md) for the full versioned contract, JSON schema, and compatibility policy.
345
+
308
346
  Hook policy can be controlled by agents at runtime via `teams` tool actions:
309
347
 
310
348
  - `{ "action": "hooks_policy_get" }`
@@ -385,6 +423,14 @@ Deterministic leader-side integration flow that verifies failed `on_task_complet
385
423
  - follow-up task is created/assigned when policy includes `followup`
386
424
  - remediation mailbox nudge is emitted for the responsible teammate
387
425
 
426
+ ### Integration: cleanup and garbage collection
427
+
428
+ ```bash
429
+ npm run integration-cleanup-test
430
+ ```
431
+
432
+ Tests worktree/branch cleanup lifecycle, full team directory removal, GC age/activity filtering, and filesystem fallback when no git context is available.
433
+
388
434
  ### tmux dogfooding
389
435
 
390
436
  ```bash
package/WORKFLOW.md ADDED
@@ -0,0 +1,110 @@
1
+ ---
2
+ tracker:
3
+ kind: linear
4
+ api_key: "$LINEAR_API_KEY"
5
+ team_key: "SYM"
6
+ project_slug: "db8e51049f48"
7
+ active_states:
8
+ - Todo
9
+ - In Progress
10
+ - In Review
11
+ - Merging
12
+ - Rework
13
+ terminal_states:
14
+ - Done
15
+ - Closed
16
+ - Cancelled
17
+ - Canceled
18
+ - Duplicate
19
+ polling:
20
+ interval_ms: 30000
21
+ workspace:
22
+ root: "$PI_SYMPHONY_WORKSPACE_ROOT"
23
+ worker:
24
+ runtime: pi
25
+ agent:
26
+ max_concurrent_agents: 4
27
+ max_turns: 8
28
+ max_retry_backoff_ms: 300000
29
+ codex:
30
+ turn_timeout_ms: 3600000
31
+ read_timeout_ms: 5000
32
+ stall_timeout_ms: 300000
33
+ pi:
34
+ command: pi
35
+ response_timeout_ms: 60000
36
+ session_dir_name: .pi-rpc-sessions
37
+ model:
38
+ provider: anthropic
39
+ model_id: claude-opus-4-6
40
+ thinking_level: xhigh
41
+ extension_paths:
42
+ - ../extensions/workspace-guard/index.ts
43
+ - ../extensions/proof/index.ts
44
+ - ../extensions/linear-graphql/index.ts
45
+ disable_extensions: true
46
+ disable_themes: true
47
+ orchestration:
48
+ phase_store: workpad
49
+ default_phase: implementing
50
+ passive_phases:
51
+ - waiting_for_checks
52
+ - waiting_for_human
53
+ - blocked
54
+ max_rework_cycles: 3
55
+ ownership:
56
+ required_label: symphony
57
+ required_workpad_marker: "## Symphony Workpad"
58
+ rollout:
59
+ mode: mutate
60
+ preflight_required: true
61
+ kill_switch_label: no-symphony-automation
62
+ kill_switch_file: /tmp/pi-symphony.pause
63
+ pr:
64
+ auto_create: true
65
+ base_branch: main
66
+ repo_slug: tmustier/pi-agent-teams
67
+ reuse_branch_pr: true
68
+ closed_pr_policy: new_branch
69
+ attach_to_tracker: true
70
+ required_labels:
71
+ - symphony
72
+ review_comment_mode: upsert
73
+ review_comment_marker: "<!-- symphony-review -->"
74
+ review:
75
+ enabled: true
76
+ agent: pr-reviewer
77
+ output_format: structured_markdown_v1
78
+ max_passes: 2
79
+ fix_consideration_severities:
80
+ - P0
81
+ - P1
82
+ - P2
83
+ merge:
84
+ mode: disabled
85
+ executor: gh
86
+ method: squash
87
+ strategy: queue
88
+ max_rebase_attempts: 2
89
+ require_green_checks: true
90
+ require_head_match: true
91
+ require_human_approval: true
92
+ approval_states:
93
+ - Merging
94
+ hooks:
95
+ timeout_ms: 60000
96
+ observability:
97
+ dashboard_enabled: true
98
+ refresh_ms: 1000
99
+ render_interval_ms: 16
100
+ server:
101
+ port: 4041
102
+ host: 127.0.0.1
103
+ ---
104
+
105
+ Before starting implementation, read and apply the agent-friendly-design skill at:
106
+ `/Users/thomasmustier/.pi/agent/skills/agent-friendly-design/SKILL.md`
107
+
108
+ This project builds interfaces that AI agents operate (CLIs, tools, extensions, UI surfaces).
109
+ Every change should follow agent-friendly design principles: structured output, progressive disclosure,
110
+ clear error classification, token-budget awareness, and discoverability.
@@ -1,6 +1,6 @@
1
1
  # Claude Agent Teams parity roadmap (pi-agent-teams)
2
2
 
3
- Last updated: 2026-02-10
3
+ Last updated: 2026-03-21
4
4
 
5
5
  This document tracks **feature parity gaps** between:
6
6
 
@@ -8,12 +8,12 @@ This document tracks **feature parity gaps** between:
8
8
  - https://code.claude.com/docs/en/agent-teams#control-your-agent-team
9
9
  - https://code.claude.com/docs/en/interactive-mode#task-list
10
10
 
11
- and this repositorys implementation:
11
+ ...and this repository's implementation:
12
12
 
13
13
  - `pi-agent-teams` (Pi extension)
14
14
 
15
15
  > Terminology note: this extension supports `PI_TEAMS_STYLE=<style>`.
16
- > This doc often uses comrade as a generic stand-in for worker/teammate”, but **styles can customize terminology, naming, and lifecycle copy**.
16
+ > This doc often uses "comrade" as a generic stand-in for "worker/teammate", but **styles can customize terminology, naming, and lifecycle copy**.
17
17
  > Built-ins: `normal`, `soviet`, `pirate`. Custom styles live under `~/.pi/agent/teams/_styles/`.
18
18
 
19
19
  ## Scope / philosophy
@@ -30,8 +30,8 @@ This document tracks **feature parity gaps** between:
30
30
 
31
31
  These are intentional differences / additions:
32
32
 
33
- - **Configurable styles** (`/team style …`) for terminology + naming + lifecycle copy.
34
- - **Git worktrees** for isolation (`/team spawn <name> worktree`).
33
+ - **Configurable styles** (`/team style ...`) for terminology + naming + lifecycle copy.
34
+ - **Git worktrees** for isolation (`/team spawn <name> ... worktree`).
35
35
  - **Session branching** (clone leader context into a teammate).
36
36
  - A **status widget + interactive panel** (`/tw`, `/team panel`).
37
37
 
@@ -41,19 +41,20 @@ Legend: ✅ implemented • 🟡 partial • ❌ missing
41
41
 
42
42
  | Area | Claude docs behavior | Pi Teams status | Notes / next step | Priority |
43
43
  | --- | --- | --- | --- | --- |
44
- | Enablement | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` + settings | N/A | Pi extension is available when installed/loaded. | |
44
+ | Enablement | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` + settings | N/A | Pi extension is available when installed/loaded. | - |
45
45
  | Team config | `~/.claude/teams/<team>/config.json` w/ members | ✅ | `extensions/teams/team-config.ts` (under `~/.pi/agent/teams/...` or `PI_TEAMS_ROOT_DIR`). | P0 |
46
46
  | Task list (shared) | `~/.claude/tasks/<taskListId>/` + states + deps | ✅ | File-per-task + deps (`blockedBy`/`blocks`); `/team task dep add|rm|ls`; self-claim skips blocked tasks. | P0 |
47
47
  | Self-claim | Comrades self-claim next unassigned, unblocked task; file locking | ✅ | `claimNextAvailableTask()` + locks; enabled by default (`PI_TEAMS_DEFAULT_AUTO_CLAIM=1`). | P0 |
48
48
  | Explicit assign | Lead assigns task to comrade | ✅ | `/team task assign` sets owner + pings via mailbox. | P0 |
49
- | Message vs broadcast | Send to one comrade or all comrades | ✅ | `/team dm` + `/team broadcast` use mailbox; `/team send` uses RPC. Recipients = config workers + RPC map + active task owners. | P0 |
50
- | Comrade↔comrade messaging | Comrades message each other directly | ✅ | Worker tool `team_message`; messages via mailbox + CC leader via `peer_dm_sent`. | P1 |
49
+ | "Message" vs "broadcast" | Send to one comrade or all comrades | ✅ | `/team dm` + `/team broadcast` use mailbox; `/team send` uses RPC. Recipients = config workers + RPC map + active task owners. | P0 |
50
+ | Comrade↔comrade messaging | Comrades message each other directly | ✅ | Worker tool `team_message` (with `urgent` flag for mid-turn interrupts); messages via mailbox + CC leader via `peer_dm_sent`. | P1 |
51
51
  | Display modes | In-process selection (Shift+Up/Down); split panes (tmux/iTerm) | ❌ | Pi has widget/panel + commands, but no terminal-level comrade navigation/panes. | P2 |
52
52
  | Delegate mode | Lead restricted to coordination-only tools | ✅ | `/team delegate [on|off]`; `tool_call` blocks `bash/edit/write`; widget shows `[delegate]`. | P1 |
53
- | Plan approval | Comrade can be plan required and needs lead approval to implement | ✅ | `/team spawn <name> plan` → read-only tools; sends `plan_approval_request`; `/team plan approve|reject`. | P1 |
54
- | Shutdown handshake | Lead requests shutdown; comrade can approve/reject | ✅ | Protocol: `shutdown_request` → `shutdown_approved` / `shutdown_rejected`. `/team shutdown <name>` (graceful), `/team kill <name>` (SIGTERM). Wording is style-controlled (e.g. was asked to shut down”, walked the plank). | P1 |
55
- | Cleanup team | Clean up the team removes shared resources after comrades stopped | ✅ | `/team cleanup [--force]` deletes only `<teamsRoot>/<teamId>` after safety checks. | P1 |
53
+ | Plan approval | Comrade can be "plan required" and needs lead approval to implement | ✅ | `/team spawn <name> plan` → read-only tools; sends `plan_approval_request`; `/team plan approve|reject`. | P1 |
54
+ | Shutdown handshake | Lead requests shutdown; comrade can approve/reject | ✅ | Protocol: `shutdown_request` → `shutdown_approved` / `shutdown_rejected`. `/team shutdown <name>` (graceful), `/team kill <name>` (SIGTERM). Wording is style-controlled (e.g. "was asked to shut down", "walked the plank"). | P1 |
55
+ | Cleanup team | "Clean up the team" removes shared resources after comrades stopped | ✅ | `/team done [--force]` ends run (stops teammates, hides widget, auto-detects completion). `/team cleanup [--force]` deletes artifacts. | P1 |
56
56
  | Hooks / quality gates | `ComradeIdle`, `TaskCompleted` hooks | 🟡 | Optional leader-side hook runner (idle/task-complete/task-fail) via `PI_TEAMS_HOOKS_ENABLED=1` + scripts under `_hooks/`; inline failure surfacing + failure-action policies (`warn`/`followup`/`reopen`/`reopen_followup`) implemented; stable hook context payload exposed via `PI_TEAMS_HOOK_CONTEXT_JSON` + auto-remediation flow (reopen cap / follow-up owner policy / teammate notification). Runtime policy changes are agent-invocable via `teams` actions (`hooks_policy_get` / `hooks_policy_set`). | P2 |
57
+ | Widget liveliness | Status updates in near real-time | ✅ | Event-driven widget refresh on teammate tool start/end and turn completion; auto-done detection with `/team done` hint. | P2 |
57
58
  | Task list UX | Ctrl+T toggle; show all/clear tasks by asking | 🟡 | Widget + `/team task list` + `/team task show` + `/team task clear`; panel supports fast `t`/`shift+t` toggle into task-centric view (`Ctrl+T` is reserved by Pi for thinking blocks). | P0 |
58
59
  | Shared task list across sessions | `CLAUDE_CODE_TASK_LIST_ID=...` | ✅ | Worker env: `PI_TEAMS_TASK_LIST_ID` (manual workers). Leader: `/team task use <taskListId>` (persisted). Newly spawned workers inherit; existing workers need restart. | P1 |
59
60
  | Join/attach flow | Join existing team context from another running session | 🟡 | `/team attach list`, `/team attach <teamId> [--claim]`, `/team detach` plus claim heartbeat/takeover handshake added. Widget/panel now show attached-mode banner + detach hint. | P2 |
@@ -94,13 +95,14 @@ Legend: ✅ implemented • 🟡 partial • ❌ missing
94
95
  - `/team cleanup [--force]` deletes only `<teamsRoot>/<teamId>` after safety checks.
95
96
 
96
97
  8) **Peer-to-peer messaging** ✅
97
- - Worker tool `team_message`
98
+ - Worker tool `team_message` with `urgent` flag for mid-turn interrupts
98
99
  - Mailbox transport; leader CC notifications
100
+ - Urgent messages delivered via `sendUserMessage({ deliverAs: "steer" })` to interrupt active turns
99
101
 
100
102
  9) **Shared task list across sessions** ✅
101
103
  - `/team task use <taskListId>` + persisted `config.json`
102
104
 
103
- ### P2: UX + product-level parity
105
+ ### P2: UX + "product-level" parity
104
106
 
105
107
  10) **Hooks / quality gates** 🟡 (partial)
106
108
  - Implemented: optional leader-side hook runner (opt-in + timeout + logs).
@@ -123,6 +125,7 @@ Legend: ✅ implemented • 🟡 partial • ❌ missing
123
125
  - Implemented: agent-invocable lifecycle actions via `teams` tool (`member_spawn|shutdown|kill|prune`).
124
126
  - Implemented: agent-invocable governance actions via `teams` tool (`plan_approve|plan_reject`).
125
127
  - Implemented: agent-invocable model policy introspection/check actions via `teams` tool (`model_policy_get|model_policy_check`) to validate spawn overrides before execution.
128
+ - Implemented: agent-invocable end-of-run via `teams` tool (`team_done`) with structured error classification (`status`/`reason`/`hint`).
126
129
  - Next: optional tmux split-pane integration and deeper dependency/task editing flows in panel.
127
130
 
128
131
  12) **Join/attach flow** 🟡 (partial)
@@ -0,0 +1,183 @@
1
+ # Hook Contract
2
+
3
+ Versioned specification for the interface between pi-agent-teams and hook scripts.
4
+
5
+ Hook scripts are external programs (`.js`, `.mjs`, `.sh`, or bare executables) that run
6
+ on lifecycle events. This document defines what hooks receive and what pi-agent-teams
7
+ guarantees across releases.
8
+
9
+ ## Current version
10
+
11
+ **Contract version: 1**
12
+
13
+ Exported as `HOOK_CONTRACT_VERSION` from `extensions/teams/hooks.ts`.
14
+
15
+ ## Contract surface
16
+
17
+ Hooks receive context through two channels:
18
+
19
+ | Channel | Format | Key / field |
20
+ |---|---|---|
21
+ | Environment variable | string | `PI_TEAMS_HOOK_CONTEXT_VERSION` — the contract version as a string |
22
+ | Environment variable | JSON string | `PI_TEAMS_HOOK_CONTEXT_JSON` — structured payload (schema below) |
23
+ | Environment variables | flat strings | `PI_TEAMS_HOOK_EVENT`, `PI_TEAMS_TEAM_ID`, etc. (convenience) |
24
+ | Exit code | integer | `0` = pass, non-zero = fail |
25
+
26
+ ### Context JSON schema (v1)
27
+
28
+ ```jsonc
29
+ {
30
+ "version": 1, // always matches PI_TEAMS_HOOK_CONTEXT_VERSION
31
+ "event": "task_completed", // "idle" | "task_completed" | "task_failed"
32
+ "team": {
33
+ "id": "<teamId>",
34
+ "dir": "<absolute path>",
35
+ "taskListId": "<taskListId>",
36
+ "style": "normal" // current UI style id
37
+ },
38
+ "member": "<name>" | null, // teammate that triggered the event
39
+ "timestamp": "<ISO 8601>" | null,
40
+ "task": { // null for "idle" events; may also be null
41
+ // for task_completed/task_failed if the task
42
+ // was cleared before the leader processed it
43
+ "id": "3",
44
+ "subject": "<text>", // truncated to 1,000 chars
45
+ "description": "<text>", // truncated to 8,000 chars
46
+ "owner": "<name>" | null,
47
+ "status": "completed", // "pending" | "in_progress" | "completed"
48
+ // NOTE: for task_failed events the status is
49
+ // typically "pending" (reset before hook runs)
50
+ "blockedBy": ["1", "2"], // max 200 entries
51
+ "blocks": ["5"], // max 200 entries
52
+ "metadata": {}, // freeform key-value
53
+ "createdAt": "<ISO 8601>",
54
+ "updatedAt": "<ISO 8601>"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Flat environment variables (v1)
60
+
61
+ | Variable | Always set | Description |
62
+ |---|---|---|
63
+ | `PI_TEAMS_HOOK_EVENT` | ✓ | Event name |
64
+ | `PI_TEAMS_HOOK_CONTEXT_VERSION` | ✓ | Contract version (string) |
65
+ | `PI_TEAMS_HOOK_CONTEXT_JSON` | ✓ | Full JSON payload |
66
+ | `PI_TEAMS_TEAM_ID` | ✓ | Team identifier |
67
+ | `PI_TEAMS_TEAM_DIR` | ✓ | Absolute path to team directory |
68
+ | `PI_TEAMS_TASK_LIST_ID` | ✓ | Task list identifier |
69
+ | `PI_TEAMS_STYLE` | ✓ | UI style id |
70
+ | `PI_TEAMS_MEMBER` | when available | Teammate name |
71
+ | `PI_TEAMS_EVENT_TIMESTAMP` | when available | ISO 8601 event timestamp |
72
+ | `PI_TEAMS_TASK_ID` | when task exists | Task id |
73
+ | `PI_TEAMS_TASK_SUBJECT` | when task exists | Task subject (untruncated) |
74
+ | `PI_TEAMS_TASK_OWNER` | when task has owner | Task owner name |
75
+ | `PI_TEAMS_TASK_STATUS` | when task exists | Task status |
76
+
77
+ ### Hook exit code semantics
78
+
79
+ | Exit code | Meaning | Leader behavior |
80
+ |---|---|---|
81
+ | `0` | Pass | Record success in task metadata |
82
+ | Non-zero | Fail | Apply failure policy (warn / followup / reopen / reopen_followup) |
83
+ | Timeout (SIGTERM) | Fail | Same as non-zero exit |
84
+
85
+ ## Compatibility policy
86
+
87
+ ### Additive changes (no version bump)
88
+
89
+ The following changes are **backward-compatible** and do NOT increment the contract version:
90
+
91
+ - Adding new **optional** fields to the context JSON (hooks must tolerate unknown keys)
92
+ - Adding new **environment variables** (hooks must tolerate new env vars)
93
+ - Adding new **event types** (hooks only receive events matching their filename)
94
+ - Extending `metadata` with new keys
95
+ - Increasing truncation limits
96
+
97
+ ### Breaking changes (version bump required)
98
+
99
+ The following changes **require incrementing** the contract version:
100
+
101
+ - Removing or renaming existing JSON fields
102
+ - Changing the type of an existing field
103
+ - Changing the semantics of an existing field (e.g., status values)
104
+ - Reducing truncation limits below current values
105
+ - Changing exit code semantics
106
+ - Removing environment variables
107
+
108
+ ### Version lifecycle
109
+
110
+ 1. **New version**: old version continues to be supported for at least one minor release
111
+ 2. **Deprecation**: the old version emits a warning in hook logs
112
+ 3. **Removal**: the old version is dropped in the next major release
113
+
114
+ ### Hook author guidelines
115
+
116
+ Write hooks that are resilient to additive changes and race conditions:
117
+
118
+ ```js
119
+ // ✅ Good: parse only the fields you need, ignore the rest
120
+ const ctx = JSON.parse(process.env.PI_TEAMS_HOOK_CONTEXT_JSON);
121
+ const taskId = ctx.task?.id;
122
+
123
+ // ✅ Good: always guard task access — task can be null even for
124
+ // task_completed/task_failed events (race: task cleared before leader reads it)
125
+ if (!ctx.task) {
126
+ console.log("Task already cleared, skipping quality gate");
127
+ process.exit(0);
128
+ }
129
+
130
+ // ✅ Good: don't assume task.status matches the event name —
131
+ // for task_failed events the status is typically "pending" (reset before hook runs)
132
+ if (ctx.event === "task_failed") {
133
+ console.log(`Task #${ctx.task.id} failed (current status: ${ctx.task.status})`);
134
+ }
135
+
136
+ // ✅ Good: check the version for breaking changes
137
+ const version = parseInt(process.env.PI_TEAMS_HOOK_CONTEXT_VERSION, 10);
138
+ if (version > 1) {
139
+ console.error(`Unsupported hook contract version: ${version}`);
140
+ process.exit(1);
141
+ }
142
+
143
+ // ❌ Bad: assume exact shape, fail on new fields
144
+ const { version, event, team, member, timestamp, task } = ctx;
145
+ assert(Object.keys(ctx).length === 5); // breaks when fields are added
146
+
147
+ // ❌ Bad: unconditionally access task fields
148
+ const subject = ctx.task.subject; // crashes when task is null
149
+ ```
150
+
151
+ ## Hook log format
152
+
153
+ Each hook invocation produces a log file in `<teamDir>/hook-logs/` with the structure:
154
+
155
+ ```jsonc
156
+ {
157
+ "invocation": {
158
+ "event": "task_completed",
159
+ "teamId": "...",
160
+ // ... full invocation context
161
+ },
162
+ "result": {
163
+ "ran": true,
164
+ "hookPath": "/path/to/on_task_completed.js",
165
+ "command": ["node", "/path/to/on_task_completed.js"],
166
+ "exitCode": 0,
167
+ "timedOut": false,
168
+ "durationMs": 142,
169
+ "stdout": "...",
170
+ "stderr": "...",
171
+ "contractVersion": 1 // traces which version was used
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## Changelog
177
+
178
+ ### v1 (initial)
179
+
180
+ - Context JSON with `version`, `event`, `team`, `member`, `timestamp`, `task`
181
+ - Flat env vars: `PI_TEAMS_HOOK_EVENT`, `PI_TEAMS_HOOK_CONTEXT_VERSION`, `PI_TEAMS_HOOK_CONTEXT_JSON`, team/task fields
182
+ - Exit code 0 = pass, non-zero = fail
183
+ - Truncation: subject 1,000 chars, description 8,000 chars, blockedBy/blocks max 200 entries
@@ -20,13 +20,22 @@ npx tsx scripts/smoke-test.mts
20
20
  | Module | Coverage |
21
21
  |------------------|-----------------------------------------------------------------|
22
22
  | `names.ts` | `sanitizeName` character replacement, edge cases |
23
- | `fs-lock.ts` | `withLock` returns value, cleans up lock file |
24
- | `mailbox.ts` | `writeToMailbox`, `popUnreadMessages`, read-once semantics |
23
+ | `model-policy.ts`| Deprecated model detection, teammate model selection/override |
24
+ | `teams-ui-shared.ts` | `isTeamDone` (9 edge cases), display helpers |
25
+ | `fs-lock.ts` | `withLock` returns value, cleans up lock file, stale locks |
26
+ | `mailbox.ts` | `writeToMailbox`, `popUnreadMessages`, read-once, urgent flag |
25
27
  | `task-store.ts` | CRUD, `startAssignedTask`, `completeTask`, `claimNextAvailable`,|
26
28
  | | `unassignTasksForAgent`, dependencies, `clearTasks` |
27
- | `team-config.ts` | `ensureTeamConfig` (idempotent), `upsertMember`, `setMemberStatus`, `loadTeamConfig` |
29
+ | `team-config.ts` | `ensureTeamConfig` (idempotent), `upsertMember`, `setMemberStatus`, `loadTeamConfig`, hooks policy |
28
30
  | `protocol.ts` | Structured message parsers (valid + invalid JSON + wrong type) |
29
- | Pi CLI | `pi --version` executes (skipped in CI if `pi` not on PATH) |
31
+ | `teams-style.ts` | Custom styles, naming rules, pool naming |
32
+ | `hooks.ts` | Hook execution, failure policies, follow-up/reopen actions |
33
+ | `team-attach-claim.ts` | Acquire/release/heartbeat claims, staleness detection |
34
+ | `team-discovery.ts` | Team listing, config parsing, online worker counts |
35
+ | `/team done` | End-of-run cleanup, force-done with in-progress tasks, idempotency |
36
+ | `isTeamDone` | All branches: empty, completed, pending, streaming, stopped |
37
+ | docs drift guard | README, SKILL.md, help text consistency checks |
38
+ | Pi CLI | `pi --version` executes (skipped in CI if `pi` not on PATH) |
30
39
 
31
40
  **Expected result:** `PASSED: <n> FAILED: 0`
32
41
 
@@ -95,7 +104,17 @@ Ask the model:
95
104
 
96
105
  **Expected:** the `teams` tool is invoked, task created and assigned.
97
106
 
98
- ### 3g. Shutdown
107
+ ### 3g. End of run
108
+
109
+ ```
110
+ /team done
111
+ ```
112
+
113
+ **Expected:** all teammates stopped, widget hidden, summary notification shows task counts. If tasks are still in progress, use `/team done --force`.
114
+
115
+ After `/team done`, spawning a new teammate should automatically restore the widget.
116
+
117
+ ### 3h. Shutdown (alternative to done)
99
118
 
100
119
  ```
101
120
  /team shutdown agent1
@@ -138,9 +157,9 @@ pi --no-extensions -e extensions/teams/index.ts --mode rpc
138
157
 
139
158
  | # | Test | Method | Status |
140
159
  |---|-------------------------------|------------|--------|
141
- | 1 | Unit primitives (60 asserts) | Automated | ✅ |
160
+ | 1 | Unit primitives (200+ asserts)| Automated | ✅ |
142
161
  | 2 | Extension loading | CLI | ✅ |
143
- | 3 | Interactive spawn/task/dm | Manual | 📋 |
162
+ | 3 | Interactive spawn/task/dm/done| Manual | 📋 |
144
163
  | 4 | Worker-side RPC | Manual | 📋 |
145
164
 
146
165
  ✅ = verified in this run, 📋 = documented for manual execution.