0%

本文区分官方事实、他人经验、本地实验、工程判断,最后核对时间为 2026-07-30。文中的一次实验只能说明这条流程如何运转、会生成什么、在哪里失效,不能当作工具提升任务成功率的 benchmark。

概念分层和工具边界见 AI Agent工程化地图。本文只记录这条具体开发链路的复现与判断。

为避免把不同强度的证据混在一起,正文使用以下标签:

  • [官方事实]:来自固定版本的仓库、包元数据或官方文档;若正文没有成功获取,会明确写成“本次未核验”。
  • [他人经验]:来自参考文章的实践主张,不替代当前版本文档。
  • [本地实验]:来自可丢弃仓库复现或本地缓存源码静态核验;正文会注明具体方式,结论受实验设置限制。
  • [工程判断]:基于上述材料给出的采用建议,不是厂商承诺。

一、我为什么试这条链路

我关心的不是再给 Codex 叠一层提示词,而是一个更具体的问题:一句模糊需求进入仓库后,怎样留下足够稳定的需求、上下文、测试和 Git 证据,使下一次会话仍能继续工作?

以“给设备告警增加确认功能”为例,真正影响实现的不是 acknowledge() 这个方法名,而是下面这些没有写进原需求的决策:谁能确认、操作人是否必填、重复请求是幂等还是冲突、时间从哪里来、未知告警怎样处理,以及本次是否顺带引入持久化。Codex 可以通过读仓库和追问补齐它们,但如果结果只留在对话里,长任务交接时仍会丢失。

[工程判断] 我把项目级 Harness 理解为三类东西的组合:

  1. 约束 Agent 行为的仓库指令和 workflow guidance;
  2. 跨会话保存意图与状态的项目文件;
  3. 能独立运行的测试、校验脚本、CI 和 Git 记录。

这三类东西的可信度不同。提示词告诉 Agent“应该做什么”,脚本和测试只有在被调用时才会产生机械结果,PRD 则只保存已经做出的决策。把它们统称为“自动化”会掩盖关键边界。

二、参考方案究竟主张什么

[他人经验] 参考文章 Grill x Trellis 提议先用 Matt Pocock 的 grill-me 追问需求,再把共识转成 Trellis 的 PRD/Spec/Journal,最后由编码 Agent 实现、检查和收尾。它认为这种分工能覆盖从需求澄清到知识留存的生命周期,同时也承认 token 成本、团队纪律、回答质量、Spec 维护和版本变化会影响收益;对于一次性原型、很小的个人项目和边界明显的小改动,完整流程回报较低。

这个思路有用,但需要先把“原版来源”“第三方扩展”和“当前 Trellis”拆开。

[官方事实] 在 2026-07-29 观察到的 mattpocock/skills 固定树 2ab9580 中,grill-me 只委托进入 grilling。后者要求:每次只问一个问题、每个问题给出推荐、能从环境查到的事实先查、在采取行动前确认双方理解一致。这个原版没有强制生成根目录 PLAN.mdSPEC.md,也没有规定“总结之后必须再发一条单独批准消息”。

网上还能找到 chaseai-yt/grill-me-codex[本地实验] 本次做的是本地缓存源码静态核验:拿到的哈希缓存归档前缀为 fe37a70,归档 SHA-256 为 668bd9ea4cae8991a402e5d69e8b8842b28db8eb99b95c1e087aa600ca8d7366。源码指示 Agent 生成 PLAN.mdPLAN-REVIEW-LOG.md,并反复执行只读 Codex 审查;本次没有验证这些步骤的实际执行。它不是上面那个 Matt Pocock 原版,而且没有解析出完整上游 commit/date,因此不能用它的指示反推当前上游行为。

[官方事实] Trellis 0.6.10brainstorm 模板也要求每次只问一个由用户决定的问题,并先检查代码、测试、配置、文档、spec 和任务历史。它还指导 Agent 维护 prd.md,复杂任务补充 design.mdimplement.md,汇总后等待后续显式批准,再调用任务启动脚本。这里的“指导”和“等待”是 prompt/workflow guidance,不是独立的审批事务引擎。

所以两者的重叠点是提问节奏和先查仓库,差异是 Trellis 继续管理项目内产物、任务状态、检查和归档。它们不是两个必须串联的互补模块:当 PRD 已经覆盖未决事项时,再跑一轮 grilling 可能只是重复消耗;当需求仍含高代价决策时,grilling 才有增量价值。

三、先纠正几个版本与产物问题

1. 平台数会随版本漂移。 参考文章在 2026-06-29 写的是 16 个平台;[官方事实] Trellis v0.6.10 README 写的是 20 个。数字变化说明适配面在变,不证明不同 adapter 的功能完全对齐。

2. 稳定版与 beta 要分开。 [官方事实] npm 快照显示 @mindfoldhq/trellis 稳定版为 0.6.10,发布时间 2026-07-28T10:08:12.368Z;beta 为 0.7.0-beta.0v0.6.10 和观察时的 main 都解析到 c94d6fc289b7a6fdd9480bdfae4d4639c9ac2d4c。许可证元数据是 AGPL-3.0-only[工程判断] 引入企业仓库前,应由团队评估分发和合规边界。

3. 根目录 PLAN/SPEC 不会自动变成 Trellis 任务。 [本地实验] 初始化前,我在根 PLAN.mdSPEC.md 放入唯一 sentinel。trellis init 和任务创建后,sentinel 仍只存在于原文件。对 0.6.10 的完整顶层帮助、各子命令帮助和命令注册源码进行限定搜索,也没有发现导入任意根 PLAN.md / SPEC.md 的命令或处理器。后来的 prd.mddesign.mdimplement.md 都是手工映射,不是自动转换。

4. 当前 Codex 侧名称与旧文章不同。 [本地实验] 0.6.10 实际生成的 Codex skills 是 trellis-starttrellis-checktrellis-finish-work。没有生成 trellis-record-session;journal 记录被并入 finish 的指导流程。trellis-checktrellis-finish-work 也不是 shell executable,而是 Agent 读取的 skill。

5. 代码搜索不是最后追加的一站。 [工程判断] 对仓库事实的检查应该发生在需求澄清、计划、实现之前和过程中。先查现有状态模型与测试,才能提出正确问题;改动时还要继续定位调用者、惯例和影响面;验证时再核对 diff。把搜索工具排成“Codex 写完之后才执行的 codebase-memory 阶段”,会让前面的规格建立在想象而不是代码上。

四、一次真实复现:设备告警确认功能

1. 基线与模糊需求

[本地实验] 我在一个小型 Node.js fixture 中使用这句起始需求:

Add alarm acknowledgement so an operator can acknowledge an active device alarm.

先读仓库得到四个事实:

  • src/alarm-store.jsMap 保存告警,初始 statusactiveacknowledgementnull
  • 时间由可注入的 ISO 时间函数提供;
  • raise()get() 返回结构化克隆;
  • 基线只有 raise/get、拷贝隔离、重复 raise、缺失 get 四项测试,没有 clear/resolve 或持久化。

安装步骤本身也留下了边界。skills@1.5.21 执行:

1
skills add mattpocock/skills --skill grill-me --agent codex -y

因为无法解析 github.com退出码是 1,网络安装没有成功。实验随后使用带 SHA-256 记录的 Matt Pocock 官方来源缓存作为 fallback。这个降级路径保证能复核采用了哪份文本,但不能写成“已成功安装 grill-me”。

2. grill-me实际问出了什么:规则驱动的预设决策记录

前面的安装已经退出 1。下表八个问题是依照哈希缓存中的一次一问、先查环境等规则整理的;accepted answers 来自 Task 4 预设实验契约,不是一次成功安装后的自由访谈。本实验验证的是怎样把模糊请求映射成验收决策,不验证 grill-me 的自主问答效果。

未决问题 接受的决定 对实现/测试的影响
哪些状态可确认 只允许 active 告警 不发明不存在的历史状态语义
操作人身份 operatorId 必填且不能全是空白 增加输入校验
备注 可选,缺省归一成 null 保持记录结构稳定
审计字段 { operatorId, note, acknowledgedAt } 时间来自注入时钟
同一操作人重试 返回原告警,不再次读取时钟 幂等且保留首次审计事实
不同操作人再次确认 拒绝 不静默覆盖首次记录
未知告警 ID 拒绝 确认不是 upsert
clear/resolve 与持久化 不在本次范围 控制变更面

原始一句话只部分表达了“active-only”,没有定义校验、重试/冲突、记录结构、时钟来源、克隆边界和范围排除。[工程判断] 这类规则驱动澄清的潜在价值不在于问题数量,而在于把会改变测试 oracle 的用户决策提到写代码之前;这项价值仍需在真实交互中另行验证。

这份预设记录覆盖了 Trellis brainstorm 和 grilling 共同关注的部分维度,但 prompt 执行不是确定性的。本次实验不能证明两者会逐字、按相同顺序问出这八题;当八项决定已经写入 PRD 后,Trellis 的收敛指导应只处理剩余缺口,这仍是预期的 Agent 行为,不是防重复的机械保证。

3. 怎样交接给Trellis

实验使用控制器提供的本地固定版 @mindfoldhq/trellis@0.6.10。推荐把命令中的路径按 task.py create 实际返回结果解析,不要假定日期目录:

1
2
3
4
5
6
7
8
9
trellis init --codex -u harness-lab
TASK_PATH="$(python3 ./.trellis/scripts/task.py create "Add alarm acknowledgement" --slug alarm-acknowledgement)"
PRD_PATH="$TASK_PATH/prd.md"

# 把八项决定手工写入 "$PRD_PATH";复杂任务再维护 design.md / implement.md
python3 ./.trellis/scripts/task.py add-context "$TASK_PATH" implement AGENTS.md "Repository rules"
python3 ./.trellis/scripts/task.py add-context "$TASK_PATH" check AGENTS.md "Repository rules"
python3 ./.trellis/scripts/task.py validate "$TASK_PATH"
python3 ./.trellis/scripts/task.py start "$TASK_PATH"

prd.mddesign.mdimplement.md 等任务产物留在任务目录内,由任务创建和后续手工维护,Trellis guidance 会单独读取它们;推荐命令不把 task-local PRD 再写入 JSONL context。

[本地实验] 结果是:init 退出 0,task.py create 退出 0;旧的 task.py init-context 退出 2,并明确提示它已在 v0.5.0-beta.12 移除。为了验证失败边界,本实验还额外把 $PRD_PATH 分别加入 implement/check;加上推荐路径中的两个 AGENTS.md context,共四次 add-context,每次都退出 0。随后 validatestart 也退出 0,任务状态从 planning 变为 in_progress。这个 task-local PRD 自引用是冗余配置,并在 archive 后变 stale,不是推荐命令。

这里必须再次强调:根 PLAN/SPEC sentinel 没有被带入任务。八项预设决定是从规则驱动记录手工映射prd.md 的,不是自动转换。生成说明表示任务产物会被单独读取,JSONL 主要放 spec/research 上下文。

4. Codex怎样实现和验证

计划不是把 PRD 原样塞给 Codex 后等待结果,而是从可失败测试开始,并在实现中继续搜索现有 clone、错误和时间注入惯例:

1
2
3
4
5
6
7
8
9
读 AGENTS.md、prd.md、现有 store 和测试
-> 新增 5 个验收测试
-> RED:确认失败原因是 acknowledge 缺失
-> 实现最小状态转换
-> GREEN:运行项目测试
-> 查看 diff、任务上下文和范围
-> 再运行测试与 whitespace check
-> 提交产品变更
-> 显式执行 archive 和 journal 脚本

RED 阶段,npm test 退出 1;直接运行同一测试文件得到 9 项中 4 pass、5 fail,五项新行为都因 acknowledge 不存在而失败。GREEN 和最终阶段,npm test 均退出 0,直接运行是 9/9 pass、0 fail

trellis-check 提醒 Agent 看 diff、任务产物、spec 和项目检查,但测试之所以真正运行,是因为执行了 npm test。同理,trellis-finish-work 会提示当前任务还有未提交代码时停止;实验确实先提交功能,再显式调用:

1
2
3
4
5
python3 ./.trellis/scripts/task.py archive alarm-acknowledgement
python3 ./.trellis/scripts/add_session.py \
--title "Alarm acknowledgement" \
--commit "a6a6f92" \
--summary "Added deterministic, idempotent alarm acknowledgement with conflict and validation tests."

archive 和 journal 脚本被显式调用后才产生机械效果,分别自动提交 65566515737c20。初始化和任务流程没有自动创建或切换 Git worktree,worktree_pathnull;任务内容也没有自动提升到 .trellis/spec/

5. 生成了什么,付出了什么

功能提交 a6a6f92 共改变 115 个文件、增加 18,686 行

  • 111 个 harness/config/generated 文件:.agents/.codex/.trellis/.gitattributes
  • 2 个产品文件:src/alarm-store.jstest/alarm-store.test.js
  • 2 个实验 fixture:根 PLAN.mdSPEC.md

其中 emitted adapter files 共 54 个.agents/skills/ 下 46 个,.codex/ 下 8 个(3 个 agent TOML、1 个 config、1 个 hooks JSON、3 个 hook Python)。这是一次 blank-template offline fallback 初始化的实测 footprint,不应外推成每个仓库每次都会产生相同行数。

还观察到两个需要精确限定的失败:

  1. staged git diff --cached --check 退出 2,在 3 个未修改的 Trellis 生成文件中报告 4 条空白诊断:两个 EOF 多余空行和两处行尾空白。它说明生成产物也要进入 diff 门禁,不说明产品代码有这四个问题。
  2. 归档前 task validation 退出 0;归档后退出 1。原因是实验手工把 task-local prd.md 自引用写入两个 JSONL,归档移动目录后保留了旧 active-task 路径。生成说明表示 task artifacts 本来会单独读取,因此这个结果受实验设置约束,不能单独证明 archive 存在缺陷。

