跳到正文

LeanCTX

一层贴在编码 agent 旁边、决定什么内容能到模型眼前的本地软件:文件读取会被压缩并缓存,命令输出按每条命令各自的规则压缩,会话里的发现跨对话留存,本地代理重写每一个请求同时不弄坏服务商的 prompt cache——还有一本节省账、一份预算和一块仪表盘,报告它究竟量到了什么。

Screenshot of LeanCTX
编辑截图, 1 Oct 2026LeanCTX ↗

这是什么

LeanCTX 是一层给编码 agent 用的本地上下文软件。它装在 agent 旁边,把原生的文件读取、搜索与 shell 命令换成一组叫 ctx_* 的 MCP 工具,然后决定真正能到模型眼前的究竟是什么:读取按若干种模式压缩,同一份文件的第二次读取只返回一段紧凑而确定的引用,命令输出按每条命令各自的规则压缩,一个会话里的发现、决定与动过的文件能活过一次重启。它还会跑一个本地代理,在请求上行发给服务商之前重写它,同时不弄坏服务商的 prompt cache;它留下一本节省账和一份 Shadow Mode 基线来说明量到的结果,并提供仪表盘与上下文窗口的预算。README 称它是一个 AI Value Gate,并承诺零配置与本地优先;代码是 Apache-2.0 下的 Rust,3,837 个星,由一位开发者和他的 agent 在六个月里写成,从 2026-03-23 的 v0.1.0 到 2026-09-27 的 v3.10.5。

谁做的仓库的 8,285 次提交里有 7,782 次记在他名下,其中 5,551 次绑定了 yvgude 这个账号;整个历史里有 2,265 次提交完全没有关联账号。这些提交背后的邮箱地址中,最大的一个来自机器本地,2,224 次,另一个来自 Hotmail,517 次。仓库创建于 2026-03-23,他一路推到 2026-09-30。除他之外贡献者名单上有 49 个名字,最前面的 cedric013 有 120 次提交,andig 70 次,dasTholo 43 次,一个依赖机器人 40 次。

它是怎么搭起来的

组成 · 6

形状是一层而不是一个 agent:一个本地进程,编码 agent 通过 MCP 跟它说话,它一侧站在 agent 与仓库之间,另一侧站在模型服务商之前。agent 关于一个仓库所知道的一切都要经过那组叫 ctx_* 的工具,正因为如此,这层才能决定一次读取返回什么、同一份文件的第二次读取还需不需要返回任何东西、一条命令的输出被渲染成什么样,以及会话与会话之间记住什么。有两条约束贯穿整个代码库,而不是各自住在一个模块里。其一,输出必须是输入的确定函数,因为服务商侧的 prompt cache 只为字节稳定的文字付钱,而输出正文里一个时间戳就能把它毁掉。其二,压缩永远不能比被压缩的东西更贵,所以默认路径可以干脆选择不压,也正是为什么压前压后的测量结果被提交在代码旁边。围着这两条的,是一个产品需要的那些部分:一本节省账和收据来说明省下了什么,一个处理请求流的代理,一套规定了能读什么、能跑什么的策略与路径边界,以及所有跨边界的东西都配了 schema 的版本化契约。这个项目还把自己的主张边界写了下来——一份自认「非产品文档」的实现导览把每个领域标成 Available、Preview 或 Research 并各给一条主张边界,说产品边界是刻意窄的:它只控制推理之前的上下文,不替换 agent、不替换它的任务逻辑、模型选择、工具与重试策略。

