Skip to content

LeanCTX

A local layer that sits beside a coding agent and decides what reaches the model: file reads are compressed and cached, command output is compressed by per-command rules, session findings persist across chats, and a local proxy rewrites each request without breaking the provider’s prompt cache — with a savings ledger, a budget and a dashboard for what it measured.

Screenshot of LeanCTX
Editor screenshot, 1 Oct 2026LeanCTX ↗

What it is

LeanCTX is a local context layer for coding agents. It installs beside an agent, replaces native file reads, searches and shell commands with MCP tools named ctx_*, and then decides what actually reaches the model: reads are compressed in one of several modes, a second read of the same file returns a compact deterministic reference instead of the file again, command output is compressed by per-command rules, and the session’s findings, decisions and touched files survive a restart. It runs a local proxy that rewrites each request on its way to the provider without invalidating the provider’s prompt cache, keeps a savings ledger and a Shadow Mode baseline for what it measured, and offers a dashboard and a budget for the context window. The README calls it an AI Value Gate and promises zero configuration and a local-first design; the code is Rust under Apache-2.0, 3,837 stars, written in six months by one developer working with agents, from v0.1.0 on 2026-03-23 to v3.10.5 on 2026-09-27.

Who built itThe repository’s 8,285 commits are recorded under his name in 7,782 cases, and 5,551 commits are tied to the yvgude account; 2,265 commits in the history carry no linked account at all. The addresses behind the total include a machine-local one with 2,224 commits and a Hotmail one with 517. He created the repository on 2026-03-23 and pushed to it until 2026-09-30. Beyond him the contributor list holds 49 names, led by cedric013 with 120 commits, andig with 70, dasTholo with 43 and a dependency bot with 40.

How it is put together

The parts · 6

The shape is a layer rather than an agent: one local process that a coding agent talks to over MCP, standing between the agent and the repository on one side and the model provider on the other. Everything the agent learns about a repository passes through tools named ctx_*, which is what lets the layer decide what a read returns, whether a second read of the same file needs to return anything at all, how a command’s output is rendered, and what is remembered between sessions. Two constraints run through the whole codebase instead of living in one module. Output must be a deterministic function of its inputs, because provider-side prompt caching rewards byte-stable text and a timestamp in an output body destroys it. And compression must never cost more than the thing it compresses, which is why the default path can decline to compress at all and why the before-and-after measurements are committed next to the code. Around that sit the parts a product needs: a savings ledger and receipts to show what was saved, a proxy for the request stream, policy and path boundaries for what may be read or run, and versioned contracts with schemas for everything that crosses a boundary. The project also states its own claim boundary in writing — an implementation-orientation stub maps each area to Available, Preview or Research and says the product boundary is deliberately narrow, controlling context before inference and not replacing the agent, its task logic, its model choice, its tools or its retry policy.