[工程判断] 这笔账的核心不是“18,686 行太多”,而是这些文件是否换来了当前项目需要的跨会话状态。如果需求一天完成、一个人维护、CI 足够清晰,111 个框架文件会变成审查和升级成本;如果任务跨周、多人/多 Agent 交接,而且 PRD、context、journal 真会被使用,这些文件才可能有回报。

五、不同项目不要使用同一套流程

下面是我会实际采用的三层,而不是把 Trellis 设为默认前置条件:

层级 Trigger Files Commands Exit condition Maintenance cost
smallAGENTS.md -> Codex -> verification -> diff 边界清楚、单会话可完成、影响面小 现有 AGENTS.md、产品代码、测试 读指令与代码;运行定向测试/构建;git diff --checkgit diff 验收测试通过,diff 可解释,没有遗留状态需要交接 低:维护仓库指令和既有 CI
mediumalignment -> SPEC/PLAN -> Codex -> CI 有若干产品决策或跨模块改动,但任务仍能由一个短期分支承载 轻量 SPEC.md / PLAN.md 或 issue、产品代码、测试 一次一问澄清;写验收标准;Codex 实现;运行 CI 未决项归零,规格与测试一致,CI 通过,Git 历史可审查 中:规格要随变更更新,避免重复文档
complex-long-runninggrill-me when needed -> Trellis task/context -> Codex/check -> Git/CI -> archive 跨周、多人或多 Agent 交接,上下文分散,确实需要任务状态与 journal .trellis/tasks/、必要 specs/context、产品代码、测试、journal 按需 grilling;task.py create/add-context/validate/start;实现与检查;Git/CI;显式 archive、journal 独立验证通过,工作代码已提交,任务归档且后续会话能从项目产物恢复意图 高:生成文件审查、版本升级、上下文清理、归档路径和许可证治理

两个升级触发器最实用:第一,关键决策在对话结束后还要被别人使用;第二,任务的验证和上下文已经无法用一个 issue 加 CI 清楚表达。反过来,如果 PRD 没人读、journal 没人接续、任务一两个小时就结束,就停在 small 或 medium。

small:单会话小改动

  • 适用 / 不适用 / 升级: 需求和验收已经明确、改动可在一个会话内完成时使用;若仍有会改变测试结果的产品决策,就先升级到 medium;不为跨周交接保留临时任务状态。
  • 文件职责: AGENTS.md 只保存长期仓库规则;产品文件承载实现;测试文件承载可重复验收,不另建一次性 PRD。
  • 执行: Codex 先读规则、现有实现和测试,再修改并运行仓库已有命令。本文所在 QQsNote 可直接执行:
1
2
3
4
npm test
npm run build
git diff --check
git diff
  • 退出 / 成本: 测试和构建通过、diff 可解释且无待交接状态即结束;成本只有既有规则和 CI 的维护。
  • Claude Code: 复用相同的仓库文件和 shell 验证,只替换宿主 Agent 的调用入口;本次未取得当前官方正文,因此不补写未经核验的专用命令。

medium:短期分支上的明确功能

  • 适用 / 不适用 / 升级: 有若干产品取舍或跨模块接口、但一个短期分支仍能承载时使用;如果状态要跨周、跨人员或跨 Agent 恢复,再升级到 complex-long-running。已有 issue 已完整承担规格职责时,不重复创建 SPEC.md
  • 文件职责: SPEC.md 只写需求、范围和验收;PLAN.md 只写实现顺序、依赖和验证;产品代码与测试分别承载行为和 oracle。两份文档完成后删除还是保留,应由仓库约定决定,避免出现第二事实源。
  • 执行: 对齐未决项并写入 SPEC/PLAN,Codex 实现后运行本地 CI 等价命令,再由远端 CI 重放。QQsNote 的本地门禁是:
1
2
3
4
npm test
npm run build
git diff --check
git diff
  • 退出 / 成本: 未决项归零、SPEC 与测试一致、本地门禁和远端 CI 通过、Git 历史可审查即结束;额外成本是保持计划、规格和代码同步。
  • Claude Code: 继续使用同一 SPEC/PLAN、测试和 CI;只替换规划与编码会话入口,不假设其当前原生命令与 Codex 等价。

complex-long-running:需要可恢复任务状态

  • 适用 / 不适用 / 降级: 任务跨周、多人或多 Agent 交接,并且后续会话确实要消费 task/context/journal 时使用;若这些产物连续多个任务无人读取,就降回 medium 并移除冗余状态。
  • 文件职责: task-local prd.md 保存本次需求和验收,design.md 保存技术契约,implement.md 保存执行步骤;JSONL 只列实现/检查需要的稳定 spec 或 research context;.trellis/spec/ 只保存跨任务长期规则;journal 只记录跨会话过程和决策历史。
  • 执行: 在完成需求对齐和手工产物映射后,使用 Trellis 脚本推进状态;不要把 task-local PRD 再自引用进 JSONL:
1
2
3
4
5
6
7
8
9
10
TASK_PATH="$(python3 ./.trellis/scripts/task.py create \
"Add alarm acknowledgement" --slug alarm-acknowledgement)"
python3 ./.trellis/scripts/task.py add-context \
"$TASK_PATH" implement AGENTS.md "Repository rules"
python3 ./.trellis/scripts/task.py add-context \
"$TASK_PATH" check AGENTS.md "Repository rules"
python3 ./.trellis/scripts/task.py validate "$TASK_PATH"
python3 ./.trellis/scripts/task.py start "$TASK_PATH"
npm test
git diff --check

实现、检查和 Git/CI 通过并提交代码后,再显式执行归档与 journal;脚本不会因为 skill 文本存在而自动运行:

1
2
3
4
5
6
COMMIT_SHA="$(git rev-parse HEAD)"
python3 ./.trellis/scripts/task.py archive alarm-acknowledgement
python3 ./.trellis/scripts/add_session.py \
--title "Alarm acknowledgement" \
--commit "$COMMIT_SHA" \
--summary "Implemented and independently verified alarm acknowledgement."
  • 退出 / 成本: 独立验证通过、代码已提交、任务已归档,且新会话能只凭仓库产物恢复意图才结束;成本包括生成文件审查、升级、上下文清理、归档和许可证治理。
  • Claude Code: Trellis 核心 task 脚本与仓库产物可以保持不变,但宿主 adapter、skill 名称和 hooks 必须按当前版本重新核验;本文不根据未取得的官方正文推定与 Codex 完全相同。

六、Trellis、Spec Kit、Superpowers和原生Codex怎样选

先限定迁移场景:将已经提交的项目迁回 native Codex + AGENTS.md,同时保留 requirements、tests 和有用的 Git history。下表的 migration surface 是工程判断,不是评分。Trellis 固定在 0.6.10;Spec Kit、Superpowers 上游完整 commit/date,以及 Codex 当前官方正文,本次没有全部拿到,因此对应单元格保留 bounded/inconclusive,不能据此定量排名。

