@henryqw/pi-subagent 4.0.1 → 4.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.
- package/CONTEXT.md +3 -3
- package/README.md +44 -28
- package/dist/index.d.ts +6 -0
- package/dist/index.js +52 -27
- package/docs/adr/001-composable-ephemeral-execution.md +1 -1
- package/docs/orchestration.md +20 -11
- package/examples/roles/implementer.md +12 -3
- package/examples/roles/reviewer.md +10 -2
- package/extensions/subagent.ts +1 -2
- package/package.json +5 -1
- package/skills/pi-subagent-delegated-development/SKILL.md +54 -0
package/CONTEXT.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Provide validated user Roles, shared task-model Pi launch policy, generic managed Herdr Subagent hosting,
|
|
5
|
+
Provide validated built-in and user Roles, shared task-model Pi launch policy, generic managed Herdr Subagent hosting, a `delegate_task` extension that runs one selected bounded workflow in one or more ephemeral child processes, and a bundled Main-side `pi-subagent-delegated-development` policy Skill. The Skill requires a clean committed Git `HEAD`, exact clean committed-diff review through a private byte-verified patch file reference, and post-validation recovery cleanup; it adds no runtime behavior or changes generic fallback.
|
|
6
6
|
|
|
7
7
|
## Domain glossary
|
|
8
8
|
|
|
9
9
|
- **Main**: Pi session delegating work.
|
|
10
10
|
- **Subagent**: isolated Pi child process handling one task.
|
|
11
|
-
- **Role**: user-owned Markdown definition of a reusable responsibility, with a name, description, system instructions, optional base tool allowlist, extensions, and Skill names.
|
|
11
|
+
- **Role**: package-shipped built-in or user-owned Markdown definition of a reusable responsibility, with a name, description, system instructions, optional base tool allowlist, extensions, and Skill names.
|
|
12
12
|
- **Model Class**: `fast`, `balanced`, `frontier`, or `fav`, assigned in shared task-model settings or overridden by Main from task complexity.
|
|
13
13
|
- **Route**: configured model and thinking-level pair selected from a shared Model Class profile; the primary route precedes its optional fallback.
|
|
14
14
|
- **Delegated Task**: one bounded work request sent from Main to one Role.
|
|
@@ -27,6 +27,6 @@ Provide validated user Roles, shared task-model Pi launch policy, generic manage
|
|
|
27
27
|
- Role Skill names resolve through Main's effective Pi Skill registry; unavailable names warn and skip without blocking delegation.
|
|
28
28
|
- Main selects Role and may override Model Class and thinking level per task; omitted class uses shared `pi-subagent/delegateTask` assignment, initially `balanced`. Library callers select Role plus their own shared task ID.
|
|
29
29
|
- The selected profile resolves primary then fallback only before launch when a route, model, or thinking level is unavailable. If neither route is usable, launch rejects with `Run /task-models`; a started child is never retried by this package.
|
|
30
|
-
- Role Markdown files and Subagent JSON config live only in the user `config/pi-subagent` directory; model routes live in shared `config/pi-task-models.json
|
|
30
|
+
- User Role Markdown files and Subagent JSON config live only in the user `config/pi-subagent` directory; model routes live in shared `config/pi-task-models.json`. Package-shipped built-in Roles (`implementer`, `reviewer`) resolve from the package's own `examples/roles/` Markdown through the same parser; a same-named user file explicitly overrides a built-in.
|
|
31
31
|
- Numbered Codex routes prefer Main's active account slot and explicitly load the multi-Codex child extension.
|
|
32
32
|
- Generic Herdr host functions validate workspace ownership and provisioning identity while callers retain domain state, prompts, and lifecycle decisions.
|
package/README.md
CHANGED
|
@@ -49,41 +49,71 @@ Parallel mode starts entries concurrently, waits for every entry, and reports th
|
|
|
49
49
|
|
|
50
50
|
Background workflows are session-scoped. Session shutdown or reload aborts them and may deliver only recoverable-work evidence or no follow-up message.
|
|
51
51
|
|
|
52
|
-
Each delegation resolves its own
|
|
52
|
+
Each delegation resolves its own Role, resources, route, and optional worktree request. When available, `isolation: worktree` gives each entry a deterministic separate worktree; non-Git or unborn-`HEAD` contexts may use Main's cwd. Siblings and chain steps never implicitly share one created worktree.
|
|
53
53
|
|
|
54
54
|
See [Orchestration, isolation, and the public API](./docs/orchestration.md) for the complete contract and JavaScript composition examples.
|
|
55
55
|
|
|
56
56
|
## Config
|
|
57
57
|
|
|
58
|
-
`~/.pi/agent/config/pi-subagent
|
|
58
|
+
pi-subagent owns the extension-named config directory `~/.pi/agent/config/pi-subagent/`, which holds two kinds of user-owned configuration: one Markdown file per Role (see [Roles](#roles)) and its own optional JSON file below. Model routing is *not* configured here; children resolve routes through the shared `@henryqw/pi-task-models` config at `~/.pi/agent/config/pi-task-models.json`.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
`~/.pi/agent/config/pi-subagent/pi-subagent.json` controls the ephemeral child pool and timeouts. All fields are optional; a missing file uses defaults.
|
|
61
|
+
|
|
62
|
+
| Field | Required | Possible values | Default |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| `maxSubagents` | No | Safe integer ≥ 1 | `5` |
|
|
65
|
+
| `timeout.idleMinutes` | No | Positive number of minutes where minutes × 60 000 ms ≤ 2,147,483,647 | `10` |
|
|
66
|
+
| `timeout.maxMinutes` | No | Positive number within the same ms cap that must be greater than `timeout.idleMinutes`, otherwise the whole `timeout` object falls back to defaults | `30` |
|
|
67
|
+
|
|
68
|
+
Excess children wait FIFO without consuming child timeout. `PI_SUBAGENT_MAX_SUBAGENTS` overrides `maxSubagents` for the session (positive integer; an invalid value prevents the extension from loading, leaving `delegate_task` unavailable).
|
|
69
|
+
|
|
70
|
+
This JSON is read leniently: malformed JSON, a non-object root, unknown keys, or invalid values are collected into one warning and the affected settings fall back to defaults; the file is never rewritten.
|
|
71
|
+
|
|
72
|
+
### Role frontmatter
|
|
73
|
+
|
|
74
|
+
Each Role `.md` file in the same directory accepts these frontmatter fields:
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
| Field | Required | Possible values | Default |
|
|
77
|
+
| --- | --- | --- | --- |
|
|
78
|
+
| `name` | Yes | Non-empty text; unique across roles | — |
|
|
79
|
+
| `description` | Yes | Non-empty text | — |
|
|
80
|
+
| `tools` | No | Array or comma-separated string of tool names | Omitted: caller tools win, else Pi defaults apply |
|
|
81
|
+
| `isolation` | No | `worktree` | None |
|
|
82
|
+
| `extensions` | No | Absolute paths, `~/…`, `file://`, or package sources (`npm:`, `git:`, `github:`, `https?:`, `ssh:`) | None |
|
|
83
|
+
| `skills` | No | Array or comma-separated string of skill names | None; unavailable names warn and skip |
|
|
84
|
+
| body | Yes | System-prompt Markdown after the frontmatter | — |
|
|
85
|
+
|
|
86
|
+
An unreadable or invalid Role file fails role loading fast; duplicate role names are rejected.
|
|
68
87
|
|
|
69
88
|
## Roles
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
The package ships two working built-in Roles, always available without any configuration:
|
|
91
|
+
|
|
92
|
+
- `implementer`: focused edits requesting worktree isolation; commits completed scoped changes locally and never pushes or opens PRs without authorization
|
|
93
|
+
- `reviewer`: read-only correctness review from a supplied exact patch file reference (path, bytes, SHA-256) and referenced files; never edits or commits
|
|
94
|
+
|
|
95
|
+
A same-named Markdown file in `~/.pi/agent/config/pi-subagent/` explicitly overrides the built-in default.
|
|
96
|
+
|
|
97
|
+
The repository also includes optional inert samples:
|
|
72
98
|
|
|
73
99
|
- [`scout`](./examples/roles/scout.md): read-only discovery
|
|
74
|
-
- [`implementer`](./examples/roles/implementer.md): focused edits requesting worktree isolation
|
|
75
|
-
- [`reviewer`](./examples/roles/reviewer.md): read-only correctness review
|
|
76
100
|
- [`synthesizer`](./examples/roles/synthesizer.md): reconcile supplied reports
|
|
77
101
|
|
|
78
|
-
Copy them manually from the
|
|
102
|
+
Copy them manually from your installed `@henryqw/pi-subagent` package (npm installs ship the `examples/roles/` directory) if you want them as a starting point:
|
|
79
103
|
|
|
80
104
|
```bash
|
|
81
105
|
mkdir -p ~/.pi/agent/config/pi-subagent
|
|
82
|
-
cp
|
|
106
|
+
cp <package-install-dir>/examples/roles/scout.md ~/.pi/agent/config/pi-subagent/
|
|
83
107
|
```
|
|
84
108
|
|
|
109
|
+
Locate the install directory with `npm root` inside your project, or via Pi's package installation path.
|
|
110
|
+
|
|
85
111
|
The package never installs or writes Role configuration. Sample names are not built-ins; after copying, edit or replace them as your own Roles.
|
|
86
112
|
|
|
113
|
+
## Skill
|
|
114
|
+
|
|
115
|
+
The bundled [`pi-subagent-delegated-development`](./skills/pi-subagent-delegated-development/SKILL.md) Skill is opinionated orchestration policy for Main. It fails before delegation unless Main's cwd is a clean Git working tree with a committed `HEAD`; then it requires a clean retained implementer worktree, a verified base/branch/tip committed diff, and review from a privately generated, byte-verified exact patch file reference (path, bytes, SHA-256) plus referenced files—not inline patch text. It re-checks Main before integrating the reviewed commit, preserves artifacts on integration or validation failure, and removes eligible retained worktrees, task branches, and temporary patch files only after focused validation passes. Dirty or incomplete work goes to a fresh repair delegation. This leaves the generic `delegate_task` non-Git/unborn-`HEAD` fallback unchanged. All model and agent work stays in Pi via `delegate_task`; deterministic developer tools remain allowed. It is guidance only — it adds no runtime code, config, or Role installation, and preserves the [composition-outside-the-executor](./docs/adr/001-composable-ephemeral-execution.md) boundary.
|
|
116
|
+
|
|
87
117
|
A Role owns its base tools, extensions, named Skills, instructions, and optional `isolation: worktree`. Ambient extension and Skill discovery is disabled in children. With neither Role tools nor caller tools, Pi defaults remain; caller tools with omitted Role tools snapshot Main's effective active built-ins and install the child policy. Loaded Role/caller extension tools still activate, parent-only tools stay excluded, and unavailable named Skills warn and skip.
|
|
88
118
|
|
|
89
119
|
## Library API
|
|
@@ -91,17 +121,3 @@ A Role owns its base tools, extensions, named Skills, instructions, and optional
|
|
|
91
121
|
The package root exports Role loading and launch resolution, `createEphemeralSubagentExecutor`, worktree helpers, and generic managed Herdr lifecycle helpers. The ephemeral executor is for code already running inside active Pi; it does not provide standalone Node.js Pi discovery or launch support.
|
|
92
122
|
|
|
93
123
|
Use [`docs/orchestration.md`](./docs/orchestration.md#public-role-and-executor-api) for exact API behavior and a post-permit `prepare` example using `resolveRoleLaunch` against the latest Pi context.
|
|
94
|
-
|
|
95
|
-
## Remove
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
pi remove npm:@henryqw/pi-subagent
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Development
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
npm test --workspace @henryqw/pi-subagent
|
|
105
|
-
npm run typecheck --workspace @henryqw/pi-subagent
|
|
106
|
-
npm run pack:check --workspace @henryqw/pi-subagent
|
|
107
|
-
```
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,12 @@ export interface ResolvedRoleSkills {
|
|
|
38
38
|
paths: string[];
|
|
39
39
|
missing: string[];
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Validated built-in implementer/reviewer Roles plus valid user roles from
|
|
43
|
+
* `config/pi-subagent`. A user role with a built-in name overrides the default;
|
|
44
|
+
* duplicate names among user files are an error. Missing user directory returns
|
|
45
|
+
* the built-ins.
|
|
46
|
+
*/
|
|
41
47
|
export declare function loadRoles(agentDir?: string): Role[];
|
|
42
48
|
export declare function resolveTaskRoute(ctx: ExtensionContext, profileName: ProfileName, agentDir?: string, thinking?: ThinkingLevel): ResolvedTaskRoute;
|
|
43
49
|
export declare function resolveRoleSkills(pi: Pick<ExtensionAPI, "getCommands">, role: Role): ResolvedRoleSkills;
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,53 @@ function validateExtension(extension, source) {
|
|
|
42
42
|
function extensionList(value, source) {
|
|
43
43
|
return stringList(value, "extensions", source).map((extension) => validateExtension(extension, source));
|
|
44
44
|
}
|
|
45
|
+
// Built-in Roles required by the bundled pi-subagent-delegated-development Skill;
|
|
46
|
+
// resolved from the package-shipped Markdown relative to this module.
|
|
47
|
+
const BUILTIN_ROLE_FILES = ["implementer.md", "reviewer.md"];
|
|
48
|
+
/** Single-file Role parser shared by built-in and user roles. */
|
|
49
|
+
function parseRoleFile(file, raw) {
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = parseFrontmatter(raw);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new Error(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
|
+
}
|
|
57
|
+
const frontmatter = parsed.frontmatter;
|
|
58
|
+
const isolation = frontmatter.isolation === undefined ? undefined : cleanText(frontmatter.isolation, "isolation", file);
|
|
59
|
+
if (isolation !== undefined && isolation !== "worktree")
|
|
60
|
+
throw new Error(`${file}: isolation must be "worktree".`);
|
|
61
|
+
return {
|
|
62
|
+
name: cleanText(frontmatter.name, "name", file),
|
|
63
|
+
description: cleanText(frontmatter.description, "description", file),
|
|
64
|
+
tools: frontmatter.tools === undefined ? undefined : stringList(frontmatter.tools, "tools", file, true),
|
|
65
|
+
isolation,
|
|
66
|
+
extensions: extensionList(frontmatter.extensions, file),
|
|
67
|
+
skills: stringList(frontmatter.skills, "skills", file),
|
|
68
|
+
systemPrompt: cleanText(parsed.body, "system prompt", file),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function readRoleFile(file) {
|
|
72
|
+
try {
|
|
73
|
+
return parseRoleFile(file, readFileSync(file, "utf8"));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error instanceof Error && error.message.startsWith(`${file}: `))
|
|
77
|
+
throw error;
|
|
78
|
+
throw new Error(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function builtinRoles() {
|
|
82
|
+
return BUILTIN_ROLE_FILES.map((name) => readRoleFile(fileURLToPath(new URL(`../examples/roles/${name}`, import.meta.url))));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Validated built-in implementer/reviewer Roles plus valid user roles from
|
|
86
|
+
* `config/pi-subagent`. A user role with a built-in name overrides the default;
|
|
87
|
+
* duplicate names among user files are an error. Missing user directory returns
|
|
88
|
+
* the built-ins.
|
|
89
|
+
*/
|
|
45
90
|
export function loadRoles(agentDir = getAgentDir()) {
|
|
91
|
+
const byName = new Map(builtinRoles().map((role) => [role.name, role]));
|
|
46
92
|
const dir = join(agentDir, "config", "pi-subagent");
|
|
47
93
|
let entries;
|
|
48
94
|
try {
|
|
@@ -50,42 +96,21 @@ export function loadRoles(agentDir = getAgentDir()) {
|
|
|
50
96
|
}
|
|
51
97
|
catch (error) {
|
|
52
98
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
53
|
-
return [];
|
|
99
|
+
return [...byName.values()];
|
|
54
100
|
throw error;
|
|
55
101
|
}
|
|
56
|
-
const
|
|
102
|
+
const userRoles = entries
|
|
57
103
|
.filter((entry) => entry.name.endsWith(".md") && (entry.isFile() || entry.isSymbolicLink()))
|
|
58
104
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
59
|
-
.map((entry) =>
|
|
60
|
-
const file = join(dir, entry.name);
|
|
61
|
-
let parsed;
|
|
62
|
-
try {
|
|
63
|
-
parsed = parseFrontmatter(readFileSync(file, "utf8"));
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
throw new Error(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
67
|
-
}
|
|
68
|
-
const frontmatter = parsed.frontmatter;
|
|
69
|
-
const isolation = frontmatter.isolation === undefined ? undefined : cleanText(frontmatter.isolation, "isolation", file);
|
|
70
|
-
if (isolation !== undefined && isolation !== "worktree")
|
|
71
|
-
throw new Error(`${file}: isolation must be "worktree".`);
|
|
72
|
-
return {
|
|
73
|
-
name: cleanText(frontmatter.name, "name", file),
|
|
74
|
-
description: cleanText(frontmatter.description, "description", file),
|
|
75
|
-
tools: frontmatter.tools === undefined ? undefined : stringList(frontmatter.tools, "tools", file, true),
|
|
76
|
-
isolation,
|
|
77
|
-
extensions: extensionList(frontmatter.extensions, file),
|
|
78
|
-
skills: stringList(frontmatter.skills, "skills", file),
|
|
79
|
-
systemPrompt: cleanText(parsed.body, "system prompt", file),
|
|
80
|
-
};
|
|
81
|
-
});
|
|
105
|
+
.map((entry) => readRoleFile(join(dir, entry.name)));
|
|
82
106
|
const names = new Set();
|
|
83
|
-
for (const role of
|
|
107
|
+
for (const role of userRoles) {
|
|
84
108
|
if (names.has(role.name))
|
|
85
109
|
throw new Error(`Duplicate Subagent role: ${role.name}.`);
|
|
86
110
|
names.add(role.name);
|
|
111
|
+
byName.set(role.name, role);
|
|
87
112
|
}
|
|
88
|
-
return
|
|
113
|
+
return [...byName.values()];
|
|
89
114
|
}
|
|
90
115
|
export function resolveTaskRoute(ctx, profileName, agentDir = getAgentDir(), thinking) {
|
|
91
116
|
let config;
|
|
@@ -12,7 +12,7 @@ Resource Policy is split at launch preparation:
|
|
|
12
12
|
- Caller may add explicit tools, extensions, and environment through `createRoleLaunch`.
|
|
13
13
|
- The executor receives the resulting Pi Launch and does not discover resources.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Built-in `implementer` and `reviewer` Roles ship with the package as Markdown in `examples/roles/` and resolve through the same single-file parser at load time; a same-named user file in `~/.pi/agent/config/pi-subagent/` explicitly overrides a built-in. Other repository samples are inert and user-owned only after manual copying to `~/.pi/agent/config/pi-subagent/`. This package does not install, copy, or write user configuration; copy instructions belong in downstream user documentation.
|
|
16
16
|
|
|
17
17
|
## Consequences
|
|
18
18
|
|
package/docs/orchestration.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`pi-subagent` separates tool policy from execution mechanism:
|
|
4
4
|
|
|
5
5
|
```text
|
|
6
|
-
user
|
|
6
|
+
Role (built-in or user override) + latest Pi registries ── resolveRoleLaunch ──> PiLaunch
|
|
7
7
|
│
|
|
8
8
|
caller-owned task, cwd, signal ─────────────────────────┤
|
|
9
9
|
v
|
|
@@ -63,7 +63,7 @@ Single mode puts one delegation's fields at the top level.
|
|
|
63
63
|
|
|
64
64
|
| Field | Required | Contract |
|
|
65
65
|
| --- | --- | --- |
|
|
66
|
-
| `role` | yes | Name of a Role in the user's effective `config/pi-subagent` directory
|
|
66
|
+
| `role` | yes | Name of a Role in the user's effective `config/pi-subagent` directory or a package-shipped built-in (`implementer`, `reviewer`); a same-named user file overrides the built-in. |
|
|
67
67
|
| `task` | yes | Non-empty bounded task packet. |
|
|
68
68
|
| `model` | no | Designated `provider/modelId`; takes precedence over `modelClass`. |
|
|
69
69
|
| `modelClass` | no | `fast`, `balanced`, `frontier`, or `fav`; omission uses shared task assignment. |
|
|
@@ -83,14 +83,14 @@ All Main-visible text for one tool call shares one aggregate 50 KiB UTF-8 transp
|
|
|
83
83
|
|
|
84
84
|
Every single entry, parallel sibling, and chain step independently:
|
|
85
85
|
|
|
86
|
-
1. loads its selected
|
|
86
|
+
1. loads its selected Role;
|
|
87
87
|
2. resolves its route and named Skills from the latest effective Pi context after receiving an executor permit;
|
|
88
88
|
3. creates its Role launch policy; and
|
|
89
89
|
4. when the Role requests `isolation: worktree`, creates a worktree identified by the tool call, mode, and input index.
|
|
90
90
|
|
|
91
91
|
Separate deterministic identities produce separate hashed worktree paths and branches. Parallel siblings cannot collide, and a chain does not base one step's worktree on the preceding step's branch. `{previous}` passes text only. There is no implicit shared worktree or hidden workflow state.
|
|
92
92
|
|
|
93
|
-
A worktree starts from Main's current `HEAD`. Clean worktrees with no child commits are pruned; committed, dirty, switched, unmeasurable, or otherwise recoverable work is preserved and reported. Non-git directories and repositories with an unborn `HEAD` use Main's working directory. Git submodules reject worktree isolation, and setup failure in a real repository throws rather than silently sharing Main's checkout.
|
|
93
|
+
A worktree starts from Main's current `HEAD`. Clean worktrees with no child commits are pruned; committed, dirty, switched, unmeasurable, or otherwise recoverable work is preserved and reported. Non-git directories and repositories with an unborn `HEAD` use Main's working directory. Git submodules reject worktree isolation, and setup failure in a real repository throws rather than silently sharing Main's checkout. This generic fallback remains unchanged: the bundled delegated-development Skill separately refuses to begin without a committed Git `HEAD`.
|
|
94
94
|
|
|
95
95
|
If steps must share files, make that an explicit caller decision: use an intentionally shared workspace, merge preserved child commits, or pass state through a caller-owned store. Do not rely on chain order to imply filesystem sharing.
|
|
96
96
|
|
|
@@ -116,7 +116,7 @@ The package root exports the following mechanism-level APIs:
|
|
|
116
116
|
|
|
117
117
|
| API | Responsibility |
|
|
118
118
|
| --- | --- |
|
|
119
|
-
| `loadRoles(agentDir?)` | Validate and load user Role Markdown. |
|
|
119
|
+
| `loadRoles(agentDir?)` | Validate and load package-shipped built-in and user Role Markdown. |
|
|
120
120
|
| `resolveRoleSkills(pi, role)` | Resolve Role Skill names from Pi's effective registry. |
|
|
121
121
|
| `resolveRoleLaunch(pi, ctx, input)` | Resolve a shared task route and produce `ResolvedRoleLaunch`. |
|
|
122
122
|
| `createRoleLaunch(pi, ctx, input)` | Produce the same launch from a caller-supplied resolved route. |
|
|
@@ -319,24 +319,33 @@ if (!approved) throw new Error(`Review did not pass after ${maxReviewRounds} rou
|
|
|
319
319
|
|
|
320
320
|
The verdict schema, parser, round state, shared workspace, and terminal decision all belong to the caller. Add a richer protocol only when the workflow requires one; do not encode it as a recursive package workflow definition.
|
|
321
321
|
|
|
322
|
-
##
|
|
322
|
+
## Built-in Roles and samples
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
The package ships two working built-in Roles, validated by the same parser as user roles and always present even with no `config/pi-subagent` directory:
|
|
325
|
+
|
|
326
|
+
| Built-in | Behavior |
|
|
327
|
+
| --- | --- |
|
|
328
|
+
| `implementer` | Focused implementation requesting `isolation: worktree`; commits scoped changes locally, never pushes or opens PRs without authorization. Non-Git or unborn-`HEAD` contexts may use Main's cwd. |
|
|
329
|
+
| `reviewer` | Read-only correctness review from a supplied exact patch file reference (path, bytes, SHA-256) and referenced files; never edits or commits. |
|
|
330
|
+
|
|
331
|
+
A same-named Markdown file in `config/pi-subagent/` explicitly overrides the built-in default.
|
|
332
|
+
|
|
333
|
+
Additional repository samples are inert starting points, not installed configuration:
|
|
325
334
|
|
|
326
335
|
| Sample | Intended starting point |
|
|
327
336
|
| --- | --- |
|
|
328
337
|
| [`scout`](../examples/roles/scout.md) | Read-only code/evidence mapping. |
|
|
329
|
-
| [`implementer`](../examples/roles/implementer.md) | Focused implementation requesting `isolation: worktree`; non-Git or unborn-`HEAD` contexts may use Main's cwd. |
|
|
330
|
-
| [`reviewer`](../examples/roles/reviewer.md) | Read-only correctness review. |
|
|
331
338
|
| [`synthesizer`](../examples/roles/synthesizer.md) | Reconcile supplied reports without broad discovery. |
|
|
332
339
|
|
|
333
|
-
|
|
340
|
+
Copy the package-shipped samples from your installed `@henryqw/pi-subagent` package (npm installs include `examples/roles/`) if you want them:
|
|
334
341
|
|
|
335
342
|
```bash
|
|
336
343
|
mkdir -p ~/.pi/agent/config/pi-subagent
|
|
337
|
-
cp
|
|
344
|
+
cp <package-install-dir>/examples/roles/scout.md ~/.pi/agent/config/pi-subagent/
|
|
338
345
|
```
|
|
339
346
|
|
|
340
347
|
The package never creates, copies, updates, or removes files in `~/.pi/agent/config/pi-subagent/`. Once copied, the files and their names are entirely user-owned.
|
|
341
348
|
|
|
349
|
+
The bundled [`pi-subagent-delegated-development`](../skills/pi-subagent-delegated-development/SKILL.md) Skill is Main-side orchestration policy only. It prescribes an implement → review → merge loop over the built-in `implementer` and `reviewer` Roles, with private byte-verified exact-patch file references rather than inline patch text and cleanup only after integration validation. It defines no workflow AST, runtime code, or configuration; `delegate_task` remains the flat single/parallel/chain mechanism.
|
|
350
|
+
|
|
342
351
|
See the architectural decision: [Compose workflows outside the ephemeral executor](./adr/001-composable-ephemeral-execution.md).
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: implementer
|
|
3
3
|
description: Implements and validates one bounded change, requesting worktree isolation
|
|
4
|
-
tools:
|
|
4
|
+
tools:
|
|
5
|
+
- read
|
|
6
|
+
- bash
|
|
7
|
+
- edit
|
|
8
|
+
- write
|
|
9
|
+
- grep
|
|
10
|
+
- find
|
|
11
|
+
- ls
|
|
5
12
|
isolation: worktree
|
|
6
13
|
---
|
|
7
14
|
|
|
@@ -9,6 +16,8 @@ Implement one bounded task.
|
|
|
9
16
|
|
|
10
17
|
Read applicable repository instructions and domain context first. Inspect the existing flow and its callers before editing. Work only in explicitly assigned files and preserve unrelated changes. Fix the root cause with the smallest complete diff, reusing existing patterns and dependencies.
|
|
11
18
|
|
|
12
|
-
Run focused validation that would fail if the change were wrong. Do not access credentials, use the network, generate artifacts, or broaden scope unless the task explicitly requires it.
|
|
19
|
+
Run focused validation that would fail if the change were wrong. Do not access credentials, use the network, generate artifacts, or broaden scope unless the task explicitly requires it. Never invoke external LLM APIs, SDKs, agent harnesses, or model CLIs; deterministic developer tools remain allowed.
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
Commit completed scoped changes locally unless the task forbids it. Never push or open pull requests without explicit authorization.
|
|
22
|
+
|
|
23
|
+
Return the retained worktree path, branch, base commit, tip commit, changed files from the base-to-tip committed diff, clean `git status --porcelain=v1 --untracked-files=all` result, validation results, and remaining risks. Do not remove the retained worktree or task branch; Main cleans them only after successful integration and validation.
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reviewer
|
|
3
3
|
description: Reviews one bounded change for correctness without changing files
|
|
4
|
-
tools:
|
|
4
|
+
tools:
|
|
5
|
+
- read
|
|
6
|
+
- grep
|
|
7
|
+
- find
|
|
8
|
+
- ls
|
|
5
9
|
---
|
|
6
10
|
|
|
7
11
|
Perform a read-only correctness review of one bounded change.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
Refuse review unless the task supplies the base commit, tip commit, complete exact base-to-tip patch file reference (path, byte count, SHA-256), and explicit review context: `{type:'child_branch', branch}` or `{type:'integration_head'}`. Review from that supplied file and the files it references only; do not infer a diff from a branch or worktree. Read the patch in bounded chunks when needed and use only `read`, `grep`, `find`, or `ls` for referenced files.
|
|
14
|
+
|
|
15
|
+
Review only the supplied requirements, exact patch file, and explicitly referenced files, including any relevant callers, contracts, and tests. Check correctness, regressions, trust-boundary validation, error handling, and missing high-value tests. Do not run commands or tests. Never edit files, commit, push, or otherwise modify state.
|
|
16
|
+
|
|
17
|
+
Never invoke external LLM APIs, SDKs, agent harnesses, or model CLIs.
|
|
10
18
|
|
|
11
19
|
Return findings first, ordered by severity. Every finding must include file and line evidence, impact, and the smallest valid fix. If there are no findings, say so and list any unvalidated risk.
|
package/extensions/subagent.ts
CHANGED
|
@@ -178,8 +178,7 @@ function failedToolPatch(error: WorkflowFailureError | WorkflowAbortedError) {
|
|
|
178
178
|
|
|
179
179
|
const roleSummary = (): string => {
|
|
180
180
|
try {
|
|
181
|
-
|
|
182
|
-
return roles.length ? roles.map((role) => `${role.name}: ${role.description}`).join("; ") : "none configured";
|
|
181
|
+
return loadRoles().map((role) => `${role.name}: ${role.description}`).join("; ");
|
|
183
182
|
} catch (error) {
|
|
184
183
|
return `configuration error: ${error instanceof Error ? error.message : String(error)}`;
|
|
185
184
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Delegate bounded single, parallel, or chained tasks to isolated Pi roles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"extensions",
|
|
19
19
|
"docs",
|
|
20
20
|
"examples",
|
|
21
|
+
"skills",
|
|
21
22
|
"README.md",
|
|
22
23
|
"CONTEXT.md",
|
|
23
24
|
"LICENSE"
|
|
@@ -57,6 +58,9 @@
|
|
|
57
58
|
"pi": {
|
|
58
59
|
"extensions": [
|
|
59
60
|
"./extensions/subagent.ts"
|
|
61
|
+
],
|
|
62
|
+
"skills": [
|
|
63
|
+
"./skills"
|
|
60
64
|
]
|
|
61
65
|
},
|
|
62
66
|
"dependencies": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-subagent-delegated-development
|
|
3
|
+
description: Orchestrate delegated development with pi-subagent delegate_task. Use when splitting implementation work into bounded units for isolated implementer children reviewed by a read-only reviewer before merging.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Delegated Development
|
|
7
|
+
|
|
8
|
+
You are Main. Decompose work into bounded units and coordinate; never edit files yourself.
|
|
9
|
+
|
|
10
|
+
All model and agent work in this workflow goes through Pi's `delegate_task` and Pi-managed children. Do not invoke external LLM APIs, SDKs, agent harnesses, or model CLIs; ordinary deterministic developer tools such as `git`, npm, test runners, and compilers remain allowed.
|
|
11
|
+
|
|
12
|
+
## Required preflight
|
|
13
|
+
|
|
14
|
+
Before any delegation, require that Main's cwd is a Git working tree with a committed `HEAD`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
test "$(git rev-parse --is-inside-work-tree)" = true &&
|
|
18
|
+
git rev-parse --verify -q HEAD^{commit} >/dev/null
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If this fails, stop before delegation and report that this Skill requires a Git repository with a committed `HEAD`; do not rely on the generic worktree fallback to Main's cwd.
|
|
22
|
+
|
|
23
|
+
Before recording each unit's `base=$(git rev-parse HEAD)`, and again immediately before integration and validation, require `git status --porcelain=v1 --untracked-files=all` to be empty. If Git cannot inspect status or any tracked or untracked change is present, stop and ask the user to preserve the changes; never stash, discard, hide, or work around them.
|
|
24
|
+
|
|
25
|
+
## Roles
|
|
26
|
+
|
|
27
|
+
The package-shipped built-in Roles `implementer` (`isolation: worktree`) and `reviewer` (read-only patch-and-file review) are always available; no installation step is required. A same-name user override replaces the built-in entirely. Before using this workflow, Main must verify that each override preserves implementer worktree isolation and reviewer read-only patch-and-file constraints; fail clearly if it does not. Do not invent substitute Roles.
|
|
28
|
+
|
|
29
|
+
## Per-unit loop
|
|
30
|
+
|
|
31
|
+
For each bounded unit:
|
|
32
|
+
|
|
33
|
+
1. **Implement** — one `delegate_task` single call to `implementer`. The packet states the objective, touched scope, required validation, and recorded base commit. Require its output to identify the retained worktree path, branch, base commit, tip commit, changed files from the base-to-tip committed diff, and clean `git status --porcelain=v1 --untracked-files=all` result.
|
|
34
|
+
2. **Verify and review** — refuse review or merge unless Main independently verifies that the worktree was retained and is clean (including untracked files), the reported base/branch/tip identities are complete and match Git, the tip is descended from the recorded base, and the reported changed files equal `git diff --name-only "$base" "$tip"`. All intended changes must be in that committed base-to-tip diff. If any evidence is missing or any check fails, send the work to a fresh implementer repair; never review dirty or uncommitted work.
|
|
35
|
+
|
|
36
|
+
Create a private temporary exact-patch artifact outside the repository from `git diff --no-textconv --no-ext-diff --ignore-submodules=none --binary "$base" "$tip"`. Independently regenerate that same diff and byte-compare it with the artifact, then record its path, byte count, and SHA-256. If creation, regeneration, comparison, byte count, or checksum fails, stop and report the artifact failure clearly; do not review or merge. Do not put any complete patch content in `delegate_task` text or argv.
|
|
37
|
+
|
|
38
|
+
Make one `delegate_task` single call to `reviewer` with only a bounded metadata packet: base, tip, review context `{type:'child_branch', branch}`, the verified complete patch file reference (`path`, `bytes`, `sha256`), and the verified changed paths. If that complete metadata cannot fit the task transport bound, stop and report it rather than truncating or inlining patch content. Chain entries do not share files: `{previous}` passes text only.
|
|
39
|
+
3. **Merge** — only after an approving review, re-check Main's clean status, verify the branch tip still equals the reviewed tip commit, then merge that exact commit (not the branch name) into Main's current worktree and run focused validation there. Never merge on unresolved findings.
|
|
40
|
+
4. **Clean up after success** — only after the exact reviewed tip is integrated and focused validation passes, remove each reported retained worktree, then safely delete its task branch. Include a superseded repair-round worktree only when its exact tip is an ancestor of integrated `HEAD`. For each candidate, verify ancestry first, use non-forced worktree removal followed by `git branch -d`, and stop/report cleanup failure without deleting later evidence. On any integration or validation failure, preserve every temporary patch artifact, retained worktree, and task branch for recovery. After integration and validation succeed, remove the temporary reviewer patch artifacts.
|
|
41
|
+
|
|
42
|
+
## Findings
|
|
43
|
+
|
|
44
|
+
Any reviewer finding goes back as a **fresh** `implementer` delegation containing the findings plus the reviewed base/branch/tip identities and complete exact patch file reference, followed by a fresh review of the new state. A fresh repair implementer starts in a new worktree from Main HEAD and does not contain the prior unit commit: the repair packet must first bring the whole reviewed `$base..$tip` range into its fresh worktree by merging the exact `$tip`, or cherry-picking every range commit in order. Cherry-pick `$tip` alone only after `git rev-list --count "$base..$tip"` verifies the range is exactly one commit; then address the findings. Dirty or uncommitted work also goes only to this fresh repair path. Bound the loop (e.g. three rounds); past the bound, stop and report to the user instead of merging.
|
|
45
|
+
|
|
46
|
+
## Parallelism
|
|
47
|
+
|
|
48
|
+
Independent units may run concurrently via one `delegate_task` parallel call (max 8 entries) or concurrent single calls, each still following its own implement → review cycle. Never parallelize a unit's review ahead of its implementation. Each child gets its own deterministic worktree; they never share files implicitly.
|
|
49
|
+
|
|
50
|
+
## Boundaries
|
|
51
|
+
|
|
52
|
+
- Never bypass review, edit inside a child's worktree, or re-implement a child's work yourself.
|
|
53
|
+
- On child failure, recover from the reported preserved-worktree evidence; retry at most once per unit before escalating to the user.
|
|
54
|
+
- Do not push, publish, release, or open PRs without explicit user authorization.
|