@aeondave/pi-persona 1.16.3 → 1.17.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/AGENTS.md CHANGED
@@ -10,6 +10,9 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
10
10
 
11
11
  - Typecheck (must stay clean): `npm run typecheck` (`tsc --noEmit`, strict)
12
12
  - All tests: `npm test` · unit only: `npm run test:unit`
13
+ - Settings SDK smoke (isolated profile, no provider/network requests):
14
+ `node --import tsx scripts/settings-smoke.mjs [absolute-pi-package-entry]`.
15
+ Omitting the argument uses the development SDK; pass an installed host entry to qualify that version.
13
16
  - One file: `node --import tsx --import ./test/setup/hermetic-env.ts --test test/unit/core/frontmatter.test.ts`
14
17
  - The second `--import` is the hermeticity choke point: it strips the whole `PI_PERSONA_*`
15
18
  namespace and pins `PI_AGENT_DIR` at a throwaway dir before any test module loads, so a
@@ -115,8 +118,9 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
115
118
  never double-reported; snapshots leave running children active. In interactive sessions `delegate`
116
119
  is background-by-default (`sync: true` opts a call out; headless `pi -p` defaults to sync so the
117
120
  single turn carries the result). An explicit `async` flag takes precedence over `sync` and the default.
118
- Periodic async status is a durable, expandable operator-only entry, never a model wake or context
119
- message; completions, asks, unread bus messages, and newly-stalled alerts keep their actionable paths.
121
+ 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.
120
124
  Message ids and run ids are separate: `intercom message { messageId }` retrieves retained bus
121
125
  text, `intercom result { to: runId }` retrieves a settled run. Both explicit and automatic inbox
122
126
  drains retain bounded history (256 messages / 256,000 body characters). Ask settlement clears
@@ -233,7 +237,7 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
233
237
  - `src/engine/` — `child.ts`, `inproc.ts` (default), `adapter.ts`, `async.ts` (async tracker/peek), `worktree.ts` (git-worktree isolation), `stream.ts` (event→state).
234
238
  - `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`).
235
239
  - `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.
236
- - `src/persona/` — `persona.ts` (parse + `expandCouncilPreset`), `controller.ts`, `gating.ts`, `orchestrate.ts`, `config-store.ts`.
240
+ - `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).
237
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).
238
242
  - Bundled data-driven assets (discovery precedence builtin < user `~/.pi/agent/persona` < project `.pi/`):