固定维度 Trellis 0.6.10 GitHub Spec Kit Superpowers(本地缓存 11c74d6b 原生 Codex + AGENTS.md
Main artifacts .trellis/spec/task.jsonprd.md,可选 design.md / implement.md,JSONL context、journals Bounded / inconclusive:候选 constitution 与 feature artifacts,本次未做固定 commit 核验 已观察的 design/plan 文档、skills、测试与 Git 产物 Bounded / inconclusive:官方正文不可得,本次不声明等价的 PRD/journal 产物
Persistent state task JSON + 仓库 journals Bounded / inconclusive:未固定版本核验状态模型 plan checklists + Git history;范围限于本地缓存 Bounded / inconclusive:background/resume 与项目状态正文未核验
Prompt/workflow guidance 生成模板指导 brainstorm、check、finish 和批准转换 Bounded / inconclusive:当前模板未固定版本核验 缓存 skills 对规划、TDD、review、verification、subagent、worktree 给出命令式指导 仓库指令和 Skills 是候选指导面,但当前官方正文未取回
Mechanical enforcement task.py 可在显式调用时 create/validate/start/archive;journal 脚本可记录;模板文字本身仍是 advisory Bounded / inconclusive:CLI/check 行为未固定版本核验 测试和 Git 命令只有执行时才机械生效;本次缓存未建立独立 policy engine hooks、sandbox、approvals 是待核验候选面;本次不补写具体能力
Host binding 共享 .trellis/ core 加 host adapters;本次观察到 Codex adapter Bounded / inconclusive:未固定版本核验 harness-specific 安装,主体是 Markdown skill 内容 Bounded candidate:Codex 专用配置/钩子与纯 Markdown 指令的边界待官方正文核验
Migration surface 保留 requirements/tests/history;翻译有用 PRD/spec;替换或删除 task JSON/JSONL、journals、scripts、hooks、generated adapters 先固定版本审计;保留仍有用的 Markdown spec/plan/task,替换或删除 .specify/ 模板与命令 保留 design/plans/tests/history;移除 plugin/skills,把仍需的流程写回 AGENTS.md 或原生 skills 目标基线;只翻译来源系统仍有用的约定,继续保留 requirements/tests/history

选择时我会先问“需要保存哪一种状态”,而不是“哪个框架功能更多”:

  • 只需要稳定仓库约束和独立验证,原生 Codex + AGENTS.md 是最小基线;但本次官方正文获取失败,我不据此声称它复刻 Trellis journals 或任务状态。
  • 需要 feature spec 驱动流程时,Spec Kit 是候选;本次没有完成固定版本正文核验,先做小范围试用再决定目录约定。
  • 希望把设计、计划、TDD、review、verification 和 worktree 组织成一组 skills 时,Superpowers 本地缓存展示了这一路径;它的 workflow guidance 也不能代替测试执行。
  • 需要仓库内任务状态、JSONL context、归档和 journal,且愿意承担生成物与合规维护时,再选 Trellis。

Claude Code 只补一条差异边界:本次列出了 Anthropic 关于 memory、skills、hooks、subagents、permissions、worktree workflow 的候选官方入口,但没有成功取得页面正文、最终跳转 URL 或 HTTP 状态。因此没有完成同版本核验,也不作 Claude Code 与 Codex/Trellis 的 parity 结论。

七、我的结论

这次实验证明了什么: 一句模糊告警需求可以经过八项决策变成明确测试;手工映射后的 Trellis task/context 能被 validate、start、archive 和 journal 脚本处理;Codex 在该 fixture 上完成了从 4 pass/5 fail 到 9/9 pass;同时我们测到了 adapter 数量、生成文件成本、advisory skill 与机械脚本的边界。

它没有证明什么: 没有 matched-task、固定模型/预算/仓库状态、多次重复和独立验收的对照实验,所以没有证明 Trellis 比原生 Codex + AGENTS.md 有更高成功率;没有证明两个 brainstorm 会稳定地产生相同问题;没有证明 PLAN/SPEC 会自动导入;没有证明 Codex、Claude Code、Spec Kit 和 Superpowers 在当前版本功能对等。

我的采用规则是:先用需求、测试、CI 和 Git 建立可迁移的证据,再按交接压力增加 Harness。若任务边界明确、单会话结束、生成文件比产品 diff 大得多,直接跳过 Trellis。若已经引入但连续任务没有消费 task state/journal,升级频繁造成维护负担,或许可证/adapter 配置不适合仓库,就在保留 requirements、tests、Git history 后卸载它,并把仍有价值的规则收敛回 AGENTS.md

八、这类项目层Harness会怎样发展

[工程判断] 我只做三个有证据约束的方向判断。

第一,portable artifacts 会比专用命令更长寿。可读的 requirements、acceptance tests、ADR/plan 和 Git history 能跨 Agent 迁移;任务 JSON、hook 和 adapter 则需要版本化转换。Harness 应允许前者脱离框架继续使用。

第二,independent verification 会成为中心,而不是收尾装饰。需求澄清和生成代码都可能来自同一个模型,不能让它们互相自证。可重复测试、静态检查、CI、diff review 和运行证据必须能由另一进程、另一 Agent 或人独立重放。

第三,native-agent convergence 会压缩外置适配层的价值区间。当宿主逐步提供项目指令、skills、hooks、权限或任务执行接口时,Harness 的差异会更多落在可迁移状态模型和团队工作流,而不是命令包装数量。由于本次没有取回 Codex/Claude Code 当前官方正文,这里只表达架构方向,不声称具体产品已经完成收敛。

主要参考资料

这篇解决什么问题

上篇:Flow Graph 入门:用 TypeScript 实现工业视觉流程执行器

上篇已经得到一条可运行的最小链路:

1
schema -> validate -> compile -> execute -> trace

但工业视觉和机器人应用软件还会遇到更难的问题:画布如何保存完整语义、一帧一帧的数据如何流动、下游变慢时如何限制上游,以及一个名字叫 execute 的节点为什么不能自动获得真实硬件能力。

这篇不再复制一套完整代码,而是建立下一阶段的工程边界。重点是:

  • React Flow 只是编辑器,不是运行时。
  • 流式不是“拓扑排序执行得快一点”。
  • 能自由拖线,不代表能自由获得设备副作用。
  • 工业安全必须由运行端能力和 fail-closed 规则保证。

1. 先回顾上篇的边界

上篇的执行器是确定性的批式 DAG:一张图运行一次,每个节点运行一次,下游等上游产出后继续。

它已经能证明:

  • 边精确连接端口。
  • 端口类型在加载期检查。
  • 必填输入不能漏接。
  • 顶层图不能有环。
  • 图先编译,再按稳定顺序运行。

它还不能表达:

  • 一个 source 连续产生多帧。
  • branch 未选中的路径如何标记。
  • 多个上游何时算“到齐”。
  • 队列满时上游该等待、丢弃还是降采样。
  • 什么条件满足后才允许真实设备动作。

接下来不是给现有 for 循环不断加 if,而是逐项定义这些语义。

2. React Flow 只负责编辑,不定义运行语义

React Flow 的 handle 很适合表示端口。关键是保存时不能把端口信息压扁成 A -> B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import type { Edge, Node } from "@xyflow/react";

interface FlowEdge {
id: string;
from: { node: string; port: string };
to: { node: string; port: string };
}

function toFlowEdge(edge: Edge): FlowEdge {
if (!edge.sourceHandle || !edge.targetHandle) {
throw new Error(`edge ${edge.id} must connect explicit ports`);
}
return {
id: edge.id,
from: { node: edge.source, port: edge.sourceHandle },
to: { node: edge.target, port: edge.targetHandle },
};
}

对应关系必须直接、可逆:

1
2
sourceHandle -> 输出端口 ID
targetHandle -> 输入端口 ID

节点坐标不影响执行,应单独保存:

1
2
3
4
5
6
7
8
9
interface EditorMetadata {
positions: Record<string, { x: number; y: number }>;
viewport?: { x: number; y: number; zoom: number };
}

interface EditorDocument {
graph: FlowDocument;
metadata: EditorMetadata;
}

拖线时的类型校验只是体验优化。后端或本地 runtime 仍要重新校验,因为 JSON 可能来自文件、CLI、测试 fixture 或旧客户端。

3. 编辑模型、编译模型和运行模型

把三种模型塞进一个巨大对象,往往会让 UI 状态、持久化格式和临时运行状态互相污染。

1
2
3
编辑模型:节点配置、端口边、坐标、分组和注释
编译模型:拓扑顺序、直接边绑定和资源引用
运行模型:消息 handle、节点状态、取消信号和 trace
1
2
3
4
5
6
7
flowchart LR
E[Editor Document] --> V[Validate]
V --> C[Compile]
C --> P[Static Plan]
P --> R[Runtime State]
R --> T[Trace]
T --> E

Flow 编辑不等于运行时必须创建重型消息系统

端口图是语义层。单进程执行时,可以在加载期把边解析为直接绑定:

1
detect.detection -> adjust.detection

运行时只需要拿到已绑定的上游输出并调用 runner,不必每个节点都查全局表,也不必为低频 DAG 引入分布式 broker。

图像和点云也不应沿边复制本体。端口消息传 FrameHandle 或引用,实际数据由资源存储管理:

1
2
3
4
interface FrameHandle {
frameId: string;
kind: "image_2d" | "point_cloud";
}

选择这种分离的代价是:schema 变更时要同时考虑编辑文档迁移和编译器兼容,不能靠一个对象“到处都能用”来逃避版本治理。

4. Fan-out、fan-in、barrier 和 branch

这几个术语经常同时出现,最好逐个定义。

Fan-out:一个输出发给多个下游

1
2
3
flowchart LR
C[Capture] --> D1[Detect Defect]
C --> D2[Estimate Pose]

若 payload 很大,两个下游应共享只读 handle,而不是各复制一份图像。

Fan-in:多个上游汇入同一节点

1
2
3
flowchart LR
D[Detection] --> A[Decision]
P[Pose Estimate] --> A

fan-in 只描述结构,不自动说明节点何时执行。节点需要一个明确的等待规则。

Barrier:等一组条件全部满足

例如 decision 必须同时收到 DetectionResultPoseEstimate。在批式 DAG 中,required inputs 到齐即可运行;在流式系统中,还必须知道哪些消息属于同一个工件或同一帧。

关联键至少包含:

1
(runId, itemId)

不能只按端口收集,否则可能把工件 A 的检测结果和工件 B 的位姿拼成一组。

Branch:只激活一条路径

branch 的两个输出可以是 passreview。未选中的路径应该标记为 skipped,不是 failed

这三种状态不能混为一谈:

状态 含义
missing 本应有数据,但尚未到达或确实丢失
skipped 分支语义明确决定不执行
failed 节点尝试执行后出错

如果不显式区分,fan-in 节点很难判断应该继续等待、跳过还是报错。

5. 从批式 DAG 到流式 Flow

批式 DAG 的执行单位是“节点的一次运行”;流式 Flow 的执行单位是“消息的一次到达”。

维度 批式 DAG 流式 Flow
调度单位 节点 消息或 item
source 产出 一次结果 多次 emit
完成条件 节点 Promise 完成 需要显式 end/complete
内存风险 一次运行的数据 队列可能持续增长
背压 通常不明显 必须定义
barrier required 输入到齐 同一关联键的数据到齐

一种最小流式 runner 接口是:

1
2
3
4
5
6
7
8
9
10
11
12
interface OutputEvent {
port: string;
itemId: string;
value: unknown;
}

interface StreamingNodeRunner {
run(
inputs: AsyncIterable<OutputEvent>,
context: RunContext,
): AsyncIterable<OutputEvent>;
}

Backpressure 不是性能优化,而是内存上界

假设相机每秒输出 30 帧,检测节点每秒只能处理 10 帧。若输入队列无限增长,系统只是把“处理不过来”延迟成“稍后内存耗尽”。

使用有界 channel 时,队列满后必须选择策略:

  • await:让 producer 等待,适合不能丢数据的离线处理。
  • drop-oldest:只保留新数据,适合实时预览。
  • sample:按频率采样,适合监控界面。
  • reject:明确失败,适合必须保证完整批次的流程。

策略属于业务契约,不能由底层队列随意决定。

实用升级顺序

  1. 先让一个 source 能连续 emit。
  2. 让一条线性链处理多消息。
  3. 增加 end 信号和取消。
  4. 增加有界队列和背压策略。
  5. 再加 fan-out。
  6. 最后实现按 (runId, itemId) 收集的 barrier。

不要一开始同时引入分支、循环、并行、流式和分布式执行。

6. 取消、超时、重试和 trace

取消必须传到真实 I/O

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function withTimeout<T>(
operation: (signal: AbortSignal) => Promise<T>,
timeoutMs: number,
parentSignal: AbortSignal,
): Promise<T> {
const controller = new AbortController();
const timer = setTimeout(
() => controller.abort(new Error(`timeout after ${timeoutMs}ms`)),
timeoutMs,
);
const cancelFromParent = () => controller.abort(parentSignal.reason);
parentSignal.addEventListener("abort", cancelFromParent, { once: true });

try {
return await operation(controller.signal);
} finally {
clearTimeout(timer);
parentSignal.removeEventListener("abort", cancelFromParent);
}
}

只在外层 Promise.race() 一个 timeout,会让调用方停止等待,却不一定停止底层 I/O。对设备动作而言,这是假取消:UI 显示已停止,设备请求仍可能继续。

重试要同时满足三个条件

  1. 错误明确标记为 transient(暂时性故障)。
  2. 操作幂等,或带稳定 idempotency key。
  3. 重复执行不会制造额外物理副作用。

网络读取可能重试;机器人移动、PLC 写入和工艺派发不能默认重试。

Trace 是统一的可观察性基础

至少记录:

1
2
graphVersion, runId, nodeId, status, durationMs,
retryCount, runMode, input/output summary, errorCode

图像和点云只记录 handle、尺寸和摘要,不把整个 payload 写进日志。Trace 同时服务于节点高亮、性能分析、失败定位、运行回放和安全审计。

7. L1 原子节点与 L2 复合节点

L1(Level 1)原子节点只做一件事,例如:

  • capture:产出 frame handle。
  • detect:frame -> detection。
  • collect:按关联键收集消息。
  • log:输出摘要。

L2(Level 2)复合节点把常用链封装成一个外部契约,例如:

1
InspectPart = capture -> detect -> aggregate

复合节点需要定义:

  • 对外 typed ports。
  • 外部端口到内部悬空端口的映射。
  • config 如何传入内部节点。
  • trace 默认折叠还是展开。
  • 子图版本和依赖。

选择 L1/L2 分层的收益是:开发者可展开调试,操作员可使用更少、更贴近工艺概念的节点。代价是复合节点需要单独的版本、迁移和 trace 展示规则。

8. 六个值得保留的架构决议

下面把设计演进整理成六张决议卡。它们是通用工程结论,不依赖某个具体项目。

8.1 从隐式黑板到 typed edges

背景问题:节点通过字符串后缀、全局变量名或扫描共享对象寻找上游数据。图能运行,但数据依赖不在图上,错误通常到运行期才出现。

考虑方案:继续靠文档约定;统一显式变量名;让业务产物通过 typed edges 传递。

最终选择:业务数据沿 typed edges 传递;context 只保留 run ID、日志、取消信号等运行能力。

理由:依赖可见,端口可在加载期校验,多个相同类型的 source 也不会因命名猜测而冲突。

代价:需要维护端口 schema 和图迁移,旧的隐式数据引用不能自动继续工作。

8.2 编辑是 Flow,执行是编译后的静态计划

背景问题:直接让运行时解释画布对象,会不断扫描边、读取 UI 字段,并把展示状态带入执行逻辑。

考虑方案:运行时直接解释编辑 JSON;每次执行动态查找;加载时编译静态计划。

最终选择:保存完整端口图,加载时校验、拓扑排序并绑定边,运行时消费静态计划。

理由:编辑语义完整,同时减少运行时动态查找;确定性也更容易测试。

代价:编译模型需要缓存失效和版本管理,编辑后必须重新编译。

8.3 L1 原子节点与 L2 复合节点分层

背景问题:全用原子节点会让操作画布被大量胶水步骤淹没;全用大黑盒又难以组合和调试。

考虑方案:只保留原子节点;只提供固定流程;原子节点与用户可复用复合节点并存。

最终选择:L1 提供清晰、可测试的原子能力,L2 封装高频工艺和受控流程。

理由:兼顾组合能力和操作员认知负担,调试时还能展开内部链。

代价:复合节点的端口映射、版本和 trace 展开需要额外设计。

8.4 教学节点不等于真实硬件能力

背景问题:把 registeradjustexecute 做成可自由组合且都能真实运行的节点,可能允许用户绕过必要的安全判断。

考虑方案:相信画布连线正确;在每个节点重复安全逻辑;把真实能力收敛到受控复合节点和 runner。

最终选择:细粒度节点可用于 simulate、教学和 dry-run;真实纠偏通过受控复合节点调用单一 runner。

理由:安全边界不依赖 UI,不会因为多一种连法就出现第二条设备写入路径。

代价:模拟图和真实能力不完全一一对应,UI 必须明确标识哪些节点仅用于模拟。

8.5 工位 barrier 与数值 gate 正交

背景问题:数据齐全不代表设备已到位;设备到位也不代表调整量安全。

考虑方案:只设一个综合布尔值;只检查工位;分别建模两道门。

最终选择:工位 barrier 检查数据和设备状态,数值 gate 判断调整量,两者都通过才允许真实动作。

理由:两道门处理不同故障,失败原因可观察,也便于分别测试。

代价:运行状态更多,需要明确超时、错误码和操作员处理流程。

8.6 批量流程把计算与派发分开

背景问题:计算节点内部直接下发设备,会让中间产物无法预览、重放和统一审核,多项计算也难以在最终派发前叠加。

考虑方案:边算边发;每个调整节点自行下发;先计算全部调整,再通过 barrier 统一派发。

最终选择:纯计算节点产出新数据,受控派发节点执行副作用。

理由:中间结果可 trace、可 dry-run、可比较;批量数据齐备后再进入统一安全路径。

代价:需要保存中间产物,并处理它们的版本、生命周期和一致性。

9. Simulate、real 与能力注入

run mode 不是 UI 上的装饰开关,而是 runtime 的强制策略。

Mode Runner Barrier Numeric gate Result
simulate absent any any run fixture only; no hardware
simulate present any any still simulate; no hardware
real absent any any fail-closed
real present not ready any fail-closed
real present ready reject fail-closed
real present ready accept controlled dispatch

节点名称不是权限

图中出现 execute,不代表它天然能控制设备。真实副作用还必须同时满足:

1
2
3
4
runMode == real
AND runner 已注入
AND barrier ready
AND numeric gate accept

runner 是 capability(能力)对象,只由应用装配层注入:

1
2
3
4
5
6
7
interface MotionRunner {
dispatch(command: ApprovedCommand, signal: AbortSignal): Promise<void>;
}

interface RuntimeServices {
motionRunner?: MotionRunner;
}

simulate 模式,即使 runner 存在也不能调用。real 模式缺 runner 必须拒绝,不能悄悄退化成“假成功”。

这种设计的代价是依赖注入和测试装配更复杂,但安全能力不会因节点配置或前端请求而凭空出现。

10. 两道正交的安全门

考虑“质检工位计算调整量,执行工位应用结果”的流程:

1
2
3
4
5
6
flowchart LR
I[Inspection Data] --> B[Station Barrier]
S[Station Ready] --> B
B --> G[Numeric Gate]
A[Adjustment] --> G
G --> R[Controlled Runner]

工位 barrier 检查

  • 所需检测、位姿和调整数据是否属于同一 runId/itemId
  • 设备是否到达指定工位。
  • 必需的握手信号是否在超时内满足。

数值 gate 检查

  • 调整量是否在允许范围。
  • 输入标定和坐标变换是否有效。
  • 决策是 Apply、Skip、Clamp 还是 Reject。

真实 runner 不接受未经 gate 的原始调整量。更稳妥的做法是让类型也表达这一点:

1
2
3
4
5
interface GatedCommand {
decision: "apply" | "clamp";
effectiveOffsetMm: number;
auditId: string;
}

fail-closed 的含义是:runner 缺失、barrier 未满足、gate 拒绝或状态无法确认时,结果都是“不执行真实动作”,同时留下可诊断错误,而不是猜测一个默认值继续。

11. 批量流程为什么要把计算与派发分开

工业视觉不总是拍一帧就立即动作。常见批量流程是:

1
2
3
4
5
6
7
采集多视点
-> 分别检测/配准
-> 按 itemId 映射到目标工艺对象
-> 汇总全部调整
-> 工位 barrier
-> 统一安全检查
-> 批量派发

建议的数据形态:

1
2
3
4
5
6
7
8
9
10
11
interface PlannedAdjustment {
itemId: string;
targetId: string;
offsetMm: number;
sourceFrameId: string;
}

interface AdjustmentBatch {
runId: string;
items: PlannedAdjustment[];
}

计算节点只产生 AdjustmentBatch,不持有 runner。派发节点只接受经过映射、barrier 和 gate 的批次。

这样做可在派发前完成:

  • 预览每个目标对应哪一帧、哪项调整。
  • 检查是否漏项或重复映射。
  • dry-run 比较调整前后的结果。
  • 记录统一审计 trace。

代价是必须定义批次完成条件。若某帧永远不到,需要明确 timeout 后是整批失败、跳过该项还是进入人工确认,不能无限等待。

12. React Flow 编辑器的最小实现边界

第一版编辑器只需要做到六件事:

  1. 从 registry 展示节点面板。
  2. 根据 typed ports 渲染 handles。
  3. 拖线时检查方向和类型。
  4. 用 schema 驱动属性编辑。
  5. 保存、加载完整 FlowDocument + EditorMetadata
  6. 把 trace 映射为节点状态和端口产物摘要。

状态展示应稳定,不因文案或图标改变节点尺寸:

状态 展示
running 高亮边框和当前输入
success 耗时、输出数量和摘要
failed 稳定错误码和错误位置
skipped 降低强调度并说明分支原因

不要在第一版加入多人协作、无限画布优化、自动布局市场、插件商店或真实设备控制。这些都不是验证 Flow 语义的必要条件。

工程验收清单

1. Typed graph editor

交付物:React Flow 节点面板、typed handles、拖线校验、保存加载和后端二次校验。

验收:错误类型不能连接;手改 JSON 后 runtime 仍能拒绝;保存再加载保持端口和配置不变。

面试价值:证明前端交互、schema 建模和执行契约能连成一个系统,而不只是画一张流程图。

2. Streaming frame demo

交付物:模拟相机连续产生 frame handle,检测节点较慢,有界队列提供可切换背压策略。

验收:长时间运行内存不随消息数无限增长;trace 能显示丢弃、等待或采样数量;取消后 source 和下游都停止。

面试价值:证明理解实时数据流、资源上界和可观察性,适合机器人应用软件与工业数据平台方向。

3. Simulate-only safety workflow

交付物:工位 barrier、数值 gate、run-mode 矩阵和审计 trace;runner 使用 mock,默认 simulate。

验收:缺 runner、barrier 未就绪或 gate 拒绝时均 fail-closed;任何测试都不能产生真实 I/O。

面试价值:证明能讨论工业副作用、权限边界和故障模式,而不是把“工作流执行”简化为函数调用。

对转型和面试的价值

这两篇最适合支撑以下定位:

  • 工业数字孪生与机器人可视化工程师。
  • 机器人应用软件工程师。
  • 设备集成、HMI 和工业数据流方向。
  • 工业视觉工作流与 AI 应用工程师。

它利用了已有的 TypeScript、前端交互和系统架构优势,同时补上图校验、调度语义、背压、trace 和工业安全边界。

需要保持职业表述克制:typed graph demo 和 simulate workflow 能证明系统软件思维,但不能证明实时控制、机器人全身控制、真实产线调试或商业机器人交付。要让能力更可信,下一步应补一段可运行演示视频、架构图、失败案例和测试报告。

AI Agent 的工程效果,不只取决于模型。任务怎样拆分、上下文怎样进入、工具怎样授权、状态怎样恢复、结果怎样验证,往往更能决定一次任务是否真正完成。

本文只负责概念边界、工作模式和升级条件,不是产品目录、安装指南或实践日志。具体的 grill-me -> Trellis -> Codex 复现实验见 Grill-me × Trellis × Codex实践:项目级Harness如何落地

最后核对:2026-07-30

一、先建立分层:Chat、Agent与Agent Team

Chat 主要生成回答。Agent 在回答之外形成“观察环境、选择动作、读取结果、继续或停止”的循环。Agent Team 则增加任务委派、上下文隔离、同步、仲裁和结果聚合。

可以把常见概念放进下面这张关系图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                Skills / instructions
(inject guidance)
|
v
+--------------------------------------------------+
| Harness |
| model + agent loop + context + tools + |
| permissions + verification + observability |
+--------------------------------------------------+
| tool/data connection | execution control
v v
MCP / native tools Workflow / durable runtime
(state/dependencies/retry/recovery)

Collaboration topology: subagents / multi-agent
Service interoperability: independent agent <-> A2A <-> independent agent

这些关系不是逐层依赖,而是帮助判断问题属于哪一类工程责任:

层级 主要回答的问题 关键边界
Model 内容或工具调用怎样生成 模型能力不等于端到端任务成功
Agent loop 何时观察、行动、重试和停止 有循环不等于有权限、恢复和审计
Skills 某类流程知识怎样按需进入上下文 prompt guidance 不等于机械执行
Harness 模型在什么工程环境里工作 同一模型换 Harness,结果可能不同
MCP / native tools 怎样连接工具和数据 连接能力不决定任务怎样拆
Workflow / durable runtime 状态、依赖、重试、恢复和审批怎样推进 流程控制不应隐含在长对话里
Subagent / multi-agent 协作拓扑怎样拆分、隔离和汇总 Agent 更多不自动带来质量或速度
A2A 独立 Agent 服务怎样互操作和回传 服务互通不等于内部规划或任务拆分

因此,“支持 MCP”不等于“支持多智能体协作”;给多个角色写提示词,也不等于拥有可恢复的工作流。

最稳妥的默认值仍是强单 Agent:先让一个 Agent 在完整约束和验证闭环下完成任务,再依据可观测到的瓶颈升级复杂度。

二、六类工作模式分别解决什么

1. Skills-driven Agent:复用流程知识

Skills 把散落在 Wiki、聊天记录和个人记忆中的 SOP,整理成可按需加载的流程知识。一个实用 Skill 通常说明触发条件、检查顺序、禁止动作、所需资料、产物格式、验收方式和停止条件。

Skill 可以提示 Agent 调用脚本或运行测试,但文字本身只是 guidance。只有脚本、测试、策略引擎或宿主权限机制被实际执行,才会产生机械约束。Skill 也不负责持久状态、事务恢复或服务间通信。

适合提炼 Skill 的信号是:同类任务反复发生,成败依赖固定检查顺序,并且产物能够被命令、schema、截图或清单验收。

2. Subagents:隔离少量独立子任务

Subagent 适合边界清楚、依赖较少、可以独立交付摘要或 patch 的子任务。关键不是给角色起名,而是写清输入范围、文件所有权、输出契约、局部验收和依赖关系。

只读调查和互不重叠的文件可以并行;共享接口应先约定;高冲突文件保持 single-writer。子 Agent 的一致意见不能替代测试,汇总后仍要做跨模块验证。

3. Agent Swarm:扩展宽搜索空间

Swarm 面向大量或数量未知的同构分片,例如批量扫描资料、搜索候选或从很多文件抽取相同字段。它需要明确的分片键、去重规则、覆盖率、聚合器、预算、并发上限和停止条件。

串行依赖链、共享写入、严格事务和设备控制都不是好的 swarm 场景。没有强单 Agent 基线时,也无法判断并行是否真正节省了时间或只是放大 token 与协调成本。

4. 确定性 Workflow:固定状态,局部使用 Agent

当步骤、状态转移、重试、幂等和审计能够预先定义时,应让普通软件管理流程,只让 Agent 处理分类、抽取、检索、解释或候选生成等开放节点。

1
2
3
4
5
6
7
事件进入
-> 读取结构化状态
-> Agent 生成候选
-> 规则校验
-> 必要时人工批准
-> 确定性执行器
-> 结果与审计记录

这样才能对重复消息、超时、进程重启和补偿路径做确定性测试,而不是让模型在对话里临时发明状态机。

5. 后台长任务:把状态移出对话

跨分钟、小时或天的任务不能依赖不断增长的聊天上下文。计划、检查点、artifact、事件、权限租约和预算需要外置;每次恢复只重建当前步骤所需的最小上下文。

长任务至少需要可查询进度、取消与恢复、心跳与超时、幂等键、原始证据追踪,以及外部环境或仓库版本漂移检测。只有任务真的需要跨故障恢复时,durable execution 的引入成本才有回报。

6. Human-in-the-loop:在副作用前治理

发布、付费、删除、外部通信、权限升级和设备写入等动作,应在具体副作用前设置审批。审批界面需要展示目标、参数、证据、diff、影响范围和回滚方式,而不是让人对模糊意图点“同意”。

人工批准属于治理措施,不是功能安全措施。它不能替代最小权限、参数限制、联锁、安全 PLC、安全控制器、风险评估和适用标准。

三、复杂任务怎样拆才有价值

高质量拆分不是生成更多待办项,而是建立可执行、可验证的任务图。每个节点至少要写清目标、输入、输出 artifact、所有权、依赖、验收条件、预算,以及失败后的重试、降级或人工升级路径。

先画依赖,再谈并行。 已就绪且互不冲突的节点数只是结构上的并行上限;实际并发还受 API 限流、token 预算、文件冲突和协调成本约束。

上下文按职责分区。 主 Agent 保存全局目标和依赖,子 Agent 只获得完成任务所需的材料与权限。共享事实应进入可引用的文件、数据库或 artifact,而不是依赖层层摘要。

共享写入坚持 single-writer。 搜索、阅读和独立模块实现可以并行;共享接口和高冲突文件由一个 Agent 落盘,其他 Agent 提供建议或 patch 草案。

验证必须提供独立证据。 测试、类型检查、schema 校验、截图对比、查询结果、仿真输出和人工检查可以构成证据;同源模型扮演 reviewer,只能作为补充。

replanning 应由事件触发。 依赖变化、验收失败、预算超限、资源不可用或新证据推翻假设时再更新计划。每一步都重写全局计划,容易浪费上下文并造成目标漂移。

四、Skills与Harness的边界

Agent Skills Specification提供了一种可移植的目录约定:入口描述触发条件和流程,脚本、模板与参考资料按需加载。它的重要价值是渐进式注入上下文,而不是把所有领域知识长期塞进系统提示。

Superpowers把 brainstorming、计划、TDD、调试、审查和完成前验证组织成一组强流程 Skills;Matt Pocock Skills则提供了可阅读、可改写的领域 Skill 样例。它们适合借鉴方法和检查点,但不能因为流程写得严格,就假设宿主一定执行了测试或隔离了权限。

Harness 是模型实际工作的完整工程环境,至少包括:

  • context:仓库规则、对话、检索结果和 artifact 怎样进入;
  • tools:怎样搜索、编辑、运行命令、浏览页面和连接外部服务;
  • permissions:工具、目录、凭证和副作用怎样隔离与审批;
  • state:宿主提供哪些会话或任务状态面,以及缺失部分怎样外接;
  • verification:测试、构建、diff 和验收结果怎样运行与呈现;
  • observability:工具轨迹、耗时、预算、失败位置和实际副作用能否追踪。

Codex、Claude Code 等 coding agent 产品可以作为 coding Harness 的主宿主或起点,但不能据此假定它们完整覆盖上述所有组件。持久状态、审批、CI、策略和 durable workflow 是否需要外接,应根据项目需求和当前已核验的产品能力决定。对多数开发团队,先从现有宿主建立任务成功率、人工返工和验证轨迹,比从零搭 Agent runtime 更务实。

五、grill-me与Trellis放在哪一层

grill-me 是可选的 alignment Skill:它通过逐项追问暴露计划或需求中的隐含取舍。观察到的 Matt Pocock 原版没有规定必须生成根目录 PLAN.mdSPEC.md,因此不能把第三方扩展的产物约定反推为原版行为。

Trellis是 repository-level project Harness extension,由仓库文件、Skills、hooks 和 scripts 共同组织规格、任务上下文与跨会话记录。它不是基础模型,也不是具有严格事务语义的 durable workflow engine;提示文字仍需 Agent 遵守,脚本只有显式运行才产生机械效果。

两者在 brainstorm 上有重叠,不应固定串行。小任务可以全部跳过,中型任务按需保留轻量 SPEC/PLAN,只有复杂且长期、确实需要跨会话交接的任务才考虑 Trellis;根目录 PLAN/SPEC 不会自动成为 Trellis 任务,需要显式映射到它的项目产物。

所有命令、版本漂移、实验数据和替代方案比较都放在 Grill-me × Trellis × Codex实践:项目级Harness如何落地,本文不重复实践结论的证据细节。

六、MCP、A2A与Workflow怎样配合

Model Context Protocol解决 tool/data connection:客户端怎样发现并调用 tools、resources 和 prompts。它不决定复杂任务怎样拆,也不自动证明 server 可信、业务权限正确或返回结果安全。

A2A解决独立 agent service delegation:跨团队、跨产品或跨部署域的 Agent 怎样发现彼此、委派任务并回传消息与 artifact。它不替代进程内部的规划算法,也不替代身份、授权、租户隔离和审计。

Workflow 管理状态、依赖、分支、retry、approval 和收敛。它可以在固定节点调用 Agent,也可以通过队列或 durable runtime 承载长任务,但开放推理与流程可靠性应分层设计。

1
2
3
4
5
Skills   = 如何做这一类任务
Harness = 模型在什么工程环境中工作
MCP = 怎样连接工具与数据
A2A = 怎样委派给独立 Agent 服务
Workflow = 状态、依赖、重试与审批怎样推进

小团队通常先需要稳定 Harness、可复用 Skills 和少量 MCP。只有存在独立服务边界、异步任务契约或组织级委派时,A2A 才值得进入架构;只有任务能独立拆分并局部验收时,subagent 或 multi-agent 才值得升级。

七、代表性工具地图

工具只用于定位类别,不代表横向排名,也不根据未核验的细功能下结论。

需求 代表类别或工具 采用时先验证
仓库内编码 Harness Codex、Claude Code 真实仓库任务的一次通过率、权限和验证轨迹
可复用流程知识 Agent Skills、Superpowers、Matt Pocock Skills 触发是否准确,guidance 是否真正执行
工具与数据连接 MCP、宿主原生工具 server 信任、最小权限、参数校验和审计
独立 Agent 服务互操作 A2A 身份、授权委托、任务契约和失败语义
有状态 Agent workflow LangGraph、Google ADK、Agents SDK 状态模型、重试、checkpoint 和人工闸门
长任务持久执行 durable workflow / queue runtime 幂等、恢复、取消、漂移检测和运维成本

选型重点不是“支持多少 Agent 类型”,而是它是否解决了当前瓶颈,并且没有建立第二套无人维护的事实来源。

八、六类场景如何落位

场景 preferred mode minimum evidence key safety boundary
research 单 Agent 检索;资料可独立分片时少量 subagents 每个关键结论回链一手来源,保留原始证据与检索范围 不把同源 reviewer 的一致意见当事实验证
coding coding Harness + 仓库规则 + 测试闭环 可复现问题、定向测试、构建、diff 审查 高冲突文件 single-writer,发布与破坏性操作单独批准
product/frontend 单 Agent 保持整体一致性,视觉调查可独立并行 真实交互、桌面与移动截图、可访问性和溢出检查 不让生成结果绕过设计系统、权限或用户数据边界
operations 确定性 workflow 包围受限 Agent 幂等、超时、重试、dry-run、审计和故障注入 Agent 提议动作,策略与执行器控制真实副作用
industrial integration 设备状态模型 + 确定性状态机 + 只读 Agent 状态新鲜度、命令反馈关联、断线重连、事件重放 白名单与参数范围独立于 LLM,写设备需额外治理
robotics 状态采集/规划/执行分层,Agent 位于非实时解释与候选计划层 frame 与时间戳校验、仿真、动作反馈、拒绝与失败样本 LLM 不进入实时控制或功能安全回路,不绕过联锁与急停

九、选型检查清单

维度 必须回答的问题
task structure 任务能否独立拆分?依赖、共享状态和 single-writer 在哪里?
state / recovery 进程终止、超时、重复投递和环境漂移后怎样恢复?
permissions 每个 Agent、Skill 和连接器能读写什么,凭证怎样隔离?
verification 哪些测试、schema、截图、仿真或人工证据能够判定完成?
maintenance / migration 生成文件、专用状态、升级和迁移成本由谁承担?哪些 artifact 能脱离框架继续使用?

如果这些问题无法回答,增加 Agent 数量通常只会放大不确定性。

十、常见失败模式

  • 伪并行: 把强依赖步骤同时派发,或让多个 Agent 修改共享文件,最终等待和合并成本超过收益。
  • 同源 reviewer: 相同模型、上下文和数据源换几个角色名,输出仍高度相关,不能代替独立验证器。
  • context broadcast: 给每个 Agent 广播完整仓库、历史和权限,造成 token、信息泄露与攻击面同步膨胀。
  • 长对话当状态: 上下文压缩、环境变化和权限过期都会破坏恢复,应把状态、事件和 artifact 外置。
  • 模型错误直接变成副作用: 缺少参数校验、最小权限、dry-run、幂等和回滚时,一次错误调用就可能造成真实损失。
  • over-autonomy: 能调用工具不代表应该自动执行;高代价动作需要确定性边界,工业和机器人系统还需要独立安全设计。

十一、采用顺序

采用顺序应保持为:

1
strong single-agent baseline -> repeatable Skills -> explicit task state/workflow -> subagents only for independent work -> durable execution only for long tasks

每次升级都应对应一个已测量的瓶颈:重复说明很多,才提炼 Skill;任务需要跨会话恢复,才引入显式状态;存在可独立验收的任务宽度,才使用 subagents;任务跨故障和长时间运行,才承担 durable execution 的成本。

在工业和机器人方向,Agent 更适合手册检索、状态解释、候选诊断、计划草案和操作审计。设备状态机、轨迹规划、命令校验、实时控制、联锁与功能安全仍应由可验证的确定性系统承担。

最终判断标准不是生成了多少角色、Markdown 或 trace,而是任务成功率、墙钟时间、恢复能力、权限边界和验证质量是否得到可重复的改善。

主要参考资料

先说结论:基础没有消失,资深标准正在变化

Vibe Coding 降低了代码生成和 API 查询成本,但没有降低软件失败的代价。代码来得越快,审查边界、验证行为、定位故障和对结果负责越重要。

面试也不会整齐地从“八股”切换到 AI 协作。不同公司、岗位和面试官差异很大,标准化基础题仍是成本较低的筛选手段。对资深工程师,更常见的有效追问是:

  • 为什么这个机制会产生当前现象?
  • 结论在哪些条件下失效?
  • 出现性能、并发或安全问题时怎样定位?
  • 为什么选择这个方案,替代方案有什么代价?
  • 这是生产经验、个人实验,还是根据原理作出的推断?
  • AI 生成的代码看似能跑,怎样证明它可以进入系统?

因此复习策略不是“继续背完所有题”或“以后全靠 AI”,而是:

基础知识达到稳定过线水平,主要精力投入项目深挖、工程取舍、代码审查和验证证据。

对已有 11 年 Web/full-stack 经验、正在转向机器人系统软件和工业可视化的工程师,Vue、TypeScript 与 Node.js 是能力底座和差异化资产,不应成为占满全部学习时间的舒适区。

四层知识地图

能力层 掌握标准 Vue / TypeScript / Node.js 示例 复习策略
必须闭卷掌握 能用自己的语言解释机制,并写出最小代码 响应式与更新调度、控制流收窄与类型擦除、事件循环与错误传播 周期性回忆,五分钟讲清,接受两层追问
资深工程追问 能联系故障、性能、安全、可靠性和架构边界 更新风暴、异步竞态、运行时数据污染、事件循环阻塞、背压、优雅停机 用真实项目或可复现实验回答,说明取舍和指标
可以借助文档或 AI 知道从哪里查、如何做最小实验确认 冷门 API 参数、少用配置、精确守卫顺序、版本相关实现细节 不长期背诵,面试前按职位描述定向恢复
AI 代码审查能力 能指出风险、修改理由和验证证据 断言代替校验、资源泄漏、缺少超时、错误吞没、无界并发、错误权限假设 保留 AI 初稿、审查记录、修复和测试结果

哪些内容不值得长期投入

  • 仅对某个旧版本成立、又能快速查询的执行顺序。
  • 为展示技巧而构造的 TypeScript 递归类型谜题。
  • Vue 2 的冷门 API 和已经退出主流项目的配置细节。
  • 没有输入规模、环境和测量数据的性能结论。
  • 脱离业务不变量的“最佳实践”列表。

这不表示这些内容永远不会被问到,而是它们不应挤占建立新职业证据的时间。遇到目标公司明确使用旧技术栈时,再进行定向准备。

三条专题主线

Vue:从 API 使用推进到更新模型

闭卷掌握响应式依赖、更新调度、computed/watchkey 和组件状态所有权。资深追问集中在高频更新、异步竞态、组件边界、大列表和 Web3D 对象如何避免无意义深代理。

目标不是背 Vue 源码函数名,而是能把一次状态写入追踪到 effect、scheduler、组件渲染和 DOM patch,并知道如何用 Vue Devtools 与浏览器 Performance 验证。

TypeScript:从“有类型”推进到可信边界

闭卷掌握结构化类型、unknown、判别联合、泛型关系、类型收窄和类型擦除。资深追问集中在公共 API 类型、方差、严格配置、声明文件、运行时输入和类型测试。

看到 AI 生成的 req.body as Commandresponse.json() as User 时,应立即追问:数据由谁验证、验证了哪些业务约束、失败如何处理、类型断言的证据是什么。

Node.js:从“异步非阻塞”推进到服务可靠性

闭卷掌握 V8/libuv/OS 的职责、事件循环稳定关系、I/O 与 CPU 任务边界、Stream 背压和错误传播。资深追问集中在尾延迟、事件循环阻塞、内存、超时取消、幂等、优雅停机和可观测性。

回答“Node.js 是单线程”时必须说明指的是 JavaScript 执行模型,而不是整个运行时只有一个线程。回答“流省内存”时必须说明缓冲、背压、并发和测量条件。

仍需单独准备的通用基础

这三条主线不能代替 JavaScript 语言、浏览器、网络、数据结构、数据库和系统设计。至少要能解释:

  • 作用域、闭包、原型、Promise、微任务和错误传播。
  • 浏览器渲染、布局绘制、事件、缓存、同源策略和常见安全边界。
  • HTTP 方法语义、状态码、缓存、Cookie、认证与授权。
  • 常见数据结构复杂度、并发限制、数据库索引和事务边界。

本轮文章不重复扩写这些主题,避免把索引变成无法复习的百科全书。

资深回答的六步结构

面对原理题或场景题,可以按六步组织,而不是一上来倾倒术语:

  1. 结论:先直接回答问题,限定讨论条件。
  2. 机制:说明关键数据流、控制流或类型关系。
  3. 场景:把机制放回输入规模、并发、生命周期和业务目标。
  4. 风险:指出错误、安全、性能、一致性和维护风险。
  5. 替代方案:比较至少一个可行替代,并说明采用成本。
  6. 验证与证据:给指标、profile、日志、测试、故障复盘或最小实验;没有生产经历时明确说是实验或推理。

“我会使用虚拟列表”只是方案名称;“先证明 DOM 数量和 layout/paint 是主要瓶颈,再决定虚拟列表窗口、滚动定位和可访问性策略”才体现资深判断。

跨栈场景:机器人与工业设备状态平台

问题:5,000 台设备高频更新,页面越来越卡,如何定位?

1. 先定义现象和目标

确认是消息延迟、交互卡顿、图表掉帧还是内存上涨;记录设备数、每台更新频率、单条消息大小、允许的 UI 新鲜度和报警延迟。没有这些条件,“优化 Vue”没有明确目标。

2. 把链路拆开测量

1
2
3
4
5
6
7
设备 / 模拟器
-> Node.js 网关接收与解析
-> 校验、聚合和消息分发
-> WebSocket 客户端接收
-> TypeScript 领域模型
-> Vue 响应式状态与组件更新
-> DOM / Canvas / WebGL 绘制
  • 网关观察吞吐量、事件循环延迟、CPU、内存、队列深度和下游耗时。
  • 浏览器观察消息处理耗时、Long Task、Vue 组件更新、DOM 数量、layout/paint 和帧率。
  • 给消息加时间戳与 correlation ID,区分服务端积压、网络延迟和前端处理延迟。

3. 分离采集频率与展示频率

设备数据可以高频到达,但人眼和 DOM 不需要逐条刷新。把数据写入有界缓冲,按 100 至 250 ms 合并为 UI 快照。位姿展示可按设备保留最新值;报警、审计和控制结果不能用同样策略静默覆盖。

4. 守住类型和业务边界

WebSocket 数据先视为 unknown,验证消息类型、设备标识、时间戳、数值范围和版本,再转换为判别联合。TypeScript 接口不能代替这些检查。

5. 根据证据选择优化

  • Vue 更新过多:批量发布快照、稳定 props、减少无意义深响应。
  • DOM 数量过多:虚拟滚动、分组展开或只展示视口数据。
  • Canvas/WebGL 绘制过重:控制渲染频率、复用对象、减少材质和 draw call。
  • 主线程解析或计算过重:评估 Worker,并计入数据复制与调度成本。
  • Node.js 消费跟不上:使用背压、有界并发、批处理、过载拒绝或分区扩展。

6. 给出验收指标

例如:在 5,000 台设备、每台每秒 10 条模拟遥测下,网关队列不持续增长;报警端到端 p99 小于约定阈值;页面交互无超过约定时长的 Long Task;稳态内存不随运行时间持续增长。具体阈值必须由业务和测试环境确定,不能把示例数字写成行业标准。

AI 生成代码审查练习

下面是一段表面上能工作的 AI 生成代码:

1
2
3
4
5
app.post('/devices/:id/command', async (req, res) => {
const command = req.body as RobotCommand
await sendCommand(req.params.id, command)
res.json({ ok: true })
})

对于普通信息系统,这段代码已经缺少关键边界;对于机器人命令接口,直接上线更不可接受。

至少应发现的问题

  1. as RobotCommand 只是类型断言,没有验证请求体结构、数值范围和命令版本。
  2. 没有认证,也没有验证用户是否有权操作目标设备和当前空间。
  3. 没有命令 allowlist、安全门控、设备状态检查和速度/工作空间限制。
  4. 没有超时、取消和下游无响应时的行为定义。
  5. 不清楚命令是否幂等;客户端重试可能造成第二次物理动作。
  6. 没有 command ID、审计记录、操作者、批准信息和结果状态。
  7. async 失败如何进入 Express 错误边界不明确,可能产生未处理 rejection 或错误响应。
  8. 没有同一设备的并发、顺序、状态冲突和急停优先级策略。
  9. HTTP 返回成功是否表示“已接收”“已规划”“已下发”还是“已执行完成”不明确。
  10. 没有指标、trace 和安全事件日志,故障后无法还原链路。

修正方向

安全的接口不是增加几个 try/catch。它至少需要如下分层:

1
2
3
4
5
6
7
8
HTTP 边界
-> 认证与资源授权
-> 运行时 schema + 领域约束校验
-> command ID / 幂等与设备级并发策略
-> 工作流和人工批准
-> 确定性安全控制与硬件互锁
-> 下发、反馈、超时与状态查询
-> 审计、指标、日志和告警

LLM 可以生成 handler、schema 初稿和测试样例,但它不能从不存在的需求中推导出可信权限、设备安全状态和物理约束。工程师必须补齐系统保证,并通过测试、仿真、故障注入和现场安全流程验证。

复习时应保留 AI 初稿、审查清单、修正后的代码与测试结果。这个过程比只展示“我用 AI 很快写完接口”更能证明新时代的工程能力。

面向当前职业转型的复习投入

以下比例针对“资深 Web/full-stack 工程师转向机器人系统软件、工业可视化和机器视觉”的当前阶段,不是所有读者的通用公式。

非求职冲刺期

投入 方向 交付物
45% 机器人数字孪生、调试台、视觉引导或设备状态项目 可运行 Demo、架构图、事件日志、失败案例和演示视频
25% C++、Python、Linux、坐标系、运动学和工业通信 SDK 调用、坐标变换实验、协议模拟器、测试记录
15% JavaScript、Vue、TypeScript、Node.js 机制恢复 机制卡、最小示例、两层追问答案
10% AI 辅助编码、代码审查、测试和验证 AI 初稿、拒绝理由、修复 diff、自动化测试
5% 项目复盘、简历叙事和口头表达 STAR/架构复盘、三分钟与二十分钟两个版本

这个配置的理由是:Web 基础已经是优势,继续投入会提高熟练度,却不能单独证明机器人系统能力。当前更稀缺的是把 Web3D、实时数据、设备状态、视觉和安全约束整合成可检查的作品。

投递前六至八周

把通用编程基础、编码练习和模拟面试提高到总时间的 30% 至 35%,依据目标职位描述补齐算法、网络、数据库和目标语言;同时继续维护项目 Demo 和证据,不要在面试前把作品开发完全停掉。

目标职位不同,权重也应调整:

  • 工业数字孪生 / 机器人可视化:Vue/Three.js、实时数据、性能和坐标系权重更高。
  • 机器人应用软件 / 系统集成:Node/Python/C++、设备协议、状态机、异常恢复权重更高。
  • 机器视觉应用:OpenCV、标定、PnP、手眼标定、部署与光学条件权重更高。
  • 纯 VLA、强化学习或控制算法岗位:当前仍是高跨度方向,不能用 Web 八股或 Agent 使用经验替代数学、训练和机器人实验能力。

分级自测题

A. 基础事实:能否直接回答

  • Vue 的 computedwatch 分别解决什么问题?
  • key 为什么首先是身份问题?
  • TypeScript 的 unknownany 有什么差别?
  • 类型断言为什么不能校验 JSON?
  • Node.js 所谓“单线程”的边界是什么?
  • writable.write() 返回 false 表示什么?

B. 机制解释:能否继续追问两层

  • 一次 Vue 响应式写入怎样到达 DOM 更新?
  • 为什么从 reactive 直接解构可能丢失响应性?
  • 判别联合怎样与 never 形成穷尽性检查?
  • Promise 微任务、process.nextTick() 和事件循环是什么关系?
  • pipeline() 比连续 pipe() 多承担了什么职责?

C. 工程决策:能否比较方案

  • 高频设备数据应该每条更新 UI、定时批量发布还是只保留最新值?不同消息是否应使用相同策略?
  • 什么情况下选择 Worker Threads,什么情况下选择独立进程或服务?
  • 运行时校验使用手写 guard 还是 schema 库,怎样决定?
  • Vue 页面缓存解决了什么,数据缓存又由谁负责?

D. 故障诊断:能否提供证据

  • 页面运行两小时后内存持续增长,先看哪些指标和 profile?
  • Node.js 平均延迟正常但 p99 很高,怎样分层定位?
  • 路由参数切换后偶尔显示上一台设备,原因和修复是什么?
  • AI 生成的重试逻辑导致设备动作重复,系统缺了哪些保证?

E. 项目证据:能否诚实说明边界

  • 哪个项目证明你处理过实时数据、背压或高频渲染?
  • 哪个故障是你亲自定位的,最初假设与最终原因是否一致?
  • 哪些结论只有个人实验,还没有生产验证?
  • 如果重新设计当前项目,会保留和改变什么?

可执行复习清单

机制卡

  • [ ] 每张卡只覆盖一个机制,包含结论、运行过程、失败边界和最小实验。
  • [ ] 能闭卷讲五分钟,并回答至少两层“为什么”和“什么时候失效”。
  • [ ] 面试价值:通过基础筛选,避免资深候选人在核心原理上失速。

项目深挖包

  • [ ] 准备架构图、关键接口、状态流、技术取舍、性能数据、测试和一次失败复盘。
  • [ ] 能围绕同一项目接受二十分钟追问,而不是只复述产品功能。
  • [ ] 面试价值:证明系统集成、正确性、可靠性、可观测性和交付能力。

AI 代码审查记录

  • [ ] 保留原始提示和生成代码,不把修正后的结果伪装成人工一次写成。
  • [ ] 标出接受、拒绝和需要实验确认的部分,并写明证据。
  • [ ] 用测试、静态检查、基准或故障注入验证修正结果。
  • [ ] 面试价值:证明会使用 AI,同时保留工程判断和结果责任。

职业转型证据

  • [ ] 至少完成一个机器人调试台、数字孪生或视觉引导工作流的可运行作品。
  • [ ] README 说明数据流、状态、坐标系、安全边界、故障处理和测试策略。
  • [ ] 明确区分已有 Web 工程证据、正在形成的机器人软件能力和尚未证明的算法能力。
  • [ ] 面试价值:把“11 年 Web 工程师想转机器人”变成“能够交付面向机器人的软件系统,并持续补强机器人深度”。

更新时间:2026-07

核心观点:Codebase Memory MCP 的本质不是 AI Coding Agent,而是 AI 的 Context Layer,用于帮助 AI 快速理解大型代码库。

一、项目简介

Codebase Memory MCP 是一个 MCP(Model Context Protocol)Server。它能够扫描整个代码仓库,构建代码知识图谱(Knowledge Graph),供 Claude Code、Codex、Cursor 等 AI Agent 查询。

它的目标不是代替 IDE,也不是直接生成代码,而是解决 AI Coding 中最重要的问题之一:

Context Retrieval(上下文获取)

对于大型项目,AI 往往需要花费大量时间:

  • grep 搜索
  • 查找文件
  • 阅读大量无关源码
  • 建立调用关系

Codebase Memory MCP 将这些工作提前完成,并保存成结构化知识图谱,使 AI 能够通过查询而不是全文扫描获取上下文。

二、它解决的问题

传统 AI Coding 工作方式大致是:

1
2
3
4
5
6
7
8
9
10
11
Agent

grep

read file

grep

read file

...

大型仓库中,大量 Token 会消耗在“寻找代码”上。

使用 Codebase Memory MCP 后,工作方式变成:

1
2
3
4
5
6
7
Agent

MCP Tool Call

Knowledge Graph

返回结构化关系

例如,Agent 想知道:

1
谁调用了 ProcessOrder()?

无需 grep 整个仓库,可以直接返回类似关系:

1
2
3
4
5
6
7
CheckoutController

OrderApplication

OrderService

ProcessOrder()

三、核心思想

1. 它不是文档

很多人容易把它理解成:

1
2
3
4
5
Repository

生成 Markdown

AI 阅读 Markdown

这个理解不准确。

更接近真实的流程是:

1
2
3
4
5
6
7
AI Agent

MCP Tool

Knowledge Graph(SQLite)

返回 JSON

例如返回:

1
2
3
4
5
6
7
{
"symbol": "OrderService",
"called_by": [
"CheckoutController",
"RetryWorker"
]
}

AI 获取的是结构化数据,而不是一大段索引文本。

2. 它更像数据库

Codebase Memory 保存的对象可能包括:

  • Class
  • Function
  • Method
  • Package
  • Interface
  • Module
  • Route
  • Resource

保存的关系可能包括:

  • CALLS
  • IMPORTS
  • IMPLEMENTS
  • HTTP_CALLS
  • DATA_FLOW
  • TESTS
  • CONFIGURES

因此它更像:

1
2
3
4
5
Neo4j
+
SQLite
+
MCP Server

而不是:

1
2
3
Markdown
+
RAG

四、整体工作流程

第一步:扫描代码仓库

启动 Codebase Memory MCP 后,它会解析整个代码仓库:

1
2
3
4
5
Repository

Tree-sitter

AST

第二步:构建知识图谱

从 AST 和工程结构中抽取:

  • Class
  • Method
  • Symbol
  • Import
  • Call Graph
  • Dependency

最终形成:

1
Knowledge Graph(SQLite)

例如:

1
2
3
4
5
6
7
OrderController

OrderService

Redis

MySQL

第三步:启动 MCP Server

MCP Server 向 AI 提供一系列 Tool,例如:

  • find_symbol
  • search_graph
  • trace_call_path
  • architecture_summary
  • impact_analysis
  • query_graph

Agent 不需要知道数据库位置,只需要调用 Tool。

第四步:AI Coding

例如用户提出需求:

1
增加订单取消功能

Agent 可能执行:

1
2
3
4
5
6
7
8
9
10
11
architecture_summary()

find_symbol()

trace_call_path()

impact_analysis()

read_file()

edit_file()

注意:Graph 并不会替代阅读源码。

它只是帮助 Agent 找到应该阅读哪些源码

第五步:修改后更新图谱

修改代码后,通常有两种方式更新图谱。

方式一:全量重新扫描

1
2
3
Repository

重新 Index

优点是简单,缺点是大型项目速度较慢。

方式二:增量更新(推荐)

监听文件变化:

1
2
3
4
5
OrderService.java

重新解析 AST

更新 Graph

这样无需重新扫描整个仓库。

五、完整工作流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Git Repository


Codebase Memory MCP


Tree-sitter AST


Knowledge Graph(SQLite)


MCP Server

├──────────────┐
▼ ▼
Claude Code Codex CLI
│ │
└────Tool Calls┘

查询调用关系
查询依赖
查询影响范围


精准定位源码


修改代码


文件监听 / 增量更新 Graph

六、与传统 RAG 的区别

传统 RAG 流程:

1
2
3
4
5
6
7
8
9
Question

Embedding

Vector Search

Chunk

LLM

返回结果通常是文本片段。

Knowledge Graph 流程:

1
2
3
4
5
6
7
8
9
Question

Graph Query

Node

Relation

LLM

返回结果通常是结构化关系,例如:

1
2
3
4
5
Controller

Service

Repository

它返回的不是“几段可能相关的文本”,而是“实体和实体之间的关系”。

七、与 Sourcegraph 的区别

Sourcegraph Codebase Memory MCP
面向开发者 面向 AI Agent
Code Search Graph Query
Web UI MCP Tool
人浏览代码 AI 查询上下文
代码导航 AI Context Provider

Sourcegraph 更像:

Google Search

Codebase Memory 更像:

Neo4j + MCP

八、优势

1. 快速理解大型仓库

减少:

  • grep
  • find
  • read

提升 AI 获取上下文的效率。

2. Call Graph 查询

例如:

1
谁调用 ProcessOrder()?

无需扫描整个仓库。

3. Impact Analysis

例如:

1
修改 User 实体,会影响哪些模块?

Graph 可以帮助分析:

  • Controller
  • Service
  • Repository
  • Test
  • API

4. Architecture Summary

快速生成:

  • Layer
  • Module
  • Entry Point
  • Hotspot

帮助 Agent 理解整体架构。

5. 支持大型仓库

相比反复读取源码,Graph 查询通常:

  • 更快
  • Token 更少
  • 上下文更稳定

九、局限性

1. 不理解设计原因

Graph 能回答:

  • What
  • How

但不能直接回答:

  • Why

例如:

1
为什么这里不用 RabbitMQ?

这类问题仍然需要:

  • ADR
  • Design Doc
  • PR
  • Issue
  • Wiki

2. 动态语言分析有限

对于以下场景,静态图谱无法完全覆盖:

  • Reflection
  • 动态导入
  • Plugin
  • Runtime Dispatch

3. 不能替代 IDE

IDE 仍负责:

  • Rename
  • Refactor
  • Diagnostics
  • Go To Definition

Knowledge Graph 负责帮助 AI 理解项目。两者定位不同。

十、个人评价

技术价值

评分:★★★★★(9.5/10)

非常适合:

  • 大型代码仓库
  • AI Coding
  • MCP Agent
  • 企业级项目

成熟度

评分:★★★★☆(约 7-8/10)

项目发展很快,但仍需注意:

  • API 可能持续变化
  • 生态仍在快速演进
  • 不同语言和框架的图谱质量会有差异

是否值得学习

值得。

真正值得学习的不是某个具体 API,而是背后的工程思想:

Context Engineering(上下文工程)

未来 AI Coding 的竞争力,不再只是更强的大模型,而是更完整的上下文系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Git

Code Graph

ADR

Design Docs

Issue

Runtime Trace

Knowledge Base

AI Agent

LLM 只是最上层。真正决定 AI Agent 能力上限的是:

Context Layer(上下文层)

十一、我的总结

Codebase Memory MCP 并不是一个“更会写代码”的 AI Agent,而是 AI Agent 的“导航系统”。

它通过预先构建代码知识图谱,让 Agent 从“反复扫描源码”转变为“查询结构化上下文”,从而显著降低 Token 消耗,提高定位效率,并增强对大型代码仓库的理解能力。

对于 AI Native 开发而言,它代表了一种重要方向:

AI 的能力不仅取决于模型本身,更取决于提供给模型的上下文质量。

随着 AI 开发工具的发展,代码知识图谱很可能会与 ADR、设计文档、Issue、运行时监控、测试结果等信息融合,形成完整的 Context Layer,成为未来 AI Native 软件工程的重要基础设施。

后续可继续研究的问题

这篇笔记后续可以继续补充:

  • Codebase Memory MCP 与 LSP 的边界
  • Tree-sitter 对不同语言的解析能力差异
  • 静态图谱与运行时 Trace 如何融合
  • 如何把 ADR、Issue、PR、测试结果纳入 Context Layer
  • 如何评估一个 Code Graph 对 AI Coding 的真实帮助

涉及项目最新状态、API、安装方式或版本兼容性时,需要重新查证。

背景

在机器人数字孪生、运动规划可视化、碰撞检测和工业仿真中,经常会遇到两类模型:

  • 机器人结构模型:描述机器人有哪些连杆、关节、坐标系、关节轴、关节限位。
  • CAD 几何模型:描述零件真实形状、曲面、实体、装配关系、STEP/IGES 等工程格式。

URDF 和 OCCT 分别对应这两类问题。

一句话理解:

URDF 负责“机器人是什么结构、怎么运动”;OCCT 负责“几何实体是什么形状、如何加工和转换”。

在机器人软件里,URDF 更偏机器人语义,OCCT 更偏 CAD 几何内核。

一、URDF 是什么

URDF 全称是 Unified Robot Description Format,是 ROS 生态中常用的机器人描述格式,本质是一个 XML 文件。

它主要描述:

  • 机器人由哪些 link 组成。
  • link 之间通过哪些 joint 连接。
  • 每个关节的类型、轴向、限位和初始变换。
  • 每个 link 的视觉模型、碰撞模型和惯性参数。

URDF 不是三维建模软件,也不是运动控制算法。它更像机器人系统中的“结构配置文件”。

二、URDF 的核心元素

1. robot

robot 是根节点,表示一个机器人模型。

1
2
3
<robot name="six_axis_robot">
...
</robot>

link 表示机器人上的刚体部件,例如:

  • base_link
  • shoulder_link
  • upper_arm_link
  • wrist_link
  • tool0

一个 link 可以包含三类信息:

  • visual:给人看的外观模型。
  • collision:给碰撞检测用的简化模型。
  • inertial:给动力学仿真用的质量、质心和惯性矩阵。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
<link name="base_link">
<visual>
<geometry>
<mesh filename="package://robot_description/meshes/base.stl"/>
</geometry>
</visual>

<collision>
<geometry>
<mesh filename="package://robot_description/meshes/base_collision.stl"/>
</geometry>
</collision>
</link>

3. joint

joint 表示两个 link 之间的连接关系。

常见关节类型:

  • fixed:固定连接。
  • revolute:有限角度旋转关节。
  • continuous:无限旋转关节。
  • prismatic:直线滑动关节。
  • floating:六自由度浮动关节。
  • planar:平面运动关节。

六轴工业机器人通常主要由多个 revolute 关节组成。

示例:

1
2
3
4
5
6
7
<joint name="joint_1" type="revolute">
<parent link="base_link"/>
<child link="link_1"/>
<origin xyz="0 0 0.35" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit lower="-3.14" upper="3.14" effort="150" velocity="2.5"/>
</joint>

关键字段:

  • parent:父 link。
  • child:子 link。
  • origin:child frame 相对 parent frame 的初始变换。
  • axis:关节运动轴,在 joint frame 中表达。
  • limit:关节上下限、最大力矩、最大速度。

三、URDF 中的坐标系

URDF 的关键不是“模型长什么样”,而是“坐标系如何连接”。

一个典型链路可以理解为:

1
2
3
4
5
6
7
8
9
world

base_link
↓ joint_1
link_1
↓ joint_2
link_2
↓ ...
tool0

每个 joint 的 origin 都是一段刚体变换。机器人正运动学就是把这些变换按链路顺序连乘:

1
2
3
4
5
T_base_tool =
T_base_link1(q1) ×
T_link1_link2(q2) ×
...
T_linkN_tool(qN)

这也是 URDF 能服务于 TF、FK、可视化和碰撞检测的原因。

四、URDF 的视觉模型与碰撞模型

URDF 中的几何一般分为两套:

visual

用于显示,模型可以比较精细,例如高面数 STL、DAE、OBJ。

作用:

  • RViz 显示
  • Web3D 显示
  • 数字孪生展示
  • Demo 演示

collision

用于碰撞检测,模型通常要简化。

原因:

  • 高精度 CAD 网格计算慢。
  • 碰撞检测更关注是否接触,不一定需要完整外观。
  • 工业场景通常需要稳定、快速、可解释。

常见做法:

  • 用 box、cylinder、sphere 代替复杂零件。
  • 用低面数 mesh。
  • 把复杂 link 拆成多个简单 collision primitive。

五、URDF 的局限

URDF 很适合描述树状机器人结构,但也有明显局限:

  • 不擅长表达闭链结构。
  • 不适合直接表达复杂装配约束。
  • 不负责路径规划、控制算法和任务逻辑。
  • XML 对大型模型可读性一般,通常会结合 xacro 模板生成。
  • 对工业机器人厂家私有参数、控制器语义、工艺坐标系表达有限。

因此 URDF 更适合作为机器人软件的“结构中间层”,而不是完整的机器人产品数据模型。

六、OCCT 是什么

OCCT 全称是 Open CASCADE Technology,是一个开源 CAD/CAM/CAE 几何建模内核。

它擅长处理:

  • B-Rep 实体建模
  • 曲线、曲面、拓扑结构
  • STEP、IGES 等 CAD 格式导入导出
  • 布尔运算
  • 倒角、圆角、偏移
  • 网格剖分
  • 几何测量
  • 装配和形状遍历

如果说 URDF 更像机器人结构描述文件,那么 OCCT 更像 CAD 软件背后的几何引擎。

七、OCCT 的核心概念

1. Geometry 与 Topology

OCCT 中需要区分两个概念:

  • Geometry:数学几何,例如点、线、圆、曲线、平面、曲面。
  • Topology:拓扑结构,例如点、边、线框、面、壳、实体。

简单理解:

1
2
Geometry:形状背后的数学定义
Topology:这些几何对象如何连接成一个实体

例如一个圆柱体:

  • Geometry 包含圆柱曲面、上下两个平面。
  • Topology 包含 face、edge、wire、shell、solid。

2. Shape 层级

OCCT 常见拓扑层级:

1
2
3
4
5
6
7
8
9
10
11
12
13
Vertex

Edge

Wire

Face

Shell

Solid

Compound

含义:

  • Vertex:点。
  • Edge:边。
  • Wire:边组成的闭合或非闭合线框。
  • Face:面。
  • Shell:多个面组成的壳。
  • Solid:封闭体。
  • Compound:多个 Shape 的组合。

3. B-Rep

B-Rep 是 Boundary Representation,边界表示法。

它不是用三角面片直接表示实体,而是用边界曲面和拓扑关系表示实体。

对比:

1
2
3
4
5
Mesh:
三角形 + 顶点

B-Rep:
曲面 + 边界 + 拓扑关系

这也是 CAD 模型比普通游戏模型更适合工程计算的原因。

八、OCCT 常见能力

1. 读取 CAD 文件

OCCT 可以读取 STEP、IGES 等工程 CAD 格式。

典型用途:

  • 导入机械臂零件 STEP 文件。
  • 导入夹具、工装、工件模型。
  • 提取装配体中的零件层级。

2. 几何测量

可用于计算:

  • 包围盒
  • 体积
  • 面积
  • 质心
  • 距离
  • 干涉关系

这些能力在机器人仿真和工装布局中很有价值。

3. 布尔运算

常见布尔操作:

  • Fuse:并集。
  • Cut:差集。
  • Common:交集。

在工装设计、碰撞空间构造、夹具简化时会用到。

4. 网格剖分

机器人可视化和 Web3D 通常不能直接渲染 B-Rep,需要把 CAD 模型转成 mesh。

流程大致是:

1
2
3
4
5
6
7
8
9
STEP / IGES

OCCT 读取为 B-Rep Shape

Mesh triangulation

STL / OBJ / glTF

Three.js / RViz / Web Viewer

九、URDF 与 OCCT 的关系

URDF 和 OCCT 不是同一层东西。

维度 URDF OCCT
核心定位 机器人结构描述 CAD 几何建模内核
主要数据 link、joint、axis、limit、origin shape、face、edge、solid、surface
文件形态 XML C++ API / CAD 文件处理库
常见输入 STL、DAE、OBJ、xacro STEP、IGES、BREP
常见输出 机器人模型树 B-Rep、mesh、测量结果
适合问题 FK、TF、可视化、碰撞配置 CAD 导入、几何分析、网格转换

更合理的关系是:

1
2
3
4
5
6
7
8
9
10
11
CAD / STEP

OCCT

几何清理、简化、剖分、导出 mesh

STL / DAE / glTF

URDF visual / collision

机器人仿真 / 可视化 / 碰撞检测

也就是说:

OCCT 可以帮助准备和处理 URDF 中引用的几何资源,但 URDF 负责表达机器人运动结构。

十、在机器人数字孪生中的使用方式

一个机器人数字孪生调试台可能包含这些模块:

1
2
3
4
5
6
7
8
9
10
11
URDF

解析 link / joint / limit / axis

构建机器人层级树

加载 mesh

FK 更新 link transform

Three.js 显示机器人姿态

如果引入 OCCT,则可以扩展 CAD 处理链路:

1
2
3
4
5
6
7
8
9
10
11
STEP 工装模型

OCCT 读取

提取装配层级和包围盒

生成可视化 mesh

导入 Three.js 场景

用于布局、干涉检查和碰撞区域显示

对 Web/可视化工程师来说,关键不是一开始就实现完整 CAD 内核,而是理解数据层次:

  • URDF 提供机器人运动链。
  • Mesh 提供可显示外观。
  • Collision geometry 提供碰撞近似。
  • OCCT 提供 CAD 到 mesh/测量/简化的工程能力。

十一、学习重点

URDF 学习重点

先掌握:

  • link / joint / origin / axis / limit
  • visual 和 collision 的区别
  • base、flange、tool0、TCP 的关系
  • URDF 到 TF tree 的转换
  • URDF 与 FK 的关系
  • xacro 的基本模板化能力

再深入:

  • inertial 参数
  • Gazebo / ros2_control 扩展
  • Mimic joint
  • 多机器人命名空间
  • 碰撞模型简化策略

OCCT 学习重点

先掌握:

  • STEP / IGES / STL / glTF 的区别
  • B-Rep 与 Mesh 的区别
  • Shape、Face、Edge、Solid 的层级
  • CAD 模型导入和遍历
  • 包围盒、体积、距离等基础测量
  • CAD 到 mesh 的转换流程

再深入:

  • 布尔运算
  • 曲面修复
  • 装配结构解析
  • 网格精度控制
  • 与碰撞检测库的衔接

十二、容易混淆的点

1. URDF 不是 CAD 格式

URDF 可以引用 mesh 文件,但它本身不是 CAD 文件。

它关心的是机器人结构、关节、坐标系和运动关系。

2. STL 不是机器人模型

STL 只是一堆三角面片,不知道哪个部分是 link,也不知道关节轴和关节限位。

要让 STL 成为机器人模型的一部分,需要 URDF 提供结构语义。

3. CAD 高精度不等于仿真好用

CAD 模型通常过于复杂,直接用于实时可视化和碰撞检测会很慢。

工程上经常需要:

  • visual 模型适度降面。
  • collision 模型大幅简化。
  • 保留关键外形,去掉螺丝孔、倒角、小特征。

4. 坐标系比模型外观更重要

机器人模型看起来对,不代表运动学一定对。

更关键的是:

  • joint origin 是否正确。
  • joint axis 是否正确。
  • mesh 坐标是否和 link frame 对齐。
  • 单位是否一致。
  • tool0 / flange / TCP 是否区分清楚。

十三、最小实践建议

练习一:读懂一个六轴机械臂 URDF

交付物:

  • 画出 link-joint 树。
  • 标出 6 个 joint 的 axis。
  • 列出每个 joint 的 lower / upper / velocity。
  • 解释 base_link、flange、tool0 的关系。

验收标准:

  • 能用自己的话解释每个 joint 的父子 link。
  • 能指出 TCP 位姿由哪些 transform 连乘得到。
  • 能说明 visual mesh 和 collision mesh 是否一致。

面试价值:

证明自己不是只会看 3D 模型,而是能理解机器人结构数据和坐标系。

练习二:CAD 到机器人可视化资源转换

交付物:

  • 找一个 STEP 零件。
  • 用 OCCT 或基于 OCCT 的工具读取。
  • 导出 STL/glTF。
  • 放入 Three.js 或 URDF visual 中显示。

验收标准:

  • 模型单位正确。
  • 模型朝向正确。
  • 包围盒尺寸符合预期。
  • 文件体积和面数适合实时显示。

面试价值:

证明自己理解工业 CAD 资源如何进入机器人软件和 Web3D 可视化链路。

练习三:构建简化碰撞模型

交付物:

  • 为一个复杂 link 建立简化 collision geometry。
  • 对比 visual mesh 和 collision mesh。
  • 记录简化前后的面数、包围盒和碰撞检测性能差异。

验收标准:

  • collision 模型不会明显漏掉关键外形。
  • 运行速度比高精度 mesh 更稳定。
  • 能解释为什么不能直接用完整 CAD 做实时碰撞。

面试价值:

证明自己具备工程取舍意识,而不是只追求模型精细。

十四、个人定位价值

对从 Web/可视化转向机器人软件来说,URDF 和 OCCT 很值得学习。

原因是它们正好连接了几个关键能力:

  • 机器人结构建模
  • 坐标系和运动学
  • CAD 工程数据
  • Web3D 可视化
  • 数字孪生
  • 碰撞检测和仿真

更现实的定位不是一开始就做底层控制算法,而是先成为能把机器人模型、CAD 资产、运动链路和可视化调试工具打通的人。

这条路线与工业机器人数字孪生、运动规划可视化、机器视觉工位仿真和机器人系统集成都高度相关。

十五、总结

URDF 和 OCCT 解决的是机器人软件中的两个不同层次的问题。

URDF 解决:

  • 机器人由哪些 link 和 joint 组成。
  • 关节如何运动。
  • 坐标系如何连接。
  • 可视化模型和碰撞模型如何挂到机器人结构上。

OCCT 解决:

  • CAD 几何如何读取。
  • B-Rep 实体如何表达。
  • STEP/IGES 如何转换成可渲染 mesh。
  • 几何如何测量、简化和处理。

它们组合起来,可以形成一条很实用的工程链路:

1
2
3
4
5
6
7
8
9
10
11
工业 CAD

OCCT 几何处理

Mesh 资源

URDF 机器人结构

FK / TF / 碰撞检测

Web3D / RViz / 数字孪生

这也是机器人应用软件和工业数字孪生方向中非常值得补强的基础知识。

背景

很多项目里的 Dockerfile 并不是为了把所有系统能力一次性打包到生产现场,而是先服务于更常规的 Web 工程目标:

  • 把前端项目打包成静态资源镜像
  • 把 API service 打包成服务镜像
  • 在 CI 中运行 lint、typecheck、unit test、integration test
  • 固定 Node、pnpm、Python、Rust 或其他构建环境版本
  • 让团队在本地和 CI 中使用一致的构建命令
  • 把可交付部分推送到 registry,供测试环境或生产环境拉取

这篇笔记整理一套常规 Web 应用适用的 Docker 发布流程。它主要面向前端、BFF、API service、后台任务 worker 这类服务,不讨论工业硬件 SDK、机器人控制、现场设备网络等更复杂的部署问题。

先给结论

常规 Web 应用的 Docker 发布目标不是“把开发机完整复制进镜像”,而是生成一个可重复、可追溯、可回滚的运行制品。

一条比较健康的 Web Docker 发布链路应该包含:

  1. .dockerignore 控制构建上下文。
  2. 多阶段 Dockerfile 区分依赖安装、构建、测试和运行镜像。
  3. 前端静态资源使用 Nginx、Caddy 或对象存储/CDN 发布。
  4. API service 使用精简 runtime image,只放运行所需文件。
  5. CI 使用相同 Dockerfile 或相同基础镜像运行 lint/test/build。
  6. 镜像 tag 使用版本号、git sha、环境和构建 profile,不依赖 latest
  7. 生产配置通过环境变量、secret、config mount 或平台配置注入,不写死在镜像里。
  8. 发布后有 healthcheck、日志、metrics 和回滚方案。

如果项目中的 Dockerfile 目前只是为了打包前端、API service 层,或者为了在 CI 中跑 lint/test,那么它首先应被看作 Web 工程的构建与验证工具。

Docker在Web项目中的常见用途

1. 本地开发环境

用 compose 启动数据库、Redis、消息队列、对象存储模拟器等依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data

redis:
image: redis:7
ports:
- "6379:6379"

volumes:
postgres-data:

这类 compose 文件主要用于开发和测试,不一定等同于生产部署文件。

2. CI中的lint和自动化测试

Docker 可以固定 CI 环境,避免“我本机能跑、CI 跑不了”:

1
2
docker build --target test -t web-api:test .
docker run --rm web-api:test

如果 Dockerfile 里定义了 linttestbuild 等 stage,CI 就可以复用同一份环境描述。

3. 前端静态资源发布

典型流程是:

  1. 用 Node 镜像安装依赖并构建。
  2. dist/build/ 复制到 Nginx/Caddy 镜像。
  3. 运行时只保留静态文件和 Web server,不保留完整 node_modules。

这样最终镜像通常会比直接把整个项目塞进 Node 镜像小很多。

4. API service发布

后端服务镜像通常只包含:

  • 编译后的应用代码
  • 生产依赖
  • 运行时配置入口
  • 健康检查入口
  • 必要 CA 证书、时区或系统库

不要把 .git、测试报告、开发缓存、未使用 SDK、私钥、.env 文件放进生产镜像。

项目结构建议

一个常见 Web 项目可以按下面方式组织:

1
2
3
4
5
6
7
8
9
10
11
12
13
project/
apps/
web/
Dockerfile
api/
Dockerfile
deploy/
compose.dev.yml
compose.prod.yml
nginx.conf
.dockerignore
package.json
pnpm-lock.yaml

也可以只有一个根目录 Dockerfile,但要把 stage 命名清楚:

1
2
3
4
5
6
base
deps
lint
test
build
runtime

重点不是目录必须长这样,而是团队要能一眼看懂:

  • 哪个镜像用于 CI
  • 哪个镜像用于前端发布
  • 哪个镜像用于 API service 运行
  • 哪个 compose 文件只用于本地开发
  • 哪个 compose 文件用于测试环境或生产环境

第一步:准备.dockerignore

.dockerignore 会影响 Docker build 的上下文大小,也影响是否把敏感文件带进构建过程。

常见内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.git
.github
.vscode
.idea
node_modules
dist
build
coverage
.next
.nuxt
.turbo
.cache
*.log
.env
.env.*

注意:如果 CI build 需要某些 Dockerfile 或 compose 文件,不要盲目忽略。规则要结合项目结构调整。

第二步:编写前端生产Dockerfile

以 Vite/React/Vue/Angular 这类前端应用为例,可以使用多阶段构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# apps/web/Dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

FROM deps AS build
COPY . .
RUN pnpm run build

FROM nginx:1.27-alpine AS runtime
COPY --from=build /app/dist /usr/share/nginx/html
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

对应的 Nginx SPA 配置可以类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 80;
server_name _;

root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}

