@bramburn/ai-compact-hot-swap0.1.1
pi coding-agent extension: background compaction with hot-swap of the live context window
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
ai-compact-hot-swap
A pi coding-agent extension providing a manually-invoked /hot-swap-compact command that compacts the session context in the background and hot-swaps the live context window when summarization completes.
Why
pi's built-in /compact aborts the in-flight turn and blocks until summarization finishes. /hot-swap-compact never interrupts ongoing work:
- Idle session — compaction starts immediately in the background via
ctx.compact({ onComplete, onError }). - Busy session (turn in flight) — compaction is queued instead of run, because
AgentSession.compact()would abort the running turn. The queued compaction fires automatically on the nextturn_endevent. Re-invoking the command while one is queued or running just reports the existing state; only one pending compaction is tracked at a time.
When compaction finishes you get a notification (Hot swap applied: context replaced by summary (backlog through entry N)), where N is the session entry count captured at trigger time so you know the range of history that was swapped. Failures are notified too, and internal state is cleared.
Install
Prerequisites
- pi installed (
pi --version) - Node.js ≥ 22.19.0 (required by the extension's
enginesfield — needed for native TypeScript loading) - A peer-dep-compatible pi installation (pi bundles
@earendil-works/pi-coding-agent ≥ 0.85.0automatically)
From npm (recommended)
The extension is published on npm as @bramburn/ai-compact-hot-swap.
Global install (available in every project):
pi install npm:@bramburn/ai-compact-hot-swap
By default this writes to ~/.pi/agent/settings.json and installs to ~/.pi/agent/npm/@bramburn/ai-compact-hot-swap. After install, pi auto-loads the extension on every startup; no reload required for new sessions.
Project-local install (recommended for teams / pinned versions):
pi install npm:@bramburn/ai-compact-hot-swap -l
This writes to <project>/.pi/settings.json and installs to <project>/.pi/npm/. Commit the resulting .pi/settings.json entry so teammates auto-install the package when they trust the project. See the official pi packages docs for the trust / install-on-startup flow.
Pin to a specific version:
pi install npm:@bramburn/ai-compact-hot-swap@0.1.0
Pinned installs are skipped by pi update --extensions / pi update --all (see Updates below). Use a version range like @^0.1 to track minor upgrades, or omit the version entirely to always pull the latest.
Ad-hoc / try-before-install
Run the extension for a single session without persisting it anywhere:
pi -e npm:@bramburn/ai-compact-hot-swap
# …or against a local checkout:
pi -e /absolute/path/to/ai-compact-hot-swap
This installs to a temporary directory and is removed when the session ends. Perfect for evaluating before committing to a global / project install.
Manual install (legacy / dev)
Copy or symlink this directory into one of:
~/.pi/agent/extensions/ai-compact-hot-swap(available in every project), or<project>/.pi/extensions/ai-compact-hot-swap(project-local)
This bypasses the pi install machinery entirely. Useful when developing the extension locally — symlink so your edits are picked up on /reload.
Verify the install
After installing, start pi and look for the extension in the startup header, or run:
pi list # shows installed packages and their versions
pi config # toggle the extension on/off interactively
The command registers as /hot-swap-compact — type / in the editor to see it in the slash menu.
Uninstall
pi remove npm:@bramburn/ai-compact-hot-swap
For project-local installs add -l. For manual installs, just delete the copied / symlinked directory.
Usage
/hot-swap-compact [optional custom instructions]
Any trailing text is forwarded to ctx.compact({ customInstructions }) to steer the summarizer prompt.
Custom summarizer (optional)
By default, /hot-swap-compact is just a non-blocking wrapper around pi's normal compaction pipeline — which itself honors pi's built-in PI_SUMMARIZER_BASE_URL / PI_SUMMARIZER_MODEL / PI_SUMMARIZER_API_KEY env overrides. The extension does nothing extra.
If you want the summarization step to use a different model than the one running the session — a cheaper model, a local model for privacy, or a model with better summarization quality — the extension can register a separate OpenAI-compatible endpoint as a custom summarizer via the env vars below. This feature is fully opt-in.
Required env vars (all three must be set together)
| Env var | Meaning |
|---|---|
PI_HOTSWAP_SUMMARIZER_BASE_URL |
Base URL of the OpenAI-compatible endpoint (e.g. https://api.openai.com/v1, http://localhost:11434/v1 for Ollama, your private proxy URL) |
PI_HOTSWAP_SUMMARIZER_API_KEY |
Bearer token the endpoint expects. Use any non-empty string for endpoints that ignore auth (Ollama, most local servers). The literal string is passed through; the extension does not validate it. |
PI_HOTSWAP_SUMMARIZER_MODEL |
Model id the endpoint should use for summarization (e.g. gpt-4o-mini, llama3.1:8b, qwen2.5:14b) |
The three must be set as a group — see "Partial configuration" below for the exact behavior when one or two are missing.
Optional env var
| Env var | Meaning |
|---|---|
PI_HOTSWAP_SUMMARIZER_PROVIDER |
Internal id used to register the summarizer with pi's provider registry. Defaults to hotswap-summarizer. You only need to override this if the default id collides with another provider you've registered elsewhere — either via ~/.pi/agent/models.json or another extension. If you see a provider already registered error in the logs, set this to a unique value like bramburn-hotswap-summarizer to disambiguate. |
What happens at runtime
When all three required vars are set, the extension registers your endpoint with pi's provider/model registry the first time a compaction fires in the session, then intercepts every subsequent session_before_compact event:
- Register the provider (first compaction only). Calls
pi.registerProvider(providerId, { ... })with a single model entry pointing at your endpoint. A closure flag (providerRegistered) guards against re-registering on later compactions — without it, the second compaction in the same session would throw a duplicate-registration error. - Handle
session_before_compact. pi fires this event before every compaction in the session — manual/compact, threshold-based auto-compaction, context-overflow recovery, and our/hot-swap-compactall route through it. The extension:
- Builds a summarization prompt from the conversation history (including the previous summary, if any).
- Calls your endpoint via
ctx.modelRegistry.complete(...)withmaxTokens: 8192andcacheRetention: "none"(summaries aren't reused across sessions). - Returns
{ compaction: { summary, firstKeptEntryId, tokensBefore, usage } }to pi. This is the same shape pi's built-in summarizer returns, so pi applies the result to the session exactly as if it had summarized itself — the backlog is replaced by the summary, kept entries are preserved, and the session continues with the new context.
If the registered model can't be found, returns an empty summary, or throws, the extension emits a notification (Hot-swap summarizer model not found, Hot-swap summary was empty, or Hot-swap summarization failed: ...) and returns undefined. pi then falls back to its default summarization and the compaction still completes.
Partial configuration
If you set one or two of the required vars but leave the others unset (typo, half-exported shell, forgot to set PI_HOTSWAP_SUMMARIZER_API_KEY), the extension detects the partial set on startup and prints a warning to stderr naming the missing vars:
[hot-swap-compact] PI_HOTSWAP_SUMMARIZER_* override is incomplete
(missing: PI_HOTSWAP_SUMMARIZER_API_KEY). Falling back to the
session's current model for compaction.
The custom summarizer is disabled for the whole session — every compaction (including /hot-swap-compact) routes through pi's normal path using the session's current model. The contract is intentionally all-or-nothing: partial configurations are treated as misconfigurations, never as opt-ins for some-but-not-all compactions.
This mirrors pi's own PI_SUMMARIZER_* env vars (same three names, same "set all three or none" semantics) so the two override knobs feel consistent.
No env vars set
The extension is a no-op. /hot-swap-compact calls ctx.compact(...) exactly like pi's built-in /compact, except non-blocking.
Scope: this affects every compaction in the session
Because the handler is wired to session_before_compact (not just /hot-swap-compact), the custom summarizer handles all compactions in the session once the env vars are set:
/hot-swap-compact(this extension)- The built-in
/compactcommand - Threshold-based auto-compaction (pi's setting that compacts when context grows)
- Context-overflow recovery (when a request would exceed the model's window)
Unset the env vars and start a new session to restore pi's default summarization everywhere.
Model defaults
The registered model advertises contextWindow: 1_000_000 and maxTokens: 8192 to pi. These are budgeting hints for pi, not hard limits enforced against your endpoint:
maxTokenscaps the length of the summary your endpoint is allowed to return.contextWindowshould be at least as large as the longest conversation you expect to summarize in one shot. pi does not chunk the prompt for extension-provided summarizations — if your real endpoint has a smaller window (say 128k for Llama-3-class models), a very long session can overflow it and the call will fail. In that case, either pick a model with a larger native window or lower pi's auto-compact threshold so the conversation never grows that large.
Edge cases
- Compaction failure — notified via
ui.notifywith levelerror; queued/running state is cleared. - Extension reloaded mid-compaction — all state lives in the extension instance, so a reload loses the pending flag and the in-flight compaction's completion callback. The session is unaffected (compaction still completes server-side); only the notification is lost.
- No UI — notifications are skipped when running headless (
ctx.hasUIguard), except the partial-env warning which also goes toconsole.warn.
Updates
Update all packages
pi update --extensions # update packages + reconcile pinned git refs
pi update --all # update pi itself too + packages + git refs
Both commands walk your installed packages and pull the latest matching version from their source. Scoped / pinned installs are skipped by default — see below.
Update this package only
pi update npm:@bramburn/ai-compact-hot-swap
# …or the long form:
pi update --extension npm:@bramburn/ai-compact-hot-swap
Update to a specific version
Re-install with the version you want:
pi install npm:@bramburn/ai-compact-hot-swap@0.2.0
This rewrites the entry in your settings (~/.pi/agent/settings.json or <project>/.pi/settings.json) with the new pin.
Revert to an older version
Same as above — install the older version:
pi install npm:@bramburn/ai-compact-hot-swap@0.1.0
Pinned installs are not auto-updated
If you installed with an explicit version (@1.2.3 or @^1.2), pi update --extensions will not move the pin. This is intentional — versioned specs are treated as user commitments. To move a pinned package, run pi install again with the new version (or no version, to pick up the latest).
For automatic upgrades, install without a version: pi install npm:@bramburn/ai-compact-hot-swap (always tracks latest) or with a minor-range: npm:@bramburn/ai-compact-hot-swap@^0.1 (tracks the latest 0.x.x).
Refresh model catalogs only
pi update --models
This refreshes the model registry without touching package versions. Useful after a new model is published by your provider.
Contribution
The repo is at https://github.com/bramburn/ai-compact-hot-swap. Issues and PRs welcome.
Local development
Clone and install:
git clone https://github.com/bramburn/ai-compact-hot-swap.git
cd ai-compact-hot-swap
npm install
The repo pins @typescript/native-preview (the tsgo binary) as a devDependency so npm run typecheck works without any global toolchain.
Run the extension from your working copy without publishing:
# Either symlink into pi's extensions dir:
ln -s "$(pwd)" ~/.pi/agent/extensions/ai-compact-hot-swap
# …or run ad-hoc for one session:
pi -e "$(pwd)/index.ts"
Symlinking picks up live edits on the next /reload in pi; ad-hoc runs use the on-disk file directly.
Project structure
.
├── index.ts
├── package.json
├── tsconfig.json
├── .npmignore
├── README.md
└── LICENSE
Where:
index.ts— the extension; the only file shipped to npmpackage.json— npm metadata + thepi.extensionsmanifest that tells pi what to loadtsconfig.json— typecheck config (dev-only; not shipped).npmignore— whitelist for the npm tarballREADME.md— this fileLICENSE— MIT
The extension is a single TypeScript file. The npm tarball publishes exactly 4 files (LICENSE, README.md, index.ts, package.json). No build step — pi loads index.ts directly via Node's TypeScript stripping (≥ 22.19).
Code conventions
- Single default export —
export default function (pi: ExtensionAPI)registered by pi's extension loader. - No top-level side effects — keep all state inside the factory closure so each session gets its own instance.
- Strict TypeScript —
strict: true,verbatimModuleSyntax,erasableSyntaxOnly. The file must compile cleanly without emitting JS. - Use the
piSDK only via@earendil-works/pi-coding-agent— declared inpeerDependencies(not bundled). - Notifications — prefer
ctx.ui.notify(level, message)overconsole.logso headless runs stay quiet. Fall back toconsole.warnonly for warnings the user must see regardless of UI state.
Typecheck & smoke test
npm run typecheck # tsgo --noEmit -p tsconfig.json
The bundled tsconfig.json maps @earendil-works/pi-coding-agent to a local pi checkout for types — adjust the paths entry (or remove it and rely on the installed peer dep) if your layout differs.
End-to-end smoke test: install the published version into a throwaway project and confirm the extension loads:
mkdir /tmp/pi-smoke && cd /tmp/pi-smoke
npm init -y >/dev/null
npm install @bramburn/ai-compact-hot-swap@latest
pi -e ./node_modules/@bramburn/ai-compact-hot-swap/index.ts
In the session, type /hot-swap-compact and confirm the command appears in the slash menu.
Submitting changes
- Fork the repo.
- Create a topic branch:
git checkout -b fix/your-bugorfeat/your-feature. - Make your change. Run
npm run typecheckuntil clean. - Commit with a descriptive message. If your change is user-visible, mention it in the PR description.
- Open a PR against
main. CI (if you set it up) will re-run the typecheck.
Release process
Maintainer-only — the package is published from this repo by @bramburn.
Bump
versioninpackage.json(semver:patchfor fixes,minorfor new features,majorfor breaking changes).Update
README.mdif the user-facing surface changed (new env vars, new commands, behavior changes).Run
npm run typecheckto make sureprepublishOnlywill pass.Commit the version bump:
git commit -am "release: v0.x.y".git tag v0.x.y && git push --tags(optional — the tag isn't strictly needed since npm carries its own version, but it helps the GitHub release page).Publish:
npm publish --access publicnpm will prompt for an OTP if the npm account has 2FA enabled (recommended). Pass it directly to skip the interactive prompt:
npm publish --access public --otp=<code>Verify:
npm view @bramburn/ai-compact-hot-swap version curl -sH "Accept: application/json" https://registry.npmjs.org/@bramburn/ai-compact-hot-swap | headShould report
0.x.yand a JSON object (the CDN may return 404 to non-JSON Accept headers due to a Cloudflare quirk — use the explicitAccept: application/jsonheader).Create the GitHub release at https://github.com/bramburn/ai-compact-hot-swap/releases with the tag from step 5 and a short changelog.
License
MIT © 2026 bramburn