Skip to content

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.

  • 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 systematic CLI
Packaged defaultsExport for customization
SetupInstall Systematic as a Pi package; nothing elsesystematic pi-subagents export
Where the files liveInside the installed package<cwd>/.pi/agents/ or $PI_CODING_AGENT_DIR/agents/
Carries systematic.json overlaysNo — plain defaultsYes — model, thinking, max_turns, tools, skills
Picks up Systematic updatesWhen the package updatesWhen 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.

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.json is read only after Pi has trusted that project. pi-subagents builds the Agent tool 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 -e runs and git installs expose no personas. pi-subagents reads only the packages in settings.json, and a git install has no built dist/ directory.
  • Collisions are silent. A package’s persona never replaces a file you wrote, and nothing tells you when your file wins.
  • /agents shows 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.json overlays, and disabled_agents and agents.<name>.disable do 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, tools or skills from systematic.json, run systematic pi-subagents export. The exported file outranks the packaged one.
  • Turn off one persona with /agents → Disable inside Pi. A disabled_agents entry does not do this.
  • Turn off every packaged persona by setting "packageAgents": false in subagents.json. The global file is $PI_CODING_AGENT_DIR/subagents.json (~/.pi/agent/subagents.json by default); a project’s .pi/subagents.json overrides 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 the Agent tool description that every session sends. Setting "toolDescriptionMode": "compact" in subagents.json shortens it.

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.

  • 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. Only systematic_delegate enforces a persona’s declared tools.
  • No temperature or mode. pi-subagents frontmatter has no equivalent, so an authored temperature: 0.1 runs at Pi’s default. OpenCode still honors it.
  • Two names for one persona. systematic_delegate takes the bare name (repo-research-analyst); pi-subagents takes systematic-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-reviewer and pr-comment-resolver.
  • A global export shadows everywhere. An export with --scope global replaces the packaged persona in every project, not only the current one.
  • Background by default. pi-subagents runs a top-level Agent call in the background unless the call passes run_in_background: false, so the result does not come back inline. Waiting for it needs the flag, or "backgroundByDefault": false in subagents.json.

All commands use the form systematic pi-subagents <subcommand> [--scope project|global].

Default scope is project.

SubcommandWhat it does
previewShow what would be exported — files to create, update, refuse, remove, or leave in place. No writes.
exportWrite persona files to the agents dir. Refuses to overwrite non-Systematic files.
refreshRe-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.
cleanupRemove all Systematic-generated files and the manifest.
ScopeTarget directory
project (default)<cwd>/.pi/agents/
global$PI_CODING_AGENT_DIR/agents/ or ~/.pi/agent/agents/ if $PI_CODING_AGENT_DIR is unset
Terminal window
# 1. See what would be written before committing
npx @fro.bot/systematic pi-subagents preview
# 2. Export personas to the project agents dir
npx @fro.bot/systematic pi-subagents export
# 3. After a Systematic update, pull in changes to owned files
npx @fro.bot/systematic pi-subagents refresh
# 4. Remove all generated files when you're done
npx @fro.bot/systematic pi-subagents cleanup

Use --scope global on any subcommand to target the global agents directory instead:

Terminal window
npx @fro.bot/systematic pi-subagents export --scope global
npx @fro.bot/systematic pi-subagents refresh --scope global
npx @fro.bot/systematic pi-subagents cleanup --scope global

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 its model field is applied to exported personas (per-agent overlay wins over category overlay; model: null on 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 no model field in this namespace — model always comes from categories/agents.

Config source chain depends on export scope:

  • --scope project (default) loads user → project → custom, in increasing priority.
  • --scope global loads user → custom only — 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.

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.

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.

FieldTypeValues
thinkingenumoff, minimal, low, medium, high, xhigh, max
max_turnsnonnegative integer0 means unlimited
toolscomma-selector stringbuilt-in tool names, */all/none, or extension selectors (e.g. "read,grep,glob")
skillstrue or comma-separated stringtrue enables all skills; a string of skill names installed in the target Pi environment selects specific skills

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.

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.

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.

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-subagents commands read them each time they run.
  • Unchanged files are removed. export and refresh delete 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 export or refresh replaces the file with the generated content.
  • Scope follows config loading. A project-level disable does not change a --scope global export, which never reads project config. To hide a persona everywhere, disable it in your user config.
  • Project config can remove files. A disabled_agents entry or agents.<name>.disable in project config makes a project-scope export or refresh remove that persona’s unchanged exported file. preview lists 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.

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.

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.

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.

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.

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.

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.

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.