location /healthz {
access_log off;
return 200 "ok\n";
}
}

构建:

1
docker build -f apps/web/Dockerfile -t registry.example.com/app/web:1.0.0-a1b2c3d .

验证:

1
2
docker run --rm -p 8080:80 registry.example.com/app/web:1.0.0-a1b2c3d
curl http://localhost:8080/healthz

第三步:编写API service生产Dockerfile

以 Node API service 为例,重点是区分构建依赖和运行依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# apps/api/Dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

FROM deps AS build
COPY . .
RUN pnpm run build
RUN pnpm prune --prod

FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]

如果是 Go、Rust、Java、Python,思路类似:

  • build stage 负责安装编译工具和依赖
  • runtime stage 只保留运行所需的二进制、jar、venv 或源码
  • 镜像启动命令明确,配置从外部注入
  • 健康检查接口由应用提供,例如 /healthz/readyz

构建:

1
docker build -f apps/api/Dockerfile -t registry.example.com/app/api:1.0.0-a1b2c3d .

验证:

1
2
3
4
5
docker run --rm -p 3000:3000 \
-e DATABASE_URL="postgres://app:app@host.docker.internal:5432/app" \
registry.example.com/app/api:1.0.0-a1b2c3d

curl http://localhost:3000/healthz

第四步:为lint和test设计独立stage