rust/src/
运行时:2,060 个文件、24,297 KB,装着核心库、MCP 工具实现、shell 压缩器、服务商代理、命令行、一块本地仪表盘与一个 HTTP 服务。文件体积很有这个项目的特征——proxy/mod.rs 55,672 字节,shell/compress/engine.rs 53,355,shell_hook.rs 49,882,tools/ctx_architecture.rs 47,966,shell/agent_wrapper.rs 46,688,cli/addon_cmd.rs 41,809。
rust/src/core/
上下文机器本身。光 context_kernel/ 就有约八十五个文件,覆盖激活、去重、策略、收据、归因、降级与各种客户端桥;context_package/ 是一个带清单、注册表、签名和自己校验器的包管理器;context_ledger/、context_os/、context_snapshot/、bm25_index/ 与 graph_index/ 分别装着预算、总线、时间线与检索;而 patterns/ 里每一个工具一个命令输出的压缩模块,从 git、kubectl 一直到 pytest、terraform 与 trivy。
rust/src/proxy/
请求路径:一百多个文件负责压缩、路由、计量与缓存服务商流量,其中有 cache_aligner.rs 27,088 字节、effort_routing.rs 35,094、openai_responses.rs 47,392、ccr.rs 35,229、usage_meter.rs 34,857、history_prune.rs 34,387——另有 Anthropic、OpenAI、Google、Bedrock 与 ChatGPT 各自的适配器,以及一个专门的确定性守卫。
docs/
读者可以自己去核对的文档。contracts/ 是 165 个文件、534 KB 的契约文本、JSON schema 与成对的有效/无效夹具;reference/ 是 39 个文件、391 KB,其中包括生成的 config-keys.md(58,664 字节)与 mcp-tools.md(30,912 字节);guides/ 里每个 agent 一页——Claude Code、Codex CLI、Cursor、Gemini CLI、Windsurf、OpenCode、Aider 与 Pi;ga/ 有十份从安装到灾难恢复的运维文档;adrs/ 有八份决策记录。
packages/、clients/ 与那些门禁
运行时 crate 之外的所有东西。leanctx-verify 是一个独立校验器,它的 receipt.rs 有 67,703 字节、v2.rs 有 62,801;pi-lean-ctx 是一段 48,892 字节的 TypeScript 桥加一段 25,813 字节的 MCP 桥;ocla-grpc、一个 VS Code 包、一个带 18,303 字节安装脚本的 npm 二进制包,围着一个用外部消费者夹具测试过的 Rust 客户端 crate。十五个工作流、119 KB 守着这一切——ci.yml 38,180 字节、release.yml 36,372,旁边还有代码扫描、CLA 检查、历史审计、依赖更新、客户端发布与 Windows 签名——28 个脚本里领头的是一份 60,673 字节的开放核心边界检查和一份 13,323 字节的 preflight,而 security/evidence/ 存着十三份门禁证据,其中包括一份 121,140 字节的全历史基线。
_archive/ 与根目录文档
退役的表面与写下来的记录。_archive/ 收着项目不再发布的那些客户端——JetBrains(86 个文件)、VS Code(25 个)、Chrome、Emacs、Sublime、Neovim、两代 Python SDK、一个 Go SDK、一个 Datadog 集成与发布素材——外加基准语料、四份分别从信息论、神经科学、数学与系统工程写出的研究文档,以及一个 Lean 工程,它的证明覆盖压缩读取模式、terse 引擎、机密安全、一个交接状态机与若干策略性质。根目录上是 950,445 字节的 CHANGELOG.md、36,812 的 README.md、22,696 的 SECURITY.md、11,859 的 AGENTS.md,以及 .codex/vision-input/ 里合计约 150 KB 的四份文档。

取舍,以及它替代了什么

  • 压缩绝不发得比原始文件还多 替代 不管省不省都照压每一次读取

    这是 pull request 里写明而不是推测出来的:一次 auto 读取若落到压不小文件的模式上,原本会返回一条横幅加整个文件,在六百来个 token 的文件上比原样还贵,所以现在直接返回裸文件。支撑这个改动的测量被提交在 scripts/benchmark/results/latest/ 里,而且里面就包含一点都没省下来的那些情形。

  • 请求前缀保持字节稳定,哪怕代价是这一轮不发更新 替代 每一轮都用最新的压缩结果重写请求

    当守卫把压缩撤回、或者根本没有东西需要改时,代理转发客户端原始的字节,而不是重新序列化过的请求体,因为服务商缓存只为不变的文字付钱。同一套推理也写进了仓库自己对工具输出的规矩:内容、模式与任务的确定函数,输出正文里不许有时间戳、计数器或随机成分。

  • 把没人调用的模块删掉 替代 让它们继续被编译、继续被发出去

    24 个 core 模块、约 12,500 行被删掉,删之前查过二进制、测试、工作区里其他 crate 与外部 SDK;其中六个曾在 changelog 条目里被宣布过,而作者先查了两个 v4 分支,因为更早一轮删除曾经删掉待合并分支仍然需要的东西。另有一个模块明知没有二进制路径调用也被特意留下,作为一条库边界。

  • 在认不出进程的平台上失败关闭 替代 让注册在未知平台上照样进行

    进程身份只支持 macOS、Linux 与 Windows,其他目标刻意返回空,于是 FreeBSD 上服务器完全不可用,因为 agent bus 注册拒绝一个它认不出的进程。FreeBSD 现在读到的是其他平台同样的两个事实——进程启动时间与可执行文件路径,并交叉核对,使得被复用的进程号不可能共享它们——两个都拿不到的目标仍然失败关闭,所有权检查的安全姿态因此没有改变。

  • 给每个领域一个状态标签,而不是从代码里推断主张 替代 把「实现存在」当成产品承诺

    那份架构文档明确自认非产品文档,把每个领域标成 Available、Preview 或 Research 并各给一条主张边界,指向两份内部文档——它们压过这份文件,也压过任何从源码树推断出来的东西——并且禁止把一个目录、模块、测试夹具、命令或图读成兼容性承诺。同一份文件里写着那条窄边界:这一层只控制推理之前的上下文,不替换客户的 agent、任务逻辑、模型选择、工具与重试策略。

