@aeondave/pi-persona 1.17.1 → 1.17.4

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 (146) hide show
  1. package/AGENTS.md +103 -64
  2. package/CHANGELOG.md +310 -1
  3. package/README.md +37 -23
  4. package/agents/architect.md +21 -0
  5. package/agents/balthasar.md +17 -22
  6. package/agents/casper.md +17 -23
  7. package/agents/evidence-verifier.md +31 -16
  8. package/agents/melchior.md +17 -22
  9. package/agents/operator.md +15 -55
  10. package/agents/research.md +14 -48
  11. package/agents/reviewer.md +20 -39
  12. package/agents/scout.md +14 -15
  13. package/agents/verifier.md +15 -16
  14. package/docs/ARCHITECTURE.md +280 -92
  15. package/docs/COLLABORATION.md +110 -0
  16. package/docs/EXPERIENCE.md +52 -17
  17. package/docs/MONITORS.md +62 -3
  18. package/docs/README.md +12 -0
  19. package/docs/REFERENCE.md +139 -38
  20. package/docs/SPINE.md +2 -2
  21. package/docs/STRATEGIES.md +23 -8
  22. package/docs/TELEMETRY.md +1 -0
  23. package/flows/deep-review.flow.json +1 -1
  24. package/package.json +15 -8
  25. package/personas/audit.md +28 -17
  26. package/personas/dev.md +30 -32
  27. package/personas/elite.md +223 -221
  28. package/personas/judge.md +25 -19
  29. package/personas/magi.md +26 -29
  30. package/personas/planner.md +31 -24
  31. package/personas/researcher.md +25 -26
  32. package/personas/swarm.md +35 -25
  33. package/personas/verify.md +30 -19
  34. package/src/agents/agent.ts +26 -0
  35. package/src/bridge.ts +28 -10
  36. package/src/bus/broker/client.ts +119 -21
  37. package/src/bus/broker/host.ts +123 -8
  38. package/src/bus/broker/messages.ts +31 -4
  39. package/src/bus/contact.ts +113 -38
  40. package/src/bus/peers.ts +15 -0
  41. package/src/bus/result-reader.ts +88 -0
  42. package/src/core/brief.ts +436 -353
  43. package/src/core/capabilities.ts +1 -1
  44. package/src/core/config.ts +15 -10
  45. package/src/core/leg-thinking.ts +57 -0
  46. package/src/core/model-catalog.ts +171 -0
  47. package/src/core/model-tier.ts +89 -0
  48. package/src/core/models.ts +13 -4
  49. package/src/core/monitor-output.ts +85 -0
  50. package/src/core/monitor.ts +34 -2
  51. package/src/core/nudge.ts +176 -23
  52. package/src/core/ownership.ts +1 -2
  53. package/src/core/pi-compat.ts +5 -12
  54. package/src/core/seed.ts +99 -6
  55. package/src/core/session-runs.ts +87 -0
  56. package/src/core/settings.ts +1 -1
  57. package/src/core/timer.ts +34 -9
  58. package/src/core/types.ts +1 -1
  59. package/src/engine/adapter.ts +95 -27
  60. package/src/engine/async.ts +213 -155
  61. package/src/engine/child.ts +12 -4
  62. package/src/engine/host-module.ts +12 -0
  63. package/src/engine/inproc.ts +99 -53
  64. package/src/engine/result-grant.ts +62 -0
  65. package/src/engine/session-tools.ts +6 -0
  66. package/src/engine/stream.ts +10 -20
  67. package/src/engine/worktree.ts +17 -7
  68. package/src/exocom/collaboration-delivery.ts +29 -0
  69. package/src/exocom/collaboration.ts +148 -0
  70. package/src/exocom/envelope.ts +43 -4
  71. package/src/exocom/gate.ts +7 -2
  72. package/src/exocom/install.ts +47 -18
  73. package/src/exocom/ledger.ts +58 -8
  74. package/src/exocom/plane.ts +32 -1
  75. package/src/exocom/profile.ts +40 -0
  76. package/src/exocom/registry.ts +5 -0
  77. package/src/exocom/status.ts +11 -1
  78. package/src/exocom/write-guard.ts +7 -0
  79. package/src/extension/broker-host.ts +61 -9
  80. package/src/extension/engine.ts +54 -28
  81. package/src/extension/hooks.ts +952 -868
  82. package/src/extension/identity.ts +41 -75
  83. package/src/extension/persona-model-settings.ts +31 -13
  84. package/src/extension/results.ts +136 -0
  85. package/src/extension/shared.ts +32 -7
  86. package/src/extension.ts +2965 -2611
  87. package/src/loader.ts +11 -3
  88. package/src/monitor/archive.ts +235 -0
  89. package/src/monitor/session.ts +21 -1
  90. package/src/orchestration/flow-journal.ts +33 -6
  91. package/src/orchestration/flow-run.ts +160 -32
  92. package/src/orchestration/flow.ts +20 -2
  93. package/src/orchestration/reducers.ts +9 -5
  94. package/src/orchestration/render.ts +1 -1
  95. package/src/orchestration/roster.ts +71 -23
  96. package/src/orchestration/sdk.ts +61 -11
  97. package/src/orchestration/strategies/compete.ts +5 -1
  98. package/src/orchestration/strategies/council-rounds.ts +2 -0
  99. package/src/orchestration/strategies/critic-loop.ts +3 -0
  100. package/src/orchestration/strategies/debate.ts +2 -0
  101. package/src/orchestration/strategies/fanout.ts +3 -2
  102. package/src/orchestration/strategies/judge.ts +5 -1
  103. package/src/orchestration/strategies/magi.ts +40 -23
  104. package/src/orchestration/strategies/map.ts +13 -3
  105. package/src/orchestration/strategies/pair.ts +2 -0
  106. package/src/orchestration/strategies/pipeline.ts +2 -0
  107. package/src/orchestration/strategies/synthesize.ts +27 -7
  108. package/src/orchestration/strategy.ts +31 -0
  109. package/src/orchestration/types.ts +10 -0
  110. package/src/persona/model-participants.ts +20 -7
  111. package/src/persona/orchestrate.ts +2 -0
  112. package/src/persona/persona.ts +8 -4
  113. package/src/results/archive.ts +281 -0
  114. package/src/results/record.ts +166 -0
  115. package/src/results/retrieval.ts +142 -0
  116. package/src/results/types.ts +128 -0
  117. package/src/telemetry/contract.ts +1 -1
  118. package/src/tools/background-run.ts +151 -0
  119. package/src/tools/council.ts +108 -66
  120. package/src/tools/delegate-tool.ts +349 -283
  121. package/src/tools/delegate.ts +35 -216
  122. package/src/tools/exocom-collaboration-input.ts +168 -0
  123. package/src/tools/exocom-collaboration.ts +315 -0
  124. package/src/tools/exocom-profile.ts +102 -0
  125. package/src/tools/exocom-work.ts +18 -53
  126. package/src/tools/exocom.ts +35 -22
  127. package/src/tools/flow.ts +103 -30
  128. package/src/tools/intercom-tool.ts +202 -128
  129. package/src/tools/intercom.ts +1 -1
  130. package/src/tools/models.ts +34 -9
  131. package/src/tools/monitor.ts +58 -6
  132. package/src/tools/read-result.ts +52 -0
  133. package/src/tools/timer.ts +34 -15
  134. package/src/ui/agent-overlay.ts +65 -16
  135. package/src/ui/agent-tree.ts +127 -19
  136. package/src/ui/compact-widget.ts +25 -0
  137. package/src/ui/failure-summary.ts +55 -0
  138. package/src/ui/literal-tool-result.ts +24 -0
  139. package/src/ui/model-picker.ts +158 -81
  140. package/src/ui/orchestration-launch.ts +49 -0
  141. package/src/ui/persona-settings.ts +9 -3
  142. package/src/ui/presentation.ts +24 -0
  143. package/src/ui/report-body.ts +63 -0
  144. package/src/ui/session-cards.ts +366 -0
  145. package/src/ui/wake-rows.ts +262 -0
  146. package/teams.yaml +5 -1
