pi-context-engineer

Context governor for Pi Fabric and Pi Fovea: taint-gates data flow, budgets selection, compresses results, offloads payloads, and records context cost.

Packages

Package details

extensionskill

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.6.0
Published
Sep 7, 2026
Downloads
644/mo · 525/wk
Author
p_yan
License
MIT
Types
extension, skill
Size
1.4 MB
Dependencies
1 dependency · 4 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ],
  "skills": [
    "./skills"
  ],
  "providesTools": [
    "ce_exec",
    "ctx_read",
    "ctx_summarize",
    "ctx_remember",
    "ctx_recall",
    "ctx_forget",
    "ctx_delegate",
    "ctx_offload",
    "ctx_status"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-context-engineer

A context governor for Pi, Pi Fabric, and optionally Pi Fovea.

The roles are deliberately separate:

  • Fabric executes and orchestrates work without exposing intermediate values to Main.
  • Fovea discovers the most relevant repository regions.
  • Context Engineer decides what data is allowed to cross the boundary and records the cost.

Features

Static data-flow gate

fabric_exec programs are analyzed before execution. Tool-originated values are tracked through aliases, destructuring, object/array construction, method chains, callbacks, local helper functions, Promise.all, and function arguments.

The analyzer distinguishes:

  • RAW — direct or near-direct tool data
  • ENCODEDString(raw), JSON.stringify(raw), templates, and similar representations that still contain the payload
  • UNKNOWN — an untrusted helper received tainted data
  • PROJECTED — scalar fields, counts, keys, and field projections (only scalar projections are inherently bounded)
  • SELECTED — slices, filters, matches, and bounded Fovea results (only explicit bounds are safe)
  • AGGREGATED — reductions and scalar summaries
  • COMPRESSED — context summaries
  • OFFLOADED — disk handles

Scalar projections, some()/every()/includes(), one-item selectors such as find(), explicit slice(0, N), Fovea maxTokens, summaries, and offloads establish bounds. Numeric bounds survive aliases and local helper arguments, and common tool status fields such as ok, exitCode, and truncated are treated as scalars. map(), filter(), reduce(), Object.entries()/values(), trim(), and replace() can still retain the full source, so the analyzer marks them unbounded unless a later operation derives an explicit bound.

By default, an uncertain source-bearing return is classified as a warning but executes silently. The runtime boundary guard then keeps a small actual result or offloads a large one. Use ce_exec when you want the static diagnostic explicitly. This avoids blocking useful work because of a conservative static approximation. Set strict: true or blockUnboundedReturns: true for fail-closed preflight. Default preflight is advisory even for zero-source projections and estimated oversized returns. Fabric retains authoritative syntax/execution validation; only explicit strict/blocking settings turn CE diagnostics into pre-execution blocks. The number of internal calls is not the primary limit: a Fabric program may make many calls when its boundary result is controlled.

Runtime boundary guard

CE leaves all internal Fabric values intact: ordinary pi.* results, MCP results, captured Fovea/CE tools, and provider proxy details.result. Fabric owns its executor/transport limits. Explicit ctx_offload remains available when the program deliberately wants a handle.

At the model boundary CE budgets the sum of all text blocks, not just the first. Single-text handles store the exact text unchanged. Multi-text handles store { textBlocks: [{ index, text }, ...] }, preserving every original content-array index; use jsonPath: "$.textBlocks[0].text" or byte ranges to recover text. Media retain their positions, short independent notes get a bounded inline allowance, and rewritten slots share one recovery handle. A storage failure leaves the original result visible rather than discarding evidence.

Large final text results are offloaded; budgeted Fovea results that meet their requested size are left alone in auto mode. Artifact metadata alone cannot exempt an oversized payload. JSON previews prioritize short nested status/path/count fields over bulky siblings. Fabric's sectioned-YAML results retain small sections and source-line-labeled outline excerpts; previews are selections, not complete return values. summarize uses deterministic structural compression with the full original stored behind a recovery handle. Oversized errors also retain exact originals, not just selected diagnostics.

CE never rewrites grep patterns or other tool arguments. Fabric's catalog repairs handle argument/action naming near-misses; its entropy compiler optimizes tool surfaces. Neither requires CE to guess regex intent.

Recognized successful edit envelopes containing verbose diff/patch strings are compacted from 2 KB onward, even below the ordinary offload threshold. The acknowledgment stays visible and the complete original diff/patch remains retrievable. This only changes model-boundary text, never ordinary intermediate Pi values. Set compactEditResults: false to disable the lower edit threshold, or resultPolicy: "inline" to bypass successful-result rewriting altogether.

Runtime size advisories are disabled by default because bounded 4–8 KB results are usually intentional; set runtimeAdvisoryThreshold to opt in.

Fovea-aware selection

When Fovea is installed, use its captured tools inside Fabric:

const focus = await extensions.fovea_focus({
  query: "authentication",
  maxTokens: 500,
});

return extensions.ctx_summarize({
  text: focus,
  maxTokens: 300,
});

A Fovea call with maxTokens is classified as a budgeted SELECT operation. Context Engineer does not reproduce Fovea's code graph.

Context-effect registry (v0.5)

The analyzer consumes a small exported registry describing how calls affect context:

import { contextEffectFor } from "pi-context-engineer";

contextEffectFor("pi.read"); // { kind: "source" }
contextEffectFor("extensions.ctx_read"); // SELECT bounded by length bytes
contextEffectFor("extensions.ctx_summarize"); // COMPRESS bounded by maxTokens
contextEffectFor("extensions.ctx_offload"); // OFFLOAD

Unregistered calls under mcp.*, agents.*, workflow.*, state.*, memory.*, schema.*, mesh.*, council.*, rlm.*, fabric.*, and direct shell/web tools are conservatively classified as source effects; exact CE/Fovea entries override that fallback.

The registry remains descriptive, while the analyzer reports metrics.returnBound, metrics.returnProvenance, and an independent metrics.quantitativeDecision. Bounds distinguish exact values from proven upper bounds; the upper-bound algebra supports safe-integer +, -, *, Math.min, expression-level conditional joins, and Math.max when every operand is bounded. Conditional joins require both branches to be finite and use the larger branch bound; unsupported operations and control-flow assignment remain unknown. Quantitative policy defaults to 8192 bytes, 4000 tokens, and 8192 characters. Only bytes, tokens, and characters are directly comparable to those budgets; characters mean JavaScript UTF-16 code units (String.length), with no implicit byte/token conversion. Element and record bounds remain structural and are not used as context-size proofs. A within-budget proof additively clears only the legacy unbounded-return block; legacy-safe paths remain safe, while over-budget and not-comparable proofs remain blocked. Configure overrides in .pi/context-engineer.json with policy.maxBytes, policy.maxTokens, and policy.maxCharacters; each must be a non-negative safe integer no greater than 1,000,000,000. explainProgram(source) and formatProgramExplanation(...) expose the decision, budget, proven maximum, and result.

Addressable context store

Stored payloads include content hashes, provenance/source, content type, creation/access timestamps, estimated tokens, and expiry. Identical payloads deduplicate. ctx_read supports UTF-8 byte ranges, literal line queries, and focused JSON-path lookups such as $.results[0].name. Ranged results expose copyable offset / nextOffset; query mode formats at most 100 match windows by default (maxMatches, capped at 500), samples matchedLines, and preserves the exact totalMatches. The complete serialized result—not only its text field—is budgeted below the recursive-offload threshold. Handles expose structural previews (JSON keys/counts, or head/tail text/code) rather than dumping arbitrary payloads. At the model boundary, use ctx_read({ id, offset, length }); inside Fabric code, use extensions.ctx_read(...). Offload previews are compacted before their first model exposure. After exposure, CE leaves messages unchanged—including ctx_read output—so later calls preserve cacheable prefixes and the agent keeps its evidence. CE does not register a historical context rewrite hook. Pi's explicit or automatic session compaction remains responsible for shortening accumulated history; stored payloads remain re-readable subject to retention limits. By default, entries expire after one week and the store is capped at 500 MB; cleanup runs opportunistically during store activity.

Telemetry

The extension records sizes and strategies—not prompts or payloads—in .pi/context-store/context-events.jsonl. Metrics separate internalTokensProcessed, mainTokensPrevented, mainTokensInjected, and storeTokensWritten; the legacy savedTokens field aliases Main-context prevention for compatibility. Telemetry is scoped by extension runtime, host Pi session, or workspace lifetime; ctx_status defaults to a compact report for the selected scope (session by default), without repeated sibling scopes or per-event/per-strategy diagnostics. A fresh runtime can still request session/lifetime activity from earlier runtimes. Use ctx_status({ scope: "lifetime", detail: "full" }) for all three scopes and diagnostic breakdowns. Existing consumers of .runtime, .session, or .lifetime should request detail: "full"; compact callers use .summary.

Interactive commands:

/ce status          current-session observed savings
/ce status --all   all recorded workspace sessions
/ce trace          recent policy events
/ce explain        current architecture and policy
/ce settings       effective project configuration
/ce clear          clear telemetry

The numbers are approximate context-token estimates, not billing reconciliation or proof of agent effectiveness. Internal helper calls do not count as Main exposure; reading an existing handle does not count the same source as prevented again. Use actual model Usage (including nested calls and cache/cost fields) to evaluate billed savings. ctx_status exposes childCalls, observed childUsage, and childUsageComplete; incomplete/missing reports are not zero-cost work. Native standalone calls attach observed Usage on success and failure. Current Fabric capture omits nested tool Usage from native totals, so childUsageOutsideParent reports the additional observed usage separately; add it exactly once, not all childUsage again. The effectiveness harness performs this accounting from isolated-workspace telemetry.

Fabric example

const result = await pi.grep({ pattern: "TODO", path: "src" });

return (await extensions.ctx_summarize({
  text: result,
  mode: "code",
  maxTokens: 300,
})).text; // one model-facing copy; structured details stay exact internally

Summarization modes

ctx_summarize accepts an inline text value or an offloaded id:

  • structural (default) — free deterministic JSON/text extraction.
  • code — free deterministic code-aware extraction of imports, signatures, and head/tail windows.
  • model — isolated no-tools semantic summarization; large inputs are chunked and reduced hierarchically.

Unknown modes reject at the registered Pi tool boundary. maxTokens is an approximate UTF-8 return budget, clamped to 64–4000. Model mode also accepts maxInputTokens (default 32000 approximate source tokens per child), maxChunks (default 16, maximum 64), maxCalls (default twice maxChunks, maximum 128), and a whole-operation timeoutSeconds (default 90, range 10–110). Binary reduction guarantees progress; no redundant final model call is made. Model-source data, including inline input, has an exact recovery handle. strategy: "direct" explicitly selects a prefix and reports complete, coveredBytes, totalBytes, and nextOffset; it is never an implicit fallback. Prefer deterministic code or structural unless semantic compression is needed.

Offloaded results include a copyable ctx_read({ id, offset, length }) example at the model boundary; inside Fabric code use extensions.ctx_read(...). Use query for literal matches or jsonPath for focused JSON values.

For large data, write it off-window instead of returning it directly:

const text = await pi.read({ path: "large.json" });

return (await extensions.ctx_offload({
  key: "large-json",
  source: "read",
  data: text,
})).text;

Use the returned handle with ctx_read to retrieve a range, literal matches, or a focused JSON path later.

Tools

The registered tools are callable directly by the model and inside Fabric through extensions.*:

  • ctx_read — select a range, literal matches, or a JSON path from a stored handle
  • ctx_summarize — free structural/code compression or isolated no-tools model compression
  • ctx_remember / ctx_recall / ctx_forget — persistent project facts with bounded recall, named upserts, and deletion
  • ctx_delegate — isolated child Pi with maxTokens, maxTurns (default 8, range 1–32), a nested-safe deadline, bounded process-output buffers, and parent cancellation. Supported adapters receive real generation caps. Codex has no supported server output-cap field, so it uses an explicitly approximate streaming guard instead; generationBudget reports this limitation and billed output may overshoot or be unknown on cancellation. childOutputTruncated: true exposes provider-length truncation. Oversized returned text has an exact recovery handle. Pure summaries disable tools/project context and use a minimal prompt. Timeout/abort terminates process trees, escalating to SIGKILL on POSIX.
  • ctx_offload — manual Write operation
  • ctx_status — current policy thresholds and observed savings
  • ce_exec — explicit static preflight (PASS, WARN, or BLOCK)

For Fabric-native orchestration and recursive decomposition, call Fabric's agents.* APIs directly when available.

Prefer compact discovery before requesting complete schemas. For example, select only matching tool names/refs inside Fabric rather than returning the entire catalog:

const catalog = await tools.list({});
return catalog.filter(t => /blender/i.test(t.name + " " + t.description))
  .slice(0, 10).map(t => ({ name: t.name, ref: t.ref }));

Then describe only the tools you will call. Context Engineer does not override Fabric's discovery API.

Configuration

Create .pi/context-engineer.json in a project when needed:

{
  "enabled": true,
  "strict": false,
  "blockUnboundedReturns": false,
  "maxReturnTokens": 4000,
  "readOffloadThreshold": 16384,
  "resultPolicy": "auto",
  "errorCompactionThreshold": 4096,
  "errorCompactionPreviewBytes": 4096,
  "offloadPreviewBytes": 2048,
  "runtimeAdvisoryThreshold": 0,
  "compactEditResults": true,
  "notifyOnStart": false,
  "storeMaxBytes": 500000000,
  "storeTtlMs": 604800000
}

Config reference

All options live in .pi/context-engineer.json (field names are exact CeConfig keys). Byte budgets are UTF-8 bytes; token estimates are ~bytes/4 and ASCII-biased (see note below).

Option Type Default Notes
enabled boolean true Set false to disable enforcement hooks.
strict boolean false Fail closed on statically unbounded source returns and soft warnings.
blockUnboundedReturns boolean false Block statically unbounded source returns before execution; strict: true implies blocking.
maxReturnTokens number 4000 Static advisory budget only; enforced by explicit strict/blocking modes. Maps to ~16 KB at ~4 chars/token (ASCII-biased).
maxUnprocessedToolCalls number legacy Accepted for older configs; data-flow reduction and observed context cost are primary.
quantitativePolicy object {maxBytes: 8192, maxTokens: 4000, maxCharacters: 8192} Single policy source for static preflight and runtime budgets.
policy object alias User-facing alias for quantitativePolicy; when both are set they are unified with policy as source.
quantitativePolicy.maxBytes / policy.maxBytes number (bytes) 8192 Static preflight budget; also unifies the runtime offload budget when readOffloadThreshold is unset (see resolveRuntimeByteBudget). Max 1,000,000,000.
quantitativePolicy.maxTokens / policy.maxTokens number 4000 Token budget for token-unit bounds. Max 1,000,000,000.
quantitativePolicy.maxCharacters / policy.maxCharacters number 8192 Character budget (UTF-16 code units). Max 1,000,000,000.
readOffloadThreshold number (bytes) 16384 Runtime auto-offload budget; wins over policy.maxBytes when set. Clamped to 256–1,000,000,000; clamps surface via ctx_status warnings.
resultPolicy "auto" | "inline" | "offload" | "summarize" "auto" auto previews/offloads by threshold; inline preserves all results (including errors); offload forces handles for successful results; summarize uses threshold-based deterministic structural compression with exact recovery (no model call).
errorCompactionThreshold number (bytes) 4096 Model-boundary errors at/above this size are compacted (min 256).
errorCompactionPreviewBytes number (bytes) 4096 Visible budget retained by a compacted error, including the recovery recipe (256–64,000).
offloadPreviewBytes number (bytes) 2048 Structural first-use handle preview budget (256–4,096).
runtimeAdvisoryThreshold number (bytes) 0 Fabric boundary results at/above this size get a one-line advisory; 0 disables nudges.
compactEditResults boolean true Compact recognized verbose 2 KB+ successful edit envelopes (diff/patch) into an acknowledgment + recovery handle. Set false to keep them inline.
notifyOnStart boolean false Opt into the session-start toast.
storeMaxBytes number (bytes) 500000000 Transient store cap; cannot exceed 500 MB. Newest write is never evicted; over-budget writes evict oldest first (LRU) after TTL sweep.
storeTtlMs number (ms) 604800000 Transient entry TTL (1 week). Remembered facts use a separate persistent namespace (no TTL, 5 MB budget).
compactStaleResults boolean deprecated, ignored Always effective false; already-exposed messages stay prefix-stable. Accepted so old configs still load.
nestedResultThreshold number deprecated, ignored Always effective null; internal Fabric provider values are never rewritten. Accepted so old configs still load.

Bad JSON in .pi/context-engineer.json does not throw: defaults load and a warning surfaces via ctx_status. Out-of-range numeric budgets are clamped (floored, min/max) with a ctx_status warning instead of failing startup.

Bytes vs tokens

Prefer byte budgets. Static preflight defaults to 8 KB (quantitativePolicy.maxBytes) while the runtime auto-offload default is 16 KB (readOffloadThreshold / DEFAULT_RUNTIME_OFFLOAD_BYTES) because static estimates are conservative upper bounds and runtime measures actual UTF-8 bytes. Token equivalents are heuristic: ~4 chars/token, so 16 KB ≈ 4,000 tokens for ASCII. Multibyte UTF-8 inflates bytes faster than the heuristic implies, and boundary messages report bytes with the ASCII-biased token estimate in parentheses. Invalid policy objects are ignored with a ctx_status warning; per-budget maximum is 1,000,000,000.

Selection and compression notes

  • ctx_read({ query }) is literal and case-sensitive by default. Set regex: true for a per-line JavaScript RegExp and/or ignoreCase: true. Invalid regex and storage failures reject at the Pi tool boundary with structured error diagnostics; internal handlers use { error, code, isError: true }. Storage uses explicit ok status, so payloads and remembered facts beginning Error: remain valid evidence. Regex/ignore-case scans are bounded and report scan truncation.
  • Hierarchical inputs exceeding maxChunks fail before model calls, with summary_input_budget_exceeded and an exact recovery handle. Narrow the input or explicitly raise the budget; no first-chunk-only summary is substituted. Stored input reads stop after enough chunks to detect overflow. Structural/code stored summaries inspect a bounded 512 KB prefix and report truncation. Summaries are lossy navigation aids; recover original evidence when exact details matter.

Project configuration is re-read when the file's modification time changes, so tuning does not require an extension reload. /ce settings shows effective values, including defaults.

  • strict or blockUnboundedReturns restores fail-closed static enforcement.
  • resultPolicy is auto (threshold-based previews), inline (never rewrite boundary results, including errors), offload (force addressable handles for successful results), or summarize (threshold-based deterministic structural summaries with recovery handles; no child model call). ctx_read remains exempt from successful-result offloading to prevent recursive handles.
  • errorCompactionThreshold and errorCompactionPreviewBytes bound oversized model-facing errors, including the recovery recipe in the visible budget. Full original diagnostics are stored first; nested Fabric errors remain untouched.
  • nestedResultThreshold is deprecated and ignored (effective value null): Fabric owns its internal transport limits. CE never substitutes handles for internal provider values.
  • runtimeAdvisoryThreshold: 0 disables repetitive size nudges; use a positive byte threshold to opt in.
  • compactStaleResults is deprecated and ignored (effective value false, even in older configs that set it to true). CE never shortens already-exposed messages; use Pi session compaction to reduce accumulated history.
  • compactEditResults (default true) additionally offloads recognized successful 2 KB+ edit envelopes containing a verbose diff/patch. Set it to false to keep those results inline below the normal threshold. resultPolicy: "inline" still bypasses all successful-result rewriting.
  • notifyOnStart controls the session-start toast, which is off by default.

Transient context-store entries default to one week (604800000 ms) and 500 MB (500000000 bytes). Remembered facts use a separate persistent store with no TTL and a 5 MB budget. Configured transient storage budgets cannot exceed the 500 MB cap. Payloads use a metadata index plus content-addressed private blobs; writes are atomic and cleanup removes expired, dangling, and over-budget entries.

Install in Pi

After installing from npm, Pi loads the extension and skill from the package manifest:

pi install npm:pi-context-engineer

Try it for one run without saving it to settings:

pi -e npm:pi-context-engineer

The package can also be loaded from a checkout:

pi --extension /path/to/pi-context-engineer/src/index.ts

Automated releases

Publishing is tag-driven through .github/workflows/publish.yml and uses npm trusted publishing (OIDC), so no npm token is stored in GitHub. Configure the repository once in the npm package settings under Trusted Publishers:

  • Provider: GitHub Actions
  • Owner: p-yan-6908
  • Repository: pi-context-engineer
  • Workflow: publish.yml

Then release a new version with:

npm version patch
git push origin main --follow-tags

The workflow verifies the vX.Y.Z tag matches package.json, runs the full test suite, and publishes to npm.

Verification

npm ci
npm test

The test suite includes more than 50 static data-flow cases plus live hook probes covering:

  • aliases, destructuring, scalar result fields, numeric bounds passed through local helpers, and paging metadata
  • String/JSON.stringify false reductions
  • unknown helper arguments
  • callback projections and identity maps
  • Promise.all
  • bounded and unbounded Fovea calls
  • nested Fabric provider proxies
  • intermediate-result preservation, including large MCP/Fovea/CE provider proxies under every result policy
  • automatic final offload, prefix-stable previews across retries and branches, legacy config compatibility, serialized ctx_read envelope caps, and bounded query work
  • runtime-first versus strict preflight, bounded delegation, and deterministic ctx_summarize modes
  • content deduplication, UTF-8 ranges, unchanged grep inputs (including valid ripgrep-only regex syntax), and lossless multi-text/error recovery
  • generated adversarial transformations for aliases, destructuring, callbacks, async wrappers, computed access, loops, and Promise aggregates
  • the explicit src/context-effects.ts registry used by the analyzer for source/select/compress/offload policy metadata, including current providers and PowerShell/process tools
  • JSON-path handle selection, structural previews, oversized error compaction, explicit result policies, and cross-runtime telemetry scopes

Run the generated adversarial suite directly with the normal npm test command. It fails closed when an unsafe transformation is classified as safe.

Benchmarking

The deterministic plumbing harness compares raw payload exposure with CE addressable storage, bounded selection, and hierarchical summarization. Its known-marker validators and mocked models test mechanics, not unchanged agent effectiveness:

npm run bench

It covers huge grep/JSON/build-log payloads, repeated reads, parallel provider-like results, Fovea-like source selection, large summaries, and nested-agent handoffs. The default one-warmup/three-iteration run reports median and p95 wall time to .tmp/context-benchmark.json; bench/result.schema.json defines retained release results with sourceCommit/runtime provenance and per-iteration samples. The v0.5.0 retained workload result is bench/results/v0.5.0.json; the frozen v0.4.0 result remains bench/results/v0.4.0.json for comparable baseline evidence.

The separate v0.5 quantitative-policy result is bench/results/v0.5.0-quantitative-policy.json; it emphasizes maintained legacy parity plus intentional symbolic-cap acceptance, not a misleading performance comparison. Metrics include Main-context exposure (not total token usage), Main input/output/injected tokens, child-model tokens, wall time, disk bytes, selected bytes retrieved, task correctness, quality-adjusted savings, and context efficiency.

Opt-in real runtime smoke tests launch the local pi CLI with CE and Fabric extensions. They require a configured model and are intentionally separate from CI's deterministic suite:

CE_RUN_E2E=1 PI_MODEL=openai-codex/gpt-5.6-luna npm run bench:e2e

Paired agent-effectiveness evaluation

Unlike the plumbing suite, this opt-in harness runs paired CE-on/off agents on isolated copies of multi-file tasks with hidden validators, late evidence, and stale-handle failures. It uses the shipped automatic offload policy, does not supply a retrieval needle, and reports task outcomes, actual observed Usage/cache/cost, latency, recovery calls, and retries. Unknown measurements remain explicitly partial/unavailable. A small fixture suite does not establish general non-inferiority.

npm run verify:effectiveness  # offline fixture/scoring/accounting tests; also in npm test
CE_RUN_EFFECTIVENESS=1 PI_MODEL=openai-codex/gpt-5.6-luna npm run bench:effectiveness

Real runs require explicit opt-in and a configured model; they can incur provider usage. Reports default to .tmp/context-effectiveness.json. See bench/README.md for options and limitations.

License

MIT