Skip to content

Integrate pi-lens' post-edit verification loop into billion-context-pi (native, cache-safe) #374

Description

@Juna9969

Summary

billion-context-pi (bcp) is the sole context manager in a session — it intercepts the context event and runs an 8-stage pipeline (assign refs → sync blocks → prune → filter → hide calls → recommend → nudge → emergency truncate). pi-lens is a post-edit verification engine — after every write/edit it runs LSP diagnostics, lint, format, autofix, structural rules, and security scans, and feeds findings back to the agent.

This issue proposes integrating pi-lens' verification loop into bcp as a native, config-gated engine (checks.*), so one extension owns both token economics and edit correctness. The integration is not a copy-paste: the two extensions conflict in seven concrete places. Each conflict and its resolution is detailed below. The guiding rule for every resolution: the check loop stays, the delivery mechanisms that damage the provider prefix cache are replaced by bcp-native primitives (same-turn tool-result attachment, ACP refs, pull tool, existing nudge slot).

Why merge pi-lens into bcp

The benefit: token savings compound, error costs compound faster

A compression engine that lets broken code through is a false economy. Concrete numbers from the same corpus behind the conflict attributions below (2105-turn, multi-provider):

  • A type error that survives the editing turn costs 2–5 extra agent turns to discover, diagnose, and fix (test failure → read traceback → re-read file → fix → re-verify). At ~10–30K tokens per agentic turn, that is 50–150K wasted tokens per missed defect — versus ~0.5–2K tokens for a same-turn diagnostic summary.
  • The failure mode gets worse precisely where bcp shines: long sessions. The deeper the conversation, the more context must be re-paid for every wasted turn, and the more likely an unnoticed early error is to be carried into compressed summaries — then the agent keeps building on a corrupted premise with no record of when it went wrong.
  • pi-lens' check loop attacks exactly this: typecheck/lint/format/security-scan after every write/edit, results back same-turn, fixed on the spot. Defect cost drops from "turns later, at full context price" to "this turn, at summary price".

Why bcp is the right host (not pi-lens alone, not coexistence)

  • Single-handler architecture: bcp is designed to be the only context handler; a post-bcp handler can rewrite what bcp already decided. Checks findings are context — routing them through bcp's own primitives (D1–D3) is the only architecture that keeps bcp's guarantees intact.
  • The compression pipeline needs the checks data more than the checks need compressing: severity-tiered findings (🔴/🔑/advisory) with honest labels are short-lived working-set data — ideal absorb/protect candidates. Living outside bcp, lens' findings are just more unmanaged transcript; living inside, they become first-class, ref-addressable, auto-compressible citizens.
  • One tool surface: no duplicated tool schemas per request, one config system, one honest-labeling discipline for both context state and code health.

What bcp gets (revenue)

  1. Post-edit verification loop (the core claim — currently missing entirely).
  2. Defect-cost reduction as measured by the A/B protocol below (tokens billed, turns-to-green, residual errors).
  3. A defensible answer to the recurring community question "why does my session spend more with checks extensions installed" — because bcp will ship the loop without the cache-hostile delivery.