package/AGENTS.md CHANGED
@@ -20,7 +20,11 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
20
20
  A test that exercises a variable sets it for itself, after that has run. Drop the flag and you
21
21
  are testing your shell, not the code.
22
22
  - Live end-to-end (REAL model calls, spends tokens): `npm run drive -- --persona <name> --model <provider/id> "<prompt>"`
23
+ - `drive.ts` exits non-zero and prints `[persona] ...` when the requested persona is not installed.
23
24
  - Control ops: `LIVE_MODEL=<provider/id> node --import tsx scripts/control-test.mjs` (STEER/STOP/RESUME)
25
+ - Interactive wake contract (manual, spends tokens, refuses without `LIVE_MODEL`):
26
+ `LIVE_MODEL=<provider/id> npm run smoke:wake` — RPC session, 3-leg `delegate` fan-out, asserts ONE
27
+ wake carrying all results and no peek/timer/sleep polling before it. Run it before a release.
24
28
  - Models must be **provider-qualified**, e.g. `claude-pro-max-native/claude-opus-4-8` or `.../claude-haiku-4-5`.
25
29
 
26
30
  ## Conventions
@@ -33,8 +37,13 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
33
37
  `@earendil-works/pi-*`; never bundle copies.
34
38
  - npm publishes as `@aeondave/pi-persona`. Pi supplies the four `@earendil-works/pi-*` packages
