@gotgenes/pi-subagents 1.0.0 → 1.0.2
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/AGENTS.md +4 -82
- package/CHANGELOG.md +29 -0
- package/README.md +18 -4
- package/docs/architecture/architecture.md +391 -0
- package/docs/decisions/0001-deferred-patches.md +13 -14
- package/docs/plans/0051-update-adr-0001-hard-fork.md +74 -0
- package/package.json +12 -17
- package/.markdownlint-cli2.yaml +0 -19
- package/.release-please-manifest.json +0 -3
- package/dist/agent-manager.d.ts +0 -108
- package/dist/agent-manager.js +0 -390
- package/dist/agent-runner.d.ts +0 -93
- package/dist/agent-runner.js +0 -428
- package/dist/agent-types.d.ts +0 -48
- package/dist/agent-types.js +0 -136
- package/dist/context.d.ts +0 -12
- package/dist/context.js +0 -56
- package/dist/cross-extension-rpc.d.ts +0 -46
- package/dist/cross-extension-rpc.js +0 -54
- package/dist/custom-agents.d.ts +0 -14
- package/dist/custom-agents.js +0 -127
- package/dist/default-agents.d.ts +0 -7
- package/dist/default-agents.js +0 -119
- package/dist/env.d.ts +0 -6
- package/dist/env.js +0 -28
- package/dist/group-join.d.ts +0 -32
- package/dist/group-join.js +0 -116
- package/dist/index.d.ts +0 -13
- package/dist/index.js +0 -1731
- package/dist/invocation-config.d.ts +0 -22
- package/dist/invocation-config.js +0 -15
- package/dist/memory.d.ts +0 -49
- package/dist/memory.js +0 -151
- package/dist/model-resolver.d.ts +0 -19
- package/dist/model-resolver.js +0 -62
- package/dist/output-file.d.ts +0 -24
- package/dist/output-file.js +0 -86
- package/dist/prompts.d.ts +0 -29
- package/dist/prompts.js +0 -72
- package/dist/schedule-store.d.ts +0 -36
- package/dist/schedule-store.js +0 -144
- package/dist/schedule.d.ts +0 -109
- package/dist/schedule.js +0 -338
- package/dist/settings.d.ts +0 -66
- package/dist/settings.js +0 -130
- package/dist/skill-loader.d.ts +0 -24
- package/dist/skill-loader.js +0 -93
- package/dist/types.d.ts +0 -164
- package/dist/types.js +0 -5
- package/dist/ui/agent-widget.d.ts +0 -134
- package/dist/ui/agent-widget.js +0 -451
- package/dist/ui/conversation-viewer.d.ts +0 -35
- package/dist/ui/conversation-viewer.js +0 -252
- package/dist/ui/schedule-menu.d.ts +0 -16
- package/dist/ui/schedule-menu.js +0 -95
- package/dist/usage.d.ts +0 -50
- package/dist/usage.js +0 -49
- package/dist/worktree.d.ts +0 -36
- package/dist/worktree.js +0 -139
- package/prek.toml +0 -24
- package/release-please-config.json +0 -22
package/AGENTS.md
CHANGED
|
@@ -1,85 +1,7 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
⚠️ It looks like the agent was started from a package subdirectory.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
It carries a small number of patches needed for downstream consumers (notably [RepOne](https://github.com/Tiny-IG-Software/repone)) that intend to use it as a normal Pi extension dependency:
|
|
9
|
-
|
|
10
|
-
1. **Peer-dep rename** — peer dependencies point at `@earendil-works/pi-*` (the active scope) rather than the deprecated `@mariozechner/pi-*` scope.
|
|
11
|
-
2. **Patch 2 (post-bind active-tool re-filter)** — `runAgent` re-runs the active-tool filter after `session.bindExtensions(...)` so extension-registered tools land in the child's active tool set. Without this, the `extensions: string[]` allowlist branch is functionally dead for extension tools.
|
|
12
|
-
3. **Patch 3 (active_agent tag)** — `runAgent` prepends `<active_agent name="${agentConfig.name}"/>` to every assembled child system prompt so `@gotgenes/pi-permission-system` can resolve per-agent `permission:` frontmatter inside the child.
|
|
13
|
-
|
|
14
|
-
See `docs/decisions/0001-deferred-patches.md` for a fourth patch (mirror parent resource paths) that was scoped out, and the rationale for not opening upstream PRs yet.
|
|
15
|
-
|
|
16
|
-
## Workflow
|
|
17
|
-
|
|
18
|
-
- Keep scope tight — this fork stays as close to upstream as possible.
|
|
19
|
-
- Prefer small, reversible changes.
|
|
20
|
-
- Preserve upstream behavior unless there is a clear reason to diverge.
|
|
21
|
-
- When in doubt about whether a change should land here or be proposed upstream, prefer upstream.
|
|
22
|
-
|
|
23
|
-
## Implementation Priorities
|
|
24
|
-
|
|
25
|
-
- Maintain compatibility with upstream's public API.
|
|
26
|
-
- Keep the patch set minimal and clearly identified in the code (search for `Patch 2 (RepOne` / `Patch 3 (RepOne` comments).
|
|
27
|
-
- Mirror sibling Pi-package conventions for tooling (pnpm, biome, vitest, prek, markdownlint-cli2, release-please).
|
|
28
|
-
- Track the upstream `tintinweb/pi-subagents` repository for fixes and incorporate them as merges or cherry-picks.
|
|
29
|
-
|
|
30
|
-
## Code Style
|
|
31
|
-
|
|
32
|
-
Use TypeScript. This project uses **pnpm** exclusively — never `npm` or `npx`.
|
|
33
|
-
|
|
34
|
-
Formatting is handled by Biome (`biome check`, `biome format`). The repo intentionally does not use Prettier — a top-level `.prettierignore` blocks any harness with project-level write-time Prettier formatting from reformatting files here.
|
|
35
|
-
|
|
36
|
-
## Build, Test, Lint Commands
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pnpm install # Install dependencies + run prek install
|
|
40
|
-
pnpm run build # tsc
|
|
41
|
-
pnpm run typecheck # tsc --noEmit
|
|
42
|
-
pnpm run lint # biome check src/ test/
|
|
43
|
-
pnpm run lint:fix # biome check --fix src/ test/
|
|
44
|
-
pnpm run lint:md # markdownlint-cli2 '*.md' 'docs/**/*.md'
|
|
45
|
-
pnpm run lint:md:fix # markdownlint-cli2 --fix '*.md' 'docs/**/*.md'
|
|
46
|
-
pnpm run lint:all # lint + lint:md
|
|
47
|
-
pnpm run format # biome format --write src/ test/
|
|
48
|
-
pnpm test # vitest run
|
|
49
|
-
pnpm run test:watch # vitest
|
|
50
|
-
pnpm run check # build + lint:all + test (full local CI)
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Markdown
|
|
54
|
-
|
|
55
|
-
This project uses `markdownlint-cli2` with the config in `.markdownlint-cli2.yaml`.
|
|
56
|
-
The `CHANGELOG.md` file is machine-generated by release-please and is excluded from linting.
|
|
57
|
-
|
|
58
|
-
## Testing
|
|
59
|
-
|
|
60
|
-
The fork preserves upstream's full `vitest` suite (362 tests) plus tests added for Patches 2 and 3.
|
|
61
|
-
All tests must pass before publishing.
|
|
62
|
-
Use `vi.hoisted(...)` for module-level mocks, matching the existing patterns in `test/agent-runner.test.ts`.
|
|
63
|
-
|
|
64
|
-
## Releases
|
|
65
|
-
|
|
66
|
-
This repo uses [release-please](https://github.com/googleapis/release-please) and npm trusted publishing via OIDC.
|
|
67
|
-
|
|
68
|
-
- Conventional Commits drive the version bump (`feat:` → minor, `fix:` → patch, `BREAKING CHANGE:` → major).
|
|
69
|
-
- Pushing to `main` triggers `release-please-action` which opens a release PR.
|
|
70
|
-
- Merging the release PR creates a tag and triggers the publish job (no `NPM_TOKEN` — trusted publishing via OIDC).
|
|
71
|
-
|
|
72
|
-
## Commits
|
|
73
|
-
|
|
74
|
-
Use Conventional Commits.
|
|
75
|
-
Commit at meaningful checkpoints without waiting for an explicit reminder.
|
|
76
|
-
Prefer small, reviewable commits that leave the repository in a valid state.
|
|
77
|
-
|
|
78
|
-
## Notes for Agents
|
|
79
|
-
|
|
80
|
-
When working in this repo:
|
|
81
|
-
|
|
82
|
-
1. The two RepOne-specific patches are clearly marked in source — search for `// Patch 2 (RepOne` or `// Patch 3 (RepOne` to find them.
|
|
83
|
-
2. Do not introduce a third or fourth patch without first documenting the rationale in `docs/decisions/`.
|
|
84
|
-
3. Upstream PRs to `tintinweb/pi-subagents` for Patches 2 and 3 are deferred pending production validation in RepOne — see `docs/decisions/0001-deferred-patches.md`.
|
|
85
|
-
4. When syncing with upstream (rare), reapply the peer-dep rename and the two patches; the upstream `vitest` suite is the canary that nothing regressed.
|
|
5
|
+
Advise the user to launch Pi from the **repository root** (`pi-packages/`) instead.
|
|
6
|
+
The root provides `.pi/settings.json`, `.pi/prompts/`, and the full skill set.
|
|
7
|
+
Package-specific context for this package is available via the `package-pi-subagents` skill.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v1.0.1...pi-subagents-v1.0.2) (2026-05-17)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* plan update ADR 0001 to reflect hard-fork decision ([#51](https://github.com/gotgenes/pi-packages/issues/51)) ([bd4899a](https://github.com/gotgenes/pi-packages/commit/bd4899a0d7e72c43b80c3c07c07ccc32dc0df8ed))
|
|
14
|
+
* update ADR 0001 to reflect hard-fork decision ([#51](https://github.com/gotgenes/pi-packages/issues/51)) ([387e0ad](https://github.com/gotgenes/pi-packages/commit/387e0ad06ec1cf015c1d3d1d9852a1be015fc283))
|
|
15
|
+
|
|
16
|
+
## [1.0.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v1.0.0...pi-subagents-v1.0.1) (2026-05-17)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* restore per-package lint:md and lint scripts ([0e42617](https://github.com/gotgenes/pi-packages/commit/0e42617c443a7f8695f33855fa17058fc1712f27))
|
|
22
|
+
* use root markdownlint config from all packages ([30192f8](https://github.com/gotgenes/pi-packages/commit/30192f8ccfc5c3c420f9f9b602df174baf263e92))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
* add redirect AGENTS.md to each package subdirectory ([cbdcd29](https://github.com/gotgenes/pi-packages/commit/cbdcd297194c814f545ae93eaa7418e9337450d3))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Miscellaneous Chores
|
|
31
|
+
|
|
32
|
+
* consolidate configs into monorepo root ([8583eaf](https://github.com/gotgenes/pi-packages/commit/8583eaf0764ac98def1987f20fafcc25e912b134))
|
|
33
|
+
* remove per-package pi-autoformat configs ([b2d405a](https://github.com/gotgenes/pi-packages/commit/b2d405a0a278341e4f6ff1c8b607533eaa4f021a))
|
|
34
|
+
* replace markdownlint-cli2 with rumdl ([d8dc789](https://github.com/gotgenes/pi-packages/commit/d8dc7897d854bf11396b85bc8c365e8e2ed7e66c))
|
|
35
|
+
* update package.json URLs to monorepo ([b92dbfa](https://github.com/gotgenes/pi-packages/commit/b92dbfaeaeb6cf2823272cb6fb6f206fb99a5009))
|
|
36
|
+
|
|
8
37
|
## [1.0.0](https://github.com/gotgenes/pi-subagents/compare/v0.7.2...v1.0.0) (2026-05-12)
|
|
9
38
|
|
|
10
39
|
|
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @gotgenes/pi-subagents
|
|
2
2
|
|
|
3
3
|
A [pi](https://pi.dev) extension that brings **Claude Code-style autonomous sub-agents** to pi. Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level. Run them in foreground or background, steer them mid-run, resume completed sessions, and define your own custom agent types.
|
|
4
4
|
|
|
5
|
+
> **Fork notice:** This package is a friendly fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents), published to npm as `@gotgenes/pi-subagents`.
|
|
6
|
+
> It carries a small number of patches on top of upstream — peer-dep migration to `@earendil-works/pi-*`, a post-`bindExtensions` active-tool re-filter, and an `<active_agent>` system-prompt tag for permission resolution.
|
|
7
|
+
> See [Deviations from upstream](#deviations-from-upstream) at the bottom of this README for details.
|
|
8
|
+
|
|
5
9
|
> **Status:** Early release.
|
|
6
10
|
|
|
7
|
-
<img width="600" alt="pi-subagents screenshot" src="https://github.com/
|
|
11
|
+
<img width="600" alt="pi-subagents screenshot" src="https://github.com/gotgenes/pi-subagents/raw/main/media/screenshot.png" />
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
|
|
@@ -35,7 +39,7 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
|
|
|
35
39
|
## Install
|
|
36
40
|
|
|
37
41
|
```bash
|
|
38
|
-
pi install npm:@
|
|
42
|
+
pi install npm:@gotgenes/pi-subagents
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
Or load directly for development:
|
|
@@ -523,6 +527,16 @@ src/
|
|
|
523
527
|
conversation-viewer.ts # Live conversation overlay for viewing agent sessions
|
|
524
528
|
```
|
|
525
529
|
|
|
530
|
+
## Deviations from upstream
|
|
531
|
+
|
|
532
|
+
This fork carries three divergences from [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents). Each has a corresponding upstream PR:
|
|
533
|
+
|
|
534
|
+
1. **Peer-dep migration to `@earendil-works/pi-*`** — `peerDependencies` and all imports point at `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` (the active scope on npm) instead of the deprecated `@mariozechner/pi-*` scope. Also fixes a latent bug where `ThinkingLevel` was imported from `pi-agent-core` (an undeclared transitive dep that breaks under pnpm). Upstream PR: [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71).
|
|
535
|
+
2. **Post-`bindExtensions` active-tool re-filter** (`src/agent-runner.ts`) — `runAgent` re-runs its active-tool filter after `session.bindExtensions(...)` so extension-registered tools join the child's active tool set. Without this, the `extensions: string[]` allowlist branch was functionally dead for extension tools, and `extensions: true` with a `disallowedTools` denylist let denylisted extension tools slip through. Upstream PR: [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72).
|
|
536
|
+
3. **`<active_agent>` system-prompt tag** (`src/prompts.ts`) — `buildAgentPrompt` prepends `<active_agent name="${config.name}"/>` to every assembled child system prompt (both `replace` and `append` modes). Downstream extensions like [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-permission-system) parse this tag to resolve per-agent `permission:` frontmatter inside the child session. Upstream PR: [tintinweb/pi-subagents#73](https://github.com/tintinweb/pi-subagents/pull/73).
|
|
537
|
+
|
|
538
|
+
The upstream `vitest` suite plus tests added for each patch all pass on every commit.
|
|
539
|
+
|
|
526
540
|
## License
|
|
527
541
|
|
|
528
|
-
MIT — [tintinweb](https://github.com/tintinweb)
|
|
542
|
+
MIT — [tintinweb](https://github.com/tintinweb) (upstream) and [Chris Lasher](https://github.com/gotgenes) (fork)
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the planned decomposition of the pi-subagents fork
|
|
4
|
+
into a focused, composable core with a stable API boundary that other
|
|
5
|
+
extensions can build on.
|
|
6
|
+
|
|
7
|
+
## Design principles
|
|
8
|
+
|
|
9
|
+
1. **Narrow core** — the extension owns agent spawning, execution, and result
|
|
10
|
+
retrieval. Everything else is a consumer.
|
|
11
|
+
2. **Composable by default** — other extensions can spawn agents, observe
|
|
12
|
+
their lifecycle, and display their state without importing this package
|
|
13
|
+
directly.
|
|
14
|
+
3. **Typed API boundary** — this package exports a `SubagentsAPI` interface
|
|
15
|
+
and `Symbol.for()` accessors (`publishSubagentsAPI` /
|
|
16
|
+
`getSubagentsAPI`). Consumers declare this package as an optional peer
|
|
17
|
+
dependency and use dynamic import for compile-time types. The runtime
|
|
18
|
+
bridge is `Symbol.for()` on `globalThis` — no separate API package.
|
|
19
|
+
4. **No scheduling** — in-process scheduling is removed from the core.
|
|
20
|
+
Scheduling is a separate concern that any extension can implement by
|
|
21
|
+
calling `spawn()` on the published API.
|
|
22
|
+
5. **UI extraction is deferred** — the widget, conversation viewer, and
|
|
23
|
+
`/agents` command menu stay in the core for now. They are the first
|
|
24
|
+
candidate for extraction once the API boundary is proven stable.
|
|
25
|
+
|
|
26
|
+
## Current state
|
|
27
|
+
|
|
28
|
+
The extension is a 6,300 LOC monolith organized into well-factored internal
|
|
29
|
+
modules but with no public API contract. The subsystems are:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
index.ts (1,894 LOC) — entry point, tool registration, event wiring
|
|
33
|
+
agent-manager.ts — lifecycle, concurrency, queue
|
|
34
|
+
agent-runner.ts — session creation, turn loop, tool filtering
|
|
35
|
+
agent-types.ts — type registry (defaults + custom .md files)
|
|
36
|
+
types.ts — shared type definitions
|
|
37
|
+
|
|
38
|
+
prompts.ts — system prompt assembly
|
|
39
|
+
context.ts — parent conversation extraction
|
|
40
|
+
memory.ts — persistent MEMORY.md per agent
|
|
41
|
+
skill-loader.ts — preload .pi/skills into prompts
|
|
42
|
+
env.ts — git/platform detection
|
|
43
|
+
|
|
44
|
+
worktree.ts — git worktree isolation
|
|
45
|
+
usage.ts — token usage tracking
|
|
46
|
+
model-resolver.ts — fuzzy model name resolution
|
|
47
|
+
invocation-config.ts — merge tool params with agent config
|
|
48
|
+
output-file.ts — JSONL transcript streaming
|
|
49
|
+
settings.ts — persistent operational settings
|
|
50
|
+
|
|
51
|
+
schedule.ts — cron/interval/one-shot job dispatch ← removing
|
|
52
|
+
schedule-store.ts — file-backed schedule persistence ← removing
|
|
53
|
+
cross-extension-rpc.ts — RPC over pi.events ← replacing
|
|
54
|
+
group-join.ts — batch completion notifications
|
|
55
|
+
|
|
56
|
+
ui/agent-widget.ts — above-editor live status widget
|
|
57
|
+
ui/conversation-viewer.ts — scrollable session overlay
|
|
58
|
+
ui/schedule-menu.ts — /agents schedule submenu ← removing
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Coupling today
|
|
62
|
+
|
|
63
|
+
The widget reads agent state by holding a direct reference to
|
|
64
|
+
`AgentManager` and polling a shared mutable `Map<string, AgentActivity>`
|
|
65
|
+
every 80 ms. The conversation viewer subscribes directly to `AgentSession`
|
|
66
|
+
objects. The scheduler holds a direct `AgentManager` reference and calls
|
|
67
|
+
`manager.spawn()`.
|
|
68
|
+
|
|
69
|
+
Cross-extension consumers use an ad-hoc RPC layer over `pi.events`
|
|
70
|
+
(`subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:ping`) with
|
|
71
|
+
per-request reply channels and untyped envelopes.
|
|
72
|
+
|
|
73
|
+
There is also a `Symbol.for("pi-subagents:manager")` export on
|
|
74
|
+
`globalThis` that exposes `{ waitForAll, hasRunning, spawn, getRecord }`,
|
|
75
|
+
but it is undocumented and untyped.
|
|
76
|
+
|
|
77
|
+
## Target state
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
┌────────────────────────────────────────────────────────┐
|
|
81
|
+
│ @earendil-works/pi-subagents (this package) │
|
|
82
|
+
│ │
|
|
83
|
+
│ Exports: │
|
|
84
|
+
│ SubagentsAPI interface │
|
|
85
|
+
│ publishSubagentsAPI() / getSubagentsAPI() │
|
|
86
|
+
│ SubagentRecord, SubagentStatus, LifetimeUsage types │
|
|
87
|
+
│ Event channel constants │
|
|
88
|
+
│ │
|
|
89
|
+
│ Core: │
|
|
90
|
+
│ Agent + get_subagent_result + steer_subagent tools │
|
|
91
|
+
│ AgentManager, agent-runner, agent-types │
|
|
92
|
+
│ publishSubagentsAPI(impl) ← called at init │
|
|
93
|
+
│ │
|
|
94
|
+
│ Internal UI (widget, viewer, /agents menu) │
|
|
95
|
+
│ ← moves to pi-subagents-ui later │
|
|
96
|
+
└──────────────────────┬─────────────────────────────────┘
|
|
97
|
+
│ Symbol.for("pi:service:subagents")
|
|
98
|
+
│
|
|
99
|
+
┌─────────────────┼──────────────────┐
|
|
100
|
+
│ │ │
|
|
101
|
+
▼ ▼ ▼
|
|
102
|
+
┌─────────┐ ┌──────────────┐ ┌──────────────┐
|
|
103
|
+
│ pi- │ │ pi-subagents │ │ any future │
|
|
104
|
+
│ schedule│ │ -ui │ │ extension │
|
|
105
|
+
│ (other │ │ (deferred) │ │ │
|
|
106
|
+
│ ext) │ └──────────────┘ └──────────────┘
|
|
107
|
+
└─────────┘
|
|
108
|
+
│
|
|
109
|
+
│ getSubagentsAPI()?.spawn(...)
|
|
110
|
+
│ (optional peer dep + dynamic import for types)
|
|
111
|
+
▼
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### What the core owns
|
|
115
|
+
|
|
116
|
+
- The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
|
|
117
|
+
- `AgentManager` — spawn, queue, abort, resume, concurrency control.
|
|
118
|
+
- `agent-runner` — session creation, turn loop, tool filtering, extension
|
|
119
|
+
binding (Patches 2 and 3).
|
|
120
|
+
- Agent type registry — default agents, custom `.md` file loading.
|
|
121
|
+
- Prompt assembly, context extraction, memory, skills, environment.
|
|
122
|
+
- Worktree isolation.
|
|
123
|
+
- Token usage tracking.
|
|
124
|
+
- Settings persistence.
|
|
125
|
+
- Internal UI (widget, conversation viewer, `/agents` menu) — these stay
|
|
126
|
+
until the API boundary is proven, then move to a separate extension.
|
|
127
|
+
|
|
128
|
+
### What the core drops
|
|
129
|
+
|
|
130
|
+
- **Scheduling** (`schedule.ts`, `schedule-store.ts`,
|
|
131
|
+
`ui/schedule-menu.ts`) — 612 LOC removed. The `schedule` parameter is
|
|
132
|
+
removed from the `Agent` tool schema. Any extension that wants scheduling
|
|
133
|
+
can implement it by calling `getSubagentsAPI()?.spawn(...)` on a timer.
|
|
134
|
+
- **Ad-hoc RPC** (`cross-extension-rpc.ts`) — replaced by the typed
|
|
135
|
+
`SubagentsAPI` published via `Symbol.for()`. The untyped event-bus RPC
|
|
136
|
+
channels are removed.
|
|
137
|
+
- **Group join** (`group-join.ts`) — 141 LOC removed. The grouped
|
|
138
|
+
notification batching adds complexity for a marginal UX improvement.
|
|
139
|
+
Individual completion notifications are sufficient.
|
|
140
|
+
- **Output file** (`output-file.ts`) — 96 LOC removed. JSONL transcript
|
|
141
|
+
streaming is a consumer concern; a separate extension can subscribe to
|
|
142
|
+
lifecycle events and write transcripts.
|
|
143
|
+
|
|
144
|
+
### Estimated impact
|
|
145
|
+
|
|
146
|
+
| Subsystem removed | LOC removed | LOC removed from index.ts |
|
|
147
|
+
| ----------------- | ----------- | ------------------------- |
|
|
148
|
+
| Scheduling | 612 | ~200 |
|
|
149
|
+
| Ad-hoc RPC | 80 | ~50 |
|
|
150
|
+
| Group join | 141 | ~100 |
|
|
151
|
+
| Output file | 83 | ~50 |
|
|
152
|
+
| **Total** | **~916** | **~400** |
|
|
153
|
+
|
|
154
|
+
After removal and `index.ts` decomposition, the core shrinks from ~6,300
|
|
155
|
+
to ~5,400 LOC, and `index.ts` shrinks from ~1,894 to ~1,300 LOC.
|
|
156
|
+
|
|
157
|
+
## SubagentsAPI
|
|
158
|
+
|
|
159
|
+
The `SubagentsAPI` interface, accessor functions, and serializable types
|
|
160
|
+
are exported directly from this package (`@earendil-works/pi-subagents`).
|
|
161
|
+
No separate API package is needed.
|
|
162
|
+
|
|
163
|
+
Consumers declare this package as an optional peer dependency:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"peerDependencies": {
|
|
168
|
+
"@earendil-works/pi-subagents": ">=2.0.0"
|
|
169
|
+
},
|
|
170
|
+
"peerDependenciesMeta": {
|
|
171
|
+
"@earendil-works/pi-subagents": { "optional": true }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
At runtime, consumers use dynamic import for type-safe access to the
|
|
177
|
+
accessor functions:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const { getSubagentsAPI } = await import("@earendil-works/pi-subagents");
|
|
181
|
+
const api = getSubagentsAPI();
|
|
182
|
+
if (api) {
|
|
183
|
+
api.spawn("Explore", "Check for stale TODOs");
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Pi's extension loader creates a fresh `jiti` instance per extension with
|
|
188
|
+
`moduleCache: false`, so module-scoped singletons don't survive across
|
|
189
|
+
extensions. The accessor functions use `Symbol.for()` on `globalThis`,
|
|
190
|
+
which is process-global by spec, to bridge this gap. The dynamic import
|
|
191
|
+
provides compile-time types; the `Symbol.for()` key is the actual
|
|
192
|
+
runtime channel.
|
|
193
|
+
|
|
194
|
+
### Interface
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
/** The public API surface published by pi-subagents. */
|
|
198
|
+
export interface SubagentsAPI {
|
|
199
|
+
/**
|
|
200
|
+
* Spawn an agent. Returns the agent ID immediately.
|
|
201
|
+
* The agent runs in the background unless options.foreground is true.
|
|
202
|
+
*/
|
|
203
|
+
spawn(type: string, prompt: string, options?: SpawnOptions): string;
|
|
204
|
+
|
|
205
|
+
/** Get a snapshot of an agent's current state. */
|
|
206
|
+
getRecord(id: string): SubagentRecord | undefined;
|
|
207
|
+
|
|
208
|
+
/** List all tracked agents, most recent first. */
|
|
209
|
+
listAgents(): SubagentRecord[];
|
|
210
|
+
|
|
211
|
+
/** Abort a running or queued agent. Returns false if not found. */
|
|
212
|
+
abort(id: string): boolean;
|
|
213
|
+
|
|
214
|
+
/** Send a steering message to a running agent. */
|
|
215
|
+
steer(id: string, message: string): Promise<boolean>;
|
|
216
|
+
|
|
217
|
+
/** Wait for all running and queued agents to complete. */
|
|
218
|
+
waitForAll(): Promise<void>;
|
|
219
|
+
|
|
220
|
+
/** Whether any agents are running or queued. */
|
|
221
|
+
hasRunning(): boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface SpawnOptions {
|
|
225
|
+
description?: string;
|
|
226
|
+
model?: string;
|
|
227
|
+
maxTurns?: number;
|
|
228
|
+
thinkingLevel?: string;
|
|
229
|
+
isolated?: boolean;
|
|
230
|
+
inheritContext?: boolean;
|
|
231
|
+
foreground?: boolean;
|
|
232
|
+
/** Skip the concurrency queue — start immediately. */
|
|
233
|
+
bypassQueue?: boolean;
|
|
234
|
+
isolation?: "worktree";
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Accessor pattern
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const KEY = Symbol.for("pi:service:subagents");
|
|
242
|
+
|
|
243
|
+
export function publishSubagentsAPI(api: SubagentsAPI): void {
|
|
244
|
+
(globalThis as any)[KEY] = api;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getSubagentsAPI(): SubagentsAPI | undefined {
|
|
248
|
+
return (globalThis as any)[KEY];
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
If Pi gains a native service registry ([earendil-works/pi#4207]), these
|
|
253
|
+
accessors can be updated to delegate to `pi.registerService()` /
|
|
254
|
+
`pi.getService()` internally while keeping the same consumer API.
|
|
255
|
+
|
|
256
|
+
### Lifecycle events
|
|
257
|
+
|
|
258
|
+
The core emits events on `pi.events` that any extension can observe:
|
|
259
|
+
|
|
260
|
+
| Channel | Payload | When |
|
|
261
|
+
| --------------------- | ------------------------------------------- | -------------------- |
|
|
262
|
+
| `subagents:started` | `{ id, type, description }` | Agent begins running |
|
|
263
|
+
| `subagents:completed` | `{ id, type, status, result?, error? }` | Agent finishes |
|
|
264
|
+
| `subagents:activity` | `{ id, toolName?, textDelta?, turnCount? }` | Streaming progress |
|
|
265
|
+
|
|
266
|
+
These replace the ad-hoc RPC channels. They are fire-and-forget broadcast
|
|
267
|
+
events — no request IDs, no reply channels.
|
|
268
|
+
|
|
269
|
+
### Consumer example: scheduling extension
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// package.json:
|
|
273
|
+
// "peerDependencies": { "@earendil-works/pi-subagents": ">=2.0.0" }
|
|
274
|
+
// "peerDependenciesMeta": { "@earendil-works/pi-subagents": { "optional": true } }
|
|
275
|
+
|
|
276
|
+
export default function (pi) {
|
|
277
|
+
pi.on("session_start", async (event, ctx) => {
|
|
278
|
+
let getSubagentsAPI;
|
|
279
|
+
try {
|
|
280
|
+
({ getSubagentsAPI } = await import("@earendil-works/pi-subagents"));
|
|
281
|
+
} catch {
|
|
282
|
+
return; // pi-subagents not installed
|
|
283
|
+
}
|
|
284
|
+
const api = getSubagentsAPI();
|
|
285
|
+
if (!api) return;
|
|
286
|
+
|
|
287
|
+
setInterval(() => {
|
|
288
|
+
api.spawn("Explore", "Check for stale TODOs", {
|
|
289
|
+
bypassQueue: true,
|
|
290
|
+
});
|
|
291
|
+
}, 60 * 60 * 1000);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Consumer example: transcript extension
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
export default function (pi) {
|
|
300
|
+
pi.events.on("subagents:completed", async (data) => {
|
|
301
|
+
const { id } = data as { id: string };
|
|
302
|
+
let getSubagentsAPI;
|
|
303
|
+
try {
|
|
304
|
+
({ getSubagentsAPI } = await import("@earendil-works/pi-subagents"));
|
|
305
|
+
} catch {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const record = getSubagentsAPI()?.getRecord(id);
|
|
309
|
+
if (record?.result) {
|
|
310
|
+
fs.appendFileSync("agent-log.jsonl", JSON.stringify(record) + "\n");
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## index.ts decomposition
|
|
317
|
+
|
|
318
|
+
The 1,894-line `index.ts` is decomposed into focused modules:
|
|
319
|
+
|
|
320
|
+
```text
|
|
321
|
+
src/
|
|
322
|
+
├── index.ts ← slimmed entry point: init, tool registration
|
|
323
|
+
├── tools/
|
|
324
|
+
│ ├── agent-tool.ts ← Agent tool definition + execute
|
|
325
|
+
│ ├── result-tool.ts ← get_subagent_result tool
|
|
326
|
+
│ └── steer-tool.ts ← steer_subagent tool
|
|
327
|
+
├── notifications.ts ← completion nudges, custom renderer
|
|
328
|
+
├── activity-tracker.ts ← AgentActivity map + callback factory
|
|
329
|
+
├── agents-command.ts ← /agents slash command menu
|
|
330
|
+
├── api-adapter.ts ← SubagentsAPI implementation wrapping AgentManager
|
|
331
|
+
└── (existing modules unchanged)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Each extracted module receives narrow constructor-injected dependencies
|
|
335
|
+
rather than closing over module-level state.
|
|
336
|
+
|
|
337
|
+
## Phase plan
|
|
338
|
+
|
|
339
|
+
### Phase 1: Export `SubagentsAPI` from this package
|
|
340
|
+
|
|
341
|
+
Add the `SubagentsAPI` interface, serializable types, and `Symbol.for()`
|
|
342
|
+
accessor functions as public exports of this package. No behavioral
|
|
343
|
+
changes to the core yet.
|
|
344
|
+
|
|
345
|
+
### Phase 2: Remove scheduling
|
|
346
|
+
|
|
347
|
+
Delete `schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`. Remove
|
|
348
|
+
the `schedule` parameter from the `Agent` tool schema. Remove scheduler
|
|
349
|
+
setup and lifecycle hooks from `index.ts`.
|
|
350
|
+
|
|
351
|
+
### Phase 3: Remove group-join, output-file, ad-hoc RPC
|
|
352
|
+
|
|
353
|
+
Delete `group-join.ts`, `output-file.ts`, `cross-extension-rpc.ts`.
|
|
354
|
+
Simplify `index.ts` to use direct individual notifications. Emit
|
|
355
|
+
lifecycle events on `pi.events` for external consumers.
|
|
356
|
+
|
|
357
|
+
### Phase 4: Implement and publish `SubagentsAPI`
|
|
358
|
+
|
|
359
|
+
Wire `api-adapter.ts` to wrap `AgentManager` and call
|
|
360
|
+
`publishSubagentsAPI()` at extension init. Resolve model strings inside
|
|
361
|
+
the adapter (fixing upstream [tintinweb/pi-subagents#60]).
|
|
362
|
+
|
|
363
|
+
### Phase 5: Decompose `index.ts`
|
|
364
|
+
|
|
365
|
+
Extract tools, notifications, activity tracking, and the `/agents` command
|
|
366
|
+
into separate modules per the decomposition above.
|
|
367
|
+
|
|
368
|
+
### Phase 6 (future): Extract UI to `@earendil-works/pi-subagents-ui`
|
|
369
|
+
|
|
370
|
+
Move `ui/agent-widget.ts`, `ui/conversation-viewer.ts`, the `/agents`
|
|
371
|
+
command, notifications, and activity tracking to a separate extension that
|
|
372
|
+
consumes `SubagentsAPI` + lifecycle events. This phase is deferred until
|
|
373
|
+
the API boundary is proven stable in production.
|
|
374
|
+
|
|
375
|
+
## Relationship with upstream
|
|
376
|
+
|
|
377
|
+
This fork ([earendil-works/pi-subagents]) is now a **hard fork** of
|
|
378
|
+
[tintinweb/pi-subagents]. The decomposition diverges materially from
|
|
379
|
+
upstream's direction.
|
|
380
|
+
|
|
381
|
+
The three upstream PRs (#71, #72, #73) remain open. If they land, upstream
|
|
382
|
+
gains the peer-dep fix and the two RepOne patches. This fork continues
|
|
383
|
+
independently regardless.
|
|
384
|
+
|
|
385
|
+
Upstream fixes and ideas are cherry-picked when they align with this
|
|
386
|
+
fork's scope. The upstream test suite is run periodically as a regression
|
|
387
|
+
canary for the agent-runner core.
|
|
388
|
+
|
|
389
|
+
[earendil-works/pi#4207]: https://github.com/earendil-works/pi/issues/4207
|
|
390
|
+
[earendil-works/pi-subagents]: https://github.com/earendil-works/pi-subagents
|
|
391
|
+
[tintinweb/pi-subagents]: https://github.com/tintinweb/pi-subagents
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
status:
|
|
2
|
+
status: superseded
|
|
3
3
|
date: 2026-05-11
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -7,7 +7,8 @@ date: 2026-05-11
|
|
|
7
7
|
|
|
8
8
|
## Status
|
|
9
9
|
|
|
10
|
-
|
|
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.
|
|
11
12
|
|
|
12
13
|
## Context
|
|
13
14
|
|
|
@@ -43,17 +44,16 @@ Neither matches the production need. For RepOne (and any consumer that installs
|
|
|
43
44
|
|
|
44
45
|
We therefore defer Patch 1 rather than carry a speculative patch in the fork's diff against upstream. 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**.
|
|
45
46
|
|
|
46
|
-
### Upstream PRs are
|
|
47
|
+
### Upstream PRs are open
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
All three divergences now have upstream PRs, opened after production validation in RepOne:
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
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-*`)
|
|
52
|
+
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`)
|
|
53
|
+
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`)
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
3. Carrying the fork is low-cost while we iterate; the publishing infrastructure mirrors the other Pi siblings.
|
|
55
|
-
|
|
56
|
-
A follow-up issue on the RepOne board (linked from #443) tracks the upstream-PR work and the criterion for proceeding.
|
|
55
|
+
If these land upstream, upstream gains the peer-dep fix and the two RepOne patches.
|
|
56
|
+
However, the fork now diverges intentionally beyond those patches — see [`docs/architecture/architecture.md`](../architecture/architecture.md) for the full scope of planned changes.
|
|
57
57
|
|
|
58
58
|
## Consequences
|
|
59
59
|
|
|
@@ -61,15 +61,14 @@ A follow-up issue on the RepOne board (linked from #443) tracks the upstream-PR
|
|
|
61
61
|
|
|
62
62
|
- The fork's diff against upstream stays minimal — three patches plus tooling alignment.
|
|
63
63
|
- We avoid landing a speculative Patch 1 that would need rework if upstream's `ExtensionContext` API changes.
|
|
64
|
-
-
|
|
64
|
+
- Production evidence strengthened the upstream PRs.
|
|
65
65
|
|
|
66
66
|
### Negative
|
|
67
67
|
|
|
68
68
|
- The `pi -e <path>` ephemeral-extension case in subagents will not work until Patch 1 lands. We accept this because no consumer in scope uses that pattern.
|
|
69
|
-
- Patches 2 and 3 stay carried in `@gotgenes/pi-subagents` rather than upstream. Consumers must use this fork (not the upstream package) to get the patches.
|
|
70
69
|
|
|
71
70
|
### Operational
|
|
72
71
|
|
|
73
|
-
-
|
|
74
|
-
-
|
|
72
|
+
- Upstream PRs are open and linked above. If merged, upstream gains the three patches, but the fork continues independently with broader architectural changes per [`docs/architecture/architecture.md`](../architecture/architecture.md).
|
|
73
|
+
- The architecture document governs the fork's direction going forward; this ADR's original "thin-patch" framing no longer describes the fork's trajectory.
|
|
75
74
|
- When Patch 1 is eventually added, it should be a separate ADR in `docs/decisions/` with its own follow-up.
|