@tduyng/pi-subagent 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1,68 +1,81 @@
1
1
  # @tduyng/pi-subagent
2
2
 
3
- Delegates work to child pi sessions running in-process. The parent stays lean because the child reads the files, returns one message plus a file list and usage stats, and then its context is discarded. Each finished child also appends its usage to `<agentDir>/overhead.jsonl` under the parent's session, so `/cost` can show what the delegation spent alongside the conversation.
3
+ A subagent that stays small on purpose. It reads docs, logs and source on a cheap model, returns one answer, and leaves none of that reading in the parent's context.
4
4
 
5
- ## How delegation works
5
+ A session spends most of its tokens reading. A question about which files build a retry policy has a paragraph for an answer and 40 KB of source behind it, and the parent carries that 40 KB into every later turn. The child's context dies with the task, so the cost of a delegation is bounded by its answer.
6
6
 
7
- The `subagent` tool sends one task, or up to `MAX_TASKS` tasks, to a child
8
- session. At most `MAX_CONCURRENT` children run at once. Each child runs on the
9
- current model unless a task, profile, or `subagent.json` override changes it.
10
- The child sees the task prompt, does the work, and returns one message. The
11
- parent never carries the child's exploration.
7
+ Fanning four edit-capable children over one change is the case this is not for. Every child builds its own system prompt and tool list, and their edits still have to be reconciled.
12
8
 
13
- Use `scout` for read-only file and document search, code tracing, analysis, and
14
- short sourced summaries. Omitting `agent` selects `scout`. It is the cheap
15
- default for I/O-heavy work. `subagent.json` supplies its low-cost model.
9
+ 379 tokens a request for the tool. The bulk-read guard and the log reducer are event handlers, so they add no schema and no prompt text.
16
10
 
17
- Use `verifier` only
18
- after an artifact exists and the task explicitly asks for verification or
19
- review. Keep ordinary implementation, debugging, design, builds, tests, and lint
20
- in the parent session.
11
+ ## The tool
21
12
 
22
- ### Resume ids
13
+ ```text
14
+ subagent(task: "Which files construct the retry policy? Return path:line for each.", agent: "scout")
15
+ ```
16
+
17
+ One task or up to 8, at most 4 running at once.
18
+
19
+ | Field | Meaning |
20
+ | --- | --- |
21
+ | `task` | What the child does |
22
+ | `agent` | Profile name, `scout` by default |
23
+ | `cwd` | Working directory for the child |
24
+ | `isolation` | `worktree` to give the child its own git checkout |
25
+ | `schema` | Ask for structured output |
26
+ | `context` | `fork` to replay the parent's history into the child |
27
+ | `resume` | Task id, or `all`, to continue a stopped child |
28
+
29
+ ## Scout
30
+
31
+ `agents/scout.md` is bundled, so `agent: "scout"` works with no setup and no profile. It is also the default when no agent is named, so the cheap read-only child is what a plain call gets.
32
+
33
+ Scout carries `tools: read, grep, find, ls`, `inheritSkills: false`, and `thinking: low`. It never edits, never runs a command, never reaches the network, and answers in a fixed `STATUS / FINDING / NEXT` shape with file:line evidence.
23
34
 
24
- A child that reaches its turn budget is told to wrap up and gets a fresh budget in the same session, so a cap never turns work into a failure. If the child still stops early, its last text comes back as a `[partial answer]`. Salvage walks the whole transcript for that text, not just the last message, so a child stopped mid-tool-burst still returns what it wrote. Every failed task prints a `resume:` id. Pass `resume: "<id>"` (or `"all"`) to continue where it stopped instead of paying for the investigation again. An optional `task` alongside `resume` steers the continuation. Failed sessions are kept 30 minutes, completed ones 5.
35
+ Override it with a copy in `~/.pi/agent/agents/scout.md` or `.pi/agents/scout.md`. A project file shadows a user file, and both shadow the bundled one.
25
36
 
26
- A deadline is reported as a timeout, not a cancellation. Pi keeps any final text produced before the deadline, marks it partial, and retains the session for resume.
37
+ The child inherits the parent's model unless told otherwise, which defeats the point. Set it once in `~/.pi/agent/subagent.json`:
27
38
 
28
- Every run also writes `run.json` under `<agent-dir>/subagents/<session-id>/<run-id>/` with the task prompt, model, tools, and the path to each child session. The file is rewritten after each task transition, so it survives the process. Reopening the same Pi session reads it back: children interrupted less than ten minutes ago are resumed from their saved sessions and their result arrives as a `subagent-durable` message, while older interruptions are listed with their resume ids. `resume` therefore works after a restart, not only inside the process that started the child.
39
+ ```json
40
+ {
41
+ "model": "claude/claude-haiku-5-5",
42
+ "thinking": "low"
43
+ }
44
+ ```
45
+
46
+ `.pi/subagent.json` overrides either field per project. A `model` on the profile wins over the file, so a judgment role such as `verifier` can opt out and ask for a strong model.
47
+
48
+ ## A stopped child is resumable
29
49
 
30
- ### Worktree isolation
50
+ A child that hits its turn budget is told to wrap up and gets a fresh budget in the same session, so a cap never turns work into a failure. If it still stops early, its last text comes back marked `[partial answer]`, recovered by walking the whole transcript, so a child stopped mid tool-burst still returns what it wrote.
31
51
 
32
- With `isolation: "worktree"`, each child runs in its own git worktree on a branch named `pi-task-<id>`, so concurrent children never edit the same checkout and a commit a child makes outlives the checkout. The task result names the branch.
52
+ Every failed task prints a `resume:` id. Passing it back continues where the child stopped instead of paying for the investigation again. Failed sessions are kept 30 minutes and completed ones 5.
33
53
 
34
- ### Structured output
54
+ Every run writes `run.json` under `<agent-dir>/subagents/<session-id>/<run-id>/` with the task, model, tools and the path to each child session. Reopening the session reads it back: a child interrupted less than ten minutes ago is resumed from its saved session and its answer arrives as a `subagent-durable` message, and older interruptions are listed with their resume ids. `resume` therefore works after a restart.
35
55
 
36
- Pass `schema` to ask the child to call a `structured_output` tool exactly once. The parsed payload comes back in the task result. If the child finishes without calling the tool, the extension re-prompts the same session up to three times, so the investigation is not paid for twice. If it still refuses, the text answer stands and the task completes marked partial. If a provider rejects the schema tool, the extension removes it and retries once in plain-text mode.
56
+ ## Worktrees, schemas, forks
37
57
 
38
- ### Fork context
58
+ `isolation: "worktree"` runs each child in its own git worktree on a branch named `pi-task-<id>`, so concurrent children never edit the same checkout and a commit the child makes outlives it. The result names the branch. This is also the answer to two sessions committing in one repository, which no path reservation covers.
39
59
 
40
- With `context: "fork"`, the parent's conversation history is replayed into the child as context entries.
60
+ `schema` asks the child to call `structured_output` exactly once, and the parsed payload comes back in the task result. A child that finishes without calling it is re-prompted in the same session up to three times, so the investigation is not paid for twice. If it still refuses, the text answer stands and the task completes marked partial. If a provider rejects the schema tool, the extension removes it and retries once as plain text.
41
61
 
42
- ## Agent profiles
62
+ `context: "fork"` replays the parent's conversation history into the child as context entries.
43
63
 
44
- The `/agents` command lists agent profiles, which are markdown files with YAML frontmatter in `<agent-dir>/agents` or `.pi/agents`. Project agents shadow user agents of the same name. Each profile specifies a tool list, model, thinking level, limits, and optional color for the widget. Profiles can also set `inheritProjectContext` and `inheritSkills` to control whether the child picks up the parent project's context files and skills, and `systemPromptMode: replace` to swap the default system prompt instead of appending to it. The `subagent` widget in the TUI shows up to 8 running and recently finished children, with a summary line of counts by status. Navigate with Down/Up, Enter opens a live transcript viewer, d/x dismisses.
64
+ ## Two guards on the parent
45
65
 
46
- ## Bulk-read guard
66
+ **Bulk read.** The guard blocks a read of a file above `BULK_READ_MIN_LINES` lines (default 350), and blocks `cat`, `less` or `more` that would dump a large file. The block names `outline` first, so the model fetches a map of declarations rather than the text. The second call for the same path passes, and so does a read that returns fewer lines than the threshold, whether from a `limit`, an `offset`, a small file, or `head` and `tail` on their 10-line default.
47
67
 
48
- The guard blocks reads of files above `BULK_READ_MIN_LINES` lines (default 350). It also blocks `cat`, `less`, and `more` commands that would dump large files. The second call for the same path passes through, so the file stays reachable. A read passes when it returns fewer lines than the threshold, whether that comes from a `limit`, an `offset`, a small file, or `head`/`tail` on their 10-line default, and so does a piped command.
68
+ **Log reducer.** The reducer replaces a build or test log above 4,096 bytes from a diagnostic command with a short receipt of quoted lines. It verifies every quote byte for byte against the archived original, and if validation fails the full log reaches the model untouched. It runs on `tool_result`, so the swap happens before the result enters history, and the raw log is archived next to the session.
49
69
 
50
- ## Log reducer
70
+ Both are what stop a delegation from being undone by the next `cat`.
51
71
 