依据ARCHITECTURE.md、AGENTS.md(11,859 字节)、pull request #1910、#1911、#1912、#1923、#1932、#1934、#1935、#1936、#1938、#1940、#1948、#1954、#1955、#1956、#1957 的正文、scripts/benchmark/results/latest/ 里成对的原始文件与压缩结果、scripts/preflight.sh、security/evidence/*,以及完整 3,890 个文件树的目录汇总与文件体积。

制作过程

6 个阶段
  1. 01

    六个月、8,285 次提交,以及几乎每一次提交上的尾注

    仓库建于 2026-03-23,第一次提交是当天的 Initial release: lean-ctx MCP Server v0.1.0,时间戳 13:30:19Z——比仓库本身还早六个小时。最新一次提交是 2026-09-30 20:48:28Z 的一次合并。中间是 192 天里的 8,285 次提交,三月到九月按月分布为 487 / 695 / 413 / 1,059 / 4,601 / 655 / 375,也就是说光七月一个月就占了整部历史的 56%。共同作者尾注有 5,673 条,其中最大的一群是 Cursor,5,219 条。Claude 系列加起来 300 条——Opus 5.5 一百条、Opus 5 六十三条、Opus 5(1M context)五十三条、Opus 4.8(1M context)三十九条、Fable 5 二十三条、Opus 4.7 十条、Fable 5.1 七条、Opus 4.8 四条、Sonnet 4.6 一条——另有 Codex、Copilot、Mistral Vibe、Codebuff 与 Paperclip 各一条。贡献者里作者本人的账号占 5,551 次,其后是 cedric013 的 120 次、andig 的 70 次、dasTholo 的 43 次、dependabot 的 40 次、ousatov-ua 的 31 次,再加四十四个名字。最近二十个 release 从 2026-07-11 的 v3.9.7 排到 2026-09-27 的 v3.10.5,标签里还有 vscode-v0.1.0 与 vscode-v0.2.0。代码树 192,142 KB,仓库有 3,837 个星、352 个 fork、23 个 watcher 与 8 个未关的 issue。

  2. 02

    工作方法写在 AGENTS.md 里

    AGENTS.md 有 11,859 字节,读起来更像写给 agent 的手册而不是写给人的。每一个实质任务都从一次强制的路由判断开始——直接做还是开一群——负责人选那条「拿到合格结果最快」的路,用 0 到 15 个 agent,十五是并发硬上限,每个工人一个不重复的角色。这条规矩归编排负责人所有:被派了子任务的工人通过 lean-ctx 协调、干完自己那份角色,但不经要求不会再开一群。仓库里每个 Codex CLI agent 都必须用 --model gpt-6-luna,推理力度拉满。原生的 Read、Grep、Glob 与 Shell 被策略禁用,ctx_* 这套 MCP 工具是读写文件与跑命令的唯一通路,原生调用被拒时会返回一条指明替代品的错误。每个临时工人都要在 agent bus 上注册,作为它的第一个 MCP 操作。最有主张的一段是测试政策:已经有约 12,900 个测试,而每一个新测试都要在三个操作系统上永远消耗 CI 分钟,所以一个测试只有能抓住「别的任何东西都抓不住的、将来很可能出现的 bug」才配存在;修 bug 只加一个回归测试,放在能复现它的最低那一层,而且这个测试在没有修复时必须失败。「一个测试都不写也是一个合格答案」被明确写了下来;删掉一个测试也可以,只要它是冗余的,并在提交信息里说明理由。每次提交前的门是 cargo test --lib、把警告当错误的 clippy,以及格式检查——而且绝不用看结尾几行的方式判断结果,因为一个红的测试会从眼前滑过去。

  3. 03

    压缩必须自己付得起成本

    压缩器是这个仓库里最大的一台机器。命令输出按命令族各有一个模块,放在 rust/src/core/patterns/ 下——git、kubectl、pytest、terraform、npm、pnpm、maven、poetry、ruff、trivy、syft 等等几十个——引擎本体在 rust/src/shell/compress/engine.rs,53,355 字节;而 AGENTS.md 说 shell 压缩模式有九十五种以上。九月底的一条 pull request 修掉了对这类工具来说最要命的那种失败:压缩有可能比原始文件还贵。一次 auto 读取如果落到一个根本压不小文件的模式上,原本会返回一条横幅加整个文件,在六百来个 token 的文件上比原样返回还贵,现在它直接返回裸文件;entropy 模式原本会把一个典型 Rust 文件的每一行都留下、aggressiveness 设了也没用,现在按「相对于这份文件的意外程度」设一条下限来丢行,默认省下大约 10% 到 35%,调高 aggressiveness 会丢得严格更多。这些数字是被提交进仓库而不是被描述出来的:scripts/benchmark/results/latest/ 里放着成对的原始文件与压缩结果,从 9_tree_core 的 40,138 字节压到 193,到那些一点都没省下来的——1_read_core_mod 14,795 到 14,796,3_test_triage 1,495 到 1,495,4_git_log 1,472 到 1,472。

  4. 04

    把 prompt cache 当成硬约束

    服务商的 prompt cache 只为不变的文字付钱,所以这个项目把字节稳定当成硬性要求而不是讲究。AGENTS.md 把它写成了规矩:工具输出必须是「文件内容、模式、CRP 模式、任务」的确定函数;输出正文里不许出现时间戳、计数器或随机成分;产物路径按内容寻址,从产生它的那条命令的哈希推出来;动态追加只允许以状态触发的后缀形式、带稳定的表头。代理带着同样的约束。rust/src/proxy/ 有一百多个文件——proxy/mod.rs 55,672 字节,cache_aligner.rs 27,088,ccr.rs 35,229,history_prune.rs 34,387,openai_responses.rs 47,392——而其中一条 pull request 整篇都在讲别把缓存打坏:决定要注入多少额外思考的复杂度分数是按会话稳定的,所以注入的那一段在每一轮里完全一样;思考预算被压到 max_tokens 的一半,于是 2,048 以下的请求根本不注入;客户端自己设的推理力度永远不被覆盖;而当某个守卫把压缩撤回、或者什么都不需要改时,代理转发的是客户端原始的字节,而不是重新序列化过的请求体。压缩一段客户端已经缓存过的系统提示,只在「这一轮从缓存读省下的钱能在会话观察到的长度内还清那次一次性重写成本」时才会发生。

  5. 05

    来自用户的报告,以及几处关于措辞的修复

    九月底最后两天的 issue,是这个项目在压力下如何行事最好的记录。一位 Windows 用户报告一条多行 PowerShell 命令整条流水线失败,像 if ($t) { … } 这样的普通语句被当成命令名拒掉;而在复现的过程中作者发现了更糟的事:白名单遍历器从来不往脚本块里面看,于是一个会删文件的循环在主分支上通过了 cmdlet 检查,要等修完才被拦住。同一个用户的另外两份报告讲的是一条说错规则的拒绝信息——守卫其实拒绝「把输出重定向到除临时路径与已配置允许路径之外的任何目标」,不管在不在项目里,但信息写的是「project path」,于是报告者照字面理解、换了一个项目外的路径,得到的拒绝一字不差。作者的回复把两件事分开:「The verdict was right and the wording was wrong, as you said.」第三份报告来自 FreeBSD,每一次 ctx_* 调用都失败,因为进程身份只实现了 macOS、Linux 与 Windows,其他目标都刻意失败关闭。第四处由一个在 Windows 上时红时绿的测试翻出来,结果是一个生产 bug:一个后台线程在网络调用(可能耗时数秒)结束后把整份配置快照写回去,静默地撤掉了这期间从命令行、仪表盘或编辑器做下的修改。还有一条 pull request 最终没有合并,理由就是量过之后没有效果。

  6. 06

    做减法、一处扫描缺口,以及那个归档目录

    同一周里有两条 pull request 是删东西。大的一条从 core crate 里删掉24 个模块、约 12,500 行,没有任何代码路径用到它们——不是二进制、不是测试与基准、不是工作区里其他 crate,也不是外部 SDK。其中六个曾在更早的 changelog 条目里作为研究模块被宣布过,这一点值得记下来:把一个模块发出去和把它讲出来,看来是两件互不相干的事。删之前作者查了两个 v4 分支,因为更早一轮删除曾经删掉了待合并分支仍然需要的东西;另有一个模块明知没有二进制路径调用也被特意留下,作为一条库边界。同一周还暴露出一处更旧的缺口:代码扫描只覆盖 JavaScript、Actions 与 Rust,没有 Python,而被停用的默认配置曾经是唯一扫 Python 的东西,于是自四月起 _archive/ 之外的 83 个 Python 文件完全没有被扫描过,其中包括要发给用户的那个插件和跑安全门禁的那些脚本。归档目录里放着退役的表面——JetBrains(86 个文件)、VS Code(25 个)、Chrome、Emacs、Sublime 与 Neovim 的客户端,两代 Python SDK,一个 Go SDK,发布素材,以及四个生成演示 GIF 的 VHS 磁带脚本——还有一样不太常见的东西:一个 Lean 工程,用机器检查的证明去证压缩读取模式、terse 引擎与它的质量性质、机密安全、一个交接状态机,以及包括预算执行与路径隔离在内的策略性质,旁边还有一篇 16,370 字节的论文。仓库根目录上 CHANGELOG.md 有 950,445 字节,是整棵树里最大的文本文件。

相关档案

全部档案 →