pi-squire 0.15.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.
- package/CHANGELOG.md +323 -1
- package/README.md +918 -117
- package/package.json +2 -1
- package/src/agents.ts +57 -36
- package/src/child-tasks.ts +221 -0
- package/src/child.ts +35 -41
- package/src/completion-holds.ts +104 -0
- package/src/dashboard.ts +144 -0
- package/src/git.ts +6 -1
- package/src/identity.ts +152 -0
- package/src/index.ts +358 -56
- package/src/local-state.ts +21 -0
- package/src/navigator-view.ts +186 -0
- package/src/navigator.ts +152 -107
- package/src/poll.ts +1 -0
- package/src/render.ts +4 -1
- package/src/result-close.ts +37 -0
- package/src/result-restore.ts +95 -0
- package/src/result-types.ts +29 -1
- package/src/results-presentation.ts +47 -0
- package/src/results.ts +174 -108
- package/src/resume-checks.ts +14 -2
- package/src/resume-picker.ts +9 -2
- package/src/resume.ts +62 -13
- package/src/session-owner.ts +279 -0
- package/src/task-replies.ts +247 -0
- package/src/task-settlement.ts +81 -0
- package/src/task-store.ts +26 -0
- package/src/tmux.ts +134 -54
- package/src/tool-ceiling.ts +125 -0
- package/src/tracking.ts +72 -65
- package/src/tree-close.ts +236 -0
- package/src/tree-fence.ts +51 -0
- package/src/tree-lock.ts +77 -0
- package/src/tree-notice.ts +124 -0
- package/src/tree-registry.ts +365 -0
- package/src/tree-settings.ts +31 -0
- package/src/tree-types.ts +32 -0
- package/src/tree-view.ts +246 -0
- package/src/trust-control.ts +214 -0
- package/src/trust-prompt.ts +163 -0
- package/src/trust-wire.ts +203 -0
- package/src/widget-activity-join.ts +24 -0
- package/src/widget-activity-runtime.ts +104 -0
- package/src/widget-activity.ts +104 -0
- package/src/widget-ancestors.ts +142 -0
- package/src/widget-current.ts +41 -0
- package/src/widget-data.ts +147 -0
- package/src/widget-focus.ts +133 -0
- package/src/widget-live-process.ts +31 -0
- package/src/widget-live.ts +356 -0
- package/src/widget-render.ts +181 -0
- package/src/widget-selection.ts +58 -0
- package/src/widget-settings.ts +29 -0
- package/src/widget-summary.ts +51 -0
- package/src/widget-title-file.ts +127 -0
- package/src/widget-title.ts +118 -0
- package/src/widget.ts +272 -0
- package/src/window-placement.ts +30 -5
- 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)
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
@@ -140,28 +142,273 @@ temporary folder, not as text on the command line.
|
|
|
140
142
|
resumes with another storage directory selected. A parent whose session
|
|
141
143
|
directory is not an absolute path refuses to launch, before sending the
|
|
142
144
|
task.
|
|
143
|
-
-
|
|
144
|
-
|
|
145
|
-
|
|
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)).
|
|
146
149
|
- Without a UI the command and the `delegate` tool still work, except a
|
|
147
|
-
|
|
150
|
+
top-level launch whose project trust needs a question (a nested launch can
|
|
151
|
+
ask its attended root; see
|
|
148
152
|
[Project trust](#project-trust)). They need tmux
|
|
149
153
|
and a provider of `message:*`, and refuse with a message when either is
|
|
150
154
|
missing. In print mode (`pi -p`) Pi does not show the refusal notices, so
|
|
151
155
|
the refusal is silent there.
|
|
152
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
|
+
|
|
153
361
|
## Where delegate windows open
|
|
154
362
|
|
|
155
|
-
By default a delegate's window opens in a **companion session**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
162
406
|
|
|
163
407
|
- The session is created on demand, by the launch that needs it, and tmux
|
|
164
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.
|
|
165
412
|
- `detach-on-destroy` is set to `off` (and the mark set) on that session only: if you are
|
|
166
413
|
looking at it when its last delegate exits, tmux moves you to your most
|
|
167
414
|
recent other session instead of detaching you. Your sessions and tmux's
|
|
@@ -180,24 +427,212 @@ To open windows in your own session, as before, set `windowPlacement` in
|
|
|
180
427
|
```
|
|
181
428
|
|
|
182
429
|
The values are `companion` (the default when the setting is absent) and
|
|
183
|
-
`current`. The
|
|
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
|
|
184
439
|
with an error naming the file, before the task is sent or a window opens.
|
|
185
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
|
+
|
|
186
605
|
## Reaching delegates
|
|
187
606
|
|
|
188
607
|
Delegate windows sit out of your way, so reach them from the parent. `/delegates`
|
|
189
|
-
(or `alt+shift+d`) opens
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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.
|
|
193
624
|
|
|
194
625
|
| Key | Does |
|
|
195
626
|
|---|---|
|
|
196
|
-
| `↑` / `↓` | select a delegation;
|
|
197
|
-
| `enter` | switch your tmux client to
|
|
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 |
|
|
198
631
|
| `p` | peek: a snapshot of the window's pane (`capture-pane`) in an overlay; `r` refreshes, `enter` switches, `esc` returns to the list |
|
|
199
|
-
| `x` | close the
|
|
200
|
-
| `
|
|
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)) |
|
|
201
636
|
|
|
202
637
|
Switch acts on the most recently active tmux client attached to the parent's
|
|
203
638
|
session. With none attached it refuses with a notice; peek and close still
|
|
@@ -210,6 +645,22 @@ often rebound); the notice after a switch says so.
|
|
|
210
645
|
without the panel: with no flag or with `--switch`, it switches; `--peek` opens
|
|
211
646
|
the peek overlay. Ids complete from the open delegations.
|
|
212
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
|
+
|
|
213
664
|
## Working directory
|
|
214
665
|
|
|
215
666
|
`--cwd <dir>` (the `delegate` tool's `cwd`) runs the delegate in an existing
|
|
@@ -310,10 +761,12 @@ is `/Users/me/.pi/agent/squire`, the matching relative override resolves to
|
|
|
310
761
|
`/Users/me/.pi/agent/special-checkouts/feature-topic`. The Git branch remains
|
|
311
762
|
`feature/topic`.
|
|
312
763
|
|
|
313
|
-
|
|
314
|
-
built-in default;
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
317
770
|
not choose a source checkout or base commit. Pi discovers `AGENTS.md`, skills
|
|
318
771
|
and other project context from the created directory and its ancestors, so the
|
|
319
772
|
chosen layout determines which instructions and skills apply. For a layout
|
|
@@ -372,9 +825,12 @@ command, and nothing acts on it.
|
|
|
372
825
|
|
|
373
826
|
### Project trust
|
|
374
827
|
|
|
375
|
-
The delegate's trust flag
|
|
376
|
-
|
|
377
|
-
|
|
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`.
|
|
378
834
|
|
|
379
835
|
1. A directory in the parent's own repository, by Git's common directory, so
|
|
380
836
|
including its linked worktrees, gets the parent's current trust, even a
|
|
@@ -392,8 +848,9 @@ uses the parent's current trust for that repository. The model has no trust inpu
|
|
|
392
848
|
(`.pi/settings.json`, extensions, skills and the like, or an
|
|
393
849
|
`.agents/skills` directory above it), the delegate starts with
|
|
394
850
|
`--no-approve` and no question.
|
|
395
|
-
4. Otherwise the
|
|
396
|
-
it is the key, and what each
|
|
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:
|
|
397
854
|
`Trust`, `Trust parent folder (<folder>)` (left out at the filesystem
|
|
398
855
|
root), `Trust (this session only)`, `Do not trust` and
|
|
399
856
|
`Do not trust (this session only)`. The two "this session only" answers
|
|
@@ -404,9 +861,52 @@ uses the parent's current trust for that repository. The model has no trust inpu
|
|
|
404
861
|
the turn or tool call stops a launch that is waiting for or answering a
|
|
405
862
|
question; nothing is saved, sent or opened.
|
|
406
863
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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.
|
|
410
910
|
|
|
411
911
|
## Agents
|
|
412
912
|
|
|
@@ -432,18 +932,31 @@ optional, and the [first-use example](#first-use) is a complete agent file:
|
|
|
432
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. |
|
|
433
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. |
|
|
434
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. |
|
|
435
|
-
| `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
|
|
436
|
-
| `extensions` | Extension paths only this agent's delegate loads (see below). |
|
|
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. |
|
|
437
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. |
|
|
438
|
-
| `
|
|
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. |
|
|
439
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. |
|
|
440
940
|
| `enabled` | `false` leaves the agent out. A project file with `enabled: false` hides a global agent of the same name in that project. |
|
|
441
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. |
|
|
442
942
|
|
|
443
|
-
Any other key, such as `max_turns`, `inherit_context
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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).
|
|
447
960
|
|
|
448
961
|
`tools` lists the delegate's tools as a comma-separated or YAML list
|
|
449
962
|
(`tools: read, grep` or `tools: [read, grep]`). Each entry of a YAML list
|
|
@@ -460,12 +973,15 @@ loadable by `tool_search`, whatever its exposure. Pi filters MCP tools by
|
|
|
460
973
|
such entry pi-squire adds the entry `mcp__`, which names no tool; an empty
|
|
461
974
|
selection is `--no-tools`, which removes MCP tools too. To give a delegate an
|
|
462
975
|
MCP server's tools, name them, for example `mcp__docs__*`. An extension that
|
|
463
|
-
turns tools on at run time can still add only tools the list allows.
|
|
464
|
-
are not checked against the parent's tools
|
|
465
|
-
|
|
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.
|
|
466
981
|
|
|
467
982
|
- No `tools` field: the delegate gets the parent session's tools as they are
|
|
468
|
-
when the launch (or resume) starts
|
|
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
|
|
469
985
|
`--tools`, plus the `delegateTools` entries. A parent limited to `read`
|
|
470
986
|
gives such a delegate only `read`.
|
|
471
987
|
- An empty `tools` field, `tools:` or `tools: []`: the delegate gets no tools
|
|
@@ -480,18 +996,14 @@ agent's own `extensions` exists only in the child.
|
|
|
480
996
|
- A `+name` entry, or tool names and `-name` entries in one list, make the
|
|
481
997
|
file invalid, and the agent is disabled (see below).
|
|
482
998
|
|
|
483
|
-
The tools the agent's own `extensions` register are never in the parent's
|
|
484
|
-
tools, and pi-squire does not load those extensions in the parent to find
|
|
485
|
-
them. Such a tool reaches the delegate only when a plain `tools` list or the
|
|
486
|
-
`delegateTools` setting names it (or matches it with a `*` pattern); with no
|
|
487
|
-
`tools` field, or only `-name` entries, the delegate does not get it.
|
|
488
|
-
|
|
489
999
|
The delegation tools (`delegate`, `delegation_status`, `delegation_close`,
|
|
490
|
-
`delegation_resume`) are
|
|
491
|
-
|
|
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).
|
|
492
1004
|
|
|
493
1005
|
The delegate's result reaches the parent through the `message:*` provider's
|
|
494
|
-
reply when
|
|
1006
|
+
reply hook when squire settles the task, not through a tool, so a short `tools` list cannot
|
|
495
1007
|
stop it answering. To let a delegate message its parent mid-task, whatever its
|
|
496
1008
|
`tools`, list the tools your `message:*` provider gives a session for that in
|
|
497
1009
|
the `delegateTools` setting, in `<agent dir>/squire/settings.json` beside
|
|
@@ -503,20 +1015,14 @@ the `delegateTools` setting, in `<agent dir>/squire/settings.json` beside
|
|
|
503
1015
|
|
|
504
1016
|
Each entry, a tool name or `*` pattern, is added to every delegate's tools,
|
|
505
1017
|
whether they come from its agent's `tools` or from the parent (after any
|
|
506
|
-
`-name` entries are taken off)
|
|
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
|
|
507
1021
|
is empty by default, is read at each launch and resume, and a value that is
|
|
508
1022
|
not a list of names, or an entry that holds a comma or starts with `+` or
|
|
509
1023
|
`-`, refuses every launch with an error naming the file, before the task is sent or a window
|
|
510
1024
|
opens; it does not disable agents one by one.
|
|
511
1025
|
|
|
512
|
-
`extensions` lists extension paths only this agent's delegate loads, as a
|
|
513
|
-
comma-separated or YAML list. Each reaches the child as a repeated
|
|
514
|
-
`-e <absolute path>`, on top of its normal extensions, so pi-squire and the
|
|
515
|
-
`message:*` provider still load and the result still reaches the parent. `~`
|
|
516
|
-
and `~/` expand to the home directory and a relative path resolves against the
|
|
517
|
-
folder holding the agent file. A path that does not exist when the roster is
|
|
518
|
-
read leaves the agent out, with a warning.
|
|
519
|
-
|
|
520
1026
|
A `model` or `fallback` entry that is an exact `provider/id` in Pi's registry
|
|
521
1027
|
is checked as before: a model without credentials or with an active
|
|
522
1028
|
usage-limit mark is skipped for the next candidate. Anything else, such as an
|
|
@@ -539,11 +1045,10 @@ the prompt exactly as every delegate was built before `prompt_mode` existed.
|
|
|
539
1045
|
|
|
540
1046
|
An agent file that cannot be used is invalid, and its agent is disabled:
|
|
541
1047
|
frontmatter that is not readable YAML or not a set of fields, an `enabled` or
|
|
542
|
-
`
|
|
543
|
-
|
|
544
|
-
entries mixed, a `tools` or `fallback` YAML list entry holding a comma, a
|
|
545
|
-
`model` or `fallback` entry with a `*` pattern
|
|
546
|
-
does not exist. A disabled agent is left
|
|
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
|
|
547
1052
|
out of the roster and of the `delegate` tool's description, so the model never
|
|
548
1053
|
picks it, and a warning names the file and the reason when the session starts
|
|
549
1054
|
(and before a run, when a file turns invalid while the session runs). A call
|
|
@@ -558,13 +1063,16 @@ add one.
|
|
|
558
1063
|
## Configuration
|
|
559
1064
|
|
|
560
1065
|
`<agent dir>/squire/` holds the optional parent guidance file and
|
|
561
|
-
`settings.json` (`
|
|
1066
|
+
`settings.json` (`maxDepth`, `maxConcurrentDelegates`, `maxDelegatesPerRoot`,
|
|
1067
|
+
`windowPlacement`, `worktreeRoot`, `delegateTools`, `delegateWidgetPlacement` and
|
|
1068
|
+
`delegateWidgetMaxEntries`); the
|
|
562
1069
|
agents live in `<agent dir>/agents/` (see [Agents](#agents)).
|
|
563
1070
|
`delegation_prompt.md` is Markdown, inserted unchanged as the
|
|
564
1071
|
`squire_parent_delegation` system-prompt section before each new run, including
|
|
565
1072
|
a run started by an idle delegate result. It is not a conversation or user
|
|
566
1073
|
message, is not duplicated in the `delegate` tool description or guidelines,
|
|
567
|
-
and is not
|
|
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`
|
|
568
1076
|
tool is active and not hidden; when delegation is inactive or hidden, the
|
|
569
1077
|
section is removed and tool selection is left as it was. A missing or whitespace-only
|
|
570
1078
|
file adds no section. Editing the file replaces the section at the next run;
|
|
@@ -574,12 +1082,21 @@ valid delegation working. The package ships no default delegation or approval
|
|
|
574
1082
|
policy.
|
|
575
1083
|
|
|
576
1084
|
Refresh happens before a new run, not before every model request in a tool
|
|
577
|
-
loop, and the extension does not watch the filesystem. `pi-squire`
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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`.
|
|
583
1100
|
`PI_UNATTENDED=1` is a general signal, not specific to pi-squire: no human is
|
|
584
1101
|
watching this session, even though it runs in a TUI. Other extensions may read
|
|
585
1102
|
it to skip a dialog or confirmation instead of waiting on one, and any other
|
|
@@ -592,18 +1109,19 @@ is refused with close matches, and so is a model with no configured
|
|
|
592
1109
|
credentials. An API key that exists only as an environment variable in the
|
|
593
1110
|
parent's shell does not reach the delegate, so log in through Pi or make the
|
|
594
1111
|
variable part of the tmux server's environment. `--auto-exit` and `--no-auto-exit` override the
|
|
595
|
-
agent's `
|
|
1112
|
+
agent's `auto_exit`.
|
|
596
1113
|
|
|
597
1114
|
## Tools
|
|
598
1115
|
|
|
599
1116
|
The model gets four tools. They share the command's launch path and the
|
|
600
|
-
session's delegation records.
|
|
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.
|
|
601
1119
|
|
|
602
1120
|
| Tool | Parameters | What it does |
|
|
603
1121
|
| --- | --- | --- |
|
|
604
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. |
|
|
605
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. |
|
|
606
|
-
| `delegation_close` | `id` |
|
|
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. |
|
|
607
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)). |
|
|
608
1126
|
|
|
609
1127
|
The `delegate` tool's description lists the current roster, one line per agent:
|
|
@@ -707,10 +1225,63 @@ even when no delegation is recorded. The details gain a `marks` array of
|
|
|
707
1225
|
`{ scope, source, reason, clearsAt }` and a `readingErrors` array (see
|
|
708
1226
|
[Usage limits](#usage-limits)).
|
|
709
1227
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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.
|
|
714
1285
|
|
|
715
1286
|
## Usage limits
|
|
716
1287
|
|
|
@@ -812,17 +1383,20 @@ mark, it changes nothing and lists the active marks. No tool clears marks.
|
|
|
812
1383
|
|
|
813
1384
|
The parent resolves each delegation's auto-exit from the call
|
|
814
1385
|
(`auto_exit`, or `--auto-exit` / `--no-auto-exit`), then the agent's
|
|
815
|
-
`
|
|
1386
|
+
`auto_exit`, then on, and passes it to the child as `PI_DELEGATE_AUTO_EXIT`
|
|
816
1387
|
(`1` or `0`). Inside the delegate:
|
|
817
1388
|
|
|
818
1389
|
- While it is on, a run that settles as a normal completion shuts the delegate
|
|
819
1390
|
down (`ctx.shutdown()`) on the next event-loop turn, after the settle
|
|
820
1391
|
reply has been sent, so its tmux window closes. The session file stays, and
|
|
821
1392
|
`pi --session <path>` reopens it (see [Reopening a delegate's session](#reopening-a-delegates-session)).
|
|
822
|
-
- A failed run, such as one ending on a usage limit or another provider
|
|
823
|
-
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
|
|
824
1395
|
reply and the session file stays. Only a run the user stopped never exits
|
|
825
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.
|
|
826
1400
|
- The user taking over turns it off for the rest of the session, with a
|
|
827
1401
|
notice: typed or RPC input, or a run the user stopped (Esc).
|
|
828
1402
|
- `/auto-exit` inside the delegate turns it on again for the next normal
|
|
@@ -830,7 +1404,8 @@ The parent resolves each delegation's auto-exit from the call
|
|
|
830
1404
|
- The footer shows that this is a delegate, its agent, and whether auto-exit
|
|
831
1405
|
is on: `👦 worker delegate · auto-exit on`, or
|
|
832
1406
|
`👦 worker delegate · auto-exit off`, with 👦 or 👧
|
|
833
|
-
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.
|
|
834
1409
|
|
|
835
1410
|
There is no `subagent_done` tool: auto-exit off only means the window stays.
|
|
836
1411
|
|
|
@@ -881,10 +1456,12 @@ Delegate session: <session file or id>
|
|
|
881
1456
|
5 non-blank lines with heading marks, backticks and bold stripped;
|
|
882
1457
|
expanded (Ctrl+O), it shows the result as Markdown, the quoted task, then
|
|
883
1458
|
the ids and files, with the home directory shown as `~`.
|
|
884
|
-
- Stopping a delegate's run (Esc) sends no result:
|
|
1459
|
+
- Stopping a delegate's run (Esc) sends no result: pi-squire holds the
|
|
885
1460
|
task, so the delegation stays running while the user steers it. The next
|
|
886
|
-
run that completes sends the result, `done`, and its body opens
|
|
887
|
-
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.
|
|
888
1465
|
- A `failed` reply from a delegate that recorded a usage-limit mark (see
|
|
889
1466
|
[Usage limits](#usage-limits)) gets one more line right after `Status:`,
|
|
890
1467
|
for example `Usage limit: the delegate hit a usage limit on provider
|
|
@@ -915,15 +1492,26 @@ Delegate session: <session file or id>
|
|
|
915
1492
|
current tool calls, before the next model call. A follow-up would wait
|
|
916
1493
|
until the parent had answered, by which time it had often read the
|
|
917
1494
|
envelope itself, and the late result cost a second, redundant turn.
|
|
918
|
-
- The
|
|
919
|
-
|
|
920
|
-
|
|
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.
|
|
921
1500
|
- Each result and each close is recorded in the parent session, and the
|
|
922
1501
|
records are rebuilt synchronously at session start. A resume still
|
|
923
1502
|
recognises replies, including one that arrived while the parent was
|
|
924
1503
|
closed, and still reports a recorded close as closed. A result is shown
|
|
925
|
-
once
|
|
926
|
-
|
|
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.
|
|
927
1515
|
|
|
928
1516
|
While any delegation is running (no result, no close), the parent polls every
|
|
929
1517
|
5 seconds; the timer does not keep the process alive and stops when nothing is
|
|
@@ -935,7 +1523,8 @@ gone. A delegation whose window is gone and still has no result after that
|
|
|
935
1523
|
scan is recorded as closed without a result, a record separate from
|
|
936
1524
|
`delegation_close`'s, and the parent gets one `[delegate]` message naming the
|
|
937
1525
|
delegation, with the delegate session path when one is found, sent as a
|
|
938
|
-
`followUp` with `triggerTurn
|
|
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
|
|
939
1528
|
`message:scan` or when tmux reports an error. A reply that arrives later is
|
|
940
1529
|
still delivered and makes the delegation done. A resumed session polls again
|
|
941
1530
|
for every delegation it restores as running.
|
|
@@ -952,6 +1541,13 @@ absolute path opens that file directly.
|
|
|
952
1541
|
|
|
953
1542
|
## Resuming a delegation
|
|
954
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
|
+
|
|
955
1551
|
An exited delegate can continue its own session with a follow-up task:
|
|
956
1552
|
|
|
957
1553
|
```text
|
|
@@ -987,8 +1583,10 @@ and it resumes as the command does.
|
|
|
987
1583
|
opened would reach the next resume as a second task. The reopened delegate
|
|
988
1584
|
receives it once its mailbox starts. If the send fails, the new window is
|
|
989
1585
|
killed and nothing is recorded.
|
|
990
|
-
-
|
|
991
|
-
|
|
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.
|
|
992
1590
|
- It is refused, before anything is sent or opened, unless: the delegation is a
|
|
993
1591
|
started one (not a workspace attempt); it is not `running` (mail a running
|
|
994
1592
|
delegate instead) and tmux can be asked; no window with its id and name still
|
|
@@ -997,7 +1595,8 @@ and it resumes as the command does.
|
|
|
997
1595
|
file's header still exists; its agent is still in the roster and not disabled
|
|
998
1596
|
(a disabled agent's refusal names its file and the reason); and no other
|
|
999
1597
|
resume of it is under way. A delegate whose window was killed mid-turn can be
|
|
1000
|
-
resumed
|
|
1598
|
+
resumed after a completed close; an unresolved active subtree close refuses
|
|
1599
|
+
resume until positively resolved by an explicit retry.
|
|
1001
1600
|
- A removed working directory, a removed managed worktree included, makes a
|
|
1002
1601
|
delegation not resumable: Pi runs a reopened session in its stored directory.
|
|
1003
1602
|
The window opens in that directory, with project trust decided again for it as
|
|
@@ -1014,7 +1613,7 @@ and it resumes as the command does.
|
|
|
1014
1613
|
field the agent locks is the exception: it runs on the
|
|
1015
1614
|
agent file's current `model` (then its fallbacks, even when the delegate had
|
|
1016
1615
|
moved to a fallback) or `thinking`, a value passed for it is not used, with
|
|
1017
|
-
a notice, and passing the file's own value is the same as passing nothing. The agent's prompt
|
|
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
|
|
1018
1617
|
roster now, and an agent without a `tools` field gets the parent's tools as
|
|
1019
1618
|
they are at the resume.
|
|
1020
1619
|
- `session:launch` is emitted as for any launch, with the same payload.
|
|
@@ -1031,7 +1630,8 @@ that block.
|
|
|
1031
1630
|
|
|
1032
1631
|
### `session:launch`
|
|
1033
1632
|
|
|
1034
|
-
`pi-squire` provides this hook. It emits
|
|
1633
|
+
`pi-squire` provides this hook. It emits
|
|
1634
|
+
`{ args, env, agent, cwd, toolCallId, parent, root, depth, chain }`
|
|
1035
1635
|
just before it opens the delegate's window: for a launch, after the task has
|
|
1036
1636
|
been sent; for a resume, before it is sent, since a resume mails the task once
|
|
1037
1637
|
the window is open (see [Resuming a delegation](#resuming-a-delegation)).
|
|
@@ -1043,10 +1643,25 @@ the window is open (see [Resuming a delegation](#resuming-a-delegation)).
|
|
|
1043
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 |
|
|
1044
1644
|
| `cwd` | the directory the window opens in, the parent's or the resolved `--cwd`; read it, do not change it |
|
|
1045
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. |
|
|
1046
1650
|
|
|
1047
1651
|
Listeners may only append to `args` and add keys to `env`; there is no veto,
|
|
1048
|
-
and
|
|
1049
|
-
|
|
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.
|
|
1050
1665
|
|
|
1051
1666
|
`cwd`, the trust flag (`--approve` or `--no-approve`), `--session-dir` and the
|
|
1052
1667
|
session choice (`--session-id` for a new delegate, `--session` for a [resumed](#resuming-a-delegation)
|
|
@@ -1063,6 +1678,121 @@ pi.events.on("session:launch", (payload) => {
|
|
|
1063
1678
|
payload.args.push("--auto");
|
|
1064
1679
|
payload.env.GATE_MODE = "auto";
|
|
1065
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");
|
|
1066
1796
|
});
|
|
1067
1797
|
```
|
|
1068
1798
|
|
|
@@ -1075,6 +1805,66 @@ and `pi-squire` refuses with a message naming `message:*`. The envelope's `id`
|
|
|
1075
1805
|
is kept in the delegation's record, so an answer can be matched to it. The
|
|
1076
1806
|
provider's own README carries the contract.
|
|
1077
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
|
+
|
|
1078
1868
|
### `message:inbound`
|
|
1079
1869
|
|
|
1080
1870
|
`pi-squire` consumes this hook for answers: a reply whose `in_reply_to` names a
|
|
@@ -1087,8 +1877,10 @@ to the provider.
|
|
|
1087
1877
|
Inside a delegate, `pi-squire` also takes over the task: a `request` whose
|
|
1088
1878
|
`from` is the parent's address. It sets `handled` and sends the task as a user
|
|
1089
1879
|
prompt labelled `[delegate]`, so the run starts the way a typed prompt does.
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
|
1092
1884
|
it to be the sender's address, which for a delegate is its session id; a reply
|
|
1093
1885
|
from any other sender is left to the provider, even when it answers a recorded
|
|
1094
1886
|
request. The provider's README carries the contract.
|
|
@@ -1114,7 +1906,12 @@ contract.
|
|
|
1114
1906
|
|
|
1115
1907
|
## Testing limits
|
|
1116
1908
|
|
|
1117
|
-
The automated tests drive the extension through
|
|
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
|
|
1118
1915
|
the registered tool definitions, the structured prompt options a simulated run
|
|
1119
1916
|
preparation receives, file handling, visibility gating and the existing launch
|
|
1120
1917
|
and result paths. They do not prove actual Pi event scheduling, final provider
|
|
@@ -1137,12 +1934,13 @@ version and is listed under "Breaking" in [CHANGELOG.md](CHANGELOG.md).
|
|
|
1137
1934
|
|
|
1138
1935
|
Other extensions may rely on:
|
|
1139
1936
|
|
|
1140
|
-
- the `session:launch` payload
|
|
1937
|
+
- the `session:launch` payload
|
|
1938
|
+
`{ args, env, agent, cwd, toolCallId, parent, root, depth, chain }`, emitted
|
|
1141
1939
|
synchronously before the window opens (after the task is sent for a launch,
|
|
1142
1940
|
before it is sent for a resume), with
|
|
1143
1941
|
`cwd`, the trust flag and `--session-dir` chosen by `pi-squire` (see
|
|
1144
1942
|
[Hooks](#hooks));
|
|
1145
|
-
- 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
|
|
1146
1944
|
their sections document;
|
|
1147
1945
|
- the `delegate`, `delegation_status`, `delegation_close` and `delegation_resume` tool names,
|
|
1148
1946
|
parameters, the recorded `workspace` shape and the `workspaceSnapshot`
|
|
@@ -1156,21 +1954,24 @@ Other extensions may rely on:
|
|
|
1156
1954
|
- the agent file's frontmatter keys and its locations,
|
|
1157
1955
|
`<agent dir>/agents/<name>.md` and, in a trusted project,
|
|
1158
1956
|
`<cwd>/.pi/agents/<name>.md`;
|
|
1159
|
-
- the `
|
|
1957
|
+
- the `maxDepth`, `maxConcurrentDelegates`, `maxDelegatesPerRoot`, `delegateTools`,
|
|
1958
|
+
`windowPlacement`, `worktreeRoot`, `delegateWidgetPlacement` and
|
|
1959
|
+
`delegateWidgetMaxEntries` settings in
|
|
1160
1960
|
`<agent dir>/squire/settings.json`;
|
|
1161
1961
|
- the optional `<agent dir>/squire/delegation_prompt.md` file and the
|
|
1162
1962
|
`squire_parent_delegation` system-prompt section;
|
|
1163
|
-
- the child environment variables `PI_DELEGATE_PARENT`,
|
|
1164
|
-
`
|
|
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`.
|
|
1165
1966
|
|
|
1166
1967
|
A `message:*` provider must:
|
|
1167
1968
|
|
|
1168
|
-
- implement `message:send`, `message:inbound` and `message:scan`
|
|
1169
|
-
under [Hooks](#hooks);
|
|
1969
|
+
- implement `message:send`, `message:reply`, `message:inbound` and `message:scan`
|
|
1970
|
+
as documented under [Hooks](#hooks);
|
|
1170
1971
|
- set a reply's `from` to the sender's address, so the delegate's reply can be
|
|
1171
1972
|
matched to its task;
|
|
1172
|
-
-
|
|
1173
|
-
|
|
1973
|
+
- write explicit request replies through `message:reply`, without automatically
|
|
1974
|
+
answering requests at settle; asks remain separately owned by the provider.
|
|
1174
1975
|
|
|
1175
1976
|
## Contributing
|
|
1176
1977
|
|