@cr1ms0n/pi-subagent 0.8.7 → 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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.8.9 — 2026-09-16
6
+
7
+ ### Restore the stable 0.8.7 behavior
8
+
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.
10
+
5
11
  ## 0.8.7 — 2026-09-14
6
12
 
7
13
  ### Provider-safe tool schemas
@@ -1,125 +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
- - `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.
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,88 +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 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.
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cr1ms0n/pi-subagent",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "description": "Community fork of Luke Parke's pi-subagent with explicit model policy and model visibility for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",