pi-squire 0.14.0 → 0.16.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 (62) hide show
  1. package/CHANGELOG.md +438 -1
  2. package/README.md +1062 -152
  3. package/package.json +4 -3
  4. package/src/agents.ts +395 -124
  5. package/src/child-tasks.ts +221 -0
  6. package/src/child.ts +107 -62
  7. package/src/completion-holds.ts +104 -0
  8. package/src/dashboard.ts +144 -0
  9. package/src/git.ts +6 -1
  10. package/src/identity.ts +152 -0
  11. package/src/index.ts +585 -107
  12. package/src/local-state.ts +21 -0
  13. package/src/navigator-view.ts +186 -0
  14. package/src/navigator.ts +152 -107
  15. package/src/new-delegate.ts +17 -8
  16. package/src/poll.ts +1 -0
  17. package/src/render.ts +4 -1
  18. package/src/result-close.ts +37 -0
  19. package/src/result-restore.ts +95 -0
  20. package/src/result-sessions.ts +39 -0
  21. package/src/result-types.ts +29 -1
  22. package/src/results-presentation.ts +47 -0
  23. package/src/results.ts +176 -108
  24. package/src/resume-checks.ts +59 -14
  25. package/src/resume-picker.ts +9 -2
  26. package/src/resume.ts +69 -15
  27. package/src/session-owner.ts +279 -0
  28. package/src/task-replies.ts +247 -0
  29. package/src/task-settlement.ts +81 -0
  30. package/src/task-store.ts +26 -0
  31. package/src/tmux.ts +134 -54
  32. package/src/tool-ceiling.ts +125 -0
  33. package/src/tracking.ts +72 -65
  34. package/src/tree-close.ts +236 -0
  35. package/src/tree-fence.ts +51 -0
  36. package/src/tree-lock.ts +77 -0
  37. package/src/tree-notice.ts +124 -0
  38. package/src/tree-registry.ts +365 -0
  39. package/src/tree-settings.ts +31 -0
  40. package/src/tree-types.ts +32 -0
  41. package/src/tree-view.ts +246 -0
  42. package/src/trust-control.ts +214 -0
  43. package/src/trust-prompt.ts +163 -0
  44. package/src/trust-wire.ts +203 -0
  45. package/src/widget-activity-join.ts +24 -0
  46. package/src/widget-activity-runtime.ts +104 -0
  47. package/src/widget-activity.ts +104 -0
  48. package/src/widget-ancestors.ts +142 -0
  49. package/src/widget-current.ts +41 -0
  50. package/src/widget-data.ts +147 -0
  51. package/src/widget-focus.ts +133 -0
  52. package/src/widget-live-process.ts +31 -0
  53. package/src/widget-live.ts +356 -0
  54. package/src/widget-render.ts +181 -0
  55. package/src/widget-selection.ts +58 -0
  56. package/src/widget-settings.ts +29 -0
  57. package/src/widget-summary.ts +51 -0
  58. package/src/widget-title-file.ts +127 -0
  59. package/src/widget-title.ts +118 -0
  60. package/src/widget.ts +272 -0
  61. package/src/window-placement.ts +30 -5
  62. package/src/workspace.ts +38 -90
package/README.md CHANGED
@@ -21,13 +21,15 @@ You need:
21
21
  relative to the pane's session (see [Where delegate windows
