Codewhale 的 AI Agent 契约体系:CLAUDE.md 如何把 Ponytail 决策阶梯与代码优先证据政策交给编码 Agent
Codewhale 仓库根目录下的 CLAUDE.md 并不是一份独立的规则文档,而是一个极简的"入口契约":它通过 @AGENTS.md 导入声明,将仓库的 Agent 行为准则、测试证据政策、分支权限与离线边界整体委托给 AGENTS.md 这份权威合同。读完本文,你将理解 Codewhale 如何把"最懒资深工程师"式的 Ponytail 决策阶梯、单向迁移规则、直接提交 main 的权限边界和 cargo test 证据纪律固化为可执行的仓库契约,并能把这套"契约即文档、预算即门控"的模式借鉴到自己团队的 AI 辅助开发流程中。
CLAUDE.md:三行入口与"@import 即权威"的契约结构
先看 CLAUDE.md 的完整内容结构(全文仅 11 行):
# Claude entrypoint
@AGENTS.md
The imported contract is authoritative. Do not invent stricter test or branch
rituals: follow its code-first evidence policy, one-way migration rule, clean
direct-main permission, and the current task's offline boundary.
Its ponytail decision ladder runs before you write code, every time. Read it
there — it is not restated here, because rung 2 is "already in this codebase?"
and a second copy of a rule is the thing the rule forbids.
这段文本传达了四条关键信息:
@AGENTS.md是导入语法:对 Claude Code 这类 Agent 工具而言,@文件指令会在会话启动时把被引用文件的内容注入上下文。因此 CLAUDE.md 的"真身"就是 AGENTS.md,而不是它自身。- 被导入的契约具有权威性(authoritative):Agent 不得自行发明比契约更严格的测试或分支仪式,必须遵循其中声明的四个要素——代码优先的证据政策(code-first evidence policy)、单向迁移规则(one-way migration rule)、干净的直接提交 main 权限(clean direct-main permission)、当前任务的离线边界(offline boundary)。
- Ponytail 决策阶梯每次写代码前都要走一遍,且它只存在于 AGENTS.md 一处。
- 为什么 CLAUDE.md 刻意不复述规则:因为决策阶梯的第 2 级就是"本代码库里已经有了吗?"——规则的第二份副本,恰恰是规则所禁止的东西。这是一个精巧的自洽设计:入口文件用"不复制"这一行为本身,示范了它所加载的规则。
此外,AGENTS.md 开头还声明了作用域规则:"The nearest scoped AGENTS.md adds path-specific rules"——距离当前路径更近的 AGENTS.md 会叠加路径专属规则。仓库中确实存在这样的作用域文件,例如 crates/tui/AGENTS.md 针对终端 UI 定义了"每个事实只有一个 owner"、状态栏颜色语义、以及 --lib 与 --tests 互斥的验证选择规则。这种"根契约 + 就近作用域契约"的分层结构,是理解整个 Codewhale Agent 体系的基础。
Ponytail 决策阶梯:写代码之前先走完的七级台阶
AGENTS.md 的核心是 "The ponytail method"(ponytail 决策法),源自仓库标注的外部同名理念——"房间里最懒的资深工程师:他什么都不说,写一行代码,然后它工作了。最好的代码就是你从未写出的代码。" 要求在写代码之前按顺序走完决策阶梯,并在第一个能回答问题的台阶停下:
| 台阶 | 问题 | 动作 |
|---|---|---|
| 1 | 这个功能需要存在吗? | 直接跳过 |
| 2 | 本代码库里已经有了吗? | 复用它 |
| 3 | 标准库能做吗? | 用标准库 |
| 4 | 平台原生特性能做吗? | 用它 |
| 5 | 已安装的依赖能做吗? | 用它 |
| 6 | 一行代码能解决吗? | 一行解决 |
| 7 | 以上都不行 | 写"能工作的最小实现" |
文档强调这条阶梯在理解问题之后运行:"对方案可以懒,对先读代码绝不懒——不读调用点就写的短 diff 不是 ponytail,而是猜测。"
任何台阶都不允许省略的边界
契约同时划定了"永远不裁剪"的红线:信任边界校验、数据丢失处理、安全、可访问性("简洁不是撤掉守卫的理由")。
第 2 级台阶的落地:grep 规则与两条推论
仓库承认第 2 级("已经有了吗")是它最容易失败的台阶,并为此设立了带名字的规则:在新增任何名为 model_*、*_config、provider_* 或声称"桥接/镜像/暂存"已有事物的模块之前,先 grep 已有实现并直接修改它;若确需新层,模块文档必须指名它替代的前身,否则应修改原实现。文档给出两条在此仓库"挣得"的推论:
- 抽象必须删除调用方代码。 如果采用一个抽象纯粹是义务(强制方法、没有真正干活的默认实现体),它会被构建出来、被采用一次、然后被废弃。
- 要么迁移最后一个消费者,要么不要开始。 "新框架 + 一个调用方 + 把其余的记进 ticket + 把警告静音"这种做法会同时交付两套系统,外加一条已经不再为真的注释。如果迁移放不进来,应缩小切片(slice),而不是缩小采用范围。
这条推论在源码中有一个可验证的"运行收据":scripts/check-dead-code-budget.py 是一个针对 #[allow(dead_code)] 属性的棘轮(ratchet)脚本——它的注释写道:"一次清扫是快照;一份预算是方向。这个门控让数量成为单向门"。脚本本身只机械检查一件事:dead_code 总数不允许回升,配合预算文件 scripts/dead-code-budget.json 运行(python3 scripts/check-dead-code-budget.py 强制检查,--update 重写预算)。
工作规则:状态检查、直接提交权限与离线边界
AGENTS.md 的 "Working rules" 一节定义了 Agent 的日常行为约束,其中与 CLAUDE.md 声明的四个要素一一对应:
- 先检查状态与现有消费者,再动手。 编辑前检查 git 状态,保留无关的、已修改的、未跟踪的工作。
- 直接提交 main 的权限(clean direct-main permission):一个小的、连贯的变更,在该检出(checkout)是最新、干净且独占受影响文件时,可以直接提交到
main;而 worktree 仍然是处理冲突、脏状态、过期或独立工作的正确安全边界。本地提交权限绝不隐含推送、合并、打标签、发布或部署权限。 - 离线边界(offline boundary):当任务是纯本地任务时保持完全离线——不浏览、不做 GitHub 或远程 Git 操作、不下载、不安装依赖、不调用 provider、不传输源码/diff;把缺失的外部回执(receipt)记录下来,继续本地工作。
- 命名与兼容标识的迁移纪律:公开名是 Codewhale;
CodeWhale、codew、协议名、存储键等兼容标识只能通过显式迁移变更。 - Provider 与模型始终是一等公民且 provider-中立。
- 不重写已发布的历史、不重打标签、不 force-push 共享分支、未经显式授权不发布,并保留人类贡献者的署名。
合入他人工作与门控合并:把"贵"算在贡献者头上
Landing other people's work
外部贡献者的分支变旧,是因为"我们"在持续合入,而不是他们做错了什么,因此"把他们的时间当作比我们的更贵":
- 绝不让贡献者围绕我们的变更重做 rebase。 如果 PR 只因 main 前进而冲突,由维护者解决;先读他们相对 merge base 的 diff,精确知道他们加了什么,然后重放这些内容,而不是手工合并两个大侧面然后祈祷。
- 函数中间的冲突不能靠"两边都保留"解决。 Git 的冲突标记可能落在函数体内部,两侧都留会产生括号不平衡、看起来合理但编译不过的代码。正确做法是整体取一侧,再把另一侧的增补插回它原来的锚点。
maintainerCanModify不保证对 fork 的推送权限。 推送被拒时,把解决后的合并落到本仓库的integration/<topic>-<pr>-<date>分支上,从那里落地。集成分支是任何有冲突或多个 PR 在动的场景的常规路径——比反复 rebase 到一个持续移动的 main 更便宜,且不动贡献者的分支。- 假设 PR 卡死之前先检查贡献门控。 未列入名单的作者,其 workflow 运行会停在
action_required永远不启动,PR 看起来被遗弃其实没人看过。批准运行之后修根因:把作者加进.github/APPROVED_CONTRIBUTORS(格式all:username),或在其线程里评论/lgtm(PR 范围)//lgtmi(issue 范围)。 - 署名要保留在机械层面,不只是礼貌层面。 commit 作者与
Co-authored-bytrailer 必须使用贡献者自己的 GitHub 绑定邮箱;AUTHOR_MAP与.mailmap只是项目约定——GitHub 的贡献图并不读取它们。
Merging under a gate
- 门控就是它的产物(artifact)。 当某条 rail 声明 PR 只在验收记录通过时才能合并,合并时那条记录必须字面上写着 PASS。"我重跑了,失败项都是本 PR 不拥有的行"是一个应当先写进产物的判断,而不是越过门控的理由。
- 读 review 线程,而不是只看 check 汇总。 绿色的 check 加上未读的、已有确认发现的 review,等于带着已知 bug 合并。
- 产物含糊时,消除的是含糊,而不是合并本身。
声称"测试通过":代码优先的证据政策
这是 CLAUDE.md 所承诺的 "code-first evidence policy" 的具体展开,三条规则各自都有真实教训支撑:
- 引用真实的
test result: N passed; M failed行,并确认覆盖该变更的测试N > 0。cargo test <filter>在 filter 什么都没匹配到时以退出码 0 结束——这里已经发生过把退出码单独当作通过证据的事故。 - 优先证明回归测试在修复之前会失败。 两边都通过的测试锁定的是实现,不是缺陷。
- 信任任何 harness 的分数之前先审计它。 文档给出的真实事故:
ok = ok and X or True会被解析成(ok and X) or True,曾静默地把 12 行未评估的报告报成通过。
同样的证据纪律延伸到测试哲学本身("Code, migrations, and evidence" 一节):
- 产品意图与观察到的运行时行为,优先于测试所偏好的实现形状;修产品,不要为了保住脆弱断言而扭曲生产代码。
- 测试是选择性证据,不是规格说明书。默认不加测试。 只在它廉价地保护高风险行为(安全、数据完整性、协议兼容性、复现的回归)时才新增或保留。
- 重写或删除重复覆盖、冻结内部实现、过度指定文案或布局、保留过时行为、或成本高于其所覆盖风险的测试;但绝不为让门控通过而削弱真实的安全或数据完整性行为。
- 优先选择聚焦编译、相关既有检查和直接的产品/手工证据;只有当变更真正产生跨切面或发布风险时才跑大套件;不重复重跑未变化的套件。
- 声明过的迁移是单向的。 仓库采用替代架构或共享主干后,新工作走新路径,被触及的遗留代码向它移动,不为方便再添一个遗留调用点;兼容路径只为真实的外部契约保留,并显式标注该边界。
当前契约:单条 turn loop、唯一 base prompt 与源码护栏
"Current contracts" 一节列出了仓库当前最容易被误判或误改的结构性事实,每一条都可以在源码中直接验证:
1. 唯一 base prompt。 BASE_PROMPT 位于 crates/tui/src/prompts/text.rs,是该文件中的 pub const BASE_PROMPT: &str。从源码注释可以确认,它曾是 4 个目录下 17 个 prompts/*.md 文件的集合,后被逐字节合并进单一模块以便"像运行时组装那样自上而下读完整个 prompt 契约";同一文件还包含面向非交互宿主的 HEADLESS_BASE_PROMPT(crates/tui/src/prompts/text.rs),以及语言镜像律、人格层、审批策略层等按组装顺序排列的常量。
2. 全仓库恰好一条 turn loop。 唯一的回合循环是 crates/tui/src/core/engine/turn_loop.rs 中的 Engine::run_turn。文档特别澄清了一个极易混淆的事实:crates/tui/src/core/ 是 TUI crate 内部的一个模块,不是 crates/core——后者负责请求构造、有界片段(bounded fragments)与 thread/session 类型,且本身不跑任何回合。
3. 守卫测试用源码扫描而非类型检查防止第二条 loop。 crates/core/tests/single_turn_loop.rs 的测试头注释解释了动机:crates/core 曾有一棵占位的 engine/ 树,其 Engine::run 收到 Op::SendMessage 后只追加 journal 并发出假的 TurnComplete,从不接触模型;它没有调用方,但文档注释支撑着架构文档里"core 拥有 agent loop"的说法,读者完全可能在其上构建。因此守卫被设计为源码扫描——被防止的对象是"第二份实现",按定义它不会与第一份互相可达,类型检查抓不到。测试遍历整个 crates/ 目录树收集 .rs 文件,断言以 async fn run_turn / pub async fn run_turn / pub(crate) async fn run_turn / pub(super) async fn run_turn 开头的行有且仅有一处,并对源文件总数做下限检查(不足 100 个文件则扫描不可信)。
4. 其余反复被误判为死代码的活跃模块——删除前必须先验证消费者:tui/src/context_budget.rs、tui/src/model_registry.rs、tui/src/prompt_zones.rs、tui/src/tools/remember.rs 与 config/src/route/;原生记忆在 tui/src/native_memory.rs,tools/remember.rs 是它的捕获路径。
5. KV-cache 前缀契约。 系统提示 + 工具目录是会话钉住的 KV-cache 前缀(详见 docs/CACHE.md)。任何新的会话上下文贡献者必须声明其 KV-cache 影响——冻结前缀,还是仅追加历史;永远不要把易变事实拼进前缀,而是以 user 角色消息追加。
6. 模型侧的子代理工具只有 agent 一个,不得复活已移除的 agent_open/agent_eval/agent_close/delegate_to_agent 表面或平行的生命周期/标签系统。
按风险选命令:验证命令与 dev-test.sh 面积映射
契约明确反对仪式化地跑全套测试,而是"按风险选择"。根契约给出的代表性命令(以当前仓库内容为准):
cargo fmt --all -- --check
cargo test -p codewhale-config -p codewhale-protocol
cargo test --workspace
cargo build --release -p codewhale-cli -p codewhale-tui
以及三条补充规则:cargo nextest run(配置在 .config/nextest.toml)是运行有意选出的套件的最快方式;cargo test --no-run 可以在不执行无关用例的情况下回答编译问题;cargo test --doc 在 doc 示例变更时覆盖它们。
把"代码区域 → 最快 -p 调用"这件事工程化的是 scripts/dev-test.sh:给定一个区域名或源码路径,它打印并执行该区域最快的 cargo/nextest 调用,例如脚本内置的映射 config → cargo test -p codewhale-config --lib --locked、tui → cargo test -p codewhale-tui --lib --locked、tui-integration → cargo test -p codewhale-tui --test integration --locked 等。脚本注释与 docs/BUILD_PERFORMANCE.md 说明它还叠加了可移植的隔离构建目录拓扑(scripts/dev-cache.sh、scripts/dev-cargo.sh),使新 worktree 也能获得独立的 Cargo 构建目录;CODEWHALE_DEV_NEXTEST=0 可强制使用 libtest 而非 nextest。
作用域契约 crates/tui/AGENTS.md 进一步给出了 TUI 专属的验证选择:--lib 与 --tests 互不相交,选拥有该行为的那个;只有风险真正横跨两者时才用两者或 workspace 门控;对 PTY 失败,先直接复现行为再修改。
最后,契约要求汇报实际运行过的命令,并区分源码、本地测试、打包产物、CI 与公开发布状态;描述该论断实际需要的证据——"测试数量不是产品质量的代理";社区报告、PR、日志和 review 都是证据。
署名规则与新门控的试运行要求
两条收尾规则值得单独强调:
- 收割的贡献者署名依然是规则。 当贡献者的工作以我们的 commit 落地时,该 commit 必须携带
Harvested from PR #N by @handle与按其 GitHub 绑定邮箱署名的Co-authored-by,以便auto-close-harvested.yml带着署名关闭其 PR,让贡献图反映现实;规范的人类身份来自.github/AUTHOR_MAP。同时,"bot 或 agent 是否也出现在 trailer 中"已不再重要——曾经负责检查 trailer 身份的 CI 校验已被移除,因为它拒绝了普通的 agent 提交,成本高于它买到的整洁度;给人署名,别在清洗工具 trailer 上花时间。 - 保持无关工作原样,新的强制检查默认 dry-run,除非被显式批准。
小结:一份"可执行"的 Agent 契约
把 CLAUDE.md 与其委托的 AGENTS.md 合起来看,Codewhale 展示了一种把 AI 编码 Agent 的行为约束从"提示词"升格为"仓库基础设施"的做法:
- 入口不复制规则,而是
@导入权威契约,用"不复述"本身示范第 2 级台阶; - 抽象原则全部落到机械门控——
#[allow(dead_code)]预算棘轮(scripts/check-dead-code-budget.py)、扫描整个 workspace 的单 turn loop 守卫(crates/core/tests/single_turn_loop.rs)、按面积映射最快测试调用的 scripts/dev-test.sh; - 证据纪律替代仪式:引用真实的
test result行、确认N > 0、审计 harness、按风险选最小验证集; - 权限与迁移都是单向的:本地提交不含推送权限、声明的迁移不回退、兼容路径必须显式标注。
对维护者而言,这份文档的价值不在于任何单条规则,而在于"每条规则要么可执行、要么可验证"的整体设计——这正是它敢在 CLAUDE.md 里只留三行导入的原因。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00