@yefengr/pi-subagents 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +2411 -0
  2. package/LICENSE +21 -0
  3. package/README.md +374 -2
  4. package/UPSTREAM.md +48 -0
  5. package/dist/public.d.ts +201 -0
  6. package/dist/settings.d.ts +82 -0
  7. package/docs/architecture/architecture.md +833 -0
  8. package/docs/architecture/client-server-opportunities.md +127 -0
  9. package/docs/architecture/history/phase-1-api-boundary.md +8 -0
  10. package/docs/architecture/history/phase-10-structural-decomposition.md +141 -0
  11. package/docs/architecture/history/phase-11-closure-to-class.md +100 -0
  12. package/docs/architecture/history/phase-12-complexity-test-fixtures.md +55 -0
  13. package/docs/architecture/history/phase-13-remaining-smells.md +88 -0
  14. package/docs/architecture/history/phase-14-strip-policy.md +49 -0
  15. package/docs/architecture/history/phase-15-domain-model-evolution.md +73 -0
  16. package/docs/architecture/history/phase-16-invert-dependencies.md +144 -0
  17. package/docs/architecture/history/phase-17-core-consolidation.md +214 -0
  18. package/docs/architecture/history/phase-18-reconsider-ui.md +166 -0
  19. package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
  20. package/docs/architecture/history/phase-2-remove-scheduling.md +9 -0
  21. package/docs/architecture/history/phase-20-result-delivery.md +245 -0
  22. package/docs/architecture/history/phase-21-classification-model-boundary.md +107 -0
  23. package/docs/architecture/history/phase-3-remove-rpc-groupjoin.md +11 -0
  24. package/docs/architecture/history/phase-4-implement-service.md +8 -0
  25. package/docs/architecture/history/phase-5-decompose-index.md +42 -0
  26. package/docs/architecture/history/phase-7-encapsulation.md +173 -0
  27. package/docs/architecture/history/phase-8-testability.md +103 -0
  28. package/docs/architecture/history/phase-9-observation-ctx.md +122 -0
  29. package/docs/comparison-with-upstream.md +79 -0
  30. package/docs/configuration.md +215 -0
  31. package/docs/decisions/0001-deferred-patches.md +80 -0
  32. package/docs/decisions/0002-extensions-on-a-minimal-core.md +114 -0
  33. package/docs/decisions/0003-publish-bundled-type-declarations.md +71 -0
  34. package/docs/decisions/0004-reconsider-ui-direction.md +279 -0
  35. package/index.ts +1 -0
  36. package/package.json +91 -4
  37. package/src/config/agent-enablement.ts +132 -0
  38. package/src/config/agent-types.ts +144 -0
  39. package/src/config/custom-agents.ts +114 -0
  40. package/src/config/default-agents.ts +120 -0
  41. package/src/config/invocation-config.ts +39 -0
  42. package/src/config/resource-limits.ts +26 -0
  43. package/src/config/settings-file.ts +174 -0
  44. package/src/debug.ts +14 -0
  45. package/src/handlers/index.ts +3 -0
  46. package/src/handlers/interrupt.ts +58 -0
  47. package/src/handlers/lifecycle.ts +65 -0
  48. package/src/handlers/tool-start.ts +32 -0
  49. package/src/index.ts +257 -0
  50. package/src/layered-settings.ts +105 -0
  51. package/src/lifecycle/child-lifecycle.ts +89 -0
  52. package/src/lifecycle/child-shutdown.ts +105 -0
  53. package/src/lifecycle/concurrency-limiter.ts +123 -0
  54. package/src/lifecycle/create-subagent-session.ts +265 -0
  55. package/src/lifecycle/invocation-control.ts +109 -0
  56. package/src/lifecycle/lifecycle-record.ts +159 -0
  57. package/src/lifecycle/parent-snapshot.ts +46 -0
  58. package/src/lifecycle/run-listeners.ts +41 -0
  59. package/src/lifecycle/subagent-manager.ts +392 -0
  60. package/src/lifecycle/subagent-session.ts +287 -0
  61. package/src/lifecycle/subagent-state.ts +307 -0
  62. package/src/lifecycle/subagent.ts +854 -0
  63. package/src/lifecycle/usage.ts +60 -0
  64. package/src/lifecycle/workspace-bracket.ts +59 -0
  65. package/src/lifecycle/workspace.ts +47 -0
  66. package/src/observation/composite-subagent-observer.ts +53 -0
  67. package/src/observation/notification.ts +233 -0
  68. package/src/observation/record-observer.ts +78 -0
  69. package/src/observation/renderer.ts +106 -0
  70. package/src/observation/subagent-events-observer.ts +75 -0
  71. package/src/runtime.ts +78 -0
  72. package/src/service/service-adapter.ts +159 -0
  73. package/src/service/service.ts +147 -0
  74. package/src/session/content-items.ts +53 -0
  75. package/src/session/context.ts +80 -0
  76. package/src/session/conversation.ts +49 -0
  77. package/src/session/env.ts +40 -0
  78. package/src/session/model-resolver.ts +135 -0
  79. package/src/session/package-exclusions.ts +75 -0
  80. package/src/session/prompts.ts +145 -0
  81. package/src/session/provider-inheritance.ts +38 -0
  82. package/src/session/session-config.ts +177 -0
  83. package/src/session/session-dir.ts +38 -0
  84. package/src/settings.ts +436 -0
  85. package/src/tools/agent-tool.ts +289 -0
  86. package/src/tools/background-spawner.ts +73 -0
  87. package/src/tools/foreground-runner.ts +135 -0
  88. package/src/tools/get-result-report.ts +73 -0
  89. package/src/tools/get-result-tool.ts +109 -0
  90. package/src/tools/helpers.ts +134 -0
  91. package/src/tools/result-renderer.ts +121 -0
  92. package/src/tools/spawn-config.ts +182 -0
  93. package/src/tools/steer-tool.ts +120 -0
  94. package/src/types.ts +145 -0
  95. package/src/ui/agent-widget.ts +313 -0
  96. package/src/ui/display.ts +214 -0
  97. package/src/ui/glyphs.ts +62 -0
  98. package/src/ui/session-navigation.ts +152 -0
  99. package/src/ui/session-navigator.ts +257 -0
  100. package/src/ui/subagents-agents.ts +116 -0
  101. package/src/ui/subagents-settings.ts +207 -0
  102. package/src/ui/transcript-content.ts +374 -0
  103. package/src/ui/widget-renderer.ts +289 -0