22
22
  open](#where-delegate-windows-open)), so a client attached elsewhere does
23
23
  not matter.
24
- - A provider of `message:*`;
25
- [pi-session-mail](https://github.com/gvanderclay/pi-session-mail) is one
26
- example. Without a provider there is no way to send the task or get an
24
+ - A provider of the four `message:*` hooks, including `message:reply`;
25
+ [pi-session-mail](https://github.com/gvanderclay/pi-session-mail) **0.6.0 or
26
+ later** supplies them. Without a provider there is no way to send the task or get an
27
27
  answer, so `/delegate` refuses rather than start a delegate that cannot
28
28
  report back.
29
29
 
30
- With pi-session-mail as the provider, for example, install both from npm:
30
+ Upgrade pi-session-mail and pi-squire together, then restart or reload both
31
+ parent and delegate sessions. Older mail providers are unsupported; there is
32
+ no version negotiation or legacy automatic-answer path. Install both from npm:
31
33
 
32
34
  ```bash
33
35
  pi install npm:pi-session-mail
@@ -46,22 +48,24 @@ The package has no runtime dependencies and no build step.
46
48
 
47
49
  The package ships no agents, so create one first. `<agent dir>` is Pi's agent
48
50
  directory: `~/.pi/agent`, or `PI_CODING_AGENT_DIR` when that is set. Save this
49
- as `<agent dir>/squire/agents/scout/AGENT.md`, replacing
50
- `provider/model-id` with a model your Pi can use:
51
+ as `<agent dir>/agents/scout.md`, replacing `provider/model-id` with a model
52
+ your Pi can use:
51
53
 
52
54
  ```markdown
53
55
  ---
54
56
  description: Looks things up and answers in one message
55
57
  model: provider/model-id
56
58
  thinking: medium
57
- exclude-tools: edit, write
59
+ tools: read, grep, find, ls
58
60
  ---
59
61
 
60
62
  You research one question at a time and answer concisely, citing the files
61
63
  you read.
62
64
  ```
63
65
 
64
- The roster is read at call time, so no reload is needed. Delegation guidance
66
+ Every field is optional: without `model` and `thinking` the delegate runs on
67
+ your session's, and without `tools` it has your session's tools (see
68
+ [Agents](#agents)). The roster is read at call time, so no reload is needed. Delegation guidance
65
69
  for the parent is optional: save Markdown as
66
70
  `<agent dir>/squire/delegation_prompt.md`, and it is read before each run.
67
71
  Then, from a Pi session running in tmux, hand it a task:
@@ -92,7 +96,8 @@ or opened.
92
96
  ## How a delegation works
93
97
 
94
98
  `/delegate <agent> [--model <provider/id>] [--thinking <level>] [--label <name>] [--cwd <dir> | --worktree <branch> [--worktree-source <dir>] [--worktree-base <commit-ish>]] [--auto-exit | --no-auto-exit] <task…>`
95
- reads the agent from `<agent dir>/squire/agents/<name>/AGENT.md`, writes
99
+ reads the agent from `<agent dir>/agents/<name>.md` (or a trusted project's
100
+ `.pi/agents/<name>.md`; see [Agents](#agents)), writes
96
101
  the task to the delegate's inbox through `message:send`, emits
97
102
  `session:launch` so other extensions can shape the launch, then opens a
98
103
  detached window running the parent's own Pi in the parent's working directory,
@@ -102,7 +107,8 @@ prompt reaches the child as a path to a private file under the system
102
107
  temporary folder, not as text on the command line.
103
108
 
104
109
  - The window and the child's Pi session share one name: `<agent>-<label>`, or
105
- `<agent>-<first 8 characters of the id>` without a label. A label is at most
110
+ `<agent>-<first 8 characters of the id>` without a label, where `<agent>` is
111
+ the agent's `display_name` when it has one. A label is at most
106
112
  32 letters, digits, `_` or `-`, starting with a letter or digit. A `-` joins
107
113
  it rather than `:`, since tmux reads `:` in a target as session and window.
108
114
  - Labels need not be unique. The delegation id stays the handle for
@@ -136,28 +142,273 @@ temporary folder, not as text on the command line.
136
142
  resumes with another storage directory selected. A parent whose session
137
143
  directory is not an absolute path refuses to launch, before sending the
138
144
  task.
139
- - Inside a delegate (`PI_DELEGATE_PARENT` set) the extension registers only
140
- auto-exit's side, the `/auto-exit` command and its handlers, so a delegate
141
- cannot start delegates of its own.
145
+ - Delegates restore their identity and task state at session start. Delegation
146
+ tools are inactive unless the current agent opts in below `maxDepth`; launch
147
+ and resume admission enforce that boundary even through commands (see
148
+ [Nested delegation](#nested-delegation-unreleased)).
142
149
  - Without a UI the command and the `delegate` tool still work, except a
143
- `--cwd` launch whose project trust needs a question (see
150
+ top-level launch whose project trust needs a question (a nested launch can
151
+ ask its attended root; see
144
152
  [Project trust](#project-trust)). They need tmux
145
153
  and a provider of `message:*`, and refuse with a message when either is
146
154
  missing. In print mode (`pi -p`) Pi does not show the refusal notices, so
147
155
  the refusal is silent there.
148
156
 
157
+ ## Nested delegation (unreleased)
158
+
159
+ This integration is not ready for production nested agents: descendant tree
160
+ controls and full milestone validation remain required slices. Use only isolated
161
+ development fixtures until the full nesting
162
+ milestone is integrated and tested.
163
+
164
+ The default `maxDepth` in `<agent dir>/squire/settings.json` is **1**: root
165
+ (depth 0) → delegate (depth 1), with no grandchildren. Set `maxDepth: 2` for
166
+ root → coordinator → worker. A readable value must be a positive safe integer;
167
+ an absent/unreadable settings file uses the safe depth-1 default for exposure.
168
+ Launch/resume refuses malformed, unreadable or non-object settings rather than
169
+ risk silently losing configured tree caps.
170
+
171
+ A coordinator's agent file opts in with
172
+ `allow_nested_delegation: [worker, reviewer]`, or `true` for any current roster
173
+ agent. Absent, false or an empty list grants no nesting. A list names exact
174
+ agent identities; duplicates are removed. Other values (including null, empty
175
+ scalars, quoted booleans, malformed lists and patterns) disable the agent with
176
+ a path-specific warning. Unknown or unavailable names warn and cannot launch;
177
+ known entries remain usable, and later roster changes apply normally.
178
+ Below the limit the grant adds `delegate`,
179
+ `delegation_status`, `delegation_close` and `delegation_resume` even with an
180
+ explicit `tools` list, subject to the nested tool ceiling. Merely listing those
181
+ tools (including by pattern) does not opt in and produces
182
+ a roster warning. At the depth limit they are inactive and not passed onward.
183
+
184
+ Every launch/resume re-reads the direct parent's current grant before side
185
+ effects. A refusal names the permitted agents or depth/setting. Removing an
186
+ agent from the grant blocks future launches/resumes, not existing work.
187
+ Trusted project agents still shadow global definitions. Each launching session
188
+ keeps the existing throttled quota checks and shared reactive usage-limit marks.
189
+
190
+ A nested launch or resume resolves its requested tools, `delegateTools` additions
191
+ and automatic nesting grants, then restricts them to operations available to its
192
+ direct parent. This includes active model-only operations and registered
193
+ codemode/deferred tools, not just displayed schemas. Inactive direct tools and
194
+ hidden tools are excluded. Patterns become exact names; removed tools and
195
+ restricted patterns are reported in the launch/resume notice. The final
196
+ registration allowlist is enforced after synchronous `session:launch`
197
+ customization, so additions cannot register or reach tool names outside the
198
+ direct parent’s ceiling. Top-level launch selection is unchanged.
199
+
200
+ Each agent keeps its own permission policy; squire does not inherit or compare
201
+ effective policies. A granting agent with a `permission:` map (even `{}`) warns
202
+ when a listed child lacks one; `true` checks all applicable roster agents.
203
+ This is only a heuristic warning, not authorization or proof of lesser privilege.
204
+ Permission policy parsing/enforcement remains the permission provider’s job.
205
+ Tool-name restrictions and configuration permission rules are not filesystem
206
+ isolation: tools such as bash can write files or start processes. The ceiling
207
+ bounds tool names, not arbitrary extension code or the implementation an
208
+ extension registers under an allowed name. Rules asking
209
+ before changes to agent/squire configuration cannot promise interception of
210
+ every indirect write by scripts or repository changes; exact shell handling
211
+ belongs to the permission provider.
212
+
213
+ A coordinator's task finishes child-first: no final task reply or auto-exit
214
+ while any direct-child task is unfinished. Idle runs and open windows are not
215
+ task completion; a finished `auto_exit: false` child does not hold the barrier.
216
+ Child results steer only their direct parent. The coordinator must receive
217
+ their outcomes in a model request and subsequently settle before reporting
218
+ upstream. A result arriving during final settlement therefore requires another
219
+ processing run, not just decrementing a count. Out-of-order work and rolling
220
+ fan-out are supported; raw grandchild results are not independently forwarded
221
+ to root.
222
+
223
+ Task takeover and per-run guidance use the same current agent grant/depth check
224
+ as tool exposure. Leaves keep the original final-message wording; only effective
225
+ coordinators receive child-first final-report instructions. Manual reopening and
226
+ grant changes use the current role, not the launch-time role.
227
+
228
+ Intermediate failures, including exhausted fallbacks, keep the coordinator open
229
+ while workers run. Child results can wake it again; its eventual final run
230
+ determines done/failed. A user stop holds its task through failed runs as before.
231
+ A confirmed vanished worker produces the existing closed-without-result
232
+ outcome, which the coordinator processes without treating it as successful work.
233
+ Unknown tmux state is not disappearance and does not release the barrier.
234
+
235
+ Identity (direct parent, root, depth, own agent and ordered agent-name chain)
236
+ and child-task facts survive manual reopening of the same Pi session with the
237
+ same agent directory, without launch environment variables. Reopening does not
238
+ adopt another parent's children. Closing/reloading/exiting a parent does not
239
+ terminate its workers. Descendant views and exact-session restoration are
240
+ available below, including explicit ancestor-scoped subtree close. Full integrated
241
+ real-model/permission-forwarding validation is still required before production use.
242
+
243
+ A nested launch/resume needing an unresolved trust decision asks the attended
244
+ root directly, outside model task mail (see [Project trust](#project-trust)).
245
+ Already-decided trust, inherited-directory trust and explicit approve/no-approve flags remain.
246
+
247
+ ### Tree-wide launch budgets
248
+
249
+ All sessions beneath a root share these call-time settings in
250
+ `<agent dir>/squire/settings.json`:
251
+
252
+ | Setting | Meaning | Absent with maxDepth > 1 | Absent with maxDepth 1 |
253
+ | --- | --- | --- | --- |
254
+ | `maxConcurrentDelegates` | Unfinished delegations across the tree, including coordinators waiting for children | 16 | unlimited |
255
+ | `maxDelegatesPerRoot` | Launches over that root session's lifetime, including resumes | 64 | unlimited |
256
+
257
+ Both accept nonnegative safe integers; **0 means unlimited**. A changed setting
258
+ applies to subsequent admissions. Lowering it never kills existing work. At a
259
+ cap, launch/resume refuses with the current count and limit, before mail,
260
+ worktree creation or killing an exited resume window. There is no launch queue.
261
+ A finished task frees concurrency even if its `auto_exit: false` window stays
262
+ open. It never refunds its lifetime launch. A coordinator's intermediate settle
263
+ or a held user stop does not finish its task.
264
+
265
+ Shared structural facts and reservations live under
266
+ `<agent dir>/squire/trees/<encoded root session id>.json`. The registry indexes
267
+ ownership, ancestry, resume chains and launch locators; local child-task records
268
+ still own result delivery/processing. Publishing those saved parent facts is
269
+ best-effort: failure warns but does not suppress result/gone messages, steer,
270
+ close tracking or local outcome processing. Every admission reconciles durable
271
+ facts under the registry lock before checking caps, so missed publication does
272
+ not create spare capacity. True local child-task storage failures still block
273
+ completion and admission. Frozen worker replies publish terminal accounting
274
+ before transport, so a finished worker frees a slot even while its parent is
275
+ absent. A failed worker-terminal publication retains the outbox and blocks auto-exit;
276
+ retry/reopen preserves the existing answer. This accounting publication does
277
+ not claim that the parent received or processed the reply.
278
+
279
+ Admissions use a cross-process mutex on a **local filesystem**, not NFS, and
280
+ atomically reserve both budgets. Short lock contention can wait up to five
281
+ seconds, then refuses; cap exhaustion never waits for a slot. Lock contenders
282
+ have unique files; only a process proven absent permits cleanup, never a lease
283
+ expiry. A reused PID or unknown process state conservatively blocks cleanup.
284
+ Keep the same agent directory when reopening a session. Registry corruption
285
+ refuses admission instead of resetting the budget. Atomic replacement protects
286
+ ordinary process restarts; these records do not promise power-loss durability.
287
+
288
+ Confirmed pre-start failure refunds both reservations. After a tmux create may
289
+ have been dispatched, an error/timeout is **uncertain**, not a refund. A child
290
+ that starts publishes its own window locator to recover a lost tmux response.
291
+ Subsequent admissions reconcile durable parent/worker facts and observed windows,
292
+ even with the root/intermediate parent absent. Confirmed disappearance frees
293
+ concurrency but is not success; the direct parent still processes its own
294
+ missing-result notification or late reply. Unknown tmux state releases nothing.
295
+ If neither start nor no-start can be established, the reservation remains
296
+ charged; no timeout guesses it away or relaunches it. No parent-death watcher,
297
+ repair daemon, mailbox scraping or implicit ownership transfer is involved.
298
+
299
+ ## Restoring and inspecting a delegation tree
300
+
301
+ Reopen the **same Pi session id**, using the same agent directory, to restore its
302
+ identity, direct-child tracking, polling, pending results and owed answers.
303
+ Still-running windows are not recreated. Shared counts survive; reopening a
304
+ historically gone but unfinished coordinator counts it toward concurrency again,
305
+ without charging another lifetime launch. A Squire resume is different: it
306
+ starts another delegation and spends another lifetime launch.
307
+
308
+ Only one Squire runtime may act for an exact session identity. Process ownership
309
+ uses the same cross-process lock as admission, with a per-runtime token and no
310
+ time lease. A live/paused duplicate is refused and asked to shut down; it cannot
311
+ launch, process outcomes, send final replies or start a trust service. Already-
312
+ claimed matching Squire task/reply input is staged under
313
+ `<agent dir>/squire/owners/<encoded session id>/inbound/` before acknowledgment,
314
+ with an atomic per-envelope file, independent of the ownership lock. Only the
315
+ owner drains it, in provider timestamp/path order, through normal handling;
316
+ callbacks run outside the owner mutex and idle drain checks do not write lock
317
+ files. If staging is unwritable, an active ready owner can process directly.
318
+ A duplicate that cannot retain input reports the claimed path and asks for a
319
+ resend: delivery cannot be lossless without writable storage.
320
+
321
+ This safeguard covers Squire input, not ordinary mail/asks: a duplicate provider
322
+ can deliver those only in the losing process, not to the owner. Its shutdown can
323
+ also remove the shared running record, making the owner look not running to
324
+ `session_mail_ask` until its next provider announcement (such as `agent_start`).
325
+ Squire does not read or rewrite provider mailbox/running-record internals. Keep
326
+ one Pi process per session. Ownership refusals name the PID and owner file; if
327
+ stale/corrupt, verify the original owner has ended, stop affected Squire runtimes
328
+ and repair only that record. Never remove live lock tickets or force adoption.
329
+
330
+ Fresh sessions do not adopt earlier trees. At startup a fresh root inspects only
331
+ this agent directory's newest **64** registry files, and only entries using its
332
+ current session-storage directory. It reports at most **10** direct owners with
333
+ positively matched live windows and explains reopening the owning session (or
334
+ returning to its existing owner). Registry-content reads are limited to 1 MiB
335
+ per tree and one **200 ms total cooperative notice budget**, including the tmux
336
+ wait, with registry lock waits capped at the remaining budget or 20 ms. The scan
337
+ yields between trees and reports unavailable tmux, unreadable owner records or
338
+ truncated scope once. Individual filesystem operations cannot be preempted.
339
+ Directory names/mtimes are enumerated, but no recursive
340
+ session search, mailbox search or other agent-directory search occurs. Exact-id
341
+ restoration is not restricted by this notice budget.
342
+
343
+ Both `delegation_status` and `/delegates` show descendants: a root sees its tree;
344
+ a coordinator sees only its own subtree, not siblings or unrelated roots.
345
+ Hierarchy uses stable Pi session ids, not the new delegation id of a resume.
346
+ Parent/root/depth, launch/result/session paths, workspaces, current verified
347
+ window and resume history remain visible. Shared accounting is labeled separately
348
+ from semantic result state: a finished accounting entry is not proof its direct
349
+ parent received or processed the result. Registry-only rows without a complete
350
+ launch record remain unknown. Views read foreign parents' facts but never inject
351
+ grandchildren's raw results into the root conversation.
352
+
353
+ Only the **direct parent** may resume a child. Ancestor visibility grants no
354
+ resume authority; forked/cloned/copied history with a new Pi id grants none.
355
+ Return to the original parent. Identity is read from exact-id durable state,
356
+ written before launch; inherited launch environment cannot assign a new session
357
+ to the old tree. Current allowlists, depth, caps, callable-tool ceiling and
358
+ directory/source trust still apply to resume, including its picker. A live
359
+ same-id Squire owner and obsolete local/shared resume chains are refused.
360
+
149
361
  ## Where delegate windows open
150
362
 
151
- By default a delegate's window opens in a **companion session** named after
152
- yours with `-squire` added: from the session `work`, delegates go to
153
- `work-squire`. Your own session stays as it is, even when its name already
154
- ends in `-squire`: a session called `pi-squire` gets `pi-squire-squire`.
155
- pi-squire marks each companion session it creates with the tmux session
156
- option `@squire-companion`; a parent that runs in a marked session uses it, so
157
- a delegate that delegates does not make `work-squire-squire`.
363
+ By default a delegate's window opens in a shared **project companion session**
364
+ named `<project>-delegate-session` on your existing tmux server. The owning
365
+ root Pi session's project chooses it, not the parent tmux session or the
366
+ delegate's working directory. Launching into another repository or creating a
367
+ worktree there still uses the root's companion; resuming a delegate does too.
368
+
369
+ Nested launches inherit the original root project: A → coordinator in B →
370
+ worker in C all select A's companion. The preferred base name (for example,
371
+ `A-delegate-session`) is saved as `rootPlacement` in the existing durable
372
+ delegation identity and shared tree records, not recomputed from B/C or the
373
+ containing tmux session. Reopening the **same** coordinator session without its
374
+ launch environment restores that preference; it can launch or directly resume
375
+ its workers while the root is absent, provided target trust is already resolved.
376
+ Resume retains the original preference and still requires the direct parent.
377
+ The saved preference is not a numbered selected session or a tmux locator:
378
+ every open still applies the managed/unmarked selection rules below.
379
+
380
+ In Git, the project name is the **main checkout's folder name**. Roots in
381
+ repository subdirectories and linked worktrees therefore share the main
382
+ checkout's companion. Outside Git, use the root working directory's folder
383
+ name (a non-Git subdirectory has its own name). If Git inspection fails or
384
+ cannot locate the main checkout identity, the launch reports a notice and
385
+ uses the root working-directory basename instead.
386
+
387
+ Project names preserve ASCII letters (including case), digits, hyphens and
388
+ underscores. Runs of other characters become one hyphen, edge hyphens are
389
+ trimmed, and an empty name becomes `project`: `Client API` uses
390
+ `Client-API-delegate-session`.
391
+
392
+ Multiple roots share a companion even across parent tmux sessions. Distinct
393
+ paths with the same basename, separate clones named alike, and names that
394
+ normalize identically intentionally share it. This groups windows only:
395
+ delegation records, task mail, results, limits and close-all remain scoped to
396
+ each root.
397
+
398
+ pi-squire verifies the tmux session option `@squire-companion` before reusing
399
+ an occupied name. An unmarked session is left untouched; numbered alternatives
400
+ start at `<project>-delegate-session-2`, skipping unmarked alternatives too.
401
+ Later launches reuse an existing managed alternative even if a lower name
402
+ becomes free. Unexpected duplicate managed candidates use the first in numeric
403
+ order, without moving windows or repairing sessions. Existing legacy companions
404
+ and live windows are neither renamed nor migrated; only new launches and
405
+ resumes use project naming.
158
406
 
159
407
  - The session is created on demand, by the launch that needs it, and tmux
160
408
  removes it with its last window, so it does not outlive its delegates.
409
+ - Closing delegates (including close-all) kills only that root's tracked
410
+ windows. Manually killing the shared tmux companion session terminates
411
+ **all** its windows, including other roots' delegates.
161
412
  - `detach-on-destroy` is set to `off` (and the mark set) on that session only: if you are
162
413
  looking at it when its last delegate exits, tmux moves you to your most
163
414
  recent other session instead of detaching you. Your sessions and tmux's
@@ -176,22 +427,212 @@ To open windows in your own session, as before, set `windowPlacement` in
176
427
  ```
177
428
 
178
429
  The values are `companion` (the default when the setting is absent) and
179
- `current`. The file is read at each launch. Any other value refuses the launch
430
+ `current`. The mode is read at each launch/resume independently of the saved
431
+ project preference. Even a `current` launch saves the original root project for
432
+ later companion launches; `current` itself still opens in the invoking pane's
433
+ session. Missing or malformed `rootPlacement` in a recorded identity, or conflicting
434
+ saved/launch identities, refuses restore/admission/resume. Identity-less records
435
+ also cannot be resumed after this update, including by their original root
436
+ (see [Resuming a delegation](#resuming-a-delegation)). There is
437
+ no identity migration, directory relocation/rename tracking or companion repair.
438
+ The file is read at each launch. Any other value refuses the launch
180
439
  with an error naming the file, before the task is sent or a window opens.
181
440
 
441
+ <a id="direct-delegate-overview"></a>
442
+
443
+ ## Delegate overview
444
+
445
+ In interactive Pi, Squire shows a compact **Delegates** tree above the editor.
446
+ It lists this session's running direct delegates first, then finished delegates
447
+ whose windows are still open, newest launch first within each group. A resumed
448
+ delegate keeps one row keyed by its stable Pi session id, not by its new
449
+ delegation id or mutable title. Failed and stopped results keep their actual
450
+ outcome labels; an open window or quiet session is not evidence of success.
451
+
452
+ Rows show the recorded delegate identity in bold, the current native Pi session
453
+ title when available, and task state. No title is repeated when it matches the
454
+ identity. The recorded launch model (or result model when available) and
455
+ transcript-file age appear when space permits: model is
456
+ dropped first, then file age, before identity/state are compressed. At very
457
+ narrow widths the state glyph remains beside the truncated identity.
458
+ `file … ago` is only a last-active hint from transcript mtime, not live tool
459
+ activity. When available, a second line shows factual live activity (below).
460
+ Each coordinator row summarizes **all descendant tasks**, at every depth,
461
+ including tasks whose windows have exited. Counts are per delegation/launch id,
462
+ not per stable session: a failed task and its resumed successor both count, so
463
+ a retry cannot erase a known failure. Actual replies supply done/failed/stopped;
464
+ saved gone/closed-without-result tasks count as missing. Shared registry
465
+ finished/closed/closing flags are not successful replies or proof another
466
+ parent processed an outcome. Missing parent facts or tree coverage are labeled
467
+ unknown/partial/unavailable, never all done. Summary and activity share one
468
+ secondary line; when both do not fit, the task summary wins.
469
+
470
+ Delegates also see their ancestors vertically, root to direct parent, with the
471
+ direct parent marked `(parent)` (including `root (parent)`). A connected
472
+ **Delegates** branch contains only the current session's direct children.
473
+ There is no current-session row or empty child heading; leaves show ancestors
474
+ only. Ancestor rows and secondary lines do not consume child-entry slots.
475
+
476
+ Configure it in the existing `<agent dir>/squire/settings.json`, alongside
477
+ `windowPlacement`, worktree and nesting preferences:
478
+
479
+ ```json
480
+ {
481
+ "delegateWidgetPlacement": "above",
482
+ "delegateWidgetMaxEntries": 3
483
+ }
484
+ ```
485
+
486
+ - `delegateWidgetPlacement`: `above` (default) or `below` the editor.
487
+ - `delegateWidgetMaxEntries`: nonnegative safe integer, default **3**. Each
488
+ direct child consumes one slot. `+N more direct delegates` counts hidden
489
+ eligible direct children, not descendants. **0** hides child rows and restores
490
+ Squire's running-count footer while preserving ancestor rows/navigation;
491
+ zero children never produce an empty heading.
492
+ - These preferences apply equally to roots and coordinators. Interactive views
493
+ re-read them about once a second; invalid widget preferences warn once per
494
+ distinct error and use the defaults without changing task state.
495
+
496
+ Launch/result/close changes refresh promptly; an independent one-second observer
497
+ keeps finished-open rows, window eligibility and later native title changes
498
+ fresh even when the result poll has stopped. Only visible rows resolve titles and
499
+ file metadata and live activity; overflow counts still include every eligible child. Closed/gone-only
500
+ history without a live reattached session does not keep the observer listing
501
+ windows. A positively reopened gone coordinator may appear at its current window
502
+ with its missing-outcome history intact; reopening does not make its task running. A failed tmux listing is unknown,
503
+ not proof that a window disappeared: running tasks remain eligible, but
504
+ finished rows require a positively open matching window.
505
+
506
+ Titles come from the latest complete native `session_info` in the delegate's
507
+ known session file, including external /resume-picker renames and explicit
508
+ clears. A small owner-only disposable title mirror under
509
+ `<agent dir>/squire/session-titles/` covers sessions with no persisted title
510
+ (including before the first transcript flush). Naming tools are optional;
511
+ `/name`, `--name` and Pi's native setter work. Persisted metadata takes priority
512
+ over that mirror: an external picker rename may differ temporarily from the
513
+ child's own in-memory name. Files are read incrementally, with bounded work per
514
+ refresh; very large existing transcripts may take several refreshes to catch up.
515
+
516
+ ### Factual live activity
517
+
518
+ The secondary line follows actual Pi tool-execution events: `reading…`,
519
+ `editing…`, `writing…`, `command call…`, or `using <tool name>…`. Parallel
520
+ and nested calls are tracked by their actual call ids, not by tool name.
521
+ Active nested calls take precedence over their orchestrator. A tool start is
522
+ observed before permission interception: `command call…` means an outstanding
523
+ call, not proof that a subprocess started or permission was granted. Squire
524
+ does not inspect commands to guess intent, or display arguments, paths, tool
525
+ results, thinking or provider payloads.
526
+
527
+ Otherwise, while an assistant response streams, `response: …` shows a literal
528
+ single-line excerpt bounded to 512 UTF-8 bytes. It clears when that message
529
+ ends; a quick response may finish between refreshes and never appear. Calls
530
+ clear on matching end, run end/settlement, stop, reload and shutdown. Known
531
+ task results/close suppress activity even if a delegate's last snapshot remains.
532
+ A settled or silent coordinator can still own unfinished child tasks: activity
533
+ does not determine task completion, thinking or waiting for a human.
534
+
535
+ This best-effort feed uses bounded owner-only atomic snapshots under
536
+ `<agent dir>/squire/live-sessions/`, separate from title mirrors and durable
537
+ task/tree state. Each runtime owns a unique generation; dead/reused processes
538
+ are rejected and their own snapshots can be removed, while unknown or duplicate
539
+ live owners make activity unavailable. No inactivity timeout or stale-state UI
540
+ is introduced. The same private launch correlation distinguishes a resumed task
541
+ from its stable Pi session. Manual reopen without this correlation does not
542
+ supply task-row activity yet. Missing, corrupt or inaccessible telemetry never
543
+ blocks task launch, reply delivery or settlement, and causes no chat or model
544
+ calls. Publication requires positive Squire ownership at startup; later factual
545
+ end/settlement observation continues even if task-owner processing fails or its
546
+ record becomes unreadable, without re-enabling actions. Event publishing works
547
+ in non-TUI delegates and with the list hidden.
548
+ One child-entry slot may occupy two physical lines; there is no empty secondary
549
+ line or tool-use counter.
550
+
551
+ ### Navigation
552
+
553
+ At an **empty built-in prompt**, press **Down** to select the direct parent in a
554
+ delegate session, or the first child at root. Up/Down traverse ancestors and
555
+ eligible children in display order; only children scroll within the configured cap;
556
+ hidden-above/below counts refer to direct children. Selection follows stable
557
+ session identity through renames, reordering and resumes. Enter opens that
558
+ child's details in the **same dashboard** as /delegates, scoped to that child's
559
+ branch (including descendant and resume history, not its siblings). Press **b**
560
+ for the branch list to select and inspect its workers. Initial details Escape
561
+ returns directly to the compact list; details opened from the branch list return
562
+ to that branch list first. Branch-list Escape returns to the compact list, then
563
+ Escape again returns to the prompt; Up past the first compact row also leaves
564
+ the list. A short hint appears only while the list owns these keys.
565
+ Typing or any unrelated key leaves list navigation and passes that key through.
566
+ While a widget-opened dashboard is loading or re-listing between actions, keys
567
+ are absorbed only if the exact empty built-in prompt still owns focus: Escape
568
+ cannot abort a run and action letters cannot leak into that prompt. Once a
569
+ real dashboard, dialog, completion menu or unknown component owns input, its
570
+ keys pass through normally.
571
+ Cap zero never creates invisible selectable children; ancestors remain selectable
572
+ and /delegates still works.
573
+
574
+ Enter on an ancestor switches directly to its verified **current live tmux
575
+ window**, not to the dashboard or a historical launch window. Root and leaf
576
+ processes publish their own positive pane/window observation in the same
577
+ disposable generation snapshot used for activity. The reader checks the elected
578
+ exact-session owner, process incarnation, tmux server socket/PID/start identity,
579
+ exact pane and window id **and** name. Current window placement is read from tmux;
580
+ cwd, companion names and rootPlacement are never used to guess a root window.
581
+ Own-pane observations refresh about once a second even while quiet or cap zero.
582
+ A rename/move before the owner refreshes fails closed; a later positive update
583
+ makes the new locator available. This navigates to the window, not a specific
584
+ pane's keyboard focus.
585
+
586
+ An exact-id manual reopen can supply a new locator without launch environment,
587
+ while old task history stays unchanged. It still cannot supply launch-correlated
588
+ task activity. Missing/unreadable/ambiguous metadata, a different server, a dead
589
+ pane or no attached client reports an unavailable target. Navigation never
590
+ adopts, recreates, kills or implicitly resumes an ancestor. New/forked session ids
591
+ do not inherit ancestor authority from copied history.
592
+
593
+ Squire owns only its widget key and its existing status entries. It does not
594
+ replace your footer or editor, and cleans up observers/input listeners on
595
+ shutdown or reload. Automatic navigation deliberately passes through custom
596
+ editors, autocomplete, dialogs, permission questions and unrelated dashboards.
597
+ It relies on **Pi TUI internals**, isolated in a small compatibility adapter:
598
+ the concrete focus getter and exact built-in editor identity. Compatibility was
599
+ tested with the installed **Pi 1.4.0 bundled CLI**, including regular/fullscreen
600
+ mode changes. Unknown focus/editor/completion capabilities disable key capture
601
+ rather than guessing; /delegates remains available. This is not a generic
602
+ custom-editor integration or a supported Pi focus hook. JSON/RPC hosts retain
603
+ the running-count status rather than receiving a terminal widget.
604
+
182
605
  ## Reaching delegates
183
606
 
184
607
  Delegate windows sit out of your way, so reach them from the parent. `/delegates`
185
- (or `alt+shift+d`) opens a panel of the delegations whose window is open: running,
186
- or done with auto-exit off, newest first. The model has no tool for any of this.
608
+ (or `alt+shift+d`) opens the same dashboard used by widget Enter, at the overall
609
+ view of its descendant hierarchy, including
610
+ closed/history rows. Parents precede their children; resume history stays
611
+ visible. Each row shows stable launch identity, the available current native Pi
612
+ title, agent display label, semantic state/outcome and verified window availability.
613
+ Enter opens selected details with identities, paths, result envelope, workspace
614
+ and resume information. Historical launch windows are labeled separately from
615
+ current verified windows. Lists and details scroll within the available screen.
616
+ Widget entry uses the same screen for only its selected branch; `b` exposes that
617
+ branch's list without widening its scope. A title change never changes routing.
618
+ Metadata is refreshed when opening/reopening the screen after an action; the
619
+ compact overview remains the live activity/summary view.
620
+ Only a verified live window supports switch/peek; the model has no
621
+ tool that moves the terminal. Any ancestor can explicitly close a selected
622
+ subtree. The root also has a confirmed close-all action; shared companion names
623
+ never grant control over another root.
187
624
 
188
625
  | Key | Does |
189
626
  |---|---|
190
- | `↑` / `↓` | select a delegation; `esc` leaves the panel |
191
- | `enter` | switch your tmux client to the window |
627
+ | `↑` / `↓` | select a delegation in the list; scroll in details |
628
+ | `enter` | open selected details; from details, switch your tmux client to its verified window |
629
+ | `esc` | details → overall list → prompt; initial widget details → compact list; branch-list details → branch list → compact list |
630
+ | `b` | widget entry only: show the selected branch list, preserving its target and history |
192
631
  | `p` | peek: a snapshot of the window's pane (`capture-pane`) in an overlay; `r` refreshes, `enter` switches, `esc` returns to the list |
193
- | `x` | close the delegation as `delegation_close` does; asks first only while it is running |
194
- | `n` | new delegate: the same form `/delegate` opens with no arguments (see [First use](#first-use)) |
632
+ | `x` | explicitly close the selected subtree as `delegation_close` does; confirms running work and verified live navigation rows |
633
+ | `a` | root only: confirm closing **all root** descendant subtrees, including absent coordinators, even when viewing one branch |
634
+ | `r` | resume the selected eligible direct child, collecting a follow-up through the existing resume flow; descendants without authority are refused |
635
+ | `n` | new child of the invoking session, not of the selected remote row: the same form `/delegate` opens with no arguments (see [First use](#first-use)) |
195
636
 
196
637
  Switch acts on the most recently active tmux client attached to the parent's
197
638
  session. With none attached it refuses with a notice; peek and close still
@@ -204,6 +645,22 @@ often rebound); the notice after a switch says so.
204
645
  without the panel: with no flag or with `--switch`, it switches; `--peek` opens
205
646
  the peek overlay. Ids complete from the open delegations.
206
647
 
648
+ Dashboard switch/peek and `/delegate-open` share current-locator verification.
649
+ For the latest known launch of a stable session, a positively observed current
650
+ runtime supersedes its old launch window; a later lost/changed locator refuses
651
+ rather than falling back to an old still-open window. Exact Pi pane liveness
652
+ allows navigation even if a different active pane has exited, and peek captures
653
+ that verified Pi pane. Legacy tracked windows still require matching id and
654
+ name. Reattached process visibility does not rewrite historical gone/closed/
655
+ result facts, invent task activity, or grant resume authority. Current telemetry
656
+ is navigation evidence only: close still uses the existing shared control
657
+ runner's durable locators/fences and reports partial failures honestly. A live
658
+ reattached navigation window may remain open when it differs from the durable
659
+ control locator; confirmation warns about that distinction. Current-locator
660
+ supersession memory lasts only for this navigator runtime: after a restart,
661
+ before the first positive live observation, a legacy id+name-verified historical
662
+ window can still be offered.
663
+
207
664
  ## Working directory
208
665
 
209
666
  `--cwd <dir>` (the `delegate` tool's `cwd`) runs the delegate in an existing
@@ -304,10 +761,12 @@ is `/Users/me/.pi/agent/squire`, the matching relative override resolves to
304
761
  `/Users/me/.pi/agent/special-checkouts/feature-topic`. The Git branch remains
305
762
  `feature/topic`.
306
763
 
307
- The settings file is read only for managed creation. A missing file uses the
308
- built-in default; malformed or inaccessible settings, invalid templates and
309
- duplicate canonical repository keys refuse creation rather than silently
310
- falling back. They do not affect inherited or `cwd` launches. Placement does
764
+ Worktree placement keys are read only for managed creation. A missing file
765
+ uses the built-in default; invalid templates and duplicate canonical repository
766
+ keys refuse creation rather than silently falling back. Those creation-key
767
+ errors do not affect inherited or `cwd` launches, but malformed, inaccessible
768
+ or non-object shared settings refuse every launch/resume because tree caps
769
+ cannot be read safely. Placement does
311
770
  not choose a source checkout or base commit. Pi discovers `AGENTS.md`, skills
312
771
  and other project context from the created directory and its ancestors, so the
313
772
  chosen layout determines which instructions and skills apply. For a layout
@@ -366,9 +825,12 @@ command, and nothing acts on it.
366
825
 
367
826
  ### Project trust
368
827
 
369
- The delegate's trust flag for a `--cwd` directory is decided in the parent,
370
- before the task is sent or a window opens. A same-repository managed worktree
371
- uses the parent's current trust for that repository. The model has no trust input.
828
+ The requesting parent resolves the delegate's trust flag before task mail,
829
+ worktree creation or a window opens. Unresolved choices go to the root user,
830
+ not to an intervening model or an unattended delegate UI. A same-repository
831
+ managed worktree uses the requester's current trust for that repository.
832
+ The model has no trust input; every launch/resume still passes explicit
833
+ `--approve` or `--no-approve`.
372
834
 
373
835
  1. A directory in the parent's own repository, by Git's common directory, so
374
836
  including its linked worktrees, gets the parent's current trust, even a
@@ -386,8 +848,9 @@ uses the parent's current trust for that repository. The model has no trust inpu
386
848
  (`.pi/settings.json`, extensions, skills and the like, or an
387
849
  `.agents/skills` directory above it), the delegate starts with
388
850
  `--no-approve` and no question.
389
- 4. Otherwise the parent asks, naming the directory, the main repository when
390
- it is the key, and what each answer saves:
851
+ 4. Otherwise the root asks, naming the requesting agent chain, requester
852
+ session id, directory, the main repository when it is the key, and what each
853
+ answer saves:
391
854
  `Trust`, `Trust parent folder (<folder>)` (left out at the filesystem
392
855
  root), `Trust (this session only)`, `Do not trust` and
393
856
  `Do not trust (this session only)`. The two "this session only" answers
@@ -398,55 +861,178 @@ uses the parent's current trust for that repository. The model has no trust inpu
398
861
  the turn or tool call stops a launch that is waiting for or answering a
399
862
  question; nothing is saved, sent or opened.
400
863
 
401
- Without a UI a question cannot be asked, so the launch is refused: open Pi in
402
- that directory and answer its trust question first. A trust store that cannot
403
- be read or written also refuses the launch; nothing is granted by default.
864
+ An unresolved nested question requires the same root Pi session to be running
865
+ with dialog-capable UI (TUI or RPC), without `PI_UNATTENDED=1`. An absent or
866
+ unavailable root refuses immediately; a disconnected root fails its pending
867
+ launches, rather than waiting for the root to return. Already-resolved trust
868
+ and resource-free targets need no root connection. A root's own trust in a
869
+ different repository never supplies approval. For a top-level launch without UI,
870
+ open Pi in the trust key directory named by the refusal (the source/main checkout
871
+ for a managed worktree), answer its trust question, then delegate again.
872
+
873
+ Root questions, local and remote together, are serialized, even while the
874
+ root model is busy. Each request has a **10-minute deadline including queue
875
+ wait**. Escape, tool/turn cancellation, requester or root shutdown/reload/session
876
+ replacement, disconnection and expiry cancel that launch and dismiss its
877
+ question; canceled queued requests are skipped. An unrelated root model run's
878
+ abort does not cancel a remote request. Late decisions cannot save trust or
879
+ start the canceled work. A new launch after reopening makes a new request;
880
+ pending requests and decisions are never replayed.
881
+
882
+ Squire uses an authenticated **127.0.0.1 loopback connection**, not mail or
883
+ `session_mail_ask`, for this control. The root writes its random port and
884
+ per-runtime token to the owner-only
885
+ `<agent dir>/squire/trust/<SHA-256 of root id>.json` descriptor. Only squire
886
+ reads it; no provider files, APIs or tool grants are needed. Unreachable
887
+ endpoints fail at once (connection establishment is bounded to one second).
888
+ A fresh client-nonce/HMAC handshake verifies the root before sending any
889
+ question or secret, so a reused stale port cannot impersonate it. Root restart
890
+ changes the token, invalidating old frames.
891
+ The descriptor is transient and removed on ordinary shutdown; a crash can
892
+ leave a harmless stale descriptor that cannot approve anything. Like the rest
893
+ of squire, this is not an OS boundary against processes running as you.
894
+
895
+ Only the live **requester** saves a persistent choice in that agent directory's
896
+ Pi `trust.json`, after cancellation/deadline checks and before launch.
897
+ The root holds the queue until the requester has applied the answer or
898
+ disconnected, so queued requests re-read saved decisions. The two session-only
899
+ choices save nothing and apply only to that launch. `Do not trust` launches
900
+ untrusted; Escape cancels instead. A trust store that cannot be read or written
901
+ refuses the launch; nothing is granted by default.
902
+
903
+ Model questions still use `session_mail_ask` to the direct parent, parent by
904
+ parent. Permission prompts remain with the permission provider, with each
905
+ child's own agent policy: there is **no permission inheritance**. Its bridge
906
+ can label a requester with `PI_DELEGATE_CHAIN` (comma-separated roster names,
907
+ root downward including the current delegate); `session:launch.chain` is the
908
+ corresponding frozen string array. Routing uses separate parent/root session
909
+ ids, not chain names. Squire does not implement that permission bridge.
404
910
 
405
911
  ## Agents
406
912
 
407
- The roster is read at call time from
408
- `<agent dir>/squire/agents/<name>/AGENT.md`, one directory per agent, so a new
409
- agent is usable without a reload. Frontmatter
410
- carries `description`, `model` (`provider/id`) and `thinking`, and optionally
411
- `auto-exit` (`true` or `false`, default `true`; see [Auto-exit](#auto-exit)),
412
- `exclude-tools`, `tools`, `extensions` and `fallback`. The body is the delegate's system prompt,
413
- appended to Pi's own prompt; it reaches the child as a path to a private file
414
- under the system temporary folder, so the text stays off the command line. The
415
- [first-use example](#first-use) is a complete `AGENT.md`.
416
-
417
- `exclude-tools` names the tools the delegate goes without, as a
418
- comma-separated list (`exclude-tools: edit, write`) or a YAML list
419
- (`exclude-tools: [edit, write]`). It reaches the child as
420
- `--exclude-tools edit,write`, which Pi subtracts from the tools it would
421
- otherwise turn on. It is a denylist only: without it the delegate gets every
422
- tool, and no flag is passed. There is no per-call override. Each name is
423
- checked against the tools the parent session has registered
424
- (`pi.getAllTools()`), since Pi ignores an unknown name without a word. The
425
- delegate's result reaches the parent through the `message:*` provider's reply
426
- when its run settles, not through a tool, so excluding tools cannot stop it
427
- answering.
428
-
429
- `tools` lists tools for the delegate as a comma-separated or YAML list
430
- (`tools: device_run`). It reaches the child as `--tools device_run`, which
431
- covers extension tools too. Without discovery tools such as `codemode` or
432
- `tool_search`, only the listed tools are visible. Pi can keep unlisted MCP
433
- tools registered unless the list includes an `mcp__` entry; discovery tools can
434
- also expose them. Names are not checked against the parent's tools, since a
435
- tool from one of the agent's own `extensions` exists only in the child. Empty or
436
- absent passes no flag. `exclude-tools` still applies alongside.
437
-
438
- `extensions` lists extension paths only this agent's delegate loads, as a
439
- comma-separated or YAML list. Each reaches the child as a repeated
440
- `-e <absolute path>`, on top of its normal extensions, so pi-squire and the
441
- `message:*` provider still load and the result still reaches the parent. `~`
442
- and `~/` expand to the home directory and a relative path resolves against the
443
- directory holding that `AGENT.md`. A path that does not exist when the roster
444
- is read leaves the agent out, with a warning.
445
-
446
- `fallback` lists models to try, in order, when the agent's `model` is
447
- usage-limited (see [Usage limits](#usage-limits)), as a comma-separated list
448
- or a YAML list of `provider/id`. An entry that is not `provider/id` is dropped
449
- with a warning; the agent still loads.
913
+ An agent is one Markdown file, `<name>.md`, in the agents folders Pi agent
914
+ tooling shares; the file name without `.md` is the agent's name (letters,
915
+ digits, `_` or `-`). The roster is read at call time, so a new or edited file
916
+ is usable without a reload.
917
+
918
+ - `<agent dir>/agents/<name>.md` holds the global agents.
919
+ - `<cwd>/.pi/agents/<name>.md` holds a project's agents, read only when Pi
920
+ reports the parent's project as trusted. A project file replaces the global
921
+ file of the same name. In an untrusted project only the global agents load,
922
+ so a cloned repository cannot change a delegate's prompt, model or tools.
923
+
924
+ The body is the delegate's instructions, appended to Pi's own prompt; it
925
+ reaches the child as a path to a private file under the system temporary
926
+ folder, so the text stays off the command line. Every frontmatter field is
927
+ optional, and the [first-use example](#first-use) is a complete agent file:
928
+
929
+ | Field | Meaning |
930
+ | --- | --- |
931
+ | `description` | Shown in the roster and the `delegate` tool's description. Defaults to the name. |
932
+ | `display_name` | Shown instead of the name on the delegate card, in `/delegates` and in the window title (`<display_name>-<label>`). The agent is still called by its name. It may hold only ASCII letters, digits, `-` and `_`; any other value (a space, `#`, `.`, a non-ASCII letter, or an empty value) makes the file invalid, so the agent is disabled. |
933
+ | `model` | One model: `provider/id` or an alias such as `sonnet`, passed to the child's `--model` unchanged, so Pi resolves it to one model. A `*` pattern makes the file invalid. Absent means the parent session's current model. |
934
+ | `thinking` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh` or `max`. Absent means the parent session's level; an unknown value is dropped with a warning and the parent's level applies. Pi clamps the level to the model. |
935
+ | `tools` | The delegate's tools (see below): tool names for exactly those, or only `-name` entries for the parent session's current tools less those. Absent means the parent session's current tools; empty (`tools:` or `tools: []`) means no tools except the `delegateTools` setting. MCP tools follow the selection like any other. |
936
+ | `fallback` | Models to try, in order, when `model` is usage-limited (see [Usage limits](#usage-limits)). Each entry is one model, like `model`; a `*` pattern, or a YAML list entry holding a comma, makes the file invalid. A comma-separated line (`fallback: a, b`) is two entries. |
937
+ | `auto_exit` | `true` or `false`, default `true`; see [Auto-exit](#auto-exit). |
938
+ | `allow_nested_delegation` | Unreleased: `true` or a YAML list of exact child agent names; grants nested delegation below `maxDepth`, subject to the parent tool ceiling (see [Nested delegation](#nested-delegation-unreleased)). Absent, false or an empty list grants none. Malformed values disable the agent; unavailable names warn. |
939
+ | `prompt_mode` | `append` (the default) wraps the body in `<agent_instructions>` and `</agent_instructions>` lines; `replace` adds the body unwrapped. An empty body adds nothing either way. |
940
+ | `enabled` | `false` leaves the agent out. A project file with `enabled: false` hides a global agent of the same name in that project. |
941
+ | `locked` | `true`, or a list of `model` and `thinking`. A call to `delegate`, `/delegate` or a resume that passes a locked field does not get it: the agent's value is used (the parent's when the file sets none) and the result says which agent locked which field. Passing the agent's own value is the same as passing nothing: no notice, and its fallbacks still apply. A resume of a locked field runs on the file's value as it is now, not on what the delegate last ran on. `true` locks the fields the file sets. |
942
+
943
+ Any other key, such as `max_turns`, `inherit_context` or `run_in_background`,
944
+ is ignored without a warning and left for whatever else reads the file.
945
+ Squire observes only whether `permission:` is a map for the nested-grant warning
946
+ above; it leaves interpretation and enforcement to the permission provider.
947
+
948
+ **Breaking frontmatter change:** use `auto_exit` instead of `auto-exit`.
949
+ The old `auto-exit` and `extensions` fields are silently ignored, including
950
+ malformed values and missing extension paths. In particular, an old
951
+ `auto-exit: false` no longer keeps the delegate open: without `auto_exit: false`
952
+ or a call override, auto-exit defaults to on. Agent-specific extension loading
953
+ has been removed from both launch and resume. Normal Pi extension discovery
954
+ and [`session:launch`](#sessionlaunch) customization remain available.
955
+ The `--auto-exit` / `--no-auto-exit` flags, `/auto-exit` command and
956
+ `PI_DELEGATE_AUTO_EXIT` environment variable are unchanged.
957
+
958
+ `exclude-tools` is no longer read; a file that still has it gets a warning
959
+ (use `tools` instead).
960
+
961
+ `tools` lists the delegate's tools as a comma-separated or YAML list
962
+ (`tools: read, grep` or `tools: [read, grep]`). Each entry of a YAML list
963
+ names one tool: an entry holding a comma, such as `tools: ["read,-bash"]`,
964
+ makes the file invalid. pi-squire works out the delegate's whole selection
965
+ itself and always passes the child a plain list, `--tools read,grep`, which Pi
966
+ treats as the whole selection, extension tools included; entries may be `*`
967
+ patterns. It never passes Pi `+name` or `-name` entries. MCP tools follow the
968
+ list like any other tool: an MCP tool (`mcp__<server>__<tool>`, or one of the
969
+ MCP resource tools) that no entry names or matches is not registered in the
970
+ delegate at all, so it is neither declared nor callable from `codemode` nor
971
+ loadable by `tool_search`, whatever its exposure. Pi filters MCP tools by
972
+ `--tools` only once an entry starts with `mcp__`, so when the list has no
973
+ such entry pi-squire adds the entry `mcp__`, which names no tool; an empty
974
+ selection is `--no-tools`, which removes MCP tools too. To give a delegate an
975
+ MCP server's tools, name them, for example `mcp__docs__*`. An extension that
976
+ turns tools on at run time can still add only tools the list allows. Top-level tool names
977
+ are not checked against the parent's tools: normal Pi extension discovery
978
+ or `session:launch` customization can provide tools in the child. Nested launches
979
+ and resumes instead apply the direct parent’s callable-tool ceiling after all
980
+ selection and customization, as described above; this is not filesystem isolation.
981
+
982
+ - No `tools` field: the delegate gets the parent session's tools as they are
983
+ when the launch (or resume) starts: active tools for a top-level launch, or
984
+ available tools including codemode/deferred operations for a nested one, passed as
985
+ `--tools`, plus the `delegateTools` entries. A parent limited to `read`
986
+ gives such a delegate only `read`.
987
+ - An empty `tools` field, `tools:` or `tools: []`: the delegate gets no tools
988
+ except the `delegateTools` entries. With none of those either, it starts
989
+ with `--no-tools`.
990
+ - A `tools` list of tool names: exactly those tools, plus the
991
+ `delegateTools` entries.
992
+ - A `tools` list of only `-name` entries (`tools: -bash, -write`): the
993
+ parent's tools, as with no `tools` field, less those, plus the
994
+ `delegateTools` entries. A `-name` entry may be a `*` pattern
995
+ (`-mcp__docs__*`).
996
+ - A `+name` entry, or tool names and `-name` entries in one list, make the
997
+ file invalid, and the agent is disabled (see below).
998
+
999
+ The delegation tools (`delegate`, `delegation_status`, `delegation_close`,
1000
+ `delegation_resume`) are removed from ordinary tool selections. An opted-in
1001
+ coordinator below `maxDepth` receives them automatically; a leaf has them
1002
+ inactive. Commands are still registered but cannot bypass admission. See the
1003
+ unreleased [nesting restrictions](#nested-delegation-unreleased).
1004
+
1005
+ The delegate's result reaches the parent through the `message:*` provider's
1006
+ reply hook when squire settles the task, not through a tool, so a short `tools` list cannot
1007
+ stop it answering. To let a delegate message its parent mid-task, whatever its
1008
+ `tools`, list the tools your `message:*` provider gives a session for that in
1009
+ the `delegateTools` setting, in `<agent dir>/squire/settings.json` beside
1010
+ `windowPlacement` and `worktreeRoot`:
1011
+
1012
+ ```json
1013
+ { "delegateTools": ["<tool or pattern>"] }
1014
+ ```
1015
+
1016
+ Each entry, a tool name or `*` pattern, is added to every delegate's tools,
1017
+ whether they come from its agent's `tools` or from the parent (after any
1018
+ `-name` entries are taken off). Top-level launches pass these to `--tools`
1019
+ unchanged; nested launches/resumes resolve and cap them by the direct parent’s
1020
+ available tools. The setting
1021
+ is empty by default, is read at each launch and resume, and a value that is
1022
+ not a list of names, or an entry that holds a comma or starts with `+` or
1023
+ `-`, refuses every launch with an error naming the file, before the task is sent or a window
1024
+ opens; it does not disable agents one by one.
1025
+
1026
+ A `model` or `fallback` entry that is an exact `provider/id` in Pi's registry
1027
+ is checked as before: a model without credentials or with an active
1028
+ usage-limit mark is skipped for the next candidate. Anything else, such as an
1029
+ alias, goes to the child's `--model` unchanged, and Pi resolves it to one
1030
+ model; if Pi cannot, the child's Pi reports the error and its window closes
1031
+ without a result. When a running delegate moves to a fallback after a usage
1032
+ limit, it resolves each entry the way Pi resolves `--model`: an exact
1033
+ `provider/id`, or a bare id or alias such as `sonnet`, to one model, in the
1034
+ order the entries are listed. No entry may be a `*` pattern: a `model` or
1035
+ `fallback` entry with `*` makes the file invalid, and the agent is disabled.
450
1036
 
451
1037
  The body is followed by one fixed final-message line and a paragraph naming the
452
1038
  parent session's address. The delegate's task arrives as a message from that
@@ -454,24 +1040,39 @@ session, and that message and every later one from the same address are
454
1040
  instructions to follow as given. Other sessions are colleagues: the delegate
455
1041
  may answer them, but checks with the parent before doing work the parent did
456
1042
  not ask for. The paragraph names no provider, package or tool, so it holds
457
- whatever `message:*` provider delivers the task.
458
-
459
- An `AGENT.md` that cannot be used is left out of the roster and named in a
460
- warning. That covers missing fields, a model that is not `provider/id`, an
461
- unknown thinking level, an `auto-exit` that is not `true` or `false`, an
462
- `exclude-tools` that is not a list of names or names a tool the session does
463
- not have, a `tools` or `extensions` that is not a list, an extension path that
464
- does not exist, and an empty body. With no agents at all, `/delegate` says how to
1043
+ whatever `message:*` provider delivers the task. `prompt_mode: replace` builds
1044
+ the prompt exactly as every delegate was built before `prompt_mode` existed.
1045
+
1046
+ An agent file that cannot be used is invalid, and its agent is disabled:
1047
+ frontmatter that is not readable YAML or not a set of fields, an `enabled` or
1048
+ `auto_exit` that is not `true` or `false`, a `tools` value that is not a list,
1049
+ a `tools` list with a `+name` entry or with tool names and `-name`
1050
+ entries mixed, a `tools` or `fallback` YAML list entry holding a comma, or a
1051
+ `model` or `fallback` entry with a `*` pattern. A disabled agent is left
1052
+ out of the roster and of the `delegate` tool's description, so the model never
1053
+ picks it, and a warning names the file and the reason when the session starts
1054
+ (and before a run, when a file turns invalid while the session runs). A call
1055
+ or resume that names it anyway is refused with that reason. A
1056
+ field that cannot be used (a `fallback` that is not a list, an unknown
1057
+ `thinking`, `prompt_mode` or `locked` entry) is dropped with a warning and the
1058
+ agent still loads. While the retired `<agent dir>/squire/agents/` folder
1059
+ exists, a warning says to move each `<name>/AGENT.md` to
1060
+ `<agent dir>/agents/<name>.md`. With no agents at all, `/delegate` says how to
465
1061
  add one.
466
1062
 
467
1063
  ## Configuration
468
1064
 
469
- `<agent dir>/squire/` holds the roster and the optional parent guidance file.
1065
+ `<agent dir>/squire/` holds the optional parent guidance file and
1066
+ `settings.json` (`maxDepth`, `maxConcurrentDelegates`, `maxDelegatesPerRoot`,
1067
+ `windowPlacement`, `worktreeRoot`, `delegateTools`, `delegateWidgetPlacement` and
1068
+ `delegateWidgetMaxEntries`); the
1069
+ agents live in `<agent dir>/agents/` (see [Agents](#agents)).
470
1070
  `delegation_prompt.md` is Markdown, inserted unchanged as the
471
1071
  `squire_parent_delegation` system-prompt section before each new run, including
472
1072
  a run started by an idle delegate result. It is not a conversation or user
473
1073
  message, is not duplicated in the `delegate` tool description or guidelines,
474
- and is not sent to the child. The guidance is added only while the `delegate`
1074
+ and is not copied into the launched child's prompt. An opted-in coordinator
1075
+ reads this same file for its own parent role. The guidance is added only while the `delegate`
475
1076
  tool is active and not hidden; when delegation is inactive or hidden, the
476
1077
  section is removed and tool selection is left as it was. A missing or whitespace-only
477
1078
  file adds no section. Editing the file replaces the section at the next run;
@@ -481,43 +1082,56 @@ valid delegation working. The package ships no default delegation or approval
481
1082
  policy.
482
1083
 
483
1084
  Refresh happens before a new run, not before every model request in a tool
484
- loop, and the extension does not watch the filesystem. `pi-squire` reads no
485
- settings file and no environment variable of its own. `PI_DELEGATE_PARENT` and `PI_DELEGATE_AUTO_EXIT` are set
486
- for the child, never read as configuration by the parent. The window starts
487
- with the tmux server's environment plus only these from the parent:
488
- `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR` (when the parent has it
489
- set; `--session-dir` outranks it), `PI_DELEGATE_PARENT` and `PI_DELEGATE_AUTO_EXIT`, and it always gets `PI_UNATTENDED=1`.
1085
+ loop, and the extension does not watch the filesystem. `pi-squire` uses launch variables to bootstrap a delegate; durable session
1086
+ identity takes precedence when it reopens. The window starts with the tmux
1087
+ server's environment plus `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR`
1088
+ (when set; `--session-dir` outranks it), and these delegate values:
1089
+
1090
+ - `PI_DELEGATE_PARENT`: direct parent's session id;
1091
+ - `PI_DELEGATE_ROOT`: root session id;
1092
+ - `PI_DELEGATE_DEPTH`: child depth as a decimal string;
1093
+ - `PI_DELEGATE_AGENT`: this child's roster identity (not display name);
1094
+ - `PI_DELEGATE_CHAIN`: comma-separated agent names, root downward, including
1095
+ the child last, e.g. `coordinator,worker`; names label requests, session ids route them;
1096
+ - `PI_DELEGATE_AUTO_EXIT`: `1` or `0`;
1097
+ - `PI_DELEGATE_FALLBACK`: remaining model candidates, when any.
1098
+
1099
+ It always gets `PI_UNATTENDED=1`.
490
1100
  `PI_UNATTENDED=1` is a general signal, not specific to pi-squire: no human is
491
1101
  watching this session, even though it runs in a TUI. Other extensions may read
492
1102
  it to skip a dialog or confirmation instead of waiting on one, and any other
493
1103
  runner that starts Pi unattended can set it too. Anything else the
494
1104
  parent process has, such as a `CLAUDE_CONFIG_DIR` set by a shell alias,
495
1105
  reaches the child only through a `session:launch` contributor. `--model` and
496
- `--thinking` override the agent's defaults for one call, and an unknown value
1106
+ `--thinking` override the agent's defaults for one call, unless the agent
1107
+ locks them (see [Agents](#agents)), and an unknown value
497
1108
  is refused with close matches, and so is a model with no configured
498
1109
  credentials. An API key that exists only as an environment variable in the
499
1110
  parent's shell does not reach the delegate, so log in through Pi or make the
500
1111
  variable part of the tmux server's environment. `--auto-exit` and `--no-auto-exit` override the
501
- agent's `auto-exit`.
1112
+ agent's `auto_exit`.
502
1113
 
503
1114
  ## Tools
504
1115
 
505
1116
  The model gets four tools. They share the command's launch path and the
506
- session's delegation records. None of them is registered inside a delegate.
1117
+ session's delegation records. In a delegate they are active only for an
1118
+ opted-in coordinator below `maxDepth`; admission also checks every launch/resume.
507
1119
 
508
1120
  | Tool | Parameters | What it does |
509
1121
  | --- | --- | --- |
510
1122
  | `delegate` | `agent`, `task`, optional `model`, `thinking`, `label`, `cwd` and `auto_exit` (boolean) | Validates the call the way the command does, starts the delegation without opening a dialog (except an unresolved [trust question](#project-trust) for `cwd`), and returns the delegation id at once without waiting for a result. |
511
1123
  | `delegation_status` | optional `id` | Reports each delegation as running, done, closed or closed without a result, with the window, the delegate's session and the result envelope's path. |
512
- | `delegation_close` | `id` | Kills the window through tmux and records the close, so `delegation_status` reports it closed. |
1124
+ | `delegation_close` | `id` | Explicitly fences and closes a descendant subtree using current verified windows. Returns per-member outcomes and complete/partial status; retains worktrees and branches. |
513
1125
  | `delegation_resume` | `id`, `task`, optional `model`, `thinking` and `auto_exit` | Reopens an exited delegate's own session in a new window with a follow-up task, and returns the new delegation's id at once (see [Resuming a delegation](#resuming-a-delegation)). |
514
1126
 
515
1127
  The `delegate` tool's description lists the current roster, one line per agent:
516
- its name, description, default model and thinking, auto-exit when it is off,
517
- and its excluded tools.
1128
+ its name and any display name, description, default model (or this session's)
1129
+ and fallbacks, thinking, auto-exit when it is off, its tools (`no tools` for
1130
+ an empty `tools` field, `this session's except …` for `-name` entries) and the
1131
+ fields it locks.
518
1132
 
519
1133
  ```text
520
- - scout — Read-only recon (default provider/model-id, thinking low, auto-exit off, no edit/write)
1134
+ - scout "Recon" — Read-only recon (default provider/model-id, thinking low, auto-exit off, tools: read/grep, locked: model)
521
1135
  ```
522
1136
 
523
1137
  The model picks an agent from these lines without reading files. The
@@ -529,9 +1143,9 @@ merely to wait; a substantive follow-up remains fine. The description is
529
1143
  rebuilt at session start and before each new run when the exposed roster
530
1144
  changes, so an edit, addition or removal shows up in the run being prepared,
531
1145
  including a run woken by an idle delegate result. Unchanged metadata is not
532
- re-registered. Pi cannot list the session's tools while extensions load, so
533
- the description built then skips the `exclude-tools` check, and the one built
534
- after startup applies it. The roster itself is still read at call time, and an
1146
+ re-registered. The description built while extensions load lists only the
1147
+ global agents, since the project's trust is known only once the session
1148
+ starts. The roster itself is still read at call time, and an
535
1149
  unknown agent, model or thinking level is refused with close matches and
536
1150
  nothing starts.
537
1151
 
@@ -611,10 +1225,63 @@ even when no delegation is recorded. The details gain a `marks` array of
611
1225
  `{ scope, source, reason, clearsAt }` and a `readingErrors` array (see
612
1226
  [Usage limits](#usage-limits)).
613
1227
 
614
- `delegation_close` kills the window and records the close even when the
615
- result has already arrived. When the window is already gone it only records
616
- the close. An unknown or already closed id returns a message and kills
617
- nothing. The delegate's session file and result envelope stay on disk.
1228
+ ## Explicit subtree close
1229
+
1230
+ `delegation_close` and the `/delegates` close actions enumerate persistent
1231
+ records and close the selected session and all its descendants, including work
1232
+ whose coordinator is offline. Any ancestor may close; unrelated trees, siblings
1233
+ outside the selected subtree and user windows remain untouched. Only the root
1234
+ has the panel's `a` close-all action. Close-all fences new root admissions too;
1235
+ neither action kills a companion tmux session wholesale.
1236
+
1237
+ Admission is fenced under the shared tree lock **before** window effects.
1238
+ Already-reserved pre-dispatch launches cannot dispatch; an already-dispatched
1239
+ create stays charged and fenced until its outcome/current locator is known.
1240
+ The explicit call makes at most three passes, with two 25 ms waits for late
1241
+ locator publication, and otherwise returns partial. It never infers no-start
1242
+ from a timeout, dead opener, finished accounting or an empty tmux listing.
1243
+ There is no automatic close-repair daemon. Retry the explicit operation after
1244
+ positive create/window evidence becomes available; unknown launches are not
1245
+ refunded or relaunched speculatively.
1246
+
1247
+ Only a window matching the **current recorded id and name** is killed, even
1248
+ if its program has exited or its tmux session moved. A same-id manually reopened
1249
+ coordinator can have a newer shared locator than its parent's launch history;
1250
+ close uses that locator. An already absent verified window records a close.
1251
+ Individual tmux or persistence errors do not erase successful members. A failed
1252
+ initial registry read/fence refuses before any kill; a post-kill publication
1253
+ failure reports the side effect and uncertainty, not successful completion.
1254
+
1255
+ The tool's `details` and `structuredContent` match its output schema:
1256
+ `{id, windowId?, windowName?, closed, complete, alreadyClosed, killed, members,
1257
+ error?}`. Each member has `{id, sessionId, windowId?, windowName?, outcome,
1258
+ closed, killed, error?}`; outcome is `killed`, `absent`, `already-closed`,
1259
+ `not-started`, `pending` or `error`. Aggregate `closed/complete` is true only
1260
+ when every captured member is resolved; partial returns `isError:true` while
1261
+ retaining structured details. Repeat close visits unresolved descendants even
1262
+ if the selected coordinator alone was already closed. Unknown/out-of-scope
1263
+ ids and obsolete resumed records refuse without window effects.
1264
+
1265
+ Confirmed shared closes are control/accounting facts, not fabricated worker
1266
+ results. The sole direct-parent owner imports them into its own durable
1267
+ child-task inbox as canceled/missing work, then processes them in a model run
1268
+ before finishing upstream. An offline coordinator does this on same-id reopen;
1269
+ the ancestor never writes its semantic state or emits raw grandchild reports.
1270
+ A stopped root retains its previous replay hold on reopen. Fresh live close
1271
+ outcomes wake normally, like other new outcomes. Real replies arriving before,
1272
+ during or after close are retained and correlated normally, not replaced by a
1273
+ success or cancellation envelope.
1274
+
1275
+ Once the explicit operation is complete, a legitimate direct-parent resume can
1276
+ supersede that session's completed fence as a **new lifetime-counted delegation**.
1277
+ It still passes current authority/owner, chain, depth, grants, caps, tool-ceiling
1278
+ and trust checks. Failed pre-dispatch resumes leave the old fence effective.
1279
+ Closed descendants remain canceled; resuming a coordinator does not revive them.
1280
+ An unresolved active close cannot be bypassed by resume.
1281
+
1282
+ Quit, crash, reload, session switch and auto-exit do **not** cascade. Close never
1283
+ removes worktrees, branches, session files or result envelopes. Existing narrow
1284
+ rollback of pristine resources on a known failed launch is unchanged.
618
1285
 
619
1286
  ## Usage limits
620
1287
 
@@ -647,8 +1314,9 @@ The launch also passes the candidates after the chosen one to the delegate as
647
1314
  `PI_DELEGATE_FALLBACK` (none for an explicit model). When a delegate's run
648
1315
  ends on a usage limit, it switches its session to the first of them that has
649
1316
  credentials and is not marked, adds a message saying so, and continues the
650
- task there, so work done so far is kept. Each fallback is tried once; when
651
- none is left, the run fails as before. A `done` result from such a delegate
1317
+ task there, so work done so far is kept. An alias resolves to one model, as
1318
+ it would in Pi's `--model`. Each model is tried once, never the one that just
1319
+ failed; when none is left, the run fails as before. A `done` result from such a delegate
652
1320
  carries the `Usage limit:` line too, saying it finished on a fallback.
653
1321
 
654
1322
  Before a launch, pi-squire also reads the OpenCode Go quota for a paid
@@ -715,17 +1383,20 @@ mark, it changes nothing and lists the active marks. No tool clears marks.
715
1383
 
716
1384
  The parent resolves each delegation's auto-exit from the call
717
1385
  (`auto_exit`, or `--auto-exit` / `--no-auto-exit`), then the agent's
718
- `auto-exit`, then on, and passes it to the child as `PI_DELEGATE_AUTO_EXIT`
1386
+ `auto_exit`, then on, and passes it to the child as `PI_DELEGATE_AUTO_EXIT`
719
1387
  (`1` or `0`). Inside the delegate:
720
1388
 
721
1389
  - While it is on, a run that settles as a normal completion shuts the delegate
722
1390
  down (`ctx.shutdown()`) on the next event-loop turn, after the settle
723
1391
  reply has been sent, so its tmux window closes. The session file stays, and
724
1392
  `pi --session <path>` reopens it (see [Reopening a delegate's session](#reopening-a-delegates-session)).
725
- - A failed run, such as one ending on a usage limit or another provider
726
- error, exits like a normal completion; the parent still gets the failed
1393
+ - A failed final run, such as one ending on a usage limit or another provider
1394
+ error after fallbacks are exhausted, exits like a normal completion; the parent still gets the failed
727
1395
  reply and the session file stays. Only a run the user stopped never exits
728
1396
  the delegate.
1397
+ - A coordinator cannot exit until its child tasks finish and their outcomes
1398
+ are processed in a subsequently settled run. An intermediate failure sends
1399
+ no final task reply and keeps it available for child results.
729
1400
  - The user taking over turns it off for the rest of the session, with a
730
1401
  notice: typed or RPC input, or a run the user stopped (Esc).
731
1402
  - `/auto-exit` inside the delegate turns it on again for the next normal
@@ -733,7 +1404,8 @@ The parent resolves each delegation's auto-exit from the call
733
1404
  - The footer shows that this is a delegate, its agent, and whether auto-exit
734
1405
  is on: `👦 worker delegate · auto-exit on`, or
735
1406
  `👦 worker delegate · auto-exit off`, with 👦 or 👧
736
- picked at random per session.
1407
+ picked at random per session. Its status key is `delegate-self`; its own
1408
+ running-child count uses `delegate`, so the footers do not overwrite each other.
737
1409
 
738
1410
  There is no `subagent_done` tool: auto-exit off only means the window stays.
739
1411
 
@@ -784,10 +1456,12 @@ Delegate session: <session file or id>
784
1456
  5 non-blank lines with heading marks, backticks and bold stripped;
785
1457
  expanded (Ctrl+O), it shows the result as Markdown, the quoted task, then
786
1458
  the ids and files, with the home directory shown as `~`.
787
- - Stopping a delegate's run (Esc) sends no result: the provider holds the
1459
+ - Stopping a delegate's run (Esc) sends no result: pi-squire holds the
788
1460
  task, so the delegation stays running while the user steers it. The next
789
- run that completes sends the result, `done`, and its body opens with a note
790
- that the user took over partway.
1461
+ run that completes successfully sends the result, `done`, and its body opens
1462
+ with a note that the user took over partway. Failed runs in between do not
1463
+ answer a held task. Asks are different: the mail provider still owns their
1464
+ replies, including `stopped` replies, and does not hold them.
791
1465
  - A `failed` reply from a delegate that recorded a usage-limit mark (see
792
1466
  [Usage limits](#usage-limits)) gets one more line right after `Status:`,
793
1467
  for example `Usage limit: the delegate hit a usage limit on provider
@@ -818,15 +1492,26 @@ Delegate session: <session file or id>
818
1492
  current tool calls, before the next model call. A follow-up would wait
819
1493
  until the parent had answered, by which time it had often read the
820
1494
  envelope itself, and the late result cost a second, redundant turn.
821
- - The footer counts the running delegations (`👨 N delegates running`, with 👨 or 👩
822
- picked at random per session), clears one entry per result or close and
823
- hides itself at zero.
1495
+ - The [direct-delegate overview](#direct-delegate-overview) replaces Squire’s
1496
+ running-count entry in interactive Pi. With its cap set to zero, or in a
1497
+ non-TUI host, the footer counts running delegations (`👨 N delegates running`,
1498
+ with 👨 or 👩 picked at random per session), clears one entry per result or
1499
+ close and hides itself at zero. The own-delegate badge stays separate.
824
1500
  - Each result and each close is recorded in the parent session, and the
825
1501
  records are rebuilt synchronously at session start. A resume still
826
1502
  recognises replies, including one that arrived while the parent was
827
1503
  closed, and still reports a recorded close as closed. A result is shown
828
- once and never again.
829
- - Plain replies, and every request, are left to the provider.
1504
+ once for ordinary transport retries. Unprocessed coordinator outcomes may
1505
+ be replayed on reopening. A root retains outcomes until processing too: an
1506
+ already-displayed result wakes a recovery turn without displaying the final
1507
+ result again, and pending content is supplied to the next model request.
1508
+ An explicit root user stop is persisted: reopening does not start that work
1509
+ again automatically. Previously held outcomes wait for a later input or a
1510
+ genuinely fresh outcome. New live results still steer/start a turn, and new
1511
+ gone outcomes keep their normal wake delivery; they clear the held replay
1512
+ state. Coordinator held-task semantics stay unchanged.
1513
+ - Plain replies, and requests not taken over as this delegate's task, are left
1514
+ to the provider. Squire never answers those ordinary requests.
830
1515
 
831
1516
  While any delegation is running (no result, no close), the parent polls every
832
1517
  5 seconds; the timer does not keep the process alive and stops when nothing is
@@ -838,7 +1523,8 @@ gone. A delegation whose window is gone and still has no result after that
838
1523
  scan is recorded as closed without a result, a record separate from
839
1524
  `delegation_close`'s, and the parent gets one `[delegate]` message naming the
840
1525
  delegation, with the delegate session path when one is found, sent as a
841
- `followUp` with `triggerTurn`. A tick declares nothing when nobody answers
1526
+ `followUp` with `triggerTurn` (a coordinator uses `steer` to process the missing
1527
+ outcome before its final report). A tick declares nothing when nobody answers
842
1528
  `message:scan` or when tmux reports an error. A reply that arrives later is
843
1529
  still delivered and makes the delegation done. A resumed session polls again
844
1530
  for every delegation it restores as running.
@@ -855,6 +1541,13 @@ absolute path opens that file directly.
855
1541
 
856
1542
  ## Resuming a delegation
857
1543
 
1544
+ **Breaking resume change:** delegations recorded without the required
1545
+ original-root placement identity cannot be resumed after this update. This
1546
+ includes pre-nesting and identity-less records, even when their original root
1547
+ reopens them. The tool, command and picker refuse before trust resolution, mail,
1548
+ killing an exited window, reserving a launch or spawning a new window. There is
1549
+ no legacy identity synthesis or migration.
1550
+
858
1551
  An exited delegate can continue its own session with a follow-up task:
859
1552
 
860
1553
  ```text
@@ -890,16 +1583,20 @@ and it resumes as the command does.
890
1583
  opened would reach the next resume as a second task. The reopened delegate
891
1584
  receives it once its mailbox starts. If the send fails, the new window is
892
1585
  killed and nothing is recorded.
893
- - A chain resumes from its newest delegation; resuming an older one is refused,
894
- naming the newest.
1586
+ - Only its direct parent may resume it, preserving the original root/depth.
1587
+ The current parent grant, depth/cap/tool-ceiling/trust checks are reapplied.
1588
+ A chain resumes from its newest local/shared delegation; older records and
1589
+ copied fork/clone history are refused, naming the owner or newest record.
895
1590
  - It is refused, before anything is sent or opened, unless: the delegation is a
896
1591
  started one (not a workspace attempt); it is not `running` (mail a running
897
1592
  delegate instead) and tmux can be asked; no window with its id and name still
898
1593
  runs a program (an exited one is killed first; a window that merely shares the
899
1594
  name is ignored); its session file is found and the working directory in the
900
- file's header still exists; its agent is still in the roster; and no other
1595
+ file's header still exists; its agent is still in the roster and not disabled
1596
+ (a disabled agent's refusal names its file and the reason); and no other
901
1597
  resume of it is under way. A delegate whose window was killed mid-turn can be
902
- resumed.
1598
+ resumed after a completed close; an unresolved active subtree close refuses
1599
+ resume until positively resolved by an explicit retry.
903
1600
  - A removed working directory, a removed managed worktree included, makes a
904
1601
  delegation not resumable: Pi runs a reopened session in its stored directory.
905
1602
  The window opens in that directory, with project trust decided again for it as
@@ -908,9 +1605,17 @@ and it resumes as the command does.
908
1605
  - Without `model` it continues on the model the delegate finished on, moving to
909
1606
  its agent's fallbacks when that one is unavailable or marked (see
910
1607
  [Usage limits](#usage-limits)). An explicit `model` is used alone and refused
911
- rather than substituted. `thinking` and `auto_exit` default to the
912
- delegation's own. The agent's prompt, tools and extensions are those of the
913
- roster now.
1608
+ rather than substituted. Without `thinking` it continues on the level the
1609
+ delegate last ran on: the last thinking-level change on its session's
1610
+ active branch (Pi records one at start, one per change, and one when a
1611
+ model change clamps the level), or the delegation's recorded level when
1612
+ the session has none. `auto_exit` defaults to the delegation's own. A
1613
+ field the agent locks is the exception: it runs on the
1614
+ agent file's current `model` (then its fallbacks, even when the delegate had
1615
+ moved to a fallback) or `thinking`, a value passed for it is not used, with
1616
+ a notice, and passing the file's own value is the same as passing nothing. The agent's prompt and tools are those of the
1617
+ roster now, and an agent without a `tools` field gets the parent's tools as
1618
+ they are at the resume.
914
1619
  - `session:launch` is emitted as for any launch, with the same payload.
915
1620
  - Not detected: a Pi process you opened yourself on the same session outside
916
1621
  pi-squire's window. Close it before resuming.
@@ -925,7 +1630,8 @@ that block.
925
1630
 
926
1631
  ### `session:launch`
927
1632
 
928
- `pi-squire` provides this hook. It emits `{ args, env, agent, cwd, toolCallId }`
1633
+ `pi-squire` provides this hook. It emits
1634
+ `{ args, env, agent, cwd, toolCallId, parent, root, depth, chain }`
929
1635
  just before it opens the delegate's window: for a launch, after the task has
930
1636
  been sent; for a resume, before it is sent, since a resume mails the task once
931
1637
  the window is open (see [Resuming a delegation](#resuming-a-delegation)).
@@ -934,13 +1640,28 @@ the window is open (see [Resuming a delegation](#resuming-a-delegation)).
934
1640
  | --- | --- |
935
1641
  | `args` | the child `pi`'s complete argument list, launcher first |
936
1642
  | `env` | the child's environment, as `KEY`/`value` pairs |
937
- | `agent` | the agent's name, as in `<agent dir>/squire/agents/<name>/AGENT.md`; read it, do not change it |
1643
+ | `agent` | the agent's name, as in `<agent dir>/agents/<name>.md` or `<cwd>/.pi/agents/<name>.md` (never its `display_name`); read it, do not change it |
938
1644
  | `cwd` | the directory the window opens in, the parent's or the resolved `--cwd`; read it, do not change it |
939
1645
  | `toolCallId` | the ID of the `delegate` or `delegation_resume` tool call that started the launch; absent when started from the `/delegate` or `/delegate-resume` command; read it, do not change it |
1646
+ | `parent` | Read-only direct parent session id. |
1647
+ | `root` | Read-only root session id; retained on resume. |
1648
+ | `depth` | Read-only numeric child depth, root = 0; retained on resume. |
1649
+ | `chain` | Read-only frozen array of roster agent names ordered root downward, including the current child last; no session ids. |
940
1650
 
941
1651
  Listeners may only append to `args` and add keys to `env`; there is no veto,
942
- and `pi-squire` passes both to tmux unchanged. Do all work synchronously: the
943
- emitter reads the payload the moment `emit` returns.
1652
+ and top-level launches pass both to tmux unchanged. For nested launches and
1653
+ resumes, after all listeners return, pi-squire resolves the final tool selection
1654
+ and appends an authoritative exact-name `--tools` registration allowlist bounded
1655
+ by the direct parent. This limits which tool names the child can register and
1656
+ reach, including names provided by hook-added extensions, late registrations
1657
+ and MCP. It does not
1658
+ limit extension code or implementations registered under allowed names;
1659
+ unrelated arguments/environment keys survive. For nested launches and resumes,
1660
+ final customized arguments that Pi cannot parse refuse the launch before the
1661
+ window opens.
1662
+ An empty final set also receives `--no-tools` and an empty allowlist so earlier
1663
+ selection flags cannot restore tools. Do all work synchronously: the emitter
1664
+ reads the payload the moment `emit` returns.
944
1665
 
945
1666
  `cwd`, the trust flag (`--approve` or `--no-approve`), `--session-dir` and the
946
1667
  session choice (`--session-id` for a new delegate, `--session` for a [resumed](#resuming-a-delegation)
@@ -957,6 +1678,121 @@ pi.events.on("session:launch", (payload) => {
957
1678
  payload.args.push("--auto");
958
1679
  payload.env.GATE_MODE = "auto";
959
1680
  payload.env.GATE_ROOT = payload.cwd;
1681
+ payload.env.GATE_CHAIN = payload.chain.join(",");
1682
+ payload.env.GATE_PARENT = payload.parent;
1683
+ assert.equal(payload.chain.at(-1), payload.agent);
1684
+ assert.ok(Number.isSafeInteger(payload.depth));
1685
+ assert.ok(Object.isFrozen(payload.chain));
1686
+ });
1687
+ ```
1688
+
1689
+ ### `squire:identity`
1690
+
1691
+ `pi-squire` emits this synchronous, read-only notification from its identity
1692
+ restore handler in `session_start`, before its later asynchronous startup work.
1693
+ The payload is `{ sessionId, status, identity? }` for the active exact session id:
1694
+
1695
+ - `status: "delegate"`: its saved identity passed Squire's validation and was
1696
+ successfully persisted again. `identity` contains only
1697
+ `{ parent, root, agent, chain, depth }`, with the same meanings as
1698
+ `session:launch`.
1699
+ - `status: "root"`: no identity record exists for that exact id. This is absence
1700
+ of Squire delegate identity, not proof that the session is an original root.
1701
+ - `status: "invalid"`: reading, validating or persisting its identity failed.
1702
+ No prior identity or raw error is included; Squire's existing restore error
1703
+ and launch/completion refusal remain in effect.
1704
+
1705
+ The payload, identity projection and copied `chain` array are frozen. Internal
1706
+ placement and auto-exit state are not exposed. Environment variables, display
1707
+ labels and copied transcript entries are not identity authority.
1708
+
1709
+ Pi 1.3 and 1.4 recreate the extension runtime and dispatch `session_start`
1710
+ after new, fork/clone and session switch/import; reload also dispatches it.
1711
+ Each restore reads only the destination's exact id, so a new/forked session
1712
+ without its own record reports `root`, not the source's delegate identity.
1713
+ This event does not adopt sessions or migrate records.
1714
+
1715
+ Register listeners before Squire loads and do all observation synchronously.
1716
+ The bus does not replay notifications, await asynchronous listeners, acknowledge
1717
+ listener success or veto later startup; listener exceptions are reported by Pi.
1718
+ Consumers own resetting their state at session shutdown/replacement, matching
1719
+ `sessionId` to their current context, and handling missing or invalid
1720
+ notifications. A consumer that must initialize before another extension's
1721
+ `session_start` must load its listener before Squire, and Squire before that
1722
+ extension. This is an observation seam, not an authorization gate or a provider
1723
+ integration.
1724
+
1725
+ ```js squire:identity
1726
+ pi.events.on("squire:identity", (snapshot) => {
1727
+ assert.ok(Object.isFrozen(snapshot));
1728
+ assert.equal(typeof snapshot.sessionId, "string");
1729
+ assert.ok(["root", "delegate", "invalid"].includes(snapshot.status));
1730
+ if (snapshot.status === "delegate") {
1731
+ assert.ok(Object.isFrozen(snapshot.identity));
1732
+ assert.ok(Object.isFrozen(snapshot.identity.chain));
1733
+ assert.equal(typeof snapshot.identity.root, "string");
1734
+ assert.equal("rootPlacement" in snapshot.identity, false);
1735
+ } else {
1736
+ assert.equal("identity" in snapshot, false);
1737
+ }
1738
+ });
1739
+ ```
1740
+
1741
+ ### Process-local completion holds (v1)
1742
+
1743
+ Squire composes external work with its existing completion predicate through a
1744
+ provider-neutral data table at `globalThis[Symbol.for("pi.completion-holds.v1")]`:
1745
+
1746
+ ```ts
1747
+ {
1748
+ version: 1,
1749
+ hosts: Map<sessionId, Map<hostName, runtimeToken>>,
1750
+ holds: Map<sessionId, Map<holdId, { provider: string, ready: boolean }>>
1751
+ }
1752
+ ```
1753
+
1754
+ An active Squire runtime registers `"pi-squire"` with a fresh string runtime token
1755
+ only during its exact session's `session_start`, after process ownership is
1756
+ acquired. A losing duplicate does not advertise support. Every
1757
+ `session_shutdown`, including reload, removes only that runtime's matching host
1758
+ token. Stale shutdown cannot remove a replacement's registration.
1759
+
1760
+ Providers must check for a well-formed, nonempty host map for the current exact
1761
+ session before promising a hold. Register a unique hold id with `ready:false`
1762
+ atomically before launching the work; refund only that provisional entry if
1763
+ launch fails. Keep accepted entries across reload, including while the provider
1764
+ is unavailable. Set `ready:true` only after the terminal result has been observed
1765
+ and successfully processed; the provider owns that decision and its normal-run
1766
+ commit, before Squire reads readiness at `agent_settled`. Squire neither observes
1767
+ external output nor decides whether an operation succeeded.
1768
+
1769
+ All current-session holds must be well formed and ready before Squire sends a
1770
+ new final task reply, marks its tree entry finished, or auto-exits. An absent
1771
+ table or no holds preserves normal completion. A malformed table/version or
1772
+ malformed current-session host/hold entry blocks completion; Squire does not
1773
+ overwrite it to repair storage. Other sessions' holds do not block this session.
1774
+ An absent host does not invalidate already-ready entries; it refuses promises
1775
+ of new holds. Providers clear only their own holds at non-reload teardown;
1776
+ Squire never removes provider entries.
1777
+
1778
+ This is trusted in-process extension data, not a model-callable release API,
1779
+ cross-process registry, durable reopen block, or authorization boundary. There
1780
+ are no callbacks in the table, fake child tasks, polling/continuation loops, or
1781
+ automatic model calls to keep a session alive. Ordinary shutdown cancels the
1782
+ provider's work; reopening does not recover these holds. Already-frozen reply
1783
+ outboxes retain their existing retry semantics.
1784
+
1785
+ This executable observer loads after Squire and checks only the host contract:
1786
+
1787
+ ```js completion-holds:v1
1788
+ pi.on("session_start", (_event, ctx) => {
1789
+ const table = globalThis[Symbol.for("pi.completion-holds.v1")];
1790
+ assert.equal(table.version, 1);
1791
+ assert.ok(table.hosts instanceof Map);
1792
+ assert.ok(table.holds instanceof Map);
1793
+ const hosts = table.hosts.get(ctx.sessionManager.getSessionId());
1794
+ assert.ok(hosts instanceof Map);
1795
+ assert.equal(typeof hosts.get("pi-squire"), "string");
960
1796
  });
961
1797
  ```
962
1798
 
@@ -969,6 +1805,66 @@ and `pi-squire` refuses with a message naming `message:*`. The envelope's `id`
969
1805
  is kept in the delegation's record, so an answer can be matched to it. The
970
1806
  provider's own README carries the contract.
971
1807
 
1808
+ ### `message:reply`
1809
+
1810
+ After the final run settles (including fallback continuations and the durable
1811
+ child-first processing barrier), squire emits
1812
+ `{ to, inReplyTo, body, status }`: the direct parent's address, the owed request
1813
+ ids in batches of at most 50, the exact result text, and `done` or `failed`.
1814
+ The provider must synchronously set `envelope` or `error`; neither means no
1815
+ provider. Squire keeps the task owed until the write succeeds and completion
1816
+ is recorded. The provider's README owns the transport contract.
1817
+
1818
+ Task ownership and a frozen final-answer outbox live in
1819
+ `<agent dir>/squire/task-replies/<session id>.json`, independently of Pi's
1820
+ buffered transcript. Atomic file replacement preserves pending answers over
1821
+ reload, restart, squire resume and manual reopening of the same session with
1822
+ the same agent directory. Identity and auto-exit state live separately in
1823
+ `<agent dir>/squire/identity/<encoded session id>.json`; manual reopening
1824
+ restores parent/root/depth/agent/chain, the original `rootPlacement` companion
1825
+ preference and the saved auto-exit setting. This internal placement scalar adds
1826
+ no launch environment variable or `session:launch` field. The
1827
+ launch-time remaining fallback list is not restored by a manual reopen;
1828
+ squire resume still supplies its current fallback candidates.
1829
+
1830
+ Direct-child facts and pending result/gone outcomes live in
1831
+ `<agent dir>/squire/child-tasks/<encoded parent session id>.json`, independently
1832
+ of transcript buffering. Starts and terminal outcomes are saved locally before
1833
+ result injection. Restoring these facts precedes Squire task handling. If mail
1834
+ claims before that restoration (including delayed startup or a duplicate
1835
+ process), matching Squire input is staged durably for the sole owner.
1836
+ Each model request includes pending outcomes; only those observed by a request
1837
+ and followed by a non-stopped settlement are acknowledged. The task reply gate
1838
+ requires no unfinished children and no unprocessed outcomes. Storage failures, including incomplete transcript/child-identity restoration,
1839
+ block final completion/auto-exit and new launch/resume admission in roots and
1840
+ coordinators. Read-only admission checks the restored store health; a write
1841
+ preflight persists unchanged task facts before mail, windows, worktree creation
1842
+ or killing an exited resume window. Repair unhealthy storage and reload before
1843
+ launching again. An incomplete-restore latch also refuses incidental saves, so
1844
+ partial in-memory records cannot replace known pending obligations. These local
1845
+ task records remain the result-processing authority;
1846
+ the shared [tree registry](#tree-wide-launch-budgets) indexes their durable
1847
+ lifecycle facts without copying task bodies or processing outcomes.
1848
+
1849
+ A failed write produces a visible `delegate: could not handle the task reply`
1850
+ error and blocks auto-exit. A frozen answer is retried on the next settled
1851
+ run, or on reload/reopen (starting on the next event-loop turn); the next
1852
+ run's text does not replace that answer. An async startup handler between
1853
+ squire and mail can delay provider initialization past that turn, so a failed
1854
+ startup attempt gets two additional reply attempts, 100 ms apart, on an
1855
+ unref'd timer. After those attempts, any failure is visible and no ongoing
1856
+ retry timer remains. Fix the provider or storage error and reload/reopen to
1857
+ retry without a model call. A successful startup retry does not itself close
1858
+ the window.
1859
+
1860
+ Mail does not deduplicate replies. A crash after the envelope write but before
1861
+ completion recording can cause squire to resend the saved outbox. This is
1862
+ at-least-once transport, not atomic exactly-once delivery. The parent matches
1863
+ every correlated task id, records each as answered, and suppresses further
1864
+ final results for those ids even when a retry has a new envelope id. One
1865
+ envelope answering several owed tasks is shown once, using its last pending
1866
+ delegation for the displayed task quote.
1867
+
972
1868
  ### `message:inbound`
973
1869
 
974
1870
  `pi-squire` consumes this hook for answers: a reply whose `in_reply_to` names a
@@ -981,8 +1877,10 @@ to the provider.
981
1877
  Inside a delegate, `pi-squire` also takes over the task: a `request` whose
982
1878
  `from` is the parent's address. It sets `handled` and sends the task as a user
983
1879
  prompt labelled `[delegate]`, so the run starts the way a typed prompt does.
984
- The provider still arms the automatic answer for a request a listener took
985
- over. Every other message to the delegate is left to the provider. `pi-squire` reads `from` and requires
1880
+ Squire durably records its parent and request id before starting the task;
1881
+ it owns the answer through `message:reply`. The provider arms no automatic
1882
+ request answer. Every other message to the delegate is left to the provider.
1883
+ `pi-squire` reads `from` and requires
986
1884
  it to be the sender's address, which for a delegate is its session id; a reply
987
1885
  from any other sender is left to the provider, even when it answers a recorded
988
1886
  request. The provider's README carries the contract.
@@ -1008,7 +1906,12 @@ contract.
1008
1906
 
1009
1907
  ## Testing limits
1010
1908
 
1011
- The automated tests drive the extension through a fake Pi session. They check
1909
+ The automated tests drive the extension through fake Pi lifecycles. The
1910
+ reusable `test/mail-harness.ts` runs multiple independent extension instances
1911
+ with the real, exactly pinned pi-session-mail 0.6.0 over an isolated shared
1912
+ mail root. Protocol tests cover correlation, asks, stops, fallback, pending
1913
+ answers, retries and duplicate delivery after the write/completion crash
1914
+ window. These are not real model runs or real process-crash tests. They check
1012
1915
  the registered tool definitions, the structured prompt options a simulated run
1013
1916
  preparation receives, file handling, visibility gating and the existing launch
1014
1917
  and result paths. They do not prove actual Pi event scheduling, final provider
@@ -1031,12 +1934,13 @@ version and is listed under "Breaking" in [CHANGELOG.md](CHANGELOG.md).
1031
1934
 
1032
1935
  Other extensions may rely on:
1033
1936
 
1034
- - the `session:launch` payload `{ args, env, agent, cwd, toolCallId }`, emitted
1937
+ - the `session:launch` payload
1938
+ `{ args, env, agent, cwd, toolCallId, parent, root, depth, chain }`, emitted
1035
1939
  synchronously before the window opens (after the task is sent for a launch,
1036
1940
  before it is sent for a resume), with
1037
1941
  `cwd`, the trust flag and `--session-dir` chosen by `pi-squire` (see
1038
1942
  [Hooks](#hooks));
1039
- - the `message:send`, `message:inbound` and `message:scan` hooks, consumed as
1943
+ - the `message:send`, `message:reply`, `message:inbound` and `message:scan` hooks, consumed as
1040
1944
  their sections document;
1041
1945
  - the `delegate`, `delegation_status`, `delegation_close` and `delegation_resume` tool names,
1042
1946
  parameters, the recorded `workspace` shape and the `workspaceSnapshot`
@@ -1047,21 +1951,27 @@ Other extensions may rely on:
1047
1951
  - the `/delegates` command and its `alt+shift+d` shortcut, and `/delegate-open <id>` with its
1048
1952
  `--switch` and `--peek` flags;
1049
1953
  - the `[delegate]` result text and the closed-without-a-result text;
1050
- - `AGENT.md`'s frontmatter keys and its location,
1051
- `<agent dir>/squire/agents/<name>/AGENT.md`;
1954
+ - the agent file's frontmatter keys and its locations,
1955
+ `<agent dir>/agents/<name>.md` and, in a trusted project,
1956
+ `<cwd>/.pi/agents/<name>.md`;
1957
+ - the `maxDepth`, `maxConcurrentDelegates`, `maxDelegatesPerRoot`, `delegateTools`,
1958
+ `windowPlacement`, `worktreeRoot`, `delegateWidgetPlacement` and
1959
+ `delegateWidgetMaxEntries` settings in
1960
+ `<agent dir>/squire/settings.json`;
1052
1961
  - the optional `<agent dir>/squire/delegation_prompt.md` file and the
1053
1962
  `squire_parent_delegation` system-prompt section;
1054
- - the child environment variables `PI_DELEGATE_PARENT`,
1055
- `PI_DELEGATE_AUTO_EXIT` and `PI_UNATTENDED=1`.
1963
+ - the child environment variables `PI_DELEGATE_PARENT`, `PI_DELEGATE_ROOT`,
1964
+ `PI_DELEGATE_DEPTH`, `PI_DELEGATE_AGENT`, `PI_DELEGATE_CHAIN`,
1965
+ `PI_DELEGATE_AUTO_EXIT`, `PI_DELEGATE_FALLBACK` and `PI_UNATTENDED=1`.
1056
1966
 
1057
1967
  A `message:*` provider must:
1058
1968
 
1059
- - implement `message:send`, `message:inbound` and `message:scan` as documented
1060
- under [Hooks](#hooks);
1969
+ - implement `message:send`, `message:reply`, `message:inbound` and `message:scan`
1970
+ as documented under [Hooks](#hooks);
1061
1971
  - set a reply's `from` to the sender's address, so the delegate's reply can be
1062
1972
  matched to its task;
1063
- - reply on its own when the delegate's run settles, since that reply is what
1064
- becomes the result.
1973
+ - write explicit request replies through `message:reply`, without automatically
1974
+ answering requests at settle; asks remain separately owned by the provider.
1065
1975
 
1066
1976
  ## Contributing
1067
1977