如果 Dockerfile 的主要用途是 CI 中跑 lint 或自动化测试,可以显式定义目标 stage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

FROM deps AS lint
COPY . .
CMD ["pnpm", "run", "lint"]

FROM deps AS test
COPY . .
CMD ["pnpm", "run", "test"]

FROM deps AS build
COPY . .
RUN pnpm run build

CI 中分别执行:

1
2
3
4
5
docker build --target lint -t app-lint .
docker run --rm app-lint

docker build --target test -t app-test .
docker run --rm app-test

这类镜像不一定要推送到生产 registry。它们的价值是固定验证环境,而不是作为线上 runtime。

第五步:用compose编排本地和测试环境

常规 Web 应用可以用 compose 串起前端、API 和数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
services:
web:
image: registry.example.com/app/web:1.0.0-a1b2c3d
ports:
- "8080:80"
depends_on:
- api

api:
image: registry.example.com/app/api:1.0.0-a1b2c3d
environment:
NODE_ENV: production
DATABASE_URL: postgres://app:app@postgres:5432/app
REDIS_URL: redis://redis:6379
ports:
- "3000:3000"
depends_on:
- postgres
- redis
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
interval: 10s
timeout: 3s
retries: 5

postgres:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- postgres-data:/var/lib/postgresql/data