@@ -0,0 +1,79 @@
1
+ # Comparison with upstream
2
+
3
+ > Historical document imported from the verified `@gotgenes/pi-subagents@19.3.4` baseline. It describes the lineage and comparison state at that revision, not the current maintenance policy of `@yefengr/pi-subagents`; see [`../UPSTREAM.md`](../UPSTREAM.md).
4
+
5
+ `@gotgenes/pi-subagents` began as a fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) by [@tintinweb](https://github.com/tintinweb).
6
+ The original design — autonomous subagent dispatch, the live widget, the conversation viewer, custom agent types — is the foundation everything here builds on.
7
+
8
+ It has since become an independently maintained hard fork.
9
+ It follows its own architecture, does not track upstream as a merge target, and cherry-picks upstream fixes only when they fit its scope.
10
+ This document compares the fork against the current upstream release so you can choose between them.
11
+
12
+ Versions compared: `@gotgenes/pi-subagents` 16.2.1 and `@tintinweb/pi-subagents` 0.10.3 (current at the time of writing).
13
+
14
+ ## At a glance
15
+
16
+ | Aspect | @gotgenes/pi-subagents | @tintinweb/pi-subagents |
17
+ | --------------- | ------------------------------- | --------------------------------------- |
18
+ | Philosophy | Minimal, composable core | Batteries-included, all-in-one |
19
+ | Pi peer scope | `@earendil-works/pi-*` (>=0.75) | `@earendil-works/pi-*` (>=0.74) |
20
+ | Spawn tool name | `subagent` | `Agent` |
21
+ | Runtime deps | `@sinclair/typebox` | `@sinclair/typebox`, `croner`, `nanoid` |
22
+ | License | MIT | MIT |
23
+
24
+ Both ship TypeScript source directly and target the same `@earendil-works/pi-*` Pi. This fork exposes `./index.ts` as a thin Pi entry point; its implementation remains in `./src/index.ts`.
25
+ The peer-dep migration that prompted the original fork has since landed upstream, so the Pi scope is no longer a differentiator.
26
+
27
+ ## Common ground
28
+
29
+ Both extensions provide the same core experience:
30
+
31
+ - Foreground/background subagents with a live above-editor widget and a conversation viewer.
32
+ - Custom agent types defined in `.pi/agents/<name>.md` with YAML frontmatter (system prompt, model, thinking, tools).
33
+ - Fuzzy model selection, context inheritance, mid-run steering, session resume, and graceful turn limits.
34
+ - A `pi.events` lifecycle bus (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`).
35
+
36
+ ## What upstream has that this fork does not
37
+
38
+ Upstream is the batteries-included option.
39
+ It keeps several subsystems built in that this fork deliberately removed or delegated:
40
+
41
+ | Capability | @tintinweb/pi-subagents | @gotgenes/pi-subagents |
42
+ | ----------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
43
+ | Tool restrictions | `disallowed_tools` frontmatter (denylist) | Delegated — `permission:` via [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-packages/tree/main/packages/pi-permission-system) |
44
+ | Worktree isolation | Built-in | Delegated — [`@gotgenes/pi-subagents-worktrees`](https://github.com/gotgenes/pi-packages/tree/main/packages/pi-subagents-worktrees) |
45
+ | Persistent agent memory | `memory:` frontmatter (project / local / user) | Removed |
46
+ | Skill preloading | `skills:` frontmatter (preload named skills) | Removed — children always inherit the parent's skills |
47
+ | Scheduling | Cron / interval / one-shot subagents (`schedule`) | Removed |
48
+ | Cross-extension control | `subagents:rpc:*` event RPC | Replaced by a typed service (below) |
49
+ | Model-scope enforcement | `enabledModels` allowlist validation | Not included |
50
+ | Notifications | Smart group-join consolidation | Individual per-agent notifications |
51
+
52
+ ## What this fork adds
53
+
54
+ This fork is a minimal core that other extensions build on, plus a small companion ecosystem:
55
+
56
+ - **Typed service API** — `SubagentsService` exposed via `Symbol.for()` accessors, so another extension can spawn and manage subagents without importing this package or relying on ad-hoc event RPC.
57
+ - **Child-session lifecycle events** — `subagents:child:spawning` / `session-created` / `completed` / `disposed`, with `session-created` firing synchronously before `bindExtensions()` so consumers can register the child session deterministically.
58
+ - **`<active_agent>` system-prompt tag** — lets [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-packages/tree/main/packages/pi-permission-system) resolve per-agent `permission:` frontmatter (allow / ask / deny — richer than a binary denylist) inside the child session.
59
+ - **Companion packages** — permission policy and worktree isolation live in dedicated packages rather than the core.
60
+ - **Re-architected codebase** — decomposed into seven domains behind a typed public API boundary, backed by ~994 tests.
61
+
62
+ ## Which should I use?
63
+
64
+ **Use `@tintinweb/pi-subagents`** if you want a single, batteries-included extension with nothing else to install: built-in tool denylist, scheduled / cron subagents, cross-extension RPC, and model-scope enforcement in one package.
65
+ It is the canonical upstream and the original.
66
+
67
+ **Use `@gotgenes/pi-subagents`** if you want a minimal, composable core: richer allow / ask / deny permissions and worktree isolation through companion packages, a typed service plus lifecycle events to build your own extensions on, and an actively refactored codebase — and you do not need built-in scheduling, RPC, or model-scope enforcement.
68
+
69
+ The spawn tool is named `subagent` here versus `Agent` upstream, so prompts and docs that hard-code the tool name are not drop-in portable between the two.
70
+
71
+ ## Patches contributed upstream
72
+
73
+ Three of the fork's early changes were opened as PRs against upstream and remain a record of the shared lineage:
74
+
75
+ 1. Peer-dep migration to `@earendil-works/pi-*` — [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71) (upstream has since migrated).
76
+ 2. Post-`bindExtensions` active-tool re-filter — [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72).
77
+ 3. `<active_agent>` system-prompt tag — [tintinweb/pi-subagents#73](https://github.com/tintinweb/pi-subagents/pull/73).
78
+
79
+ The fork has since diverged well beyond these.
@@ -0,0 +1,215 @@
1
+ # Configuration
2
+
3
+ `@yefengr/pi-subagents` has two configuration surfaces: **agent definition files** that describe an agent type, and a **`subagents.json`** settings file that tunes the runtime.
4
+ Neither is required — every field has a default.
5
+
6
+ For the tools, commands, events, and service API, see the [README](../README.md).
7
+
8
+ ## Default Agent Types
9
+
10
+ | Type | Tools | Model | Prompt Mode | Description |
11
+ | ----------------- | -------------------------- | ----------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------ |
12
+ | `general-purpose` | all 7 | inherit | `append` (parent twin) | Inherits the parent's full system prompt — same rules, CLAUDE.md, project conventions |
13
+ | `Explore` | read, bash, grep, find, ls | `anthropic/claude-haiku-4-5-20251001` (exact pin) | `replace` | Fast codebase exploration (read-only); inherits the parent prompt as a base |
14
+ | `Plan` | read, bash, grep, find, ls | inherit | `replace` | Software architect for implementation planning (read-only); inherits the parent prompt as a base |
15
+
16
+ The `general-purpose` agent is a **parent twin** — it receives the parent's entire system prompt plus a sub-agent context bridge, so it follows the same rules the parent does.
17
+ Explore and Plan use `replace` mode: the parent prompt is the cacheable base and their specialist read-only instructions are appended last, giving them the final say.
18
+ Explore's model is an exact pin, not a fallback: if it is unavailable, unauthenticated, or cannot be resolved by the registry, the spawn fails instead of inheriting the parent model.
19
+
20
+ In every mode, a child that runs somewhere other than the parent — one given an isolated workspace by a `WorkspaceProvider` — does not inherit the parent's `Current working directory:` footer.
21
+ That line is stripped from the inherited prompt, leaving the fresh footer Pi appends for the child session's own directory as the single, correct claim; without the strip, the child follows the parent's path instead.
22
+ A child sharing the parent's directory inherits the prompt untouched, so its prefix stays byte-identical to the parent's.
23
+
24
+ Default agents can be **overridden** by creating a `.md` file with the same name (e.g. `.pi/agents/general-purpose.md`). Enablement is managed by `/subagents:agents` and stored per project or globally in `subagents.json`.
25
+
26
+ ## Custom Agents
27
+
28
+ Define custom agent types by creating `.md` files.
29
+ The filename becomes the agent type name.
30
+ Any name is allowed — using a default agent's name overrides it.
31
+
32
+ Agents are discovered from two locations (higher priority wins):
33
+
34
+ | Priority | Location | Scope |
35
+ | ----------- | -------------------------------------------------------------------------------- | ----------------------------- |
36
+ | 1 (highest) | `.pi/agents/<name>.md` | Project — per-repo agents |
37
+ | 2 | `$PI_CODING_AGENT_DIR/agents/<name>.md` (default `~/.pi/agent/agents/<name>.md`) | Global — available everywhere |
38
+
39
+ Project-level agents override global ones with the same name, so you can customize a global agent for a specific project.
40
+ The global location follows the upstream `PI_CODING_AGENT_DIR` env var — set it to relocate all pi-coding-agent state (agents, skills, settings) to a custom directory.
41
+
42
+ ### Example: `.pi/agents/auditor.md`
43
+
44
+ ```markdown
45
+ ---
46
+ description: Security Code Reviewer
47
+ tools: read, grep, find, bash
48
+ model: anthropic/claude-opus-4-6
49
+ thinking: high
50
+ max_turns: 30
51
+ wrap_up_turns: 2
52
+ max_duration_seconds: 900
53
+ ---
54
+
55
+ You are a security auditor.
56
+ Review code for vulnerabilities including:
57
+
58
+ - Injection flaws (SQL, command, XSS)
59
+ - Authentication and authorization issues
60
+ - Sensitive data exposure
61
+ - Insecure configurations
62
+
63
+ Report findings with file paths, line numbers, severity, and remediation advice.
64
+ ```
65
+
66
+ Then spawn it like any built-in type:
67
+
68
+ ```text
69
+ subagent({ subagent_type: "auditor", prompt: "Review the auth module", description: "Security audit" })
70
+ ```
71
+
72
+ ### Frontmatter Fields
73
+
74
+ All fields are optional — sensible defaults for everything.
75
+
76
+ | Field | Default | Description |
77
+ | ------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78
+ | `description` | filename | Agent description shown in tool listings |
79
+ | `display_name` | — | Display name for UI (e.g. widget, agent list) |
80
+ | `tools` | all 7 | The agent's complete tool allowlist — built-in or extension-registered names. `none` for no tools. See [Tool selection](#tool-selection) |
81
+ | `model` | inherit parent | Exact model pin (`provider/modelId`) or unqualified fuzzy name (`"haiku"`, `"sonnet"`). A qualified pin never fuzzy-matches another model or provider; an explicit model that cannot be resolved fails the spawn instead of inheriting the parent. |
82
+ | `thinking` | inherit | off, minimal, low, medium, high, xhigh |
83
+ | `max_turns` | global limit | Positive hard maximum for model turns. It can only tighten the global setting; `0` is invalid. |
84
+ | `wrap_up_turns` | global setting | Non-negative turns reserved within the hard maximum for a final response. It is clamped to `0..max_turns - 1`. |
85
+ | `max_duration_seconds` | global limit | Positive execution-time limit in seconds. It can only tighten the global setting. |
86
+ | `prompt_mode` | `append` | `replace`: parent prompt is the cacheable base; body is appended last with full control (no `<sub_agent_context>` bridge, no `<agent_instructions>` wrapper). `append`: parent prompt is the base; body is wrapped in `<agent_instructions>` and a sub-agent context bridge is injected (agent acts as a "parent twin") |
87
+ | `inherit_context` | `false` | Fork parent conversation into agent |
88
+ | `run_in_background` | `false` | Run in background by default |
89
+ Enablement is not an agent-definition field. Use `/subagents:agents` to choose `当前项目` or `全局`, then enable, disable, or restore inheritance. Project overrides are stored in `<cwd>/.pi/subagents.json`; personal global overrides are stored in `~/.pi/agent/subagents.local.json`. The shared `~/.pi/agent/subagents.json` remains read-only for this feature. The persisted `agentEnabled` map uses project-over-global precedence.
90
+
91
+ Frontmatter is authoritative for `model`, `thinking`, `inherit_context`, and `run_in_background`: when set, those values are locked for that agent and tool parameters only fill omitted fields.
92
+ Only an omitted `model` inherits the parent model. An explicit model from the profile or the tool call is a pin: if it cannot be resolved (unknown name, model without auth, or a `provider/modelId` that is not an exact available match), the spawn fails with the available-model list and never substitutes the parent model.
93
+ Resource settings are deliberately different. `max_turns` and `max_duration_seconds` from a tool call may only lower the profile and global ceilings; they can never raise them. `wrap_up_turns` follows tool-call, then profile, then global precedence and is clamped below the effective hard turn limit.
94
+
95
+ ### Tool selection
96
+
97
+ `tools` is the agent's **complete allowlist**, not a filter over the built-ins.
98
+ A child session gets exactly the tools it names — nothing else is enabled, whoever registered it.
99
+
100
+ Entries may name built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) or tools registered by any extension:
101
+
102
+ ```yaml
103
+ ---
104
+ description: Browser-driving researcher
105
+ tools: read, grep, find, agent_browser
106
+ ---
107
+ ```
108
+
109
+ Naming an extension's tool is the supported way to give a child access to it.
110
+ This matters because a child loads the parent's extensions and runs their setup functions, so an extension **does** call `registerTool` inside the child — and Pi then drops that tool, because the allowlist is applied before the child's tool registry is built.
111
+ The registration reports no error; the tool simply is not there.
112
+ List the tool by name and it is admitted the moment its extension registers it.
113
+
114
+ Three names are always removed from a child, even when an agent lists them: `subagent`, `get_subagent_result`, and `steer_subagent`.
115
+ This is the recursion guard — without it, an agent could spawn agents of its own without bound.
116
+
117
+ Accepted forms, all equivalent:
118
+
119
+ ```yaml
120
+ tools: read, grep, find # comma-separated
121
+ tools: [read, grep, find] # YAML flow sequence
122
+ tools: # YAML block sequence
123
+ - read
124
+ - grep
125
+ - find
126
+ tools: none # no tools at all
127
+ ```
128
+
129
+ Omitting `tools` entirely gives the agent all seven built-ins and no extension tools.
130
+
131
+ Two other settings interact with this list:
132
+
133
+ - [`excludedExtensionPackages`](#excluding-package-extensions-from-children) stops an extension from loading in children at all, so naming one of its tools has no effect there.
134
+ - When [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-packages/tree/main/packages/pi-permission-system) is installed, its `permission:` frontmatter narrows the set further, per turn.
135
+ Use it to deny a tool; use `tools` to decide what the agent has in the first place.
136
+
137
+ ## Persistent Settings
138
+
139
+ Runtime tuning values set via `/subagents:settings` (max concurrency, max turns, reserved wrap-up turns, maximum execution duration, maximum queue wait, abort grace, the two session-retention windows, and the abort-on-interrupt policy) persist across pi restarts.
140
+ A completed subagent's record is kept for the whole parent session (so `get_subagent_result` never misses); only its heavy in-memory session is released — after `consumedSessionRetentionMinutes` once the result has been collected, or after the `unconsumedSessionRetentionMinutes` safety cap if it never was.
141
+ Runtime settings and agent enablement use separate layers. Runtime settings are merged from two files:
142
+
143
+ - **Global shared:** `~/.pi/agent/subagents.json` — machine-wide runtime defaults and shared child-session policy. It is synchronized from `pi/agent/subagents.json`.
144
+ - **Project:** `<cwd>/.pi/subagents.json` — per-project runtime overrides. Written by `/subagents:settings`.
145
+
146
+ Agent enablement has an additional personal global layer:
147
+
148
+ - **Personal global:** `~/.pi/agent/subagents.local.json` — local `agentEnabled` overrides written by `/subagents:agents`.
149
+ - **Project:** `<cwd>/.pi/subagents.json` — project `agentEnabled` overrides, which take precedence over the personal global layer.
150
+
151
+ `/subagents:agents` never writes the shared global file. If an older shared file still contains `agentEnabled`, it is read as a compatibility layer below `subagents.local.json`.
152
+
153
+ **Precedence:** project overrides global on any field present in both.
154
+ Missing fields fall back to hardcoded defaults: max concurrency `4`, max turns `30`, reserved wrap-up turns `2`, maximum execution duration `1800` seconds, maximum queue wait `900` seconds, abort grace `5` seconds, consumed-session retention `10` minutes, unconsumed-session retention `720` minutes, and abort-all-on-interrupt `true`.
155
+
156
+ **Example — global defaults for a beefy machine:**
157
+
158
+ ```bash
159
+ mkdir -p ~/.pi/agent
160
+ cat > ~/.pi/agent/subagents.json <<'EOF'
161
+ {
162
+ "maxConcurrent": 16,
163
+ "maxTurns": 20,
164
+ "wrapUpTurns": 3,
165
+ "maxDurationSeconds": 1200,
166
+ "maxQueueWaitSeconds": 300,
167
+ "abortGraceSeconds": 10,
168
+ "unconsumedSessionRetentionMinutes": 1440,
169
+ "abortAllOnInterrupt": false
170
+ }
171
+ EOF
172
+ ```
173
+
174
+ Every project now starts with concurrency 16 and the stated finite resource ceilings, while ESC remains with the parent. Individual projects can still override settings through `/subagents:settings`.
175
+
176
+ All resource budgets are finite: `maxTurns`, `maxDurationSeconds`, `maxQueueWaitSeconds`, and `abortGraceSeconds` require positive integers; `wrapUpTurns` permits `0` and is clamped below `maxTurns`. A running invocation starts its duration clock at execution and includes workspace setup, session construction, model calls, and tools. Background queue wait is measured separately. On duration expiry or cancellation, the runtime signals cooperative abort; if the invocation remains unsettled once `abortGraceSeconds` passes, parent waiters and the concurrency slot are released. A same-process tool that ignores `AbortSignal` cannot be forcibly killed, and a cancelled record cannot resume until that prior invocation settles.
177
+
178
+ **Failure behavior:** missing file is silent; malformed JSON logs a `[pi-subagents] Ignoring malformed settings at …` warning to stderr; invalid/out-of-range field values are dropped per-field; write failures downgrade the `/subagents:settings` toast to a warning with `(session only; failed to persist)`.
179
+
180
+ ### Excluding package extensions from children
181
+
182
+ Some package extensions are parent-scoped or expensive to initialize per session.
183
+ Because children run in the parent's process, such an extension initializing once per child multiplies its cost in a single heap — enough, in the case that motivated this feature, to exhaust the V8 heap with four concurrent children.
184
+
185
+ List the offending packages under `excludedExtensionPackages` to keep their extensions out of child sessions:
186
+
187
+ ```json
188
+ {
189
+ "excludedExtensionPackages": ["npm:@cortexkit/pi-magic-context"]
190
+ }
191
+ ```
192
+
193
+ Entries must match Pi's configured package source string exactly, as it appears in your Pi `settings.json` `packages` array — there is no glob or prefix matching.
194
+
195
+ What this does and does not do:
196
+
197
+ - Only the matched packages' **extensions** are disabled, and only in children.
198
+ Their skills, prompts, and themes stay available to children.
199
+ - The parent session is unaffected, as is the child's own settings — only the child's resource loading is filtered.
200
+ - The exclusion happens during package resolution, so the extension's module is never imported and its factory never runs in the child.
201
+ - Excluding a package keeps the **tools** that extension registers out of child sessions too: its factory never runs there, so an agent that names one of those tools in [`tools`](#tool-selection) gets nothing.
202
+ If you need the tools but want the extension's resources released when the child is disposed, exclusion is the wrong lever — see [Child session lifecycle](../README.md#child-session-lifecycle).
203
+
204
+ This key is hand-edited in the shared global or project `subagents.json`; `/subagents:settings` does not expose it, but it is preserved when you change other runtime settings there. It is unrelated to personal `agentEnabled` overrides.
205
+ An absent or empty list reproduces the default behavior, in which children inherit every parent extension.
206
+
207
+ ### Abort on interrupt
208
+
209
+ By default, pressing ESC to interrupt the parent agent also aborts every subagent.
210
+ Set `abortAllOnInterrupt` to `false` (or flip it from `/subagents:settings`) to keep background and queued subagents running when you interrupt the parent — useful when you spawn long background work and then want to steer the parent without losing it.
211
+
212
+ A foreground agent aborts on ESC regardless of this setting.
213
+ It holds the parent's own run signal for the duration of its blocking tool call, so the interrupt reaches it directly; the policy governs background and queued agents.
214
+
215
+ The policy is read at the moment ESC fires, so flipping it mid-session applies to the very next interrupt.
@@ -0,0 +1,80 @@
1
+ ---
2
+ status: superseded
3
+ date: 2026-05-11
4
+ ---
5
+
6
+ # 0001 — Deferred fork patches and upstream-PR strategy
7
+
8
+ ## Status
9
+
10
+ Superseded by [`docs/architecture/architecture.md`](../architecture/architecture.md), which commits to a hard fork with material scope reduction (scheduling removal, `SubagentsAPI` boundary, `index.ts` decomposition).
11
+ The original rationale below remains useful context.
12
+
13
+ ## Context
14
+
15
+ This fork was created to land three pieces of work identified during RepOne issue [#442](https://github.com/Tiny-IG-Software/repone/issues/442):
16
+
17
+ 1. **Peer-dep rename** — `@mariozechner/pi-*` → `@earendil-works/pi-*`.
18
+ 2. **Patch 2 — Re-activate extension tools post-`bindExtensions`** (Spike 3 finding).
19
+ 3. **Patch 3 — Inject `<active_agent>` tag** (Spike 4 finding).
20
+
21
+ A fourth piece of work was scoped during the same spike round but deferred:
22
+
23
+ - **Patch 1 — Mirror parent's `additionalExtensionPaths` (and siblings) into the child's `DefaultResourceLoader`** (Spike 2 finding).
24
+
25
+ This ADR records why Patch 1 was deferred and the strategy for upstream PRs back to [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents).
26
+
27
+ ## Decision
28
+
29
+ ### Patch 1 is deferred
30
+
31
+ The original Spike 2 finding was that the parent's `additionalExtensionPaths` does not propagate to the child's `DefaultResourceLoader`.
32
+ The fix was sketched as "plumb parent's `additionalExtensionPaths` (and siblings) into the child."
33
+
34
+ During planning for this fork, two implementation constraints surfaced:
35
+
36
+ 1. The parent's `DefaultResourceLoader.additionalExtensionPaths` is **private** — no public getter on `ExtensionContext`.
37
+ 2. The parent's CLI flags (e.g., `pi -e <path>`) are parsed in `main.js` and not surfaced through any extension API.
38
+
39
+ A working patch would have to either:
40
+
41
+ - Accept new fields in `RunOptions` so callers supply the paths explicitly, **or**
42
+ - Reach into `process.argv` to re-resolve `-e`/`--extensions` flags from the child's perspective.
43
+
44
+ Neither matches the production need.
45
+ For RepOne (and any consumer that installs extensions via `pi install`), extensions are settings-discoverable: children inherit them independently of the parent's `DefaultResourceLoader` configuration.
46
+ The `pi -e <path>` ephemeral-extension case is the only beneficiary of Patch 1, and it does not appear in our workflow.
47
+
48
+ We therefore defer Patch 1 rather than carry a speculative patch in the fork's diff against upstream.
49
+ A follow-up issue on the RepOne board (linked from #443) captures the criterion for revisiting: **a workflow that needs `pi -e <path>` ephemeral extensions to reach children**.
50
+
51
+ ### Upstream PRs are open
52
+
53
+ All three divergences now have upstream PRs, opened after production validation in RepOne:
54
+
55
+ 1. **Peer-dep migration** — [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71) (`fix(deps)!: migrate from deprecated @mariozechner/pi-* to @earendil-works/pi-*`)
56
+ 2. **Post-bind re-filter** — [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72) (`fix(agent-runner): re-filter active tools after bindExtensions so extension tools land in child`)
57
+ 3. **Active-agent tag** — [tintinweb/pi-subagents#73](https://github.com/tintinweb/pi-subagents/pull/73) (`feat(prompts): inject <active_agent name="..."/> tag for permission resolution`)
58
+
59
+ If these land upstream, upstream gains the peer-dep fix and the two RepOne patches.
60
+ However, the fork now diverges intentionally beyond those patches — see [`docs/architecture/architecture.md`](../architecture/architecture.md) for the full scope of planned changes.
61
+
62
+ ## Consequences
63
+
64
+ ### Positive
65
+
66
+ - The fork's diff against upstream stays minimal — three patches plus tooling alignment.
67
+ - We avoid landing a speculative Patch 1 that would need rework if upstream's `ExtensionContext` API changes.
68
+ - Production evidence strengthened the upstream PRs.
69
+
70
+ ### Negative
71
+
72
+ - The `pi -e <path>` ephemeral-extension case in subagents will not work until Patch 1 lands.
73
+ We accept this because no consumer in scope uses that pattern.
74
+
75
+ ### Operational
76
+
77
+ - Upstream PRs are open and linked above.
78
+ If merged, upstream gains the three patches, but the fork continues independently with broader architectural changes per [`docs/architecture/architecture.md`](../architecture/architecture.md).
79
+ - The architecture document governs the fork's direction going forward; this ADR's original "thin-patch" framing no longer describes the fork's trajectory.
80
+ - When Patch 1 is eventually added, it should be a separate ADR in `docs/decisions/` with its own follow-up.
@@ -0,0 +1,114 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-05-29
4
+ ---
5
+
6
+ # 0002 — Workspaces and permissions are extensions on a minimal core
7
+
8
+ ## Status
9
+
10
+ Accepted.
11
+ Supersedes the "agent collaborator architecture" framing of Phase 16 (an abandoned exploration) and the work shipped under it: issue #256 (`WorktreeIsolation` as an `Agent` collaborator) and issue #257 (`ChildSessionFactory` extraction, parked at planning).
12
+ Reclaims Phase 16's original intent — "invert dependencies" — and extends it to evict worktree isolation from the core.
13
+
14
+ ## Context
15
+
16
+ The core question that triggered this decision: a single-method `ChildSessionFactory` with a `create(cwd?)` method (planned for #257) looked like it wanted to be a function, and the `cwd` parameter was late-bound.
17
+ Pulling that thread exposed progressively more rudimentary issues.
18
+
19
+ 1. `cwd` is late-bound because `WorktreeIsolation.setup()` is called lazily inside `Agent.run()`, after construction — a two-phase `construct-then-setup()` that violates design principle 8 ("Construct complete").
20
+ 2. The worktree is *ready* only at dequeue (a concurrency slot is held and `git worktree add` has run).
21
+ "Construct when ready" therefore means constructing the worktree at run-start, not at spawn — which dissolves the lazy `setup()` and makes `cwd` knowable at construction.
22
+ 3. The worktree and the child session share one lifespan: both are born at run-start and torn down at completion (the worktree's cleanup saves a branch; the session is disposed).
23
+ Resources with one lifetime are one resource, not sibling collaborators that `Agent` must sequence.
24
+ The `create(cwd?)` parameter only existed because we split one run-scoped resource (the worktree) out and made `Agent` relay its output back in.
25
+ 4. Worktrees are not intrinsic to what makes subagents useful.
26
+ The maintainer never uses them (WIP-of-1, trunk-based, CI/CD).
27
+ Git worktree isolation is one *strategy* for answering "where does this child run, and what brackets the run?"
28
+ — a container, a throwaway tmpdir, or a remote sandbox are others.
29
+ The core needs only *a working directory and a disposal hook*; the default (the parent's cwd, no setup/teardown) is always correct.
30
+ 5. This mirrors Phase 14, which evicted tool/extension *policy* (`disallowed_tools`, `extensions` filtering) to `@gotgenes/pi-permission-system`.
31
+ Worktrees are *environment* policy; they belong outside the core for the same reason.
32
+
33
+ Permissions and workspaces are orthogonal concerns that must compose as independent extensions on the core, never knowing about each other.
34
+
35
+ ## Decision
36
+
37
+ pi-subagents is a minimal orchestrator: it spawns a child session derived from the parent, runs the turn loop, tracks and streams and collects the result, gates concurrency, supports resume, and **publishes its lifecycle**.
38
+ Everything else attaches through exactly two extension surfaces, distinguished by the direction of information flow.
39
+
40
+ ### Two extension surfaces
41
+
42
+ 1. **Lifecycle events (observational) — unlimited.**
43
+ The core emits awaited, ordered events for the child-execution lifecycle (`spawning`, `session-created` pre-`bindExtensions`, `completed`, `disposed`).
44
+ Any number of extensions subscribe; handlers return nothing.
45
+ Reactive concerns live here: permission detection, telemetry, UI, notifications.
46
+ Adding a reactive concern never modifies the core.
47
+
48
+ 2. **Provider seams (generative) — rationed.**
49
+ The rare concern that must *inject* a value the core consumes synchronously registers a provider the core consults.
50
+ Today there is exactly one: the **workspace provider** (it returns the child's working directory plus bracketed setup/teardown).
51
+ A provider seam is the only place the core is "open," so the list is kept as small as possible.
52
+
53
+ ### The discriminator
54
+
55
+ When deciding how a concern attaches:
56
+
57
+ - It only needs to **know** what happened → subscribe to a lifecycle event (observational, unlimited).
58
+ - It must **return a value the core consumes** → register a provider (generative, rationed).
59
+
60
+ Permissions are observational: the core does not enforce policy; it publishes the child's identity at the pre-bind instant so the permission extension (loaded in the child) can detect "am I a subagent?"
61
+ and gate tool calls at runtime.
62
+ Workspaces are generative: the core cannot default the cwd away when an isolation strategy is requested, so the provider hands it back.
63
+
64
+ ### The governing rule: no vacant hooks
65
+
66
+ The architecture must *admit* a seam without *shipping* it until a concrete consumer exists.
67
+ A provider seam with no consumer is not extensibility — it is a speculative abstraction that taxes every reader, and `fallow` flags it as dead.
68
+ Latent extensibility (the design can host the seam additively) is the deliverable; a vacant hook is not.
69
+
70
+ ### What leaves the core
71
+
72
+ - **Worktree isolation** (`worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, the `isolation: "worktree"` spawn mode) → a new package, `@gotgenes/pi-subagents-worktrees`, that implements the workspace provider and owns the git plumbing and the "saved to branch" result.
73
+ - **`permission-bridge.ts`** → retired.
74
+ The core stops reaching *out* to `Symbol.for("@gotgenes/pi-permission-system:service")` and instead *emits* lifecycle events the permission system subscribes to.
75
+ - **`isolated` / `extensions: false` / `noSkills`** → removed.
76
+ Deny-at-use (the in-child permission layer blocking disallowed tool calls) covers what `isolated` pretended to do for tools.
77
+ Prevent-load (refusing to bind an extension because of load-time side effects, cost, or true sandboxing) is genuinely generative and cannot be reduced to observation, so it is left as a *latent* (un-built) provider seam, added only if a real consumer needs it.
78
+
79
+ ### What stays in the core (not policy)
80
+
81
+ - The **recursion guard** (stripping the core's own `subagent` / `get_subagent_result` / `steer_subagent` tools from children).
82
+ It defends the core's own invariant — a subagent must not recursively spawn — keyed off the core's own tool names.
83
+ With `isolated` gone, children always load the parent's resources, so the guard becomes unconditional rather than gated on `cfg.extensions`.
84
+
85
+ ### Composition test
86
+
87
+ Install neither extension, only permissions, only workspaces, or both: the core is byte-for-byte identical in all four cases, and the two extensions never reference each other.
88
+ Permissions depend only on the core's events; workspaces depend only on the core's provider seam; the core depends on neither.
89
+
90
+ ## Consequences
91
+
92
+ - The "agent collaborator architecture" Phase 16 (give `Agent` a worktree collaborator + a session factory) is abandoned.
93
+ #256 is superseded (worktree was placed in the wrong layer); #257 is parked (it polished a subsystem slated for eviction).
94
+ - A new package `@gotgenes/pi-subagents-worktrees` is introduced; the core spawn API drops `isolation` and `isolated`.
95
+ - `permission-bridge.ts` is removed; `@gotgenes/pi-permission-system` migrates from a published-service lookup to lifecycle-event subscription, which requires the core to emit an awaited, ordered `session-created` event before `bindExtensions()`.
96
+ Confirming Pi's event model supports awaited pre-bind emission is the first investigation of the reclaimed phase.
97
+ - Once the cwd is resolved through the provider seam rather than relayed by `Agent`, child-session creation can construct a born-complete execution and the "runner" concept dissolves — recovering the structural goal of the abandoned collaborator steps by a cleaner route.
98
+ - The reclaimed Phase 16 roadmap and step issues live in [`docs/architecture/architecture.md`](../architecture/architecture.md).
99
+
100
+ ## Amendment: prevent-load ships as a settings key, not a provider seam (#696)
101
+
102
+ This decision reserved prevent-load as a *latent* provider seam, "added only if a real consumer needs it."
103
+ A real consumer arrived: in-process children reloaded `@cortexkit/pi-magic-context`, whose per-session initialization scans the whole Pi session store, and four concurrent children exhausted the V8 heap in the shared process.
104
+
105
+ The seam was nonetheless **not** built.
106
+ The governing rule above — no vacant hooks — decides it: a provider seam is warranted when an *extension* must inject a value the core consumes, and no extension wants to supply a prevent-load policy.
107
+ The only policy source is the operator's configuration, so a registerable provider whose sole consumer would be pi-subagents' own settings reader is precisely the speculative abstraction this ADR forbids.
108
+
109
+ The core therefore reads an `excludedExtensionPackages` list from the layered `subagents.json` and filters the child's package view before resource loading.
110
+ This is narrower than the per-agent policy Phase 14 evicted: it is global/project scope only, never per agent type; it names packages rather than individual extensions or tools; and it carries no tool-permission semantics, which remain deny-at-use in `@gotgenes/pi-permission-system`.
111
+ Default inheritance is unchanged — an absent or empty list reproduces prior behavior exactly.
112
+
113
+ Latent extensibility is preserved rather than spent.
114
+ The filtering is applied at the composition root, so if a sandboxing extension ever needs to supply the policy generatively, a provider seam can be added additively without disturbing the settings key or the assembly factory.
@@ -0,0 +1,71 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-05-29
4
+ ---
5
+
6
+ # 0003 — Publish a bundled `.d.ts` for the public surface
7
+
8
+ ## Status
9
+
10
+ Accepted.
11
+ Introduces the repository's first build step, scoped to type declarations only.
12
+
13
+ ## Context
14
+
15
+ `@gotgenes/pi-subagents` could not be imported by another TypeScript package in this workspace.
16
+ Issue #263 (extract worktree isolation to `@gotgenes/pi-subagents-worktrees`) is the first intra-repo consumer: it must `implements WorkspaceProvider` and call `getSubagentsService().registerWorkspaceProvider(...)`, both of which require importing the package by name.
17
+
18
+ A `tsc --traceResolution` of a sibling consuming the package surfaced two compounding failures.
19
+
20
+ 1. `package.json` `exports["."]` pointed at `./src/service.ts`, which does not exist — the real module is `./src/service/service.ts`.
21
+ A latent bug, unnoticed because nothing in-repo imported the package by name.
22
+ 2. Once corrected, the public entry's internal alias imports cascade.
23
+ `service/service.ts` imports `type LifetimeUsage` and `type WorkspaceProvider` via the `#src/*` alias.
24
+ When a sibling's `tsc` follows the symlink, the consumer's own `paths` (`#src/*` → `./src/*`) intercept first and resolve into the *consumer's* `src/` — a global-`paths` collision, since both packages define `#src/*`.
25
+ The fallback to the publisher's `package.json` `imports` field also fails: `tsc` cannot resolve the extensionless `.ts` target under Node `imports` semantics ("Import specifier '#src/lifecycle/usage' does not exist in package.json scope").
26
+
27
+ The public entry's type closure is deeply entangled: `WorkspaceProvider` (in `lifecycle/workspace.ts`) reaches `AgentStatus` in the 510-line `lifecycle/agent.ts`, plus `SubagentType`/`AgentInvocation` from `types.ts` (which itself re-exports the `Agent` class).
28
+ A shallow alias-free entry is therefore not achievable without a substantial source restructure.
29
+
30
+ This collides with the ship-source model ([ADR-0002]): every package ships raw `.ts` executed directly by Pi, with no build step.
31
+
32
+ ## Decision
33
+
34
+ Emit a single, self-contained `dist/public.d.ts` for the public surface and advertise it through a `types` export condition, while the runtime entry continues to serve `.ts` source.
35
+
36
+ ```jsonc
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/public.d.ts",
40
+ "default": "./src/service/service.ts"
41
+ }
42
+ }
43
+ ```
44
+
45
+ - `rollup-plugin-dts` rolls the declaration graph rooted at `src/service/service.ts` into one file, inlining the internal `#src/*` types and keeping peer-dependency types (`@earendil-works/*`, `@sinclair/typebox`) external.
46
+ We ship `.ts` source, so only the declaration bundle is emitted — no JS.
47
+ - The bundle is generated at `prepack` time and shipped via a `files` allowlist; it is gitignored and never committed.
48
+ - `default` → `./src/service/service.ts` fixes the stale path and serves runtime consumers; its `import type` lines erase, so no runtime `#src/*` resolution is needed.
49
+ - A `pnpm pack` → throwaway-consumer → `tsc` harness proves external consumability with no publish round-trip and no workspace privileges.
50
+
51
+ This is the repository's first build step.
52
+ It is deliberately narrow: it produces type declarations only and changes nothing about how Pi loads the extension from source (`pi.extensions: ["./src/index.ts"]` is untouched).
53
+
54
+ ## Alternatives considered
55
+
56
+ - Alias-free public entry (restructure the source so the entry's full type closure resolves via same-directory `./` imports).
57
+ Mechanically possible, but it requires moving the `AgentStatus`/`SubagentType`/`AgentInvocation`/`WorkspaceProvider` definitions and untangling the `agent.ts`/`types.ts` graph, with care that inner layers do not import the outer service layer.
58
+ `eslint`'s `no-parent-relative-imports` rule (which forbids `../`) narrows the options further.
59
+ Larger blast radius than emitting a `.d.ts`, and it churns the domain model to serve a packaging concern.
60
+ - A self-contained entry that re-declares the public types inline, guarded by a conformance test.
61
+ Avoids a build step but duplicates the seam/usage/status type definitions, which drift over time.
62
+
63
+ ## Consequences
64
+
65
+ - The repository now has a build step, but it is type-only and isolated to this package; the ship-source model is otherwise intact.
66
+ - Consumers (including `@gotgenes/pi-subagents-worktrees` in #263) consume the packaged public interface like any external developer — no `workspace:*` privileges.
67
+ - The `types` condition points at a build-time artifact; an in-repo workspace-linked consumer that imported the package would need `dist/public.d.ts` present.
68
+ This is acceptable because no in-repo package imports the surface yet; #263 consumes the built artifact from the published tarball.
69
+ - Sequencing: #270 must be published (its release-please PR merged) before #263 edits `pi-subagents` core, so #263's changes do not batch into the same `pi-subagents` release.
70
+
71
+ [ADR-0002]: ./0002-extensions-on-a-minimal-core.md