Archyl Harness:在动手之前先声明自己要干什么的编码代理
上周我写过三个代理、三个 pull request,以及一个不一致的系统。那篇结尾留了一个练习:挑出你团队最近一次合并了不止一个由代理写的 pull request 的那一周,把它们并排读完,然后问一问,在你现在的配置下,有什么会告诉你它们彼此不一致。
我在我们自己的仓库上做了这件事,答案是:没有。不是"审阅的人最后注意到了",也不是"CI 拦下了一半"。就是没有,因为没有哪个代理说过自己接下来要做什么。每一个都读了仓库、写了代码、开了一个 pull request。人能看见其中两个在同一个服务上干活的第一个时刻是评审,而那是最后一个时刻,到那时两边都已经自信完了。
所以我们把缺掉的那一步做了出来。Archyl Harness 这周发布了。它不是又一个编码代理。它坐在你已经在跑的那些代理之上,让每一个代理在碰任何东西之前,对着已记录的架构声明一个工作单元。
从内部看一次工作会话
这个闭环有四次调用,以 MCP tool 的形式暴露出来。代理先做计划,开一次会话,一边工作一边发 heartbeat,最后带着真正发生的事情把会话关掉。
下面是其中的第二次调用,来自 Archyl 项目自己身上的一次真实会话,做了删减:
▶ start_work_session(
task: "rank recalled memories by freshness so stale facts stop winning",
agentName: "claude-code/vincent")
# Harness Session
- **Session ID**: `24643fa6…`
- **Gate**: warn
- 1 target element(s) are being worked on by other active sessions — coordinate before changing them
- **Leased elements** (you are the announced worker on these):
- component `Harness Service`
- container `MCP Server`
- **Conflicts** (someone else is already working here):
- MCP Server — held by claude-code/memory-ui: render the memory graph with element clusters
**Protocol**: call `heartbeat_work_session` at least every 30 minutes while working,
and `finish_work_session` with a summary (and decisions worth recording) when done.
## Most relevant elements
- **Harness Service** (component) — `backend/internal/service/harness`
Work sessions, leases, preflight gate, element memory.
## Related decisions (respect these)
- ADR-5: Agents propose, humans merge [accepted]
## What previous sessions did here
- **MCP Server** [PITFALL] (claude-code/vincent, 1 day ago): every new ID argument
name must be added to idArgumentResolvers in authz.go, or the cross-org check
silently skips it.
在这一次调用里发生了四件事,而它们没有一件是一份规则文件做得到的。
这个任务是对着 C4 模型解析出来的,所以代理拿到的是真正相关的那一片架构,而不是全部。咨询式租约(advisory lease)被加在它即将改动的元素上,下一个代理就是这样知道有这一个代理在的。预检 gate 返回了一个判定。而那份简报还带来了约束这次工作的决定,以及上一个站在这里的代理吃过苦头学到的东西。
最后那一行就是 memory(记忆),它值得一篇自己的文章,而不是这篇里的一段。简短版本是:会话会把笔记、约定和坑挂到架构元素上留下来,下一次会话会自动把它们拿回去。
gate 有三种判定,而 deny 是罕见的那个
预检 gate 是刻意做小的。它在工作开始之前回答一个问题,给出一个代理可以据此行动的判定。
allow 的意思是:没有别的会话在你的目标元素上持有租约,也没有 error 级别的 guardrail 适用于这个任务。继续。
warn 是常见的那个,而且会附上理由。要么已经有另一个会话在你即将改动的元素上工作,要么有一条严重级别为 error 的合规规则覆盖了这个任务。前一种情况下的准确字符串就是你在上面看到的那条:N target element(s) are being worked on by other active sessions — coordinate before changing them。代理会继续,但它必须处理列出的每一条理由,而这些理由是会点名字的。
deny 只在会话自己要求时才会发生。传入 exclusive: true,租约冲突就会让会话停下,而不是只警告它。这是给那种绝不能和别人抢的工作准备的标志:一次 schema 迁移、一次 contract 变更、一次会牵动所有调用方的 rename。会话根本不会开起来,代理会被告知去向用户汇报,而不是绕过去。
在这件事上说准确,比把 gate 讲得聪明更重要。deny 不是策略引擎。它不会读你的计划然后出于原则拒绝它。它只是在你说过某个元素是排他的之后,拒绝让两个代理同时认领同一个元素,除此之外的一切都是代理必须给出交代的警告。
Guard 盯着写入
会话覆盖的是意图。Guard 覆盖的是真正被写下去的东西。
它是给 Claude Code 用的 PreToolUse 钩子,随插件一起安装。在代理写入或编辑一个文件之前,钩子会把这个文件按编辑之后的样子重建出来,发给你项目的合规规则,然后读回判定。一个 critical 级别的违规会挡住这次写入,并把理由返回给代理:
Archyl Guard: this change violates the project's architecture rules for
backend/internal/adapter/http/handlers/report.go:
- [critical] No direct database access from HTTP handlers — move the query
behind a service
Adjust the change to respect these rules, or ask the user whether to override them.
代理读到这个,把分层改好,然后继续。没有人被打断,而那次违规从来没有进到任何分支里。
有两个设计选择值得直说。ARCHYL_GUARD_BLOCK 控制阈值:默认是 critical,想拦得更多就用 high,只想警告就用 off。而且这个钩子在任何情况下都是 fail-open 的。没有 API key、没有网络、没装 jq、响应很慢:这次编辑照样原样通过。一个能把别人的编辑会话搞崩的 governance 工具,一周之内就会被卸载,所以它不能这么干。
把闭环合上
finish_work_session 接收一个诚实的结果:一段总结、值得记录下来的决定、还没做完的后续事项。租约被释放,总结被钉到这次会话持有过的元素上,而如果这次工作改变了架构,createChangeRequest: true 会开出一个 Architecture Change Request(架构变更请求)草稿。
正是这一部分让模型不会悄悄 drift 掉。一个重构了某个服务的代理,不会偷偷去改 C4 模型。它提交一份提案,由人来读文档应该怎么跟上,而合并要过我们上周写过的那道版本检查。代理提出建议。人来合并。我们没有打算拿掉这条边界。
在这一切之上,Agent Hub 里的 Fleet console 实时显示组织里的每一次会话:谁在工作、在做什么、持有哪些元素、卡在哪个 gate 后面、最后一次 heartbeat 有多新。处于活跃租约下的元素,还会直接在 C4 图上显示一个"正在处理"的标记,而这正是"这里面已经有别人"这句话真正有用的那个视图。
我们把它建在它自己下面
Harness 是由在 Harness 之下工作的代理们,在一个记录着 Archyl 的 Archyl 项目上建出来的。
这不是演示。这是唯一能弄清楚这个闭环能不能扛住真实工作的办法,而它也确实把产品改了好几次。有些会话是真的撞到了 warn,撞在真实的冲突上,因为确实有两个代理在同一个小时里编辑同一个 container。上面那段 transcript 里的坑,是某次会话在为它搭进去一个下午之后写下的 memory,而后来的一次会话在碰同一个文件之前,从简报里把它拿了回去。那些会话里出来了三个 Architecture Change Request,每一个都是一个人在审阅模型该如何跟上代理刚刚做过的事。
它也带来了一些更小的修正,那种只有 dogfooding 才会暴露出来的。控制台里的 gate 徽标以前会为 allow 渲染一个中性的小标签,直到有人指出:在每一行都显示"没有任何问题"的徽标就是噪音。现在,当判定是没有理由的 allow 时,它什么都不渲染,而这背后的理由被作为一条约定存进了项目里,这样下一个碰那个 component 的代理就不会好心地把它加回去。
安装只要一条命令
在你仓库的根目录下:
curl -fsSL https://raw.githubusercontent.com/archyl-com/agent-skills/main/templates/setup.sh | bash
它会问你要项目和一个 API key,然后写下三样东西:一个指向 Archyl MCP 服务器、带 ?profile=coding 的 .mcp.json,一个可以提交进仓库、把仓库绑定到项目上的 .archyl.json(key 留在你的环境里),以及追加到 CLAUDE.md 和 AGENTS.md 里的 harness 闭环:
# Architecture — Archyl Harness
This project's architecture is documented in Archyl. Work under the harness loop:
1. For any non-trivial task, call `plan_work` first — it returns an implementation
plan grounded in the documented architecture.
2. BEFORE changing code, call `start_work_session` (task + your agent name).
Read the briefing: gate verdict, leased elements, conflicts, decisions, guardrails.
...
然后在 Claude Code 里执行 /plugin marketplace add archyl-com/agent-skills 和 /plugin install archyl-developer@archyl-marketplace,这会带来 archyl-harness skill 和 Guard 钩子。插件版本 0.7.0 已经上线。
?profile=coding 是让其余部分能跑起来的那个小细节。Archyl 的 MCP 服务器暴露了 189 个 tool,对于管理一套架构来说这是正确的数量,而对于摆在一个正想加限流的代理面前来说,这是错误的数量。coding 这个 profile 只公布 16 个:定位、按任务圈定的上下文、四个会话 tool、memory,以及合规和 diff 检查。没有任何一个会直接编辑模型,因为那条路要走变更请求。在我们自己的测试里,拿到完整目录的代理会去把目录探索一遍。拿到十六个 tool 的代理会照着闭环走。
它做不到的事
租约是咨询式的。它不锁任何东西。租约只是告诉第二个代理第一个就在里面——在它的简报里,在控制台里,在图上。它不会拦住它。现在这是刻意的,因为在你的架构模型上加一把硬锁,是一种非常有效的、让你团队在某个代理半途死掉时干不下去的办法;但你不该把租约描述成给团队用的互斥。
一个从不开会话的代理是隐形的。这里的每一条保证,都从代理调用 start_work_session 开始。协议里没有任何东西强制这次调用。skill 和 CLAUDE.md 里的片段把它变成默认行为;一个铁了心的代理,或者一个没带 harness skill 就连上来的代理,照样会像以前那样写代码。Guard 钩子是唯一一个不需要配合就会触发的部分,而且只在 Claude Code 里。
deny 的好坏取决于你写了什么。gate 读的是你的合规规则和你的租约。一套空的规则加上一个代理,会永远产出 allow,这在技术上完全正确,也完全没有信息量。
计划是有依据的,但不等于正确。plan_work 是一个从你的 C4 模型、ADR 和 guardrail 构建出来的 AI 计划,并带有一个确定性的兜底:在没有配置 AI 提供方、或者模型返回了没法用的东西时,它会返回排好序的基础事实。它尊重已记录的架构。它不知道已记录的架构是不是一个好主意。
变更请求需要一个已知的作者。用没有绑定到某个用户的凭据启动的会话开不出变更请求,finish_work_session 会在响应里这么说,而不是直接失败。如果你的 CI 机器人用的 key 是组织级的,它的结果会落成 memory,但不会落成一份提案。
从哪里开始
如果你已经在对着一个有文档的 Archyl 项目跑代理,上面那条安装命令大概要五分钟,而第一次会话就会告诉你一些东西。挑一个有两个代理同时在跑的下午,盯着 Fleet console 看。有意思的时刻是第一个 warn,因为它给一次过去要到评审才看得见的碰撞点了名。
如果你还没有一份记录下来的架构,那才是真正的前提条件,而且和从来一样:Harness 是用模型来仲裁的,所以空的模型什么也仲裁不了。
Harness 是 archyl 的一部分:工作会话、预检 gate、Fleet console 和 memory。插件、skills 和 Guard 钩子以及 GitHub Actions 都是开源的。完整的安装说明在 Harness 指南里。相关阅读:很多代理,一个架构、为什么你的代理有一份规则文件而没有模型,以及它背后的 MCP Server。