@fosterg4/pi-subagent 1.0.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 ADDED
@@ -0,0 +1,170 @@
1
+ # @fosterg4/pi-subagent
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@fosterg4/pi-subagent)](https://www.npmjs.com/package/@fosterg4/pi-subagent)
4
+
5
+ Delegate complex tasks to specialized sub-agents with isolated context windows, structured JSON handoff, contract schemas, and live TUI streaming — all within [pi](https://pi.dev).
6
+
7
+ ## Features
8
+
9
+ - **Isolated context** — Each subagent runs in a separate `pi` process with its own context window
10
+ - **Three execution modes** — Single, parallel (max 8, concurrency 4), and chain (sequential with data handoff)
11
+ - **Structured JSON handoff** — Agents pass typed data between each other, not freeform markdown
12
+ - **Contract schemas** — `inputSchema`/`outputSchema` in agent frontmatter ensures valid handoffs
13
+ - **Live TUI streaming** — Subagent tool calls (read, bash, grep, etc.) stream in real-time
14
+ - **Bundled agents** — 4 built-in agents ready to use: scout, planner, reviewer, worker
15
+ - **Workflow prompts** — `/implement`, `/scout-and-plan`, `/implement-and-review` commands
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pi install npm:@fosterg4/pi-subagent
21
+ ```
22
+
23
+ For a quick test without installing:
24
+
25
+ ```bash
26
+ pi -e npm:@fosterg4/pi-subagent
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Single agent
32
+
33
+ Ask the LLM to use a subagent:
34
+
35
+ ```
36
+ Use scout to find all authentication code in the project
37
+ ```
38
+
39
+ The LLM will call the `subagent` tool with `{ agent: "scout", task: "..." }`.
40
+
41
+ ### Parallel execution
42
+
43
+ ```
44
+ Run 2 scouts in parallel: one to find models, one to find providers
45
+ ```
46
+
47
+ ### Chained workflow
48
+
49
+ ```
50
+ Use a chain: first have scout find the read tool, then have planner suggest improvements
51
+ ```
52
+
53
+ ### Workflow prompts
54
+
55
+ ```
56
+ /implement add Redis caching to the session store
57
+ /scout-and-plan refactor auth to support OAuth
58
+ /implement-and-review add input validation to API endpoints
59
+ ```
60
+
61
+ ## Bundled Agents
62
+
63
+ | Agent | Purpose | Default Model |
64
+ |-------|---------|---------------|
65
+ | `scout` | Fast codebase recon — returns structured findings | claude-haiku-4-5 |
66
+ | `planner` | Creates implementation plans from context & requirements | claude-sonnet-4-5 |
67
+ | `reviewer` | Code review — quality, security, maintainability | claude-sonnet-4-5 |
68
+ | `worker` | General-purpose implementation with full capabilities | claude-sonnet-4-5 |
69
+
70
+ Each agent has a defined `inputSchema` and `outputSchema` in its frontmatter, ensuring structured data flows between chained agents.
71
+
72
+ ## Tool Parameters
73
+
74
+ The `subagent` tool accepts three mutually exclusive modes:
75
+
76
+ ### Single mode
77
+
78
+ ```json
79
+ {
80
+ "agent": "scout",
81
+ "task": "Find all authentication code",
82
+ "cwd": "/optional/working/directory"
83
+ }
84
+ ```
85
+
86
+ ### Parallel mode
87
+
88
+ ```json
89
+ {
90
+ "tasks": [
91
+ { "agent": "scout", "task": "Find models" },
92
+ { "agent": "scout", "task": "Find providers" }
93
+ ]
94
+ }
95
+ ```
96
+
97
+ ### Chain mode
98
+
99
+ ```json
100
+ {
101
+ "chain": [
102
+ { "agent": "scout", "task": "Investigate the codebase" },
103
+ { "agent": "planner", "task": "Create a plan from: {previous}" }
104
+ ]
105
+ }
106
+ ```
107
+
108
+ ### Common options
109
+
110
+ | Option | Type | Default | Description |
111
+ |--------|------|---------|-------------|
112
+ | `agentScope` | `"user"`, `"project"`, `"both"` | `"user"` | Which agent directories to search |
113
+ | `confirmProjectAgents` | `boolean` | `true` | Prompt before running project agents |
114
+ | `cwd` | `string` | current dir | Working directory for subprocess |
115
+
116
+ ## Custom Agents
117
+
118
+ Create your own agents as `.md` files with YAML frontmatter:
119
+
120
+ ```markdown
121
+ ---
122
+ name: my-agent
123
+ description: What this agent does
124
+ tools: read, grep, find, ls, bash
125
+ model: claude-haiku-4-5
126
+ inputSchema:
127
+ type: object
128
+ properties:
129
+ query:
130
+ type: string
131
+ required: [query]
132
+ outputSchema:
133
+ type: object
134
+ properties:
135
+ result:
136
+ type: string
137
+ required: [result]
138
+ ---
139
+
140
+ System prompt for the agent goes here.
141
+ ```
142
+
143
+ **Agent locations** (priority: project > user > bundled):
144
+ - `~/.pi/agent/agents/*.md` — User-level (always loaded)
145
+ - `.pi/agents/*.md` — Project-level (requires `agentScope: "project"` or `"both"`)
146
+ - Bundled with package — Lowest priority, always available
147
+
148
+ ## Security
149
+
150
+ - **User agents** (`~/.pi/agent/agents/`): Always trusted
151
+ - **Project agents** (`.pi/agents/`): Requires confirmation prompt before execution
152
+ - **Bundled agents**: Trusted by virtue of package installation
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ # Clone and test locally
158
+ git clone https://github.com/fosterg4/pi-subagent.git
159
+ cd pi-subagent
160
+
161
+ # Test with pi
162
+ pi -e ./index.ts
163
+
164
+ # Publish
165
+ npm publish --access public
166
+ ```
167
+
168
+ ## License
169
+
170
+ MIT
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: planner
3
+ description: Creates implementation plans from context and requirements
4
+ tools: read, grep, find, ls
5
+ model: claude-sonnet-4-5
6
+ inputSchema:
7
+ type: object
8
+ properties:
9
+ findings:
10
+ type: object
11
+ description: "Scout findings or context about the codebase"
12
+ requirements:
13
+ type: string
14
+ description: "What needs to be built or changed"
15
+ required: [requirements]
16
+ outputSchema:
17
+ type: object
18
+ properties:
19
+ goal:
20
+ type: string
21
+ steps:
22
+ type: array
23
+ items:
24
+ type: object
25
+ properties:
26
+ step: { type: number }
27
+ action: { type: string }
28
+ file: { type: string }
29
+ details: { type: string }
30
+ filesToModify:
31
+ type: array
32
+ items:
33
+ type: object
34
+ properties:
35
+ path: { type: string }
36
+ changes: { type: string }
37
+ newFiles:
38
+ type: array
39
+ items:
40
+ type: object
41
+ properties:
42
+ path: { type: string }
43
+ purpose: { type: string }
44
+ risks:
45
+ type: string
46
+ required: [goal, steps, filesToModify]
47
+ ---
48
+
49
+ You are a planning specialist. You receive context (from a scout) and requirements, then produce a clear implementation plan.
50
+
51
+ You must NOT make any changes. Only read, analyze, and plan.
52
+
53
+ Return your plan as a JSON object matching the outputSchema.
54
+
55
+ ## Output format (JSON)
56
+
57
+ ```json
58
+ {
59
+ "goal": "One sentence summary",
60
+ "steps": [
61
+ { "step": 1, "action": "Modify function X", "file": "src/file.ts", "details": "What to change" }
62
+ ],
63
+ "filesToModify": [
64
+ { "path": "src/file.ts", "changes": "What changes" }
65
+ ],
66
+ "newFiles": [
67
+ { "path": "src/new.ts", "purpose": "Purpose" }
68
+ ],
69
+ "risks": "Anything to watch out for"
70
+ }
71
+ ```
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: reviewer
3
+ description: Code review specialist for quality and security analysis
4
+ tools: read, grep, find, ls, bash
5
+ model: claude-sonnet-4-5
6
+ inputSchema:
7
+ type: object
8
+ properties:
9
+ changes:
10
+ type: string
11
+ description: "Description of what was changed"
12
+ files:
13
+ type: array
14
+ items:
15
+ type: object
16
+ properties:
17
+ path: { type: string }
18
+ description: { type: string }
19
+ required: [changes, files]
20
+ outputSchema:
21
+ type: object
22
+ properties:
23
+ filesReviewed:
24
+ type: array
25
+ items:
26
+ type: string
27
+ critical:
28
+ type: array
29
+ items:
30
+ type: object
31
+ properties:
32
+ location: { type: string }
33
+ issue: { type: string }
34
+ warnings:
35
+ type: array
36
+ items:
37
+ type: object
38
+ properties:
39
+ location: { type: string }
40
+ issue: { type: string }
41
+ suggestions:
42
+ type: array
43
+ items:
44
+ type: object
45
+ properties:
46
+ location: { type: string }
47
+ idea: { type: string }
48
+ summary:
49
+ type: string
50
+ required: [filesReviewed, critical, warnings, summary]
51
+ ---
52
+
53
+ You are a senior code reviewer. Analyze code for quality, security, and maintainability.
54
+
55
+ Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
56
+ Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
57
+
58
+ Strategy:
59
+ 1. Run `git diff` to see recent changes (if applicable)
60
+ 2. Read the modified files
61
+ 3. Check for bugs, security issues, code smells
62
+
63
+ Return your review as a JSON object matching the outputSchema.
64
+
65
+ ## Output format (JSON)
66
+
67
+ ```json
68
+ {
69
+ "filesReviewed": ["src/file.ts"],
70
+ "critical": [
71
+ { "location": "file.ts:42", "issue": "Issue description" }
72
+ ],
73
+ "warnings": [
74
+ { "location": "file.ts:100", "issue": "Issue description" }
75
+ ],
76
+ "suggestions": [
77
+ { "location": "file.ts:150", "idea": "Improvement idea" }
78
+ ],
79
+ "summary": "Overall assessment in 2-3 sentences."
80
+ }
81
+ ```
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: scout
3
+ description: Fast codebase recon that returns compressed context for handoff to other agents
4
+ tools: read, grep, find, ls, bash
5
+ model: claude-haiku-4-5
6
+ inputSchema:
7
+ type: object
8
+ properties:
9
+ query:
10
+ type: string
11
+ description: "What to investigate"
12
+ thoroughness:
13
+ type: string
14
+ enum: [quick, medium, thorough]
15
+ description: "Depth of investigation"
16
+ default: medium
17
+ required: [query]
18
+ outputSchema:
19
+ type: object
20
+ properties:
21
+ filesRetrieved:
22
+ type: array
23
+ items:
24
+ type: object
25
+ properties:
26
+ path: { type: string }
27
+ lines: { type: string }
28
+ description: { type: string }
29
+ architecture:
30
+ type: string
31
+ keyCode:
32
+ type: string
33
+ startHere:
34
+ type: string
35
+ required: [filesRetrieved, architecture]
36
+ ---
37
+
38
+ You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
39
+
40
+ Your output will be passed to an agent who has NOT seen the files you explored.
41
+
42
+ Thoroughness (infer from task, default medium):
43
+ - Quick: Targeted lookups, key files only
44
+ - Medium: Follow imports, read critical sections
45
+ - Thorough: Trace all dependencies, check tests/types
46
+
47
+ Strategy:
48
+ 1. grep/find to locate relevant code
49
+ 2. Read key sections (not entire files)
50
+ 3. Identify types, interfaces, key functions
51
+ 4. Note dependencies between files
52
+
53
+ Return your findings as a JSON object matching the outputSchema.
54
+
55
+ ## Output format (JSON)
56
+
57
+ ```json
58
+ {
59
+ "filesRetrieved": [
60
+ { "path": "src/file.ts", "lines": "10-50", "description": "What's here" }
61
+ ],
62
+ "architecture": "Brief explanation of how the pieces connect.",
63
+ "keyCode": "Critical code snippet",
64
+ "startHere": "Which file to look at first and why"
65
+ }
66
+ ```
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: worker
3
+ description: General-purpose subagent with full capabilities, isolated context
4
+ model: claude-sonnet-4-5
5
+ inputSchema:
6
+ type: object
7
+ properties:
8
+ plan:
9
+ type: object
10
+ description: "Implementation plan with steps, files, and context"
11
+ context:
12
+ type: string
13
+ description: "Additional context about the codebase"
14
+ required: [plan]
15
+ outputSchema:
16
+ type: object
17
+ properties:
18
+ completed:
19
+ type: string
20
+ filesChanged:
21
+ type: array
22
+ items:
23
+ type: object
24
+ properties:
25
+ path: { type: string }
26
+ change: { type: string }
27
+ notes:
28
+ type: string
29
+ required: [completed, filesChanged]
30
+ ---
31
+
32
+ You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation.
33
+
34
+ Work autonomously to complete the assigned task. Use all available tools as needed.
35
+
36
+ Return your results as a JSON object matching the outputSchema.
37
+
38
+ ## Output format (JSON)
39
+
40
+ ```json
41
+ {
42
+ "completed": "What was done.",
43
+ "filesChanged": [
44
+ { "path": "src/file.ts", "change": "What changed" }
45
+ ],
46
+ "notes": "Anything the main agent should know."
47
+ }
48
+ ```
package/agents.ts ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Agent discovery and configuration
3
+ *
4
+ * Discovers agents from three sources (lowest to highest priority):
5
+ * 1. Bundled agents (shipped with the @fosterg4/pi-subagent package)
6
+ * 2. User agents (~/.pi/agent/agents/)
7
+ * 3. Project agents (.pi/agents/ - requires "project" or "both" scope)
8
+ */
9
+
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
14
+
15
+ export type AgentScope = "user" | "project" | "both";
16
+
17
+ export interface AgentConfig {
18
+ name: string;
19
+ description: string;
20
+ tools?: string[];
21
+ model?: string;
22
+ systemPrompt: string;
23
+ source: "user" | "project" | "bundled";
24
+ filePath: string;
25
+ inputSchema?: Record<string, unknown>;
26
+ outputSchema?: Record<string, unknown>;
27
+ }
28
+
29
+ export interface AgentDiscoveryResult {
30
+ agents: AgentConfig[];
31
+ projectAgentsDir: string | null;
32
+ }
33
+
34
+ /**
35
+ * Resolve the directory where the package's bundled agents live.
36
+ * Uses import.meta.url to find the package directory at runtime.
37
+ */
38
+ function getBundledAgentsDir(): string | null {
39
+ try {
40
+ const currentFile = fileURLToPath(import.meta.url);
41
+ const packageDir = path.dirname(currentFile);
42
+ const bundledDir = path.join(packageDir, "agents");
43
+ return fs.existsSync(bundledDir) ? bundledDir : null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function loadAgentsFromDir(
50
+ dir: string,
51
+ source: AgentConfig["source"],
52
+ ): AgentConfig[] {
53
+ const agents: AgentConfig[] = [];
54
+
55
+ if (!fs.existsSync(dir)) return agents;
56
+
57
+ let entries: fs.Dirent[];
58
+ try {
59
+ entries = fs.readdirSync(dir, { withFileTypes: true });
60
+ } catch {
61
+ return agents;
62
+ }
63
+
64
+ for (const entry of entries) {
65
+ if (!entry.name.endsWith(".md")) continue;
66
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
67
+
68
+ const filePath = path.join(dir, entry.name);
69
+ let content: string;
70
+ try {
71
+ content = fs.readFileSync(filePath, "utf-8");
72
+ } catch {
73
+ continue;
74
+ }
75
+
76
+ const { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);
77
+
78
+ if (!frontmatter.name || !frontmatter.description) continue;
79
+
80
+ const name = String(frontmatter.name);
81
+ const description = String(frontmatter.description);
82
+
83
+ const toolsStr = frontmatter.tools;
84
+ const tools =
85
+ typeof toolsStr === "string"
86
+ ? toolsStr
87
+ .split(",")
88
+ .map((t: string) => t.trim())
89
+ .filter(Boolean)
90
+ : undefined;
91
+
92
+ const model = typeof frontmatter.model === "string" ? frontmatter.model : undefined;
93
+
94
+ // Parse contract schemas (optional)
95
+ const inputSchema =
96
+ frontmatter.inputSchema && typeof frontmatter.inputSchema === "object"
97
+ ? (frontmatter.inputSchema as Record<string, unknown>)
98
+ : undefined;
99
+
100
+ const outputSchema =
101
+ frontmatter.outputSchema && typeof frontmatter.outputSchema === "object"
102
+ ? (frontmatter.outputSchema as Record<string, unknown>)
103
+ : undefined;
104
+
105
+ agents.push({
106
+ name,
107
+ description,
108
+ tools: tools && tools.length > 0 ? tools : undefined,
109
+ model,
110
+ systemPrompt: body,
111
+ source,
112
+ filePath,
113
+ inputSchema,
114
+ outputSchema,
115
+ });
116
+ }
117
+
118
+ return agents;
119
+ }
120
+
121
+ function isDirectory(p: string): boolean {
122
+ try {
123
+ return fs.statSync(p).isDirectory();
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ function findNearestProjectAgentsDir(cwd: string): string | null {
130
+ let currentDir = cwd;
131
+ while (true) {
132
+ const candidate = path.join(currentDir, ".pi", "agents");
133
+ if (isDirectory(candidate)) return candidate;
134
+
135
+ const parentDir = path.dirname(currentDir);
136
+ if (parentDir === currentDir) return null;
137
+ currentDir = parentDir;
138
+ }
139
+ }
140
+
141
+ export function discoverAgents(
142
+ cwd: string,
143
+ scope: AgentScope,
144
+ ): AgentDiscoveryResult {
145
+ const userDir = path.join(getAgentDir(), "agents");
146
+ const projectAgentsDir = findNearestProjectAgentsDir(cwd);
147
+ const bundledDir = getBundledAgentsDir();
148
+
149
+ // Load agents from each source
150
+ const bundledAgents = bundledDir ? loadAgentsFromDir(bundledDir, "bundled") : [];
151
+ const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
152
+ const projectAgents =
153
+ scope === "user" || !projectAgentsDir
154
+ ? []
155
+ : loadAgentsFromDir(projectAgentsDir, "project");
156
+
157
+ // Merge with override priority: project > user > bundled
158
+ const agentMap = new Map<string, AgentConfig>();
159
+
160
+ // Lowest priority: bundled
161
+ for (const agent of bundledAgents) {
162
+ agentMap.set(agent.name, agent);
163
+ }
164
+ // Middle priority: user (overrides bundled)
165
+ if (scope !== "project") {
166
+ for (const agent of userAgents) {
167
+ agentMap.set(agent.name, agent);
168
+ }
169
+ }
170
+ // Highest priority: project (overrides user and bundled)
171
+ if (scope !== "user" && projectAgentsDir) {
172
+ for (const agent of projectAgents) {
173
+ agentMap.set(agent.name, agent);
174
+ }
175
+ }
176
+
177
+ return {
178
+ agents: Array.from(agentMap.values()),
179
+ projectAgentsDir,
180
+ };
181
+ }
182
+
183
+ export function formatAgentList(
184
+ agents: AgentConfig[],
185
+ maxItems: number,
186
+ ): { text: string; remaining: number } {
187
+ if (agents.length === 0) return { text: "none", remaining: 0 };
188
+ const listed = agents.slice(0, maxItems);
189
+ const remaining = agents.length - listed.length;
190
+ return {
191
+ text: listed
192
+ .map((a) => `${a.name} (${a.source}): ${a.description}`)
193
+ .join("; "),
194
+ remaining,
195
+ };
196
+ }