给 Coding Agent 加一道"架构闸门":Jin 是如何做到"先审架构、再写代码、不漂移"的

2 阅读5分钟

这是 Jin(一个 architecture-first 的桌面 coding agent)的核心设计拆解。不吹模型,只讲工程。

0. 问题:为什么"先写代码"的 agent 让人不敢用生产

Codex / OpenCode / Claude Code 这类工具,工作流程是:

需求 → 模型调工具(读文件/写代码/跑测试)→ 甩出 diff → 你 review → 合并

痛点集中在最后两步:diff 越大,review 越贵;review 不完,不敢合;不敢合,就没人敢在真实项目里放开手脚。 而且中途模型很容易"漂移"——加了没要求的东西、改了没授权的地方、忘了最初的意图。

Jin 的思路是把闸门前移到"意图/架构层",而不是"代码层":

需求 → 模型产出【架构变更提案】 → 人审提案(批准/驳回) → 才允许实现 → 自动验证+自修 → 版本化

下面逐个讲关键实现。

1. 架构台账(Ledger):把"这个项目长什么样"变成可查询的事实

Jin 启动时对代码库做扫描,建一张 L0(包/目录)/ L1(模块)/ facet(职责面) 的图,加上 LSP 拿到的符号定义,落成持久化的 snapshot。

它的作用不是"展示",而是当实现的边界约束:

  • 提案里的每个 op 都引用台账里的 node(node.add / node.update / node.remove)。
  • 实现阶段,模型只能改提案批准过的 file 白名单。越界 → 工具层直接拒。

这一步就压住了漂移的一半:模型没机会在批准范围外动手。

2. 提案 = 你 review 的对象(不是 diff)

需求进来后,planner 产出的不是代码,是一个 JSON 结构:

  • kind: plan,title / goal / message
  • tasks[]:每个任务有 files[]、acceptance[](验收标准)、dependsOn
  • nodeIntents[]:这次要动哪些 L1/facet
  • ops[]:结构变更(增删改 node/edge)

你在 UI 里看的是这个提案 + 影响面,不是几百行 diff。批准后它才进入实现。

关键点:验收标准(acceptance)是自然语言写的,实现后有一步 acceptance review——模型对照"你当初要的效果"逐条检查代码是否满足。这是"意图一致性"的自动兜底。

3. 符号级上下文:别把整仓喂给模型

这是我认为最有价值的一块。传统 agent(OpenCode/Codex)实现时上下文要么是整文件、要么靠模型自己 grep。Jin 的做法:

// 用 LSP 符号索引,只取"这次要动的符号"前后 40 行
for (const path of wantedPaths) {
  const symbols = lspSymbols.filter(s => s.path === path);
  body = symbols.map(s => slice(lines, s.line - 40, s.line + 40)).mergeOverlaps();
}
  • wantedPaths 由架构台账推出(op 里的 node.file + nodeIntents + 复用候选)。
  • 节点没写 file 时,用 LSP 符号名反查定义所在文件补进去;查不到的,才提示"用 grep/glob 定位"。
  • 目标文件里既有的 LSP error/warning 也拼进 prompt 末尾:"这里本来就有问题,顺带修掉"。

效果:模型看到的不是 800 行整文件(其中 700 行无关),而是"精确相关的符号块 + 已知待修问题"。token 省了,漂移少了,还顺带修既有 bug。

4. 实现闭环:写码 → 跑检查 → 失败自修

实现用的是 Eko 引擎(一个 agent 运行时,工具循环)。Jin 在 runEkoCoder 里做的事:

  1. 工具集:read_file / write_file / apply_patch / run_shell / delete_file(都是 staging 式,先入虚拟工作区,批准后才落盘)+ grep / glob / search_code(RAG) / architecture / todo_write / view_image / fetch_url / web_search / mcp__*。
  2. 上下文:上文第 3 点的符号级相关代码。
  3. 收尾前:agent 主动调 run_checks(跑项目自己的 test/typecheck/lint)。
  4. 失败回灌:实现后 Jin 再跑一遍 runProjectChecks,失败的把输出喂回 Eko 自修一轮,再重跑。
  5. 一致性校验:重扫代码库,检查批准的架构是否真的落成(node/facet 在不在位、文件是否出现)。

也就是说"代码写对"不靠模型自觉,靠自动检查 + 失败自修 + 一致性校验三道。

5. 版本化与撤销:让"改了"可逆

  • 每次实现是一个 WAL 版本,可回放、可 diff 两个版本、可重放。
  • 实现前 git stash create 打一个悬空 commit 当 checkpoint;UI 上"撤销本轮" = git restore --source=<checkpoint>,不动 HEAD。
  • 危险操作(删文件、改 git 历史、管道执行远端脚本)在 shell 层有黑名单 + 审计(.jin/cache/shell-audit.jsonl)。

6. 上下文管理:像 Codex 一样压缩,但多了"本地裁剪"

会话长了会爆上下文。Jin 的压缩策略(compactionMode):

  • trim(本地裁剪,无模型):按轮次把旧消息切成"用户请求原文 + 助手结论尾巴",丢掉中间的 reasoning/tool 噪音。即时、离线、确定。
  • llm(摘要):让模型把旧半段写成固定结构的 handoff(Goal/Decisions/Work/Open questions/Next steps)。
  • auto(默认):先 trim,trim 完还超预算才升级 llm;llm 失败自动回退 trim。

阈值 = 上下文窗口 × 压缩比 × 0.45(架构 prompt 本身占大头,所以提前压)。

7. 其它工程细节(一句话版)

  • 多 provider:OpenAI Chat/Responses、Anthropic Messages、Gemini generateContent、Ollama。模型预设表自动带上下文窗口/价格/vision。
  • MCP:tools/list 元数据进工具表(mcp__server__tool),结果序列化为文本,全走审计。
  • AGENTS.md:项目级 + 全局两层,规划/实现提示词自动注入,改完即时生效。
  • Hooks:.jin/hooks.json 的 preToolUse(可拦截)/postToolUse(审计)/beforeImplement;.jin/commands/*.md 自定义斜杠命令。
  • LLM 诊断日志:JSONL + 控制台,空流/工具不支持/HTTP 降级都有记录,排障不用猜。

8. 它和"裸用 OpenCode/Codex"到底差在哪

不是"多一个工具",是多了一道闸门 + 一套边界约束:

OpenCode / CodexJin
review 对象代码 diff架构提案(影响面)
漂移控制靠模型自律 + 你 review白名单越界即拒 + 一致性校验
上下文整仓 / 模型自 grepLSP 符号级 + 既有诊断
正确性兜底跑测试(手动/工具)自动检查 + 失败自修 + 一致性校验
可逆性git(你手动)WAL 版本 + checkpoint 一键撤销

一句话总结:Jin 不让你 review 代码,它让你 review 意图;正确性交给自动闭环。

9. 适用边界

  • 适合:中大型、多模块、有架构边界、需要可审计的项目;你当负责人/架构师。
  • 不适合:一次性脚本、demo、快速原型——直接 Claude Code 更快。
  • 如果你压根不想做"提案审批",Jin 的核心价值就没了。

开源github.com/ZYF93/Jin,欢迎 PR / 拍砖。