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

这是 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 的做法:

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

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

9. 适用边界

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

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

相关推荐
Linye12 分钟前
AI 写的 HTML 原型全是空壳?聊聊真实 DOM 交互避坑
前端·人工智能
Jvav练习生21 分钟前
AI Agent 总在关键时刻“失忆”翻车?一文讲透上下文工程(附可运行 Python 代码)
后端
ZANE21 分钟前
纯前端实现的 PDF 与图片互相转换工具技术实现
前端
leo在掘金28 分钟前
只挂了11小时,5000台开发机被塞进SSH公钥
后端·架构
xjz184233 分钟前
Rust 运算符优先级:括号不只用来改变顺序
后端
写码跑山的老艾34 分钟前
为什么有了 MySQL,还要 Redis 和 Elasticsearch?从一件商品看数据系统的权衡
后端
对象存储与RustFS40 分钟前
Jenkins 的构建产物别堆在控制器磁盘:用 RustFS 当它的 S3 工件仓库
后端·docker·云原生
王中阳Go40 分钟前
100 件货卖出 116 件,库存停在 -16
后端·mysql·go