@inobit/pi-subagent-presets
Batch-configure the model and thinking level of pi-subagents agents per project, and export the result as a reusable global profile blueprint.
Package details
Install @inobit/pi-subagent-presets from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@inobit/pi-subagent-presets- Package
@inobit/pi-subagent-presets- Version
0.1.1- Published
- Oct 7, 2026
- Downloads
- not available
- Author
- inobit
- License
- MIT
- Types
- extension
- Size
- 235.3 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@inobit/pi-subagent-presets
English | 中文
Batch-configure the model and thinking level of every pi-subagents agent per project, and export the result as a reusable global profile template.
- One command:
/subagent-presetsopens a matrix of agents × (model, thinking) and writes a project-levelsubagents.agentOverridesin one save - Field-level merge, not overwrite: global values for
tools/skills/acceptanceRole/machine/ … are copied into the project entry, so a project entry never silently drops what you configured globally - What you see is what runs: the
modelandthinkingcolumns show the value that will actually take effect, not the current file contents - Reusable template: every save can also export a global profile under
~/.pi/agent/profiles/pi-subagents/, interoperable with the official/subagents-profilesand/subagents-load-profile - Never breaks your config: rows with a provider-scoped config, rows disabled upstream, and rows you did not touch are left exactly as they are
- Soft dependency:
pi-subagentsis optional. Without it the command still runs in a degraded mode (see Soft dependency)
Installation
pi install npm:@inobit/pi-subagent-presets
Restart Pi or run /reload.
Local dev:
# isolated: only the extension given to -e is enabled
pi -ne -e ./packages/pi-subagent-presets
# full: your other user extensions are loaded too
pi -e ./packages/pi-subagent-presets
What differs:
-ne(--no-extensions) enables only the one extension passed to-eand skips every other user extension. That guarantees you exercise the working-tree code instead of an installed copy.It also hides user extensions that register model providers. If some of your model catalogue comes from such an extension, those models become "not in registry" under
-ne, which in turn disables thinking-level clamping and themaxThinkingceiling check. Use the command without-newhenever you need to verify model-dependent behaviour.
Usage
/subagent-presets # base = project entry ?? default profile
/subagent-presets --from work # base = the "work" profile (--from replaces the whole base)
| Key | Action |
|---|---|
↑ ↓ |
Move the selected row |
enter |
Open the model picker (inline search box above the list, always focused — just type) |
shift+tab |
Cycle the thinking level (pi's default levels when the row has no model) |
r |
Reset: do not write a project entry (state becomes GLOBAL); editing any field turns it into "write this instead" |
e |
Edit the whole entry that will be written in $EDITOR (model / thinking included; validation only warns) |
S |
Save |
esc |
Quit (asks twice when there are unsaved changes) |
The matrix has four columns (agent / model / thinking / state). state is
computed live from where the fields of the entry to be written come from:
| Condition | state |
Meaning |
|---|---|---|
| we provide no fields | GLOBAL |
this agent is not written to the project at all |
| the global layer has no field left for us | OVERRIDE |
we take it over completely |
| otherwise | MERGE |
partial: some global fields are not covered by the entry we write |
The state column never expands into a per-field breakdown; see the e editor and the
save dialog's diff for that.
Whenever a row ends up in MERGE / OVERRIDE, the complete merged entry is written —
including fields that came from the global layer — because a built-in agent's entry is
replaced per agent, so anything we don't write is dropped rather than inherited. Only a row
that stays GLOBAL is not written, and an existing project entry for it is deleted.
Per-row special states never enter the state column — they are shown in place after the
agent name (DISABLED / ⚠UPSTREAM DISABLED / ⚠MISSING + strikethrough / 🔒 / =alias).
A field without a value renders as genuinely blank.
The model picker pins one fixed row above the (possibly very long) model list:
| Option | Written | Effective model at runtime |
|---|---|---|
Parent session model |
the string "inherit" |
the parent session model, skipping subagents.defaultModel |
There is deliberately no "None" / delete-the-key option: for built-in agents
pi-subagents replaces the whole entry per agent (applyBuiltinOverrides returns as soon
as a project entry exists, so the global entry is never consulted). A project entry
without a model key therefore does not fall back to the global model — it falls through
to definition → subagents.defaultModel → parent session model, which is not a value you
can pick here. Use r to reset the row, or delete the key yourself in e.
Note: this per-agent replacement applies to built-in agents. Agents you define yourself in
.pi/agents/*.mdgo throughapplyCustomAgentOverrides, which is a field-by-field merge (user then project).
Non-interactive sessions print a summary of every agent and return without writing anything.
注意事项
上游对 agentOverrides 有两条语义相反的解析路径,取决于 agent 种类。写代码 / 排错前先确认你要改的 agent 属于哪一类:
| agent 种类 | 上游路径 | 语义 |
|---|---|---|
内置(pi-subagents 自带的 worker / reviewer / researcher / …) |
applyBuiltinOverrides |
按 agent 整体替换:项目里只要有该 agent 的条目,全局那条整条不参与。项目条目没写的字段回落到 agent 定义 → subagents.default* → 父会话模型 |
自定义(你在 .pi/agents/*.md 或某个 package 里定义的) |
applyCustomAgentOverrides |
逐字段合并:先应用全局、再应用项目(“project wins, without dropping user-only fields”) |
由此有三个容易踩的地方:
- 在
e里删掉一个字段,两类 agent 结果不同。 内置:字段真的没了(先回落到定义层,定义层也没有才彻底消失)。自定义:全局的值又填回来—— 删键等于「回落到全局」,无法表达「我不要这个字段」。要强制清空得写"machine": false(上游把false映射为delete)。 MERGE在两类 agent 上的含义不同。 它只表示「我们提供的字段没有覆盖全局的全部字段」。对内置 agent 意味着那些全局字段被丢弃;对自定义 agent 意味着它们回落到全局值。- 本扩展「写完整合并」的效果也不同。 对内置是必须的(否则字段真丢);对自定义,运行期结果一样,但项目里从此存了一份快照 —— 之后全局改
model,这个项目不会跟着变。
(判断 agent 属于哪一类,看 pi-subagents discovery 的四桶:builtin / package / user / project。)
Merge semantics
For built-in agents — which is every agent pi-subagents ships with — agentOverrides is resolved upstream per agent, not per field: if the project has an entry for reviewer, the global entry for reviewer is skipped entirely. Fields the project entry does not write fall back to the agent's own frontmatter, then to the top-level subagents.default*, then to the parent session model.
That is why hand-writing a project entry is lossy. This extension fixes it by writing the merge:
① base = --from profile | project entry ?? default profile
② global = ~/.pi/agent/settings.json (always)
③ frontmatter model + thinking (display only, never written)
save ⇒ for each changed row: write ① ∘ ②, field by field
Because every field written into the project entry becomes the final value, the resolved result is identical to "follow global" — field for field. Untouched rows are not written, so they keep following the global config.
The cost of pinning
Once the merged result is written into the project file, it no longer follows the global config: later edits to ~/.pi/agent/settings.json will not reach that project. The save dialog lists exactly which fields are being pinned so you can decide per row.
Worth keeping in mind for custom agents in particular: those follow the global config field by field, but after the merge is written they hold a snapshot just the same and stop following it.
Soft dependency
pi-subagents is an optional peer dependency (peerDependenciesMeta.pi-subagents.optional). Installing this package alone is not useful.
| Level | What you get | Requires |
|---|---|---|
| L0 | Merge, save, profile export, model list, thinking levels, the 26-field validator, both "do not materialize" guards | pi core + pi-ai only |
| L1 | Row classification (normal / upstream-disabled / upstream-missing / alias), maxThinking ceiling, definition-layer fallbacks, project-root resolution, cache clear |
discoverAgentsAll present in the installed pi-subagents |
| L2 | Yellow banner only — never blocks writing | — |
The five "upstream unavailable" cases all degrade instead of failing: not installed, install root not found, no loadable JS entry (git checkouts ship .ts only), version too old, or discoverAgents itself throws because a settings file contains an illegal value (red banner, degraded mode, never a crash).
Exceptions we deliberately do not materialize
Two situations would let our own write destroy an explicit user intent, so the affected row is left alone (neither written nor deleted):
- Provider-scoped config: if
agentOverridesByProvider.<any provider>.<agent>exists in the global settings, the project entry would kill that whole user-side entry. The row is marked 🔒 and the save dialog names the providers. - Top-level
disableThinking/disableBuiltins(project or global): writing would resurrect the affected agents. We do not block, but we show a yellow banner and ask for a second confirmation, and we list the fields that come back (disableBuiltinsresurrects whole entries, not justthinking).
A project-side provider layer is reported too (it keeps overriding the fields you save in the base layer) but does not block.
Whitelist = managed set
agents in <agentDir>/extensions/pi-subagent-presets/config.json is the managed set: the agents that appear in the matrix, and the only ones allowed to appear in the project agentOverrides. Removing one means "stop managing it" — its project entry is dropped on the next save in that project.
Default (7 pure-Pi runners):
{ "agents": ["worker", "scout", "reviewer", "oracle", "researcher", "delegate", "evidence-auditor"] }
advisor is an alias of oracle (keys must be the canonical name — an override written on advisor does nothing, so it is never written). The six external CLI runners (claude-code* / codex-exec* / cursor-agent*) are excluded because they ignore model / thinking at run time.
The project layer (<cwd>/.pi/extensions/pi-subagent-presets/config.json, trusted projects only) replaces the global list rather than merging with it — otherwise removing an agent would never take effect.
Profiles
Profiles live in ~/.pi/agent/profiles/pi-subagents/<name>.json, the same directory the official tooling uses. Format:
{ "subagents": { "agentOverrides": { "reviewer": { "model": "p/m", "thinking": "high" } } } }
- Names must match
^[A-Za-z0-9][A-Za-z0-9._-]*$; a trailing.jsonis stripped. - A profile carries no top-level
subagentskeys (defaultModel,defaultThinking,maxThinking, …). Those are not shadowed by an agent entry, so they are never exported — configure them separately in a new project. - Every field is validated before export.
model: falseis legal in project settings but not in a profile, so it is stripped with a notice; an entry that becomes empty is dropped entirely. - Reading a profile runs the same validator. An illegal value is a red banner and the merge is refused.
The e JSON editor
e opens $EDITOR (or settings.externalEditor / $VISUAL / $EDITOR) on the whole
entry that will be written — the exact object that lands in
subagents.agentOverrides.<agent>, model and thinking included. The comment header is a
commented field skeleton: one line per one of the 26 fields, with its value domain.
- The matrix and
eare two views of one draft: editingmodel/thinkinghere syncs into the matrix and takes effect. - Deleting a key is the only way to make a field "unset": remove the line. The save honours the deletion and never resurrects it from the merge base.
- Validation only warns — it never blocks the save and never rewrites what you typed (illegal values included):
| Case | Result |
|---|---|
Known field with an illegal value / non-level thinking |
⚠️ 上游会对以下内容报错(已照原样保存):reviewer.outputMode="x" |
| Unknown key | ⚠️ 未知键会被静默丢弃:reviewer.foo |
Those warnings are summarised on the save screen. The only thing still rejected is a
top-level non-object (that shape cannot be stored in agentOverrides at all).
Notes
- The
statecolumn only showsGLOBAL/MERGE/OVERRIDE, computed live from where each field of the entry to be written comes from.project entry exists + state === GLOBALmeans the entry contributes nothing to the final result, so the save deletes it (that is whatracts on). - The two kinds of "disabled" behave differently:
disabled: trueinside the merge result (your own config) stays editable — set it tofalseto re-enable; disabled in the four buckets but not in the merge result behaves exactly like "upstream no longer has this agent" (not editable, never written), only the marker differs. - Writing only replaces
subagents.agentOverrides. Everything else insettings.jsonkeeps its key set and values; the JSON formatting (indent, key order, trailing newline) is normalized. - No
/reloadis needed: the discovery cache fingerprint includessize:mtimeMsof both settings files, so the next launch picks the values up. We also callclearAgentDiscoveryCachewhen available. - Verify a result with pi-subagents' own
/subagents-models <agent>. - The project config directory name is not hardcoded — it is resolved from pi's
CONFIG_DIR_NAME(and pi-subagents resolves the same name from its ownpackage.json). - The project root follows pi-subagents'
findConfiguredProjectRootwhen available (including its.agents-directory candidates, home cutoff, andprojectRootResolutionpolicy). Without the upstream, the root isctx.cwd, and a banner tells you the file may not be picked up.
License
MIT