What the merged engine avoids (cost avoided by not porting lens as-is)

  • Ephemeral injection → tail divergence every turn (lens' own docs concede: PI_LENS_NO_CONTEXT_INJECTION=1 exists "for cache-sensitive sessions").
  • Lazy tool activation → whole-prefix invalidation from token 0 ("a transposition is a changed prefix, i.e. a cache miss" — lens' tool-set-policy comment).
  • Full-content append → up to 2 MiB/file of permanent transcript bloat.

Why the two systems want to merge

  • bcp's blind spot: it conserves tokens but verifies nothing. After an edit, nothing tells the agent whether the file still typechecks or leaks a secret. Breakage surfaces turns later (failing tests), after the context for those turns has already been paid for. An immediate loop shortens the feedback cycle — the same resource bcp exists to conserve.
  • pi-lens' delivery debt: community feedback blames pi-lens for inflated token spend via cache invalidation. Mechanism-level attribution (cross-checked against pi-lens' own docs, which ship PI_LENS_NO_CONTEXT_INJECTION=1 "for cache-sensitive sessions" and warn that "a transposition is a changed prefix, i.e. a cache miss"): the damage comes from delivery (ephemeral injected user messages, mid-session tool-list mutation, full-file content appended to persisted tool results), not from running checks. All three delivery patterns are replaceable; the check loop itself is cache-neutral.

Conflict map

# Conflict Root cause Resolution
C1 Both extensions hook the context event; bcp only tolerates handlers running before it pi-lens delivers findings as ephemeral user-role message injections through its own context handler; extension load order is not contractually defined, so a post-bcp handler can rewrite what bcp already decided Single handler. All pi-lens model-facing delivery is re-routed (see D1–D3). Context injection is deleted. bcp remains the only context handler
C2 Same-moment tool-result race: pi-lens autofix appends findings into the write tool result; bcp absorb compresses large tool results at arrival (builds/logs/greps are force-absorbed) Both act on the tool result in the same tick, with no defined order Fixed ordering contract: checks complete first → summary attaches → absorb evaluates the annotated result. The attachment region (bounded summary) is exempt from force-absorb in the current turn; detail goes to the ref store and is compressed later through the normal ref flow
C3 pi-lens persists the full authoritative post-fix file content in the tool result (up to 2 MiB/file, shared budget for multi-file bash writes) — permanent transcript bloat, anathema to a compression engine AUTHORITATIVE_CONTENT optimizes for "no re-read needed" at any token cost Replace with integrity pointer: content hash + "file was modified — re-read" notice + ref-store detail. The degraded over-cap path pi-lens already ships becomes the only path; the model re-reads on demand (cheap with warm read caching)
C4 pi-lens' lazy tool activation (activate-tools) mutates the tool block mid-session → prefix invalidated from token 0; bcp is explicitly prefix-cache-disciplined (byte-stable system header via systemToUser) Situational tools were an optimization to shrink request payloads Delete lazy activation. Fixed tool set registered at session start, identical every turn. The byte-stability discipline bcp applies to the system header extends to the tools block
C5 Turn-boundary duplication: pi-lens emits at agent_end/turn-end (deferred autofix/format queue, turn findings, async test results into next turn) while bcp also nudges there (block status) Two independent emitters at the same boundary → two message sources, double cache surface One nudge slot, fixed content contract: line 1 = block status (existing), line 2 = open blocking-findings count (only if > 0). Async test results land in the ref store, surfaced via same-turn attach when fast or via pull tool otherwise
C6 pi-lens read-guard requires read-before-edit in-session; bcp's acp_delegate subagents write files from child contexts that never "read" in the parent session Read tracking is session-local; delegate writes are legitimate but invisible to it Bridge-recorded mutations count as reads: delegate (and any third-party) writes register via the existing mutation bridge (globalThis[Symbol.for("pi-lens:mutation-bridge")], recordMutation({filePath, kind, editRanges, consumer})); read-guard accepts bridge-recorded mutations for guard purposes. Keeping the symbol unchanged preserves ecosystem compatibility
C7 Runtime footprint mismatch: bcp is a pure message transformer; pi-lens spawns and warms long-running LSP server children An execution engine inside a compression extension changes the package's dependency surface Config-gated module, default off (checks.enabled: false). Engine lifecycle bound to the pi session; warm pool + cooldown registry preserved as-is. Zero process cost when disabled

Integration design

Module mapping (pi-lens subsystem → bcp destination)

pi-lens subsystem bcp destination Required change
LSP server management (spawn/warm/cooldown/instance registry) new src/checks/lsp/ execution layer lifecycle bound to pi session; none functional
write → immediate autofix in same tool result keep timing; attach = severity-tagged summary + ACP ref drop full-content append (C3); ordering contract (C2)
edit → deferred autofix/format queue at agent_end keep queue; output re-routed to ref store + nudge line 2 re-route delivery only
Unified LSP diagnostics + impact cascade (reverse-dependency neighbors) execution layer; results to ref store storage only
ast-grep structural rules, opengrep (always-on), gitleaks/trivy/govulncheck/knip/jscpd/madge/vulture/zizmor (config-gated) execution layer, config-gated under checks.* (nearest-config-wins preserved) none
Async test runner (results next turn; fixed exclusions **/integration/**, **/e2e/**) results to ref store; surfaced same-turn when fast, else pull tool delivery re-route (C5)
lens_diagnostics pull tool (mode=delta/all/full) keep as checks_diagnostics, fixed registration at session start rename + constant tool block (C4)
Read-guard (+ /lens-allow-edit one-shot bypass) keep; accept bridge-recorded mutations C6
Mutation bridge keep symbol as-is none
Severity tiers (🔴 blocking / 🔑 action-needed / advisory) + honest labels (unconfirmed/cold/partial/stale) keep semantics verbatim — they are what makes findings trustworthy none
Context injection (turn findings / test findings / session guidance) deleted — replaced by D1–D3 below the core cache-safety decision
Lazy tools / activate-tools deleted C4
AUTHORITATIVE_CONTENT full-content append deleted — integrity pointer + ref (C3)
--lens-guard commit/push blocking (experimental) optional flag, unchanged none
Turn-status display items fold into bcp's existing footer/status line UI merge

Delivery channels (replacing all injection)

  • D1 — Same-turn attachment (per edit): findings summary appended to the current write/edit tool result, severity-tagged, bounded (≤ ~10 lines; "N errors, M warnings in X — detail ref m00421" style). This is the loop: the agent sees findings before its next action, in the same turn it made the edit. Blocking findings are always in the summary; advisory counts only.
  • D2 — Ref store (detail): full diagnostic output lives extension-side, referenced by ACP ref. Compressible through bcp's normal pipeline once aged out of the working set — detail costs tokens only while relevant. This is where the 8-stage pipeline earns its keep on the checks' own output.
  • D3 — Pull tool (on demand): checks_diagnostics mode=delta|all|full, model-invoked, zero prefix impact (constant registration). Replaces session guidance, turn-findings push, and everything a curious agent wants beyond the summary.

Per-turn pipeline (merged ordering contract)

  1. Tool call completes → checks engine runs (immediately for write; queue for edit, drained at agent_end) → findings classified → D1 summary attaches, detail → ref store (D2).
  2. Absorb evaluates the annotated result; attachment region exempt from force-absorb for the current turn (C2).
  3. context event: bcp 8-stage pipeline runs as the only handler; nudge stage extended per C5.
  4. agent_end: deferred autofix/format queue drains; footer/status updated.
  5. Compression: ref-store detail compresses via normal refs when out of the working set.

Cache-safety contract (invariant list)

  • Tools block: constant for the whole session (no activation/deactivation).
  • System header: byte-stable (bcp's existing discipline, unchanged).
  • Zero ephemeral user-role messages injected by the checks engine.
  • Same-turn attachment bounded; detail never inlined into persisted transcript.
  • Every checks artifact (findings, test results, autofix diffs) addressable by ACP ref and compressible.

Config surface (namespaced under bcp's existing config)

checks.enabled: false                  # master gate, default off
checks.autofix.enabled: true           # fixer pipeline (biome/ruff/eslint/stylelint/rubocop/clippy --fix)
checks.format.mode: deferred           # deferred|immediate, deferred default (agent_end)
checks.diagnostics.pullTool: true      # registers checks_diagnostics
checks.readGuard.enabled: true
checks.scanners.gitleaks: true         # + trivy/govulncheck/knip/jscpd/madge/vulture/zizmor, config-gated
checks.opengrep: true                  # always-on when engine enabled
checks.guard: false                    # commit/push blocking (experimental)

Nearest-config-wins per project is preserved from pi-lens.

Phased rollout (each phase independently shippable)

  • P0 — Delivery contract (no engine yet): ref store, D1 attachment format, nudge line-2 extension, constant tool registration. Ships the cache-safe primitives; upstream can validate the wire format cheaply.
  • P1 — Engine core: LSP management + checks_diagnostics pull tool (delta/all/full) + read-guard with bridge awareness.
  • P2 — Fixers: autofix pipeline + deferred agent_end queue + integrity-pointer replacement for AUTHORITATIVE_CONTENT.
  • P3 — Scanners & tests: ast-grep, opengrep, config-gated scanners, async test runner, optional guard flag.

What this issue does NOT ask for

Alternatives considered

  • Keep pi-lens installed alongside bcp: functionally verified to co-exist (one historical session ran lens diagnostics 16× alongside compress 11×), but ships the injection patterns users complain about, doubles tool schemas in every request, and relies on undefined handler ordering — bcp's README explicitly cannot defend against post-bcp context handlers.
  • Adapter/bridge only (bcp consumes an external checker): smaller maintenance ask, but the LSP runtime stays outside the package — worse out-of-box experience, version-skew risk, and the ordering contract (C2) becomes unenforceable across package boundaries.
  • Spec-only hooks: minimal maintainer cost, but fragments the ecosystem; the check loop is core to agent quality and belongs native.

Local measurements behind the conflict attributions (multi-provider, 2105-turn corpus, sanitized): providers without server-side prefix caching show zero-cache-read turns regardless of extensions; on cache-equipped providers, injected-message turns diverge only at the tail (~100 tokens/turn) while tool-list mutation is the whole-prefix invalidator — matching pi-lens' own tool-set-policy comment. A/B protocol to quantify loop benefit: same task set with/without the loop, measuring tokens billed, turns to green tests, residual errors after the agent declares done.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions