@prjct.app/pi-team 0.6.0 → 0.7.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/CONTRIBUTING.md +2 -1
  3. package/README.md +23 -177
  4. package/docs/architecture.md +36 -168
  5. package/package.json +10 -4
  6. package/src/commands/team-command.ts +37 -0
  7. package/src/domain/lease.ts +54 -0
  8. package/src/domain/member.ts +58 -0
  9. package/src/domain/message.ts +91 -0
  10. package/src/domain/request.ts +67 -0
  11. package/src/domain/team.ts +71 -0
  12. package/src/dynamic/domain.ts +110 -0
  13. package/src/dynamic/memory.ts +38 -0
  14. package/src/dynamic/panel.ts +155 -0
  15. package/src/dynamic/peer-log.ts +39 -0
  16. package/src/dynamic/runner.ts +196 -0
  17. package/src/dynamic/service.ts +292 -0
  18. package/src/dynamic/store.ts +57 -0
  19. package/src/dynamic/view.ts +21 -0
  20. package/src/dynamic/worker.ts +210 -0
  21. package/src/dynamic/workspace.ts +43 -0
  22. package/src/index.ts +204 -679
  23. package/src/process-identity.ts +68 -0
  24. package/src/runtime/delivery.ts +326 -0
  25. package/src/runtime/membership.ts +212 -0
  26. package/src/runtime/presence.ts +98 -0
  27. package/src/runtime/purge.ts +39 -0
  28. package/src/runtime/reconciler.ts +112 -0
  29. package/src/runtime/requests.ts +353 -0
  30. package/src/runtime/resources.ts +117 -0
  31. package/src/runtime/team-runtime.ts +47 -0
  32. package/src/runtime/team-tool.ts +191 -0
  33. package/src/storage/atomic.ts +347 -0
  34. package/src/storage/inbox-store.ts +290 -0
  35. package/src/storage/lease-store.ts +158 -0
  36. package/src/storage/paths.ts +76 -0
  37. package/src/storage/receipt-store.ts +117 -0
  38. package/src/storage/team-store.ts +190 -0
  39. package/src/supervisor/control-protocol.ts +125 -0
  40. package/src/supervisor/runtime-store.ts +231 -0
  41. package/src/supervisor/shutdown.ts +141 -0
  42. package/src/supervisor/supervisor.ts +657 -0
  43. package/src/supervisor/tmux-adapter.ts +192 -0
  44. package/src/supervisor/worker-bootstrap.ts +43 -0
  45. package/src/supervisor/worker-client.ts +233 -0
  46. package/src/ui/team-dashboard.ts +179 -0
  47. package/src/mailbox.ts +0 -536
  48. package/src/schema.ts +0 -25
  49. package/src/store.ts +0 -230
