Pi + pi-subagents Pairing Guide
pi-subagents is a third-party Pi extension that adds parallel dispatch, per-subagent model selection, and worktree isolation. Systematic’s curated personas reach it two ways: the installed package declares them as defaults, and an optional export writes files that carry your systematic.json overlays.
This is an opt-in complement to systematic_delegate. The bounded built-in delegate stays the default — sequential, capped at 20 turns, depth-1, re-entry guarded. pi-subagents is the power-user path for users who need parallel or multi-model delegation and have already installed and configured pi-subagents separately.
Tested interop contract: pi-subagents 0.22.0 on Pi 1.3.0. Versions outside the tested range are unsupported but nonfatal — the packaged and exported files are still valid; only the guidance contract is unverified.
Prerequisites
Section titled “Prerequisites”- Systematic installed as a Pi extension (
npx @fro.bot/systematic setup --harness pi) - pi-subagents 0.22 or newer, installed and configured separately (see its own docs). It is the first release that reads agents a package declares, and it requires Pi 1.1 or newer.
- Node.js 22.19 or newer for Pi itself, and Node.js 18+ or Bun for the
systematicCLI
Two ways to get the personas
Section titled “Two ways to get the personas”| Packaged defaults | Export for customization | |
|---|---|---|
| Setup | Install Systematic as a Pi package; nothing else | systematic pi-subagents export |
| Where the files live | Inside the installed package | <cwd>/.pi/agents/ or $PI_CODING_AGENT_DIR/agents/ |
Carries systematic.json overlays | No — plain defaults | Yes — model, thinking, max_turns, tools, skills |
| Picks up Systematic updates | When the package updates | When you run refresh |
When more than one source defines the same type, the highest wins: your project .pi/agents/, then the shared .agents/agents/, then your global agent directory, then a package. A local file replaces a packaged one silently, so an exported file shadows the packaged default of the same name.
Packaged defaults
Section titled “Packaged defaults”Installing Systematic as a Pi package is enough. The package declares a directory of persona files, and pi-subagents reads it as soon as the package is in a Pi settings.json packages[] list. Each persona is available to the Agent tool as systematic-<name> — for example systematic-repo-research-analyst.
- Project installs need trust. A package named in a project’s
.pi/settings.jsonis read only after Pi has trusted that project. pi-subagents builds theAgenttool description once at startup, from the trust decision Pi has already saved. In a project’s first session there is none, so the description does not list the packaged types, although dispatching one by name works. Later sessions list them. - Only package installs.
pi -eruns and git installs expose no personas. pi-subagents reads only the packages insettings.json, and a git install has no builtdist/directory. - Collisions are silent. A package’s persona never replaces a file you wrote, and nothing tells you when your file wins.
/agentsshows where each type came from. Packaged types appear read-only with Eject and Disable, with a source badge.- They are plain defaults. Packaged personas never receive
systematic.jsonoverlays, anddisabled_agentsandagents.<name>.disabledo not reach them. At startup Systematic warns once when either is configured, and says what to do instead (see Settings that cannot reach packaged personas).
Settings that cannot reach packaged personas
Section titled “Settings that cannot reach packaged personas”- Overlays need an export. To apply
model,thinking,max_turns,toolsorskillsfromsystematic.json, runsystematic pi-subagents export. The exported file outranks the packaged one. - Turn off one persona with
/agents → Disableinside Pi. Adisabled_agentsentry does not do this. - Turn off every packaged persona by setting
"packageAgents": falseinsubagents.json. The global file is$PI_CODING_AGENT_DIR/subagents.json(~/.pi/agent/subagents.jsonby default); a project’s.pi/subagents.jsonoverrides it. Do this when the extra weight below is not worth it. - Description cost. With pi-subagents’ default
toolDescriptionMode: "full", the 29 personas add about 6 KB of descriptions to theAgenttool description that every session sends. Setting"toolDescriptionMode": "compact"insubagents.jsonshortens it.
After upgrading
Section titled “After upgrading”A file you exported earlier keeps shadowing the packaged default and does not update on its own. Run refresh to bring it up to date, or cleanup to remove the exported files and fall back to the packaged defaults. cleanup refuses to remove a file you edited, so delete that file by hand.
Limits to know
Section titled “Limits to know”- Tools. A packaged or exported persona gets every built-in tool, because the render leaves
tools:out. OpenCode runs these personas with all tools too. Onlysystematic_delegateenforces a persona’s declared tools. - No
temperatureormode. pi-subagents frontmatter has no equivalent, so an authoredtemperature: 0.1runs at Pi’s default. OpenCode still honors it. - Two names for one persona.
systematic_delegatetakes the bare name (repo-research-analyst); pi-subagents takessystematic-repo-research-analyst. - 29 of 37 personas. Eight are left out because they depend on OpenCode-only machinery:
systematic-implementer,design-iterator,agent-native-reviewer,project-standards-reviewer,slack-researcher,learnings-researcher,kieran-typescript-reviewerandpr-comment-resolver. - A global export shadows everywhere. An export with
--scope globalreplaces the packaged persona in every project, not only the current one. - Background by default. pi-subagents runs a top-level
Agentcall in the background unless the call passesrun_in_background: false, so the result does not come back inline. Waiting for it needs the flag, or"backgroundByDefault": falseinsubagents.json.
Commands
Section titled “Commands”All commands use the form systematic pi-subagents <subcommand> [--scope project|global].
Default scope is project.
| Subcommand | What it does |
|---|---|
preview | Show what would be exported — files to create, update, refuse, remove, or leave in place. No writes. |
export | Write persona files to the agents dir. Refuses to overwrite non-Systematic files. |
refresh | Re-export owned files that have drifted from current source, and remove owned files for personas that are disabled or no longer exported. Ignores files not in the manifest. |
cleanup | Remove all Systematic-generated files and the manifest. |
Scope and target directories
Section titled “Scope and target directories”| Scope | Target directory |
|---|---|
project (default) | <cwd>/.pi/agents/ |
global | $PI_CODING_AGENT_DIR/agents/ or ~/.pi/agent/agents/ if $PI_CODING_AGENT_DIR is unset |
Usage walkthrough
Section titled “Usage walkthrough”# 1. See what would be written before committingnpx @fro.bot/systematic pi-subagents preview
# 2. Export personas to the project agents dirnpx @fro.bot/systematic pi-subagents export
# 3. After a Systematic update, pull in changes to owned filesnpx @fro.bot/systematic pi-subagents refresh
# 4. Remove all generated files when you're donenpx @fro.bot/systematic pi-subagents cleanupUse --scope global on any subcommand to target the global agents directory instead:
npx @fro.bot/systematic pi-subagents export --scope globalnpx @fro.bot/systematic pi-subagents refresh --scope globalnpx @fro.bot/systematic pi-subagents cleanup --scope globalConfiguration precedence
Section titled “Configuration precedence”Your Systematic config (systematic.json/.jsonc) is the durable source of truth. The generated persona files and their manifest are a disposable projection of that config — safe to delete and regenerate at any time with export or refresh.
Two independent overlay namespaces feed the export, resolved per-agent-beats-category in both:
categories/agents— the existing portable overlay. Only itsmodelfield is applied to exported personas (per-agent overlay wins over category overlay;model: nullon the winning overlay omits the field entirely rather than falling through).pi_subagents.categories/pi_subagents.agents— a Pi-native overlay for fields pi-subagents understands natively:thinking,max_turns,tools,skills. Category values apply first; per-agent values override field-by-field. There is nomodelfield in this namespace —modelalways comes fromcategories/agents.
Config source chain depends on export scope:
--scope project(default) loadsuser → project → custom, in increasing priority.--scope globalloadsuser → customonly — it never absorbs the current working directory’s project config, even when one exists.
The project config is .opencode/systematic.json, or .systematic.jsonc (or .systematic.json) at the repository root. When the root file exists it wins and the .opencode/ file is ignored; the two are never merged.
Trust boundaries
Section titled “Trust boundaries”A project config that fails to load is skipped by the plugin, but the project-scope export does not skip it. It writes into the repository, so it stops on the failure and writes nothing.
Project-level config (.opencode/systematic.json) is the least-trusted source. In the existing categories/agents overlay, a project-sourced model is rejected outright — config validation throws, it is not silently dropped. In the pi_subagents.categories/pi_subagents.agents overlay, project-sourced thinking, tools, and skills are silently stripped before merge — project config simply cannot grant them. max_turns is trust-any and passes through from any source, including project config. User config (~/.config/opencode/systematic.json) and custom config ($OPENCODE_CONFIG_DIR/systematic.json) may set all supported fields.
Canonical example
Section titled “Canonical example”This is a sanitized, repo-portable example — public model identifiers only, no local paths or secrets:
{ "$schema": "https://fro.bot/systematic/schemas/v3/systematic-config.schema.json", "categories": { "research": { "model": "github-copilot/gpt-6-luna" }, "review": { "model": "openai/gpt-6-astra" } }, "agents": { "repo-research-analyst": { "model": "github-copilot/gpt-6-luna" } }, "pi_subagents": { "categories": { "research": { "thinking": "medium" } }, "agents": { "repo-research-analyst": { "max_turns": 10, "tools": "read,bash", "skills": true } } }}Named tools/skills values must be discoverable by the user’s own Pi installation — pi-subagents does not know about Systematic’s bundled ce:* skill names, so don’t reference them here; use true to enable all skills, or a comma-separated list of skills the target Pi environment actually has installed.
Here, every exported persona in the research category picks up model: github-copilot/gpt-6-luna and thinking: medium, except repo-research-analyst, whose exact-agent overlay wins for both model and max_turns/tools/skills. Every persona in the review category picks up model: openai/gpt-6-astra.
Pi-native field values
Section titled “Pi-native field values”| Field | Type | Values |
|---|---|---|
thinking | enum | off, minimal, low, medium, high, xhigh, max |
max_turns | nonnegative integer | 0 means unlimited |
tools | comma-selector string | built-in tool names, */all/none, or extension selectors (e.g. "read,grep,glob") |
skills | true or comma-separated string | true enables all skills; a string of skill names installed in the target Pi environment selects specific skills |
What’s not configurable
Section titled “What’s not configurable”pi-subagents 0.22.0 has no equivalent frontmatter fields for temperature, top_p, or variant. Systematic never emits them into exported persona files — they are intentionally omitted, not silently discarded. variant (OpenCode’s reasoning-effort concept) is also never mapped onto thinking (pi-subagents’ own reasoning-effort field); they are distinct contracts and must be configured independently through pi_subagents.thinking if you want equivalent behavior.
Manual edits are not durable
Section titled “Manual edits are not durable”Editing an exported persona file directly works, but it is not the durable configuration path. Running pi-subagents refresh re-renders every manifest-owned file from current Systematic config and source, and will overwrite your manual edits if the regenerated content differs from what’s on disk. Run pi-subagents preview first if you want to see what would change before committing to refresh. To make a change stick permanently, set it in your Systematic config instead of editing the generated file.
What gets exported
Section titled “What gets exported”Systematic exports a curated subset of personas — those that are self-contained and do not depend on Systematic’s own orchestration machinery, skill-loading tool, or environment-specific tooling. Personas that require the Systematic orchestration loop (e.g. systematic-implementer), Slack MCP access, or the agent-browser extension are excluded, not adapted.
Exported files are named systematic-<sanitized-name>.md. Source persona files stay model-free — consistent with Systematic’s bundled-agent invariant. Exported files carry a model field only when you set one via the categories/agents config overlay described below; otherwise they stay model-free too.
The full curated include/exclude rationale lives in src/lib/pi-subagents-personas.ts.
Disabled personas
Section titled “Disabled personas”The export honors the same disable settings as the rest of Systematic: a persona listed in disabled_agents (by name, or as category/name) or turned off with agents.<name>.disable: true is not exported. systematic_delegate reads the same settings, so a disabled persona is also missing from its description and cannot be delegated to.
- Pi reads the settings once, at start. Restart Pi after changing them. The
systematic pi-subagentscommands read them each time they run. - Unchanged files are removed.
exportandrefreshdelete a disabled persona’s file, and its manifest entry, only if the manifest owns it and its content still matches what Systematic exported. - Edited files stay, and keep being listed. If you edited the file, it is left in place. The command lists it under “Still active in pi-subagents (edited since export, left in place)” every time it runs, because pi-subagents keeps loading it. Revert the file to its exported content, or delete it, to finish the removal. The command still exits 0. A persona that is no longer exported, such as one dropped from the curated set in a later Systematic release, is handled the same way.
- Re-enabling overwrites. If you enable a persona again while its edited file is still there, the next
exportorrefreshreplaces the file with the generated content. - Scope follows config loading. A project-level disable does not change a
--scope globalexport, which never reads project config. To hide a persona everywhere, disable it in your user config. - Project config can remove files. A
disabled_agentsentry oragents.<name>.disablein project config makes a project-scopeexportorrefreshremove that persona’s unchanged exported file.previewlists the removal first. - Personas outside the export set. A persona that is not in the curated export set can still be disabled. It disappears from
systematic_delegate, and the export has nothing to remove.
Ownership model and drift
Section titled “Ownership model and drift”A manifest file (.systematic-personas.json) in the agents dir tracks which files Systematic generated and their content hashes.
- Export refuses to overwrite any file not in the manifest (user files are never touched).
- Refresh updates only manifest-owned files whose content has drifted from current source, and removes owned files that are no longer wanted (see Disabled personas). It leaves your edits to non-owned files alone.
- Cleanup removes only files listed in the manifest, then removes the manifest itself.
Exported files are yours. You can edit them. refresh will detect drift between your copy and the current source and overwrite — only files it previously generated and owns.
Locking
Section titled “Locking”export, refresh, and cleanup each hold an exclusive per-root mutation lock (.systematic-personas.lock) for the duration of the operation, so concurrent invocations against the same agents dir refuse cleanly instead of racing. A pre-existing lock file is never auto-deleted by Systematic — if you see a lock-contention error, manually verify no other export/refresh/cleanup is actually running before removing the lock file yourself.
Path safety
Section titled “Path safety”Every operation walks the path from the scope anchor (the project cwd, or the resolved global anchor) down to the target agents directory and refuses if any intermediate component — or the agents directory itself — is a symlink or not a real directory. This anchored validation runs before the locked mutation, and every file path written, updated, or deleted is constructed via safe, root-bounded path resolution — but the validation walk and the mutation are not part of the same atomic step, so this does not eliminate every validation/write race.
Hash-gated deletion
Section titled “Hash-gated deletion”cleanup, and the removal of files that are no longer wanted during export and refresh, only delete a manifest-owned file whose on-disk content hash still matches the hash recorded in the manifest. If you have edited the file, cleanup refuses and leaves both the file and the manifest untouched. Revert the file to its exported content, or delete it by hand, then run cleanup again. Re-running refresh does not help for a disabled persona, because it leaves the edited file in place. export and refresh leave an edited file in place instead and report it as still active (see Disabled personas). refresh also overwrites current manifest-owned generated files that have drifted from source. If a manifest-owned file cannot be read at all (a directory in its place, for example), export and refresh stop with an error and change nothing. Manifest content hashes are drift/ownership evidence, not a cryptographic integrity or authenticity guarantee.
Tested runtime contract
Section titled “Tested runtime contract”Systematic’s tested interop contract targets @tintinweb/pi-subagents@0.22.0 on Pi 1.3.0. Both the packaged and the exported systematic-* personas — including systematic-repo-research-analyst — are accepted and executed by that pi-subagents version through its Agent tool, with the persona file’s own content driving the child request rather than a hardcoded prompt. A packaged persona dispatches from a project-scope or a global install, falls back to general-purpose when packageAgents is false, and loses to a local file of the same name. The combined path — a top-level pi-subagents call invoking a nested systematic_delegate — is supported end-to-end on this pinned version, consistent with the documented nested delegation boundary above.
Delegation boundaries
Section titled “Delegation boundaries”systematic_delegate is the bounded built-in delegate:
- Sequential, capped at 20 turns, same model, depth-1
- Child spawned with
noExtensions: true - Re-entry guarded
The noExtensions / depth-1 guarantee applies only to systematic_delegate’s own child. It does not bound end-to-end depth across a combined pi-subagents + Systematic path. That combined path is outside Systematic’s bounded-delegate guarantees and governed by pi-subagents’ own configuration.
If you need the safety guarantees — predictable cost, bounded recursion, no extension side-effects — use systematic_delegate. If you need parallel dispatch or per-subagent model control and understand pi-subagents’ own limits, use the personas via pi-subagents.
No automatic writes
Section titled “No automatic writes”Nothing is exported, detected, or written at extension load time. Systematic does not auto-detect whether pi-subagents is installed or active, and it never writes to your agents directories without a command: the packaged personas live inside the installed package, and every file in .pi/agents/ comes from an explicit systematic pi-subagents export. The only startup output is the one-time warnings described above.