52
- Build or test logs above 4,096 bytes, matched by diagnostic command patterns, get replaced with a short receipt of quoted lines. Every quote is verified byte-for-byte against the archived original. If validation fails, the full log reaches the model untouched. The reducer runs on `tool_result`, so the swap happens before the result enters history. The raw log is archived next to the session for full access.
72
+ ## Profiles and the widget
53
73
 
54
- ## Design
74
+ A profile is a markdown file with YAML frontmatter in `~/.pi/agent/agents/`, `.pi/agents/`, or bundled. It sets the tool list, model, thinking level, limits, an optional color, `inheritProjectContext` and `inheritSkills`, and `systemPromptMode: replace` to swap the default system prompt rather than append to it.
55
75
 
56
- - The default safe delegation is read-only I/O: `scout` searches, traces, analyses, and summarizes without editing or running commands.
57
- - `verifier` is reserved for explicit verification of an existing artifact. It may write only its verification report, never implementation files.
58
- - Ordinary implementation, debugging, design, builds, tests, and lint stay in the parent session.
59
- - The tool rejects concurrent write-capable tasks. Read-only scouts may still run in parallel. A call whose task texts normalize to the same request is rejected, so one question does not get paid for twice.
60
- - Children see one prompt and return one message. No sequential pipelines.
61
- - Children cannot spawn their own subagents. No grandchildren.
62
- - The `subagent` tool schema is present on every turn, and it lists only the fields a model sets: `task`, `agent`, `cwd`, `isolation`, `schema`, `context`, and `resume`. `label`, `thinking`, `model`, `timeoutMs`, and `maxTurns` are profile policy. They still work when a scripted caller passes them, and three of them already resolved to the profile before the split.
63
- - The `subagent` tool schema is present on every turn. The `bulk-read` and `log-reducer` extensions add no tool schemas. They run as event handlers. The registry holds at most 50 tasks. The widget shows at most 8 rows. The `touched` file list per task is capped at 12 entries. Progress updates emit at most once every 250ms.
64
- - Child deadlines default to 30 minutes. A shorter `timeoutMs` or profile `maxExecutionTimeMs` is raised to 30 minutes, while longer values pass through. A profile deadline beyond the 32-bit setTimeout ceiling (2,147,483,647 ms) clamps to the ceiling. Node would round a larger delay down to 1 ms and kill the child instantly, so the clamp degrades a bad value to a very long deadline.
65
- - Children inherit the parent's model by default. Thinking does not inherit: it resolves from the task's `thinking`, then the profile, then `subagent.json`, then `medium`.
76
+ `/agents` lists them with their tools, model and limits. The roster rarely changes mid-task, so it is a command, not a model tool.
77
+
78
+ A persistent widget lists running and recently finished children. Down on an empty prompt highlights the roster, Up and Down move, Escape clears, Enter opens a live conversation viewer with thinking, tool calls and results.
66
79
 
67
80
  ## Install
68
81
 
@@ -72,10 +85,19 @@ pi install npm:@tduyng/pi-subagent
72
85
 
73
86
  Local checkout: `pi install /path/to/dotfiles/pi/agent/extensions/pi-subagent`
74
87
 
75
- ## Configure
88
+ Worktree isolation needs `git` on PATH, and the bulk-read hint names `rg`.
89
+
90
+ | Setting | Default | Meaning |
91
+ | --- | --- | --- |
92
+ | `BULK_READ` | `1` | `0` disables the read guard |
93
+ | `BULK_READ_MIN_LINES` | `350` | Line threshold for a blocked read |
94
+ | `PI_LOG_REDUCER` | `1` | `0` disables the log reducer |
95
+ | `PI_LOG_REDUCER_TIMEOUT_MS` | `15000` | Reducer deadline, 60,000 at most |
96
+
97
+ ## Limits
76
98
 