package/CHANGELOG.md CHANGED
@@ -1,3 +1,74 @@
1
+ ## [0.7.0](https://github.com/prjct-app/pi-team/compare/v0.6.1...v0.7.0) (2026-09-22)
2
+
3
+ ### Features
4
+
5
+ * /team completions with descriptions and the prjct mark ([afa687d](https://github.com/prjct-app/pi-team/commit/afa687d2edeba4776f6e200850b6114738914b88))
6
+ * /team is a docked panel of Runs and Experts with traceable assignments ([6f5b025](https://github.com/prjct-app/pi-team/commit/6f5b025a3ad49c8188322e3a98b1d8fca3f0c4df))
7
+ * add durable managed team state ([3380be1](https://github.com/prjct-app/pi-team/commit/3380be1cc4b106041a32c24ff1cfc119b7bc5d3d))
8
+ * add live team plan panel ([58635e2](https://github.com/prjct-app/pi-team/commit/58635e2db7f12fcee407932561c4a29e8d43b79d))
9
+ * add persistent dynamic team orchestration ([0a11bfc](https://github.com/prjct-app/pi-team/commit/0a11bfc25b2a3ca910dcfa01cfedf12dc9819ab5)), closes [#49](https://github.com/prjct-app/pi-team/issues/49)
10
+ * add persistent dynamic team orchestration ([57c8d03](https://github.com/prjct-app/pi-team/commit/57c8d0350b53bad0e963debbf79e5b39c28c1c4e))
11
+ * add Team v2 messaging runtime ([d0df88f](https://github.com/prjct-app/pi-team/commit/d0df88fbbfa57ffe4a7ad7373bd18d6591db7c54))
12
+ * add Team v2 storage core ([b75d230](https://github.com/prjct-app/pi-team/commit/b75d2306efc5670fd51668658bb536d3a19b4e41))
13
+ * add Team v2 supervised peer lifecycle ([3415872](https://github.com/prjct-app/pi-team/commit/3415872ab6a6bb6076736f1d2981f174a90551eb))
14
+ * Experts message each other directly (team_peers, team_message) ([298188f](https://github.com/prjct-app/pi-team/commit/298188f1037ff492955040088d262af165468638))
15
+ * integrate Team v2 extension ([81c64da](https://github.com/prjct-app/pi-team/commit/81c64da3c3b28ddb4c7e34bd811dda6d502a4922))
16
+ * isolate managed agents in worktrees ([35a68a3](https://github.com/prjct-app/pi-team/commit/35a68a3751e962ad260c07b54d70a21b45289b1b))
17
+ * keep an unattended teammate reachable after the auto-turn cap ([e354af4](https://github.com/prjct-app/pi-team/commit/e354af44dafc49171129ccf76f6213b10d35536e))
18
+ * launch autonomous teams from prompts ([6b72638](https://github.com/prjct-app/pi-team/commit/6b7263815dc6a0c4cca5b785211b1b5eb23a5b07))
19
+ * link team requests to optional subagents ([c51722f](https://github.com/prjct-app/pi-team/commit/c51722feac67405705ca3ddd69564bf6825a2bbc))
20
+ * run managed peers in persistent terminals ([4376b08](https://github.com/prjct-app/pi-team/commit/4376b08c331707263deb49af119c909ae85a75de))
21
+ * run persistent managed peer sessions ([6b2e249](https://github.com/prjct-app/pi-team/commit/6b2e249f82499d1a988e11c07a9d3fc54c1ee393))
22
+ * schedule managed work from dependencies ([18c5817](https://github.com/prjct-app/pi-team/commit/18c5817c890a08c212856fcf36ef4d87769b6cdb))
23
+ * send each Expert the project memory for its stance ([36caefc](https://github.com/prjct-app/pi-team/commit/36caefc442b7dcbfe5a00c26bbe97fb69f13b561))
24
+
25
+ ### Bug Fixes
26
+
27
+ * a real parallel team — one Expert per role, own worktree each, orchestrator only coordinates ([140091d](https://github.com/prjct-app/pi-team/commit/140091d331ec75195a225f874847df601aa64dac))
28
+ * accept objectives through team command ([8f05337](https://github.com/prjct-app/pi-team/commit/8f05337c26a7f6cef7c4cafe7de2581c3c89d2ad))
29
+ * Experts launch again: short control socket, tmux 3.6 targets, lease token shape ([22c185b](https://github.com/prjct-app/pi-team/commit/22c185b49c91e0bedeb347795fca94df22477e1e))
30
+ * expose the authorized integration target ([f1bf132](https://github.com/prjct-app/pi-team/commit/f1bf13268985f92ff9866b9003a253908620deb0))
31
+ * harden managed runtime shutdown ([316cf72](https://github.com/prjct-app/pi-team/commit/316cf7218c7e6f27f11be4ef825a8447921cd97b))
32
+ * harden managed team records ([652e8d3](https://github.com/prjct-app/pi-team/commit/652e8d3cc4aef61280c8bbbadb6c1a16b3b65ae7))
33
+ * isolate managed peer extensions ([b69e02c](https://github.com/prjct-app/pi-team/commit/b69e02cf64b3c7ff41d039f95d36dc448a8a4a2a))
34
+ * keep worktree locks for the duration of git and refuse secret commits ([0d8c427](https://github.com/prjct-app/pi-team/commit/0d8c42783cc231b7464c79ec11c2373e8db74c05))
35
+ * only create and check the short socket fallback directory ([990f2a6](https://github.com/prjct-app/pi-team/commit/990f2a67650b5bc12542fd90ac0650675cbb8750))
36
+ * preserve lead checkout during managed work ([54e3159](https://github.com/prjct-app/pi-team/commit/54e3159368e36880305481c6fedf18edf9218c76))
37
+ * reject conflicting managed objectives ([5102c8b](https://github.com/prjct-app/pi-team/commit/5102c8b40bdc33f40eba4b18fad6d690b29b87b0))
38
+ * reject presence symlinks, redact more credentials, and bound the read cache ([c55bd27](https://github.com/prjct-app/pi-team/commit/c55bd27565441a86f711a4b8046cd7bbadeefa86))
39
+ * sanitize git and verification subprocess environments ([b4819bf](https://github.com/prjct-app/pi-team/commit/b4819bf37b5615e249cb2ca8b60557dd21dd913c))
40
+
41
+ ## Unreleased
42
+
43
+ - Add `npm run build:pi`: a compiled local build in `~/.pi/agent/builds/<package>` that Pi loads instead of the TypeScript sources; supervised workers launch the built entry.
44
+
45
+ ### Breaking changes
46
+
47
+ - replace manual create/join membership with explicit `/team <objective>` orchestration
48
+ - remove legacy inspection, migration, and v1 mailbox code from the package
49
+ - store persistent project Teams under `${PRJCT_HOME:-~/.prjct}/pi-team/orchestration-v2/`
50
+
51
+ ### Features
52
+
53
+ - persist bounded Run history and reusable Expert identities, sessions, memory, and assignment history
54
+ - dynamically create missing Experts and reuse existing roles across later Runs
55
+ - queue busy Experts and later objectives while allowing bounded parallel work across distinct Experts
56
+ - add the active-Run-only `team_orchestrate` tool and bounded status, history, doctor, and cancellation commands
57
+ - execute Experts through authenticated supervised workers with durable correlated replies and resource leases
58
+
59
+ ### Security
60
+
61
+ - keep normal prompts inert until an explicit Team objective
62
+ - fence owners and late results by process identity, epoch, generation, request, and receipt
63
+ - inherit the launching Pi environment explicitly when tmux already has a server
64
+ - validate worker request bodies and durable assignment/sender identity before model execution
65
+ - stop only proven-owned workers; interruption preserves history without automatic replay
66
+ ## [0.6.1](https://github.com/prjct-app/pi-team/compare/v0.6.0...v0.6.1) (2026-09-11)
67
+
68
+ ### Bug Fixes
69
+
70
+ * preserve mailbox locks across rolling upgrades ([3509903](https://github.com/prjct-app/pi-team/commit/3509903026205a0a3d00fcd52eecb3b970ba4d7b))
71
+
1
72
  ## [0.6.0](https://github.com/prjct-app/pi-team/compare/v0.5.7...v0.6.0) (2026-09-10)
2
73
 
3
74
  ### Features
package/CONTRIBUTING.md CHANGED
@@ -6,10 +6,11 @@
6
6
  - Deliver changes through a pull request using `.github/pull_request_template.md`.
7
7
  - Use English for code, documentation, tests, issues, and pull requests.
8
8
  - Use strict TypeScript and only APIs documented by Pi 0.85.1.
9
- - Use immutable values: `npm run check` fails on any `let` under `src/`. See [AGENTS.md](AGENTS.md).
9
+ - Use immutable values: `npm run check` fails on any `let` under `src/`.
10
10
  - Do not import host internals, monkey-patch prototypes, or access real credentials, sessions, or user configuration in tests.
11
11
  - Keep runtime dependencies in `dependencies`; list Pi-provided packages in `peerDependencies` with a `*` range.
12
12
  - Run `npm run check`, `npm test`, and `npm pack --dry-run` before review.
13
+ - Build the compiled local copy Pi loads with `npm run build:pi`. It writes `~/.pi/agent/builds/<package>` outside the repository, because compiled code inside it would load the repository's development copy of Pi instead of the host's.
13
14
  - Never push, open or merge a pull request, publish, or deploy without explicit authorization.
14
15
 
15
16
  ## Package documentation
package/README.md CHANGED
@@ -1,191 +1,49 @@
1
1
  # pi-team
2
2
 
3
- [![pi-team extension for PI Agent](https://raw.githubusercontent.com/prjct-app/pi-clipboard/main/docs/covers/pi-team.png)](https://pi.dev)
4
-
5
- Coordinate independent PI Agent sessions with local team messaging, queued tasks,
6
- and shared results.
7
-
8
- You open the terminals; `pi-team` lets those sessions send each other work, wake a
9
- free teammate, and return a result the requester can verify. Everything stays on
10
- this machine, in local files.
11
-
12
- [![Watch the pi-team demo](media/pi-team-demo/poster.png)](https://github.com/prjct-app/pi-team/raw/refs/heads/main/media/pi-team-demo/pi-team-demo.mp4)
3
+ Explicit project Teams with durable Runs, reusable Experts, and supervised local execution. Normal prompts are not intercepted.
13
4
 
14
5
  ## Install
15
6
 
16
- Requires Pi installed separately and Node.js **22.19 or later**. Tested against
17
- Pi **0.85.1**; newer versions are not yet verified. Independent community package.
7
+ Requires Pi **0.85.1**, Node.js **22.19+**, authenticated model access, and `tmux` for Expert execution.
18
8
 
19
9
  ```sh
20
10
  pi install npm:@prjct.app/pi-team
21
11
  ```
22
12
 
23
- Add `-l` for project-only installation, and restart Pi afterwards. Manage it with
24
- the usual `pi list` / `pi update` / `pi remove` and `pi config`. Do not install the
25
- same extension from both GitHub and npm: Pi treats those as different packages.
26
-
27
- ## Quickstart
28
-
29
- Open two interactive Pi terminals. In the first:
13
+ Restart Pi, then explicitly start an objective:
30
14
 
31
15
  ```text
32
- /team create demo
33
- /team join demo coordinator
16
+ /team Ship login validation
34
17
  ```
35
18
 
36
- In the second:
37
-
38
- ```text
39
- /team join demo reviewer
40
- ```
19
+ This lazily creates a project Team and queues a Run. Main Pi orchestrates through the active-only `team_orchestrate` tool. Dispatch reports whether an Expert was created or reused. Busy Experts queue; distinct Experts can execute concurrently, up to three. An Expert's identity, session file, instructions, bounded memory, and assignment history survive process shutdown and later Runs.
41
20
 
42
- Back in the first:
21
+ ## Commands
43
22
 
44
- ```text
45
- /team note reviewer Please review the current README.
46
- /team send reviewer Add a limits table to the README.
47
- ```
23
+ - `/team <objective>` — start or queue an objective (one active Run).
24
+ - `/team` or `/team status` bounded plain Team/Run/Expert/Assignment overview.
25
+ - `/team history` recent Run history.
26
+ - `/team doctor` — bounded diagnostics without process or ownership tokens.
27
+ - `/team cancel [run-id]` — cancel an owned active or queued Run.
28
+ - `/team help` — usage.
48
29
 
49
- The note appears in the reviewer's transcript without starting any model work.
50
- The request wakes the reviewer once it is idle, and its result comes back to the
51
- coordinator. Installing alone never joins a team.
30
+ The old create/join/start/migrate/legacy lifecycle commands are unsupported. No project YAML or Markdown configuration is loaded by this extension. Startup does not detect or migrate old stores.
52
31
 
53
- Supported on Linux and macOS with local disk storage. Network filesystems,
54
- cross-machine messaging, and native Windows are not supported.
32
+ ## Orchestration tool
55
33
 
56
- ## Concepts
34
+ During an active Run, `team_orchestrate` supports `dispatch`, `status`, `cancel_assignment`, `cancel_run`, and `finish`. Dispatch requires role, capabilities, task, and an explicit built-in tool allowlist; instructions are optional. It returns Expert ID, stable session reference, Assignment ID, and `created`/`reused`. Status includes bounded recent evidence. Finish is blocked while assignments remain outstanding. Expert reports are untrusted evidence, never user authorization.
57
35
 
58
- A **team** is a named local mailbox. A session joins under an **alias**: an
59
- address, not a privileged role or an automatic persona, and shared rather than
60
- private — anyone using this OS account can rejoin an offline alias and see its
61
- history. Each session keeps its own model, cwd, instructions, permissions, and
62
- conversation. Two agents editing the same files can still overwrite each other:
63
- this package does not manage file ownership.
36
+ Only an explicit objective starts orchestration. Completing a Run allows the next queued objective to start in the owning session. Shutdown, reload, new, resume, and fork terminally interrupt active work, fence ownership, and preserve queued objectives without automatic replay. A later explicit objective can claim an unowned Team and process its queue.
64
37
 
65
- | Kind | Meaning |
66
- | --- | --- |
67
- | `request` | Work for a teammate. Starts a model turn when the recipient is idle, and always produces one correlated result back to the emitter. |
68
- | `note` | Display-only FYI. Appears in the transcript; never starts a model turn. |
69
- | `result` | The automatic reply to a request: outcome, final text, and observed files. Delivered to the emitter for verification. |
38
+ ## Storage and safety
70
39
 
71
- While joined, a minimal widget above the editor shows `team · alias · state`,
72
- where state is `connected`, `working`, `paused`, or `select a model`, plus a
73
- pending count when work is queued for you.
40
+ State is isolated under `${PRJCT_HOME:-~/.prjct}/pi-team/orchestration-v2/`. Project identity uses the canonical Git root (or cwd outside Git); moving a project changes its identity. Existing old stores are not modified.
74
41
 
75
- Membership is restored automatically when the same Pi session is resumed or
76
- reloaded. `/new` and `/fork` start unaffiliated sessions on purpose.
42
+ Records use strict schemas, byte bounds, private permissions, symlink checks, locks, and atomic writes. Limits: 64 Runs, 128 Assignments, 16 Experts, 32 assignment references per Expert, and three concurrent/unresolved workers. Terminal history is pruned within these bounds. Full Pi sessions are durable and are not bounded metadata.
77
43
 
78
- ## Commands
44
+ Production execution uses authenticated supervisor control, durable requests/replies, receipts, and leases. A dispatch or process launch is not success: completion requires a correlated reply and a proven worker stop. Unprovable shutdown blocks the Expert rather than guessing ownership. Use doctor and manually verify process identity before recovery; no automatic adoption or retry is provided.
79
45
 
80
- | Command | Meaning |
81
- | --- | --- |
82
- | `/team create shop` | Create explicitly; does not join automatically |
83
- | `/team delete shop` | Permanently delete an inactive team after confirmation |
84
- | `/team rename-team shop store` | Rename a team after every member is offline |
85
- | `/team join shop backend` | Register this session and enable automatic reception |
86
- | `/team list` | List teams; refresh team-name completion |
87
- | `/team members` | Show other aliases, cwd, and idle/busy/paused/offline status; excludes this session |
88
- | `/team remove backend` | Remove an offline alias and interrupt its unresolved work after confirmation |
89
- | `/team rename-member backend api` | Rename your own alias, or an offline teammate, while preserving its history and queued work |
90
- | `/team status` | Show every unresolved requester → assignee relationship |
91
- | `/team wake [message]` | Queue an actionable check-in for every other teammate |
92
- | `/team send backend Implement login` | Queue a request that can start work |
93
- | `/team note frontend API contract changed` | Display an FYI; never starts a model turn |
94
- | `/team inbox` | Show the most recent 20 records and their states |
95
- | `/team pause` | Pause new work, without cancelling current work |
96
- | `/team resume` | Resume reception and reset the automatic turn budget |
97
- | `/team leave` | Leave; if processing a request, report its result first |
98
-
99
- Names and aliases are 1–48 lowercase letters, digits, or hyphens, starting with a
100
- letter. Unknown teams are rejected, never implicitly created; duplicate live
101
- aliases are rejected. Sending to an offline **known** alias queues until someone
102
- rejoins it. Removing that alias instead settles every unresolved request involving
103
- it; incoming requests produce an interrupted result so their requesters stop waiting.
104
- Renaming an alias rewrites its message addresses so queued work follows the new
105
- name. Team deletion and rename require every member to be offline, and destructive
106
- operations require confirmation. Tab completion covers subcommands, discovered
107
- teams, and teammates.
108
-
109
- ## Agent tools
110
-
111
- - `team_members` — discover other teammates and their status; excludes this session.
112
- - `team_send` — send `{ to, kind: "request" | "note", subject, body }`.
113
- - `team_status` — outstanding work: what you emitted and is unresolved, what is
114
- queued for you, results awaiting your review, and third-party team activity.
115
-
116
- Tools cannot create teams, join, resume reception, change permissions, or launch
117
- terminals; they require membership you established. Discovery results and recipient
118
- autocomplete exclude the current session, and sending to yourself is rejected at
119
- the mailbox boundary. A request returns **queued**, never "task completed".
120
-
121
- ## How delivery works
122
-
123
- A request or result starts a turn only when the recipient is idle, has a selected
124
- model, no pending user message or open prompt, and an empty editor. No running
125
- tool is interrupted.
126
-
127
- For each processed request the extension sends **one** result after the agent
128
- settles: the last assistant text capped at 3,000 characters (no thinking), up to
129
- 50 absolute paths observed in successful `edit`/`write` calls, and an outcome of
130
- completed, failed, or interrupted.
131
-
132
- That file list is **not** a Git diff. Files changed through bash, custom tools, or
133
- other processes are not enumerated, and the extension never infers test success
134
- from a shell command or a model claim. **A completed run is not proof of success**:
135
- review the reported outcome and the recipient worktree.
136
-
137
- Once a task result is persisted, the session can accept the next queued request.
138
- While a request you emitted stays unresolved past five minutes, a review turn
139
- asks your agent to chase the teammate or tell you what is blocked. See
140
- [Architecture](docs/architecture.md) for implementation details.
141
-
142
- ## Safety
143
-
144
- - **This is not a sandbox or an authorization system.** Agents and processes under
145
- the same OS user already have filesystem access. Prompt-level rules are not a
146
- hard guarantee against a model that ignores them. Do not place untrusted agents
147
- in a team or rely on team boundaries to protect secrets from that OS user.
148
- - Peer messages are identified as untrusted data and **are not user consent**.
149
- Agents are instructed not to relay denied work, change configuration, or evade
150
- plan mode. Peer text is never executed as a slash command or expanded as a file
151
- mention.
152
- - Messages stay in local files, but when processed their text goes to the
153
- recipient's configured **model provider** like any prompt, and results go to the
154
- requester. Do not send credentials or unrelated secrets.
155
- - User takeover during a peer task pauses reception and reports an interrupted
156
- result instead of forwarding your unrelated work.
157
-
158
- ## Limits
159
-
160
- | Limit | Value |
161
- | --- | --- |
162
- | Automatic peer turns before reception pauses | 5, then `/team resume` |
163
- | Messages in one automatically linked conversation | 8 non-result |
164
- | Unsettled deliveries per member | 50 slots, one reserved per outstanding request |
165
- | Records per team | 500, including reserved result capacity |
166
- | Members per team | 100 |
167
- | Outgoing body / serialized message | 16 KB / 20 KB |
168
- | Automatic result | 32 KB, file list shortened with a notice |
169
- | Presence lease | 30 s, renewed every 2 s |
170
- | Review threshold / cadence | 5 min unresolved, checked every 1 min |
171
- | Duplicate suppression | identical message within 1 min is refused |
172
-
173
- No daily token or monetary budget is enforced. History is never silently deleted:
174
- create a fresh team when one is full. Review cadence and the turn budget are fixed
175
- defaults, not user-configurable yet.
176
-
177
- ## Troubleshooting
178
-
179
- | Symptom | Cause and action |
180
- | --- | --- |
181
- | A request stays queued | Run `/team status`. The recipient may be busy, paused, offline, missing a model, or typing. After five minutes, review turns chase it or surface the blockage. |
182
- | `Team auto-turn limit reached` | Five automatic turns ran without user input. Review the transcript, then `/team resume`. |
183
- | `Membership expired or replaced` | Another live session took your alias. Rejoin, choosing a new alias if the old one is in use. |
184
- | `Recipient inbox full` / `Sender inbox full` | Fifty unsettled deliveries per member, one slot reserved per outstanding request. Let the teammate drain; notes need no reservation. |
185
- | `Team history full (500 records)` | At capacity; history is never deleted. Create a fresh team and rejoin. |
186
- | Repeated storage warnings | Conflicts retry automatically and never pause reception. If one persists, check that the teams directory is on a local disk and report it. |
187
- | A teammate went offline mid-task | Its claimed work is interrupted and the emitter receives that result; it is not replayed. Review the worktree, then resend explicitly. |
188
- | Work remains queued for an alias that will not return | Use `/team remove <alias>` to interrupt and settle its unresolved work, or `/team rename-member <alias> <new-alias>` to preserve the queue under a replacement alias. |
46
+ This is not an OS sandbox: tools, especially Bash, run with the current user's permissions. Do not send secrets. Metadata redaction is best-effort, not a secret detector. Model requests use the worker's configured authenticated provider. A live model/tmux/PTY roundtrip remains a manual verification requirement; deterministic tests cover the adapter boundary with fake supervision and real durable transport.
189
47
 
190
48
  ## Development
191
49
 
@@ -193,19 +51,7 @@ defaults, not user-configurable yet.
193
51
  npm ci --ignore-scripts
194
52
  npm run check
195
53
  npm test
196
- npm run check:package
54
+ npm pack --dry-run
197
55
  ```
198
56
 
199
- Pi loads the TypeScript entry point directly; there is no build step. Use `pi -e .`
200
- to try a checkout. Tests use isolated temporary state and never call model APIs.
201
-
202
- `npm run check` also enforces that `src/` contains no `let`: session state is a
203
- single immutable record updated functionally, and reads go through its accessor at
204
- the point of use rather than being captured across an `await`.
205
-
206
- ## More
207
-
208
- - [Architecture](docs/architecture.md) — storage, concurrency, recovery, context budget
209
- - [Package structure](docs/package.md) · [Releases](docs/releases.md) · [Contributing](CONTRIBUTING.md) · [Changelog](CHANGELOG.md)
210
-
211
- [MIT](LICENSE).
57
+ [Architecture](docs/architecture.md) · [Package structure](docs/package.md) · [Contributing](CONTRIBUTING.md) · [MIT](LICENSE)
@@ -1,170 +1,38 @@
1
1
  # Architecture
2
2
 
3
- How pi-team stores state, coordinates concurrent sessions, and recovers from
4
- failure. For everyday use see the [README](../README.md).
5
-
6
- ## Storage model
7
-
8
- Each team is one JSON record at `~/.pi/agent/teams/<team>/state.json`, honouring
9
- `PI_CODING_AGENT_DIR`. The record holds the member roster and the full message
10
- history. Team folders are `0700` and files `0600`; an unsafe, symlinked, or
11
- foreign-owned directory or record fails closed rather than being repaired.
12
-
13
- Records are wrapped in an envelope carrying a schema marker, a monotonically
14
- increasing revision, and a SHA-256 of the payload. A corrupt record is preserved
15
- for manual recovery, never erased. Mailboxes written before envelopes existed
16
- are read as revision 0 and rewritten as envelope records on their first write.
17
-
18
- ## Concurrency
19
-
20
- Readers never take a lock. Every publication writes a private temporary file,
21
- hard-links it into a bounded `revisions/` history, and atomically renames it into
22
- place, so a concurrent read sees either the whole previous record or the whole
23
- next one. The history doubles as recovery evidence for an interrupted write.
24
-
25
- Writers compare-and-swap on the revision under a short-lived per-team lock in
26
- `~/.pi/agent/teams/.locks/`. Keeping the lock outside the team directory lets
27
- rename and deletion fence stale publishers without allowing them to recreate a
28
- moved directory. A conflict fails fast and the caller retries against a fresh
29
- read, so many agents write concurrently instead of queueing behind a team-wide
30
- lock. A lock abandoned by a crashed writer is reclaimed after ten seconds.
31
-
32
- Because every publication renames a **new inode** into place, readers can safely
33
- cache a parsed record keyed on `(inode, size, mtime)`: a write by any process
34
- changes the inode. Cached reads serve the polling loop; mutations always read
35
- uncached, so they never operate on the shared cached object.
36
-
37
- ## Presence
38
-
39
- Presence lives outside the shared record. Each member rewrites only its own
40
- `presence/<alias>.json` every two seconds, so heartbeats add no write contention
41
- and never touch `state.json`.
42
-
43
- These files are deliberately non-durable: the atomic rename is kept, both fsyncs
44
- are not. Presence expires after 30 seconds and is rewritten every 2, so a write
45
- lost to a crash only makes a member look offline sooner — never alive longer.
46
- A member is also considered gone as soon as its recorded process has exited. A
47
- heartbeat never creates a missing parent directory, so one already in flight
48
- cannot resurrect a team after rename or deletion.
49
-
50
- ## Request lifecycle
51
-
52
- A request is queued, claimed when the recipient is idle, worked on, and settled
53
- with a result delivered back to the emitter. Once settlement is durable, the
54
- recipient can accept another team turn; the emitter verifies the result against
55
- its original request and replies in-thread only if something is missing.
56
-
57
- States are `pending`, `processing`, `completed`, `interrupted`, and `seen`
58
- (notes already displayed).
59
-
60
- ## Sweeping orphaned claims
61
-
62
- A session that dies holding a claim would otherwise leave its requester waiting
63
- forever, so peers interrupt the claim on its behalf and the requester receives an
64
- `interrupted` result.
65
-
66
- Leaving marks a member offline but retains its address and history so the same
67
- alias can rejoin. Therefore "someone is offline" is not enough to decide when to
68
- sweep. A snapshot instead reports `sweepable`, true only when a member the record
69
- still counts as connected is actually dead **and** still holds a claim, which is
70
- the only case where sweeping changes anything. Everything else degrades correctly
71
- without it: rejoining re-admits a stale alias on its own, and displayed status
72
- comes from presence rather than the record.
73
-
74
- ## Team and member lifecycle
75
-
76
- Lifecycle changes are user-only commands; agents receive no tool that can delete
77
- or rename identities. Removing a member is allowed only while it is offline. The
78
- operation removes the roster entry, cancels unresolved work emitted by that alias,
79
- and turns requests addressed to it into interrupted results for their requesters.
80
- Renaming rewrites every message endpoint so pending work and history follow the new
81
- alias. A live session may rename itself; only offline peers can be renamed by
82
- another member. Ownership tokens fence the old alias after either operation.
83
-
84
- Team rename and deletion require every recorded member to be offline. They acquire
85
- stable locks for both names in lexical order, preventing deadlock and fencing
86
- concurrent joins or publishers. Rename moves the directory first and then writes a
87
- new revision with the new team name. If the process stops between those steps,
88
- repeating the same rename recognizes and completes that partial state. Deletion
89
- first moves the directory to a hidden tombstone and then recursively removes it,
90
- so readers never observe a partially deleted public team directory.
91
-
92
- ## Context budget
93
-
94
- What an extension puts in the model context is paid on every later turn and
95
- stays in the session branch, so injected values are bounded and any elision is
96
- stated rather than silent.
97
-
98
- | Carrier | In LLM context | Used for |
99
- | --- | --- | --- |
100
- | `before_agent_start` system prompt | yes, every turn | peer rules and team identity, carried exactly once |
101
- | `sendMessage` content | yes, and it persists | peer messages and review turns |
102
- | Tool results | yes, and they persist | `team_members`, `team_send`, `team_status` |
103
- | `appendEntry` | **no**, TUI only | notes, `/team inbox`, sent-message previews |
104
-
105
- Consequences worth knowing:
106
-
107
- - Peer rules are **not** repeated inside each message; they arrive once per turn
108
- through the system prompt.
109
- - A delivered result quotes the original request excerpted to 500 characters,
110
- with the id kept. Verification of a very long request works from an extract.
111
- - `team_status` lists are capped and report an `omitted` count. Its
112
- `otherTeamWork` list carries only third-party work; anything addressed to or
113
- emitted by this session is already in the other lists.
114
- - Each `team_status` call is a point-in-time snapshot. Earlier results in the
115
- same conversation are stale but cannot be retracted, which is why each one is
116
- kept small.
117
- - Teammate discovery, status rosters, `/team members`, and recipient completion
118
- omit the current alias. The mailbox independently rejects self-addressed
119
- messages, so a stale UI or direct tool call cannot create a self-reply loop.
120
-
121
- ## Recovery and guarantees
122
-
123
- Ownership tokens fence out replaced, renamed, or removed sessions. Pending
124
- messages survive disconnection and alias/team rename. Claimed work is marked
125
- interrupted on disconnect or rejoin and is **not automatically replayed**, since
126
- edits may already have happened.
127
-
128
- This favours avoiding duplicate side effects over guaranteed execution. **There is
129
- no exactly-once guarantee** for filesystem changes or model actions: a crash after
130
- claiming but before starting also leaves an interrupted task. If storage cannot
131
- record a result, reception pauses and reports an error.
132
-
133
- Membership and pause state are recorded in Pi session entries. Resuming the same
134
- session rejoins; `/new` and `/fork` do not inherit membership. Before claiming
135
- work the extension records that restoration must pause, without pausing the live
136
- session, so an abrupt process death during a task restores paused and requires
137
- `/team resume`. A crash just before a claim can conservatively require resume too.
138
- Legacy pending-compaction fields from earlier releases are ignored on restore.
139
-
140
- Directory watchers provide prompt delivery; polling every two seconds recovers
141
- missed notifications. Both run only for joined interactive sessions and close on
142
- shutdown. Transient storage errors are reported but never pause reception.
143
-
144
- ## Design decisions and non-goals
145
-
146
- | Concern | Choice |
147
- | --- | --- |
148
- | Session creation | The user opens every terminal; nothing is spawned |
149
- | Discovery | Explicit named teams and aliases, never inferred from directories |
150
- | Transport | Local filesystem records; no broker, socket server, or daemon |
151
- | Presence | Per-member files outside the record; heartbeats never lock |
152
- | Concurrent writes | Compare-and-swap with retry; no team-wide lock |
153
- | Follow-up | Periodic review turns delegated to the agent, not programmatic retries |
154
- | Active recipient | Wait until fully idle; no steering between tools |
155
- | Offline recipient | Persist to a known alias until it rejoins |
156
- | Approval | Never supplied by peers; local policies always win |
157
- | Coordination | Direct messages and bulk check-ins; no task board, automatic compaction, or worktree manager |
158
- | Scope | Local disks only: no network filesystems, cross-machine transport, or native Windows |
159
-
160
- Not provided: file ownership between agents, a sandbox, an authorization system,
161
- or protection of secrets from other processes under the same OS user.
162
-
163
- ## Pi interfaces used
164
-
165
- `registerCommand`, `registerTool`, `sendMessage`, `appendEntry`, custom entry and
166
- message renderers, `setWidget`, `getEditorText`, `confirm`, `isIdle`,
167
- `hasPendingMessages`, session lifecycle events, UI prompt events, `tool_result`,
168
- `message_end`, and `agent_settled`. All public and documented; no host internals
169
- are imported, no prototypes patched, and peer text is never shell-evaluated or
170
- expanded as file mentions.
3
+ ## Active integration
4
+
5
+ `src/index.ts` registers only `/team`. Fresh session startup creates no records, timers, tool, or prompt changes. An explicit objective lazily binds the canonical project, persists a Run, claims a fenced owner, and calls `sendUserMessage` once when that Run becomes active. Normal input is not intercepted. `team_orchestrate` is lazily registered and active only during owned Runs; its prompt suffix is absent outside Runs. Deactivation removes only that tool, preserving unrelated active tools.
6
+
7
+ `src/dynamic/domain.ts`, `store.ts`, and `service.ts` define strict bounded Team, Run, Expert, and Assignment records. A locked atomic project record enforces one active Run, one active assignment per Expert, unique roles, and a maximum of three busy/blocked Experts. Suitable Experts are reused, busy Experts queue, and capability/tool-policy escalation is rejected. Generation plus owner epoch fences results. Terminal cancellation never becomes success after a late reply.
8
+
9
+ ## Storage
10
+
11
+ The new namespace is `${PRJCT_HOME:-~/.prjct}/pi-team/orchestration-v2/`:
12
+
13
+ ```text
14
+ projects/<project-id>/state.json
15
+ projects/<project-id>/sessions/<stable-session-ref>.jsonl
16
+ transport/teams/<project-id>/...
17
+ transport/control/...
18
+ ```
19
+
20
+ Project IDs hash canonical Git root or cwd. A project move deliberately produces a different identity. Metadata bounds are 64 Runs, 128 Assignments, 16 Experts, and 32 assignment references per Expert; old terminal records may be pruned. Private directories, strict schemas, byte bounds, storage locks, and atomic publication reuse the existing storage primitives. Pi session files retain conversation history separately from bounded Expert memory.
21
+
22
+ ## Production execution
23
+
24
+ `runner.ts` adapts the existing `TeamSupervisor`, `TeamRuntime`, membership leases, durable request/reply spool, and receipts. Transport records live in the isolated namespace; no old Team bytes are reused. The adapter creates a private stable Pi session header once and launches Pi with `--session` referencing the same file across assignments and Runs. It disables ambient extension/skill/template/context-file discovery and passes an explicit tool allowlist.
25
+
26
+ `worker.ts` is selected only for supervised worker environments. It authenticates using the existing private control socket bootstrap, validates durable Expert identity/session and active assignment generation/owner epoch, loads role instructions and bounded memory, and enforces the allowlist at `tool_call`. Its only extension tool is `team_reply`. Requests are durably claimed/read before `sendUserMessage`; replies are explicitly correlated. Membership and delivery leases renew during work. Neither launch nor a model turn is treated as successful execution. The adapter requires a valid reply receipt and a proven stop before reporting completion.
27
+
28
+ The existing supervisor revalidates owner identity, PID start identity, process group, and marked tmux metadata before bounded graceful/TERM/KILL escalation. Tokens are never projected into Team UI/history. Unproven stops block capacity and require manual investigation. Worker startup failures and deadlines produce failure, not fabricated results.
29
+
30
+ ## Interruption
31
+
32
+ Reload, new, resume, fork, and shutdown cancel active assignments and terminally cancel the active Run with an interruption reason. They stop only owned workers, release/fence the owner, retain Team/Experts/sessions/history/queued Runs, and never automatically restore execution. A new explicit objective may claim an unowned/dead-owned Team. Dead-owner recovery interrupts abandoned active work and blocks unresolved Experts rather than adopting or replaying them.
33
+
34
+ ## Views and compatibility
35
+
36
+ `view.ts` projects bounded plain status/history/doctor summaries without owner/process/control/lease tokens. Free text is control-sanitized and best-effort secret-redacted. No color is required. The previous interactive dashboard remains inactive library code. Legacy inspection/migration and v1 mailbox sources are removed; old command forms are rejected and old stores are untouched.
37
+
38
+ Only documented Pi 0.85.1 extension APIs and CLI flags are used. Deterministic tests cover scheduling, storage, lifecycle, supervisor safety, and the production adapter with fake supervision plus real durable transport. Live authenticated model/tmux/PTY behavior remains manually unchecked. There is no OS sandbox, exactly-once side-effect guarantee, automatic recovery/adoption, Git/worktree automation, deployment, cross-machine transport, or network-filesystem support.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prjct.app/pi-team",
3
- "version": "0.6.0",
4
- "description": "Coordinate independent PI Agent sessions with local team messaging, queued tasks, and shared results.",
3
+ "version": "0.7.0",
4
+ "description": "Coordinate explicit local Pi teams with durable messaging and safely supervised peers.",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",
@@ -24,10 +24,12 @@
24
24
  "image": "https://raw.githubusercontent.com/prjct-app/pi-clipboard/main/docs/covers/pi-team.png"
25
25
  },
26
26
  "scripts": {
27
+ "build": "rm -rf build && mkdir -p build && npm pack --ignore-scripts --pack-destination build >/dev/null && tar -xzf build/*.tgz -C build --strip-components=1 && rm -f build/*.tgz",
27
28
  "check": "tsc --noEmit && npm run check:immutable",
28
29
  "check:immutable": "! grep -rn '\\blet\\b' src/ || (echo 'Use immutable values: no let bindings in src/' && exit 1)",
29
30
  "test": "node --import tsx --test tests/*.test.ts tests/*.test.mjs",
30
- "check:package": "npm pack --dry-run --ignore-scripts"
31
+ "check:package": "npm pack --dry-run --ignore-scripts",
32
+ "build:pi": "node scripts/build-pi.mjs"
31
33
  },
32
34
  "engines": {
33
35
  "node": ">=22.19.0"
@@ -43,6 +45,7 @@
43
45
  "@earendil-works/pi-coding-agent": "0.85.1",
44
46
  "@earendil-works/pi-tui": "0.85.1",
45
47
  "@types/node": "^22.19.0",
48
+ "esbuild": "^0.28.1",
46
49
  "tsx": "^4.20.0",
47
50
  "typebox": "1.3.7",
48
51
  "typescript": "^5.9.3"
@@ -68,5 +71,8 @@
68
71
  "CONTRIBUTING.md",
69
72
  "LICENSE",
70
73
  "CHANGELOG.md"
71
- ]
74
+ ],
75
+ "dependencies": {
76
+ "@prjct.app/pi-tui-kit": "file:../pi-tui-kit"
77
+ }
72
78
  }
@@ -0,0 +1,37 @@
1
+ import type { AutocompleteItem } from '@earendil-works/pi-tui';
2
+ import { bounded } from '../dynamic/domain.ts';
3
+ import { completer } from '@prjct.app/pi-tui-kit';
4
+
5
+ export const TEAM_HELP = '/team <objective> | status | history | doctor | cancel [run-id] | help';
6
+ export type TeamCommand =
7
+ | { readonly action: 'objective'; readonly objective: string }
8
+ | { readonly action: 'status' | 'history' | 'doctor' | 'help' }
9
+ | { readonly action: 'cancel'; readonly runId?: string };
10
+ const removed = new Set(['create', 'join', 'migrate', 'legacy', 'start', 'stop', 'kill', 'leave', 'close', 'purge', 'receive', 'inbox']);
11
+ export function parseTeamCommand(input: string): TeamCommand {
12
+ const text = input.trim();
13
+ bounded(text, 8192, 'Objective');
14
+ if (!text) return { action: 'status' };
15
+ const [first, ...args] = text.split(/\s+/);
16
+ const action = first!.toLowerCase();
17
+ // Old lifecycle commands were short ("create shop lead"). A sentence that
18
+ // merely starts with one of those words ("Create three files…") is an objective.
19
+ if (removed.has(action) && args.length <= 2) throw new Error(`Unsupported Team command. ${TEAM_HELP}`);
20
+ if (['status', 'history', 'doctor', 'help'].includes(action)) {
21
+ if (args.length) throw new Error(TEAM_HELP);
22
+ return { action: action as 'status' | 'history' | 'doctor' | 'help' };
23
+ }
24
+ if (action === 'cancel') {
25
+ if (args.length > 1 || (args[0] && !/^[a-zA-Z0-9-]{1,128}$/.test(args[0]))) throw new Error(TEAM_HELP);
26
+ return { action, ...(args[0] ? { runId: args[0] } : {}) };
27
+ }
28
+ return { action: 'objective', objective: text };
29
+ }
30
+ /** Subcommands with the prjct mark. Anything else typed after /team is a new objective. */
31
+ export const commandCompletions: (prefix: string) => AutocompleteItem[] | null = completer([
32
+ { value: 'status', description: 'panel of Runs and Experts' },
33
+ { value: 'history', description: 'the last Runs as text' },
34
+ { value: 'doctor', description: 'ownership and blocked experts' },
35
+ { value: 'cancel', description: 'cancel the active Run (owner only)' },
36
+ { value: 'help', description: 'what /team accepts' },
37
+ ]);