rust/src/
The runtime: 2,060 files and 24,297 KB, holding the core library, the MCP tool implementations, the shell compressor, the provider proxy, the command-line interface, a local dashboard and an HTTP server. The file sizes are characteristic of the project — proxy/mod.rs at 55,672 bytes, shell/compress/engine.rs at 53,355, shell_hook.rs at 49,882, tools/ctx_architecture.rs at 47,966, shell/agent_wrapper.rs at 46,688 and cli/addon_cmd.rs at 41,809.
rust/src/core/
The context machinery. context_kernel/ alone is about eighty-five files covering activation, deduplication, policy, receipts, attribution, degradation and the client bridges; context_package/ is a package manager with manifests, a registry, signing and its own verifier; context_ledger/, context_os/, context_snapshot/, bm25_index/ and graph_index/ hold the budget, the bus, the timeline and retrieval; and patterns/ carries one command-output compression module per tool, from git and kubectl to pytest, terraform and trivy.
rust/src/proxy/
The request path: over a hundred files that compress, route, meter and cache provider traffic, among them cache_aligner.rs at 27,088 bytes, effort_routing.rs at 35,094, openai_responses.rs at 47,392, ccr.rs at 35,229, usage_meter.rs at 34,857 and history_prune.rs at 34,387 — plus per-provider adapters for Anthropic, OpenAI, Google, Bedrock and ChatGPT, and a dedicated determinism guard.
docs/
Documents a reader can check. contracts/ is 165 files and 534 KB of contract texts, JSON schemas and matched valid-and-invalid fixtures; reference/ is 39 files and 391 KB, including generated config-keys.md at 58,664 bytes and mcp-tools.md at 30,912; guides/ has a page per agent — Claude Code, Codex CLI, Cursor, Gemini CLI, Windsurf, OpenCode, Aider and Pi; ga/ holds ten operating documents from install to disaster recovery; adrs/ has eight decision records.
packages/, clients/ and the gates
Everything outside the runtime crate. leanctx-verify is a standalone verifier whose receipt.rs is 67,703 bytes and v2.rs 62,801; pi-lean-ctx is a TypeScript bridge of 48,892 bytes plus a 25,813-byte MCP bridge; ocla-grpc, a VS Code package and an npm binary package with an 18,303-byte installer surround a Rust client crate tested against an external consumer fixture. Fifteen workflows in 119 KB guard all of it — ci.yml at 38,180 bytes and release.yml at 36,372, alongside code scanning, a CLA check, a history audit, dependency updates, client publishing and Windows signing — with 28 scripts led by a 60,673-byte open-core boundary check and a 13,323-byte preflight, and security/evidence/ holding thirteen gate evidence files including a 121,140-byte full-history baseline.
_archive/ and the root documents
The retired surface and the written record. _archive/ contains the clients the project no longer ships — JetBrains (86 files), VS Code (25), Chrome, Emacs, Sublime, Neovim, two Python SDKs, a Go SDK, a Datadog integration and launch material — plus benchmark corpora, four research documents written from information theory, neuroscience, mathematics and systems engineering, and a Lean project whose proofs cover compression read modes, the terse engine, secret safety, a handoff state machine and policy properties. The root holds CHANGELOG.md at 950,445 bytes, README.md at 36,812, SECURITY.md at 22,696, AGENTS.md at 11,859 and .codex/vision-input/, four documents totalling about 150 KB.

Choices, and what they beat

  • Compression that never sends more than the raw file over compressing every read regardless of what it saves

    Stated in the pull request rather than inferred: a read in auto mode that resolved to a mode unable to shrink the file used to return a banner plus the whole file, which cost more than raw on files around 600 tokens, so it now returns the bare file. The measurements that back the change are committed in scripts/benchmark/results/latest/, and they include the cases where nothing was saved.

  • A byte-stable request prefix, even at the cost of not sending an update over rewriting every request with the newest compression

    When a guard reverts the compression, or nothing changed, the proxy forwards the client’s original bytes instead of a re-serialized body, because provider caching pays for text that does not change. The same reasoning is written into the repository’s own rule for tool output: deterministic functions of content, mode and task, with no timestamps, counters or random elements in an output body.

  • Delete modules nothing calls over keeping them compiled and shipped

    Twenty-four core modules and about 12,500 lines went, after checking the binary, the tests, the other workspace crates and the external SDK; six of them had been announced in a changelog entry, and the author checked two v4 branches first because an earlier removal round had deleted modules a pending merge still needed. One module was kept deliberately as a library boundary although no binary path calls it.

  • Fail closed where a process cannot be identified over letting registration proceed on an unknown platform

    Process identity existed for macOS, Linux and Windows only, and every other target deliberately returned nothing, which made the server unusable on FreeBSD because agent-bus registration refuses a process it cannot identify. FreeBSD was given the same two facts the other platforms use — process start time and executable path, cross-checked so a reused process id cannot share them — and targets exposing neither still fail closed, so the security posture of the ownership checks is unchanged.

  • A status label per area instead of inferring claims from code over treating implementation presence as a product promise

    The architecture file is an explicitly non-product document that maps each area to Available, Preview or Research with a claim boundary for each, points at two internal documents that override it and any inference from the source tree, and forbids reading a directory, module, test fixture, command or diagram as a compatibility commitment. The same file states the narrow boundary: the layer controls context before inference and does not replace the customer’s agent, task logic, model choice, tools or retry policy.

