create-specialized-subagent
Create specialized subagents within the subagents extension. Use when asked to create a new subagent like scout, librarian, oracle, etc.
SKILL.md
| Name | create-specialized-subagent |
| Description | Create specialized subagents within the subagents extension. Use when asked to create a new subagent like scout, librarian, oracle, etc. |
name: create-specialized-subagent description: Create specialized subagents within the subagents extension. Use when asked to create a new subagent like scout, librarian, oracle, etc.
Create Specialized Subagent
Create subagents within extensions/subagents/subagents/.
Subagents are autonomous agents with their own tools, system prompt, and model. They run as Pi tools, streaming progress and rendering results.
Directory Structure
extensions/subagents/subagents/<name>/
├── index.ts # Main tool definition (createXxxTool, executeXxx, XXX_GUIDANCE)
├── config.ts # Model config, provider constants
├── system-prompt.ts # System prompt string
├── types.ts # XxxInput, XxxDetails interfaces
├── tool-formatter.ts # formatXxxToolCall() for display
└── tools/
├── index.ts # createXxxTools() aggregator
└── <tool>.ts # Individual tool definitions
Files Overview
types.ts
XxxInput: Parameters the parent agent provides- Include
skills?: string[]for optional skill passthrough
- Include
XxxDetails: State for UI rendering, must include:toolCalls: SubagentToolCall[]spinnerFrame: numberresponse?: stringaborted?: booleanerror?: stringusage?: SubagentUsageskills?: string[]- Requested skill names (from input)skillsResolved?: number- Number of skills successfully resolvedskillsNotFound?: string[]- Skill names that were not found
config.ts
MODEL: Provider and model ID- Optional provider type aliases for external APIs
system-prompt.ts
- Role definition
- Available tools list
- Behavior rules per input combination
- Response format guidelines
- Constraints and guardrails
tool-formatter.ts
formatXxxToolCall(tc): Returns{ label, detail }for human-readable displaylabel: Action name ("Search", "Fetch")detail: Context (hostname, query, repo path)
tools/
- Individual tool definitions
- Must return
costin details for cost aggregation:return { content: [...], details: { ..., cost: response.costDollars?.total }, };
index.ts
Exports:
createXxxTool(): Tool definition withexecute,renderCall,renderResultexecuteXxx(): Direct execution without tool wrapperXXX_GUIDANCE: Markdown guidance for parent agent's system prompt
Execute Function
- Resolve skills if provided:
const { skills: skillNames } = args; let resolvedSkills: Skill[] = []; let notFoundSkills: string[] = []; if (skillNames && skillNames.length > 0) { const result = resolveSkillsByName(skillNames, ctx.cwd); resolvedSkills = result.skills; notFoundSkills = result.notFound; } - Validate inputs, return error in details if invalid (include skill info)
- Set up spinner interval (80ms), clear in
finally - Build user message, append warning if skills not found:
if (notFoundSkills.length > 0) { userMessage += `\n\n**Note:** The following skills were not found and could not be loaded: ${notFoundSkills.join(", ")}`; } - Call
executeSubagent()with:skills: resolvedSkillsin configonTextUpdateandonToolUpdatecallbacks that include skill info in details
- Handle abort/error states (include skill info in all return paths)
- Use final tool calls from
result.toolCallsfor failure checks - Check if all tool calls failed → return error
- Return
usagefrom result (include skill info)
Tool Rendering Guidelines (required)
Use shared UI abstractions from @aliou/pi-utils-ui. Do not hand-roll tool header/body/footer for new subagents.
renderCall pattern
Always use this header shape:
- First line:
[Tool Name]: [Action] [Main arg] [Option args] - Following lines: long args only (wrapped naturally)
In subagents, use ToolCallHeader:
return new ToolCallHeader(
{
toolName: "Scout", // display name, not snake_case tool id
// action only when meaningful (e.g. process start/output/kill)
mainArg: "short primary arg",
optionArgs: [{ label: "cwd", value: args.cwd ?? "" }],
longArgs: [{ label: "prompt", value: args.prompt }],
},
theme,
);
Rules:
- Keep main arg short and useful.
- Move long text (prompt/task/question/instructions/context) to
longArgs. - Do not truncate when wrapping gives better readability (e.g. query/question).
- Tool name should be human display text (
Scout,Read Session), not raw tool id.
renderResult structure
Use ToolBody + footer component (SubagentFooter for model-backed subagents).
return new ToolBody({ fields, footer }, options, theme);
Footer spacing is standardized. Keep footer data concise and machine-skim-friendly.
renderResult States
| State | Display |
|---|---|
| Aborted | Warning "Aborted" + optional completion count |
| Error | Error message |
| Running + collapsed | Spinner + current tool name |
| Running + expanded | Status line + all tool calls with indicators |
| Done + collapsed | ✓ or ✗ (if all failed) + stats |
| Done + expanded | Stats + tool summary + failed tool details + markdown response |
Registration
In extensions/subagents/index.ts:
- Import the tool and guidance
- Add guidance to
SUBAGENT_GUIDANCESarray - Register tool with
pi.registerTool(createXxxTool()) - If the subagent requires API keys, add them to
checkApiKeys()
See the existing registration pattern in the file.
API Key Validation
If your subagent requires external API keys, validate them at extension load time. This prevents the extension from loading if required keys are missing.
Add your required keys to checkApiKeys() in extensions/subagents/index.ts. See the existing implementation for the pattern.
Checklist
- Create directory:
subagents/<name>/ - Create
types.tswith Input and Details interfaces- Add
skills?: string[]to Input interface - Add
skills?,skillsResolved?,skillsNotFound?to Details interface
- Add
- Create
config.tswith model configuration - Create
system-prompt.tswith subagent instructions - Create
tools/directory with subagent's tools - Create
tool-formatter.tsfor display formatting - Create
index.tswith createXxxTool, executeXxx, XXX_GUIDANCE- Import
resolveSkillsByNameandSkilltype from lib - Add
skillsparameter to TypeBox schema - Update tool description to mention skill support
- Resolve skills in execute function
- Pass
skills: resolvedSkillsto executeSubagent - Include skill info in all details returns
- Use
ToolCallHeaderinrenderCalland follow standard line pattern - Use
ToolBody+SubagentFooterinrenderResult
- Import
- Register in
extensions/subagents/index.ts - Run
pnpm typecheck
Key Points
- Details interface: Must include
toolCalls,spinnerFrame,response,aborted,error,usage, and skill tracking fields - Skills support: All subagents should support optional
skillsparameter for specialized context - Cost tracking: Tools must return
costin details for aggregation - Spinner: 80ms interval, clear in finally block
- Extensions disabled: Subagents don't run user extensions (
extensions: []) - Failed tools: Show individually in done+expanded state
- All failed: Show error indicator only when ALL tools failed (partial success = success indicator)
- Final tool calls: Use
result.toolCallsif present to decide failure state - Mark tool as failed: When all internal tools fail, return error so parent agent sees failure
- Skill resolution: Use
resolveSkillsByName()from lib to convert skill names to Skill objects - Skill warnings: Append warning to user message if skills not found (don't fail the request)
Notifications
Subagents can emit notifications to alert users. This is useful for long-running subagent tasks or when user attention is needed.
Emitting Notifications
const NOTIFICATION_EVENT = "ad:notification";
interface NotificationEvent {
message: string;
sound?: string;
}
// In execute function, after completion
pi.events.emit(NOTIFICATION_EVENT, {
message: "Research completed",
sound: "/System/Library/Sounds/Blow.aiff",
});
When to Notify
- Subagent completes a long-running task
- Subagent encounters errors that need user attention
- Subagent needs user input (though prefer interactive tools like
ask_user)
Reference
Refer to the scout subagent for complete implementation: