@ladbabynpm/picc-subagents
Claude Code style Agent tool for pi agent. Fork of tintinweb/pi-subagents, better aligned with the harness of Claude Code.
Package details
Install @ladbabynpm/picc-subagents from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@ladbabynpm/picc-subagents- Package
@ladbabynpm/picc-subagents- Version
0.1.1- Published
- Sep 3, 2026
- Downloads
- 128/mo · 30/wk
- Author
- ladbabynpm
- License
- MIT
- Types
- extension
- Size
- 350.3 KB
- Dependencies
- 3 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
picc-subagents
Fork of @tintinweb/pi-subagents, adding
PICC_SUBAGENTS_MODEL / CLAUDE_CODE_SUBAGENT_MODEL env-var model selection, removing get_subagent_result and steer_subagent tools.
To switch back to upstream, run pi install npm:@tintinweb/pi-subagents.
Part of picc, a pi agent setup mirroring Claude Code's harness.
pi has no built-in sub-agent concept. This extension adds one faithful to Claude Code: the
Agenttool, foreground + background runs, a live widget and FleetView, custom agent types, mid-run steering, session resume, and a graceful turn limit — all with Claude Code's names, calling conventions, and UI patterns.
Usage
Install via pi install npm:@ladbabynpm/picc-subagents.
Tools
Agent
Launch a sub-agent.
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt |
string | yes | The task for the agent |
description |
string | yes | Short 3-5 word summary (shown in UI) |
subagent_type |
string | yes | Agent type (built-in or custom) |
model |
string | no | provider/modelId or fuzzy name ("haiku", "sonnet"); resolved tolerantly with provider fallback |
thinking |
string | no | off, minimal, low, medium, high, xhigh, max |
max_turns |
number | no | Max agentic turns; omit for unlimited |
run_in_background |
boolean | no | Run without blocking |
resume |
string | no | Agent ID to resume a previous session |
isolated |
boolean | no | No extension/MCP tools |
isolation |
"worktree" |
no | Run in an isolated git worktree |
inherit_context |
boolean | no | Fork parent conversation into agent |
schedule |
string | no | Fire later instead of now (cron / interval / one-shot) |
Scheduling
Pass schedule to the Agent tool to fire the agent later instead of running now:
Agent({
subagent_type: "Explore",
prompt: "Look at recent commits and summarize what changed since last week",
description: "Weekly commit review",
schedule: "0 0 9 * * 1", // 9am every Monday (6-field cron)
})
Formats:
- Cron — 6-field (
second minute hour day-of-month month day-of-week), e.g."0 */15 * * * *"every 15 minutes. - Interval —
"5m","1h","30s","2d". Fires repeatedly. - One-shot relative —
"+10m","+2h","1d". - One-shot absolute — full ISO timestamp, e.g.
"2026-12-25T09:00:00.000Z".
When a schedule fires, the spawn runs in the background and its completion arrives through the same
subagent-notification path as a manually-spawned background agent.
- Schedules are session-scoped — reset on
/new, restore on/resume. Stored at<cwd>/.pi/subagent-schedules/<sessionId>.jsonwith PID-based locking. Manage via/agents → Scheduled jobs. schedulecannot combine withinherit_contextorresume;run_in_backgroundis forcedtrue; scheduled fires bypass themaxConcurrentqueue.- Headless
pi -pdoes not wait for scheduled subagents. - Disable entirely via
/agents → Settings → Scheduling → disabled(removesschema, hides the menu entry, stops active schedulers).
Custom Agents
Define agent types by creating .md files; the filename becomes the type name, and any name
overrides a same-named default. Discovered from three locations (higher wins):
| Priority | Location | Scope |
|---|---|---|
| 1 (highest) | .pi/agents/<name>.md |
Project — pi's config dir; authoritative |
| 2 | .agents/agents/<name>.md |
Project — shared cross-tool .agents workspace |
| 3 | $PI_CODING_AGENT_DIR/agents/<name>.md (default ~/.pi/agent/agents/<name>.md) |
Global — available everywhere |
Example: .pi/agents/auditor.md
---
description: Security Code Reviewer
tools: read, grep, find, bash
model: anthropic/claude-opus-4-6
thinking: high
max_turns: 30
---
You are a security auditor. Review code for vulnerabilities including:
- Injection flaws (SQL, command, XSS)
- Authentication and authorization issues
- Sensitive data exposure
- Insecure configurations
Then spawn it like any built-in: Agent({ subagent_type: "auditor", ... }).
Frontmatter fields
All optional — sensible defaults throughout.
| Field | Default | Description |
|---|---|---|
description |
filename | Shown in tool listings |
display_name |
— | UI display name |
tools |
all 7 | Built-in names, */all, none, and ext:<ext> / ext:<ext>/<tool> selectors |
extensions |
true |
true (all), false (none), or a list: [mcp, "/abs/path.ts", "*"] |
exclude_extensions |
— | Extension denylist applied after extensions:; plain names only |
skills |
true |
Inherit skills, or a comma list to preload |
memory |
— | Persistent memory scope: project, local, or user |
disallowed_tools |
— | Tools to deny even if extensions provide them |
isolation |
— | worktree to run in an isolated git worktree |
model |
inherit | provider/modelId or fuzzy name; resolved tolerantly with provider fallback |
thinking |
inherit | off, minimal, low, medium, high, xhigh, max |
max_turns |
unlimited | Max agentic turns before graceful shutdown |
persist_session |
false |
Persist as a normal pi session (transcript written either way) |
session_dir |
pi default | Session dir when persist_session: true |
prompt_mode |
replace |
replace (full prompt) or append (parent twin — inherits AGENTS.md/CLAUDE.md) |
inherit_context |
false |
Fork parent conversation into agent |
run_in_background |
false |
Run in background by default |
isolated |
false |
Hermetic: extensions: false + skills: false, built-ins only |
enabled |
true |
false to disable (hide a default per-project) |
Frontmatter is authoritative: if it sets model, thinking, max_turns, inherit_context,
run_in_background, isolated, or isolation, those are locked; Agent parameters only fill
unspecified fields. model: resolution is forgiving (./- and a trailing date stamp are
interchangeable; a provider/modelId whose provider lacks the model retries the bare id), with
precedence exact → fuzzy under named provider → same model under any provider → unavailable
(inherits the parent model, flagged in /agents).
Tool & extension scoping
extensions: decides which extensions load; tools: decides which tools surface to the
LLM. They compose:
tools: read, grep, find # narrow built-ins; extensions still load
tools: "*" # all 7 built-ins (alias: `all`)
tools: none # zero built-ins
tools: "*, ext:mcp/search" # built-ins plus one extension tool
extensions: false # no extensions load
extensions: [mcp] # only mcp loads
exclude_extensions: pi-notify # everything except pi-notify
isolated: true # hermetic: built-ins only, no extensions/skills/context
extensions:is the sole loading authority —ext:foointools:narrows what surfaces but can't loadfooalone.- Any
ext:entry flips extension tools to an explicit allowlist:tools: "*, ext:mcp/search"exposes onlysearchfrommcp, nothing from other extensions. - Extension names match case-insensitively;
ext:foo/bartool names stay case-sensitive. A package extension matches by short name (@scope/pi-subagents→[pi-subagents]). - Plain
tools:typos fail loudly (tools-error:…), andexclude_extensions:wins over everything but is not a sandbox (excluded factories still run once).
Commands
| Command | Description |
|---|---|
/agents |
Interactive agent management menu |
/agents opens a menu of Running agents (open a live conversation viewer; Enter to steer,
x/x to stop — including background agents), Agent types (unified defaults + custom with
•/◦/✕ source indicators; Eject / Edit / Disable / Delete per state), Create new agent
(manual wizard or AI-generated), and Settings (concurrency, max turns, grace turns, join mode,
scheduling, scope models, disable defaults, tool description mode, widget mode).
Default Agent Types
| Type | Tools | Model | Prompt Mode | Description |
|---|---|---|---|---|
general-purpose |
all 7 | inherit | append (parent twin) |
Inherits the parent's full system prompt — same rules, CLAUDE.md, conventions |
Explore |
read, bash, grep, find, ls | haiku (falls back to inherit) | replace |
Fast codebase exploration (read-only) |
Plan |
read, bash, grep, find, ls | inherit | replace |
Software architect for implementation planning (read-only) |
Default agents can be ejected (export as .md), overridden (create a .md with the same
name), or disabled per-project (enabled: false).
UI
A persistent widget above the editor shows active agents (animated spinners, live tool activity,
token counts, status icons). Default widgetMode: background (foreground runs already render
inline as the Agent result); switch via /agents → Settings → Widget to all or off. The
token field is annotated with context-window utilization (NN%, color-coded) and compaction count
(⇊N).
FleetView renders a Claude Code-style navigable list of main + every running subagent below
the editor (earliest-launched first). At an empty prompt press ↓/← to enter, ↑/↓ to move,
Enter to open the live conversation, Esc to return. Inside the overlay Enter opens a steering
composer, and x stops a running agent. Toggle via /agents → Settings → Fleet view.
Individual results render Claude Code-style (⠹ running, ✓ completed/wrapped, ■ stopped, ✗
error/aborted); completed results expand (ctrl+o) to full output. Both foreground and background
agents stream to .pi/output/agent-<id>.jsonl; background completions render as themed notification
boxes while the LLM receives structured <task-notification> XML.
Graceful Max Turns
Instead of hard-aborting at max_turns, agents get a wrap-up warning, up to 5 grace turns to finish
cleanly, and a hard abort only after the grace period.
| Status | Meaning | Icon |
|---|---|---|
completed |
Finished naturally | ✓ green |
steered |
Hit limit, wrapped up in time | ✓ yellow |
aborted |
Grace period exceeded | ✗ red |
stopped |
User-initiated abort | ■ dim |
Concurrency & Join Strategies
Background agents are subject to a configurable concurrency limit (default 4); excess agents queue and start as slots free up. Foreground agents bypass the queue.
The join mode controls how background completion notifications are delivered:
| Mode | Behavior |
|---|---|
smart (default) |
2+ background agents from the same turn are consolidated into one notification; solo agents notify individually |
async |
Each agent notifies on completion (original behavior) |
group |
Force grouping even for a single agent |
Grouped agents start a 30-second window after the first completes; partial results notify on
timeout, stragglers re-batch over a 15-second window. Configure via /agents → Settings → Join mode.
Model Scope
Opt-in: off by default; enable via /agents → Settings → Scope models. When on, each spawn's
effective model is validated against pi's enabledModels list (from /scoped-models). Both global
~/.pi/agent/settings.json and project <cwd>/.pi/settings.json are honored — project overrides
global.
| Model source | Out-of-scope behavior |
|---|---|
Caller-supplied Agent({ model }) |
Hard error to the orchestrator, listing allowed models |
| Pinned in frontmatter | Warning; the pin runs (frontmatter authoritative) |
| Parent-inherited | Warning; the parent model runs |
Only exact provider/modelId entries are honored; globs, bare ids, and :thinking suffixes are
dropped. An empty/missing enabledModels makes the check a no-op.
Persistent Settings
Runtime values set via /agents → Settings persist across restarts. Two files, merged on load:
- Global:
~/.pi/agent/subagents.json— machine-wide defaults (hand-edited; the menu never writes here). - Project:
<cwd>/.pi/subagents.json— per-project overrides (written by the menu).
Project overrides global; missing fields fall back to hardcoded defaults (concurrency 4, max
turns unlimited, grace 5, join smart, defaults enabled). disableDefaultAgents (default
false) stops the three built-ins from registering. toolDescriptionMode ("full" / "compact" /
"custom") controls which Agent tool description the LLM sees; "custom" reads a markdown file
with {{typeList}}, {{compactTypeList}}, {{agentDir}}, {{scheduleGuideline}} placeholders
(see examples/agent-tool-description.md as a starting point).
Events
Lifecycle events are emitted via pi.events so other extensions can react:
| Event | When |
|---|---|
subagents:created |
Background agent registered |
subagents:started |
Agent transitions to running (incl. queued→running) |
subagents:completed |
Agent finished successfully |
subagents:failed |
Agent errored, stopped, or aborted |
subagents:steered |
Steering message sent |
subagents:compacted |
Agent's session compacted (reason: manual/threshold/overflow) |
subagents:scheduled |
Schedule lifecycle change (added/removed/updated/fired/error) |
subagents:scheduler_ready |
Scheduler bound, enabled jobs armed |
subagents:ready |
RPC handlers registered and armed (session start) |
subagents:settings_loaded / settings_changed |
Persisted settings applied / mutated |
tokens.total = input + output + cacheWrite (cacheRead excluded to avoid over-counting). Use
contextUsage.percent for current context size.
Cross-Extension RPC
Other pi extensions can spawn and stop subagents via pi.events without importing this package.
Replies use a standardized envelope: { success: true, data? } or { success: false, error }.
- Discovery — listen for
subagents:readybefore calling; treat its absence as "not available here" (give discovery a timeout). - Ping —
subagents:rpc:pingreturns the protocol version. - Spawn —
subagents:rpc:spawnwithtype,prompt, andoptions(e.g.{ description, run_in_background, model, cwd }).options.modelaccepts aModelobject or"provider/modelId"string (resolved againstctx.modelRegistry).options.cwdruns the agent in another directory (.pistill loads from the parent project). - Stop —
subagents:rpc:stopwithagentId.
Reply channels are scoped per requestId, so concurrent requests don't interfere.
Persistent Agent Memory
Set memory in frontmatter to enable memory across sessions:
| Scope | Location | Use case |
|---|---|---|
project |
.pi/agent-memory/<name>/ |
Shared across the team (committed) |
local |
.pi/agent-memory-local/<name>/ |
Machine-specific (gitignored) |
user |
~/.pi/agent-memory/<name>/ |
Global personal memory |
Memory uses a MEMORY.md index plus individual files. Agents with write tools get full read-write;
read-only agents (no write/edit) automatically get read-only memory to prevent unintended
tool escalation. disallowed_tools is respected when detecting write capability.
Worktree Isolation
Set isolation: worktree to run an agent in a temporary git worktree. On completion: no changes →
cleaned up; changes made → committed to a pi-agent-<id> branch and returned; agent committed its
own work → branch created at its HEAD. The preservation commit uses --no-verify (local-only,
never pushed). If the worktree can't be created, the tool returns a clear error — worktree is a
strict guarantee, not a hint.
Skill Preloading
Preload named skills into the agent's system prompt (skills: api-conventions, error-handling).
Discovery roots, checked in order (first match wins): <cwd>/.pi/skills/, <cwd>/.agents/skills/,
$PI_CODING_AGENT_DIR/skills/ (default ~/.pi/agent/skills/), ~/.agents/skills/, ~/.pi/skills/.
Within a root a skill foo resolves to <root>/foo.md, <root>/foo/SKILL.md, or a directory skill
found by recursive descent (skipping dotdirs and node_modules). Symlinks are rejected at every
layer; path-traversal skill names are rejected.
Requirements
No external binaries. Runs entirely on the pi runtime.
Development
npm install
npm run lint # biome check
npm run typecheck # tsc --noEmit
npm run test # vitest run
Architecture
src/
index.ts # Extension entry: tool/command registration, rendering
types.ts # Type definitions (AgentConfig, AgentRecord, etc.)
default-agents.ts # Embedded default agent configs (general-purpose, Explore, Plan)
agent-types.ts # Unified agent registry (defaults + user), tool name resolution
agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
cross-extension-rpc.ts # RPC handlers for cross-extension spawn/ping via pi.events
group-join.ts # Batched completion notifications with timeout
custom-agents.ts # Load user-defined agents from .pi/agents/, .agents/agents/, global
memory.ts # Persistent agent memory (resolve, read, build prompt blocks)
skill-loader.ts # Preload skills (Pi-standard + Agent Skills spec layouts)
output-file.ts # Streaming output transcripts
worktree.ts # Git worktree isolation (create, cleanup, prune)
prompts.ts # Config-driven system prompt builder
context.ts # Parent conversation context for inherit_context
env.ts # Environment detection (git, platform)
subagent-env.ts # PICC_SUBAGENTS_MODEL / CLAUDE_CODE_SUBAGENT_MODEL overrides (fork)
ui/
agent-widget.ts # Persistent widget: spinners, activity, status icons, theming
conversation-viewer.ts # Live conversation overlay for viewing agent sessions
Fork notes (PICC_SUBAGENTS_MODEL / CLAUDE_CODE_SUBAGENT_MODEL)
This fork adds support for picking the default subagent model from an environment
variable, mirroring claude-code's
CLAUDE_CODE_SUBAGENT_MODEL hook.
| Variable | Precedence | Notes |
|---|---|---|
PICC_SUBAGENTS_MODEL |
1 (highest) | Fork-specific. |
CLAUDE_CODE_SUBAGENT_MODEL |
2 | claude-code-compatible; ignored when PICC_SUBAGENTS_MODEL is set. |
| unset | — | Falls back to existing precedence: caller Agent({model}) > frontmatter model: > inherit parent. |
The chosen model overrides everything — frontmatter pins and Agent model: parameters —
matching claude-code's treatment of the env var as the user's authoritative default. Resolution uses
the same forgiving matcher as the Agent model parameter (fuzzy aliases, provider/modelId,
date stamps, provider fallback). A misconfigured value surfaces a Model not found: … tool error
rather than silently falling back; whitespace-only values are treated as unset. Affects in-process
Agent calls; cross-extension RPC callers pick up the same vars in their own process.
export PICC_SUBAGENTS_MODEL=haiku # always run subagents on haiku
export CLAUDE_CODE_SUBAGENT_MODEL=anthropic/claude-haiku-4-5 # claude-code-compatible