@esso0428/pi-subagents 0.15.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/CHANGELOG.md +638 -0
- package/CONTRIBUTING.md +68 -0
- package/LICENSE +21 -0
- package/README.md +745 -0
- package/SECURITY.md +95 -0
- package/dist/agent-manager.d.ts +144 -0
- package/dist/agent-manager.js +542 -0
- package/dist/agent-runner.d.ts +212 -0
- package/dist/agent-runner.js +850 -0
- package/dist/agent-types.d.ts +67 -0
- package/dist/agent-types.js +168 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.js +56 -0
- package/dist/cross-extension-rpc.d.ts +46 -0
- package/dist/cross-extension-rpc.js +76 -0
- package/dist/custom-agents.d.ts +17 -0
- package/dist/custom-agents.js +156 -0
- package/dist/default-agents.d.ts +7 -0
- package/dist/default-agents.js +122 -0
- package/dist/enabled-models.d.ts +49 -0
- package/dist/enabled-models.js +145 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.js +28 -0
- package/dist/group-join.d.ts +32 -0
- package/dist/group-join.js +116 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +2209 -0
- package/dist/invocation-config.d.ts +22 -0
- package/dist/invocation-config.js +15 -0
- package/dist/memory.d.ts +53 -0
- package/dist/memory.js +165 -0
- package/dist/model-resolver.d.ts +19 -0
- package/dist/model-resolver.js +80 -0
- package/dist/nico-overrides.d.ts +53 -0
- package/dist/nico-overrides.js +169 -0
- package/dist/output-file.d.ts +24 -0
- package/dist/output-file.js +101 -0
- package/dist/prompts.d.ts +32 -0
- package/dist/prompts.js +73 -0
- package/dist/schedule-store.d.ts +38 -0
- package/dist/schedule-store.js +155 -0
- package/dist/schedule.d.ts +109 -0
- package/dist/schedule.js +338 -0
- package/dist/settings.d.ts +141 -0
- package/dist/settings.js +162 -0
- package/dist/skill-loader.d.ts +24 -0
- package/dist/skill-loader.js +93 -0
- package/dist/status-note.d.ts +13 -0
- package/dist/status-note.js +24 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.js +5 -0
- package/dist/ui/agent-widget.d.ts +160 -0
- package/dist/ui/agent-widget.js +484 -0
- package/dist/ui/conversation-viewer.d.ts +57 -0
- package/dist/ui/conversation-viewer.js +354 -0
- package/dist/ui/fleet-list.d.ts +106 -0
- package/dist/ui/fleet-list.js +345 -0
- package/dist/ui/schedule-menu.d.ts +16 -0
- package/dist/ui/schedule-menu.js +95 -0
- package/dist/ui/viewer-keys.d.ts +20 -0
- package/dist/ui/viewer-keys.js +17 -0
- package/dist/usage.d.ts +50 -0
- package/dist/usage.js +49 -0
- package/dist/worktree.d.ts +45 -0
- package/dist/worktree.js +160 -0
- package/examples/agent-tool-description.md +42 -0
- package/package.json +56 -0
- package/src/agent-manager.ts +631 -0
- package/src/agent-runner.ts +1014 -0
- package/src/agent-types.ts +202 -0
- package/src/context.ts +58 -0
- package/src/cross-extension-rpc.ts +122 -0
- package/src/custom-agents.ts +167 -0
- package/src/default-agents.ts +126 -0
- package/src/enabled-models.ts +180 -0
- package/src/env.ts +33 -0
- package/src/group-join.ts +141 -0
- package/src/index.ts +2400 -0
- package/src/invocation-config.ts +40 -0
- package/src/memory.ts +179 -0
- package/src/model-resolver.ts +100 -0
- package/src/nico-overrides.ts +235 -0
- package/src/output-file.ts +110 -0
- package/src/prompts.ts +99 -0
- package/src/schedule-store.ts +153 -0
- package/src/schedule.ts +365 -0
- package/src/settings.ts +288 -0
- package/src/skill-loader.ts +102 -0
- package/src/status-note.ts +25 -0
- package/src/types.ts +208 -0
- package/src/ui/agent-widget.ts +566 -0
- package/src/ui/conversation-viewer.ts +362 -0
- package/src/ui/fleet-list.ts +380 -0
- package/src/ui/schedule-menu.ts +104 -0
- package/src/ui/viewer-keys.ts +39 -0
- package/src/usage.ts +60 -0
- package/src/worktree.ts +191 -0
- package/vitest.config.ts +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
# @esso0428/pi-subagents
|
|
2
|
+
|
|
3
|
+
A [pi](https://pi.dev) extension that brings **Claude Code-style autonomous sub-agents** to pi. Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level. Run them in foreground or background, steer them mid-run, resume completed sessions, and define your own custom agent types.
|
|
4
|
+
|
|
5
|
+
> **Fork of [`@tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) integrating `npm:pi-subagents`-style JSON agent overrides — configure agents via `settings.json` without writing `.md` files.**
|
|
6
|
+
|
|
7
|
+
<img width="600" alt="pi-subagents screenshot" src="https://github.com/tintinweb/pi-subagents/raw/master/media/screenshot.png" />
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`) — feels native
|
|
16
|
+
- **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 4) and smart group join (consolidated notifications)
|
|
17
|
+
- **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons. Configurable via `/agents → Settings → Widget`: `all` (every agent), `background` (default — hides foreground runs, which already render inline as the `Agent` tool result), or `off`
|
|
18
|
+
- **FleetView** — Claude Code-style navigable list of `main` + every running subagent rendered below the editor (earliest-launched first). Press `↓` (or `←`) at an empty prompt to jump in, `↑`/`↓` to move the selection, `Enter` to open the selected agent's live, auto-updating conversation, `Esc` to return. Finished agents linger briefly before dropping out, and a viewer stays open through completion so you can read the final output. Toggle via `/agents → Settings → Fleet view`
|
|
19
|
+
- **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause). Steer a running agent inline by pressing `Enter` to open a composer, typing, then `Enter` to send (`Esc` or an empty submit returns) — the message appears as a user message and redirects the agent after its current tool. Stop a still-running agent by pressing `x` (then `x` again to confirm) — both work for background agents too
|
|
20
|
+
- **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions
|
|
21
|
+
- **Mid-run steering** — inject messages into running agents to redirect their work without restarting
|
|
22
|
+
- **Session resume** — pick up where an agent left off, preserving full conversation context
|
|
23
|
+
- **Graceful turn limits** — agents get a "wrap up" warning before hard abort, producing clean partial results instead of cut-off output
|
|
24
|
+
- **Case-insensitive agent types** — `"explore"`, `"Explore"`, `"EXPLORE"` all work. Unknown types fall back to general-purpose with a note
|
|
25
|
+
- **Fuzzy model selection** — specify models by name (`"haiku"`, `"sonnet"`) instead of full IDs, with automatic filtering to only available/configured models
|
|
26
|
+
- **Context inheritance** — optionally fork the parent conversation into a sub-agent so it knows what's been discussed
|
|
27
|
+
- **Persistent agent memory** — three scopes (project, local, user) with automatic read-only fallback for agents without write tools
|
|
28
|
+
- **Git worktree isolation** — run agents in isolated repo copies; changes auto-committed to branches on completion
|
|
29
|
+
- **Skill preloading** — inject named skills into agent system prompts, discovered from `.pi/skills/`, `.agents/skills/`, and global locations (Pi-standard `<name>/SKILL.md` directory layout supported)
|
|
30
|
+
- **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
|
|
31
|
+
- **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
|
|
32
|
+
- **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
|
|
33
|
+
- **Cross-extension RPC** — other pi extensions can spawn and stop subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start
|
|
34
|
+
- **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results land via the same `subagent-notification` followUp path as manual background completions; manage via `/agents → Scheduled jobs`
|
|
35
|
+
- **Model scope enforcement** — opt-in validation that subagent model choices stay within your pi `enabledModels` allowlist (sourced from `/scoped-models`, with both global and project-local pi settings honored). Caller-supplied out-of-scope → hard error to orchestrator; frontmatter-pinned out-of-scope → warning + runs anyway (frontmatter authoritative). Toggle via `/agents → Settings → Scope models`
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pi install npm:@esso0428/pi-subagents
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or load directly for development:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pi -e ./src/index.ts
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
The parent agent spawns sub-agents using the `Agent` tool:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
Agent({
|
|
55
|
+
subagent_type: "Explore",
|
|
56
|
+
prompt: "Find all files that handle authentication",
|
|
57
|
+
description: "Find auth files",
|
|
58
|
+
run_in_background: true,
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Foreground agents block until complete and return results inline. Background agents return an ID immediately and notify you on completion.
|
|
63
|
+
|
|
64
|
+
### Scheduling
|
|
65
|
+
|
|
66
|
+
Add a `schedule` field to register the agent to fire later instead of running now:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Agent({
|
|
70
|
+
subagent_type: "Explore",
|
|
71
|
+
prompt: "Look at recent commits and summarize what changed since last week",
|
|
72
|
+
description: "Weekly commit review",
|
|
73
|
+
schedule: "0 0 9 * * 1", // 9am every Monday (6-field cron)
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Schedule formats:
|
|
78
|
+
|
|
79
|
+
- **Cron** — 6-field (`second minute hour day-of-month month day-of-week`), e.g. `"0 0 9 * * 1"` for 9am every Monday, `"0 */15 * * * *"` for every 15 minutes.
|
|
80
|
+
- **Interval** — `"5m"`, `"1h"`, `"30s"`, `"2d"`. Fires repeatedly at that interval.
|
|
81
|
+
- **One-shot relative** — `"+10m"`, `"+2h"`, `"+1d"`. Fires once at that future time.
|
|
82
|
+
- **One-shot absolute** — full ISO timestamp, e.g. `"2026-12-25T09:00:00.000Z"`.
|
|
83
|
+
|
|
84
|
+
When a schedule fires, the spawn runs in background and its completion notification arrives in the conversation through the same `subagent-notification` followUp path as a manually-spawned background agent — your parent agent reasons about the result the same way.
|
|
85
|
+
|
|
86
|
+
Schedules are **session-scoped**: they reset on `/new` and restore on `/resume`. List and cancel via `/agents → Scheduled jobs` (creation is the `Agent` tool's job — there is no parallel manual-create wizard). Storage at `<cwd>/.pi/subagent-schedules/<sessionId>.json` with PID-based file locking for cross-instance safety.
|
|
87
|
+
|
|
88
|
+
**Disable the feature entirely**: `/agents → Settings → Scheduling → disabled` removes `schedule` from the `Agent` tool spec (no LLM-context cost), hides the menu entry, and stops any active scheduler. The schema-level removal takes effect on the next pi session; the runtime kill is immediate. Re-enable from the same menu.
|
|
89
|
+
|
|
90
|
+
Restrictions:
|
|
91
|
+
- `schedule` cannot be combined with `inherit_context` (no parent conversation exists at fire time) or `resume` (schedules create fresh agents).
|
|
92
|
+
- `run_in_background` is forced to `true`.
|
|
93
|
+
- Scheduled fires bypass the `maxConcurrent` queue so a 5-minute interval cannot be deferred behind long-running manual agents.
|
|
94
|
+
- **Headless `pi -p` doesn't wait for scheduled subagents.**
|
|
95
|
+
|
|
96
|
+
## UI
|
|
97
|
+
|
|
98
|
+
The extension renders a persistent widget above the editor showing active agents. By default it shows background runs only (`widgetMode: background`) — foreground agents already render inline as the `Agent` tool result, so the widget would otherwise double-render them. Switch to `all` (every agent) or `off` (hide the widget) via `/agents → Settings → Widget`:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
● Agents
|
|
102
|
+
├─ ⠹ Agent Refactor auth module · ↻5≤30 · 5 tool uses · 33.8k token (62%) · 12.3s
|
|
103
|
+
│ ⎿ editing 2 files…
|
|
104
|
+
├─ ⠹ Explore Find auth files · ↻3 · 3 tool uses · 12.4k token (8%) · 4.1s
|
|
105
|
+
│ ⎿ searching…
|
|
106
|
+
├─ ⠹ Agent Long-running task · ↻42 · 38 tool uses · 91.0k token (84% · ⇊2) · 2m17s
|
|
107
|
+
│ ⎿ reading…
|
|
108
|
+
└─ 2 queued
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The token field is annotated with two optional signals inside parens:
|
|
112
|
+
- **`NN%`** — context-window utilization (color-coded: <70% dim, 70–85% warning, ≥85% error). Omitted when the model has no declared `contextWindow`, or briefly right after compaction.
|
|
113
|
+
- **`⇊N`** — number of times the session has compacted, when > 0. Stays dim; the percent's color carries urgency.
|
|
114
|
+
|
|
115
|
+
### FleetView
|
|
116
|
+
|
|
117
|
+
While subagents are running, a Claude Code-style navigable list renders **below** the editor:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
esc to interrupt · ← for agents · ↓ to manage
|
|
121
|
+
|
|
122
|
+
● main
|
|
123
|
+
○ general-purpose Sleep then report 1 11s · ↓ 13.1k tokens
|
|
124
|
+
○ general-purpose Sleep then report 2 11s · ↓ 13.1k tokens
|
|
125
|
+
↓ 3 more
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The list is ordered earliest-launched first, and only shows agents you can actually open (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press `↓` (or `←`) to move focus from the prompt into the list — the selected row is marked `●`, the rest `○`. `↑`/`↓` move the selection, `Enter` opens the selected agent's live conversation overlay (it auto-updates as the agent works), and `Esc` (or `↑` above `main`) returns to the prompt. Selecting `main` returns to the normal view. Inside the overlay, press `Enter` to steer the running agent — type a message and `Enter` to send it (`Esc` or an empty submit returns), and it redirects the agent the same way the `steer_subagent` tool does. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures arrow keys when the prompt is empty. Disable it entirely via `/agents → Settings → Fleet view`.
|
|
129
|
+
|
|
130
|
+
Individual agent results render Claude Code-style in the conversation:
|
|
131
|
+
|
|
132
|
+
| State | Example |
|
|
133
|
+
|-------|---------|
|
|
134
|
+
| **Running** | `⠹ ↻3≤30 · 3 tool uses · 12.4k token (8%)` / `⎿ searching, reading 3 files…` |
|
|
135
|
+
| **Completed** | `✓ ↻8 · 5 tool uses · 33.8k token (62%) · 12.3s` / `⎿ Done` |
|
|
136
|
+
| **Wrapped up** | `✓ ↻50≤50 · 50 tool uses · 89.1k token (84% · ⇊2) · 45.2s` / `⎿ Wrapped up (turn limit)` |
|
|
137
|
+
| **Stopped** | `■ ↻3 · 3 tool uses · 12.4k token (8%)` / `⎿ Stopped` |
|
|
138
|
+
| **Error** | `✗ ↻3 · 3 tool uses · 12.4k token (8%)` / `⎿ Error: timeout` |
|
|
139
|
+
| **Aborted** | `✗ ↻55≤50 · 55 tool uses · 102.3k token (95% · ⇊3)` / `⎿ Aborted (max turns exceeded)` |
|
|
140
|
+
|
|
141
|
+
Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
|
|
142
|
+
|
|
143
|
+
By default, foreground and background agents each stream their full conversation to a per-subagent transcript — a JSON-lines file at `<os-tmpdir>/pi-subagents-<uid>/<cwd>/<session>/tasks/<agent-id>.output` (owner-only `0700`, cleared on reboot). Set `output_transcript: false` on a custom agent to write no transcript path or file for it, or set `outputTranscript: false` in `subagents.json` to make transcripts opt-in for the whole project (frontmatter overrides the project default). This governs **only** the transcript: it is independent of `persist_session` (the pi session on disk), and it does not affect `isolation: worktree` (which commits the agent's work to a git branch) or `memory:` (durable files) — set those accordingly if the goal is to keep a run off disk entirely. Background agent completion notifications render as styled boxes:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
✓ Find auth files completed
|
|
147
|
+
↻3 · 3 tool uses · 12.4k token · 4.1s
|
|
148
|
+
⎿ Found 5 files related to authentication...
|
|
149
|
+
transcript: .pi/output/agent-abc123.jsonl
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Group completions render each agent as a separate block. The LLM receives structured `<task-notification>` XML for parsing, while the user sees the themed visual.
|
|
153
|
+
|
|
154
|
+
## Default Agent Types
|
|
155
|
+
|
|
156
|
+
| Type | Tools | Model | Prompt Mode | Description |
|
|
157
|
+
|------|-------|-------|-------------|-------------|
|
|
158
|
+
| `general-purpose` | all 7 | inherit | `append` (parent twin) | Inherits the parent's full system prompt — same rules, CLAUDE.md, project conventions |
|
|
159
|
+
| `Explore` | read, bash, grep, find, ls | haiku (falls back to inherit) | `replace` (standalone) | Fast codebase exploration (read-only) |
|
|
160
|
+
| `Plan` | read, bash, grep, find, ls | inherit | `replace` (standalone) | Software architect for implementation planning (read-only) |
|
|
161
|
+
|
|
162
|
+
The `general-purpose` agent is a **parent twin** — it receives the parent's entire system prompt plus a sub-agent context bridge, so it follows the same rules the parent does. Explore and Plan use standalone prompts tailored to their read-only roles.
|
|
163
|
+
|
|
164
|
+
Default agents can be **ejected** (`/agents` → select agent → Eject) to export them as `.md` files for customization, **overridden** by creating a `.md` file with the same name (e.g. `.pi/agents/general-purpose.md`), or **disabled** per-project with `enabled: false` frontmatter.
|
|
165
|
+
|
|
166
|
+
## Custom Agents
|
|
167
|
+
|
|
168
|
+
Define custom agent types by creating `.md` files. The filename becomes the agent type name. Any name is allowed — using a default agent's name overrides it.
|
|
169
|
+
|
|
170
|
+
Agents are discovered from three locations (higher priority wins):
|
|
171
|
+
|
|
172
|
+
| Priority | Location | Scope |
|
|
173
|
+
|----------|----------|-------|
|
|
174
|
+
| 1 (highest) | `.pi/agents/<name>.md` | Project — pi's config dir; authoritative, and where `/agents` writes |
|
|
175
|
+
| 2 | `.agents/agents/<name>.md` | Project — the shared cross-tool `.agents` workspace (same convention as `.agents/skills/`) |
|
|
176
|
+
| 3 | `$PI_CODING_AGENT_DIR/agents/<name>.md` (default `~/.pi/agent/agents/<name>.md`) | Global — available everywhere |
|
|
177
|
+
|
|
178
|
+
Project-level agents override global ones with the same name, so you can customize a global agent for a specific project. If both project locations define the same name, **`.pi/agents/` wins** — `.pi` stays the project authority; `.agents/agents/` is an additional read location for projects that keep their agent assets in the `.agents` workspace. The global location follows the upstream `PI_CODING_AGENT_DIR` env var — set it to relocate all pi-coding-agent state (agents, skills, settings) to a custom directory.
|
|
179
|
+
|
|
180
|
+
### Example: `.pi/agents/auditor.md`
|
|
181
|
+
|
|
182
|
+
```markdown
|
|
183
|
+
---
|
|
184
|
+
description: Security Code Reviewer
|
|
185
|
+
tools: read, grep, find, bash
|
|
186
|
+
model: anthropic/claude-opus-4-6
|
|
187
|
+
thinking: high
|
|
188
|
+
max_turns: 30
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
You are a security auditor. Review code for vulnerabilities including:
|
|
192
|
+
- Injection flaws (SQL, command, XSS)
|
|
193
|
+
- Authentication and authorization issues
|
|
194
|
+
- Sensitive data exposure
|
|
195
|
+
- Insecure configurations
|
|
196
|
+
|
|
197
|
+
Report findings with file paths, line numbers, severity, and remediation advice.
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Then spawn it like any built-in type:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
Agent({ subagent_type: "auditor", prompt: "Review the auth module", description: "Security audit" })
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Frontmatter Fields
|
|
207
|
+
|
|
208
|
+
All fields are optional — sensible defaults for everything.
|
|
209
|
+
|
|
210
|
+
| Field | Default | Description |
|
|
211
|
+
|-------|---------|-------------|
|
|
212
|
+
| `description` | filename | Agent description shown in tool listings |
|
|
213
|
+
| `display_name` | — | Display name for UI (e.g. widget, agent list) |
|
|
214
|
+
| `tools` | all 7 | Which tools the agent can call. Built-in names (`read, grep, …`), `*` / `all` (all built-ins), `none`, and `ext:<extension>` / `ext:<extension>/<tool>` selectors for extension tools. See [Tool & extension scoping](#tool--extension-scoping) below |
|
|
215
|
+
| `extensions` | `true` | Which extensions to load for the agent. `true` (all defaults), `false` (none), or an explicit list: `[mcp, "/abs/path.ts", "*"]`. See [Tool & extension scoping](#tool--extension-scoping) below |
|
|
216
|
+
| `exclude_extensions` | — | Extension denylist applied after `extensions:` — exclude wins. Plain names only (case-insensitive), no paths or `*`. Useful with `extensions: true` to drop one extension (e.g. `pi-notify`) |
|
|
217
|
+
| `skills` | `true` | Inherit skills from parent. Can be a comma-separated list of skill names to preload (see [Skill Preloading](#skill-preloading) for discovery locations) |
|
|
218
|
+
| `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
|
|
219
|
+
| `disallowed_tools` | — | Comma-separated tools to deny even if extensions provide them |
|
|
220
|
+
| `isolation` | — | Set to `worktree` to run in an isolated git worktree |
|
|
221
|
+
| `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp are interchangeable) and falls back to the same model under another provider if the named one doesn't have it |
|
|
222
|
+
| `thinking` | inherit | off, minimal, low, medium, high, xhigh, max — actual availability depends on your pi version and model; pi clamps unsupported levels down |
|
|
223
|
+
| `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
|
|
224
|
+
| `persist_session` | `false` | Persist this subagent as a normal pi session instead of keeping the session in memory only. The subagent's `.output` transcript is still written either way unless `output_transcript: false` |
|
|
225
|
+
| `output_transcript` | `true` (or `subagents.json` `outputTranscript`) | Write this subagent's `.output` transcript; when set, overrides the `subagents.json` `outputTranscript` default. Set `false` to write no transcript file or path. Governs only the transcript — independent of `persist_session`, `isolation: worktree`, and `memory:` |
|
|
226
|
+
| `session_dir` | pi default | Optional session directory when `persist_session: true`; omitted uses pi's normal session location, and relative paths resolve from the agent cwd |
|
|
227
|
+
| `prompt_mode` | `replace` | `replace`: body is the full system prompt (no AGENTS.md / CLAUDE.md inheritance). `append`: body appended to parent's prompt (agent acts as a "parent twin" — inherits parent's AGENTS.md / CLAUDE.md) |
|
|
228
|
+
| `inherit_context` | `false` | Fork parent conversation into agent |
|
|
229
|
+
| `run_in_background` | `false` | Run in background by default |
|
|
230
|
+
| `isolated` | `false` | Hermetic specialist mode: forces `extensions: false` + `skills: false` + drops `ext:` selectors. Only built-in tools. Distinct from `isolation: worktree` (filesystem) |
|
|
231
|
+
| `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
|
|
232
|
+
|
|
233
|
+
Frontmatter is authoritative. If an agent file sets `model`, `thinking`, `max_turns`, `inherit_context`, `run_in_background`, `isolated`, or `isolation`, those values are locked for that agent. `Agent` tool parameters only fill fields the agent config leaves unspecified.
|
|
234
|
+
|
|
235
|
+
**Forgiving `model:` resolution.** A `model:` pin is matched against pi's model registry tolerantly, so cosmetic id variations don't silently drop the agent back to the parent's model: `.` and `-` are treated as equivalent in version numbers (`claude-haiku-4.5` ≡ `claude-haiku-4-5`), a trailing `-YYYYMMDD` date stamp is optional (`anthropic/claude-haiku-4-5-20251001` matches an undated registry id and vice-versa), and a `provider/modelId` whose named provider doesn't carry that model retries the bare id against every provider. Precedence is **exact → fuzzy under the named provider → same model under any provider → unavailable**, so an exact match always wins and dated snapshots aren't conflated. If nothing resolves, the pin can't run and the agent inherits the parent model — `/agents → Agent types` flags this case as `(unavailable, fallback: inherit)` and shows the resolved target `(→ provider/id)` when resolution lands on a different provider or version than configured. (This is distinct from [Model Scope](#model-scope) enforcement, which matches the `enabledModels` allowlist by *exact* entry.)
|
|
236
|
+
|
|
237
|
+
### Tool & extension scoping
|
|
238
|
+
|
|
239
|
+
`extensions:` decides **which extensions load**, `tools:` decides **which tools surface to the LLM**. They compose:
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
# Default (both omitted): all extensions load, all 7 built-ins surface
|
|
243
|
+
|
|
244
|
+
tools: read, grep, find # narrow to listed built-ins; extensions still load
|
|
245
|
+
tools: "*" # all 7 built-ins (alias: `all`)
|
|
246
|
+
tools: none # zero built-ins (alias: `""`)
|
|
247
|
+
tools: "*, ext:mcp/search" # built-ins plus one extension tool
|
|
248
|
+
|
|
249
|
+
extensions: false # no extensions load
|
|
250
|
+
extensions: [mcp] # only mcp loads
|
|
251
|
+
extensions: ["*", "/abs/foo.ts"] # all defaults plus one path-loaded extension
|
|
252
|
+
|
|
253
|
+
exclude_extensions: pi-notify # everything except pi-notify (with extensions: true)
|
|
254
|
+
|
|
255
|
+
# Specialist: load one extension, expose only one of its tools, keep built-ins
|
|
256
|
+
extensions: [mcp]
|
|
257
|
+
tools: "*, ext:mcp/search"
|
|
258
|
+
|
|
259
|
+
isolated: true # hermetic: built-ins only, no extensions/skills/context
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
A few rules the examples don't make obvious:
|
|
263
|
+
|
|
264
|
+
- `extensions:` is the sole loading authority. `ext:foo` in `tools:` narrows what surfaces; it can't load `foo` on its own. Mismatches fire `extension-error:…` warnings.
|
|
265
|
+
- Any `ext:` entry flips extension tools to an explicit allowlist — unnamed extensions still load (handlers fire) but expose no tools. So `tools: "*, ext:mcp/search"` exposes only `search` from `mcp`, nothing from any other extension.
|
|
266
|
+
- Extension names match case-insensitively (`[Mcp]` = `[mcp]`); tool names in `ext:foo/bar` stay case-sensitive.
|
|
267
|
+
- Extensions that register tools **lazily** work too. MCP-backed extensions typically can't enumerate their tools until their servers connect, so they register from `session_start` or `before_agent_start` rather than at load. Subagent scoping is re-derived as tools appear, so these surface normally — including under `ext:` selectors, which keep narrowing correctly no matter when a tool shows up.
|
|
268
|
+
- An installed **package** extension matches by its package short name (`@scope/pi-subagents` → `[pi-subagents]`), in addition to its path-derived name (a package whose entry is `src/index.ts` also answers to `[src]`). Prefer the package name — the path-derived one is incidental.
|
|
269
|
+
- Plain `tools:` typos fail loudly: `tools: reed, grep` fires `tools-error:…` instead of silently producing an under-tooled agent.
|
|
270
|
+
- `exclude_extensions:` wins over `extensions:` and over `ext:` selectors — an excluded extension never loads and a `tools: ext:` entry can't pull it back. Plain names only (no paths, no `*`); a name matching nothing fires an `extension-error:…` warning.
|
|
271
|
+
- `exclude_extensions:` is **not a sandbox**: excluded extensions' factory code still executes once during loading. Exclusion suppresses their tools and their bound lifecycle hooks (`pi.on` handlers like `session_start` only fire for extensions bound to the session), but not other load-time side effects — a factory that subscribes directly to the shared `pi.events` bus stays live. Don't rely on it to contain an untrusted extension.
|
|
272
|
+
- Array and string forms are equivalent: `[a, b]` == `"a, b"`.
|
|
273
|
+
|
|
274
|
+
## `npm:pi-subagents`-Style JSON Agent Overrides
|
|
275
|
+
|
|
276
|
+
This fork adds the ability to configure — and even create — agents entirely through JSON,
|
|
277
|
+
no `.md` files required. The syntax matches [`npm:pi-subagents`](https://github.com/nicobailon/pi-subagents)
|
|
278
|
+
(also known as oh-my-opencode-agent / OMO style): put a `subagents.agentOverrides` block in
|
|
279
|
+
`settings.json` and it takes the highest priority.
|
|
280
|
+
|
|
281
|
+
### Priority Chain
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
Local JSON (.pi/settings.json → subagents.agentOverrides) ← highest
|
|
285
|
+
Global JSON (~/.pi/agent/settings.json → subagents.agentOverrides)
|
|
286
|
+
───── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
287
|
+
Local .md (.pi/agents/*.md)
|
|
288
|
+
Global .md (~/.pi/agent/agents/*.md)
|
|
289
|
+
Built-in (general-purpose, Explore, Plan) ← lowest
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
JSON overrides win over everything below the line. If an agent name in the JSON doesn't
|
|
293
|
+
match any existing `.md` or built-in agent, **it is auto-registered** with the fields you
|
|
294
|
+
provide.
|
|
295
|
+
|
|
296
|
+
### Global Override Example
|
|
297
|
+
|
|
298
|
+
Put this in `~/.pi/agent/settings.json` to set project-wide defaults for all your agents:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"subagents": {
|
|
303
|
+
"defaultModel": "opencode/deepseek-v4-flash-free",
|
|
304
|
+
"agentOverrides": {
|
|
305
|
+
"Explore": {
|
|
306
|
+
"model": "github-copilot/gpt-5-mini",
|
|
307
|
+
"thinking": "off"
|
|
308
|
+
},
|
|
309
|
+
"researcher": {
|
|
310
|
+
"model": "opencode/deepseek-v4-flash-free",
|
|
311
|
+
"thinking": "max",
|
|
312
|
+
"systemPrompt": "You are an autonomous web researcher. Search for external evidence, official docs, ecosystem behavior, recent changes, benchmarks, and primary sources. Return a focused research brief with source links, confidence level, gaps, and decision implications. Do not edit project files."
|
|
313
|
+
},
|
|
314
|
+
"reviewer": {
|
|
315
|
+
"model": "opencode/deepseek-v4-flash-free",
|
|
316
|
+
"thinking": "max",
|
|
317
|
+
"tools": ["read", "grep", "find", "ls", "bash"]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Local Project Override Example
|
|
325
|
+
|
|
326
|
+
Put this in `.pi/settings.json` within a specific project. These values **merge over** the
|
|
327
|
+
global ones, so you can override only the fields you need to change for this project:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"subagents": {
|
|
332
|
+
"agentOverrides": {
|
|
333
|
+
"researcher": {
|
|
334
|
+
"model": "openai-codex/gpt-5.4",
|
|
335
|
+
"thinking": "high"
|
|
336
|
+
},
|
|
337
|
+
"fixer": {
|
|
338
|
+
"model": "openai-codex/gpt-5.4",
|
|
339
|
+
"thinking": "high",
|
|
340
|
+
"tools": ["read", "grep", "find", "ls", "bash", "edit", "write"],
|
|
341
|
+
"systemPrompt": "You are Fixer — a fast, focused implementation specialist. Execute code changes efficiently with complete context. NO external research, NO delegation, NO planning. Implement and report."
|
|
342
|
+
},
|
|
343
|
+
"explorer": {
|
|
344
|
+
"model": "openai-codex/gpt-5.4",
|
|
345
|
+
"thinking": "high",
|
|
346
|
+
"tools": ["read", "grep", "find", "ls", "bash"]
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
In this example, `researcher` gets `openai-codex/gpt-5.4` from the local override (instead of
|
|
354
|
+
the global `opencode/deepseek-v4-flash-free`). `fixer` and `explorer` don't exist in global
|
|
355
|
+
overrides and have no `.md` files — they are **auto-registered** from this JSON alone.
|
|
356
|
+
|
|
357
|
+
### Supported Override Fields
|
|
358
|
+
|
|
359
|
+
| Field | Type | Description |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| `model` | `string \| false` | Model to use; `false` clears override and inherits parent |
|
|
362
|
+
| `thinking` | `string \| false` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` |
|
|
363
|
+
| `systemPrompt` | `string` | Full system prompt (replaces agent's default) |
|
|
364
|
+
| `tools` | `string[]` | Tool allowlist — agent gets exactly these tools and no others |
|
|
365
|
+
| `disabled` | `boolean` | `true` hides the agent from the type list |
|
|
366
|
+
| `skills` | `string[]` | Skill list. `["*"]` = all skills, `[]` = none, `["simplify"]` = only named |
|
|
367
|
+
|
|
368
|
+
### How Skills Are Mapped
|
|
369
|
+
|
|
370
|
+
| Nico JSON (`string[]`) | Resulting tintinweb value |
|
|
371
|
+
|---|---|
|
|
372
|
+
| `["*"]` | `true` — all skills inherited |
|
|
373
|
+
| `["simplify", "librarian"]` | `["simplify", "librarian"]` — only listed |
|
|
374
|
+
| `[]` or `false` | `false` — no skills |
|
|
375
|
+
| omitted | `true` — all skills (default) |
|
|
376
|
+
|
|
377
|
+
## Tools
|
|
378
|
+
|
|
379
|
+
### `Agent
|
|
380
|
+
|
|
381
|
+
Launch a sub-agent.
|
|
382
|
+
|
|
383
|
+
| Parameter | Type | Required | Description |
|
|
384
|
+
|-----------|------|----------|-------------|
|
|
385
|
+
| `prompt` | string | yes | The task for the agent |
|
|
386
|
+
| `description` | string | yes | Short 3-5 word summary (shown in UI) |
|
|
387
|
+
| `subagent_type` | string | yes | Agent type (built-in or custom) |
|
|
388
|
+
| `model` | string | no | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp interchangeable) with provider fallback |
|
|
389
|
+
| `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh, max (availability depends on pi version and model) |
|
|
390
|
+
| `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
|
|
391
|
+
| `run_in_background` | boolean | no | Run without blocking |
|
|
392
|
+
| `resume` | string | no | Agent ID to resume a previous session |
|
|
393
|
+
| `isolated` | boolean | no | No extension/MCP tools |
|
|
394
|
+
| `isolation` | `"worktree"` | no | Run in an isolated git worktree |
|
|
395
|
+
| `inherit_context` | boolean | no | Fork parent conversation into agent |
|
|
396
|
+
|
|
397
|
+
### `get_subagent_result`
|
|
398
|
+
|
|
399
|
+
Check status and retrieve results from a background agent.
|
|
400
|
+
|
|
401
|
+
| Parameter | Type | Required | Description |
|
|
402
|
+
|-----------|------|----------|-------------|
|
|
403
|
+
| `agent_id` | string | yes | Agent ID to check |
|
|
404
|
+
| `wait` | boolean | no | Wait for completion |
|
|
405
|
+
| `verbose` | boolean | no | Include full conversation log |
|
|
406
|
+
|
|
407
|
+
Cancelling a `wait: true` call (for example, with `Esc`) stops only the wait. The background agent keeps running, and its completion notification still arrives normally.
|
|
408
|
+
|
|
409
|
+
### `steer_subagent`
|
|
410
|
+
|
|
411
|
+
Send a steering message to a running agent. The message interrupts after the current tool execution.
|
|
412
|
+
|
|
413
|
+
| Parameter | Type | Required | Description |
|
|
414
|
+
|-----------|------|----------|-------------|
|
|
415
|
+
| `agent_id` | string | yes | Agent ID to steer |
|
|
416
|
+
| `message` | string | yes | Message to inject into agent conversation |
|
|
417
|
+
|
|
418
|
+
## Commands
|
|
419
|
+
|
|
420
|
+
| Command | Description |
|
|
421
|
+
|---------|-------------|
|
|
422
|
+
| `/agents` | Interactive agent management menu |
|
|
423
|
+
|
|
424
|
+
The `/agents` command opens an interactive menu:
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
Running agents (2) — 1 running, 1 done ← only shown when agents exist
|
|
428
|
+
Agent types (6) ← unified list: defaults + custom
|
|
429
|
+
Create new agent ← manual wizard or AI-generated
|
|
430
|
+
Settings ← max concurrency, max turns, grace turns, join mode
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- **Running agents** — select one to open its live conversation viewer. While it's still running, press `Enter` to open the steering composer, then `Enter` again to send a message that redirects the agent (same mechanism as the `steer_subagent` tool; `Esc` or an empty submit returns), or press `x` (then `x` again to confirm) to stop/abort it — including **background** agents, which a global Esc can't unambiguously target (Esc still stops a blocking foreground `Agent` call). A stopped agent reports its partial output flagged as incomplete, not as a completion.
|
|
434
|
+
- **Agent types** — unified list with source indicators: `•` (project), `◦` (global), `✕` (disabled). Each row shows the agent's model, and the highlighted agent's full description appears below the list. The model column flags `(unavailable, fallback: inherit)` when a configured model can't be resolved (it would silently inherit the parent model), and shows `(→ provider/id)` when it resolves to a different provider or version than configured. Select an agent to manage it:
|
|
435
|
+
- **Default agents** (no override): Eject (export as `.md`), Disable
|
|
436
|
+
- **Default agents** (ejected/overridden): Edit, Disable, Reset to default, Delete
|
|
437
|
+
- **Custom agents**: Edit, Disable, Delete
|
|
438
|
+
- **Disabled agents**: Enable, Edit, Delete
|
|
439
|
+
- **Eject** — writes the embedded default config as a `.md` file to project or personal location, so you can customize it
|
|
440
|
+
- **Disable/Enable** — toggle agent availability. Disabled agents stay visible in the list (marked `✕`) and can be re-enabled
|
|
441
|
+
- **Create new agent** — choose project/personal location, then manual wizard (step-by-step prompts for name, tools, model, thinking, system prompt) or AI-generated (describe what the agent should do and a sub-agent writes the `.md` file). Any name is allowed, including default agent names (overrides them)
|
|
442
|
+
- **Settings** — configure max concurrency, default max turns, grace turns, and join mode at runtime
|
|
443
|
+
|
|
444
|
+
## Graceful Max Turns
|
|
445
|
+
|
|
446
|
+
Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
|
|
447
|
+
|
|
448
|
+
1. At `max_turns` — steering message: *"Wrap up immediately — provide your final answer now."*
|
|
449
|
+
2. Up to 5 grace turns to finish cleanly
|
|
450
|
+
3. Hard abort only after the grace period
|
|
451
|
+
|
|
452
|
+
| Status | Meaning | Icon |
|
|
453
|
+
|--------|---------|------|
|
|
454
|
+
| `completed` | Finished naturally | `✓` green |
|
|
455
|
+
| `steered` | Hit limit, wrapped up in time | `✓` yellow |
|
|
456
|
+
| `aborted` | Grace period exceeded | `✗` red |
|
|
457
|
+
| `stopped` | User-initiated abort | `■` dim |
|
|
458
|
+
|
|
459
|
+
## Concurrency
|
|
460
|
+
|
|
461
|
+
Background agents are subject to a configurable concurrency limit (default: 4). Excess agents are automatically queued and start as running agents complete. The widget shows queued agents as a collapsed count.
|
|
462
|
+
|
|
463
|
+
Foreground agents bypass the queue — they block the parent anyway.
|
|
464
|
+
|
|
465
|
+
## Join Strategies
|
|
466
|
+
|
|
467
|
+
When background agents complete, they notify the main agent. The **join mode** controls how these notifications are delivered. It applies only to background agents.
|
|
468
|
+
|
|
469
|
+
| Mode | Behavior |
|
|
470
|
+
|------|----------|
|
|
471
|
+
| `smart` (default) | 2+ background agents spawned in the same turn are auto-grouped into a single consolidated notification. Solo agents notify individually. |
|
|
472
|
+
| `async` | Each agent sends its own notification on completion (original behavior). Best when results need incremental processing. |
|
|
473
|
+
| `group` | Force grouping even when spawning a single agent. Useful when you know more agents will follow. |
|
|
474
|
+
|
|
475
|
+
**Timeout behavior:** When agents are grouped, a 30-second timeout starts after the first agent completes. If not all agents finish in time, a partial notification is sent with completed results and remaining agents continue with a shorter 15-second re-batch window for stragglers.
|
|
476
|
+
|
|
477
|
+
**Configuration:**
|
|
478
|
+
- Configure join mode in `/agents` → Settings → Join mode
|
|
479
|
+
|
|
480
|
+
## Model Scope
|
|
481
|
+
|
|
482
|
+
**Opt-in:** off by default. Enable via `/agents → Settings → Scope models`.
|
|
483
|
+
|
|
484
|
+
When on, each subagent spawn's effective model is validated against pi's own `enabledModels` list (configured via pi's `/scoped-models` UI). pi-subagents reads that list; it doesn't manage it. Both of pi's settings files are honored: global `~/.pi/agent/settings.json` and project-local `<cwd>/.pi/settings.json`. **Project overrides global** — mirrors pi's `SettingsManager` deep-merge, so a tighter per-project scope (hand-edited into the project settings) is respected.
|
|
485
|
+
|
|
486
|
+
**Out-of-scope handling depends on source:**
|
|
487
|
+
|
|
488
|
+
| Model source | Out-of-scope behavior |
|
|
489
|
+
|---|---|
|
|
490
|
+
| Caller-supplied via `Agent({ model: "..." })` | Hard error returned to the orchestrator, listing allowed models |
|
|
491
|
+
| Pinned in agent frontmatter | Warning toast + the pinned model runs (frontmatter is authoritative) |
|
|
492
|
+
| Parent-inherited (neither set) | Warning toast + parent's model runs |
|
|
493
|
+
|
|
494
|
+
**Design:** `scopeModels` is a guardrail against the orchestrator picking unexpected models at runtime, not a hard policy against user-level config. The "frontmatter is authoritative" guarantee from v0.5.1 still holds for `model:` — caller params can't override frontmatter, and frontmatter pins run even when out of scope (with a visible warning).
|
|
495
|
+
|
|
496
|
+
**Pattern format:** only exact `provider/modelId` entries are honored (e.g. `anthropic/claude-haiku-4-5-20251001`). Glob patterns (`*sonnet*`), bare model IDs, and `:thinking` suffixes — which pi itself supports — are silently dropped here. pi's `/scoped-models` picker writes exact entries, so the limitation is invisible if you configure scope through the UI. Hand-edited globs produce an empty allowed set (scope check becomes a no-op).
|
|
497
|
+
|
|
498
|
+
**No-op safety:** if `enabledModels` is missing or empty in pi's settings, scope check skips entirely — no false positives, no spurious errors.
|
|
499
|
+
|
|
500
|
+
## Persistent Settings
|
|
501
|
+
|
|
502
|
+
Runtime tuning values set via `/agents` → Settings (max concurrency, default max turns, grace turns, default join mode, scheduling on/off, scope models on/off, disable defaults on/off, output transcript on/off, tool description full/compact/custom, widget all/background/off) persist across pi restarts. Two files, merged on load:
|
|
503
|
+
|
|
504
|
+
- **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults. Edit by hand; the `/agents` menu never writes here.
|
|
505
|
+
- **Project:** `<cwd>/.pi/subagents.json` — per-project overrides. Written by `/agents` → Settings.
|
|
506
|
+
|
|
507
|
+
**Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `4`, default max turns unlimited, grace turns `5`, join mode `smart`, defaults enabled).
|
|
508
|
+
|
|
509
|
+
**Disable defaults** (`disableDefaultAgents`, default `false`): when on, the three built-in agents (general-purpose, Explore, Plan) are not registered — only your project/global custom agents are advertised and spawnable. User-defined agents are unaffected, including ones that override a default by name. The Agent tool's type list updates on the next pi session (the tool schema is registered at startup).
|
|
510
|
+
|
|
511
|
+
**Output transcript** (`outputTranscript`, default `true`): the project/global default for writing each subagent's `.output` transcript. Toggle via `/agents → Settings → Output transcript`, or set `false` in `subagents.json` to make transcripts opt-in project-wide — useful when run transcripts shouldn't sit on disk for backup or DLP tooling to pick up. A custom agent's `output_transcript` frontmatter overrides this per agent. Applied live at spawn time. Governs only the transcript, not `persist_session`, worktree commits, or memory files.
|
|
512
|
+
|
|
513
|
+
**Tool description** (`toolDescriptionMode`, default `"full"`): which Agent tool description the LLM sees. `"full"` is the rich Claude Code-style prompt (~1,400 tokens with the default agents); `"compact"` is ~75% smaller — one-line agent type list, terse usage notes — for small/local models where tool-spec tokens are expensive. Per-option details stay in the parameter descriptions in every mode (the parameter schema is never customizable). Applies on the next pi session.
|
|
514
|
+
|
|
515
|
+
`"custom"` registers your own description from `<cwd>/.pi/agent-tool-description.md` (project) or `<agentDir>/agent-tool-description.md` (global; project wins). The file is read once at tool registration, so edits also apply on the next pi session. Dynamic parts stay live via placeholders — a static agent list would go stale the moment you add a custom agent:
|
|
516
|
+
|
|
517
|
+
```markdown
|
|
518
|
+
Launch an autonomous agent. Available types:
|
|
519
|
+
{{typeList}}
|
|
520
|
+
|
|
521
|
+
Custom agents live in .pi/agents/ or {{agentDir}}/agents/.
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Placeholders: `{{typeList}}` (full per-agent descriptions), `{{compactTypeList}}` (first sentence each), `{{agentDir}}`, `{{scheduleGuideline}}` (expands with its own leading newline + `- ` bullet when scheduling is on — place it directly after your last rule line; empty when scheduling is off). Unknown placeholders are left verbatim with a stderr warning; a missing or empty file falls back to `"full"` with a warning. Note the usual trust umbrella: a project-level file shapes the orchestrator's prompt, same as project agents and extensions do.
|
|
525
|
+
|
|
526
|
+
**Starting point:** copy [`examples/agent-tool-description.md`](examples/agent-tool-description.md) — it reproduces the default full description exactly (a CI test keeps it in sync), so you can trim from a known-good baseline instead of writing from scratch.
|
|
527
|
+
|
|
528
|
+
**Example — global defaults for a beefy machine:**
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
mkdir -p ~/.pi/agent
|
|
532
|
+
cat > ~/.pi/agent/subagents.json <<'EOF'
|
|
533
|
+
{
|
|
534
|
+
"maxConcurrent": 16,
|
|
535
|
+
"graceTurns": 10
|
|
536
|
+
}
|
|
537
|
+
EOF
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Every project now starts with concurrency 16 and grace 10, without ever touching the menu. Individual projects can still override via `/agents` → Settings.
|
|
541
|
+
|
|
542
|
+
**Failure behavior:** missing file is silent; malformed JSON logs a `[pi-subagents] Ignoring malformed settings at …` warning to stderr; invalid/out-of-range field values are dropped per-field; write failures downgrade the `/agents` toast to a warning with `(session only; failed to persist)`.
|
|
543
|
+
|
|
544
|
+
## Events
|
|
545
|
+
|
|
546
|
+
Agent lifecycle events are emitted via `pi.events.emit()` so other extensions can react:
|
|
547
|
+
|
|
548
|
+
| Event | When | Key fields |
|
|
549
|
+
|-------|------|------------|
|
|
550
|
+
| `subagents:created` | Background agent registered | `id`, `type`, `description`, `isBackground` |
|
|
551
|
+
| `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description` |
|
|
552
|
+
| `subagents:completed` | Agent finished successfully (background and foreground) | `id`, `type`, `durationMs`, `tokens` (lifetime `{ input, output, total }`), `toolUses`, `result` |
|
|
553
|
+
| `subagents:failed` | Agent errored, stopped, or aborted (background and foreground) | same as completed + `error`, `status` |
|
|
554
|
+
| `subagents:steered` | Steering message sent | `id`, `message` |
|
|
555
|
+
| `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount` |
|
|
556
|
+
| `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
|
|
557
|
+
| `subagents:scheduler_ready` | Scheduler bound to session, enabled jobs armed | `sessionId`, `jobCount` |
|
|
558
|
+
| `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | — |
|
|
559
|
+
| `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
|
|
560
|
+
| `subagents:settings_changed` | `/agents` → Settings mutation was applied | `settings`, `persisted` (`boolean` — `false` on write failure) |
|
|
561
|
+
|
|
562
|
+
`tokens.total` = `input + output + cacheWrite`. `cacheRead` is excluded — each turn's `cacheRead` is the cumulative cached prefix re-read on that one API call, so summing per-message would over-count it. Use `contextUsage.percent` (surfaced as `(NN%)` in the widget) for current context size.
|
|
563
|
+
|
|
564
|
+
## Cross-Extension RPC
|
|
565
|
+
|
|
566
|
+
Other pi extensions can spawn and stop subagents programmatically via the `pi.events` event bus, without importing this package directly.
|
|
567
|
+
|
|
568
|
+
All RPC replies use a standardized envelope: `{ success: true, data?: T }` on success, `{ success: false, error: string }` on failure.
|
|
569
|
+
|
|
570
|
+
### Discovery
|
|
571
|
+
|
|
572
|
+
Listen for `subagents:ready` to know when RPC handlers are available:
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
pi.events.on("subagents:ready", () => {
|
|
576
|
+
// RPC handlers are registered — safe to call ping/spawn/stop
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
`subagents:ready` fires only when pi-subagents is actually loaded **and bound** in the current session. A session that excludes it (via an agent's `extensions:`) emits no `subagents:ready` and does not answer the RPC channels — exactly as if pi-subagents were not installed. Treat "no `subagents:ready`" as "not available here" and give discovery a timeout rather than waiting indefinitely.
|
|
581
|
+
|
|
582
|
+
### Ping
|
|
583
|
+
|
|
584
|
+
Check if the subagents extension is loaded and get the protocol version:
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
const requestId = crypto.randomUUID();
|
|
588
|
+
const unsub = pi.events.on(`subagents:rpc:ping:reply:${requestId}`, (reply) => {
|
|
589
|
+
unsub();
|
|
590
|
+
if (reply.success) console.log("Protocol version:", reply.data.version);
|
|
591
|
+
});
|
|
592
|
+
pi.events.emit("subagents:rpc:ping", { requestId });
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Spawn
|
|
596
|
+
|
|
597
|
+
Spawn a subagent and receive its ID:
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
const requestId = crypto.randomUUID();
|
|
601
|
+
const unsub = pi.events.on(`subagents:rpc:spawn:reply:${requestId}`, (reply) => {
|
|
602
|
+
unsub();
|
|
603
|
+
if (!reply.success) {
|
|
604
|
+
console.error("Spawn failed:", reply.error);
|
|
605
|
+
} else {
|
|
606
|
+
console.log("Agent ID:", reply.data.id);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
pi.events.emit("subagents:rpc:spawn", {
|
|
610
|
+
requestId,
|
|
611
|
+
type: "general-purpose",
|
|
612
|
+
prompt: "Do something useful",
|
|
613
|
+
options: { description: "My task", run_in_background: true },
|
|
614
|
+
});
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
`options.model` accepts either a `Model` object (e.g. `ctx.model`) or a `"provider/modelId"` string — strings are resolved against `ctx.modelRegistry` at the RPC boundary, so cross-extension callers can forward serializable values without losing auth context.
|
|
618
|
+
|
|
619
|
+
`options.cwd` (absolute path to an existing directory — anything else returns an error envelope; `null` means unset) runs the agent in a different working directory than the parent session. Its tools operate there and the prompt's environment block describes it, but **`.pi` config still loads from the parent session's project** — the target directory's `.pi` extensions never execute, and its agents/skills/settings are not picked up. Combined with `isolation: "worktree"`, the worktree is created *from* the target directory's repo, the agent works at the equivalent subdirectory inside the copy (a monorepo-package cwd stays scoped to that package), and the resulting `pi-agent-*` branch lands in that repo — the completion message names it. On session end, worktree registrations are pruned in every repo that received one; only a hard crash can leave a stale entry (then: `git worktree prune` in the target repo). Agents with `memory:` keep reading/writing the parent project's memory.
|
|
620
|
+
|
|
621
|
+
### Stop
|
|
622
|
+
|
|
623
|
+
Stop a running agent by ID:
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
const requestId = crypto.randomUUID();
|
|
627
|
+
const unsub = pi.events.on(`subagents:rpc:stop:reply:${requestId}`, (reply) => {
|
|
628
|
+
unsub();
|
|
629
|
+
if (!reply.success) console.error("Stop failed:", reply.error);
|
|
630
|
+
});
|
|
631
|
+
pi.events.emit("subagents:rpc:stop", { requestId, agentId: "agent-id-here" });
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Reply channels are scoped per `requestId`, so concurrent requests don't interfere.
|
|
635
|
+
|
|
636
|
+
## Persistent Agent Memory
|
|
637
|
+
|
|
638
|
+
Agents can have persistent memory across sessions. Set `memory` in frontmatter to enable:
|
|
639
|
+
|
|
640
|
+
```yaml
|
|
641
|
+
---
|
|
642
|
+
memory: project # project | local | user
|
|
643
|
+
---
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
| Scope | Location | Use case |
|
|
647
|
+
|-------|----------|----------|
|
|
648
|
+
| `project` | `.pi/agent-memory/<name>/` | Shared across the team (committed) |
|
|
649
|
+
| `local` | `.pi/agent-memory-local/<name>/` | Machine-specific (gitignored) |
|
|
650
|
+
| `user` | `<agentDir>/agent-memory/<name>/` (default `~/.pi/agent/agent-memory/`, honors `PI_CODING_AGENT_DIR`) | Global personal memory |
|
|
651
|
+
|
|
652
|
+
The `user` scope previously hardcoded `~/.pi/agent-memory/`. If that legacy directory exists for an agent and the new location doesn't, it keeps being used — existing memories aren't orphaned.
|
|
653
|
+
|
|
654
|
+
Memory uses a `MEMORY.md` index file and individual memory files with frontmatter. Agents with write tools get full read-write access. **Read-only agents** (no `write`/`edit` tools) automatically get read-only memory — they can consume memories written by other agents but cannot modify them. This prevents unintended tool escalation.
|
|
655
|
+
|
|
656
|
+
The `disallowed_tools` field is respected when determining write capability — an agent with `tools: write` + `disallowed_tools: write` correctly gets read-only memory.
|
|
657
|
+
|
|
658
|
+
## Worktree Isolation
|
|
659
|
+
|
|
660
|
+
Set `isolation: worktree` to run an agent in a temporary git worktree:
|
|
661
|
+
|
|
662
|
+
```
|
|
663
|
+
Agent({ subagent_type: "refactor", prompt: "...", isolation: "worktree" })
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
The agent gets a full, isolated copy of the repository. On completion:
|
|
667
|
+
- **No changes:** worktree is cleaned up automatically
|
|
668
|
+
- **Changes made:** changes are committed to a new branch (`pi-agent-<id>`) and returned in the result
|
|
669
|
+
- **Agent committed its own work:** the branch is created at the agent's HEAD, preserving its commits (uncommitted leftovers are committed on top first)
|
|
670
|
+
|
|
671
|
+
The automatic preservation commit uses `--no-verify`, so local pre-commit hooks can't block it — the commit is local-only and never pushed, and pre-push/server-side hooks still apply.
|
|
672
|
+
|
|
673
|
+
If the worktree cannot be created (not a git repo, no commits, or `git worktree add` fails), the `Agent` tool returns a clear error instead of running unisolated — `isolation: "worktree"` is a strict guarantee, not a hint. Initialize git and commit at least once, or omit `isolation`.
|
|
674
|
+
|
|
675
|
+
## Skill Preloading
|
|
676
|
+
|
|
677
|
+
Skills can be preloaded by name and injected into the agent's system prompt:
|
|
678
|
+
|
|
679
|
+
```yaml
|
|
680
|
+
---
|
|
681
|
+
skills: api-conventions, error-handling
|
|
682
|
+
---
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Discovery roots** (checked in this order, first match wins):
|
|
686
|
+
|
|
687
|
+
| Scope | Path | Source |
|
|
688
|
+
|---|---|---|
|
|
689
|
+
| Project | `<cwd>/.pi/skills/` | Pi-standard |
|
|
690
|
+
| Project | `<cwd>/.agents/skills/` | [Agent Skills spec](https://agentskills.io/integrate-skills) |
|
|
691
|
+
| User | `$PI_CODING_AGENT_DIR/skills/` (default `~/.pi/agent/skills/`) | Pi-standard |
|
|
692
|
+
| User | `~/.agents/skills/` | [Agent Skills spec](https://agentskills.io/integrate-skills) |
|
|
693
|
+
| User | `~/.pi/skills/` | Legacy (pre-Pi) |
|
|
694
|
+
|
|
695
|
+
**Per root, a skill named `foo` resolves to the first of:**
|
|
696
|
+
|
|
697
|
+
- `<root>/foo.md` — flat file at the top level
|
|
698
|
+
- `<root>/foo/SKILL.md` — directory skill (top-level)
|
|
699
|
+
- `<root>/*/.../foo/SKILL.md` — directory skill, found by recursive descent
|
|
700
|
+
|
|
701
|
+
Recursion skips dotfile directories and `node_modules`. A directory that itself contains a `SKILL.md` is treated as a single skill — we don't descend into it. Traversal is byte-order sorted for deterministic resolution across filesystems.
|
|
702
|
+
|
|
703
|
+
**Security:** symlinks are rejected at every layer (root, flat file, skill directory, `SKILL.md` inside a skill directory) — intentional deviation from Pi, which follows symlinks. Skill names with path-traversal characters (`..`, `/`, `\`, spaces, leading dot, >128 chars) are rejected.
|
|
704
|
+
|
|
705
|
+
## Tool Denylist
|
|
706
|
+
|
|
707
|
+
Block specific tools from an agent even if extensions provide them:
|
|
708
|
+
|
|
709
|
+
```yaml
|
|
710
|
+
---
|
|
711
|
+
tools: read, bash, grep, write
|
|
712
|
+
disallowed_tools: write, edit
|
|
713
|
+
---
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
This is useful for creating agents that inherit extension tools but should not have write access.
|
|
717
|
+
|
|
718
|
+
## Architecture
|
|
719
|
+
|
|
720
|
+
```
|
|
721
|
+
src/
|
|
722
|
+
index.ts # Extension entry: tool/command registration, rendering
|
|
723
|
+
types.ts # Type definitions (AgentConfig, AgentRecord, etc.)
|
|
724
|
+
default-agents.ts # Embedded default agent configs (general-purpose, Explore, Plan)
|
|
725
|
+
agent-types.ts # Unified agent registry (defaults + user), tool name resolution
|
|
726
|
+
agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
|
|
727
|
+
agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
|
|
728
|
+
cross-extension-rpc.ts # RPC handlers for cross-extension spawn/ping via pi.events
|
|
729
|
+
group-join.ts # Group join manager: batched completion notifications with timeout
|
|
730
|
+
custom-agents.ts # Load user-defined agents from .pi/agents/, .agents/agents/, and global agents
|
|
731
|
+
memory.ts # Persistent agent memory (resolve, read, build prompt blocks)
|
|
732
|
+
skill-loader.ts # Preload skills (Pi-standard + Agent Skills spec layouts)
|
|
733
|
+
output-file.ts # Streaming output file transcripts for agent sessions
|
|
734
|
+
worktree.ts # Git worktree isolation (create, cleanup, prune)
|
|
735
|
+
prompts.ts # Config-driven system prompt builder
|
|
736
|
+
context.ts # Parent conversation context for inherit_context
|
|
737
|
+
env.ts # Environment detection (git, platform)
|
|
738
|
+
ui/
|
|
739
|
+
agent-widget.ts # Persistent widget: spinners, activity, status icons, theming
|
|
740
|
+
conversation-viewer.ts # Live conversation overlay for viewing agent sessions
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
## License
|
|
744
|
+
|
|
745
|
+
MIT — [tintinweb](https://github.com/tintinweb)
|