@bacnh85/pi-subagent 0.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/README.md +88 -0
- package/agents/reviewer.md +30 -0
- package/agents/scout.md +44 -0
- package/agents/worker.md +15 -0
- package/agents.ts +174 -0
- package/index.ts +701 -0
- package/package.json +46 -0
- package/render.ts +250 -0
- package/runner.ts +258 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# pi-subagent
|
|
2
|
+
|
|
3
|
+
Minimal-overhead sub-agent extension for pi. Delegate tasks to specialized agents with isolated context — running in-process via the pi SDK for zero spawn overhead and ~10x fewer tokens than process-spawning.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **In-process execution**: Uses `createAgentSession()` directly — no `spawn("pi")` overhead
|
|
8
|
+
- **Minimal token budget**: Only the agent's system prompt (no AGENTS.md, no extensions, no thinking)
|
|
9
|
+
- **Streaming progress**: Real-time tool-call and text updates during execution
|
|
10
|
+
- **Three modes**: single, parallel (max 8 tasks, 4 concurrent), chain (sequential with `{previous}`)
|
|
11
|
+
- **Abort support**: Esc propagates to all sub-agents
|
|
12
|
+
- **TUI rendering**: Collapsed/expanded views with tool-call formatting and usage stats
|
|
13
|
+
- **Bundled agents**: scout, reviewer, worker — overridable with your own
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd extensions/pi-subagent
|
|
19
|
+
npm install
|
|
20
|
+
cd ../..
|
|
21
|
+
pi install ./extensions/pi-subagent
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or test directly:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pi -e ./extensions/pi-subagent
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Single agent
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Use scout to find authentication code in this project
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Parallel execution
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Run 2 scouts in parallel: one for models, one for providers
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Chain workflow
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Chain: scout finds auth code, then reviewer checks it for security issues
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Included Agents
|
|
51
|
+
|
|
52
|
+
| Agent | Model | Tools | Purpose |
|
|
53
|
+
|-------|-------|-------|---------|
|
|
54
|
+
| `scout` | Haiku | read, grep, find, ls | Fast codebase recon |
|
|
55
|
+
| `reviewer` | Sonnet | read, grep, find, ls, bash | Code review |
|
|
56
|
+
| `worker` | Sonnet | all | General implementation |
|
|
57
|
+
|
|
58
|
+
## Custom Agents
|
|
59
|
+
|
|
60
|
+
Create Markdown files with YAML frontmatter in `~/.pi/agent/agents/` (user-level) or `.pi/agents/` (project-level):
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
---
|
|
64
|
+
name: my-agent
|
|
65
|
+
description: When to use this agent
|
|
66
|
+
tools: read, grep, find, ls, bash
|
|
67
|
+
model: claude-haiku-4-5
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
Your system prompt here. This is the ONLY prompt the sub-agent sees.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See `agent-format.md` for the full format specification.
|
|
74
|
+
|
|
75
|
+
## Architecture
|
|
76
|
+
|
|
77
|
+
Sub-agents run in-process via the pi SDK. Compared to the process-spawn approach (spawning `pi --mode json`), this saves ~4-11K tokens per sub-agent invocation by:
|
|
78
|
+
|
|
79
|
+
- Using only the agent's system prompt (no pi defaults)
|
|
80
|
+
- Skipping AGENTS.md, extensions, skills, prompt templates
|
|
81
|
+
- Disabling thinking, compaction, retry
|
|
82
|
+
- Using in-memory sessions (no disk I/O)
|
|
83
|
+
- Sharing the parent's auth/model infrastructure
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
- pi coding agent with configured API keys
|
|
88
|
+
- Peer dependencies satisfied by the pi runtime (no extra npm install needed)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Code review specialist. Use for reviewing changes, finding bugs, suggesting improvements.
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-sonnet-4-20250514
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are a senior code reviewer. Review code changes and provide specific, actionable feedback.
|
|
9
|
+
|
|
10
|
+
Focus on:
|
|
11
|
+
1. **Correctness**: Logic errors, edge cases, off-by-one
|
|
12
|
+
2. **Security**: Injection risks, auth bypasses, data leaks
|
|
13
|
+
3. **Performance**: N+1 queries, unnecessary allocations, blocking calls
|
|
14
|
+
4. **Maintainability**: Unclear naming, missing error handling, tight coupling
|
|
15
|
+
5. **Best practices**: Idiomatic patterns, testability, documentation
|
|
16
|
+
|
|
17
|
+
Output format:
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
Brief assessment (1-2 sentences)
|
|
21
|
+
|
|
22
|
+
## Issues Found
|
|
23
|
+
For each issue:
|
|
24
|
+
- **Severity**: critical | high | medium | low
|
|
25
|
+
- **File**: path with line numbers
|
|
26
|
+
- **Problem**: What's wrong
|
|
27
|
+
- **Fix**: Suggested change (code block)
|
|
28
|
+
|
|
29
|
+
## Overall Assessment
|
|
30
|
+
Green/yellow/red with reasoning.
|
package/agents/scout.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scout
|
|
3
|
+
description: Fast codebase recon that returns compressed context for handoff. Use for finding files, understanding structure, locating symbols.
|
|
4
|
+
tools: read, grep, find, ls
|
|
5
|
+
model: claude-haiku-4-5
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
|
|
9
|
+
|
|
10
|
+
Your output will be passed to an agent who has NOT seen the files you explored.
|
|
11
|
+
|
|
12
|
+
Thoroughness (infer from task, default medium):
|
|
13
|
+
- Quick: Targeted lookups, key files only
|
|
14
|
+
- Medium: Follow imports, read critical sections
|
|
15
|
+
- Thorough: Trace all dependencies, check tests/types
|
|
16
|
+
|
|
17
|
+
Strategy:
|
|
18
|
+
1. grep/find to locate relevant code
|
|
19
|
+
2. Read key sections (not entire files)
|
|
20
|
+
3. Identify types, interfaces, key functions
|
|
21
|
+
4. Note dependencies between files
|
|
22
|
+
|
|
23
|
+
Output format:
|
|
24
|
+
|
|
25
|
+
## Files Retrieved
|
|
26
|
+
List with exact line ranges:
|
|
27
|
+
1. `path/to/file.ts` (lines 10-50) - Description of what's here
|
|
28
|
+
2. `path/to/other.ts` (lines 100-150) - Description
|
|
29
|
+
3. ...
|
|
30
|
+
|
|
31
|
+
## Key Code
|
|
32
|
+
Critical types, interfaces, or functions:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
interface Example {
|
|
36
|
+
// actual code from the files
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
Brief explanation of how the pieces connect.
|
|
42
|
+
|
|
43
|
+
## Start Here
|
|
44
|
+
Which file to look at first and why.
|
package/agents/worker.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worker
|
|
3
|
+
description: General-purpose coding agent with full tool access. Use for implementation, refactoring, debugging, and complex multi-step tasks.
|
|
4
|
+
model: claude-sonnet-4-20250514
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a skilled software engineer. Implement the requested task with care and precision.
|
|
8
|
+
|
|
9
|
+
Guidelines:
|
|
10
|
+
- Read before you edit. Understand existing code first.
|
|
11
|
+
- Make minimal, focused changes. Do not refactor unrelated code.
|
|
12
|
+
- Follow existing patterns, naming, and formatting.
|
|
13
|
+
- Write clear, idiomatic code with appropriate error handling.
|
|
14
|
+
- Test your changes when practical (run tests, lint, type-check).
|
|
15
|
+
- Explain your approach briefly before making changes.
|
package/agents.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent discovery and configuration for pi-sugagents.
|
|
3
|
+
*
|
|
4
|
+
* Loads agent definitions from Markdown files with YAML frontmatter.
|
|
5
|
+
* Discovers from user-level (~/.pi/agent/agents/), project-level
|
|
6
|
+
* (.pi/agents/), and bundled skill agents. Results are cached and
|
|
7
|
+
* invalidated on /reload.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
|
|
14
|
+
export type AgentScope = "user" | "project" | "both";
|
|
15
|
+
|
|
16
|
+
export interface AgentConfig {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
tools?: string[];
|
|
20
|
+
model?: string;
|
|
21
|
+
systemPrompt: string;
|
|
22
|
+
source: "user" | "project" | "bundled";
|
|
23
|
+
filePath: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AgentDiscoveryResult {
|
|
27
|
+
agents: AgentConfig[];
|
|
28
|
+
projectAgentsDir: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AgentCache {
|
|
32
|
+
userDir: string;
|
|
33
|
+
projectDir: string | null;
|
|
34
|
+
bundledDir: string;
|
|
35
|
+
agents: AgentConfig[];
|
|
36
|
+
projectAgentsDir: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let _cache: AgentCache | null = null;
|
|
40
|
+
|
|
41
|
+
/** Clear the agent cache (call on /reload). */
|
|
42
|
+
export function invalidateAgentCache(): void {
|
|
43
|
+
_cache = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadAgentsFromDir(dir: string, source: "user" | "project" | "bundled"): AgentConfig[] {
|
|
47
|
+
const agents: AgentConfig[] = [];
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(dir)) return agents;
|
|
50
|
+
|
|
51
|
+
let entries: fs.Dirent[];
|
|
52
|
+
try {
|
|
53
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
54
|
+
} catch {
|
|
55
|
+
return agents;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
60
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
61
|
+
|
|
62
|
+
const filePath = path.join(dir, entry.name);
|
|
63
|
+
let content: string;
|
|
64
|
+
try {
|
|
65
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
66
|
+
} catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
|
|
71
|
+
|
|
72
|
+
if (!frontmatter.name || !frontmatter.description) continue;
|
|
73
|
+
|
|
74
|
+
const tools = frontmatter.tools
|
|
75
|
+
?.split(",")
|
|
76
|
+
.map((t: string) => t.trim())
|
|
77
|
+
.filter(Boolean);
|
|
78
|
+
|
|
79
|
+
agents.push({
|
|
80
|
+
name: frontmatter.name,
|
|
81
|
+
description: frontmatter.description,
|
|
82
|
+
tools: tools && tools.length > 0 ? tools : undefined,
|
|
83
|
+
model: frontmatter.model || undefined,
|
|
84
|
+
systemPrompt: body,
|
|
85
|
+
source,
|
|
86
|
+
filePath,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return agents;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isDirectory(p: string): boolean {
|
|
94
|
+
try {
|
|
95
|
+
return fs.statSync(p).isDirectory();
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
102
|
+
let currentDir = cwd;
|
|
103
|
+
while (true) {
|
|
104
|
+
const candidate = path.join(currentDir, CONFIG_DIR_NAME, "agents");
|
|
105
|
+
if (isDirectory(candidate)) return candidate;
|
|
106
|
+
|
|
107
|
+
const parentDir = path.dirname(currentDir);
|
|
108
|
+
if (parentDir === currentDir) return null;
|
|
109
|
+
currentDir = parentDir;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Discover agents from standard locations plus bundled skill agents.
|
|
115
|
+
* @param cwd - Working directory for project-level discovery.
|
|
116
|
+
* @param scope - Which agent directories to use.
|
|
117
|
+
* @param bundledAgentsDir - Path to skill-bundled agents directory.
|
|
118
|
+
*/
|
|
119
|
+
export function discoverAgents(
|
|
120
|
+
cwd: string,
|
|
121
|
+
scope: AgentScope,
|
|
122
|
+
bundledAgentsDir: string,
|
|
123
|
+
): AgentDiscoveryResult {
|
|
124
|
+
const userDir = path.join(getAgentDir(), "agents");
|
|
125
|
+
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
126
|
+
|
|
127
|
+
// Check cache
|
|
128
|
+
if (
|
|
129
|
+
_cache &&
|
|
130
|
+
_cache.userDir === userDir &&
|
|
131
|
+
_cache.projectDir === projectAgentsDir &&
|
|
132
|
+
_cache.bundledDir === bundledAgentsDir
|
|
133
|
+
) {
|
|
134
|
+
return { agents: _cache.agents, projectAgentsDir: _cache.projectAgentsDir };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
|
|
138
|
+
const projectAgents =
|
|
139
|
+
scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
|
|
140
|
+
const bundledAgents = loadAgentsFromDir(bundledAgentsDir, "bundled");
|
|
141
|
+
|
|
142
|
+
const agentMap = new Map<string, AgentConfig>();
|
|
143
|
+
|
|
144
|
+
// Priority: bundled < user < project (higher index = higher priority)
|
|
145
|
+
for (const agent of bundledAgents) agentMap.set(agent.name, agent);
|
|
146
|
+
if (scope === "both" || scope === "user") {
|
|
147
|
+
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
148
|
+
}
|
|
149
|
+
if (scope === "both" || scope === "project") {
|
|
150
|
+
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const agents = Array.from(agentMap.values());
|
|
154
|
+
|
|
155
|
+
_cache = {
|
|
156
|
+
userDir,
|
|
157
|
+
projectDir: projectAgentsDir,
|
|
158
|
+
bundledDir: bundledAgentsDir,
|
|
159
|
+
agents,
|
|
160
|
+
projectAgentsDir,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return { agents, projectAgentsDir };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {
|
|
167
|
+
if (agents.length === 0) return { text: "none", remaining: 0 };
|
|
168
|
+
const listed = agents.slice(0, maxItems);
|
|
169
|
+
const remaining = agents.length - listed.length;
|
|
170
|
+
return {
|
|
171
|
+
text: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join("; "),
|
|
172
|
+
remaining,
|
|
173
|
+
};
|
|
174
|
+
}
|