Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pi-solo

Native Pi extension for Solo, Aaron Francis's local agent + dev-stack workspace for macOS and Windows.

SCR-20260516-irqz

What it does

  • 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_start and 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 solo theme — a GitHub-dark-derived palette shipped with the extension via pi.themes. Installing the package makes the theme available in /settings; select it with "theme": "solo" in your pi settings.json.
  • Auto-detects Solo's bundled MCP helper at /Applications/Solo.app/Contents/MacOS/mcp on macOS and %LOCALAPPDATA%\Solo\mcp.exe on 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 through solo_tool.
  • Solo-native, resumable subagents. Spawn subagents as real Solo agent processes via Solo's native spawn_agent MCP 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. worker on a cheap model); roles come from skills referenced in the task text or attached to the profile. Every child gets a dedicated Pi --session JSONL 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_ID via Solo's canonical identify_session tool 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/renderResult for 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.

Install

pi install git:github.com/HazAT/pi-solo

Or pin a tag:

pi install git:github.com/HazAT/pi-solo@v0.1.0

For local development, symlink the extension subdir into your global extensions directory:

ln -s ~/Projects/pi-solo/pi-extension/solo ~/.pi/agent/extensions/solo

The extension is auto-discovered. Run /reload if Pi is already running.

Theme

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.

Header

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.

Setup

  1. Install Solo from https://soloterm.com.
  2. In Solo: Settings → MCP → toggle MCP on.
  3. (Optional) Enable Todos, Scratchpads, Timers, Key-value in the same panel to expose those tool groups.
  4. Required for subagents: In Solo: Settings → AgentsAdd tool. Add a separate Generic agent tool named pi-subagent whose command is the absolute path to this package's bin/pi-subagent launcher (find the package path with pi list). Keep the regular Pi agent tool configured with pi so fresh agents remain clean. The dedicated launcher consumes pi-solo's private cwd argument, clears any inherited PI_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.json and context files.

Commands

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

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.

Tools

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).

Profiles

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):

{
	"piSolo": {
		"subagentProfiles": {
			"worker": {
				"provider": "openrouter",
				"model": "x-ai/grok-4.5",
				"thinking": "low",
				"skill": "worker",
				"description": "implementation work on a cheap model",
			},
		},
	},
}

Skills instead of agent definitions

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.

Scratchpad artifacts

Whenever a subagent produces an artifact (plan, spec, scout report, review notes), it lands in a Solo scratchpad rather than a local file:

  1. The orchestrator pre-creates an empty scratchpad named subagent/<timestamp>-<task-slug>.
  2. 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-created Artifact heading via scratchpad_append_section — no read-modify-write, no revision guard, and no risk of clobbering concurrent edits.
  3. 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.
  4. 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 --session file 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.

Solo MCP tool surface

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",
});

Configuration

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.

How it works under the hood

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.

Idle-close

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.

Auto-binding

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 wake-up

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.

Keyboard hints

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)

Repo layout

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.

Development

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).

Status

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.

License

MIT

About

Pi extension for Solo (soloterm.com) — registers every Solo MCP tool as a first-class Pi tool, auto-binds to SOLO_PROCESS_ID, idle-closes the helper, and renders Solo keyboard shortcuts in tool results.

Resources

Stars

46 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages