brl-subagent 2.3.4 → 2.3.5
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/AGENT.md +88 -0
- package/README.md +20 -5
- package/package.json +2 -1
- package/src/index.ts +12 -6
- package/src/paths.ts +25 -0
- package/src/templates.ts +2 -1
package/AGENT.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# AGENT.md — brl-subagent capability reference (v2.3.4+)
|
|
2
|
+
|
|
3
|
+
> The `delegate_task` prompt guidelines are a derived summary of this file; if they disagree, this file wins.
|
|
4
|
+
|
|
5
|
+
This file is the authoritative reference for how the brl-subagent extension behaves
|
|
6
|
+
and how you (the conductor) should judge a delegation. Read it when planning
|
|
7
|
+
delegation-heavy work.
|
|
8
|
+
|
|
9
|
+
## Execution models
|
|
10
|
+
|
|
11
|
+
| Mode | What blocks | What wakes you | Batchable? |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| single, foreground | blocks until it returns | — (result comes back inline) | no |
|
|
14
|
+
| single, background | returns immediately with an agent id | yes — a `subagent-completion` message on terminal state | no |
|
|
15
|
+
| chain | blocks until the chain finishes or fails | — | yes (foreground) |
|
|
16
|
+
| parallel (`tasks`) | blocks until every task finishes | — | yes (foreground) |
|
|
17
|
+
| graph | blocks; runs in waves via `dependsOn` | — | yes (foreground) |
|
|
18
|
+
|
|
19
|
+
- Foreground single blocks: you wait for the result inline.
|
|
20
|
+
- Background single returns immediately with an agent id; a completion message wakes you (see below).
|
|
21
|
+
- Chain, parallel, and graph are batch — one call, many subtasks. They are foreground today; background batch is future work.
|
|
22
|
+
- Chain stops at the first failure; max 10 steps.
|
|
23
|
+
- Parallel runs every task regardless of the others — no short-circuit.
|
|
24
|
+
- Graph runs in waves, ordered by `dependsOn` edges.
|
|
25
|
+
|
|
26
|
+
## The completion contract
|
|
27
|
+
|
|
28
|
+
- A background run wakes you with a structured `subagent-completion` message when it reaches a terminal state (`completed`, `failed`, or `stopped`). The `details` carry `id`, `status`, `errorCategory` (with `errorMessage` when present), cost/tokens/duration, and `label`; the content carries a tail of output.
|
|
29
|
+
- The wake triggers a turn even when you are idle. Do not poll for it.
|
|
30
|
+
- Delivery mode by status holds at knob `"all"`: `failed`/`stopped` → steering you to act; `completed` → follow-up. The `completionNotify` knob changes both the delivery mode and the wake: under `"failed"` a `completed` run is delivered as a passive `nextTurn` (no wake) while `failed`/`stopped` still steer; under `"off"` everything is a passive `nextTurn` and nothing wakes you. (The knob controls whether an idle conductor is triggered — see below.)
|
|
31
|
+
- Knob dependency: polling is correct only when wakes are disabled (`completionNotify: "off"`). The wake is per-terminal-status and knob-scoped: under `"all"` a terminal run always wakes you; under `"failed"` only a `failed` or `stopped` run wakes you — a `completed` run stays a passive `nextTurn` (no wake); under `"off"` nothing wakes you. So polling is wrong unless wakes are disabled. One status check as a stall check is legitimate; repeated polling is not.
|
|
32
|
+
- One message per run — the extension deduplicates on the first terminal event for a run id, so don't expect multiple messages for a single run.
|
|
33
|
+
- Honest records: trust the message's `category`/abort source over your own guess. The provider/abort origin is authoritative on why the run stopped.
|
|
34
|
+
- Steering is not an abort. Being steered to act on a terminal run is not terminating it. A real `stop` is an abort — and a stopped run still wakes you.
|
|
35
|
+
|
|
36
|
+
## Delegation judgment
|
|
37
|
+
|
|
38
|
+
- Foreground vs background: if your next step needs the result inline, run foreground. If the user can keep working or is AFK, run background and act on the wake.
|
|
39
|
+
- Batch vs sequential: if the work is pre-declared, use chain/parallel/graph. If you're steering incrementally with the user, issue sequential calls.
|
|
40
|
+
- Retry taxonomy: `retryRunId` is a retry — it re-runs with the recorded task and params. A re-dispatch (a fresh call with the same task text) is not a retry. Never re-issue an identical spec after a termination without confirming the cause with the user (Rule 18).
|
|
41
|
+
- When to delegate: when the task needs an isolated context, a deep investigation, parallel research, or a long-running analysis.
|
|
42
|
+
- Approval: `approvalMode: "always"` is rejected in background — there is no dialog. Use `auto` (default) or `writes`; `writes` auto-approves in background.
|
|
43
|
+
|
|
44
|
+
## Canonical shapes
|
|
45
|
+
|
|
46
|
+
- Batch chain with `{previous}`: `chain: [{ task: "…" }, { task: "Use {previous} …" }]` — each step gets the prior step's output.
|
|
47
|
+
- Graph with `dependsOn`: subtasks declare edges; the scheduler runs them in waves.
|
|
48
|
+
- Background + act on wake: spawn a background run, then act when the completion message arrives. Never poll between spawn and wake.
|
|
49
|
+
- retryRunId: pass a failed run's id to re-run it with its recorded task and params; explicit parameters override the originals.
|
|
50
|
+
|
|
51
|
+
## Mechanics pointer
|
|
52
|
+
|
|
53
|
+
Presets and templates are documented in the schema, not here. Judgment only:
|
|
54
|
+
|
|
55
|
+
- Prefer a preset when the delegation matches a standard shape — review/audit/test/docs; override per-call deliberately.
|
|
56
|
+
- Recurring task shapes → a saved template.
|
|
57
|
+
|
|
58
|
+
The schema's `delegate_task` parameters, the `templateSummary` and `presetRestrictionSummary` lines in the prompt guidelines, and `get_subagent_result`'s description are the mechanics reference.
|
|
59
|
+
|
|
60
|
+
## Local development
|
|
61
|
+
|
|
62
|
+
This repo is the package source. Daily use runs the **published npm package**;
|
|
63
|
+
development runs the **local checkout**. Both are `pi install` entries and cannot
|
|
64
|
+
coexist — two loaded copies register the same tools — so remove one first.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Daily: the published artifact — the same thing users install
|
|
68
|
+
pi remove <local-repo-path>
|
|
69
|
+
pi install npm:brl-subagent
|
|
70
|
+
|
|
71
|
+
# Develop: load the checkout in place — no copying, no syncing
|
|
72
|
+
pi remove npm:brl-subagent
|
|
73
|
+
pi install <local-repo-path> # or a worktree: …/brl-subagent-wt-<n>
|
|
74
|
+
# then /reload pi after edits
|
|
75
|
+
|
|
76
|
+
# Back to the published artifact
|
|
77
|
+
pi remove <local-repo-path>
|
|
78
|
+
pi install npm:brl-subagent
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
A local-path install loads the directory in place and can point at an **unmerged
|
|
82
|
+
worktree**, so it doubles as live verification for a PR. `pi update --extensions`
|
|
83
|
+
advances the npm install and leaves a local-path install alone.
|
|
84
|
+
|
|
85
|
+
(The former `sync-extension.sh` — rsync into `~/.pi/agent/extensions/brl-subagent`
|
|
86
|
+
— was retired once the package was published: the local-path install supersedes it
|
|
87
|
+
and does not fight `pi update --extensions` by mutating a package-manager-owned
|
|
88
|
+
directory.)
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- **True background execution** — live monitor, real abort (`stop_subagent`), per-agent timeouts, and hard caps. Nothing orphans; nothing leaks.
|
|
15
15
|
- **Preset-driven tool scoping** — every subagent runs with exactly the tools its job needs, restricted by preset or per-call `tools`/`excludeTools`, with auto-route that picks the right preset when you don't.
|
|
16
16
|
- **Templates with slots** — saved, file-backed task templates with `${param}` placeholders for workflows you run again and again.
|
|
17
|
-
- **Safety by default** — task-fence injection protection, sanitized error paths, owner-only persistence, and a
|
|
17
|
+
- **Safety by default** — task-fence injection protection, sanitized error paths, owner-only persistence, and a 975+ test suite pinning every contract against the real pi SDK.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -22,13 +22,28 @@
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
# Global (all projects)
|
|
25
|
-
|
|
25
|
+
pi install npm:brl-subagent
|
|
26
26
|
|
|
27
|
-
# Project-local
|
|
28
|
-
|
|
27
|
+
# Project-local (commit .pi/settings.json to share with your team)
|
|
28
|
+
pi install -l npm:brl-subagent
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Pi
|
|
31
|
+
Pi installs the package and loads the extension automatically. Updates and removal:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pi update --extensions # update installed packages
|
|
35
|
+
pi remove npm:brl-subagent # uninstall
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
<details>
|
|
39
|
+
<summary>Alternative: install from git</summary>
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pi install git:github.com/azmeenafandi/brl-subagent@v2.3.4
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Git installs are **pinned** to the ref you specify. `pi update --extensions` reconciles to that ref but never advances it — upgrading means re-running `pi install` with a newer `@ref`.
|
|
46
|
+
</details>
|
|
32
47
|
|
|
33
48
|
---
|
|
34
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brl-subagent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
4
4
|
"description": "Subagent extension for pi with configurable model and thinking mode via selection UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"templates/",
|
|
17
17
|
"README.md",
|
|
18
18
|
"LICENSE",
|
|
19
|
+
"AGENT.md",
|
|
19
20
|
"!src/__tests__/"
|
|
20
21
|
],
|
|
21
22
|
"pi": {
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
} from "./types";
|
|
53
53
|
import { validateGraph, topologicalSort } from "./scheduler";
|
|
54
54
|
import { resolveTemplate, loadAllTemplates, loadBuiltinTemplates, validateTemplatePresetRefs, extractParamNames } from "./templates";
|
|
55
|
+
import { pkgPath } from "./paths";
|
|
55
56
|
|
|
56
57
|
import { sanitizeTask, validateCwd, validateOutputFile, stripAnsi, capOutput, getCurrentDepth, sanitizeErrorMessage, buildCrashResult } from "./sanitize";
|
|
57
58
|
import {
|
|
@@ -200,7 +201,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
200
201
|
// Read current version from package.json
|
|
201
202
|
const currentVersion = (() => {
|
|
202
203
|
try {
|
|
203
|
-
const pkg = JSON.parse(fs.readFileSync(
|
|
204
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath("package.json"), "utf-8"));
|
|
204
205
|
return pkg.version || "0.0.0";
|
|
205
206
|
} catch {
|
|
206
207
|
return "0.0.0";
|
|
@@ -1954,7 +1955,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1954
1955
|
|
|
1955
1956
|
// B1: Load built-in presets once at registration to expose their tool
|
|
1956
1957
|
// restrictions to the conductor before any delegate_task call happens.
|
|
1957
|
-
const registrationPresets = loadBuiltinPresets(
|
|
1958
|
+
const registrationPresets = loadBuiltinPresets(pkgPath("presets"));
|
|
1958
1959
|
const presetRestrictionSummary = buildInventorySummary(registrationPresets, (p) => {
|
|
1959
1960
|
const restricted = p.excludeTools?.length || p.tools?.length;
|
|
1960
1961
|
if (!restricted) return p.name;
|
|
@@ -1965,9 +1966,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1965
1966
|
// inventory (name + purpose + ${param} slots) to the conductor before any
|
|
1966
1967
|
// delegate_task call. Custom/user templates can't be enumerated statically,
|
|
1967
1968
|
// so the summary covers built-ins only and points at /brl-subagent templates.
|
|
1968
|
-
const registrationTemplates = loadBuiltinTemplates(
|
|
1969
|
+
const registrationTemplates = loadBuiltinTemplates(pkgPath("templates"));
|
|
1969
1970
|
const templateSummary = buildInventorySummary(registrationTemplates, formatTemplateSummaryItem);
|
|
1970
1971
|
|
|
1972
|
+
// Issue #158: resolve the shipped AGENT.md from the extension's own directory
|
|
1973
|
+
// rather than a hardcoded sync-copy destination, so npm-installed packages
|
|
1974
|
+
// (installed via `pi install npm:brl-subagent`) can find it.
|
|
1975
|
+
const agentMdPath = pkgPath("AGENT.md");
|
|
1976
|
+
|
|
1971
1977
|
pi.registerTool({
|
|
1972
1978
|
name: "delegate_task",
|
|
1973
1979
|
label: "Delegate Task",
|
|
@@ -1983,7 +1989,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1983
1989
|
"Delegate tasks to a subagent for isolated, parallel or background work",
|
|
1984
1990
|
promptGuidelines: [
|
|
1985
1991
|
"Use delegate_task when the user asks you to hand off work to a subagent, or when a task would benefit from an isolated context window (e.g., deep investigation, parallel research, long-running analysis).",
|
|
1986
|
-
|
|
1992
|
+
`The authoritative capability reference for delegation is the extension's AGENT.md — read it at ${agentMdPath}, or ./AGENT.md in the project root when present, when planning delegation-heavy work.`,
|
|
1987
1993
|
"The subagent inherits your system prompt and runs with its own model (configurable via /brl-subagent). It reports what it did when done.",
|
|
1988
1994
|
"You can customize per-call via inheritSystemPrompt and systemPrompt: set inheritSystemPrompt: false to save context, provide a systemPrompt for custom instructions, or use both to add instructions on top of inheritance.",
|
|
1989
1995
|
"Set thinkingLevel per call to match task complexity. The level is capped at the user's configured maximum. Map tasks to levels using this heuristic: off = file listing, grep, simple read. minimal = file diff, syntax check, find-and-replace. low = refactoring, test generation, documentation. medium = default — code review, debugging, moderate analysis. high = security audit, architecture review, complex debugging. xhigh = multi-step causal reasoning, research, novel problem solving. Default to 'off' or 'minimal' for trivial tasks — do not waste the user's budget.",
|
|
@@ -3691,11 +3697,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
3691
3697
|
// Issue #147: capture the session context for the completion-push subscriber.
|
|
3692
3698
|
sessionCtx = ctx;
|
|
3693
3699
|
// Load built-in presets
|
|
3694
|
-
const presetsDir =
|
|
3700
|
+
const presetsDir = pkgPath("presets");
|
|
3695
3701
|
state.builtinPresets = loadBuiltinPresets(presetsDir, log);
|
|
3696
3702
|
state.customPresets = loadCustomPresets(ctx.cwd, log);
|
|
3697
3703
|
// Builtin templates + merged full stack (custom overrides builtin)
|
|
3698
|
-
const templatesDir =
|
|
3704
|
+
const templatesDir = pkgPath("templates");
|
|
3699
3705
|
state.builtinTemplates = loadBuiltinTemplates(templatesDir, log);
|
|
3700
3706
|
state.config.templates = loadAllTemplates(ctx.cwd, log, templatesDir);
|
|
3701
3707
|
|
package/src/paths.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* brl-subagent — Package-root path resolution
|
|
3
|
+
*
|
|
4
|
+
* The extension is loaded from `<pkg>/src/` (package.json `pi.extensions`
|
|
5
|
+
* points at `./src/index.ts`), so every module in this package lives directly
|
|
6
|
+
* in `src/` and the package root is exactly one level up. `pkgPath()` is the
|
|
7
|
+
* single helper for resolving bundled assets against that root — the same base
|
|
8
|
+
* used for `presets/`, `templates/`, `package.json`, and `AGENT.md`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve `segments` against the package root.
|
|
15
|
+
*
|
|
16
|
+
* Because the extension loads from `<pkg>/src/`, the package root is one level
|
|
17
|
+
* up from this module's directory. A wrong depth here would resolve inside
|
|
18
|
+
* `src/` and fail to find the bundled asset.
|
|
19
|
+
*
|
|
20
|
+
* @param segments - Path segments below the package root (e.g. `"presets"`, `"AGENT.md"`)
|
|
21
|
+
* @returns The absolute path to the bundled asset
|
|
22
|
+
*/
|
|
23
|
+
export function pkgPath(...segments: string[]): string {
|
|
24
|
+
return path.join(__dirname, "..", ...segments);
|
|
25
|
+
}
|
package/src/templates.ts
CHANGED
|
@@ -27,6 +27,7 @@ import type { TaskTemplate, ThinkingLevel, SubagentPreset } from "./types";
|
|
|
27
27
|
import { TEMPLATE_PARAM_RE, THINKING_LEVELS } from "./types";
|
|
28
28
|
import type { Logger } from "./logging";
|
|
29
29
|
import { parseFrontmatter, sanitizeFileName } from "./presets";
|
|
30
|
+
import { pkgPath } from "./paths";
|
|
30
31
|
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// Param extraction
|
|
@@ -373,7 +374,7 @@ export function getAllTemplates(
|
|
|
373
374
|
* the extension's presets/ dir.
|
|
374
375
|
*/
|
|
375
376
|
export function loadAllTemplates(cwd: string, log?: Logger, templatesDir?: string): TaskTemplate[] {
|
|
376
|
-
const builtinDir = templatesDir ??
|
|
377
|
+
const builtinDir = templatesDir ?? pkgPath("templates");
|
|
377
378
|
return getAllTemplates(loadBuiltinTemplates(builtinDir, log), loadCustomTemplates(cwd, log));
|
|
378
379
|
}
|
|
379
380
|
|