pi-context-engineer
Keeps large Pi and Fabric tool results out of model context: offloads them to a local store behind a readable handle, compacts oversized errors and edit diffs, and records what it saved.
Package details
Install pi-context-engineer from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-context-engineer- Package
pi-context-engineer- Version
0.9.0- Published
- Oct 27, 2026
- Downloads
- 800/mo · 775/wk
- Author
- p_yan
- License
- MIT
- Types
- extension, skill
- Size
- 346 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"image": "https://raw.githubusercontent.com/p-yan-6908/pi-context-engineer/main/media/cover.svg",
"skills": [
"./skills"
],
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-context-engineer
A Pi extension that keeps large tool results out of the model's context. When a result reaching the model is over 16 KB, Context Engineer (CE) stores the full text in a local store and shows the model a short preview and a handle instead. ctx_read reads any part of it back on demand.
It works with ordinary Pi tools and with Pi Fabric's fabric_exec, and leaves Pi Fovea results alone when they fit the budget the call asked for.
What it does
- Offloads large results. CE measures the sum of a result's text blocks. Over the threshold, the exact text is stored and replaced by a notice with copyable
ctx_readcalls and a preview that works as an index: JSON keys and short fields; each Fabric section with its first lines; an outline of headed output (file separators, markdown headings,--- name ---markers, grep output by file) with each part's byte offset; or the first and last lines with the byte ranges they cover. The suggested range read starts where the preview stops. Images keep their positions. A replacement must save at leastminimumSavingsBytes(256 by default); otherwise, or if the store fails, the original stays visible. Notices offer one payload-specific read; repeated handles omit their preview. - Compacts oversized errors. An error result of 4 KB or more keeps its most useful diagnostic lines inline, plus a handle to the complete output.
- Compacts verbose edit results. A successful edit whose result carries a diff or patch of 2 KB or more keeps its acknowledgment inline; the full diff stays readable.
- Completes Fabric-truncated output when possible. Fabric caps a
fabric_execresult at 50,000 characters and saves the full text to a temporary file that it soon deletes. CE stores the full text when that artifact is available and valid. Otherwise it stores the visible fragment and explicitly warns that the full output was not recovered. - Leaves intermediate values alone. Inside a Fabric program, tool results are program data. CE uses Pi's explicit
parentToolCallIdlineage for nativeexecuteToolcalls, plus legacy Fabric IDs. Concurrent unrelated calls remain model-boundary results. - Optionally budgets a whole turn.
turnBudgetBytesis a soft aggregate limit for newly exposed text, not a rewrite of history. Later results can be offloaded even below the per-result threshold. Tiny results, exact-recovery notices, unavailable storage/read paths and self-capped helpers can exceed the soft limit; nothing is discarded to force it. - Keeps exposed messages stable. CE rewrites a result once, before the model first sees it, and never edits earlier messages, so provider prompt caches stay valid. Pi's session compaction handles accumulated history.
- Records what it did. Sizes and strategies, never payloads, go to
.pi/context-store/context-events.jsonl.
Reading a handle
At the model boundary call ctx_read; inside a Fabric program call extensions.ctx_read. Pick the mode that fits the payload:
| Mode | Parameters | Use for |
|---|---|---|
| Byte range | offset, length |
Paging through text. Results include nextOffset. |
| Line search | query (a string, or an array of terms to match any of), optional regex, ignoreCase, contextLines, maxMatches, cursor |
Finding lines. Literal and case-sensitive by default. |
| Section | section, optional offset/length |
One named part of a Fabric sectioned result; ambiguous boundaries fall back to query or byte ranges. |
| JSON path | jsonPath, for example $.results[0].name |
One value from a JSON payload. No wildcards or slices. |
Query context windows are merged: each source line appears once, even with adjacent matches. maxMatches limits matching lines per page; totalMatches still counts the scanned payload. Continue with cursor: result.details.result.nextCursor and the same handle, query, regex, ignoreCase and contextLines. Page size may change. Query cursors also page oversized UTF-8 lines without losing their remainder. Regex totals cover only the prefix when scanTruncated is true.
The offload notice only offers a mode the payload supports. A query that finds nothing says so first, then whether lines match ignoring case, whether the query looks like a regex, and how many lines each word of a phrase matches, with the array query that would find them. Literal queries scan the whole payload; regex queries scan the first 512 KB.
At the model boundary a ctx_read result shows the selected text under a one-line header that locates it, and stays below the offload threshold so reading a handle never produces another handle. Inside a Fabric program a read is program data, so that cap does not apply: a read returns up to 512 KB, with the selected text in .text and its metadata (totalBytes, nextOffset, totalMatches, ...) in .details.result. Program .text contains no paging note: check nextOffset and page before treating it as the whole payload. Filter with code and return only what you need. If a program that reads handles still returns too much, the notice says to filter before returning.
A multi-block result is stored as { "textBlocks": [{ "index": 0, "text": "..." }, ...] }, keeping each block's original position; read one with jsonPath: "$.textBlocks[0].text".
Tools
All three are callable by the model directly and inside Fabric through extensions.*:
ctx_read— read part of a stored payload (see above).ctx_summarize— deterministic summary of inlinetextor a storedid.mode: "structural"(default) extracts JSON shape or head and tail lines;mode: "code"extracts imports and signatures.maxTokens(64–4000) bounds the result. A stored payload is summarized from its first 512 KB.ctx_offload— store a payload on purpose and get a handle back:{ key, source, data }.
// Inside fabric_exec: keep a large file out of context, then read what you need.
const text = await pi.read({ path: "large.json" });
const handle = await extensions.ctx_offload({ key: "large-json", source: "read", data: text });
return (await extensions.ctx_read({ id: handle.details.id, jsonPath: "$.items[0]" })).text;
// Inside fabric_exec: count matching lines across the whole stored payload.
const matches = await extensions.ctx_read({ id: "<handle>", query: "FAIL", contextLines: 0, maxMatches: 1 });
return matches.details.result.totalMatches;
Opt-in envelope projection
Return .text when that is all you need. To retain read metadata in a Fabric return without repeating the envelope:
const r = await extensions.ctx_read({ id: "<handle>", query: "FAIL" });
return { text: r.text, metadata: r.details.result };
Pi-loaded extensions can import projectToolResult from pi-context-engineer. The package entry point is TypeScript, so a standalone Node SDK host needs a TypeScript-capable loader; a plain Node import is not supported. The helper removes exact duplicate text and transport provenance from an invocation envelope, preserving metadata, media, errors and differing content. It never mutates its input and is never applied automatically to program data.
Fabric child agents
When CE is enabled, each extension-enabled Fabric Pi child that loads CE adds ctx_read to its own tool allowlist before Fabric initializes, at every depth, so the child can read back what CE offloaded in it. Like Fabric's own agents.childQuestionTools grant, this is skipped for an explicit empty tools list and for +name/-name lists, and a malformed allowlist fails closed. Where no read path exists (a withheld allowlist, or a Pi --tools list without ctx_read), CE keeps the result inline rather than return a handle nothing can read.
Telemetry and commands
/ce status savings in the current session
/ce status --all savings across every recorded session in the workspace
/ce trace recent events
/ce settings effective configuration and warnings
/ce clear clear telemetry
/ce status also reports offload follow-up: how many offloaded handles were later read with ctx_read or ctx_summarize, and how many offloaded calls were run again anyway. A repeat suggests the notice did not make the handle usable and the same output was paid for twice. It also counts ctx_read calls by mode and how many ran inside programs: range reads, with how many started at offset 0 (usually re-reading what a preview showed); queries, with how many matched nothing; sections; and JSON paths. Query text is never recorded.
The figures are estimates at about 4 bytes per token, not billing data. mainTokensPrevented / savedTokens remain gross prevention. readbackTokens counts model-visible retrievals and final outputs derived from boundary-offloaded handles, not internal program reads. netTokensSaved subtracts those costs and can be negative; an offloaded readback is not credited as a second net saving. Provenance uses SHA-256 handle fingerprints, including across recorded sessions. Older telemetry files may contain input-derived handle fragments from earlier versions; /ce clear removes those records if desired. These estimates do not include schema, skill, tool-call or repeated-history costs; use the task benchmark below for provider-reported end-to-end input.
Configuration
Defaults fit most projects. To change them, create .pi/context-engineer.json:
{
"enabled": true,
"readOffloadThreshold": 16384,
"turnBudgetBytes": 0,
"minimumSavingsBytes": 256,
"errorCompactionThreshold": 4096,
"errorCompactionPreviewBytes": 4096,
"offloadPreviewBytes": 2048,
"compactEditResults": true,
"notifyOnStart": false,
"storeMaxBytes": 500000000,
"storeTtlMs": 604800000
}
| Option | Default | Meaning |
|---|---|---|
enabled |
true |
false turns off result rewriting. |
readOffloadThreshold |
16384 |
UTF-8 bytes from which a result is considered for offload (256–1,000,000,000). |
turnBudgetBytes |
0 |
Soft aggregate budget for new model-visible text per turn; 0 disables it. Resets at turn_start, never edits exposed messages. |
minimumSavingsBytes |
256 |
Minimum byte reduction required for automatic replacement (1–1,000,000,000); replacements never expand results. |
errorCompactionThreshold |
4096 |
UTF-8 bytes from which an error result is compacted (minimum 256). |
errorCompactionPreviewBytes |
4096 |
Visible budget of a compacted error, including its recovery call (256–64,000). |
offloadPreviewBytes |
2048 |
Preview budget in an offload notice (256–4,096). |
compactEditResults |
true |
Compact verbose edit diffs from 2 KB. |
notifyOnStart |
false |
Show a notification when a session starts. |
storeMaxBytes |
500000000 |
Store size cap; cannot exceed 500 MB. The oldest entries are evicted first, never the newest. |
storeTtlMs |
604800000 |
How long stored payloads live (one week). |
The file is re-read when it changes. Invalid JSON, out-of-range numbers and settings that earlier versions read (strict, policy, resultPolicy and others) never stop the extension: CE uses the defaults and lists a warning in /ce settings.
Budgets are UTF-8 bytes. Token figures assume about 4 bytes per token, which holds for ASCII; multibyte text uses more bytes per token.
Store
Payloads live in .pi/context-store in the workspace: a metadata index plus private content-addressed blobs, written atomically. Identical payloads are stored once. Cleanup removes expired, dangling and over-budget entries as the store is used.
Install in Pi
pi install npm:pi-context-engineer # install
pi -e npm:pi-context-engineer # try it for one run
pi --extension /path/to/pi-context-engineer/src/index.ts # from a checkout
The package also ships a skill that teaches the model how to read handles.
Verification
Development tests use Pi 1.4 and require Node 22.19+ (CI uses Node 24). Host peer ranges remain unrestricted for compatibility with older Pi installations.
npm ci
npm test
npm test builds the extension and runs deterministic, offline hook probes: offload of single and multi-block results, preview outlines and byte ranges, prefix stability, intermediate Fabric values, every ctx_read mode with its boundary and program budgets, query hints, summaries, error and edit compaction, the Fabric child grant, Pi's native tool error contract, and telemetry. New efficiency regressions exercise real Pi native executeTool with a deterministic faux provider, merged query cursors, negative net savings, compact notices, projection and turn budgets. No network model calls occur in npm test.
An opt-in smoke test launches the local pi CLI with CE and Fabric in a throwaway workspace. It needs a configured model and can incur provider usage:
CE_RUN_E2E=1 PI_MODEL=<provider>/<model> npm run e2e
Task-level CE on/off benchmark
CE_RUN_BENCHMARK=1 PI_MODEL=<provider>/<model> npm run benchmark
This opt-in command incurs provider usage. It runs lookup and counting tasks with identical paired fixtures, alternates CE-on/off order, and retains failures. It validates both final-answer quality and the required initial read. .tmp/ce-benchmark.json reports total provider input (uncached + cache reads + cache writes), output, model requests, model-issued tool calls, recorded nested calls, recovery calls, latency and reported cost estimates. A run is measured only when every assistant request has complete input, cache and output usage; missing or malformed counters make usage incomplete. Arm-level input and cost totals are null when the corresponding coverage is incomplete; per-run totals may be partial. Schemas, notices, recovery turns and repeated history are included in provider input, not added again as estimates.
By default the CE arm includes its full skill as cold-start system instructions. Set CE_BENCH_SKILL=0 for warm/instruction-free comparison, CE_BENCH_ROUNDS=3 for repeats, or CE_BENCH_OUTPUT for another report path. This is a small benchmark, not a universal effectiveness claim; compare quality before savings and inspect cache/order effects.
Both smoke tests and benchmarks resolve the optional local Fabric peer automatically. For an installation elsewhere, set PI_FABRIC_EXTENSION=/path/to/pi-fabric/dist/extension-bootstrap.js. Without the explicit opt-in environment flag, benchmark and smoke commands make no model calls.
Releases
Pushing a vX.Y.Z tag runs .github/workflows/publish.yml, which checks the tag against package.json, runs the tests, and publishes with npm trusted publishing (no stored npm token). The npm package's Trusted Publishers entry must name GitHub Actions, p-yan-6908/pi-context-engineer, and publish.yml.
npm version minor
git push origin main
# Wait for CI to pass, then push the new version tag.
git push origin vX.Y.Z
License
MIT