Read fromARCHITECTURE.md, AGENTS.md (11,859 bytes), the bodies of pull requests #1910, #1911, #1912, #1923, #1932, #1934, #1935, #1936, #1938, #1940, #1948, #1954, #1955, #1956 and #1957, scripts/benchmark/results/latest/ with its raw and compressed pairs, scripts/preflight.sh, security/evidence/*, and the directory summary and file sizes of the complete 3,890-file tree.

Build log

6 stages
  1. 01

    Six months, 8,285 commits, and a trailer on most of them

    The repository was created on 2026-03-23 and its first commit, Initial release: lean-ctx MCP Server v0.1.0, is timestamped 13:30:19Z that day — six hours before the repository itself. The newest commit is a merge on 2026-09-30 at 20:48:28Z. In between: 8,285 commits in 192 days, distributed 487 / 695 / 413 / 1,059 / 4,601 / 655 / 375 across March to September, so July alone holds 56 per cent of the history. There are 5,673 co-author trailers, and the largest single group is Cursor, at 5,219. The Claude trailers come to 300 between them — Opus 5.5 with 100, Opus 5 with 63, Opus 5 (1M context) with 53, Opus 4.8 (1M context) with 39, Fable 5 with 23, Opus 4.7 with 10, Fable 5.1 with 7, Opus 4.8 with 4 and Sonnet 4.6 with 1 — while Codex, Copilot, Mistral Vibe, Codebuff and Paperclip have one each. Among the contributors the owner’s account holds 5,551 commits, then cedric013 with 120, andig with 70, dasTholo with 43, dependabot with 40, ousatov-ua with 31 and forty-four more names. The twenty most recent releases run from v3.9.7 on 2026-07-11 to v3.10.5 on 2026-09-27, and the tags also carry vscode-v0.1.0 and vscode-v0.2.0. The tree is 192,142 KB, and the repository has 3,837 stars, 352 forks, 23 watchers and 8 open issues.

  2. 02

    The working method is written into AGENTS.md

    AGENTS.md is 11,859 bytes and reads like a manual for the agents, not for a person. Every substantive task begins with a mandatory routing decision — direct or swarm — and the lead picks the path with the shortest accepted-result time, working with 0 to 15 agents, fifteen being a hard concurrent maximum and each worker getting a distinct role. The rule belongs to the orchestration lead: a worker with a delegated subtask finishes its assigned role but does not launch another swarm unless asked. Every Codex CLI agent must run --model gpt-6-luna at maximum reasoning effort. Native Read, Grep, Glob and Shell are denied by policy, and the ctx_* MCP tools are the only path to files and commands. Every ephemeral worker registers on the agent bus as its first MCP operation. The test policy is the strictest part: about 12,900 tests already exist and every new one costs CI minutes on three operating systems forever, so a test earns its place only if it catches a plausible future bug nothing else catches; a bugfix gets exactly one regression test at the lowest layer that reproduces the bug, and that test must fail without the fix. “No test is also a valid answer” is written down as an acceptable outcome, and deleting a redundant test is fine if the commit says why. Before every commit the gate is cargo test --lib, clippy with warnings as errors, and a formatting check.

  3. 03

    Compression that has to pay for itself

    The compressor is the largest machine in the repository. Command output is handled by a module per command family under rust/src/core/patterns/ — git, kubectl, pytest, terraform, npm, pnpm, maven, poetry, ruff, trivy, syft and dozens more — with the engine itself in rust/src/shell/compress/engine.rs at 53,355 bytes, and AGENTS.md puts the number of shell compression patterns at ninety-five or more. A pull request at the end of September fixed the failure that matters most for a tool like this: compression could cost more than the raw file. A read in auto mode that resolved to a mode unable to shrink the file used to return a banner plus the whole file, which cost more than raw on files around 600 tokens, so it now returns the bare file instead; the entropy mode used to keep every line of a typical Rust file with its aggressiveness setting having no effect, and now drops lines against a file-relative surprise floor, saving roughly 10 to 35 per cent by default, with higher aggressiveness dropping strictly more. The measurements are committed rather than described: scripts/benchmark/results/latest/ holds raw and compressed pairs, from 9_tree_core at 40,138 bytes down to 193, to pairs where nothing at all was saved — 1_read_core_mod 14,795 to 14,796, 3_test_triage 1,495 to 1,495, 4_git_log 1,472 to 1,472.

  4. 04

    The prompt cache is treated as a hard constraint

    Provider prompt caching pays for text that does not change, so this project treats byte-stability as a requirement rather than a nicety. AGENTS.md states it as a rule: tool outputs must be deterministic functions of file content, mode, CRP mode and task; no timestamp, counter or random element may appear in an output body; artifact paths are content-addressed, derived from a hash of the command that produced them; and dynamic additions are allowed only as state-triggered suffixes with stable headers. The proxy carries the same constraint. rust/src/proxy/ is over a hundred files — proxy/mod.rs at 55,672 bytes, cache_aligner.rs at 27,088, effort_routing.rs at 35,094, ccr.rs at 35,229 — and one pull request is entirely about not busting the cache. The complexity score that decides how much extra thinking to inject is session-stable, so the injected block is identical across turns; the thinking budget is capped at half of max_tokens, so requests below 2,048 tokens get no injection at all; a client-set reasoning effort is never overridden; and when a guard reverts the compression, or nothing changed, the proxy forwards the client’s original bytes instead of a re-serialized body. Compressing the system prompt of a conversation the client has already cached happens only when the per-turn saving repays the one-off cache re-write within the conversation’s observed length.

  5. 05

    Reports from users, and repairs that were about wording

    The last two days of September are the best record of how the project behaves under pressure. A Windows user reported that a multi-line PowerShell command failed as a whole pipeline, with ordinary statements such as if ($t) { … } rejected as command names — and while reproducing it the author found something worse: the allowlist walker never looked inside script blocks, so a destructive loop passed the cmdlet check on the main branch and was blocked only after the fix. Two reports by another user were about a refusal message that named the wrong rule: the guard refuses output redirection to any destination that is not a scratch path or a configured allow path, inside the project or not, but the message said “a project path”, so the reporter tried a path outside the project and was refused identically. The author’s reply separates the two halves: “The verdict was right and the wording was wrong, as you said.” A fourth, found by a flaky Windows test, was a production bug: a background thread wrote a whole configuration snapshot back after network calls lasting seconds, silently reverting edits made in the meantime from the command line, the dashboard or an editor. One pull request was closed without merging, with the measurement stated as the reason.

  6. 06

    Subtraction, a scanning gap, and the archive

    Two pull requests in one week were deletions. The larger removed 24 modules from the core crate, about 12,500 lines, that no code path used — not the binary, the tests, the other workspace crates or the external SDK. Six of them had been announced in an earlier changelog entry as research modules, which is the part worth recording: shipping a module and describing it are independent acts. Before deleting, the author checked two v4 branches, because an earlier round had removed modules that a pending merge still needed; one module was kept on purpose as a library boundary even though no binary path calls it. The same week exposed an older gap: code scanning covered JavaScript, Actions and Rust but not Python — the retired default setup had been the only thing scanning it — so 83 Python files outside _archive/ had gone unscanned since April, the plugin that ships to users among them. The archive holds the retired surface — clients for JetBrains (86 files), VS Code (25), Emacs, Sublime and Neovim, two generations of a Python SDK, a Go SDK, launch material and four VHS tapes — and something unusual: a Lean project with machine-checked proofs for compression read modes, the terse engine, secret safety, a handoff state machine and policy properties, beside a 16,370-byte paper. At the root, CHANGELOG.md is 950,445 bytes, the largest text file in the tree.

Adjacent records

All records →