@johnnywu/pi-subagents 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +109 -1
- package/extensions/subagent-render.ts +2 -21
- package/extensions/subagent-tool.ts +1 -21
- package/extensions/tool-args.ts +21 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.0.1](https://github.com/jwu/pi-subagents/compare/v1.0.0...v1.0.1) (2026-05-31)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add spacing before subagent result status ([21932f5](https://github.com/jwu/pi-subagents/commit/21932f5cc7a59f504c60f36a6375f322c46acf8c))
|
|
7
|
+
|
|
1
8
|
# 1.0.0 (2026-05-30)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -2,12 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
Sub-agents extension for [pi](https://github.com/badlogic/pi-mono) coding agent.
|
|
4
4
|
|
|
5
|
+
Delegates tasks to isolated pi child processes — each running with its own model, system prompt, and tool set. Sub-agents inherit zero conversation context; all necessary context must be provided in the task description.
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
pi install npm:@johnnywu/pi-subagents
|
|
9
11
|
```
|
|
10
12
|
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
### 1. Define an agent
|
|
16
|
+
|
|
17
|
+
Create a `.pi/agents/code-reviewer.md` file in your project:
|
|
18
|
+
|
|
19
|
+
```markdown
|
|
20
|
+
---
|
|
21
|
+
name: code-reviewer
|
|
22
|
+
description: Reviews code changes for correctness and style
|
|
23
|
+
tools: read, grep, find, ls, bash
|
|
24
|
+
model: anthropic/claude-sonnet-4-6
|
|
25
|
+
thinking: low
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
You are a code reviewer. When given a diff or file list, read the relevant
|
|
29
|
+
files and provide a concise review covering:
|
|
30
|
+
|
|
31
|
+
- Logic errors and edge cases
|
|
32
|
+
- Style and consistency issues
|
|
33
|
+
- Performance concerns
|
|
34
|
+
- Test coverage gaps
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Use it
|
|
38
|
+
|
|
39
|
+
The `subagent` tool is automatically registered. In a pi session:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Review the changes in src/auth.ts using the code-reviewer agent
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or instruct pi to delegate:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Run the code-reviewer agent on the last three commits
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Agent configuration
|
|
52
|
+
|
|
53
|
+
Agents are Markdown files with YAML frontmatter.
|
|
54
|
+
|
|
55
|
+
| Field | Required | Default | Description |
|
|
56
|
+
|-------|----------|---------|-------------|
|
|
57
|
+
| `name` | **yes** | — | Unique agent identifier |
|
|
58
|
+
| `description` | no | — | Human-readable summary |
|
|
59
|
+
| `tools` | no | _none_ | Comma-separated tool whitelist (`read, write, bash, grep`, etc.) |
|
|
60
|
+
| `model` | no | parent's model | Provider/model-id (`anthropic/claude-sonnet-4-6`) |
|
|
61
|
+
| `thinking` | no | `off` | Reasoning level: `off`, `low`, `medium`, `high` |
|
|
62
|
+
| `systemPrompt` | no | `replace` | How the body is applied: `replace` (default system prompt) or `append` |
|
|
63
|
+
| `allowedAgents` | no | _all_ | Comma-separated list of sub-agents this agent may spawn |
|
|
64
|
+
| `maxDepth` | no | `10` | Maximum recursion depth (`0` = no sub-agents, `1` = one level, etc.) |
|
|
65
|
+
|
|
66
|
+
The Markdown body after the frontmatter is the agent's system prompt.
|
|
67
|
+
|
|
68
|
+
### Example with all fields
|
|
69
|
+
|
|
70
|
+
```markdown
|
|
71
|
+
---
|
|
72
|
+
name: orchestrator
|
|
73
|
+
description: High-level planner that delegates to specialists
|
|
74
|
+
tools: subagent, read, grep, find
|
|
75
|
+
model: anthropic/claude-sonnet-4-6
|
|
76
|
+
thinking: high
|
|
77
|
+
systemPrompt: replace
|
|
78
|
+
allowedAgents: code-reviewer, refactor, test-writer
|
|
79
|
+
maxDepth: 2
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
You are an orchestrator. Break complex tasks into sub-tasks and delegate
|
|
83
|
+
them to specialist agents. Combine their results and report a summary.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Agent discovery
|
|
87
|
+
|
|
88
|
+
Agents are discovered from two locations (project overrides global):
|
|
89
|
+
|
|
90
|
+
| Scope | Path |
|
|
91
|
+
|-------|------|
|
|
92
|
+
| Global | `~/.pi/agent/agents/*.md` |
|
|
93
|
+
| Project | `.pi/agents/*.md` |
|
|
94
|
+
|
|
95
|
+
Only `.md` files are scanned. Files are parsed at extension load time; parse errors produce warnings but don't block other agents.
|
|
96
|
+
|
|
97
|
+
## Recursion control
|
|
98
|
+
|
|
99
|
+
Sub-agents can spawn their own sub-agents (if the `subagent` tool is in their whitelist). Two mechanisms prevent unbounded recursion:
|
|
100
|
+
|
|
101
|
+
**`maxDepth`** — Hard limit counting from the originating agent. `maxDepth: 0` means the agent cannot spawn sub-agents. `maxDepth: 1` allows one level, etc. Defaults to `10` when the agent has `subagent` in tools.
|
|
102
|
+
|
|
103
|
+
**`allowedAgents`** — Whitelist enforced by the parent before spawning. A child process never sees agent names outside its parent's whitelist.
|
|
104
|
+
|
|
105
|
+
These are passed via environment variables (`PI_SUBAGENT_DEPTH`, `PI_SUBAGENT_MAX_DEPTH`, `PI_SUBAGENT_ALLOWED`).
|
|
106
|
+
|
|
107
|
+
## Session storage
|
|
108
|
+
|
|
109
|
+
Sub-agent sessions are saved as `.jsonl` files for post-hoc debugging:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
~/.pi/agent/sessions/--{safe-project-path}--/subagents/
|
|
113
|
+
├── 2026-05-31T03-47-16-502Z_019e7c24-a395-707a-a262-ec5b1664ffa7.jsonl
|
|
114
|
+
└── ...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Each file contains one JSON object per line — session headers, messages, tool calls, and usage data. Parent pi sessions live in the same project directory (no `subagents/` subdirectory).
|
|
118
|
+
|
|
11
119
|
## Development
|
|
12
120
|
|
|
13
121
|
```bash
|
|
@@ -23,7 +131,7 @@ bun run typecheck
|
|
|
23
131
|
# Format
|
|
24
132
|
bun run format
|
|
25
133
|
|
|
26
|
-
# Release (
|
|
134
|
+
# Release (requires GH_TOKEN and NPM_TOKEN)
|
|
27
135
|
bun run release
|
|
28
136
|
```
|
|
29
137
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
1
|
import type { AgentProgress } from './subagent-executor.ts';
|
|
2
|
+
import { numberArg, preview, shortenPath, stringArg } from './tool-args.ts';
|
|
3
3
|
|
|
4
4
|
export interface SubagentCallArgs {
|
|
5
5
|
agent?: string;
|
|
@@ -41,10 +41,6 @@ export function contextUsageSeverity(usage: {
|
|
|
41
41
|
return 'dim';
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function preview(text: string, length: number): string {
|
|
45
|
-
return text.length > length ? `${text.slice(0, length)}...` : text;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
function elapsedSeconds(ms: number): number {
|
|
49
45
|
return Math.round(ms / 1000);
|
|
50
46
|
}
|
|
@@ -85,26 +81,10 @@ function indent(text: string, spaces: number): string {
|
|
|
85
81
|
.join('\n');
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
function shortenPath(value: unknown): string | undefined {
|
|
89
|
-
if (typeof value !== 'string') return undefined;
|
|
90
|
-
const home = homedir();
|
|
91
|
-
return value.startsWith(`${home}/`) ? `~/${value.slice(home.length + 1)}` : value;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
84
|
function quote(value: string | undefined): string {
|
|
95
85
|
return value ? JSON.stringify(value) : '';
|
|
96
86
|
}
|
|
97
87
|
|
|
98
|
-
function stringArg(args: Record<string, unknown>, key: string): string | undefined {
|
|
99
|
-
const value = args[key];
|
|
100
|
-
return typeof value === 'string' ? value : undefined;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function numberArg(args: Record<string, unknown>, key: string): number | undefined {
|
|
104
|
-
const value = args[key];
|
|
105
|
-
return typeof value === 'number' ? value : undefined;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
88
|
function pathArg(args: Record<string, unknown>, fallback?: string): string {
|
|
109
89
|
return shortenPath(args.path ?? args.file_path) ?? fallback ?? '...';
|
|
110
90
|
}
|
|
@@ -221,6 +201,7 @@ export function formatSubagentResultLines(
|
|
|
221
201
|
const toolLines = formatToolLineItems(progress, options);
|
|
222
202
|
const usage = formatUsage(progress);
|
|
223
203
|
const lines: SubagentResultLine[] = [
|
|
204
|
+
{ text: '', kind: 'blank', singleLine: false },
|
|
224
205
|
{ text: statusLine, kind: 'status', singleLine: false },
|
|
225
206
|
...toolLines,
|
|
226
207
|
];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
1
|
import {
|
|
3
2
|
getMarkdownTheme,
|
|
4
3
|
keyHint,
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
formatUsage,
|
|
18
17
|
type SubagentResultLine,
|
|
19
18
|
} from './subagent-render.ts';
|
|
19
|
+
import { numberArg, preview, shortenPath, stringArg } from './tool-args.ts';
|
|
20
20
|
|
|
21
21
|
const SubagentParams = {
|
|
22
22
|
type: 'object',
|
|
@@ -98,26 +98,6 @@ type CollapsedTheme = {
|
|
|
98
98
|
bold: (text: string) => string;
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
function preview(text: string, length: number): string {
|
|
102
|
-
return text.length > length ? `${text.slice(0, length)}...` : text;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function shortenPath(value: unknown): string | undefined {
|
|
106
|
-
if (typeof value !== 'string') return undefined;
|
|
107
|
-
const home = homedir();
|
|
108
|
-
return value.startsWith(`${home}/`) ? `~/${value.slice(home.length + 1)}` : value;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function stringArg(args: Record<string, unknown>, key: string): string | undefined {
|
|
112
|
-
const value = args[key];
|
|
113
|
-
return typeof value === 'string' ? value : undefined;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function numberArg(args: Record<string, unknown>, key: string): number | undefined {
|
|
117
|
-
const value = args[key];
|
|
118
|
-
return typeof value === 'number' ? value : undefined;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
101
|
function styledPathArg(
|
|
122
102
|
args: Record<string, unknown>,
|
|
123
103
|
theme: CollapsedTheme,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
|
|
3
|
+
export function preview(text: string, length: number): string {
|
|
4
|
+
return text.length > length ? `${text.slice(0, length)}...` : text;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function shortenPath(value: unknown): string | undefined {
|
|
8
|
+
if (typeof value !== 'string') return undefined;
|
|
9
|
+
const home = homedir();
|
|
10
|
+
return value.startsWith(`${home}/`) ? `~/${value.slice(home.length + 1)}` : value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function stringArg(args: Record<string, unknown>, key: string): string | undefined {
|
|
14
|
+
const value = args[key];
|
|
15
|
+
return typeof value === 'string' ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function numberArg(args: Record<string, unknown>, key: string): number | undefined {
|
|
19
|
+
const value = args[key];
|
|
20
|
+
return typeof value === 'number' ? value : undefined;
|
|
21
|
+
}
|