@cr1ms0n/pi-subagent 0.8.8 → 0.8.9
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 +3 -5
- package/README.md +7 -20
- package/docs/ARCHITECTURE.md +125 -132
- package/docs/SECURITY.md +88 -97
- package/package.json +1 -1
- package/skills/subagent/SKILL.md +113 -121
- package/src/agents.ts +282 -288
- package/src/extension.ts +6 -33
- package/src/orchestrator.ts +247 -312
- package/src/policy.ts +531 -561
- package/src/schema.ts +189 -189
- package/src/context-policy.ts +0 -169
package/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
## 0.8.
|
|
5
|
+
## 0.8.9 — 2026-09-16
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### Restore the stable 0.8.7 behavior
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- Matching is exact and case-sensitive on the full `provider/model` string; prefixes, bare model ids, and native `openai-codex` identity do not match. Explicit requests for a context tool on a non-eligible target are rejected as unavailable, inherited/default lists are filtered, and retry fallback attempts are re-filtered per attempt so a disallowed fallback cannot inherit them. The internally constructed synthesis child uses the same gate.
|
|
11
|
-
- Missing, unreadable, invalid, or non-`remote` toolkit configuration now fails closed to no context tools. Non-Pi backends, read-only/write classification, and model-policy routing are unchanged. The package reads the existing operator-owned allowlist and does not duplicate it in `~/.pi/subagent.json`.
|
|
9
|
+
- Restore the `0.8.7` source behavior after the withdrawn `0.8.8` release. Remote Context ownership and model gating remain in the separate `pi-openai-toolkit` project.
|
|
12
10
|
|
|
13
11
|
## 0.8.7 — 2026-09-14
|
|
14
12
|
|
package/README.md
CHANGED
|
@@ -193,30 +193,17 @@ Set a persona's backend in agent frontmatter with `backend: codex`.
|
|
|
193
193
|
|
|
194
194
|
| Profile | Tools | Writes |
|
|
195
195
|
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------- |
|
|
196
|
-
| `explore` (parallel default) | read/grep/find/ls + safe extras +
|
|
196
|
+
| `explore` (parallel default) | read/grep/find/ls + safe extras + Pi context tools | no project-file writes |
|
|
197
197
|
| `review` | same as explore | no project-file writes |
|
|
198
|
-
| `general` | inherited active tools +
|
|
198
|
+
| `general` | inherited active tools + Pi context tools | yes if tools include bash/edit/write |
|
|
199
199
|
|
|
200
200
|
For the Pi backend, the context-management tools `new_context`,
|
|
201
201
|
`get_context_remaining`, `history`, and `notes` are retained in child tool
|
|
202
|
-
allowlists
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
tool. Eligibility is exact and case-sensitive on the full `provider/model`
|
|
208
|
-
string — prefixes, bare model ids, and native `openai-codex` identity do not
|
|
209
|
-
match. The package reads that operator-owned list and never duplicates it.
|
|
210
|
-
|
|
211
|
-
Context tools are control-plane tools: they may update continuity notes or the
|
|
212
|
-
remote context window, but cannot modify the child checkout or run a shell
|
|
213
|
-
command. For an eligible target they are appended even when the task supplies a
|
|
214
|
-
narrower tool list; for every other Pi target they are removed from inherited
|
|
215
|
-
tools, never passed to `--tools`, and an explicit request for one is rejected as
|
|
216
|
-
unavailable. Retry fallback attempts are filtered per attempt, so a disallowed
|
|
217
|
-
fallback cannot inherit them. If the toolkit file is missing, unreadable,
|
|
218
|
-
invalid, or not in Remote Context mode, the effective allowlist is empty (fail
|
|
219
|
-
closed). Non-Pi backends are unchanged.
|
|
202
|
+
allowlists when the parent exposes them. They are control-plane tools: they may
|
|
203
|
+
update continuity notes or the remote context window, but cannot modify the
|
|
204
|
+
child checkout or run a shell command. This exception also applies when a task
|
|
205
|
+
supplies a narrower tool list, so Pi's `contextManagement` remains usable for
|
|
206
|
+
configured gateway models.
|
|
220
207
|
|
|
221
208
|
Parallel write-capable tasks sharing one checkout are rejected unless each uses
|
|
222
209
|
`isolation: "worktree"`, distinct `cwd`, or explicit `allow_shared_writes: true`.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,132 +1,125 @@
|
|
|
1
|
-
# Architecture contract
|
|
2
|
-
|
|
3
|
-
`pi-subagent` is split by ownership boundary:
|
|
4
|
-
|
|
5
|
-
- `runner.ts`: one child process, Pi RPC protocol (JSONL commands on stdin, events on
|
|
6
|
-
stdout — a superset of `--mode json`), cancellation, process trees, budgets, and a live
|
|
7
|
-
stdin command channel used for mid-run steering. Extension UI dialogs from headless
|
|
8
|
-
children are auto-cancelled so they can never hang a run; stdin is closed after
|
|
9
|
-
`agent_settled` so RPC children shut down cleanly. Budget breaches steer a wrap-up
|
|
10
|
-
message and allow grace turns before SIGTERM (`wrappedUp` marks a clean conclusion).
|
|
11
|
-
A stall watchdog flags protocol silence, probes liveness via `get_state`, and kills
|
|
12
|
-
after a second window so retry can take over. Group kills verify process start-time
|
|
13
|
-
identity (Linux `/proc`, macOS/BSD `ps lstart`) before signalling a possibly-recycled
|
|
14
|
-
PID; transcript joins happen only on message boundaries, not per-chunk ticks.
|
|
15
|
-
- Retry with model fallback lives in `orchestrator.ts` (`isTransientFailure`): queue
|
|
16
|
-
timeouts, stalls, spawn errors, and provider errors re-run the same spec on the next
|
|
17
|
-
fallback model with accumulated usage; task-quality failures never retry.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- `
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
- `
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- `
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
`
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
`
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
27. Global slots are depth-tiered: `tryAcquireGlobalSlot(runId, depth)` admits only while
|
|
127
|
-
`activeAtOrBelowDepth(depth) < maxGlobalActive - reservedFor(depth)`, holding slots
|
|
128
|
-
back for deeper tiers so a full-width spawn tree cannot deadlock on its own children.
|
|
129
|
-
Slot records without a `depth` field count as depth 0.
|
|
130
|
-
28. `action: "plan"` is a truth oracle: it runs the exact validation and preflights of a
|
|
131
|
-
real spawn and returns the resolved plan without spawning — never a softer check, and
|
|
132
|
-
never a registry entry.
|
|
1
|
+
# Architecture contract
|
|
2
|
+
|
|
3
|
+
`pi-subagent` is split by ownership boundary:
|
|
4
|
+
|
|
5
|
+
- `runner.ts`: one child process, Pi RPC protocol (JSONL commands on stdin, events on
|
|
6
|
+
stdout — a superset of `--mode json`), cancellation, process trees, budgets, and a live
|
|
7
|
+
stdin command channel used for mid-run steering. Extension UI dialogs from headless
|
|
8
|
+
children are auto-cancelled so they can never hang a run; stdin is closed after
|
|
9
|
+
`agent_settled` so RPC children shut down cleanly. Budget breaches steer a wrap-up
|
|
10
|
+
message and allow grace turns before SIGTERM (`wrappedUp` marks a clean conclusion).
|
|
11
|
+
A stall watchdog flags protocol silence, probes liveness via `get_state`, and kills
|
|
12
|
+
after a second window so retry can take over. Group kills verify process start-time
|
|
13
|
+
identity (Linux `/proc`, macOS/BSD `ps lstart`) before signalling a possibly-recycled
|
|
14
|
+
PID; transcript joins happen only on message boundaries, not per-chunk ticks.
|
|
15
|
+
- Retry with model fallback lives in `orchestrator.ts` (`isTransientFailure`): queue
|
|
16
|
+
timeouts, stalls, spawn errors, and provider errors re-run the same spec on the next
|
|
17
|
+
fallback model with accumulated usage; task-quality failures never retry.
|
|
18
|
+
- `context: "fork"` spawns the child with `--fork <parent session file>` so it starts
|
|
19
|
+
from a real branched copy of the parent conversation. Fail-fast when the parent
|
|
20
|
+
session is not persisted; single-task only.
|
|
21
|
+
- `registry.ts`: one parent-session runtime, run state, snapshots, resume locks, and the
|
|
22
|
+
single LiveRun→snapshot/persisted-result projections used by every consumer.
|
|
23
|
+
- `semaphore.ts`: per-parent-runtime child-process limit.
|
|
24
|
+
- `process-lock.ts`: machine-wide durable coordination under `~/.pi/subagent-locks/` —
|
|
25
|
+
exclusive per-child-session resume locks, global concurrency slots, and run process
|
|
26
|
+
identity records for orphan reconcile.
|
|
27
|
+
- `launch.ts`: resolve the child `pi` invocation via `PI_SUBAGENT_BIN` or
|
|
28
|
+
`process.execPath` + CLI entry (bare PATH name only as last-resort fallback).
|
|
29
|
+
- `persistence.ts`: versioned active-branch event folding and bounded child transcript metadata.
|
|
30
|
+
- `maintenance.ts`: filesystem GC (session files) and abort-race helpers; kept out of persistence.
|
|
31
|
+
- `usage.ts`: provider-reported root/subagent/combined accounting.
|
|
32
|
+
- `policy.ts` / `schema.ts`: discriminated request validation and safe capability profiles. `schema.ts` retains the canonical TypeBox validators and derives provider-safe tool-schema projections; `extension.ts` registers those projections while validating calls with the originals. Pi context-management control-plane tools remain available to child allowlists without granting project-file write access.
|
|
33
|
+
- `config.ts`: defaults ← `~/.pi/subagent.json` ← `PI_SUBAGENT_*` env overrides.
|
|
34
|
+
- `structured.ts`: structured-output contract (dependency-free JSON-Schema subset
|
|
35
|
+
validation, fenced json:result extraction, contract/repair prompts) and
|
|
36
|
+
conservative double-encoded-arg repair. The runner gates the child's settle on
|
|
37
|
+
validation and runs one steer-based repair round before accepting failure.
|
|
38
|
+
- `agents.ts`: named agent files (`.pi/agents/`, `.agents/agents/`, global agent dir).
|
|
39
|
+
Flat-YAML frontmatter + markdown persona body; resolved in policy with explicit
|
|
40
|
+
params > agent file > profile taskDefaults > parent inheritance. Catalog refreshes
|
|
41
|
+
lazily (5s TTL) so new files work mid-session; symlinks and oversized files skipped.
|
|
42
|
+
- `notifications.ts`: background-run completion batching. Successes group within a
|
|
43
|
+
debounce window (hard cap on hold time); failures bypass batching and flush
|
|
44
|
+
immediately; delivered-state is re-checked at flush time so a consuming `wait`
|
|
45
|
+
suppresses the redundant notification.
|
|
46
|
+
- `ui.ts`: renderers, footer status and `/subagents` inspector. The ambient widget
|
|
47
|
+
(extension-side) shows BACKGROUND runs only — foreground runs render inline as the
|
|
48
|
+
tool result, so widget display would double-render them.
|
|
49
|
+
- `extension.ts`: wiring only; no business logic. Nested children at the depth ceiling do
|
|
50
|
+
not re-register the tool; only top-level parents run maintenance.
|
|
51
|
+
|
|
52
|
+
Invariants:
|
|
53
|
+
|
|
54
|
+
1. A run belongs to exactly one parent session key and cannot update another session.
|
|
55
|
+
2. No more than `maxActiveProcesses` children run per extension runtime, and no more than
|
|
56
|
+
`maxGlobalActive` across every Pi parent process on the machine.
|
|
57
|
+
3. Cancellation prevents queued tasks from spawning.
|
|
58
|
+
4. A child session may have only one direct resume writer at a time, enforced by an
|
|
59
|
+
in-memory lock *and* a durable file lock under `lockDir` that survives crashes and
|
|
60
|
+
coordinates across independent parent processes.
|
|
61
|
+
5. Parallel write-capable tasks need isolated worktrees/distinct cwd or explicit unsafe opt-in.
|
|
62
|
+
6. Every tool response is globally capped to 50 KB / 2,000 lines; full data lives in artifacts/transcripts.
|
|
63
|
+
7. Status is compact; wait is the one-shot deliverable.
|
|
64
|
+
8. On shutdown or tree navigation, child runs are cancelled and awaited for a bounded grace period.
|
|
65
|
+
9. On startup, orphan process groups recorded under `lockDir` are reaped (SIGTERM then SIGKILL)
|
|
66
|
+
before any matching child session is eligible for resume. `$state: "lost"` is a labeling
|
|
67
|
+
that keeps `resumeBlocked` until reconciliation proves death.
|
|
68
|
+
10. Billed usage is folded once per root message and once per full child run UUID.
|
|
69
|
+
11. Checkpoint persistence events are lightweight (state, usage, process identity, pointers).
|
|
70
|
+
Full transcripts and final output are persisted exactly once, in the terminal event.
|
|
71
|
+
12. High-frequency registry "changed" events coalesce (trailing window); state transitions,
|
|
72
|
+
new child sessions, billed-usage advances, and terminal events flush immediately.
|
|
73
|
+
13. `wait` is interruptible: aborting a wait returns promptly and does NOT cancel the
|
|
74
|
+
background run. Only `cancel` (or parent shutdown) aborts a run.
|
|
75
|
+
14. Budget stops (`max_turns`/`max_cost`) with at least one completed turn end as `partial`
|
|
76
|
+
and deliver their output normally. Streams truncated after useful assistant output also
|
|
77
|
+
end as `partial`. Timeouts report `state: "timeout"` with `timeoutPhase`.
|
|
78
|
+
15. `timeout_ms` covers the whole task, including semaphore queue time, but the phase
|
|
79
|
+
(queued / starting / running) is recorded so agents can apply the right retry policy.
|
|
80
|
+
16. Worktrees live under a durable root (`~/.pi/subagent-worktrees`), never a purgeable OS
|
|
81
|
+
tmpdir. Startup maintenance (top-level parents only) prunes stale git registrations,
|
|
82
|
+
removes unchanged leftovers, and sweeps changed-but-expired worktrees. Live-run
|
|
83
|
+
worktrees are always shielded. `include_wip` worktrees carry the parent's WIP patch in
|
|
84
|
+
the handle: `diff`/`apply` subtract it when subtraction is clean and otherwise report
|
|
85
|
+
the combined delta with an explicit `[includes parent WIP]` warning — never silently
|
|
86
|
+
wrong; a worktree containing only the untouched WIP patch counts as unchanged.
|
|
87
|
+
17. Process-tree reaping after a clean exit can be disabled per task with `keep_background`
|
|
88
|
+
(for legitimately backgrounded work such as dev servers); forced stops always reap.
|
|
89
|
+
18. Protocol completion prefers Pi's `agent_settled` event. Legacy `agent_end` without
|
|
90
|
+
`willRetry` is accepted for older Pi builds; `agent_end` with `willRetry: true` is
|
|
91
|
+
treated as non-terminal.
|
|
92
|
+
19. Depth and spawn-policy parsing fail closed on malformed values: env scrubbing cannot
|
|
93
|
+
silently reset the depth counter to top-level, and a malformed `PI_SUBAGENT_SPAWNS`
|
|
94
|
+
disables spawning rather than unrestricting it.
|
|
95
|
+
20. Budget breaches (`max_turns`/`max_cost`) steer a wrap-up message and allow grace
|
|
96
|
+
turns before SIGTERM; a child that concludes within grace ends `partial` with
|
|
97
|
+
`wrappedUp: true`. `graceTurns: 0` restores immediate stops.
|
|
98
|
+
21. Transient failures (queued timeout, stall, spawn error, provider error, protocol
|
|
99
|
+
truncation) retry up to `maxRetries` extra attempts, escalating through
|
|
100
|
+
`fallback_models`; usage accumulates across attempts and `attemptedModels` is
|
|
101
|
+
recorded. Task-quality failures (nonzero exit with complete protocol, cancellation,
|
|
102
|
+
budget stop, running timeout) never retry.
|
|
103
|
+
22. The stall watchdog treats protocol silence as suspect, not fatal: after
|
|
104
|
+
`stallAfterMs` the task is flagged and probed via `get_state` (a live child's
|
|
105
|
+
answer clears the flag); only continued silence for `stallKillAfterMs` more kills
|
|
106
|
+
the child — which is then a transient failure eligible for retry.
|
|
107
|
+
23. Only `async: true` runs notify on completion and appear in the ambient widget.
|
|
108
|
+
Notification delivery respects delivered-once: a `wait` that consumed the run
|
|
109
|
+
suppresses the notification.
|
|
110
|
+
24. Named agent files supply per-field defaults only; explicit request params always
|
|
111
|
+
win, and capability profiles fail closed regardless of what an agent file declares.
|
|
112
|
+
25. Structured-output validation never discards paid work: schema failure after the
|
|
113
|
+
repair round downgrades completed → partial with `structuredError`, and the raw
|
|
114
|
+
text still delivers. Validation is enforced on the parent side of the process
|
|
115
|
+
boundary — the child cannot self-attest.
|
|
116
|
+
26. Arg repair only decodes free-text fields with high-signal escape patterns
|
|
117
|
+
(literal \n or \") and no real newlines; identifier fields, tool lists, and
|
|
118
|
+
Windows-path-like strings are never modified.
|
|
119
|
+
27. Global slots are depth-tiered: `tryAcquireGlobalSlot(runId, depth)` admits only while
|
|
120
|
+
`activeAtOrBelowDepth(depth) < maxGlobalActive - reservedFor(depth)`, holding slots
|
|
121
|
+
back for deeper tiers so a full-width spawn tree cannot deadlock on its own children.
|
|
122
|
+
Slot records without a `depth` field count as depth 0.
|
|
123
|
+
28. `action: "plan"` is a truth oracle: it runs the exact validation and preflights of a
|
|
124
|
+
real spawn and returns the resolved plan without spawning — never a softer check, and
|
|
125
|
+
never a registry entry.
|
package/docs/SECURITY.md
CHANGED
|
@@ -1,97 +1,88 @@
|
|
|
1
|
-
# Security model
|
|
2
|
-
|
|
3
|
-
Pi packages run with full system access. This extension spawns child `pi`
|
|
4
|
-
processes that inherit the parent environment (including provider credentials)
|
|
5
|
-
and can use tools according to their capability profile.
|
|
6
|
-
|
|
7
|
-
## What subagents can do
|
|
8
|
-
|
|
9
|
-
| Profile | Default tools | Writes? |
|
|
10
|
-
|---------|---------------|---------|
|
|
11
|
-
| `explore` | `read`, `grep`, `find`, `ls` (+ safe extras and
|
|
12
|
-
| `review` | same as explore | No project-file writes |
|
|
13
|
-
| `general` | inherited active tools (+
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
skipped, names are validated against traversal characters, and files over
|
|
90
|
-
64KB are ignored.
|
|
91
|
-
|
|
92
|
-
## Machine-wide state
|
|
93
|
-
|
|
94
|
-
`~/.pi/subagent-locks/` holds session locks, global concurrency slots, and run
|
|
95
|
-
process identity records. It is per-user (under `$HOME`) and must not be shared
|
|
96
|
-
across untrusted users/containers without care — a compromised client could
|
|
97
|
-
interfere with lock reclaim on the same account.
|
|
1
|
+
# Security model
|
|
2
|
+
|
|
3
|
+
Pi packages run with full system access. This extension spawns child `pi`
|
|
4
|
+
processes that inherit the parent environment (including provider credentials)
|
|
5
|
+
and can use tools according to their capability profile.
|
|
6
|
+
|
|
7
|
+
## What subagents can do
|
|
8
|
+
|
|
9
|
+
| Profile | Default tools | Writes? |
|
|
10
|
+
|---------|---------------|---------|
|
|
11
|
+
| `explore` | `read`, `grep`, `find`, `ls` (+ safe extras and Pi context tools) | No project-file writes |
|
|
12
|
+
| `review` | same as explore | No project-file writes |
|
|
13
|
+
| `general` | inherited active tools (+ Pi context tools) | Yes if `bash`/`edit`/`write` are active |
|
|
14
|
+
|
|
15
|
+
Parallel mode defaults to `explore` to avoid concurrent shared writes.
|
|
16
|
+
|
|
17
|
+
## Hard rules
|
|
18
|
+
|
|
19
|
+
1. **Read-only means no project-file mutation.** `bash` can rewrite the disk and is never part of
|
|
20
|
+
an explore/review profile. Pi context-management tools (`new_context`,
|
|
21
|
+
`get_context_remaining`, `history`, `notes`) are an explicit control-plane
|
|
22
|
+
exception: they may update continuity notes/window state but cannot access
|
|
23
|
+
the project write tools.
|
|
24
|
+
2. **Parallel writers** require `isolation: "worktree"`, distinct `cwd` values,
|
|
25
|
+
or an explicit `allow_shared_writes: true` opt-in.
|
|
26
|
+
3. **Depth is capped** (`maxDepth`, default 2). Nested children at the ceiling do
|
|
27
|
+
not re-register the subagent tool. Depth is scheduling metadata — `bash` or an
|
|
28
|
+
env-scrubbing wrapper can still invoke `pi` directly, so treat it as an
|
|
29
|
+
accidental-recursion guard, not a security boundary.
|
|
30
|
+
**Spawn allowlists** (`spawns:` in agent frontmatter, env `PI_SUBAGENT_SPAWNS`)
|
|
31
|
+
refine that same guard: a child may be limited to named personas, or to none
|
|
32
|
+
(tool not registered). Like depth, this is not a sandbox — children can still
|
|
33
|
+
shell out to `pi`.
|
|
34
|
+
4. **Process caps** limit concurrency both per parent session (`maxActiveProcesses`)
|
|
35
|
+
and machine-wide (`maxGlobalActive`, default 16).
|
|
36
|
+
5. **Transcripts** under `~/.pi/subagent-sessions` may contain task content, tool
|
|
37
|
+
output, and secrets that appeared in context. Protect that directory. Task text
|
|
38
|
+
is delivered via stdin (not argv) so it stays out of `ps` listings, but it is
|
|
39
|
+
still written into the child session log.
|
|
40
|
+
6. **Background permission prompts** are limited because children run headless
|
|
41
|
+
(RPC mode). Extension UI dialogs raised inside a child are auto-cancelled so
|
|
42
|
+
they can never hang a run — which also means a child can never obtain
|
|
43
|
+
interactive consent. Prefer restricted tools for async/background runs.
|
|
44
|
+
11. **Steering messages** (`action: "steer"` and the overlay `s` key) inject text
|
|
45
|
+
into a running child's conversation with user-level authority. Anything that
|
|
46
|
+
can call the subagent tool can steer any live run in the same session.
|
|
47
|
+
7. **Process cleanup.** On POSIX, children run in their own process group so tree
|
|
48
|
+
kills work for ordinary descendants. Parent (re)start reaps orphans recorded
|
|
49
|
+
under `~/.pi/subagent-locks/runs/` so resume cannot race a still-alive writer.
|
|
50
|
+
Grandchildren that call `setsid()` can still escape a simple process-group kill.
|
|
51
|
+
8. **Resume exclusivity.** Direct resume takes a durable per-session file lock;
|
|
52
|
+
concurrent parents cannot append to the same child session.
|
|
53
|
+
9. **Profiles are tool-selection policy, not a sandbox.** Children inherit
|
|
54
|
+
`$HOME`, SSH/cloud credentials, network access, and the parent filesystem.
|
|
55
|
+
Git worktrees only isolate the checkout. For untrusted tasks, use an outer
|
|
56
|
+
container/cgroup/network policy.
|
|
57
|
+
10. **`max_cost` is accounting, not a hard provider gate.** Usage arrives after a
|
|
58
|
+
turn; orphans may spend money the ledger never sees. Combine with provider
|
|
59
|
+
account budgets for hard spend limits.
|
|
60
|
+
|
|
61
|
+
## Trust and project cwd
|
|
62
|
+
|
|
63
|
+
If `cwd` points outside the parent project, the child inherits whatever local
|
|
64
|
+
project config/trust applies to that path. Treat external `cwd` as elevated risk
|
|
65
|
+
and prefer read-only profiles when exploring third-party trees.
|
|
66
|
+
|
|
67
|
+
## Output artifacts
|
|
68
|
+
|
|
69
|
+
`output` files are written by the child. Resolve paths carefully and reject
|
|
70
|
+
duplicate output paths across parallel workers.
|
|
71
|
+
|
|
72
|
+
## Named agent files
|
|
73
|
+
|
|
74
|
+
Agent files (`.pi/agents/`, `.agents/agents/`, global agent dir) inject their
|
|
75
|
+
body into the child's system prompt and set its model/tools/budgets. A
|
|
76
|
+
project-level agent file shapes subagent behavior the same way project
|
|
77
|
+
extensions and skills do — review them like code when working in untrusted
|
|
78
|
+
repositories. Mitigations: capability profiles still fail closed (an agent
|
|
79
|
+
cannot grant write tools under `explore`/`review`), symlinked agent files are
|
|
80
|
+
skipped, names are validated against traversal characters, and files over
|
|
81
|
+
64KB are ignored.
|
|
82
|
+
|
|
83
|
+
## Machine-wide state
|
|
84
|
+
|
|
85
|
+
`~/.pi/subagent-locks/` holds session locks, global concurrency slots, and run
|
|
86
|
+
process identity records. It is per-user (under `$HOME`) and must not be shared
|
|
87
|
+
across untrusted users/containers without care — a compromised client could
|
|
88
|
+
interfere with lock reclaim on the same account.
|