35
39
  and `typebox`: keep them as `peerDependencies: "*"` with development-only copies, never runtime
36
- dependencies. The supported host floor is Pi ≥ 1.0.0 (`MIN_PI_VERSION` and the README); Node.js
37
- must be ≥ 22.19 to match that host. Keep the npm
40
+ dependencies. Target the latest stable Pi only (currently 1.4.0, aligned with `MIN_PI_VERSION`,
41
+ the four development pins and README); do not maintain historical-host compatibility branches.
42
+ **No legacy or deferred cleanup:** remove dead code, obsolete options, superseded implementations
43
+ and compatibility-only aliases with their replacement. Update every caller, fixture and document
44
+ in the same change. A completed feature must not leave temporary scaffolding or cleanup debt.
45
+ Keep a path only when it serves the current host/runtime or a demonstrated current data contract.
46
+ Node.js must be ≥ 22.19 to match that host. Keep the npm
38
47
  `files` allowlist explicit for docs/artwork, inspect `npm pack --dry-run`, and never publish drafts.
39
48
  - Supply-chain controls: pin every GitHub Action to a verified full commit SHA; keep top-level
40
49
  token permissions read-only, grant write permissions only to the reporting job that needs them,
@@ -51,7 +60,7 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
51
60
  `createAgentSession`, DEFAULT) and **ChildProcessEngine** (`engine/child`, spawns `pi --mode json -p`,
52
61
  the correctness baseline + the path worktree isolation uses). Opt out with `PI_PERSONA_ENGINE=child`.
53
62
  BOTH enforce `RUN_LIMITS.timeoutMs` as an **idle window** (no events/output ⇒ abort; the inproc
54
- watchdog is disabled for coaching children that may legitimately block on a supervisor reply) AND
63
+ watchdog is armed for every child; a live pending ask from that child re-arms it) AND
55
64
  `PI_PERSONA_AGENT_MAX_MS` as an **opt-in hard wall-clock cap** (lifetime ceiling armed once, never
56
65
  reset — catches a busy loop the idle window never does; OFF by default (0 = unlimited) so a healthy,
57
66
  progressing child runs to completion, set `<ms>` to arm it) AND
@@ -62,9 +71,14 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
62
71
  notably a headless `mcp: true` leg whose `pi-mcp-adapter` hangs on interactive OAuth; `adapter.ts`
63
72
  turns that (turns===0 + `spec.mcp`) into a clear pre-auth remedy instead of an opaque timeout.
64
73
  The child engine delivers the task over **stdin** (`pi -p` prepends piped stdin) — never argv,
65
- which would hit Windows' ~32 KiB command-line cap on flow-phase tasks. Async delegate launches
66
- share one `maxConcurrency` semaphore (`Semaphore` in `orchestration/parallel.ts`), so an async
67
- fan-out can't open more concurrent sessions than a sync one.
74
+ which would hit Windows' ~32 KiB command-line cap on flow-phase tasks. Every delegate leg, and every
75
+ tracked `council`/`flow` run (one slot for the whole run), headless included, shares one
76
+ `maxConcurrency` launch pool (`Semaphore` in `orchestration/parallel.ts`), so a fan-out can't open more
77
+ concurrent sessions than that; the strategy SDK's own semaphore gates the legs INSIDE a run.
78
+ Any event re-arms the idle window (a chatty tool is never killed), but stall credit follows one rule,
79
+ `madeHeadway` (`engine/async.ts`): raw tool-output chunks earn none, so a leg whose tool streams forever
80
+ raises only the operator-only stall card (nothing is killed, the model is not woken). The idle-timeout
81
+ failure text names the `timeoutMs` remedy.
68
82
  Each strategy SDK also gates every `agent()` call through one semaphore, including direct
69
83
  `Promise.all` calls; `parallel` overrides cannot raise its concurrency ceiling. Token budgets
70
84
  gate admission using completed usage, so already-running legs can overshoot the threshold.
@@ -111,16 +125,27 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
111
125
  blind retry loops are stopped by the runtime `DelegationLedger` (an identical agent+model+task
112
126
  delegation that failed twice is vetoed before it spawns).
