
用 Jev 构建 Agentic Harness:一份把『决定』与『执行』分开的工程实录
Avid 的 3500 字构建实录:把 Jev 当决策层放进本地优先的 Rust 编码工作区 keel。核心原则只有一句——模型可以建议下一步,宿主仍然拥有这一步:宿主准备菜单、Jev 从菜单中选、宿主再检查一次;固定路由与进行中会话绕过自动选择;选择不授予权限。附决策回执如何变成可重放的评估场景。
本文整理自 Avid(@Av1dlive)2026 年 9 月 23 日发布的 Builder's Guide 长文《How to Build Agentic Harness using Jev》,以及配套开源项目 keel 0.2.0 的代码与文档。这是一篇难得的「边界工程」实战记录:作者没有炫技,而是反复追问同一个问题——模型可以在哪里做选择,宿主代码还能不能验证这个选择。
TL;DR:如果你不想读 3500 字的正文,直接去用 github.com/codejunkie99/keel——把仓库丢给你的 agent 就行。想搞懂每一层为什么这样设计,往下读。
写代码之前:先给「决策」一个形状
作者的出发点朴素:我想要一个会变得更好的编码 agent,而且我想知道它变好时到底是什么变了。系统仍然需要选模型、限制工具;然后要检查这个选择、评判结果。这就是 Jev 工程的切入点——给系统一个可检查的选择,并保留足够的证据来判断这个选择是否有帮助。
一个有用的决策需要三样东西:
- 有名字的选项;
- 代码能检查的结果;
- 事后发生了什么的记录。
最后一点很关键:多加一次模型调用,它得挣到自己的位置。文章分四部分:如何用 Jev 做决策层、为什么编码 harness 是放置该层的好地方、keel 里建了什么、以及「自改进」到底意味着什么(和还不能证明什么)。贯穿全文的一句话是:
模型可以建议下一步,但宿主仍然拥有这一步。这个区别听起来小,却会改变整个架构。
图 1:宿主准备菜单,Jev 从菜单里挑,宿主再检查一次。
01 / 如何使用 Jev:给它一个小而清晰的工作
在这个构建里,Jev 从宿主准备的列表中选择。「宿主」指模型周围的应用代码——在 Jev 发言之前,宿主代码已经决定了哪些选项可用。它返回一个类型化结果:固定格式,代码可检查,含义是「选这个」或「我选不了」。这就是全部契约。
宿主准备菜单;Jev 从菜单里挑;宿主再检查一次挑出来的结果。
保持接口窄有实际原因:让模型返回任意指令,调用它的每一段代码都得去理解那段指令的含义;而固定格式让这件事变得「无聊得恰到好处」——读结果、对照当前状态、接受或回退。作者给了一个很好的类比:忙碌车间里的调度员可以选择哪个工作台接活,但他不能悄悄改写锯子的安全规则。
图 2:选择器可以行动的两处——新任务路由,与循环内的下一步焦点。
选择器可以在两个地方行动
1) 为新任务选路由。当新任务没有固定的 provider/模型时,宿主准备合格的候选列表,Jev 或本地 Laya 从中挑选,或弃权。用户已固定的路由、进行中的会话与恢复的会话,选择器一律保留不动。「挑最好的模型」需要边界:宿主必须能运行它提供的每一个选项,所以在 Jev 看到选项之前先过滤——剔除未安装或未启用的 provider、找不到的模型、不支持的推理档位。(provider 的登录检查发生在会话启动时,即使路由通过了宿主检查也可能失败。)
2) 在 DeepSeek 循环内选下一步。循环提供四个焦点选项:
- inspect(检查)
- implement(实现)
- verify(验证)
- answer(回答)
图 3:inspect / implement / verify / answer 各自映射到宿主定义的工具包。
每个选择映射到宿主定义的工具包。选择器返回一个焦点 id,宿主找出允许的工具,在使用前再对照当前工具定义核查一遍。「answer」是个好例子——它没有任何工具。如果选择器说 answer,系统不会递给它一个 shell 然后指望它守规矩;允许的工具包为空,因为在这里 answer 就是这个意思。
用一个小例子看清这个分工:让应用修一个解析器 bug。如果你固定了 provider,选择保持不变;如果是无固定路由的新任务且自动选择已启用,宿主提供合格路由,选择器在列表内选择,它无法召唤一个不存在的 provider。之后在嵌入式 DeepSeek 循环里,决策变小了:选 verify 会改变准入的工具包——但这不证明补丁是对的,检查仍然要跑,结果仍然要有意义。作者认为这里正是设计的精髓:「用哪个工人?」和「接下来用哪些工具?」值得分开设置边界,因为它们是不同的工作,有不同的失败模式。
图 4:宿主、决策层与 provider 自有的内部循环,需要在图上就标清归属。
重要的边界:外部 agent 保留自己的循环
keel 可以通过 Agent Client Protocol(ACP)承载多个编码 provider。这些 provider 运行自己的工具循环,也可能暴露工具、上下文大小或交接的控制。但在这个构建里,Jev 和 Laya 不接管那些内部机制。原因很实际:我们可以读 provider 的能力描述,但描述本身不等于宿主有办法执行那个动作——一个列出来的 slash 命令不会自动变成 Jev 能调用的动作(在当前路径下,slash 命令可能只是变成发给 provider 的提示文本)。
在架构图上多画一条箭头,并不等于创造了一个 API。边界跟随我们能控制的代码走。
实践上存在三层:宿主、决策层、可能还有 provider 自有的 agent 循环。宿主能把新任务路由给某个 provider,但这完全不能说明谁控制 provider 的下一次工具调用。keel 的能力审计(列出命令、技能、模式与工具)帮助划清这条线。
一个相邻表面是 computer-use:keel computer-use decide 允许 Laya 或 Jev 从宿主准备的低风险动作 id 中选择或弃权——但该命令不执行桌面动作。选择一个提议的点击和执行这次点击是分开的职责。兼容 ACP 的 agent 可以通过 MCP 使用已安装的桌面工具,那些 agent 仍然拥有自己的内部循环。
选择与权限是两回事
Jev 可以选择一个候选,但它不能批准危险动作。需要权限的工具走正常的权限路径,应用要求用户批准的地方仍然需要用户批准。模型选择不授予权威,高置信度值也不授予。
行动之前再查一次状态
应用选择之前,宿主检查该动作是否仍然匹配当前任务状态;如果 provider 列表变了、路由过期了,宿主可以拒绝并回退。DeepSeek 循环内同样在使用前再次核对命名工具。两处检查目的相同:防止一个旧决策作用于已经变化的系统。「这是无聊的部分。无聊正是你想要安全检查的样子。」
图 5:路由选择的各条路径——没有一条授予运行工具的权限。
给不确定性一条清晰的路
「我选不了」是一个合法结果。宿主仍需知道接下来做什么。回退要在调用选择器之前定义好;回退发生时要记录在案,这样回退才不会被算成选择器的功劳。验证结果并观察结果之后,宿主写入一条决策事件(receipt),让我们能问普通的工程问题:候选有哪些?选择器选了还是弃权了?验证接受了吗?回退了吗?工具结果是什么?——这些记录不是私有的思维链。模型的解释并不能证明事情为什么发生。
02 / 为什么放进编码 harness?
人们常把编码 agent 想成一次调用:prompt 进,补丁出。真实工作更像一连串分叉:
- 这个任务用哪个模型?
- agent 该先检查文件还是直接开改?
- 此刻哪些工具相关?
- 决策还在途时仓库变了没有?
- 系统该重试、弃权,还是问人?
- 改动编译过了吗?聚焦检查过了吗?用户意图保住了吗?
这些决策成本不同:小改名用慢模型是浪费时间;而错误的决策可能比慢的决策更贵——快模型在大迁移上可能需要更多重试;同一工具选择在一个工作区无害、在另一个工作区后果严重。harness 持有塑造每个动作的细节:仓库当前状态、可用的 provider 与工具定义、权限与任务状态、检查的结果。模型是这个系统的一部分,但不是系统本身。
独立的决策演示可以展示模型在三个标签间选择,但那不告诉你这个选择是否可用。放进编码 harness,宿主能回答具体问题:这些选项当时真的可用吗?任务是新的、固定的还是运行中的?被选工具在执行开始时还存在吗?权限层允许吗?工具返回了什么?验证发现回归了吗?作者的态度很直接:把选项、选择和结果给我看。没有这些,宣称 agent 变好了基本就是感觉。
图 6:Laya、Jev 与 Normal 三种模式各自的路径与宿主检查点。
三种模式、三种不同的选择
Laya 和 Jev 是独立的两个选择器;Normal 模式跳过选择。Core ML 是苹果的本地模型运行时;Jev 使用机器上已有的受保护凭据,这个构建没有密钥输入界面。「永远用最聪明的模型」忽略了延迟、隐私、凭据和用户意图——模式选择让这些权衡在应用里有了一个容身之处。
额外那次调用必须挣到自己的位置
一个重要的跑题提醒:路由可能变成自己的小官僚机构。如果选工人的时间比任务本身还长,我们造了一个精心设计的候车室。作者想看的比较是任务总成本:选择器时间 + 工人时间 + 重试 + 审查开销。
更便宜的一次决策调用,只有在整个任务受益时才有用。本文没有用编码基准证明这种收益。还有一个明晃晃的限制:选择器选不出宿主忘记提供的选项。
好的 harness 在「决定」与「执行」之间画出硬边界:路由选择时模型只拿到候选 id(指向宿主准备好的选项的标签),id 不能让模型发明新命令或新 provider;DeepSeek 循环内选择器只返回一个焦点 id,宿主把它翻译成具体的允许工具集,然后再检查一次。这意味着决策层是可替换的——调用方不需要信任它的说辞,只依赖宿主的检查。这就是作者喜欢小接口的原因:你能指出它做的确切选择、以及这个选择放行了的确切工具。多给一分自由,运行出错时也就多了一分需要解释的行为。
同时 harness 应该保留那些重要的乱糟糟的部分:选择器弃权了要显示,宿主拒绝了过期动作要显示,回退到用户原路由要记录。这些细节能让我们追踪:宿主提供了这些选项 → 选择器返回了这个 id → 宿主按这些规则接受或拒绝。也必须诚实:把决策层放进 harness 并不会让每个结果都正确——它只是让边界更容易规定、结果更容易检查。它不证明 Jev 选了最好的模型,也不意味着成功的构建是选择器的功劳。另外,「本地 Laya」描述的只是选择器——你的编码工人可能仍然调用托管模型,本地跑的 ACP 客户端也一样。在把整个工作流称为「本地」或「私有」之前,先逐跳追踪。
03 / 我们建了什么:keel 0.2.0
构建物是 keel 0.2.0,一个 local-first 的 Mac 编码应用:
- 基础:Avid 派生的编码工作区
- 语言:Rust
- 界面框架:GPUI
- 平台:Apple Silicon,macOS 15+
这个细节很重要,因为故事里有两种工作:一种是应用——带会话、provider 连接、工具与本地决策模式的真实编码工作区;另一种是 Jev 工程研究与评估材料。它们相关,但不是同一个交付物。工作从一个已有的编码环境起步(已有任务、仓库、provider 会话与工具),这让研究有了实际中心:决策附着在任务、仓库、provider 会话与工具执行上时如何表现。
设计过程:让选择保持可读
有用的设计问题很简单:Jev 能在哪里做一个应用仍可检查的选择?这个问题既给出它属于哪里的答案,也给出在宿主无法强制结果的地方停手的原因。五步:
- 识别决策边界。新任务路由与有界的下一步焦点都可行;provider 内部动作不可行,因为宿主不拥有那些循环。
- 把选项显式化。宿主需要知道哪些 provider 与模型已安装、已启用、对当前任务有效。选择器接收候选,它不发明候选。
- 保留用户意图。固定了的路由保持不动;运行中的会话不悄悄重路由。无固定的新任务是自动路由选择唯一的窄入口。
- 建好普通路径。本地 Laya 是默认;Jev 是刻意的 opt-in;Normal 模式保留给想要既有路由行为的人。
- 给失败一个形状。格式坏掉的输出、无效 id、过期任务细节、弃权——每种都需要明确的结果:宿主可以拒绝、回退或问用户。没人需要假装每个模型响应都可用。
保守吗?当然。
我宁愿要一个我能调试的窄选择,也不要一个我无法在代码里追踪的惊艳承诺。
图 7:一次新任务从到达到落地,Jev 只出现在第 4–6 步之间。
Jev 在请求路径上的位置
路由路径用大白话说是八步:
- 新任务到达,没有显式固定路由;
- 宿主读取当前 provider 与模型可用性;
- 构造有限的合格候选列表;
- 所选决策模式调用本地 Laya 或托管 Jev(若该路径启用);
- 选择器返回候选 id 或弃权;
- 宿主检查该选择仍合格且仍是当前的;
- 应用启动该路由,或按策略回退;
- 决策记录捕获发生了什么。
固定路由与已有会话绕过自动选择——这不是脚注,是核心产品规则。步骤选择器有自己的一套检查:宿主提供焦点 id 与候选工具集,选择器返回焦点,宿主按当前工具 schema 准备对应工具包,分发时再核对一次命名工具。同一原则,不同生命周期。
04 / 「自改进」应该意味着什么
发布的应用记录决策,但它不训练自己
这是作者最想划清的一条线:keel 记录选择器活动与结果,可以支撑评估,但它不会用聊天历史自动训练 Laya,也不会在一次失败后悄悄改写自己的策略。这个发布不证明科幻意义上的自改进 agent。
它给的是反馈回路的起点:一张回执可以变成一个场景(scenario),一个场景可以在改过的 prompt、策略或模型上重放,人可以审查差异并决定是否保留这次更改。不那么电影化,但我能操作「一条改动的规则、一份旧结果、一份新结果」。「agent 学到了什么」只会让我瞎猜。
把一次失败变成可重跑的案例
假设路由选择器选了一个随后不可用的 provider。不要只说「Jev 晕了」——保存细节:提供的选项与任务状态、选中的 id、宿主的检查结果、用了什么回退。然后构造可重放场景:复现同一候选集与相关任务标志 → 跑基线选择器 → 在同条件下跑候选改动 → 比较有效选择、弃权、回退、延迟与下游结果 → 由人审查改动是否改进了目标行为。工具焦点选择的案例则是另一种:模型选中的工具包在分发前 schema 变了——检查重建工具集时旧工具被过滤,也检查宿主在仍被请求时拒绝它。结构化记录的价值在于给你重建决策的条件。「agent 好像晕了」给你的只是一种情绪。
与普通 harness 对比:功劳归属问题
一个通过的任务不能告诉我们哪个组件值得表扬——也许工人在原路由上也能解出来。保持任务集与评分规则固定;工人、prompt、仓库或审查者的改动都会影响结果。为最终对比单独留一个任务集——不要在所有案例上调优,然后把熟悉的案例当证据。别扭的运行能暴露干净 demo 藏起来的缺失规则。记录失败、回退率、总时间与审查开销。作者坦率声明:这是一个提议的评估设计,本文没有声称实测的编码质量收益。
给生产变更留一道人类闸门
改进循环可以提议改动:候选构造规则、选择器 prompt 或类型化 schema、回退策略、本地模型或托管路由、焦点选择附带的工具包。每次改动:保留旧版本,重放相关场景,检查回归,然后由人批准哪个版本成为新基线。没有静默自训练;没有「它变了因为它学了」却没有 diff、没有回滚路径的情况。
从哪里开始
从宿主能检查的一个选择开始。记住六条:
- Jev 可以在宿主准备的选项中选择,它不拥有宿主;
- Laya 和 Jev 是两个独立的模式,不是同一个模型的两个名字;
- provider 自有的内部循环保持 provider 自有;
- 选择不授予权限;
- 记录支撑未来的评估循环,不等于应用会自我训练;
- 公开的 Jev 工程 repo 含框架与示例,keel 是本文讨论的应用构建。
如果你在构建编码 harness,先写下四件事:
- 它被允许做的一个决策;
- 它能看到的确切选项;
- 它弃权时会发生什么;
- 你如何知道结果是否有帮助。
然后构建证明这些规则的最窄路径。
起始点要足够小,小到坏选择无处可藏。更好的 agent,需要一个能记住哪里出了错的系统。
这就是这种工程让人喜欢的地方:模型可以聪明,而周围的系统仍然可以清晰。
原文来源:X / The Avid Builder's Guidehttps://x.com/Av1dlive/article/2102802621664985241