@jopqior/pi-subagents 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2705 -0
- package/LICENSE +21 -0
- package/README.md +503 -0
- package/dist/public.d.ts +331 -0
- package/dist/settings.d.ts +82 -0
- package/docs/architecture/architecture.md +1566 -0
- package/docs/architecture/client-server-opportunities.md +127 -0
- package/docs/architecture/history/phase-1-api-boundary.md +8 -0
- package/docs/architecture/history/phase-10-structural-decomposition.md +141 -0
- package/docs/architecture/history/phase-11-closure-to-class.md +100 -0
- package/docs/architecture/history/phase-12-complexity-test-fixtures.md +55 -0
- package/docs/architecture/history/phase-13-remaining-smells.md +88 -0
- package/docs/architecture/history/phase-14-strip-policy.md +49 -0
- package/docs/architecture/history/phase-15-domain-model-evolution.md +73 -0
- package/docs/architecture/history/phase-16-invert-dependencies.md +144 -0
- package/docs/architecture/history/phase-17-core-consolidation.md +214 -0
- package/docs/architecture/history/phase-18-reconsider-ui.md +166 -0
- package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
- package/docs/architecture/history/phase-2-remove-scheduling.md +9 -0
- package/docs/architecture/history/phase-20-result-delivery.md +245 -0
- package/docs/architecture/history/phase-21-classification-model-boundary.md +107 -0
- package/docs/architecture/history/phase-3-remove-rpc-groupjoin.md +11 -0
- package/docs/architecture/history/phase-4-implement-service.md +8 -0
- package/docs/architecture/history/phase-5-decompose-index.md +42 -0
- package/docs/architecture/history/phase-7-encapsulation.md +173 -0
- package/docs/architecture/history/phase-8-testability.md +103 -0
- package/docs/architecture/history/phase-9-observation-ctx.md +122 -0
- package/docs/comparison-with-upstream.md +77 -0
- package/docs/configuration.md +364 -0
- package/docs/decisions/0001-deferred-patches.md +80 -0
- package/docs/decisions/0002-extensions-on-a-minimal-core.md +125 -0
- package/docs/decisions/0003-publish-bundled-type-declarations.md +71 -0
- package/docs/decisions/0004-reconsider-ui-direction.md +279 -0
- package/docs/decisions/0005-subagent-record-admission-policy.md +106 -0
- package/docs/decisions/0006-inherited-prompt-is-identity-only.md +104 -0
- package/docs/decisions/0007-transcript-viewer-is-not-an-overlay.md +228 -0
- package/docs/decisions/0008-inherited-region-is-shared-parts.md +81 -0
- package/docs/decisions/0009-portable-inheritance-is-provider-scoped.md +116 -0
- package/package.json +91 -0
- package/src/config/agent-types.ts +135 -0
- package/src/config/custom-agents.ts +151 -0
- package/src/config/default-agents.ts +121 -0
- package/src/config/invocation-config.ts +167 -0
- package/src/config/thinking-level.ts +58 -0
- package/src/debug.ts +14 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/interrupt.ts +58 -0
- package/src/handlers/lifecycle.ts +71 -0
- package/src/handlers/widget-events.ts +49 -0
- package/src/index.ts +292 -0
- package/src/layered-settings.ts +105 -0
- package/src/lifecycle/child-lifecycle.ts +115 -0
- package/src/lifecycle/child-shutdown.ts +105 -0
- package/src/lifecycle/concurrency-limiter.ts +55 -0
- package/src/lifecycle/create-subagent-session.ts +335 -0
- package/src/lifecycle/parent-snapshot.ts +119 -0
- package/src/lifecycle/run-listeners.ts +37 -0
- package/src/lifecycle/selection-scope.ts +116 -0
- package/src/lifecycle/spawn-selection.ts +259 -0
- package/src/lifecycle/subagent-manager.ts +546 -0
- package/src/lifecycle/subagent-session.ts +347 -0
- package/src/lifecycle/subagent-state.ts +404 -0
- package/src/lifecycle/subagent.ts +885 -0
- package/src/lifecycle/turn-limits.ts +13 -0
- package/src/lifecycle/usage.ts +60 -0
- package/src/lifecycle/workspace-bracket.ts +76 -0
- package/src/lifecycle/workspace.ts +46 -0
- package/src/observation/composite-subagent-observer.ts +74 -0
- package/src/observation/notification.ts +430 -0
- package/src/observation/outcome-delivery.ts +239 -0
- package/src/observation/record-observer.ts +78 -0
- package/src/observation/renderer.ts +161 -0
- package/src/observation/subagent-events-observer.ts +148 -0
- package/src/runtime.ts +137 -0
- package/src/service/service-adapter.ts +201 -0
- package/src/service/service.ts +246 -0
- package/src/session/ask-parent-tool.ts +69 -0
- package/src/session/content-items.ts +53 -0
- package/src/session/context.ts +80 -0
- package/src/session/conversation.ts +49 -0
- package/src/session/env.ts +40 -0
- package/src/session/model-resolver.ts +126 -0
- package/src/session/notify-parent-tool.ts +83 -0
- package/src/session/package-exclusions.ts +75 -0
- package/src/session/prompts.ts +231 -0
- package/src/session/provider-inheritance.ts +56 -0
- package/src/session/selection-catalogue.ts +143 -0
- package/src/session/session-config.ts +202 -0
- package/src/session/session-dir.ts +38 -0
- package/src/settings.ts +447 -0
- package/src/tools/agent-tool.ts +305 -0
- package/src/tools/background-spawner.ts +83 -0
- package/src/tools/foreground-runner.ts +159 -0
- package/src/tools/get-result-renderer.ts +119 -0
- package/src/tools/get-result-report.ts +84 -0
- package/src/tools/get-result-tool.ts +192 -0
- package/src/tools/helpers.ts +118 -0
- package/src/tools/result-renderer.ts +153 -0
- package/src/tools/spawn-config.ts +192 -0
- package/src/tools/steer-tool.ts +109 -0
- package/src/types.ts +143 -0
- package/src/ui/agent-widget.ts +333 -0
- package/src/ui/bounded-lines.ts +45 -0
- package/src/ui/display.ts +180 -0
- package/src/ui/glyphs.ts +62 -0
- package/src/ui/session-navigation.ts +150 -0
- package/src/ui/session-navigator.ts +255 -0
- package/src/ui/subagents-settings.ts +179 -0
- package/src/ui/transcript-content.ts +374 -0
- package/src/ui/widget-renderer.ts +301 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Comparison with upstream
|
|
2
|
+
|
|
3
|
+
`@gotgenes/pi-subagents` began as a fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) by [@tintinweb](https://github.com/tintinweb).
|
|
4
|
+
The original design — autonomous subagent dispatch, the live widget, the conversation viewer, custom agent types — is the foundation everything here builds on.
|
|
5
|
+
|
|
6
|
+
It has since become an independently maintained hard fork.
|
|
7
|
+
It follows its own architecture, does not track upstream as a merge target, and cherry-picks upstream fixes only when they fit its scope.
|
|
8
|
+
This document compares the fork against the current upstream release so you can choose between them.
|
|
9
|
+
|
|
10
|
+
Versions compared: `@gotgenes/pi-subagents` 16.2.1 and `@tintinweb/pi-subagents` 0.10.3 (current at the time of writing).
|
|
11
|
+
|
|
12
|
+
## At a glance
|
|
13
|
+
|
|
14
|
+
| Aspect | @gotgenes/pi-subagents | @tintinweb/pi-subagents |
|
|
15
|
+
| --------------- | ------------------------------- | --------------------------------------- |
|
|
16
|
+
| Philosophy | Minimal, composable core | Batteries-included, all-in-one |
|
|
17
|
+
| Pi peer scope | `@earendil-works/pi-*` (>=0.75) | `@earendil-works/pi-*` (>=0.74) |
|
|
18
|
+
| Spawn tool name | `subagent` | `Agent` |
|
|
19
|
+
| Runtime deps | `@sinclair/typebox` | `@sinclair/typebox`, `croner`, `nanoid` |
|
|
20
|
+
| License | MIT | MIT |
|
|
21
|
+
|
|
22
|
+
Both ship TypeScript source directly (Pi runs `./src/index.ts`) and target the same `@earendil-works/pi-*` Pi.
|
|
23
|
+
The peer-dep migration that prompted the original fork has since landed upstream, so the Pi scope is no longer a differentiator.
|
|
24
|
+
|
|
25
|
+
## Common ground
|
|
26
|
+
|
|
27
|
+
Both extensions provide the same core experience:
|
|
28
|
+
|
|
29
|
+
- Foreground/background subagents with a live above-editor widget and a conversation viewer.
|
|
30
|
+
- Custom agent types defined in `.pi/agents/<name>.md` with YAML frontmatter (system prompt, model, thinking, tools).
|
|
31
|
+
- Fuzzy model selection, context inheritance, mid-run steering, session resume, and graceful turn limits.
|
|
32
|
+
- A `pi.events` lifecycle bus (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`).
|
|
33
|
+
|
|
34
|
+
## What upstream has that this fork does not
|
|
35
|
+
|
|
36
|
+
Upstream is the batteries-included option.
|
|
37
|
+
It keeps several subsystems built in that this fork deliberately removed or delegated:
|
|
38
|
+
|
|
39
|
+
| Capability | @tintinweb/pi-subagents | @gotgenes/pi-subagents |
|
|
40
|
+
| ----------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| 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) |
|
|
42
|
+
| Worktree isolation | Built-in | Delegated — [`@gotgenes/pi-subagents-worktrees`](https://github.com/gotgenes/pi-packages/tree/main/packages/pi-subagents-worktrees) |
|
|
43
|
+
| Persistent agent memory | `memory:` frontmatter (project / local / user) | Removed |
|
|
44
|
+
| Skill preloading | `skills:` frontmatter (preload named skills) | Removed — children always inherit the parent's skills |
|
|
45
|
+
| Scheduling | Cron / interval / one-shot subagents (`schedule`) | Removed |
|
|
46
|
+
| Cross-extension control | `subagents:rpc:*` event RPC | Replaced by a typed service (below) |
|
|
47
|
+
| Model-scope enforcement | `enabledModels` allowlist validation | Not included |
|
|
48
|
+
| Notifications | Smart group-join consolidation | Individual per-agent notifications |
|
|
49
|
+
|
|
50
|
+
## What this fork adds
|
|
51
|
+
|
|
52
|
+
This fork is a minimal core that other extensions build on, plus a small companion ecosystem:
|
|
53
|
+
|
|
54
|
+
- **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.
|
|
55
|
+
- **Child-session lifecycle events** — `subagents:child:spawning` / `session-created` / `bound` / `completed` / `disposed`, with `session-created` firing synchronously before `bindExtensions()` so consumers can register the child session deterministically, and `bound` firing after it resolves so they can observe what the child's extensions installed.
|
|
56
|
+
- **`<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.
|
|
57
|
+
- **Companion packages** — permission policy and worktree isolation live in dedicated packages rather than the core.
|
|
58
|
+
- **Re-architected codebase** — decomposed into seven domains behind a typed public API boundary, backed by ~994 tests.
|
|
59
|
+
|
|
60
|
+
## Which should I use?
|
|
61
|
+
|
|
62
|
+
**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.
|
|
63
|
+
It is the canonical upstream and the original.
|
|
64
|
+
|
|
65
|
+
**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.
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
69
|
+
## Patches contributed upstream
|
|
70
|
+
|
|
71
|
+
Three of the fork's early changes were opened as PRs against upstream and remain a record of the shared lineage:
|
|
72
|
+
|
|
73
|
+
1. Peer-dep migration to `@earendil-works/pi-*` — [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71) (upstream has since migrated).
|
|
74
|
+
2. Post-`bindExtensions` active-tool re-filter — [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72).
|
|
75
|
+
3. `<active_agent>` system-prompt tag — [tintinweb/pi-subagents#73](https://github.com/tintinweb/pi-subagents/pull/73).
|
|
76
|
+
|
|
77
|
+
The fork has since diverged well beyond these.
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`@jopqior/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 prompt identity — same rules, CLAUDE.md, project conventions |
|
|
13
|
+
| `Explore` | read, bash, grep, find, ls | haiku (falls back to inherit) | `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 inherited identity and nothing of its own, 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
|
+
|
|
19
|
+
Default agents can be **overridden** by creating a `.md` file with the same name (e.g. `.pi/agents/general-purpose.md`), or **disabled** per-project with `enabled: false` frontmatter.
|
|
20
|
+
|
|
21
|
+
## What a child inherits from the parent's prompt
|
|
22
|
+
|
|
23
|
+
Pi assembles a system prompt in layers.
|
|
24
|
+
Its own preamble and tool guidelines come first, then your `AGENTS.md` or `CLAUDE.md` project context, then the catalogue of available skills, then a `Current working directory:` footer — and finally whatever extensions append each turn.
|
|
25
|
+
|
|
26
|
+
A child inherits only the **stable identity** layers: everything up to, but not including, the skills catalogue.
|
|
27
|
+
The layers after it are resolved against one session, so Pi and the child's own extensions rebuild them for the child rather than the child borrowing the parent's:
|
|
28
|
+
|
|
29
|
+
| Layer | Where a child's copy comes from |
|
|
30
|
+
| ---------------------------------- | ---------------------------------------------------------- |
|
|
31
|
+
| Pi preamble, project context | inherited from the parent, byte for byte |
|
|
32
|
+
| `Available tools:` / `Guidelines:` | stated by the child's own `@gotgenes/pi-permission-system` |
|
|
33
|
+
| Skills catalogue | rebuilt by Pi for the child's own directory and tool set |
|
|
34
|
+
| `Current working directory:` | rebuilt by Pi for the child's own directory |
|
|
35
|
+
| Extension-appended blocks | rebuilt by the child's own extensions |
|
|
36
|
+
|
|
37
|
+
This matters most for a child that runs somewhere other than the parent — one given an isolated workspace by a `WorkspaceProvider`.
|
|
38
|
+
Its skills resolve from its own workspace, and its working-directory claim names that workspace.
|
|
39
|
+
Inheriting the parent's copies instead would give such a child a catalogue of skills it may not have and a directory claim that walks it back out of its workspace.
|
|
40
|
+
|
|
41
|
+
Inheriting the identity rather than the whole prompt also gives the child a leading prefix it shares with the parent, which local inference engines reuse instead of reprocessing.
|
|
42
|
+
How much that is worth depends on the host: a provider whose cache prefix covers the tool definitions ahead of the system prompt — Anthropic's does — reuses nothing for a child, because a child's tool set always differs from its parent's.
|
|
43
|
+
|
|
44
|
+
The tool sections are listed above as the child's own rather than inherited because `@gotgenes/pi-permission-system` relocates them to the end of the prompt, so each session states the tools it actually holds without editing the bytes a child inherits.
|
|
45
|
+
Without that extension installed, a child inherits the parent's `Available tools:` listing unchanged, which names the parent's tools rather than the child's ([#901]).
|
|
46
|
+
|
|
47
|
+
If you write extensions that add to the system prompt, see [Extensions that append to the system prompt](../README.md#extensions-that-append-to-the-system-prompt).
|
|
48
|
+
The reasoning behind the boundary is recorded in [ADR 0006](decisions/0006-inherited-prompt-is-identity-only.md), and what the inherited region guarantees in [ADR 0008](decisions/0008-inherited-region-is-shared-parts.md).
|
|
49
|
+
|
|
50
|
+
### Portable inheritance (opt-in)
|
|
51
|
+
|
|
52
|
+
The inherited identity includes Pi's own preamble.
|
|
53
|
+
That is correct when the child talks to the same API as its parent, and wrong when the child's provider **re-homes** the prompt into another harness — `pi-claude-bridge`, for example, projects it onto Claude Code's preset as an append.
|
|
54
|
+
There, Pi's preamble reaches an API that already has a base prompt of its own, and Anthropic's subscription gate scores the documentation-routing line inside it as a third-party app ([#883]).
|
|
55
|
+
|
|
56
|
+
Opt such a provider into `portable`, which replaces the inherited identity with the parent's operator-authored parts only:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"promptInheritance": { "claude-bridge": "portable" }
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Strategy | The child's identity | Shares a prefix with the parent |
|
|
65
|
+
| ---------------- | -------------------------------------------------------------- | ------------------------------- |
|
|
66
|
+
| `full` (default) | the parent's identity layers, byte for byte | yes |
|
|
67
|
+
| `portable` | the parent's custom prompt, append prompt, and project context | no |
|
|
68
|
+
|
|
69
|
+
The map keys on the **provider id of the child's resolved model**, so only children whose requests actually travel through that provider change strategy.
|
|
70
|
+
That is also why the key is the provider rather than the agent: a `subagent` call may override an agent's model, which moves the child to a different transport, and the strategy follows it.
|
|
71
|
+
|
|
72
|
+
Skills stay un-inherited either way — the child builds its own catalogue — and the parent's tool guidelines are never inherited under `portable`, because Pi derives them from the tools a session actually holds.
|
|
73
|
+
Project context files ride along, and must: the child's loader is built with context files suppressed, so this is the only way a portable child sees your `AGENTS.md` at all.
|
|
74
|
+
|
|
75
|
+
**Use `portable` only for a provider that re-homes the prompt into a harness supplying its own base.**
|
|
76
|
+
It is not enforced, because Pi exposes no way to identify such a provider — but pointing it at an ordinary provider is worse than leaving the default.
|
|
77
|
+
`@gotgenes/pi-anthropic-auth`, for instance, finds Pi's role line in order to shape the OAuth system prompt; a portable child has no such line, so shaping returns it unchanged and the child never receives the neutral role prompt that shaping would have substituted.
|
|
78
|
+
|
|
79
|
+
If the parent has no context files, custom prompt, or append prompt, a portable child falls back to a short generic base rather than to the full parent prompt — opting in never silently re-embeds the preamble it exists to avoid.
|
|
80
|
+
|
|
81
|
+
The reasoning is recorded in [ADR 0009](decisions/0009-portable-inheritance-is-provider-scoped.md).
|
|
82
|
+
|
|
83
|
+
[#883]: https://github.com/gotgenes/pi-packages/issues/883
|
|
84
|
+
[#901]: https://github.com/gotgenes/pi-packages/issues/901
|
|
85
|
+
|
|
86
|
+
## Custom Agents
|
|
87
|
+
|
|
88
|
+
Define custom agent types by creating `.md` files.
|
|
89
|
+
The filename becomes the agent type name.
|
|
90
|
+
Any name is allowed — using a default agent's name overrides it.
|
|
91
|
+
|
|
92
|
+
Agents are discovered from two locations (higher priority wins):
|
|
93
|
+
|
|
94
|
+
| Priority | Location | Scope |
|
|
95
|
+
| ----------- | -------------------------------------------------------------------------------- | ----------------------------- |
|
|
96
|
+
| 1 (highest) | `.pi/agents/<name>.md` | Project — per-repo agents |
|
|
97
|
+
| 2 | `$PI_CODING_AGENT_DIR/agents/<name>.md` (default `~/.pi/agent/agents/<name>.md`) | Global — available everywhere |
|
|
98
|
+
|
|
99
|
+
Project-level agents override global ones with the same name, so you can customize a global agent for a specific project.
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
### Example: `.pi/agents/auditor.md`
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
---
|
|
106
|
+
description: Security Code Reviewer
|
|
107
|
+
tools: read, grep, find, bash
|
|
108
|
+
model: anthropic/claude-opus-4-6
|
|
109
|
+
thinking: high
|
|
110
|
+
max_turns: 30
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
You are a security auditor.
|
|
114
|
+
Review code for vulnerabilities including:
|
|
115
|
+
|
|
116
|
+
- Injection flaws (SQL, command, XSS)
|
|
117
|
+
- Authentication and authorization issues
|
|
118
|
+
- Sensitive data exposure
|
|
119
|
+
- Insecure configurations
|
|
120
|
+
|
|
121
|
+
Report findings with file paths, line numbers, severity, and remediation advice.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Then spawn it like any built-in type:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
subagent({ subagent_type: "auditor", prompt: "Review the auth module", description: "Security audit" })
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Frontmatter Fields
|
|
131
|
+
|
|
132
|
+
All fields are optional — sensible defaults for everything.
|
|
133
|
+
|
|
134
|
+
| Field | Default | Description |
|
|
135
|
+
| ------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| `description` | filename | Agent description shown in tool listings |
|
|
137
|
+
| `display_name` | — | Display name for UI (e.g. widget, agent list) |
|
|
138
|
+
| `tools` | all 7 | The agent's complete tool allowlist — built-in or extension-registered names. `none` for no tools. See [Tool selection](#tool-selection) |
|
|
139
|
+
| `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
|
|
140
|
+
| `thinking` | inherit | off, minimal, low, medium, high, xhigh, max. An unrecognized value is dropped, and the agent inherits the parent's level |
|
|
141
|
+
| `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
|
|
142
|
+
| `prompt_mode` | `append` | `replace`: parent prompt is the cacheable base; body is appended last with full control and no `<agent_instructions>` wrapper. `append`: parent prompt is the base; body is wrapped in `<agent_instructions>` (agent acts as a "parent twin") |
|
|
143
|
+
| `inherit_context` | `false` | Fork parent conversation into agent |
|
|
144
|
+
| `run_in_background` | `false` | Run in background by default |
|
|
145
|
+
| `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
|
|
146
|
+
| `locked` | — | Fields a `subagent` tool caller may not override. `true` or a list of field names. See [Locking fields against callers](#locking-fields-against-callers) |
|
|
147
|
+
|
|
148
|
+
The caller decides, and the agent file fills the gaps.
|
|
149
|
+
A `subagent` tool parameter wins over the agent file's value for `model`, `thinking`, `max_turns`, `inherit_context`, and `run_in_background`; the agent file supplies whichever of those the caller left unset.
|
|
150
|
+
|
|
151
|
+
### Locking fields against callers
|
|
152
|
+
|
|
153
|
+
An agent whose model, thinking level, or turn limit is a correctness requirement rather than a default can withhold it from callers with `locked`.
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
---
|
|
157
|
+
model: anthropic/claude-haiku-4-5
|
|
158
|
+
max_turns: 10
|
|
159
|
+
locked: true
|
|
160
|
+
---
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`locked: true` withholds every field this file sets — here `model` and `max_turns`, while `thinking`, `inherit_context`, and `run_in_background` stay open because the file names no value for them.
|
|
164
|
+
This is the behavior every agent file had before locking became opt-in, so it is the one-line way to keep an existing file working unchanged.
|
|
165
|
+
|
|
166
|
+
A list withholds exactly the fields it names, in either YAML spelling:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
locked: model, thinking # comma-separated
|
|
170
|
+
locked: [model, max_turns] # flow sequence
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The list form also withholds a field this file leaves unset — `locked: [model]` with no `model:` denies the caller a model override and lets the child inherit the parent's.
|
|
174
|
+
An entry naming anything other than `model`, `thinking`, `max_turns`, `inherit_context`, or `run_in_background` is ignored.
|
|
175
|
+
|
|
176
|
+
A lock is never silent: when a caller passes a value for a locked field, the tool result says which agent locked which parameters.
|
|
177
|
+
|
|
178
|
+
A lock binds the `subagent` tool only.
|
|
179
|
+
[`SubagentsService.spawn`](../README.md#for-extension-authors) is a programmatic caller rather than a model guessing at harness settings, so its options win regardless.
|
|
180
|
+
|
|
181
|
+
A registered spawn-selection provider is a later authority for `model` and `thinking` only.
|
|
182
|
+
The operator still chooses both after ordinary resolution, and that pair overrides defaults, explicit arguments, and `locked:` values for those two fields.
|
|
183
|
+
Other locked fields are unchanged.
|
|
184
|
+
With no provider, locking and ordinary resolution are unchanged.
|
|
185
|
+
|
|
186
|
+
### Tool selection
|
|
187
|
+
|
|
188
|
+
`tools` is the agent's **complete allowlist** of capability tools, not a filter over the built-ins.
|
|
189
|
+
No tool that touches the filesystem, the shell, or the network reaches a child unless the agent names it — whoever registered it.
|
|
190
|
+
|
|
191
|
+
Entries may name built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) or tools registered by any extension:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
---
|
|
195
|
+
description: Browser-driving researcher
|
|
196
|
+
tools: read, grep, find, agent_browser
|
|
197
|
+
---
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Naming an extension's tool is the supported way to give a child access to it.
|
|
201
|
+
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.
|
|
202
|
+
The registration reports no error; the tool simply is not there.
|
|
203
|
+
List the tool by name and it is admitted the moment its extension registers it.
|
|
204
|
+
|
|
205
|
+
Three names are always removed from a child, even when an agent lists them: `subagent`, `get_subagent_result`, and `steer_subagent`.
|
|
206
|
+
This is the recursion guard — without it, an agent could spawn agents of its own without bound.
|
|
207
|
+
|
|
208
|
+
Two names are always **added**, whatever an agent lists: `ask_parent` and `notify_parent`.
|
|
209
|
+
These are the child's channel back to the agent that delegated to it — protocol the core installs in every child, like the `<active_agent>` tag and the parent-context prefix.
|
|
210
|
+
Neither reaches the filesystem, the shell, or the network, so a read-only agent stays read-only.
|
|
211
|
+
`ask_parent` records a question and tells the child to end its turn, so the delegating agent can answer by resuming it; `notify_parent` sends a one-way update and returns at once.
|
|
212
|
+
A question outlives the window in which it can be answered — the session is released after its retention window, and a workspace is torn down at run end unless the child completed — so once a resume would be refused, the result reports the question and the reason rather than the `resume` call.
|
|
213
|
+
Both go to every agent, `notify_parent` only while [`midRunUpdates`](#persistent-settings) is on.
|
|
214
|
+
Where an update lands depends on whether an announcement can still reach you in time to act on it.
|
|
215
|
+
It arrives as its own message when you are idle and the agent is still running — the one case where steering it is still possible.
|
|
216
|
+
Otherwise it rides that agent's outcome, under "Updates this agent sent while it worked": the foreground call, the resume, the `get_subagent_result` report, or the completion notice, whichever delivers that run.
|
|
217
|
+
Either way you see it once.
|
|
218
|
+
|
|
219
|
+
Accepted forms, all equivalent:
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
tools: read, grep, find # comma-separated
|
|
223
|
+
tools: [read, grep, find] # YAML flow sequence
|
|
224
|
+
tools: # YAML block sequence
|
|
225
|
+
- read
|
|
226
|
+
- grep
|
|
227
|
+
- find
|
|
228
|
+
tools: none # no tools at all
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Omitting `tools` entirely gives the agent all seven built-ins and no extension tools.
|
|
232
|
+
|
|
233
|
+
Two other settings interact with this list:
|
|
234
|
+
|
|
235
|
+
- [`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.
|
|
236
|
+
- 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.
|
|
237
|
+
Use it to deny a tool; use `tools` to decide what the agent has in the first place.
|
|
238
|
+
|
|
239
|
+
## Persistent Settings
|
|
240
|
+
|
|
241
|
+
Runtime tuning values set via `/subagents:settings` (max concurrency, default max turns, grace turns, the two session-retention windows, the abort-on-interrupt policy, and the mid-run update channel) persist across pi restarts.
|
|
242
|
+
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.
|
|
243
|
+
An agent that asked a question and has not been answered holds the safety cap rather than the consumed window, because reading a question is not finishing with the agent — the answer is delivered by resuming the very session the short window would release.
|
|
244
|
+
|
|
245
|
+
Set `midRunUpdates` to `false` to withhold `notify_parent` from every agent, leaving them no way to tell you anything before they finish.
|
|
246
|
+
`ask_parent` is unaffected: a blocked agent can still end its turn with a question.
|
|
247
|
+
Two files, merged on load:
|
|
248
|
+
|
|
249
|
+
- **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults.
|
|
250
|
+
Edit by hand; the `/subagents:settings` command never writes here.
|
|
251
|
+
- **Project:** `<cwd>/.pi/subagents.json` — per-project overrides.
|
|
252
|
+
Written by `/subagents:settings`.
|
|
253
|
+
|
|
254
|
+
**Precedence:** project overrides global on any field present in both.
|
|
255
|
+
Missing fields fall back to the hardcoded defaults (max concurrency `4`, default max turns unlimited, grace turns `5`, consumed-session retention `10` minutes, unconsumed-session retention `720` minutes, abort-all-on-interrupt `true`, mid-run updates `true`).
|
|
256
|
+
|
|
257
|
+
**Example — global defaults for a beefy machine:**
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
mkdir -p ~/.pi/agent
|
|
261
|
+
cat > ~/.pi/agent/subagents.json <<'EOF'
|
|
262
|
+
{
|
|
263
|
+
"maxConcurrent": 16,
|
|
264
|
+
"graceTurns": 10,
|
|
265
|
+
"unconsumedSessionRetentionMinutes": 1440,
|
|
266
|
+
"abortAllOnInterrupt": false,
|
|
267
|
+
"midRunUpdates": true
|
|
268
|
+
}
|
|
269
|
+
EOF
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Every project now starts with concurrency 16, grace 10, and ESC left to the parent, without ever touching the command.
|
|
273
|
+
Individual projects can still override via `/subagents:settings`.
|
|
274
|
+
|
|
275
|
+
**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)`.
|
|
276
|
+
|
|
277
|
+
### Excluding package extensions from children
|
|
278
|
+
|
|
279
|
+
Some package extensions are parent-scoped or expensive to initialize per session.
|
|
280
|
+
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.
|
|
281
|
+
|
|
282
|
+
List the offending packages under `excludedExtensionPackages` to keep their extensions out of child sessions:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"excludedExtensionPackages": ["npm:@cortexkit/pi-magic-context"]
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
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.
|
|
291
|
+
|
|
292
|
+
What this does and does not do:
|
|
293
|
+
|
|
294
|
+
- Only the matched packages' **extensions** are disabled, and only in children.
|
|
295
|
+
Their skills, prompts, and themes stay available to children.
|
|
296
|
+
- The parent session is unaffected, as is the child's own settings — only the child's resource loading is filtered.
|
|
297
|
+
- The exclusion happens during package resolution, so the extension's module is never imported and its factory never runs in the child.
|
|
298
|
+
- 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.
|
|
299
|
+
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).
|
|
300
|
+
|
|
301
|
+
This key is hand-edited in the global or project `subagents.json`; `/subagents:settings` does not expose it, but it is preserved when you change other settings there.
|
|
302
|
+
An absent or empty list reproduces the default behavior, in which children inherit every parent extension.
|
|
303
|
+
|
|
304
|
+
The same is true of [`promptInheritance`](#portable-inheritance-opt-in): hand-edited only, preserved across other settings changes, and absent means every child inherits the full parent identity.
|
|
305
|
+
|
|
306
|
+
#### Excluding a permission extension
|
|
307
|
+
|
|
308
|
+
When [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-packages/tree/main/packages/pi-permission-system) is installed, it rides into children harmlessly by construction, so exclusion is an optimization and never a correctness requirement.
|
|
309
|
+
Excluding an extension that only registers an authorizer chain link costs nothing but saves its load time: the node that adjudicates an ask still judges every descendant's request.
|
|
310
|
+
|
|
311
|
+
One case used to weaken a child, and recent versions of that extension close it.
|
|
312
|
+
An extension can declare the filesystem path *another* package's tool accesses, so that the permission system's `path` and `external_directory` gates can see it.
|
|
313
|
+
Excluding such a declaring package left the tool present in the child with its path undeclared, and the child's own gates stopped seeing it — silently, because the parent's gating is unaffected and still looks correct.
|
|
314
|
+
|
|
315
|
+
The condition needed both halves, so most exclusions could never hit it:
|
|
316
|
+
|
|
317
|
+
- Package **A** registers a tool whose path lives under a non-standard input key.
|
|
318
|
+
- Package **B** registers the path extractor for A's tool.
|
|
319
|
+
- You exclude **B** but not **A**.
|
|
320
|
+
|
|
321
|
+
If one package supplies both the tool and its extractor, excluding it removes both together and no gap opens.
|
|
322
|
+
|
|
323
|
+
Since the version of `@gotgenes/pi-permission-system` that closed this, a child session that has no extractor of its own for a tool borrows one from the session that spawned it, so the split above no longer leaves a path ungated.
|
|
324
|
+
Preview formatters resolve the same way, so an approval prompt for such a tool still shows its registered preview rather than raw JSON.
|
|
325
|
+
The borrowed declaration is recorded: the child's review-log entry carries `extractorSource: "inherited"`.
|
|
326
|
+
Nothing is borrowed across a process boundary — children here run in the parent's process, which is what makes it possible.
|
|
327
|
+
|
|
328
|
+
One thing exclusion still does **not** weaken, by design: an authorizer chain link is never borrowed from another session, because a link decides rather than describes.
|
|
329
|
+
|
|
330
|
+
Excluding `@gotgenes/pi-permission-system` itself is a different matter: a child then loads no permission node at all, so nothing gates its tool calls, no `permission:` frontmatter applies, and no `ask` is forwarded.
|
|
331
|
+
See [Subagent Integration](https://github.com/gotgenes/pi-packages/blob/main/packages/pi-permission-system/docs/subagent-integration.md#loading-asymmetry) for the full rule.
|
|
332
|
+
|
|
333
|
+
This one does not pass silently.
|
|
334
|
+
This extension announces each child once its extensions have bound, and a recent `@gotgenes/pi-permission-system` uses that announcement to notice a child with no node of its own: it records the child in its permission review log and warns once per session that the child's tool calls are ungated.
|
|
335
|
+
The warning names this setting as the likeliest cause, because a failure to load that extension in the child leaves the same absence and the parent cannot tell the two apart.
|
|
336
|
+
Remove the entry to restore gating in child sessions; keep it, knowing the children it spawns are unguarded.
|
|
337
|
+
|
|
338
|
+
### Abort on interrupt
|
|
339
|
+
|
|
340
|
+
By default, pressing ESC to interrupt the parent agent also aborts every subagent.
|
|
341
|
+
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.
|
|
342
|
+
|
|
343
|
+
A foreground agent aborts on ESC regardless of this setting.
|
|
344
|
+
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.
|
|
345
|
+
|
|
346
|
+
The policy is read at the moment ESC fires, so flipping it mid-session applies to the very next interrupt.
|
|
347
|
+
|
|
348
|
+
## Model providers in child sessions
|
|
349
|
+
|
|
350
|
+
A subagent inherits every model provider the parent can reach, including providers an extension registered at runtime with `pi.registerProvider` rather than through `models.json` or `auth.json`.
|
|
351
|
+
This is what lets a child run under a dynamically registered provider such as `pi-claude-bridge`.
|
|
352
|
+
|
|
353
|
+
The child does not share the parent's provider pool — it gets its own, with the parent's registrations copied onto it.
|
|
354
|
+
An extension loaded in a child can therefore register or unregister providers without disturbing the parent or any sibling agent.
|
|
355
|
+
|
|
356
|
+
Inheritance is a **snapshot taken when the agent spawns**.
|
|
357
|
+
A provider registered in the parent after a child has started does not appear in that running child; agents spawned afterwards pick it up.
|
|
358
|
+
This matches the rest of the parent state a child captures at spawn — working directory, model, and system prompt are all frozen the same way.
|
|
359
|
+
|
|
360
|
+
Provider inheritance needs no configuration.
|
|
361
|
+
It does require Pi 0.81.0 or newer, which is the floor this package declares — that is the release where the model registry began exposing every runtime registration for replay.
|
|
362
|
+
|
|
363
|
+
One thing the child does still share with the parent: when a provider's API key is a shell command (`"apiKey": "!my-command"`), Pi caches the command's resolved output process-wide, so parent and children reuse one result rather than re-running it per agent.
|
|
364
|
+
That cache is Pi's, not this extension's, and it predates provider inheritance.
|
|
@@ -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.
|