显式状态驱动:为Coding Agent构建可靠Harness执行框架

发布时间:2026/10/2 19:18:52

显式状态驱动:为Coding Agent构建可靠Harness执行框架 最近两个月的周末我基本都泡在一件事上让 coding agent 在一批真实仓库里稳定地完成“改需求-跑测试-提PR”这个闭环。试了很多方案后一个在社区里被反复讨论的术语落到了我面前——harness。更确切地说是 Jev 这个项目背后那套“显式状态驱动”的 harness 设计。如果你也玩过 Codex、Claude Code或者看过 DeepSeek Harness、Harness Engineering 的讨论帖你大概能感觉到现在 coding agent 的瓶颈早就不在模型能不能写代码而在我们怎么把模型的能力框进一个可观测、可恢复、不会跑偏的执行框架里。这篇文章不聊 Prompt 魔法只聊 harness 该怎么设计以及我在 Jev 上实操时踩过的坑。1. 先聊清楚harness 到底是什么为什么 Coding Agent 离不开它1.1 一个我踩过的坑模型很强但 agent 还是翻车我先讲一段真实经历。上个月让一个 coding agent 去修仓库里的一个崩溃 bug它定位挺准找到了一处空指针然后“顺便”把相邻的两个函数也重构了。结果当然是测试挂了一片。我回看对话日志才发现它根本不是故意乱改而是上下文里有一处过期的分析说那个函数“可以优化”它信了。这个案例特别典型。模型本身不笨但一个多轮任务跑下来上下文里堆积了大量中间推理、旧版本代码片段、模型自己脑补的“已完成事项”。这些信息全部混在自然语言里没有结构化边界。Agent 干着干着就把“我以为改完了”当成“我确实改完了”。这不是模型能力的问题是执行框架的问题。后来我接触了 Jev 这个方案才意识到问题的解药不是更强的大模型而是把 agent 的每一次决策都建立在“显式状态”之上。模型可以自由发挥但 harness 必须保证它每一步看到的、做的、记录的都是程序化的、可校验的、可回滚的。1.2 harness 的定位给 agent 搭舞台而不是给模型堆提示词很多人第一次听 harness 这个词会以为是个插件框架或者工具链。我的理解更朴素harness 是夹在模型与应用之间的执行外壳负责三件事——决策前给材料行动时划边界行动后记事实。光给模型一个 API、一堆工具那只是一台没有方向盘的跑车。Harness 才是那个驾驶舱它决定模型能看到什么上下文、能调用哪些工具、每次调用要满足什么前置条件、结果怎么被记录、状态怎么流转。换句话说ReAct 循环只是“模型-行动-观察”的抽象框架harness 才是这个循环的工程实现。你会问这和给模型写一个 System Prompt 有什么区别区别很大。Prompt 是建议harness 是约束。Prompt 说“你应该先分析再动手”模型可以违反harness 如果规定“只有 state.step ‘analysis_done’ 时才能调用 write_file”模型想违规也调不动。这就像交通规则和减速带的区别后者才能真正改变行为结果。1.3 Jev 是什么模型、harness还是两者都有社区里聊 Jev 的时候大家指的东西其实不太一样有人说 Jev 是一个可本地部署的模型有人说 Jev 是个聊天助手项目还有人说 Jev 是一套 harness 工程模板。我在实际用过之后倾向于一个综合判断——Jev 更像一个“模型 显式状态驱动 harness”的组合方案模型负责决策harness 负责把决策约束在真实世界可执行、可验证的状态流转里。你的模型可以换成任意一个开源基座也可以用 Jev 配套的模型服务。真正让 Jev 区别于普通 “CLI agent wrapper” 的是它把状态放到了第一公民的位置任务、步骤、工具调用记录、决策结果全部结构化为状态数据每次迭代都从状态重新开始而不是让模型在上下文里靠记忆硬撑。我后面聊的所有设计都围绕这个核心。记住一句话Jev 的思路不是“让模型更聪明”而是“让 harness 把状态托底托住”。2. 显式状态驱动把“模型脑内状态”搬到“harness 数据里”2.1 隐式状态为什么不可靠先说说传统 agent 是怎么干活的。典型的 ReAct 循环是把 System Prompt、用户任务、历史行动、观察结果全部拼成一段文本塞给模型模型输出下一步动作然后执行观察结果又追加进上下文。状态完全隐式地存在于上下文文本里。我在实战里发现这个模式有四个致命伤遗忘与幻觉。上下文一长模型对“已经完成的事”会产生记忆偏差经常出现重复调用同一个工具、或者声称改了两个文件其实只改了一个的情况。不可恢复。一旦进程中断、网络抖动、上下文截断整个任务就得从头再来。没有哪个状态文件能让你从断点接着跑。不可审计。你想知道 agent 为什么做出某个决定只能翻对话记录而且对话记录里混着推理、猜测、错误信息很难定位哪一句是“事实”。不可控。你没法在模型行动前做强校验。它可以直接输出一个“删除整个目录”的 shell 命令而没有任何机制拦截。隐式状态的本质是状态只存在于模型的脑子里而模型的脑子是不可靠的。2.2 显式状态驱动的四个核心设计原则Jev 这套方案核心可以拆成四个原则我在后面落地时几乎每个细节都在围绕它们打转状态外置所有关键信息——当前目标、任务列表、每个步骤的状态、已执行工具的结果——都存储在 harness 维护的数据结构里而不是模型上下文里。模型上下文里的内容只是状态的“投影”随时可以按需重新渲染。状态可见harness 的状态可以被外部工具读取。我想知道“agent 现在进行到哪一步了”直接查状态文件就行我想看它刚才调了什么命令事件日志里有完整记录。不需要去解析模型输出。状态可控每一次状态转换都经过 harness 的校验。模型不能直接把task.status改成“已完成”它只能发起一个动作harness 执行后确认结果才更新状态。状态不是模型说什么就是什么而是执行器验证过什么才是什么。状态可恢复因为状态是持久化的数据任务中断后可以恢复。从最近一个一致的快照重新加载让模型只处理后续步骤不需要重头再来。这四个原则互相咬合。外置是基础可见让状态可观测可控让状态可信可恢复让状态可维护。2.3 Jev 的状态模型长什么样Jev 的状态模型并不复杂我用一个 YAML 结构来描述核心部分project: id: proj-001 goal: 修复 api-server 在空响应时的崩溃 created_at: 2025-06-10T10:00:00Z plan: - id: step-1 name: 复现问题 status: done depends_on: [] - id: step-2 name: 定位根因 status: done depends_on: [step-1] - id: step-3 name: 实现修复 status: running depends_on: [step-2] - id: step-4 name: 补充回归测试 status: pending depends_on: [step-3] current_step: step-3 decisions: - step_id: step-2 model_choice: 采用可选链替代 if 判断避免多层嵌套 confidence: 0.8 rejected: [在入口处统一 try/catch, 返回默认空对象] tool_calls: - step_id: step-3 tool: read_file target: src/handlers/api.ts result_fingerprint: sha256:9f2... success: true - step_id: step-3 tool: write_file target: src/handlers/api.ts result_fingerprint: sha256:71a... success: true artifacts: - step_id: step-3 path: src/handlers/api.ts note: 已修复空响应时的崩溃路径这个结构里有几个细节值得注意status字段的变更不由模型直接写而是由 harness 在工具执行成功、校验通过后更新。decisions记录模型做过的重要选择以及被否决的方案供后续审计和回滚时参考。result_fingerprint是执行结果的哈希指纹用它可以判断同一文件是否被重复改动、同一命令是否重复执行。状态文件不是摆设它是 harness 的唯一事实源。2.4 一次状态驱动的决策循环长什么样有了状态模型整个 agent 循环就变成了一个非常清晰的“读状态-渲染-决策-执行-写状态”闭环。我在 Jev 里实际跑的循环大致是这样def agent_step(state, model, executor): # 1. 从状态存储加载当前状态 state state_store.load(state_id) # 2. 把状态渲染成模型需要的上下文 rendered render_state(state, max_tokens6000) prompt build_prompt(rendered, task_spec) # 3. 模型输出结构化动作 action model.decide(prompt) # 返回 JSON: {tool: ..., args: {...}} # 4. harness 校验动作合法性 validator.validate(action, permissionsstate.permissions) # 5. 执行工具拿到结果 observation executor.run(action.tool, action.args) # 6. 校验结果并更新状态 if validator.check_result(action, observation): state apply_transition(state, action, observation) state_store.commit(state) # 持久化 return state, observation else: state record_failure(state, action, observation) state_store.commit(state) return state, observation这个循环的关键是第 2 步的“渲染”和第 4 步的“校验”。模型看到的是经过压缩、结构清晰的状态投影模型做出的动作必须通过 harness 的规则才能执行。这两步正是显式状态驱动和普通对话式 agent 的分水岭。3. 实操落地从零搭一个 Jev 风格的显式状态 harness3.1 最小环境准备光讲理念不够我把实际搭环境的步骤整理出来。先说前提Python 3.10 以上能跑 Docker 更好后面讲沙箱会用到。Jev 的 harness 本体是一个命令行工具支持裸跑和桌面模式。官网和 GitHub 上都有安装包安装失败的大头一般是依赖版本问题建议在干净的虚拟环境里装。配置层面你需要准备一个模型服务端点。Jev 支持多种 OpenAI 兼容接口也支持本地部署。如果你走本地部署路线要点是模型服务和 harness 进程分开harness 只通过 API 访问模型这样你的状态管线和推理负载互不干扰。环境变量建议这样设置用占位符代替具体服务商信息export JEV_BASE_URLhttp://localhost:8000/v1 export JEV_API_KEYyour_local_key_here export JEV_WORKSPACE/workspace/my-repo对于刚上手的朋友我的建议是先用一个非常小的仓库、非常明确的任务跑通全流程再上复杂项目。不要一上来就套整仓级的重构任务否则你会分不清是模型不行还是 harness 配置不对。3.2 第一步定义状态结构State SchemaJev 的编排入口其实很朴素一个 YAML 文件描述任务和计划一个 JSON 文件维护实时状态。初始的task.yaml可以长这样task_id: fix-empty-response-crash goal: 修复 api-server 在空响应时的崩溃 workspace: /workspace/my-repo plan: - {name: 复现问题, tool_hint: run_tests} - {name: 定位根因, tool_hint: read_file} - {name: 实现修复, tool_hint: write_file} - {name: 补充回归测试, tool_hint: write_file} permissions: read: [/workspace/my-repo/**] write: [/workspace/my-repo/src/**, /workspace/my-repo/tests/**] shell: allowed_prefixes: [pytest, git diff, git status, cat] denied: [rm -rf, git push, sudo]重点说下permissions。我刚学 harness 时忽略了权限配置的威力后来发现这是控制 agent 行为最硬的手段。你在 YAML 里写清楚哪些目录可写、哪些 shell 命令前缀允许harness 会在模型每次发起动作之前做一次白名单校验。比任何 Prompt 里的“请谨慎操作”都管用。3.3 第二步把状态渲染进 Prompt状态文件再完善如果不会渲染模型根本读不进去。Jev 的做法不是全量塞入而是按需投影。我实际用的渲染策略是这样的只渲染current_step及其依赖步骤不渲染整个 plan。最近 10 条工具调用记录保留更早的只保留统计摘要比如“已经修改了 3 个文件全部读取成功”。决策记录只展示与当前步骤相关的条目不把历史所有考虑全倾倒给模型。渲染出来大概是这个效果## 当前状态 当前步骤: step-3 (实现修复) 依赖完成: step-1 完成, step-2 完成 目标: 修复 src/handlers/api.ts 中的空响应崩溃 ## 环境 可写目录: /workspace/my-repo/src/**, /workspace/my-repo/tests/** 读写统计: 已读 5 个文件, 已改 0 个文件 ## 最近工具调用 [10:02:11] read_file src/handlers/api.ts ok (sha256:9f2...) [10:02:15] grep getUser src/utils/user.ts ok (4 matches) ## 约束提醒 禁止执行: rm -rf, git push, sudo这个投影极大降低了模型决策的认知压力它不需要在几十万 token 的上下文里翻找“我刚才改到哪了”而是直接看结构化摘要。状态渲染的本质是给模型一份“实时更新的作战地图”而不是一整摞会议纪要。3.4 第三步工具执行器的约束与校验工具执行器是 harness 的“手脚”。Jev 在这一层做了几件关键事。第一命令白名单与危险操作拦截。模型输出的 shell 命令必须先通过前缀匹配和正则匹配命中denied列表直接拒绝。执行环境建议用容器或受限用户磁盘目录只挂载 workspace 的可写区域。这一步能防住 90% 的“模型手滑”。第二结果指纹记录。每次工具执行完都会对结果计算哈希。比如write_file后记录文件新内容的 sha256bash执行后记录 stdout 和退出码的摘要。这个指纹是状态更新的依据也是后面判断幂等性的关键。第三超时与重试策略。长时间运行的命令必须设置超时默认 120 秒。超时后强制终止并记录失败状态然后让模型选择“重试”“换个方案”或者“标记阻塞”。注意重试要有次数上限我一般设 3 次。这里有个很重要的设计取舍Jev 没有把工具调用逻辑全交给模型而是把“工具调用”当成 harness 暴露给模型的有限 API。模型可以选工具、传参数但不能自己发明工具。这保证了可观测性——每次调用都有记录可审计性——每个动作都有归宿安全性——规则可以强制执行。3.5 第四步与 Coding AgentCodex/自定义 CLI集成很多朋友一开始是把 Jev 和 Codex 之类的 CLI coding agent 放在一起用的。我自己的集成方式有两种看场景选Jev 作为外部 harness标准 CLI agent 作为前端。这种情况下Jev 负责维护状态机、权限和工具执行CLI agent 被限制成“只能通过 Jev 暴露的接口干活”。好处是状态管理完全可控坏处是少了一些 CLI 内置的交互体验。Jev 作为后端服务供自定义 agent loop 调用。我更喜欢这种。Jev 起一个本地服务暴露/state、/decide、/execute这些接口我自己写 agent 循环来控制节奏。模型随时可以从服务拉状态决策后把结果送回状态存储。优势是灵活想接什么前端都可以。热词里“jev 在 codex 中使用”指的大概就是第一种模式。不管哪种模式关键要点是一样的不让任何编码 agent 绕开 harness 的状态直接访问工作区。一旦绕过显式状态驱动就失效了又回到了模型脑内记忆那套老路。4. 问题排查与避坑实录4.1 插件加载失败“web boot: entries did not activate”这个报错我在 Jev 的插件体系上遇到过好几次社区里也到处是类似问题。报错经典形态是harness failed to load plugins: web boot: 1 entry did not activate或者多条 entry 未激活。第一次看到我以为是环境坏了排查后才发现问题几乎都出在插件声明和运行时条件不匹配上。几个典型原因插件入口函数没有被正确导出。Jev 要求插件在plugin.py里实现约定好的register(hooks)函数。少写一个下划线、或者用了registration这种自定义名字加载器就找不到入口。激活条件依赖环境变量或服务。某些插件只有在特定目录结构或环境变量存在时才激活。比如 RPA 落地插件要求有rpa_server地址你没配插件就处于未激活状态。依赖缺失。plugins 目录里的requirements.txt没安装全加载时抛异常被吞掉表现为“未激活”。排查顺序我总结成一套固定流程先开--debug看插件加载日志确认是“找不到入口”还是“依赖导入失败”然后把插件一个一个临时禁用二分定位到具体插件最后检查插件文档里的激活前置条件。八成以上问题在第二步就能定位。千万不要急着重装整个 harness先看日志。4.2 状态文件损坏与并发写入我在跑多 agent 并行任务时遇到过状态文件被并发写坏的情况。Jev 默认把状态存在 JSON 里两个进程同时提交状态更新后写的覆盖前写的任务直接乱套。解决方案分三层第一层状态写入使用原子写也就是先写临时文件再rename避免写了一半的文件被读取。第二层状态变更用事件日志 快照模式。所有状态变更追加到events.log定时生成全量快照。恢复时重放事件即可。第三层如果并发要求高状态的存储后端换成 SQLite 或者 Redis。我目前本地项目用 SQLite稳定性明显高于裸 JSON 文件。这里有个容易忽略的点状态的一致性不仅要看“文件没损坏”还要看“状态是否符合业务逻辑”。比如步骤标记done了但它的产出文件不存在那状态就是不一致的。我建议每次恢复后跑一个validate_state()把这种逻辑上的不一致也暴露出来。4.3 上下文被塞爆状态渲染过度状态渲染听起来简单但稍不注意就会过度。我把所有历史工具调用、所有决策记录都投影进上下文之后模型的 token 消耗暴增而且决策质量变差——信息太多模型开始“抓不住重点”甚至被旧决策带偏。后来我给自己定了几条硬规则单轮投影的上下文预算不超过 6000 token超出部分必须摘要化。历史工具调用最多保留最近 10 条更多信息通过“查询工具”按需获取。也就是模型想知道早年某次改动可以调用read_event_log(step_id...)而不是凭空渲染。摘要要由 harness 生成不是让模型自己总结。比如“已完成 12 次工具调用其中 2 次失败”这种统计摘要用代码算出来才可信。这条教训的核心是显式状态驱动的价值在于让状态可被按需访问而不是把所有状态都硬塞给模型。状态库是你的数据库上下文只是缓存缓存不能用数据库来当。4.4 工具调用不幂等引发的状态错乱这个坑非常隐蔽。我的 agent 有一次在恢复任务后重复执行了write_file把已经修复的文件又“恢复”成了旧版——因为旧版的读取结果还残留在恢复时的上下文里。模型以为自己在写新代码其实是在覆盖自己的上一步产出。解决思路是给工具调用引入幂等控制write_file在执行前先读取目标文件当前指纹只有目标内容与预期基线一致时才写入。shell 命令执行前记录命令指纹发现同一指纹已经成功执行过且输出未变就跳过。每次工具结果都缓存指纹恢复任务时先重放指纹缓存而不是重新执行。我这里强烈建议所有有副作用的工具都必须支持“检查后执行”模式。类似数据库里的 compare-and-swap避免因为恢复、重试导致的重复副作用。做不到这个你的 harness 状态再完整也会被“重跑”打穿。4.5 任务中断恢复机制最后聊恢复。Jev 的恢复流程其实不复杂但细节容易出错。我的推荐流程是从最后一个一致性快照加载状态。校验工作区文件指纹与状态中的artifacts.fingerprint是否一致不一致说明工作区被外部改了需要先 reconcile。把running和pending的步骤重新排队done的步骤不重复执行。重建模型上下文只渲染当前步骤及相关依赖不让模型感知到“这是一次恢复”以免它改变决策风格。继续走正常的决策循环。这个流程的关键在于第 2 步。如果不校验工作区一致性恢复后的状态和真实文件系统就可能已经分叉agent 后面的决策全部建立在虚假前提上。恢复不是“重新开始”而是“在验证过的现实上继续”。5. 一些更底层的体会我实际跑完 Jev 这套显式状态驱动的 harness 设计之后最大感受是模型可以疯但状态必须稳。你可以让模型天马行空地提方案但只要它的每一步动作都需要通过状态校验、每一步结果都要落成可查询的数据它的“疯”就被限制在了一个安全边界里。这比任何花哨的 Prompt 技巧都让人安心。还有一个体会是harness 工程不是一次性搭完就结束的它需要和你的真实工作流互相磨合。我开始只会跑一个 YAML 定义的最小闭环后来一步步加了权限细化、指纹幂等、按需渲染、事件回放每次改动都让 agent 的行为更可预测。这中间的乐趣有点像把一个毛手毛脚的新人实习生慢慢带成一个“动手前先查状态、动手后必留痕”的可靠协作者。最后再分享一个小技巧刚开始用 Jev 的时候建议你开着状态文件实时查看跑任务。盯着current_step怎么变、decisions怎么记录、工具调用的指纹怎么更新你会比用任何黑盒 CLI 都更理解 agent 在想什么。这个“看得见状态”的感觉是显式状态驱动最珍贵的体验也是我建议每个玩 coding agent 的人都亲自试一次的原因。
延伸阅读

