pi-system-prompt-patcher
Patch provider system prompts with exact, config-driven replacements.
Package details
Install pi-system-prompt-patcher from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-system-prompt-patcher- Package
pi-system-prompt-patcher- Version
0.0.10- Published
- Oct 8, 2026
- Downloads
- 1,405/mo · 707/wk
- Author
- kaanozdokmeci
- License
- MIT
- Types
- extension
- Size
- 26.5 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./extensions/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-system-prompt-patcher
Patch provider system prompts with exact, provider-aware, config-driven replacements.
The extension rewrites the top-level system field and role: "system" entries in messages
immediately before Pi sends a compatible provider request. It supports string prompts and arrays
of text content blocks. It ignores payloads without either form of system instructions.
Requires Pi >=1.1.0 <1.2.0. Releases are validated against Pi 1.1.0. The
extension refuses to load on a Pi older than 1.1.0, because Pi does not enforce
the package's peer range when it installs packages.
Pi's virtual models are not supported. The patcher selects replacement rules by the selected
model, not the model that answers a request. Pi's experimental virtual models, registered with
pi.registerVirtualModel(), stay selected while Pi routes each request to a physical model, so
the rules for the routed provider and model do not apply. Select the model directly.
Install
pi install npm:pi-system-prompt-patcher
To try the package without adding it to your settings:
pi -e npm:pi-system-prompt-patcher
Pi packages run with full system access. Review the source before installation.
Configure
Create ~/.pi/agent/pi-system-prompt-patcher.json:
{
"providers": {
"cult": {
"replacementFile": "replacements/cult.json",
"models": {
"ritual-2": "replacements/cult-ritual-2.json"
}
},
"other-provider": {
"replacementFile": "/absolute/path/to/replacements.json"
}
}
}
When PI_CODING_AGENT_DIR is set, the extension reads the configuration from that directory
instead of ~/.pi/agent.
Provider and model names are matched exactly. A model-specific file takes precedence over its
provider file. A provider can omit replacementFile when it only configures model-specific files.
Requests without a matching provider or model configuration are left unchanged.
Relative replacement file paths are resolved from the directory containing the settings file.
Absolute paths and paths beginning with ~ are also supported.
Each replacement file contains an array:
[
{
"target": "Exact text from the original system prompt",
"replacement": "Replacement text"
}
]
Targets and replacements can use two placeholders, which the extension fills in for every request:
{piPackageDir}: the running Pi's package directory, without a trailing slash. This is the directory Pi names in its documentation paths, such asMain documentation: .../README.md.{piVersion}: the running Pi's version, such as1.1.0.
Use them instead of a fixed installation path, so one rule matches every Pi installation and version:
[
{
"target": "{piPackageDir}/",
"replacement": "/opt/cult-code/{piVersion}/cult-code-coding-agent/"
}
]
Other text in braces is matched and inserted literally.
Replacements are:
- applied in array order;
- applied to every occurrence of each target across all system instruction fragments;
- selected by provider and, when configured, model;
- loaded again with the settings for every provider request, so changes do not require
/reload; - applied atomically—the provider payload is not mutated.
Each target must occur in at least one system instruction fragment, not in every fragment.
Targets do not match across fragment boundaries. User and assistant messages, tool declarations,
and non-text content blocks remain unchanged. The extension does not patch OpenAI instructions
or developer messages.
If a target is absent from all system instructions, the extension discards every patch, reports the missing target, and aborts the current agent turn. For a target with placeholders, the report shows both the filled-in target and the configured one. Invalid or unreadable settings and replacement files are reported and the request continues unchanged.
Development
mise run init
mise run check
mise run check runs the same checks and tests. npm test and npm run test:live
remove an inherited PI_PACKAGE_DIR from their test processes, so Pi resolves the
repository dependency's own package directory.
Live validation
Run npm run test:live to test the packed extension through the shipped Pi CLI
with the existing Anthropic login. The test asserts that the selected CLI reports
the version of the repository's Pi development dependency. It verifies outgoing
system replacements, configuration changes, prompt reload, and session resume:
- the assistant reply and the observed provider payload carry the configured replacement;
- after
SYSTEM.mdchanges and an extension-triggered reload, the provider payload carries the new prompt revision, so a no-op reload fails; and - after a CLI restart on the same session file, the session ID, the assistant history, and the recorded observations are unchanged before the next turn, so a fresh session fails.
Tests use isolated configuration, a temporary HOME, and synthetic prompts. The
global Pi configuration is not read.
By default the test packs the working directory with npm pack. Set
PI_PACKAGE_ARCHIVE to test a prepared archive instead. A relative path is resolved
from the current working directory. An empty value, a missing file, a directory,
an empty file, or malformed archive contents fail the test. It never falls back
to packing the worktree.
Set PI_TEST_CLI_PATH to the dist/bundle/cli.js of another installation of
the same Pi version to test that executable. scripts/test-live.ts reads the
Anthropic token through the repository Pi's pi auth print-bearer-token. Each CLI
subprocess resolves its own package directory.
Try locally
pi -e .
Release staging
- Run
npm run release -- X.Y.Zfrom a clean, synchronizedmain. - The command refuses to start unless the branch is
main, the worktree and index are clean,HEADmatchesorigin/main, the tag does not exist, andCHANGELOG.mdhas the version's section. It then updates and stages the version inpackage.jsonandpackage-lock.json, builds the exact package from the staged files, and runsnpm run test:livewithPI_PACKAGE_ARCHIVEset to that archive. Only after that test passes does it record the archive's SHA-256 in an SSH-signed release commit, prove a clean rebuild of the committed tree is reproducible, and create a lightweight tag. The rebuild does not repeat the live test. A missing prerequisite, a live test that cannot start, or a failing live test stops the release before the commit and tag. - Inspect the result, then push atomically with
git push --atomic origin main vX.Y.Z. - A read-only GitHub Actions job validates and packs the package. After approval in the tag-restricted
npm-publishenvironment, a separate GitHub-owned job verifies the signature and signed digest before attesting and staging that exact archive through npm trusted publishing. - A final job creates the immutable GitHub release for the tag from the same verified archive, its
checksum, and the version's
CHANGELOG.mdsection (Unreleasedfor prereleases). - Approve the staged package on npmjs.com, or with
npm stage approve <stage-id>.
Stable releases use latest; prereleases derive their npm dist-tag from the first prerelease identifier.
Recovering from a failed release
Inspect before changing anything:
git status --short
git diff -- package.json package-lock.json
git diff --cached -- package.json package-lock.json
git log --oneline -1
git tag --list 'vX.Y.Z'
If a prerequisite check failed, nothing changed.
If the command failed during the version update, version changes can remain in
the worktree. Once the update and git add succeed, those changes remain staged
after a package, live-test, or signing failure. No release commit or tag was
created by that attempt. Undo only its version edits in the worktree and index.
Preserve concurrent edits, including edits in those same files, and do not stage
whole files containing unrelated changes.
Do not use blanket git restore, git reset, or git clean commands. Fix the cause
and verify git status --short is empty before running the release command again.
If the command failed after signing (a rejected commit signature or an
irreproducible rebuild), the signed release commit exists on local main and no
tag exists. Do not rerun the release command, and do not push the commit: its
recorded digest has not been proven reproducible. Removing or amending that commit
rewrites local history and requires an explicit, reviewed recovery decision.
If the command failed after creating the tag, both the commit and the local tag exist. Removing the tag or the commit likewise requires an explicit, reviewed recovery decision. Do not rerun or push after this failure either. Never force-push or delete remote refs.