redis:
image: redis:7

volumes:
postgres-data:

启动:

1
docker compose -f deploy/compose.prod.yml up -d

查看状态:

1
2
docker compose -f deploy/compose.prod.yml ps
docker compose -f deploy/compose.prod.yml logs -f api

注意健康检查命令要确保镜像中真的存在。例如 Alpine 镜像里可能没有 curl,但通常有 wget;如果都没有,就需要安装工具或让应用二进制提供 healthcheck 命令。

第六步:配置和secret不要打进镜像

镜像应该尽量环境无关。开发、测试、预发、生产的差异应该来自外部配置:

  • 环境变量
  • secret manager
  • Kubernetes Secret/ConfigMap
  • Docker secret
  • 受控配置文件挂载
  • 发布平台参数

不要这样做:

1
2
ENV DATABASE_URL=postgres://prod-user:prod-pass@prod-db:5432/app
COPY .env.production .env

更推荐:

1
2
3
docker run --rm \
--env-file .env.runtime \
registry.example.com/app/api:1.0.0-a1b2c3d

.env.runtime 本身也不应该提交到公开仓库,应由部署环境或密钥系统管理。

第七步:给镜像打可追溯tag

生产不要只依赖 latest

建议同时保留:

1
2
3
registry.example.com/app/api:1.0.0
registry.example.com/app/api:1.0.0-a1b2c3d
registry.example.com/app/api:prod-20260701-a1b2c3d