239
243
  `personas/*.md`, `agents/*.md` (personas+agents share a folder, split by `persona: true` — a
@@ -249,6 +253,21 @@ the shared behavioral prompt layer: [`docs/SPINE.md`](docs/SPINE.md).
249
253
 
250
254
  ## Adding a new X (data-driven — usually no core change)
251
255
 
256
+ Settings use `/persona-settings`, a full-page registry-driven UI independent of Pi's `/settings`.
257
+ The only active preferences file is `<agentDir>/persona/settings.json` (version, sparse preferences,
258
+ per-persona model assignments, last selection). `src/core/settings.ts` owns row metadata and validation;
259
+ add/remove public preferences there rather than special-casing UI fields. Route every write through
260
+ `patchPersonaSettings`; exact null patches remove overrides without erasing concurrent edits.
261
+ The retired `config.json` and `state.json` are migration inputs only, archived as `.migrated` after
262
+ the canonical file commits. Malformed files remain untouched; never add a truncating fallback writer.
263
+ Scalar preferences apply after a Pi restart or native `/reload`, not `/new` (never auto-reload live workers).
264
+ Model edits apply to future runs through the existing participant resolver. `/persona reload` only
265
+ refreshes resources, not startup preferences. Native Pi settings and compaction are not owned here.
266
+ Mind/Flow contribute supported current values through `pi-persona:settings:collect:v1` (`{ add(id, values) }`);
267
+ their five preferences share the same registry/file. Do not hard-import a companion or start services
268
+ while collecting settings. Flow port/idle changes are for the next shared-service start, never a restart
269
+ of a service other Pi sessions are using. Skill Optimizer remains independent.
270
+
252
271
  1. **Strategy**: add `src/orchestration/strategies/<name>.ts` (export a `Strategy`), register it in `strategy.ts` `BUILTINS`, add a unit test.
253
272
  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.
254
273
  3. **Agent**: `agents/<name>.md` — optional `tools`, `model`, `isolation: worktree`, `mcp: true`
package/CHANGELOG.md CHANGED
@@ -6,6 +6,62 @@ used in its [GitHub releases](https://github.com/AeonDave/pi-persona/releases).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.17.1](https://github.com/AeonDave/pi-persona/releases/tag/v1.17.1) - 2026-10-20
10
+
11
+ ### Added
12
+
13
+ - Add `/persona-settings`, a dedicated TUI/RPC editor for pi-persona preferences, persona model
14
+ assignments, and diagnostics, plus `/persona-settings show` for a read-only headless report. The
15
+ full-page TUI supports filtering, resetting a saved override, and opening model settings.
16
+ - Persist preferences, per-persona model assignments, and the remembered persona in one versioned
17
+ `<agentDir>/persona/settings.json`. On first migration, the retired model and state files are
18
+ imported and unchanged originals are archived as `.migrated` when safe; failed or unsafe
19
+ migration keeps the source files and reports a warning.
20
+ - Let users select a model-settings owner and profile explicitly without activating it. Model
21
+ assignments affect future runs; inline pins remain read-only, and resetting removes only the
22
+ saved override. Public environment overrides retain startup precedence; scalar preference changes
23
+ wait for a Pi restart or native `/reload` (not `/new`) and are never auto-reloaded.
24
+ - Keep persona-selection revisions separate from unrelated settings saves so companion memory scopes
25
+ can follow explicit re-selections without mistaking model edits for persona switches.
26
+ - Include the loaded Mind/Flow companions in the same panel and preferences file, using optional
27
+ public-bus discovery rather than hard plugin dependencies. Keep Skill Optimizer separate and
28
+ preserve per-run `--no-flow` and the existing shared service's lifetime.
29
+
30
+ ### Fixed
31
+
32
+ - Count streamed textual tool output as child liveness without adding tool output to the assistant
33
+ transcript. Queued delegates start their stall window only after concurrency admission.
34
+ - Keep interactive stall episodes operator-only and expandable. Headless runs receive at most one
35
+ successfully delivered stall wake per run lifetime; real progress still re-arms operator episodes.
36
+ - Reject malformed UTF-8 settings and migration inputs rather than silently replacing bytes during
37
+ a save. Preserve invalid originals and keep concurrent preference/model edits in one safe store.
38
+
39
+ ## [1.17.0](https://github.com/AeonDave/pi-persona/releases/tag/v1.17.0) - 2026-10-19
40
+
41
+ ### Added
42
+
43
+ - `/persona models [council-persona]` refreshes the host catalog and reopens model selection for
44
+ the active caller's council participants, preserving cancelled choices, unrelated assignments,
45
+ and explicit inline model pins. No supervisor switch or model request is required.
46
+ - A hermetic installed-host Pi 1.3.0 qualification script covers real command dispatch, assignment
47
+ persistence, custom-message starts and missing-auth council recovery without network requests.
48
+
49
+ ### Fixed
50
+
51
+ - Publish terminal progress for single delegates before removing their tree node, preserving `done`
52
+ status in Flow and other telemetry consumers.
53
+ - Preserve Exocom reply references and optional target-correlated transport IDs while keeping each
54
+ queued/terminal telemetry row stable; retain bounded broadcast delivery receipts.
55
+ - Report an exact acknowledged broadcast receipt as delivered even when failures for other peers
56
+ exceed the retained sample; recipients without exact evidence remain conservatively unknown.
57
+ - Classify Pi's `No API key found for <provider>` diagnostic as a provider failure in both engines,
58
+ allowing the existing single main-model recovery for councils/strategies/flows. Ordinary delegate
59
+ provider pins remain strict; aborts, timeouts, contract and agent failures remain terminal.
60
+ - Report a refused persona model switch instead of silently recording an override when Pi rejects
61
+ it for missing authentication; keep the actual session model and restoration state intact.
62
+ - Remove a timing race from the supervisor-ask telemetry test: verify the persisted timeout
63
+ transition history rather than polling a transient 20 ms state through asynchronous disk writes.
64
+
9
65
  ## [1.16.3](https://github.com/AeonDave/pi-persona/releases/tag/v1.16.3) - 2026-10-13
10
66
 
11
67
  ### Fixed
package/README.md CHANGED
@@ -39,9 +39,9 @@ your customizations.
39
39
  | Install option | Command |
40
40
  |---|---|
41
41
  | This project only | `pi install -l npm:@aeondave/pi-persona` |
42
- | Pin this release | `pi install npm:@aeondave/pi-persona@1.16.3` |
42
+ | Pin this release | `pi install npm:@aeondave/pi-persona@1.17.1` |
43
43
  | Update an unpinned install | `pi update npm:@aeondave/pi-persona` |
44
- | Git alternative | `pi install git:github.com/AeonDave/pi-persona@v1.16.3` |
44
+ | Git alternative | `pi install git:github.com/AeonDave/pi-persona@v1.17.1` |
45
45
 
46
46
  Restart Pi after updating. For a pinned install, install the new version or tag explicitly.
47
47
  If switching from a Git install, remove that source first with
@@ -91,9 +91,15 @@ the worker has acted on it.
91
91
  | `/peek` | Show a compact progress summary |
92
92
  | `/persona list` | List installed personas |
93
93
  | `/persona off` | Turn off the active persona |
94
+ | `/persona models [council-persona]` | Refresh the catalog and reselect council models for the active persona |
95
+ | `/persona-settings` | Open pi-persona's settings in the TUI or RPC UI |
96
+ | `/persona-settings show` | Show a read-only settings report, including in headless sessions |
94
97
  | `/models <query>` | Find an available model |
95
98
  | `/doctor` | Check configuration and available capabilities |
96
99
 
100
+ The settings panel also includes Mind and Flow when those updated companions are loaded; all three
101
+ use one `persona/settings.json`. Skill Optimizer keeps its separate configuration.
102
+
97
103
  For time-based work, ask “remind me in ten minutes.” For an event, ask “let me know when this build
98
104
  finishes.” Keep Pi open while timers and monitors are running; they are not restored after a restart.
99
105
  See [timers and monitors](docs/MONITORS.md) for examples.
@@ -151,6 +157,8 @@ Strategies are not restricted to the similarly named persona: `council` can sele
151
157
  custom role-based groups without team files. Model selection includes external arbiters. In councils
152
158
  and flows, a provider failure or unavailable model can recover once on the supervisor's model, with
153
159
  the switch visible and both attempts accounted; ordinary delegates keep explicit provider pins strict.
160
+ To replace old council assignments without changing supervisors, run `/persona models magi`
161
+ (or `/persona models` for the active council). Cancel a choice to keep its previous value.
154
162
  See [the reference](docs/REFERENCE.md) for an inline council example and recovery limits.
155
163
 
156
164
  ## Make it yours
@@ -163,6 +171,12 @@ only when you intend to discard customizations to those files.
163
171
 
164
172
  For optional long-term memory, add [pi-persona-mind](https://github.com/AeonDave/pi-persona-mind).
165
173
 
174
+ `/persona-settings` is separate from Pi's `/settings`: it edits pi-persona preferences and saved
175
+ persona model assignments, not native Pi or compaction settings. In the TUI, `/` filters settings,
176
+ `r` resets the selected saved override, and `m` opens model assignment editing. Scalar preference
177
+ changes apply after restarting Pi or Pi's native `/reload`, not `/new`; `/persona reload` only
178
+ refreshes persona definitions. Model changes apply to future runs. See [configuration and settings](docs/REFERENCE.md#configuration-and-data).
179
+
166
180
  ## Documentation
167
181
 
168
182
  | Guide | What's inside |
@@ -187,6 +201,8 @@ npm test
187
201
  ```
188
202
 
189
203
  Pi loads the TypeScript extension directly; there is no build step.
204
+ To qualify a separately installed Pi 1.3.0 host without cloud calls or your profile, run
205
+ `node --import tsx scripts/qualify-host.mjs <absolute-path-to-pi-coding-agent-package>`.
190
206
  The [CI workflow](https://github.com/AeonDave/pi-persona/actions/workflows/test.yml) runs strict
191
207
  typechecking, dependency auditing and the test suite on Linux and Windows. The OpenSSF badge links to the project's
192
208
  scored supply-chain checks; it is not a certification that the code is free of vulnerabilities.
@@ -200,7 +200,8 @@ engine, telemetry, UI, and tool surfaces; it is not part of the transport/domain
200
200
  lifecycle metadata only, never an agent-message router or control surface. See
201
201
  [`TELEMETRY.md`](./TELEMETRY.md) for privacy, retention, writer leases, and shutdown behavior.
202
202
  - **`src/persona/`** — identity: `persona.ts` (parse + `expandCouncilPreset` + `composeSystemPrompt`),
203
- `controller.ts`, `gating.ts`, `orchestrate.ts`, `config-store.ts`, `state.ts` (last-selected persona),
203
+ `controller.ts`, `gating.ts`, `orchestrate.ts`, `config-store.ts` (pure model helpers),
204
+ `settings-store.ts` (canonical preferences, per-persona models and last-selected persona),
204
205
  `spine.ts` (the shared behavioral layer's SOURCE resolution — docs/SPINE.md; composition sits in
205
206
  `persona.ts` for supervisor turns and in the engines for delegated legs).
206
207
  - **`src/tools/`** — `delegate.ts`, `intercom.ts`, `exocom.ts` (the `exocom_list`/`exocom_send`
@@ -300,6 +301,54 @@ inline model pins taking precedence. If distinct full roles share a short displa
300
301
  keeps the hint (with `#N` suffixes for repeated node keys) but omits the uncertain model label rather
301
302
  than borrowing another role's choice. Routing still resolves the full role key, not the display hint.
302
303
 
304
+ **Model assignment repair is configuration, not execution recovery.** `/persona models [profile]`
305
+ is an explicit user edit in the Pi adapter: it reuses `resolveCouncilInvocation`,
306
+ `collectModelParticipants` and the canonical locked settings writer, without adding another roster,
307
+ model store or provider-routing policy. The active caller owns saved assignments even when borrowing
308
+ another council profile. Automatic discovery stays never-nag; explicit editing may replace saved or
309
+ agent defaults, but not inline pins. Host catalog refresh has a 20-second cooperative deadline;
310
+ reported cancellation/errors or a caller/session change leave assignments untouched. Selection uses
311
+ the refreshed host availability snapshot rather than treating all catalog entries as authenticated.
312
+ Cancelled rows preserve their old choices; accepted choices merge without clearing other personas.
313
+ Running engines retain their captured assignments. Missing credentials are classified at the engine
314
+ adapter boundary; only the SDK decides whether a main-model recovery is admissible. This separation
315
+ avoids a global reset or automatic repinning that could silently change billing and panel diversity.
316
+
317
+ **Unified settings.** `/persona-settings` is independent of Pi's `/settings`: full-page TUI, native
318
+ RPC dialogs, and a read-only `show` report. `core/settings.ts` is the public preference registry and
319
+ validator; the UI and environment precedence consume it rather than maintaining duplicate field lists.
320
+ All persistent Persona preferences live in `<agentDir>/persona/settings.json`, version 1: sparse
321
+ `preferences`, `personas` model maps and `lastPersona`. `settings-store.ts` owns bounded validated reads,
322
+ cross-process locked field patches and atomic replacement. Null removes an exact override; unknown
323
+ top-level and per-persona fields survive unrelated edits. Malformed/unsupported files are never
324
+ overwritten: activation warns and falls back, while editing fails closed. Fresh read-only inspection
325
+ does not materialize defaults.
326
+
327
+ `selectionRevision` is a non-negative counter updated only by explicit `lastPersona` patches,
328
+ including re-selection of the same name. Model/preference saves never advance it. Companion scope
329
+ readers must use this semantic marker, not the settings file's mtime, as evidence of a selection gesture.
330
+
331
+ Legacy `config.json` and `state.json` are imported only if the canonical file is absent, then archived
332
+ as exact independent `.migrated` copies after commit. Archive conflicts preserve the original and warn;
333
+ an existing canonical file is never reimported. `PI_PERSONA_STATE_FILE` only selects a legacy import
334
+ source. No active legacy writer remains. Explicit public environment values retain startup precedence;
335
+ internal child/bus guards are not preferences. Definitions, credentials and native Pi settings remain
336
+ with their existing owners.
337
+
338
+ Scalar preferences are activation-scoped: saving marks differences pending until a Pi restart or
339
+ Pi's native `/reload`, not `/new`; the panel never reloads automatically. `/persona reload` only refreshes resources.
340
+ The Models section selects the assignment owner independently of the viewed council/orchestration
341
+ profile, supports exact-key resets and keeps inline pins immutable. It patches the same file and updates
342
+ the resolver cache for future runs, never changing an already-running worker. Native compaction is untouched.
343
+
344
+ Mind/Flow settings use optional synchronous discovery on Pi's public event bus
345
+ (`pi-persona:settings:collect:v1`). The owning companion reports its current startup values; the same
346
+ central registry validates owner/type and renders only supported fields. Persona alone writes their
347
+ preferences in the canonical file; companions independently read their known fields, retaining
348
+ standalone operation with no hard runtime dependency on Persona. No collection call performs I/O,
349
+ starts services or wakes a model. Flow's desired port and idle grace apply to the next shared-service
350
+ startup, never to a daemon other sessions are already using. Skill Optimizer is outside this contract.
351
+
303
352
  ### MCP (and other `session_start`-scoped extensions) in sub-agents
304
353
 
305
354
  **A sub-agent does NOT share the supervisor's MCP session, and an in-process sub-agent gets NO MCP at
@@ -644,12 +693,14 @@ a local process able to modify that directory is already inside the trust bounda
644
693
  if delayed input hooks outlast the active run; no supported post-admission ordering guarantee
645
694
  has been established. The supervisor otherwise returns control and
646
695
  goes idle until a wake; the **peek watchdog** fires while async children run but stays SILENT unless
647
- there is something to act on — a healthy background run should not interrupt. It can start a model turn
648
- only for actionable events: a **fast** wakeup (`PI_PERSONA_PEEK_MS`, ~30s, `0` disables) when a child NEWLY
649
- crosses the `STALL_FLAG_MS` (90s) stall window (a focused *possibly stuck* alert, framed patience-first —
650
- ask the leg, don't probe its environment); messages to the supervisor also wake it. The **slow routine status card**
651
- (`PI_PERSONA_CHECKIN_MS`, ~5 min, `0` disables) is a durable, expandable operator-only session entry
652
- with the compact ProgressView digest — never full transcripts — and does not start a model turn. Live supervisor asks
696
+ there is something to act on — a healthy background run should not interrupt. Every newly detected stall
697
+ episode (`STALL_FLAG_MS`, 90s) appends a durable, expandable operator-only warning entry, including while
698
+ the supervisor is busy. Interactive stalls never wake the model. In headless sessions, the first stall
699
+ episode may produce one focused patience-first model wake per run lifetime (ask the leg, don't probe its
700
+ environment); later stall episodes for that run do not re-arm the model-wake budget. Unread messages to the
701
+ supervisor retain their existing wake path. The **slow routine status card** (`PI_PERSONA_CHECKIN_MS`,
702
+ ~5 min, `0` disables) is a durable, expandable operator-only session entry with the compact ProgressView
703
+ digest — never full transcripts — and does not start a model turn. Live supervisor asks
653
704
  are derived from the exact engine handle's optional `isWaitingForSupervisor` predicate: tree/F9/peek
654
705
  show waiting, and the watchdog suppresses false stalls until the ask settles. Parent clocks advance
655
706
  with child engine events (liveness, not proof of useful work); parents are waiting only when every
package/docs/REFERENCE.md CHANGED
@@ -188,6 +188,106 @@ files are seed sources. A fresh install has no installed personas until `/person
188
188
  | Contract | `contracts/*.contract.json` | structured output fields requested by name and pinned per run |
189
189
  | Preset | `presets/*.preset.json` | reusable council strategy, roster, and parameter bundle |
190
190
 
191
+ ### Unified pi-persona settings
192
+
193
+ `/persona-settings` opens pi-persona's own settings editor in the TUI or RPC UI. It is a separate
194
+ extension command; it does not replace Pi's native `/settings` or edit native Pi preferences such
195
+ as compaction. The TUI is a full-page, filterable panel (`/` to search, `r` to reset the selected
196
+ saved override, `m` for model assignments, `d` for diagnostics); the RPC UI offers the same
197
+ settings, model, and diagnostics flows. `/persona-settings show` emits a read-only report and is
198
+ available in headless sessions.
199
+
200
+ The sole active settings file is `<agentDir>/persona/settings.json` (normally
201
+ `~/.pi/agent/persona/settings.json`). Its version-1 top-level shape is:
202
+
203
+ ```json
204
+ {
205
+ "version": 1,
206
+ "preferences": { "engine": "inproc", "nudge": true },
207
+ "personas": { "dev": { "models": { "reviewer": "provider/model-id" } } },
208
+ "lastPersona": "dev"
209
+ }
210
+ ```
211
+
212
+ `preferences` contains the public extension settings; `personas.<name>.models` preserves
213
+ provider-qualified model assignments, and `lastPersona` remembers the selection when
214
+ `PI_PERSONA_PERSIST` is enabled. The preference registry groups all editable scalar settings as:
215
+
216
+ | Group | Preference IDs |
217
+ |---|---|
218
+ | Discovery | `extraDirs`, `seed` |
219
+ | Startup | `defaultPersona`, `persist` |
220
+ | Interface | `keybinding` |
221
+ | Delegation | `delegateDefaultAllow`, `childThinking`, `engine`, `peekEveryMs`, `checkInEveryMs`, `agentHardTimeoutMs`, `agentStartupTimeoutMs`, `nudge`, `asyncRetain`, `ledgerV2`, `changeReport` |
222
+ | Collaboration | `broker`, `exocom` |
223
+ | Prompt | `spine`, `spineLegs` |
224
+ | Mind (when loaded) | `mindCapture` (`auto`/`prompt`/`off`), `mindNudge` |
225
+ | Flow (when loaded) | `flowEnabled`, `flowPort`, `flowIdleMs` |
226
+
227
+ `disabled` and the storage path are not editable preferences. Model assignments remain scoped by
228
+ persona owner, rather than being promoted into global preferences.
229
+ `selectionRevision` is internal metadata in the same file: explicit persona-selection gestures
230
+ increment it, while model/preference edits leave it unchanged. Companion readers can therefore
231
+ distinguish a re-selection from an unrelated settings save without following whole-file timestamps.
232
+ The editor never reloads Pi automatically. Scalar preferences load after a Pi restart or Pi's native
233
+ `/reload`; `/new` alone does not reactivate Persona's extension factory. `/persona reload` reloads
234
+ persona definitions and does not apply preference changes. The panel shows when a saved value is
235
+ pending reload. Model assignments are read by the
236
+ existing resolver for future runs, so they do not alter workers already running.
237
+
238
+ Mind and Flow report their supported settings through Pi's public event bus; the panel does not import
239
+ or require either plugin. Their preferences live in the same `preferences` object above, not in new
240
+ plugin configuration files. Skill Optimizer remains independent. Absent companions do not expose
241
+ nonfunctional controls. Updated companion versions are needed to contribute settings to this panel.
242
+
243
+ `flowEnabled` controls default dashboard attachment; `--no-flow` still opts out for one run.
244
+ `flowPort` is the desired port for the next shared service start, not a command to move an existing
245
+ service. `flowIdleMs` is the advanced shared-service idle grace (50–60000 ms): it applies only with
246
+ no Pi registrations **and** no browser SSE clients. Saving it never restarts another Pi's service.
247
+
248
+ Explicit public `PI_PERSONA_*` environment values retain startup precedence over saved preferences.
249
+ The panel marks an environment-controlled setting as locked; resetting its saved value only removes
250
+ that saved override and does not defeat the environment. `PI_PERSONA_DISABLE` is an internal
251
+ process guard and is not an editable setting.
252
+
253
+ When no canonical `settings.json` exists, startup imports the retired `config.json` model store and
254
+ `state.json` persona state (or the path supplied by `PI_PERSONA_STATE_FILE`) once. Successfully
255
+ migrated, unchanged legacy files are archived as `<file>.migrated`; if validation, safe archival, or
256
+ concurrency checks fail, the legacy source is kept and a warning is reported. Once `settings.json`
257
+ exists, it is the only active store: legacy files are not dual-read. An invalid or unsafe canonical
258
+ file is reported rather than overwritten. `PI_PERSONA_STATE_FILE` now only selects a legacy state
259
+ file to import when the canonical file does not yet exist; it does not move or redirect active
260
+ settings storage.
261
+
262
+ The model editor asks you to choose an owner persona and then its current or a named council
263
+ profile; it never activates that persona. It lists the roster and strategy-declared auxiliary
264
+ participants using the runtime model precedence described below. The searchable model picker uses
265
+ Pi's currently available provider-qualified models; opening the editor does not refresh provider
266
+ catalogs, and refresh is an explicit action. Saving or removing an assignment affects future runs.
267
+ Reset removes only that saved key (`null` in a settings patch), revealing lower-precedence sources
268
+ such as a bare agent assignment; inline model pins remain read-only. “Other saved assignments” lets
269
+ you remove keys outside the selected profile without exposing unsafe legacy key text.
270
+
271
+ The existing `/persona models [council-persona]` shortcut remains available for the active caller.
272
+ It refreshes the model catalog before editing, and a named council profile does not change which
273
+ persona owns the saved assignments. Use the Models action in `/persona-settings` when you need to
274
+ choose the owner explicitly.
275
+
276
+ #### Companion settings adapter contract
277
+
278
+ The optional synchronous bus channel `pi-persona:settings:collect:v1` receives
279
+ `{ add(id, values) }`. A loaded supervisor companion responds with `add("mind", { mindCapture,
280
+ mindNudge })` or `add("flow", { flowEnabled, flowPort, flowIdleMs })`, using its current startup
281
+ snapshot. Each may report a supported subset. Persona validates ownership/types against the same
282
+ registry used by the editor and displays only reported fields; an unrelated plugin cannot contribute
283
+ an optimizer or native Pi setting through this channel. Contributors must not read credentials,
284
+ refresh providers, write files, wake the model or start a service in response to collection.
285
+
286
+ Persona alone patches the canonical settings file; companions read their own known fields and
287
+ preserve their standalone operation and environment overrides. They must release/re-arm their
288
+ listener with the owning extension lifecycle. Add a new control by updating registry metadata,
289
+ validation, the owning consumer and contract tests—not by adding another settings writer.
290
+
191
291
  `tools` accepts an allowlist such as `[read, grep]`, an `allow`/`deny` object, or `[]` for no tools.
192
292
  Entries are plain tool names or `*` patterns; Pi's `+name`/`-name` default-tool modifier
193
293
  syntax is not an allowlist and is rejected. Use the `deny` field to remove tools. Omit `tools` to
@@ -303,10 +403,28 @@ With an active persona, UI, and at least two configured models, unassigned parti
303
403
  picked and saved for the active persona, even when borrowing another persona's council profile.
304
404
  Dismissing a picker suppresses another prompt for that participant during the session, not for
305
405
  later teams with different participants. Headless runs never prompt.
406
+
407
+ Run `/persona models` to explicitly reselect the active council's models, or `/persona models magi`
408
+ to edit the MAGI panel while keeping another supervisor active. The optional name selects an
409
+ installed council persona, not a strategy or team. The command refreshes the host catalog first,
410
+ then offers each editable participant, including previously dismissed choices and saved or agent
411
+ defaults. One available model is enough for this explicit edit. Choices are saved under the **active
412
+ caller's** persona, just as for `council({ persona: "magi" })`; it never activates the borrowed
413
+ profile. Cancelled choices, unrelated assignments and other personas remain unchanged. Inline
414
+ `members[].model` pins still win and are not editable here. TUI/RPC UI is required; no model call
415
+ is made. Changes apply to future runs, not workers already running. `/persona magi` still switches
416
+ the supervisor; `/persona reload` reloads definitions without resetting saved models.
417
+ Refresh is bounded to 20 seconds cooperatively; provider errors, cancellation, or a persona/session
418
+ change cancel the edit without replacing assignments. The list uses the host's refreshed availability,
419
+ not every model in its catalog.
420
+
306
421
  An unavailable choice is not overwritten: a provider or unknown-model failure may recover once
307
422
  on the main model, with the switch reported and both attempts accounted. This does not relax
308
423
  ordinary delegate provider pins. The MAGI persona retains its canonical core team; the Judge
309
424
  persona retains impartial arbitration. Other personas choose whichever strategy fits the task.
425
+ The exact Pi diagnostic `No API key found for <provider>` counts as a provider failure in both
426
+ engines, so it can use this same bounded recovery. If the main model is the failing model too,
427
+ recovery cannot help: use `/login` or change `/model`, then reselect with `/persona models`.
310
428
 
311
429
  **A delegation policy on any persona** — fields are generic and are never keyed to a persona name.
312
430
  The runtime rejects incomplete briefs before a model call, supplies a missing contract, rejects
@@ -420,8 +538,9 @@ test; the data-driven definitions above require no core change.
420
538
  **Keys** — `f8` cycles persona; `f9` or `/agents` opens the agent overlay: ↑↓ navigate, ⏎ open, `x`
421
539
  stop, `s` steer, and Esc close.
422
540
 
423
- **Commands** — `/persona [name|off|list|reload|seed|restore]` · `/models [query]` ·
424
- `/orchestrate <task>` · `/flow <name> <task>` · `/peek [id]` · `/exocom` · `/doctor`.
541
+ **Commands** — `/persona [name|off|list|reload|seed|restore|models [council-persona]]` ·
542
+ `/persona-settings [show]` · `/models [query]` · `/orchestrate <task>` · `/flow <name> <task>` ·
543
+ `/peek [id]` · `/exocom` · `/doctor`.
425
544
 
426
545
  `/doctor` reports the cross-process broker's lifecycle on one line: `broker: on — endpoint …` once
427
546
  the host is listening, `broker: on — (starting…)` mid-attempt, or
@@ -462,11 +581,14 @@ from a different cwd. Pi owns `--model <provider/id>`, `--thinking
462
581
  | `PI_PERSONA_KEY` | `f8` | keybinding that cycles installed personas |
463
582
  | `PI_PERSONA_PERSIST` | on | `off` stops remembering the last-selected persona across sessions |
464
583
  | `PI_PERSONA_DELEGATE_DEFAULT` | allow | `deny` (or an off-word) makes an unlisted delegate target default-deny instead of default-allow |
465
- | `PI_PERSONA_STATE_FILE` | a global default path | overrides where the last-selected-persona state is stored |
466
584
  | `PI_PERSONA_ASYNC_RETAIN` | 25 | how many settled-but-uncollected async runs are retained before FIFO-evicting the oldest |
467
585
  | `PI_PERSONA_LEDGER_V2` | off | `on` widens the delegation-ledger retry-veto key to also include role/tools/isolation, not just agent+model+task |
468
586
  | `PI_PERSONA_LEG_CHANGE_REPORT` | on | `off` disables the non-worktree files-changed report (two `git status` spawns per leg) |
469
587
 
588
+ `PI_PERSONA_STATE_FILE` is only a migration input for the retired standalone state file; it no
589
+ longer selects an active persistence path. The canonical store is always
590
+ `<agentDir>/persona/settings.json`.
591
+
470
592
  ## Development
471
593
 
472
594
  ```bash
package/docs/TELEMETRY.md CHANGED
@@ -8,6 +8,14 @@ waiting from tool arguments, output text or a quiet clock.
8
8
 
9
9
  Telemetry is a vendor-neutral v2 envelope. `producerId` and `producerVersion` identify the emitting plugin; `version` identifies the wire schema. `seq` is monotonic only within `(producerId, sessionId)`, and `id` is deduplicated within that same scope.
10
10
 
11
+ Message payload `id` is the stable lifecycle-row key: queued and terminal updates keep it unchanged.
12
+ Optional `wireId` identifies the actual transport message, and `replyTo` references that transport's
13
+ original ID. Exocom sends attach `wireId` only after an exact target-correlated successful receipt;
14
+ broadcast receipts are bounded and omitted mappings remain unknown. Do not replace the queued row's
15
+ `id` with a wire ID, infer a mapping by timestamp/alias, or merge observations from different streams.
16
+ Single delegates publish their terminal progress before tree removal, so a completed worker remains
17
+ `done` in the telemetry instead of being mistaken for a stopped in-flight leg.
18
+
11
19
  The pi-persona producer writes semantic projections under `telemetry/v2/<workspace>/<producerId>/`. `<workspace>` always identifies the Pi process's actual cwd; joining another Exocom scope does not rewrite telemetry identity. It never persists task/prompt text, tool arguments/activity, model output, paths, secrets, or message bodies. Known pi-persona event payloads are allowlisted; an unreviewed producer event retains only its envelope/type (payload `{}`) at the producer and consumer boundary. Future producers must add a reviewed allowlist/schema before publishing data intended for a consumer.
12
20
 
13
21
  JSONL compaction is atomic and hysteretic. It preserves the initial `instance.started` anchor and complete records; the configured byte limit is therefore a soft bound for an anchor plus one complete event. The `.previous` backup is recovered after an interrupted swap.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeondave/pi-persona",
3
- "version": "1.16.3",
3
+ "version": "1.17.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Supervised multi-agent orchestration for Pi — async workers, live steering, cross-session collaboration, and switchable personas.",
@@ -104,7 +104,7 @@
104
104
  "typescript": "^7.0.2"
105
105
  },
106
106
  "pi": {
107
- "image": "https://raw.githubusercontent.com/AeonDave/pi-persona/v1.16.3/assets/banner.png",
107
+ "image": "https://raw.githubusercontent.com/AeonDave/pi-persona/v1.17.1/assets/banner.png",
108
108
  "video": "https://cdn.jsdelivr.net/gh/AeonDave/pi-persona@6655d281ce7965a75303dd704bd29db73db04070/assets/demo1.mp4",
109
109
  "extensions": [
110
110
  "./src/extension.ts"
@@ -15,7 +15,7 @@ export interface PiPersonaConfig {
15
15
  persist: boolean;
16
16
  delegateDefaultAllow: boolean;
17
17
  seed: boolean;
18
- /** Explicit override for the persisted-state file (else a global default). */
18
+ /** Legacy selection-file import path; ongoing persistence uses persona/settings.json. */
19
19
  stateFile?: string;
20
20
  /** Thinking level forced on delegated children (else the supervisor's level).
21
21
  * Spawned children must carry an *explicit* level or they fall into the model's