更多相关文章

2026/10/2 19:18:52

视觉导视系统:单目摄像头实现±15cm精度实时定位

简介:本资源是一份系统详实的视觉导视系统专业文档,面向环境设计、视觉传达、建筑景观及文旅项目策划从业者与高校相关专业学生,解决导视系统从理论认知到文化落地的设计实践难题。文档深入解析导视系统的定义、构成逻辑(主导示体…

2026/10/2 19:18:52

C++类模板实战:从泛型编程到动态数组实现

如果你是做 C 开发的,迟早要面对这样一个问题:自己的代码能不能在类型层面也做到复用。比如同一个栈,既要装int,又要装std::string,还得能装自定义结构体,难道每次都要复制粘贴改一遍?这就是tem…

2026/10/2 20:28:56

AI新闻日报_2026-07-07:用TaoToken统一Key追踪Agent与AI Coding动态

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 20:28:56

从HuggingFace到OpenAI兼容API:推理引擎选型与部署指南

作为一名常年在一线折腾大模型部署的工程师,我平时被问得最多的一个问题就是:“模型从 HuggingFace 上下下来了,怎么让公司内部的其他系统调用它?” 传统的做法是写一个 Python 后端,封装一下 HTTP 接口,但…

2026/10/2 20:28:56

Cesium倾斜摄影光照与阴影动态日照配置指南

上周帮朋友排一个三维场景的问题,倾斜摄影模型加载得很顺,飞到目标区域一看,整片建筑群平得像一张贴纸:太阳悬在天上,楼体外立面没有明暗过渡,地面也找不到一点影子。他第一反应是"数据做得不好"…

2026/10/2 20:23:56

道路机器人路面标志识别:VOC格式数据集制作与YOLO训练实战

简介:这套道路机器人交通标志识别数据集采用VOC标注格式,面向自动驾驶、机器人导航及计算机视觉学习者,覆盖交通灯、马路、左右转、黄线、人行道、机器人等路面导航标志,可用于目标检测、实例分割或语义分类等任务的训练与评估。包…

2026/10/2 8:16:46

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/10/2 18:20:53

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/1 10:48:55

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/2 0:02:57

PWN入门:从栈溢出原理到ROP链实战

1. 这不是“学PWN”,是重新理解你每天敲的每一行C代码我第一次在CTF赛场上写出能控制程序流的exp时,手抖得连gdb的c命令都输错三次。那道题只有23行C代码,一个gets()调用,一个printf(),一个return——它甚至没开NX&…

2026/10/2 0:02:57

Windows下cudaMallocHost显存占用之谜:WDDM与TCC模式差异及优化方案

1. 一个反直觉的显存占用现象第一次在 Windows 上看到cudaMallocHost把显存吃掉的时候,我的反应是打开任务管理器反复确认了三遍。明明调用的是主机端锁页内存分配,按 CUDA 文档的说法,这块内存应该落在系统 RAM 里,跟 GPU 的显存…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