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