Native Pi extension for Solo, Aaron Francis's local agent + dev-stack workspace for macOS and Windows.
- PI SOLO header banner with a live status line beneath it: connection state (starting / warming / connected / disabled / error), catalog size split into direct vs. gateway tools, the active surface profile, and the current model. Replaces pi's built-in header on
session_startand re-renders on model changes and on every Solo MCP client transition — so the very first thing you see on startup already advertises what the extension is contributing. - Bundled
solotheme — a GitHub-dark-derived palette shipped with the extension viapi.themes. Installing the package makes the theme available in/settings; select it with"theme": "solo"in your pisettings.json. - Auto-detects Solo's bundled MCP helper at
/Applications/Solo.app/Contents/MacOS/mcpon macOS and%LOCALAPPDATA%\Solo\mcp.exeon Windows. - Spawns it lazily and speaks JSON-RPC over stdio — no separate MCP server to configure.
- Queries Solo for its full tool catalog and exposes a curated tool surface. By default, only the handoff/workflow essentials (
scratchpad_write,scratchpad_read,scratchpad_list,scratchpad_append_section,todo_create,todo_list,todo_update,todo_complete) are first-class Pi tools; lower-frequency cleanup/admin tools stay discoverable and callable throughsolo_tool. - Solo-native, resumable subagents. Spawn subagents as real Solo agent processes via Solo's native
spawn_agentMCP tool — visible in the sidebar with Solo's agent state, fire-and-forget, and woken via Solo's idle timer. Children run Pi with the current model configuration or a named profile (model/provider/thinking/skill preset, e.g.workeron a cheap model); roles come from skills referenced in the task text or attached to the profile. Every child gets a dedicated Pi--sessionJSONL file, so pi-solo can re-attach or respawn it after the parent restarts. Artifacts (plans, specs, context documents) flow through Solo scratchpads instead of local files. - Optional Solo notifications when a Solo subagent finishes and the parent Pi is ready for input. Enable with
/solo-notify subagent,PI_SOLO_NOTIFY=subagent, or"piSolo": { "notifications": "subagent" }in Pi settings. Notifications are sent through a dedicated Solo terminal so they use Solo's in-app notification path, not macOS system notifications. - Auto-binds to
SOLO_PROCESS_IDvia Solo's canonicalidentify_sessiontool when Pi runs as a Solo agent, so timers, locks, and todos owned by this Pi process behave correctly. - Idle-closes the helper after 5 s of inactivity so it doesn't show up as a persistent subprocess under your Pi row in Solo's sidebar. Bursts of MCP calls reuse one warm helper; quiet periods cost zero subprocesses.
- Renders keyboard shortcuts on spawn/start/restart/status results so you can press
⌥3 · ⌘5(or whatever the position resolves to) to jump straight to the relevant agent in Solo's sidebar. - Polished
renderCall/renderResultfor direct Solo tools — no raw JSON dumps in your tool log. - Gracefully no-ops when Solo isn't installed or MCP is disabled in Solo settings.
pi install git:github.com/HazAT/pi-soloOr pin a tag:
pi install git:github.com/HazAT/pi-solo@v0.1.0For local development, symlink the extension subdir into your global extensions directory:
ln -s ~/Projects/pi-solo/pi-extension/solo ~/.pi/agent/extensions/soloThe extension is auto-discovered. Run /reload if Pi is already running.
The package ships a solo theme (a GitHub-dark-derived palette tuned for the banner gradient) under themes/solo.json and registers it via pi.themes in package.json. After installing the extension:
-
Pick it interactively with
/settings→ Theme →solo, or -
Set it globally in
~/.pi/agent/settings.json:{ "theme": "solo" }
The currently active custom theme hot-reloads on edit, so tweaking themes/solo.json in a clone gives immediate visual feedback.
When the extension loads it replaces pi's built-in header with a gradient PI SOLO block-letter banner plus a live status line:
● solo connected · 76 tools (7 direct · 69 gateway · core) · model claude-sonnet-4-6
The status dot reflects the SoloMcpClient state — green when connected, yellow while warming or when MCP is toggled off in Solo settings, red on error — and the line re-renders on every model change and every MCP client transition. The previously transient Solo connected — … notification is gone; the same information now lives permanently in the header.
- Install Solo from https://soloterm.com.
- In Solo:
Settings → MCP→ toggle MCP on. - (Optional) Enable Todos, Scratchpads, Timers, Key-value in the same panel to expose those tool groups.
- Required for subagents: In Solo:
Settings → Agents→ Add tool. Add a separate Generic agent tool namedpi-subagentwhose command is the absolute path to this package'sbin/pi-subagentlauncher (find the package path withpi list). Keep the regularPiagent tool configured withpiso fresh agents remain clean. The dedicated launcher consumes pi-solo's private cwd argument, clears any inheritedPI_CODING_AGENT_DIR, and then starts Pi through normal package discovery. This keeps subagents on the globally installed Pi configuration while still loading the target project's.pi/settings.jsonand context files.
| Command | Purpose |
|---|---|
/solo |
Show connection status, catalog/direct/gateway counts, bound project & process |
/solo-tools |
List Solo MCP catalog tools and whether they are direct or gateway-only |
/solo-refresh |
Re-query Solo for its current tool catalog (cheap) |
/solo-reconnect |
Force-restart the helper |
/solo-bind <process-id> |
Manually bind this Pi to a Solo process |
/solo-subagent <name> [task] |
Spawn a Solo subagent with a display name and task |
/solo-subagent-resume <id|process-id|session> [prompt] |
Resume/re-attach a subagent or respawn Pi with an existing --session file |
/solo-notify [status|test [method]|off|subagent|agent-end|all] |
Configure/test Solo notifications for completed Solo work |
Solo subagents lean into Solo's own primitives. Subagents are spawned with Solo's native spawn_agent MCP tool and run Pi — by default with the user's current model configuration, or with a per-launch model via profiles (see below). Role instructions live in the task text (point the child at a skill file when one fits) or the optional systemPrompt param, which is appended to the child's system prompt via --append-system-prompt (the base coding prompt is never replaced). You can see, attach to (⌘N), and re-focus children from the sidebar. Solo provides the agent icon and agent_state tracking; timer_fire_when_idle_any wakes the parent when the child goes idle or hits the max wait.
Use cwd when the child must genuinely start in a different project rather than merely receive instructions about that folder:
subagent({
name: "Image generation",
cwd: "/Users/me/Homebase/pi-imagegen",
task: "Generate the requested image using this project's tools and workflow.",
});Pi-solo passes the directory to bin/pi-subagent, which changes directory and clears any inherited PI_CODING_AGENT_DIR before Pi starts. The child therefore uses the globally installed Pi configuration while loading the target project's AGENTS.md, .pi/settings.json, skills, and extensions, and the Solo process remains grouped under the caller's project. Relative cwd values resolve from the parent Pi working directory.
| Tool | Purpose |
|---|---|
solo_tool |
List, inspect, or call Solo MCP catalog tools that are hidden from the direct surface. |
subagent |
Spawn a sub-agent in a Solo agent pane. Fire-and-forget; parent wakes on idle. |
subagent_resume |
Re-attach to a live subagent or respawn Pi with a saved child --session file. |
subagent_interrupt |
Send Escape to interrupt the active turn (pane stays alive). |
A profile is a named model/provider/thinking/skill preset so delegated work runs on the right (often cheaper) model without the parent remembering model IDs:
subagent({ name: "Worker: Todo 123", profile: "worker", task: "Implement Solo todo 123…" });Built-in profiles:
| Profile | Provider | Model | Thinking | Skill |
|---|---|---|---|---|
worker |
openrouter |
x-ai/grok-4.5 |
low |
worker |
scout |
openrouter |
x-ai/grok-4.5 |
low |
scout |
reviewer |
openrouter |
openai/gpt-5.6-sol |
high |
review |
A profile's skill is prepended to the task automatically (Read …/SKILL.md and follow it.). Explicit model, provider, and thinking params override the profile; without a profile, omitted params fall back to the user's current Pi configuration. The available profiles are enumerated in the subagent tool description so the model can pick one without extra context.
Override or add profiles in Pi settings (project .pi/settings.json wins over ~/.pi/agent/settings.json):
There are no agent definition files. When a subagent should play a specific role (scout, reviewer, worker, …), use a profile with a skill, or tell it in the task text to read the matching skill first, e.g. Read ~/.pi/agent/skills/review/SKILL.md and follow it. The child picks up its role from the skill.
Whenever a subagent produces an artifact (plan, spec, scout report, review notes), it lands in a Solo scratchpad rather than a local file:
- The orchestrator pre-creates an empty scratchpad named
subagent/<timestamp>-<task-slug>. - The child is launched with a dedicated Pi
--session <jsonl>file and the first prompt tells it the scratchpad name and id so it can append its artifact under the pre-createdArtifactheading viascratchpad_append_section— no read-modify-write, no revision guard, and no risk of clobbering concurrent edits. - The Solo idle timer injects a wake-up body into the parent with the process id and scratchpad id so the parent can read it directly with
scratchpad_read. - The parent persists the child session path, process id, timer id, and artifact references in the parent Pi session. On restart, pi-solo validates the old Solo process; if it is still alive, it re-arms the idle watcher, and if it is gone, it respawns Pi with the same child
--sessionfile and sends a resume prompt.
This happens by default for every subagent. Pass scratchpad: false to opt out of the scratchpad artifact; the child Pi session still exists for resumption.
PI_SOLO_TOOL_SURFACE controls how much of Solo's MCP catalog is registered directly in Pi:
| Profile | Behavior |
|---|---|
core |
Default. Direct-register only the curated handoff/workflow essentials — scratchpad_write, scratchpad_read, scratchpad_list, scratchpad_append_section, todo_create, todo_list, todo_update, and todo_complete — and route every other MCP catalog tool through solo_tool. Other Solo tools (spawn_agent, identify_session, scratchpad_find, scratchpad_tail, scratchpad_edit, project admin) stay available via the gateway and are intentionally not promoted to direct. |
full |
Direct-register every Solo MCP catalog tool. |
minimal |
Direct-register no Solo MCP catalog tools; use solo_tool for all catalog access. |
Hand-written tools remain direct in every profile: solo_tool, subagent, and subagent_interrupt.
Use the gateway to discover and call hidden tools:
solo_tool({ action: "list", query: "process" });
solo_tool({ action: "schema", name: "get_project_stats" });
solo_tool({ action: "call", name: "get_project_stats", arguments: {} });State-changing gateway calls require a short reason:
solo_tool({
action: "call",
name: "close_process",
arguments: { process_id: 42 },
reason: "close completed worker pane",
});| Env var | Default | Purpose |
|---|---|---|
SOLO_MCP_HELPER |
macOS: /Applications/Solo.app/Contents/MacOS/mcp; Windows: %LOCALAPPDATA%\Solo\mcp.exe |
Path to the bundled helper |
SOLOTERM_APP_DATA_DIR |
~/.config/soloterm |
Solo's app data dir (passed through) |
SOLO_PROCESS_ID |
— | If set, Pi auto-binds to that Solo process |
PI_SOLO_DISABLED |
— | Set to 1 to disable the extension entirely |
PI_SOLO_TOOL_SURFACE |
core |
Tool surface profile: core, full, or minimal |
PI_SOLO_NOTIFY |
off |
Solo notifications: off, subagent, agent-end, or all |
Notification modes can also be set in ~/.pi/agent/settings.json or .pi/settings.json:
{
"piSolo": {
"notifications": "subagent"
}
}Use /solo-notify test to verify Solo shows the notification before leaving it on. The default test creates/reuses a Pi Solo Notifications terminal and sends OSC 777 from there; /solo-notify test macos is available only as a diagnostic fallback.
Solo ships a bundled stdio MCP helper at /Applications/Solo.app/Contents/MacOS/mcp on macOS and %LOCALAPPDATA%\Solo\mcp.exe on Windows. The helper reads the shared MCP secret from ~/.config/soloterm/solo.db, connects to Solo's local IPC endpoint, and bridges JSON-RPC over stdio. This extension spawns that helper, speaks the standard MCP protocol over its pipes, direct-registers the selected profile's tools, and keeps the rest reachable through solo_tool.
There is no separate MCP server in this extension. We talk to Solo directly using the helper Solo already provides.
Solo's sidebar shows every Pi process's subprocess tree. A long-running helper would show up there as a persistent child. To keep things clean, the extension keeps the helper warm only during burst activity and closes it 5 seconds after the last MCP call. The tool catalog stays cached, so subsequent calls re-warm the helper in ~30 ms — transparent to the LLM.
When Pi is launched as a Solo agent (Solo sets SOLO_PROCESS_ID in the environment), the extension calls Solo's canonical identify_session on initialization, passing solo_process_id when available. The response populates the bound process/project so timers it sets, locks it acquires, and todos it owns all belong to the right Solo identity. No fallback to the older whoami / bind_session_process flow is kept — Solo 0.7.1+ is the supported baseline.
subagent launches the dedicated pi-subagent agent tool through Solo's native spawn_agent, forwarding a child Pi session file as extra_args (--session …). It then waits until Solo reports agent_state.idle, schedules timer_fire_when_idle_any, and sends the wrapped task as one user turn. When the child later goes idle (or reaches the 30 minute max wait), Solo injects a plain wake-up body into the parent Pi process with the child process_id and scratchpad id. The parent records the subagent complete in its session state, reads the scratchpad, and closes the child pane when finished.
After successful spawn_agent / spawn_process / start_process / restart_process / get_process_status calls, the extension does two cheap follow-up MCP calls (list_projects + list_processes) to figure out where the target sits in Solo's sidebar, then renders the matching keyboard shortcut:
- Same project, position ≤ 9 →
⌘5 to jump - Different project, both ≤ 9 →
⌥3 · ⌘5 to jump - Position > 9 →
⌘E to jump(Solo's universal cross-project picker)
pi-solo/
├── pi-extension/solo/
│ ├── index.ts ← main extension (MCP client + tool registration)
│ ├── header.ts ← PI SOLO banner + live status subtitle
│ └── subagents/
│ ├── index.ts ← subagent tools, commands, task/wake text
│ └── solo-surface.ts ← thin Solo backend (agent spawn/send/timer/close)
├── themes/
│ └── solo.json ← bundled `solo` theme (GitHub-dark palette)
├── test/test.ts ← unit tests (node:test)
├── vite.config.ts ← Vite+ config (fmt / lint / staged)
├── .editorconfig ← shared indent / line-ending rules
├── .vite-hooks/pre-commit ← runs `vp staged` on every commit
├── .pi/
│ ├── settings.json ← dev pointer: load the extension when pi runs in this repo
│ └── skills/release/SKILL.md ← release workflow
├── LICENSE MIT
├── package.json scripts + peerDeps + `pi.extensions`
└── README.md
Mirrors the layout of pi-interactive-subagents.
The project uses Vite+ (vp) as the unified entry point for formatting, linting, and commit-hook orchestration. There is no bundling — the Pi extension is loaded directly as TypeScript at runtime — so vp only runs Oxfmt + Oxlint here, not Vite’s build pipeline.
vp install # install dev dependencies (npm under the hood)
vp config # install the .vite-hooks/_ pre-commit shim (once per clone)
vp check # format + lint the source tree
vp check --fix # auto-fix formatting and lint issues
vp fmt # run only Oxfmt
vp lint # run only Oxlint
npm test # run unit tests (node:test)On every commit, the installed pre-commit hook runs vp staged, which executes vp check --fix against just the files in the index — trivial formatting drift is auto-fixed and re-staged before the commit lands.
To cut a release, ask Pi: “release 0.2.0” (the .pi/skills/release/SKILL.md skill drives the rest).
Built and tested on macOS 14+ and Windows 11, Pi 0.74+, Solo 0.7.1+ (the supported and tested baseline; older Solo versions are not supported). Linux remains untested.
MIT
{ "piSolo": { "subagentProfiles": { "worker": { "provider": "openrouter", "model": "x-ai/grok-4.5", "thinking": "low", "skill": "worker", "description": "implementation work on a cheap model", }, }, }, }