tag 至少应该能回答:

  • 这是哪个应用
  • 是前端还是 API
  • 对应哪个业务版本
  • 对应哪个 git commit
  • 是否是某个环境或发布批次

latest 可以用于临时测试,但不应该作为生产回滚依据。

第八步:在CI中构建、测试、推送

一个 GitLab CI 思路可以是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
stages:
- verify
- build
- publish

lint:
stage: verify
script:
- docker build --target lint -t app-lint .
- docker run --rm app-lint

test:
stage: verify
script:
- docker build --target test -t app-test .
- docker run --rm app-test

build-api:
stage: build
script:
- docker build -f apps/api/Dockerfile -t "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHORT_SHA" .

publish-api:
stage: publish
script:
- docker push "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHORT_SHA"

真实项目还应补充:

  • registry login
  • build cache
  • 多架构构建
  • 镜像漏洞扫描
  • SBOM
  • 制品签名
  • 生产发布审批

但最小可用链路是:先验证,再构建,再推送,最后部署。

第九步:部署和验证

部署动作可以由 compose、Kubernetes、Nomad、Ansible、Helm、Argo CD 或云平台完成。无论工具是什么,验证顺序都类似。

部署前确认:

  • 镜像 tag 是否正确
  • 环境变量是否完整
  • secret 是否存在
  • 数据库迁移是否已执行或有回滚策略
  • 端口和域名是否正确
  • healthcheck 是否可用
  • 日志和 metrics 是否接入

部署后确认:

1
2
curl https://example.com/healthz
curl https://api.example.com/healthz

继续检查:

  • 容器是否持续重启
  • API 日志是否有配置缺失
  • 前端是否能访问正确 API base URL
  • 静态资源路径是否正确
  • 数据库连接池是否正常
  • 关键接口是否通过 smoke test
  • 监控是否收到新版本实例数据

第十步:回滚

Docker 发布的一个重要价值是回滚到旧镜像。

回滚应该切换 image tag,而不是进入容器临时改文件:

1
2
docker compose -f deploy/compose.prod.yml pull
docker compose -f deploy/compose.prod.yml up -d

如果使用 Kubernetes,则通常回滚 Deployment revision 或修改 image tag。

回滚前要确认:

  • 数据库 schema 是否兼容旧版本
  • 配置项是否兼容旧版本
  • 前端资源和 API 是否版本匹配
  • 缓存、队列消息、任务 worker 是否有兼容问题

镜像回滚简单,不代表系统状态回滚也简单。涉及数据库迁移和异步任务时,要提前设计回滚策略。

常见问题

Dockerfile可以同时用于CI和生产吗

可以,但建议用不同 stage 区分目标。

例如 linttestbuild stage 用于 CI,runtime stage 用于生产镜像。这样可以复用依赖安装逻辑,又不会把测试工具和开发依赖带进生产镜像。

前端镜像一定要用Nginx吗

不一定。

常见选择有:

  • Nginx 或 Caddy 镜像
  • Node SSR 服务,例如 Next.js standalone
  • 对象存储加 CDN
  • 云厂商静态站点托管

如果是 SPA,Nginx/Caddy 镜像简单稳定。如果是 SSR,就需要保留 Node runtime。

docker compose能不能作为生产部署

可以用于小规模、单机或内网服务,但要明确它的边界。

如果系统需要滚动发布、自动扩缩容、多节点调度、服务发现、密钥管理、声明式回滚,Kubernetes、Nomad 或云平台会更合适。

生产镜像要不要安装curl

看健康检查方式。

如果 compose 或编排平台的 healthcheck 依赖 curl,镜像里就必须有 curl。也可以改用 wget,或者让应用提供一个内置健康检查命令。

关键原则是:healthcheck 写了什么,镜像里就必须真的能执行什么。

发布负责人检查清单

发布前:

  • .dockerignore 是否排除了无关文件和敏感文件
  • Dockerfile 是否使用多阶段构建
  • runtime image 是否只包含运行所需内容
  • lint/test/build 是否能在 CI 中稳定运行
  • image tag 是否包含版本和 git sha
  • 配置和 secret 是否没有写死在镜像里
  • healthcheck 命令是否真的存在
  • 日志是否输出到 stdout/stderr
  • 前端 API base URL 是否由配置控制
  • 数据库迁移和回滚策略是否明确

发布后:

  • /healthz/readyz 是否正常
  • 容器是否持续运行
  • 日志是否有配置错误
  • metrics 是否上报
  • 关键接口 smoke test 是否通过
  • 前端静态资源和 API 版本是否匹配
  • 旧版本镜像是否仍可回滚

这件事的项目价值

对常规 Web 应用来说,Docker 发布能力体现的是工程交付基本功:

  • 构建可复现
  • 环境可控
  • 镜像可追溯
  • 配置不写死
  • 发布可验证
  • 故障可回滚

比较可信的项目表述是:

我把前端、API service 和 CI 验证流程做成了可复现的 Docker 构建链路,通过多阶段 Dockerfile 区分 lint/test/build/runtime,使用不可变镜像 tag、外部配置、healthcheck 和 smoke test 支撑测试环境或生产环境发布。

这比单纯说“我会写 Dockerfile”更接近真实 Web 工程交付。