77
- - `subagent.json` in the agent dir (`~/.pi/agent/`) sets the default child model and thinking level. A project's `.pi/subagent.json` overrides either field. Shape: `{ "model": "provider/model-id", "thinking": "medium" }`, both optional. `thinking` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Unset, it is `medium`. No file means children inherit the parent's model.
78
- - `BULK_READ=0` disables the bulk-read guard. `BULK_READ_MIN_LINES` sets the line threshold (default 350).
79
- - `PI_LOG_REDUCER=0` disables the log reducer.
80
- - `PI_LOG_REDUCER_TIMEOUT_MS` sets the reducer deadline. The default is 15 seconds and the maximum is 60 seconds.
81
- - Agent profiles are markdown files with YAML frontmatter in `~/.pi/agent/agents/` or `.pi/agents/` (project shadowing user on name collision).
99
+ - Eight tasks per call, four running at once. A fifth concurrent write-capable child is refused rather than queued.
100
+ - Child deadlines default to 30 minutes. A profile deadline past the 32-bit `setTimeout` ceiling clamps to it, because Node rounds a larger delay down to 1 ms and would kill the child instantly.
101
+ - A child cannot spawn its own subagent, so delegation stays one level deep.
102
+ - Read-only children run in parallel. A write-capable child never runs alongside another one, and two tasks that normalize to the same request are refused.
103
+ - The registry holds 50 tasks, the widget 8 rows, and the touched list 12 entries per task.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: scout
3
+ description: Cheap read-only I/O and analysis agent. Searches files and docs, traces code, answers one bounded question, and returns a short sourced summary. Never edits, writes, runs commands, or reaches the internet.
4
+ tools: read, grep, find, ls
5
+ inheritSkills: false
6
+ thinking: low
7
+ ---
8
+
9
+ You are a SCOUT, a cheap read-only I/O and analysis subagent. The caller hands
10
+ you exactly one bounded question in a fresh context. Answer it from what is on
11
+ disk and return a short, sourced answer. You change nothing.
12
+
13
+ Use this profile for file and document search, code tracing, comparison, and
14
+ summarization. If the task asks you to implement, edit, run a check, or
15
+ make a design decision, return that the parent must handle it.
16
+
17
+ Rules:
18
+
19
+ - Read only what the question needs. Use `grep` and `find` to locate, then read
20
+ the specific range. Never read a whole file to answer a narrow question.
21
+ - Prefer file paths, line numbers, exact text, and literal command output over
22
+ general impressions. A summary is useful only when its evidence is included.
23
+ - Answer with evidence: file paths with line numbers, exact text you found, or
24
+ the literal output you observed. No paraphrase of code you did not read.
25
+ - A negative result is a real result. If the answer is not on disk, say what you
26
+ searched and what you ruled out.
27
+ - You have no write, edit, bash, or web tools. Do not ask for them, and do not
28
+ propose patches. Name the gap instead. A task that needs those capabilities
29
+ belongs in the parent session. A question about the
30
+ internet cannot be answered by you at all: say so in one line under NEXT and
31
+ stop, rather than searching the disk for a substitute.
32
+ - Spend your turns on the search that answers the question, and when you are
33
+ told to wrap up, answer with what you have and say what is still unchecked
34
+ under NEXT.
35
+
36
+ Reply with exactly this, and nothing else:
37
+
38
+ STATUS: solved|continue
39
+ FINDING: <the answer, with file:line evidence, at most five lines>
40
+ NEXT: <up to three bullets, or "none">
41
+
42
+ When a `structured_output` tool is available, call it once with the same
43
+ STATUS, FINDING, and NEXT content as its fields, and write no text after it.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tduyng/pi-subagent",
3
- "version": "0.0.2",
4
- "description": "Delegation to in-process child sessions: a subagent tool with agent profiles and worktree isolation, a side chat, a guard that redirects bulk reads, and a verified log reducer.",
3
+ "version": "0.0.4",
4
+ "description": "Cheap delegation: a child session reads docs, logs, and source, returns one answer, and its context is thrown away. Scout profile bundled.",
5
5
  "keywords": [
6
6
  "pi",
7
7
  "pi-package",
@@ -25,6 +25,7 @@
25
25
  "files": [
26
26
  "src",
27
27
  "!src/**/*.test.ts",
28
+ "agents",
28
29
  "README.md",
29
30
  "LICENSE"
30
31
  ],
@@ -101,8 +101,16 @@ export function loadAgentsFrom(dirs: string[]): Map<string, AgentProfile> {
101
101
  return agents
102
102
  }
103
103
 
104
+ const BUNDLED_AGENTS = path.join(import.meta.dirname, '..', '..', 'agents')
105
+
106
+ // The default `agent` is scout, so a checkout with no profile on disk would refuse every
107
+ // delegation that omits it. The bundled profile is the floor, not an override.
104
108
  export function loadAgents(cwd: string): Map<string, AgentProfile> {
105
- return loadAgentsFrom(agentDirs(cwd))
109
+ const agents = loadAgentsFrom(agentDirs(cwd))
110
+ for (const [name, profile] of loadAgentsFrom([BUNDLED_AGENTS])) {
111
+ if (!agents.has(name)) agents.set(name, profile)
112
+ }
113
+ return agents
106
114
  }
107
115
 
108
116
  export function parseModelName(name: string): { provider: string; id: string } | undefined {
@@ -69,7 +69,7 @@ import {
69
69
  } from './types.js'
70
70
  import { SubagentViewer, widgetLines } from './viewer.js'
71
71
 
72
- export { parseFrontmatter, loadAgentsFrom, parseModelName } from './agent.js'
72
+ export { parseFrontmatter, loadAgents, loadAgentsFrom, parseModelName } from './agent.js'
73
73
 
74
74
  const MAX_FORK_CONTEXT_CHARS = 120_000
75
75
  const RUN_SAVE_INTERVAL_MS = 5_000