113
127
  - Dynamic sub-agents: `delegate` shapes an on-the-fly specialist with `role` (extra system prompt,
114
- appended to the agent's own) + `skills` — prompt-level only, capabilities stay the gate. Intercom
115
- `wait` snapshots already-settled reports by default in interactive/RPC sessions; `sync: true` opts
116
- into a bounded join (≤ the bus-ask timeout). Headless waits default to joining; `sync: false` takes
117
- a snapshot. Collected reports are discarded from the pending completion follow-up so they are
118
- never double-reported; snapshots leave running children active. In interactive sessions `delegate`
119
- is background-by-default (`sync: true` opts a call out; headless `pi -p` defaults to sync so the
120
- single turn carries the result). An explicit `async` flag takes precedence over `sync` and the default.
128
+ appended to the agent's own) + `skills` — prompt-level only, capabilities stay the gate.
129
+ `delegate` has NO `sync`/`async` parameter and `intercom` has NO `wait`: interactive sessions
130
+ (`ctx.hasUI`, TUI and RPC) ALWAYS run `delegate` in the background (each result an idle-gated
131
+ completion follow-up; a `tasks` fan-out is ONE follow-up when its last member settles), headless
132
+ `pi -p` ALWAYS blocks and returns results inline (nothing drains a follow-up after the single turn).
133
+ A finished child wakes the supervisor by itself, so any in-turn wait (a sleep, a peek loop, a
134
+ `timer`/`monitor` used as a wait) blocks the wake it waits for. The prompt layer says to end the
135
+ turn; the call-time guards (`PollGuard` on repeated `peek`/`result`, the long-`sleep` block) live
136
+ under `config.nudge` (`PI_PERSONA_NUDGE=off`). `council` and `flow` follow the same rule through
137
+ `tools/background-run.ts` (`createBackgroundRuns`): interactive, ONE tracker run rooted at
138
+ `async:<runId>` (receipt now, one completion follow-up); headless, the same run joined inline. A
139
+ persona's `council:` defaults resolve exactly as before when the call omits strategy/roster/params; the
140
+ mandated `orchestration:` path stays pre-turn and inline, and `/orchestrate`, `/flow` and that path
141
+ register their own abort under their root row (`visibleRoot`). A rejected `gate: true` returns
142
+ `stoppedAtGate` (`ok` stays false, so mandatory flows still fail closed; a tracker run settles
143
+ `stopped`). Stop-all, checkpoint waits, operator-result hand-off, run ids, restore honesty, flow journal
144
+ identity (`flowRunHash`) and retention are in
145
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md#supervision--the-waiting-model).
121
146
  Periodic async status and each newly detected stall episode are durable, expandable operator-only
122
- entries. Interactive stalls never wake the model; headless sessions get at most one stall wake per run
123
- lifetime. Completions, asks, and unread bus messages keep their existing actionable paths.
147
+ entries; stalls never wake the model. Completions, asks, and unread bus messages keep their
148
+ existing actionable paths.
124
149
  Message ids and run ids are separate: `intercom message { messageId }` retrieves retained bus
125
150
  text, `intercom result { to: runId }` retrieves a settled run. Both explicit and automatic inbox
126
151
  drains retain bounded history (256 messages / 256,000 body characters). Ask settlement clears
@@ -129,7 +154,7 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
129
154
  reaches the supervisor as a follow-up or tool result (prompt-injection defense).
130
155
  - **Delegation nudges** (`core/nudge.ts`, on by default): a `tool_result` hook watches the supervisor's
131
156
  OWN tool stream and, when a delegating persona grinds heavy work by hand (output burn since the
132
- last `delegate`/`council` crosses a threshold),
157
+ last `delegate`/`council`/`flow` crosses a threshold),
133
158
  APPENDS a compact labelled checkpoint to that command's result for the model AND emits the same
134
159
  checkpoint as a durable, expandable `pi-persona-nudge` TUI-only entry for the operator. It is never
135
160
  hidden/context-only: tool renderers may suppress their own `content`, but cannot suppress the
@@ -137,10 +162,11 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
137
162
  directive has decayed. Pure state machine (`DelegationNudge`); gated to
138
163
  personas holding the `delegate` tool; sub-agents run in separate sessions so the hook only sees the
139
164
  supervisor. Its counterweight is **`PersistenceNudge`** — a leg that comes back `[BLOCKED]`/`FLAG:
140
- UNKNOWN` gets a "don't bank it yet" reminder on each of the three delivery paths (sync result,
141
- background completion, `intercom wait` — the last two via `engine/async.ts`'s `renderCompletion`), but
142
- they do not scan the same text: `observe` scans the whole sync tool result (which `aggregateResults`
143
- fills with every leg's body, failures included), while `renderCompletion` scans only `status: "done"`
165
+ UNKNOWN` gets a "don't bank it yet" reminder on each of its three delivery paths (the inline
166
+ `delegate`/`council`/`flow` result — headless only; the background completion
167
+ via `engine/async.ts`'s `renderCompletion`; and an explicit `intercom result` of a done run), but
168
+ they do not scan the same text: `observe` scans the whole sync tool result (every leg's body,
169
+ failures included; a headless `delegate` returns it unclipped), while `renderCompletion` scans only `status: "done"`
144
170
  runs. A background leg that FAILED while emitting `[BLOCKED]` therefore gets the failure block but no
145
171
  persistence note — deliberate (see the `renderCompletion` doc comment), not a bug to "fix" silently.
146
172
  Nested reports from codemode's `ctx.executeTool` retain bounded provenance through Pi's explicit
@@ -148,12 +174,13 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
148
174
  the surrender marker gets the note/card. Nested programmatic data is never patched with a reminder,
149
175
  and discarded reports never generate an invisible one. Relay state clears at turn/session settlement.
150
176
  `config.nudge` (`PI_PERSONA_NUDGE=off`) silences all of it: the `tool_result` hook (the by-hand
151
- reminder and the sync-result persistence one) and both `renderCompletion` call sites, which take
152
- their `scan` through the same gate — so a background/`intercom wait` report carries no note either.
177
+ reminder and the sync-result persistence one) and the `renderCompletion` / `intercom result` paths,
178
+ which take their `scan` through the same gate — so a collected report carries no note either.
153
179
  Its standing counterpart is the **delegation brief** (`core/brief.ts`): `before_agent_start` appends
154
- a live, capability-filtered roster (agents/teams/flows) + a hand-off default to the
155
- system-prompt TAIL every turn — discovery that survives context burn.
156
- - **Sibling peer comm (in-process)**: a strategy can opt a run into direct sibling messaging
180
+ a live, capability-filtered roster (agents, teams and flows, with descriptions and gated flows tagged in
181
+ headless sessions) + a hand-off default (with one decision-class → method line, checked against the
182
+ registry) to the system-prompt TAIL every turn — discovery that survives context burn.
183
+ - **Sibling peer comm**: a strategy can opt a run into direct sibling messaging
157
184
  (`AgentRunSpec.peers` — the `debate` strategy does). The child gets a `contact_peer` tool
158
185
  (`bus/peers.ts`): `list`/`send`, ONE-WAY only (blocking stays supervisor-only, so peers can
159
186
  never deadlock), peer list scoped per engine instance (never the whole bus), send budget 20,
@@ -161,7 +188,11 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
161
188
  Delivery: the in-process engine's bridge steers incoming bus messages into the child session,
162
189
  fenced with the sender attributed OUTSIDE the fence — the same bridge delivers the supervisor's
163
190
  `intercom send` (previously a dead letter). Gated by `EffectiveCapabilities.canUseBus` (OFF iff
164
- the persona explicitly denies `intercom`). The child engine ignores `peers`.
191
+ the persona explicitly denies `intercom`). Child-process legs use the broker's engine-scoped
192
+ peer roster; `contact_peer` is installed only when that run opts in and the bus is allowed.
193
+ Granted communication tools join a nonempty child tool allowlist; explicit empty lists and
194
+ tool exclusions still win. In-process supervisor contact additionally requires `coaching`;
195
+ broker-connected children expose it when the bus is allowed.
165
196
  Design: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md#the-comm-plane-in-practice) + [`docs/STRATEGIES.md`](docs/STRATEGIES.md).
166
197
  On the peer plane: `debate` and `pair` always; `map`/`synthesize` opt-in via `params.peers`.
167
198
  `magi`/`judge`/`fanout` stay peer-less BY DESIGN — independence is their bias guard
@@ -169,38 +200,37 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
169
200
  this. `compete` runs its competitors with `isolation: worktree` (REQUIRES a clean git repo; missing
170
201
  repo/dirty checkout/worktree failure fails closed and never falls back to the real tree) and returns
171
202
  the winning diff for the SUPERVISOR to apply.
172
- - **exocom** (`src/exocom/*`, `src/tools/exocom*.ts`; opt-in `PI_PERSONA_EXOCOM=1` / `--exocom`,
173
- capability-gated): a plane apart from everything above — those are all INTERNAL to one supervisor's
174
- own run (hierarchical, session-keyed children); exocom is FLAT and EXTERNAL, between independent
175
- top-level pi instances sharing one selected scope, no parent/child relationship. Bare `--exocom`
176
- keeps the current workspace scope; exact `--exocom=Ab0T` joins that existing workspace's scope from
177
- another cwd through a persistent, collision-aware, case-sensitive four-character Base62 alias.
178
- The scope selects registry/transport/ledger/artifacts, while every peer advertises its actual home
179
- workspace id/code/safe label so external file sets are explicit. A foreign peer is advisory for
180
- writes: it can inspect its own files and use postcards/ask/answer/wait/progress/release, but
181
- `exocom_claim` is inactive and rejected because the ledger's paths are repository-relative to the
182
- scope workspace. The code is a same-host/same-agent-dir join reference, not authentication.
183
- Postcards remain one-way and non-blocking: `exocom_list`/`exocom_send`
184
- plus `exocom_name` (a reply is a send with `in_reply_to` set). The separate shared JSONL work
185
- ledger adds `claim`/`ask`/`answer`/`decline`/non-blocking `wait`/`progress`/`release`: overlapping
186
- open write sets NACK atomically, and a pending targeted ask gates that participant to answer/decline,
187
- read-only tools, and `exocom_name` identity metadata. Semantic frames are signed immediate-wake signals; the ledger is authoritative
188
- if delivery is deferred. This is cooperative runtime coordination, not OS authorization. All ten
189
- tools remain in `EXOCOM_TOOL_NAMES` (with `claim` withheld for a foreign member), targeted denies win independently, and joining requires
190
- `canUseBus` plus at least one callable closer (`answer` or `decline`) so a published peer cannot be
191
- wedged by an obligation it has no way to settle. If `exocom_name` is permitted, an unnamed top-level
192
- Pi is prompted to invent a task-derived call-sign on its first user or inbound peer turn
193
- (no catalog or extra model call). Naming is metadata permitted during pending asks, without
194
- settling them; a targeted deny suppresses the prompt. Session identity is shared with standalone
195
- `agent_name` and persisted across resume/persona changes. Until a handle is chosen, its actionable
196
- naming bootstrap is delivered through the `context` hook because custom Exocom wakes can bypass
197
- `before_agent_start`; after selection the hook stays silent. The session-hash suffix of a qualified target survives renames and telemetry resolves the
198
- same canonical session.
199
- Reuses the broker's wire framing
200
- (`bus/broker/framing.ts`) and the
201
- core fence (`fenceUntrusted`/`attributeInbound`) — no new trust-sensitive code path; inbound
202
- attribution is resolved from the registry, never the envelope's self-reported `from_name`.
203
- Design: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md#exocom--the-external-plane).
203
+ - **Exocom** (`src/exocom/*`, `src/tools/exocom*.ts`; opt-in `PI_PERSONA_EXOCOM=1` / `--exocom`;
204
+ see [the collaboration guide](docs/COLLABORATION.md)): a flat external plane between independent,
205
+ top-level Pi sessions, separate from one supervisor's internal workers. Bare `--exocom` selects the
206
+ current workspace; exact `--exocom=Ab0T` joins that existing scope from another cwd through a
207
+ persistent, case-sensitive four-character Base62 alias. The scope selects registry, transport,
208
+ ledger and artifacts; each member still advertises its actual home workspace. The code is a
209
+ same-host/same-agent-dir join reference, not authentication.
210
+ `EXOCOM_TOOL_NAMES` contains 15 call-time-gated tools: `exocom_list`, `exocom_send`, `exocom_name`,
211
+ `exocom_claim`, `exocom_ask`, `exocom_answer`, `exocom_decline`, `exocom_wait`, `exocom_release`,
212
+ `exocom_progress`, `exocom_status`, `exocom_task`, `exocom_offer`, `exocom_publish`, and
213
+ `exocom_profile`. A joined foreign-workspace peer cannot use `exocom_claim`; write-set task create
214
+ and pickup also require local scope and callable claim permission. Ledger paths are advisory
215
+ coordination, never filesystem authorization.
216
+ `exocom_task create` declares a voluntary task but does not start work or reserve files. The
217
+ immutable `task_id` is the creation frame's `msg_id`; carry it with `work_key` for take, progress,
218
+ finish, offer and publish so a reused work key cannot select another generation. Write-set pickup
219
+ and its claim are one locked ledger transaction; read-only tasks may be taken from a foreign scope.
220
+ An optional `exocom_offer` expires in at most 24 hours (one hour by default) and creates no answer
221
+ obligation. Use `exocom_ask` for an explicit blocking question. `exocom_publish` shares a bounded,
222
+ explicitly written excerpt, not another session's terminal-result archive. Peer prose is fenced and
223
+ attributed using the registry identity.
224
+ Tasks, offers and evidence are each capped at 64 records and 128 KiB aggregate. Terminal or stale
225
+ task facts and expired offers are retained for up to one hour; evidence is retained for up to one
226
+ hour. `exocom_profile` reports self-described capabilities (up to 8 tags, 48 characters each) and
227
+ optional `available`/`busy`/`away` status; absent status is unknown, and profiles confer no access.
228
+ During a pending ask, only settlement, `exocom_name`, status, explicit collaboration reads and
229
+ read-only filesystem tools remain available: profile `get` is allowed, but `set` and `clear` are not.
230
+ Naming remains metadata and does not answer the ask. Joining requires `canUseBus` plus at least one
231
+ callable ask closer (`exocom_answer` or `exocom_decline`) and is rechecked on persona changes.
232
+ Design: [architecture](docs/ARCHITECTURE.md#exocom--the-external-plane) and
233
+ [collaboration guide](docs/COLLABORATION.md).
204
234
 
205
235
  ## Testing
206
236
 
@@ -232,13 +262,18 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
232
262
  (`output`) or jobs (`exit`), with both `monitor` and `bash` permissions. Pure lifecycle is in
233
263
  `src/core/monitor.ts`, process/delivery adapters in `src/monitor/`; use the existing idle notifier
234
264
  and process-tree cleanup. Session-scoped, no restoration on restart. See [`docs/MONITORS.md`](docs/MONITORS.md).
265
+ Armed timers and active monitors also show as agent-tree rows (`monitor:<id>` / `timer:<id>`, never
266
+ agents): `src/ui/wake-rows.ts` is the pure projection, fed by the core seams `TimerScheduler`
267
+ `onArmed`/`onCancelled`, `MonitorManager` `onArmed`/`onRemoved` and `createMonitorSession`'s `observe`
268
+ option (a terminal event is observed before the permission gate). A countdown replaces the stall clock; `x`
269
+ cancels one via the stop registry; `X`, the `N agents` count and telemetry exclude them.
235
270
 
236
- - `src/core/` — pure kernel: frontmatter, permissions, contract (+`parseContract`), config, discovery, fence (`fenceUntrusted`), brief (`buildDelegationBrief` — the per-turn roster + standing hand-off default; `buildExocomBrief` — the per-turn exocom peer roster, the peer-vs-sub-agent split, and the relevance bound on a peer exchange), timer (`TimerScheduler` — the alarm engine behind the `timer` tool), types.
271
+ - `src/core/` — pure kernel: frontmatter, permissions, contract (+`parseContract`), config, discovery, fence (`fenceUntrusted`), brief (`buildDelegationBrief` — the per-turn roster + standing hand-off default; `buildExocomBrief` — the per-turn exocom peer roster, the peer-vs-sub-agent split, and the relevance bound on a peer exchange), timer (`TimerScheduler` — the alarm engine behind the `timer` tool), session-runs (pure restore-notice helper: what a reload/resume lost + the run-id counter), types.
237
272
  - `src/engine/` — `child.ts`, `inproc.ts` (default), `adapter.ts`, `async.ts` (async tracker/peek), `worktree.ts` (git-worktree isolation), `stream.ts` (event→state).
238
273
  - `src/orchestration/` — `sdk.ts` (`agent`/`parallel`/`reduce`), `strategy.ts` (registry), `strategies/*.ts`, `voting.ts`, `flow*.ts` (DAG + JSONL journal + checkpoint gates), `roster.ts` (teams + `rosterSpec`: a roster member is a bare name OR an inline `{ agent, role, model, skills }` that specialises one agent — every strategy runs members through `rosterSpec`).
239
274
  - `src/bus/` — `inproc.ts` (handle-based bus: send/ask/reply/onMessage), `contact.ts` (child `contact_supervisor` tool), `peers.ts` (child `contact_peer` sibling tool — one-way, engine-scoped), `broker/` (cross-process relay, on by default: `paths.ts`/`framing.ts`/`messages.ts` pure, `host.ts`/`client.ts` over `node:net`; `PI_PERSONA_BROKER=off` restores pre-broker spawn). `src/bridge.ts` — the child-mode-only wiring loaded when `PI_PERSONA_BUS` is set.
240
275
  - `src/persona/` — `persona.ts` (parse + `expandCouncilPreset`), `controller.ts`, `gating.ts`, `orchestrate.ts`, `config-store.ts` (pure model helpers), `settings-store.ts` (sole persistent settings owner).
241
- - `src/tools/` — `delegate.ts`, `intercom.ts`, `exocom.ts`. `src/ui/` — agent-tree/overlay, model-picker, `presentation.ts` (the collapsed-card compaction/sanitization helpers), `usage.ts`. `src/extension.ts` — the single ExtensionFactory (wires tools/commands/hooks/engines).
276
+ - `src/tools/` — `delegate.ts`, `intercom.ts`, `exocom.ts`, `background-run.ts` (tracked background council/flow runs: `launch`/`settle`, `TrackedRun`). `src/ui/` — agent-tree/overlay, model-picker, `presentation.ts` (the collapsed-card compaction/sanitization helpers), `wake-rows.ts` (the monitor/timer → tree-row projection), `usage.ts`. `src/extension.ts` — the single ExtensionFactory (wires tools/commands/hooks/engines).
242
277
  - Bundled data-driven assets (discovery precedence builtin < user `~/.pi/agent/persona` < project `.pi/`):
243
278
  `personas/*.md`, `agents/*.md` (personas+agents share a folder, split by `persona: true` — a
244
279
  persona and an agent must NOT share a name; e.g. `researcher` is the persona, `research` the agent),
@@ -248,7 +283,9 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
248
283
  install shows NO personas until the user installs them. `/persona seed` copies missing defaults
249
284
  in, `/persona restore` force-restores originals (`src/core/seed.ts`). First-run auto-install is
250
285
  **opt-in**: off by default, enable with `PI_PERSONA_SEED=on` (guarded once by marker
251
- `.pi-persona-seeded`). Contracts/presets/teams keep a builtin layer (they aren't personas).
286
+ `.pi-persona-seeded`). Release prep: add the previous tag's digest
287
+ to `LEGACY_SEEDED_DEFAULTS` for every changed persona/agent (the guard test in `seed.test.ts` prints the row).
288
+ F8 and `/doctor` point to `/persona seed` when no personas are installed. Contracts/presets/teams keep a builtin layer (they aren't personas).
252
289
  - `scripts/` — `drive.ts` (headless `pi -p` log) + `drive-status.ts` (its pure exit-code/status projection), `control-test.mjs`, `flow-test.ts`, `live-suite.mjs` (every strategy/mode against a real model), `exocom-smoke.mjs` (real socket/pipe round-trip, chat + semantic claim; run via `npm run smoke:exocom`). `test/` — unit + integration.
253
290
 
254
291
  ## Adding a new X (data-driven — usually no core change)
@@ -268,14 +305,16 @@ their five preferences share the same registry/file. Do not hard-import a compan
268
305
  while collecting settings. Flow port/idle changes are for the next shared-service start, never a restart
269
306
  of a service other Pi sessions are using. Skill Optimizer remains independent.
270
307
 
271
- 1. **Strategy**: add `src/orchestration/strategies/<name>.ts` (export a `Strategy`), register it in `strategy.ts` `BUILTINS`, add a unit test.
308
+ 1. **Strategy**: add `src/orchestration/strategies/<name>.ts` (export a `Strategy`), register it in `strategy.ts` `BUILTINS`, add a unit test. Declare `when` (one line), `roster` (positional roles, only where position matters) and `required` (param keys a call must carry) — the `council` tool description is generated from them (`strategyCatalog`) and refuses a missing required param up front; never hand-write a strategy list.
272
309
  2. **Persona**: `personas/<name>.md` — frontmatter `persona: true` + optional `council:` / `orchestration:` / `delegation:` / `coaching: true`; runtime behavior is field-driven, never keyed to a persona name.
273
- 3. **Agent**: `agents/<name>.md` — optional `tools`, `model`, `isolation: worktree`, `mcp: true`
310
+ 3. **Agent**: `agents/<name>.md` — optional `tools`, `model`, `writeSet` (repository-relative paths the
311
+ agent alone writes, e.g. `research` → `[.research/]`; inherited by a write-capable `delegate` leg that gives none, so
312
+ the ownership gate sees a declared owner — declarative, never enforced), `isolation: worktree`, `mcp: true`
274
313
  (routes the leg through the child engine so `pi-mcp-adapter` initializes and its `mcp*`/direct
275
314
  tools work — the default in-process engine leaves them "not initialized"; also settable per-leg
276
315
  via `delegate`'s `mcp: true` / `AgentRunSpec.mcp`. Pass a server session id in the task to share
277
316
  an HTTP backend's state).
278
- 4. **Team**: one line in `teams.yaml` (`name: [agent, ...]`), or per-member `- { agent, role, model?, skills? }` to build an ensemble of perspectives from ONE agent (e.g. `review` is one `reviewer` × 3 lens roles). Same-agent members are disambiguated in the live tree by a role hint (`reviewer · SECURITY`) via `rosterNodeKeys`/`roleHint` (`roster.ts`) + the SDK's per-run key (`sdk.ts`), so three lenses show as three steerable nodes — keep the seeding loops and the SDK key derivation in lockstep if you touch either.
317
+ 4. **Team**: one line in `teams.yaml` (`name: [agent, ...]`), or per-member `- { agent, role, model?, skills? }` to build an ensemble of perspectives from ONE agent (e.g. `review` is one `reviewer` × 4 lens roles; `design` pairs `architect` + `reviewer` for `critic-loop`). Same-agent members are disambiguated in the live tree by a role hint (`reviewer · SECURITY`) via `rosterNodeKeys`/`roleHint` (`roster.ts`) + the SDK's per-run key (`sdk.ts`), so four lenses show as four steerable nodes — keep the seeding loops and the SDK key derivation in lockstep if you touch either.
279
318
  5. **Flow**: `flows/<name>.flow.json` — phases + `needs`, optional `gate: true` (checkpoint).
280
319
  6. **Contract**: `contracts/<name>.contract.json` (request via `outputContract`). **Preset**: `presets/<name>.preset.json`.
281
320