简介:《Learn Harness Engineering》是一套面向AI开发者的入门教程,聚焦如何通过构建完整的“Harness”环境,让AI代理在真实工程任务中不跳步、不伪造完成,稳定交付可维护代码。教程针对使用编程助手时常见的“写得快却不可靠”问题,适合工程师、研究者和技术负责人自学,也适合团队内部分享。资源包含12节沉浸式讲解和6个实战项目,逐步演示一个Electron桌面应用如何从仅靠提示词,演进为配备指令、状态、验证、范围、会话生命周期五大子系统的完整工程。压缩包共953个文件,大小约2.24MB,其中Markdown文档最多,共466个,其次为TypeScript源码和TSX组件,分别有216个和111个,另含JSON配置文件、Shell脚本、HTML页面等,共同构成文档、代码与运行环境一体的学习包。内含现成的AGENTS.md、feature_list.json、init.sh等模板,可快速接入Claude Code、Codex等多代理工具;配套PDF课程手册与中英双语可视化文档网站,也支持本地启动查阅。已有298人学习这份教程,对想亲手搭建生产级AI代理工作流的开发者是份可操作的参考。内容组织层次分明,既能帮助零基础入门,也可作为日常排查参考。
1. Harness 不是模型,是给 AI 代理上的「工程保险」
如果你直接拿一个大模型去改代码库,前十分钟往往很惊艳,改到第三个小时就开始失控:漏掉测试、把依赖悄悄弄坏、在一个无关文件里留下一段僵尸代码。问题通常不在模型,而在你少了一层东西——harness。它是给 AI 代理套上的一层工程外壳:约束它怎么规划、怎么调工具、怎么改文件、怎么回退、怎么才算完成任务。这篇教程就讲怎么从 0 到 1 构建一个完整的 harness 工程环境,让代理可靠地完成真实工程任务,并给出目录、参数、代码和踩坑记录。适合正在做 AI 编码助手、内部智能体平台,或想把 DeepSeek 这类本地模型接进工程流程的团队。
2. 拆解 harness 的三层结构:模型运行时、工具层与任务循环
2.1 模型运行时先选型:本地模型(deepseek)与远程 API 的边界
无论你用的是 agent harness 还是做垂直场景的智能体,第一层永远是对模型的接入方式。这里说的“运行时”不只是调一个接口,而是把模型重新接成可控的组件:统一的 base_url、统一的参数入口、指定的上下文窗口、以及可预期的输出长度。模型本身是黑匣子,但 harness 要把它变成白盒——你能看到它每次调用了哪个工具、花了多少步、输出是在哪一步开始偏离任务的。
选型上,本地模型(本地部署 DeepSeek 这类开源权重模型)和远程 API 的取舍无非三件事:数据边界、单位成本、上下文能力。远程 API 开箱即用,但日志里可能夹着代码片段、客户数据、内部命名,出境这关大多数企业过不去。本地模型把推理服务放在 127.0.0.1 或内网 GPU 节点上,数据不出域,代价是你得自己管显存、并发、量化精度。工程任务里我一般给本地模型开较低温度,优先保住指令跟随的确定性,而不是让它发挥“创造力”。
另一个容易忽略的参数是上下文窗口。真实工程任务的特点是多文件、长日志、来回修改,动辄几万 token。很多人在小窗口上硬试,结果代理忘记自己改过哪个文件,开始重复修改和互相覆盖。选型时把上下文窗口当作硬指标,低于 32K 的模型不建议接入完整的 harness 工程链路,只适合做单文件小任务。
2.2 工具层:把「手」交给代理之前,先做一张函数注册表
第二层是工具层,也就是代理能调用的一切外部操作:执行 shell 命令、读写文件、git 提交、搜索代码、跑测试。常见做法不是直接给模型一个终端,而是把每个能力包装成一个带 JSON Schema 的“函数”,放进注册表。模型不直接碰编辑器或终端,它只能按 Schema 发起调用请求,由 harness 这边的执行器真正落地。
{ "name": "shell_exec", "description": "在受控工作目录内执行一条 shell 命令并返回 stdout/stderr,超时后强制结束", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "要执行的完整命令,禁止换行注入" }, "timeout": { "type": "integer", "description": "超时时间,单位秒,默认 30", "default": 30 } }, "required": ["command"] } }这段 Schema 的价值在于三个字段:name 决定模型怎么在推理时选工具;description 写得越具体,模型越不会误调用;parameters 里用 required 强制关键参数。我踩过最典型的一次,是某次把 description 写成“执行命令”,结果代理在清理临时文件时直接调它执行了rm -rf一类的高危操作。后来所有危险工具都加了一个safety_level字段,harness 在执行前对高风险调用弹人工确认,比任何提示词都管用。
工具层的第二个设计点是收敛工作目录。代理能访问的路径必须限定在 workspace 内,shell 命令也统一用cd $WORKSPACE && ...包一层,别让它天然拥有整个文件系统的视野。真实工程任务里,代理需要权限边界,而不是无限权限。
2.3 任务循环:规划-执行-观察-再规划的闭环怎么写
第三层是任务循环,决定代理怎么把一个大任务拆成若干小步,并在每一步之后把结果消化进上下文。没有这一层,模型只会一次性吐出一堆代码,缺测试、缺验证、缺修正。harness 的核心约束就是对循环做节拍控制,让代理“做一步、看一步、验证一步”。
def agent_loop(model, tools, task): messages = [{"role": "user", "content": task}] for step in range(max_steps): # 硬性上限,别让代理无限跑 reply = model.chat(messages, tools=tools) if reply.finish_reason == "stop": break for call in reply.tool_calls: result = dispatch_tool(call) # 走工具注册表执行 messages.append(tool_result(call, result)) if acceptance_check(messages): # 每轮结束后做验收检查 break return messages这里的三个关键点是迭代上限、终止条件和验收时机。max_steps 不设的话,代理会在一件事上反复横跳,消耗大量 token 后回到原点;我在工程环境里默认给 30 步,复杂任务调到 60,但从没见过 100 步以上还有价值的执行过程。验收检查不是等模型自己说“做完了”,而是由 harness 去核对测试、编译结果、文件差异才算数。真实效果嘛,可以说,加上这层节拍之后,代理的行为可预测性会有质变,至少不会再假装完成任务。
3. 从 0 到 1 搭最小 harness:目录骨架、模型配置与 skill 部署
3.1 先立工程骨架:harness 项目的标准目录与配置文件
所谓“官方风格”,首先体现在目录结构上。一个成熟的 harness 工程,目录要能一眼看出哪部分是工具、哪部分是技能包、哪部分是运行日志。我常用的骨架如下,它兼顾单机调试和后续团队共享。
harness-project/ ├── config/ │ └── harness.yaml # 主配置:模型、工具、任务策略 ├── skills/ # 技能包,每个子目录一个技能 │ ├── code-review/ │ └── git-commit/ ├── plugins/ # 第三方扩展,入口文件 + 实现脚本 ├── tools/ │ └── registry.json # 函数注册表,按域名拆分维护 ├── workspace/ # 代理唯一可读写的工程目录 └── logs/ # 循环轨迹、工具调用日志、回退事件这个结构的核心关键是 workspace 的隔离性:代理的读写、执行全被锁在这个目录下,技能包和注册表都在它外面。新手常犯的第一个错误是把整个代码仓库根目录直接当作 workspace,代理跑着跑着开始改 harness 自身的配置文件。防止办法是让 workspace 永远指向任务目标目录,harness 本体和它的配置目录属于只读区。
3.2 接入本地模型:以 deepseek 为例的模型配置与关键参数
接入本地模型这件事,很多人以为配一个 base_url 就够了,实际差得远。以 DeepSeek 这类 OpenAI 兼容接口为例,你在配置里至少要显式写明 provider 类型、上下文窗口和限流参数。业界现在常说的“AI 代理助手加本地模型”,本质上就是这里多了一条本地推理通道。
# config/harness.yaml model: provider: local # local 表示不走公网 base_url: http://127.0.0.1:8000/v1 # 本地 vLLM / Ollama 兼容端点 api_key: none # 本地服务通常不校验 key name: deepseek-ai/DeepSeek-R1 temperature: 0.2 # 工程任务用低温度,提高确定性 max_tokens: 4096 # 单次输出上限,防止截断 context_window: 65536 # 低于 32K 的模型不建议接工程链路 task: max_steps: 30 timeout_seconds: 600 snapshot_before_step: true # 每步前自动 git 快照 tools: enabled: [shell_exec, file_read, file_write, git_commit, grep_search] skills: search_paths: ["./skills"] plugin_manager: web_boot: true entry: "./plugins/*/entry.json"几个参数值得单独说。temperature 设 0.2 是工程任务的通用值:代码生成更稳,指令跟随更紧;调到 0.7 以上,模型就会开始“自由发挥”,改出不在计划里的文件。max_tokens 不是越大越好,它会占用上下文预算,工程任务里单次输出 4K 足够,长任务靠多次循环推进,而不是靠一次输出塞满。context_window 要跟推理服务的内存用量匹配,别在配置里写 128K,而模型后端只开得起 32K 的 KV cache。
3.3 部署 skill 技能包:把工程经验变成代理能加载的指令
skill(技能包)是 harness 里可复用的“方法论”,本质是目录形式的指令集加少量辅助脚本。它解决的是同一个问题:团队里一名资深工程师的代码评审流程、提交规范、测试习惯,如何变成代理每次都遵守的规则,而不是靠每次在 prompt 里重新说一遍。下面是一个最小 skill 的骨架。
--- name: code-review description: 对指定目录的改动做一轮代码评审,输出问题和修改建议 version: 1.0 --- 执行步骤: 1. 先运行 git diff --name-only 拿到本次改动文件列表 2. 对每个改动文件,提取 diff 内容并检查异常、硬编码、资源泄漏 3. 将问题按严重度排序写入 workspace/review/review_YYYYMMDD.md 约束: - 只提交问题,不直接改动源码 - 找不到问题的文件,写“通过”即可,不要编造问题这个格式的要点是 frontmatter 里的 name 和 description 是模型检索它的依据,正文是执行规则。技能包的部署方法也很直接:把整个 skill 目录打包,放到目标机器的技能搜索路径下,改一下配置里的 search_paths,然后重启 harness 让技能索引刷新。内网服务器部署多了一层约束——不能在线拉取任何依赖,所以技能里引用的脚本、模板要一并打包进去,路径用相对定位,不写死绝对路径。
我见过很典型的失败案例:技能里写“运行 python3 scripts/x.py”,但 scripts 没打进包,部署到内网后代理每次执行都是“No such file or directory”。正确做法是把技能目录作为一个自包含单元:SKILL.md、py 脚本、模板文件全放在同一个目录下,执行时用dirname $SKILL_PATH定位资源。
3.4 插件机制:harness 与 agent 的扩展点区别
很多人分不清 harness 和 agent 的区别,其实一句话能讲透:agent 是模型加循环加工具的运行体,harness 是承载 agent、约束它并给它提供扩展点的环境。换句话说,harness 可以跑多个 agent,也能给 agent 加插件扩展能力,而 agent 本身不做这些事。插件机制是 harness 最能体现存在感的一层。
# 插件入口约定:每个插件目录下必须有 entry.json # 启用插件时 harness 会校验入口文件存在且可执行 harness plugin install ./plugins/code-formatter harness plugin list harness plugin uninstall code-formatter插件的安装要遵循入口协议。entry.json 的作用是向 harness 声明插件的能力清单和入口路径,这样管理器才知道在代理启动时“插什么、怎么插、按什么顺序插”。一个插件的典型结构是entry.json加实现脚本,entry 里的路径建议相对于插件目录写,不要写成机器上的绝对路径,否则整个插件一挪目录就失效。这几乎是插件无法安装的头号原因:不是插件坏了,是入口路径解析错了。
4. 让「可靠」落地:任务评测、代码回退与权限沙箱
4.1 任务完成度评测:从模型自评到工程验收
可靠性不是靠模型“保证”,而是靠评测闭环兜底。harness 工程里有一个原则:凡是代理声称完成的任务,都必须经过另一组验证器核对。最简单的验证器是“命令在退出码上正不正确”“测试有没有新增失败”“是否产生了非预期的文件改动”。这些都不需要模型参与,纯规则就能判。
def acceptance_check(messages): checks = [ ("测试通过", "pytest --tb=short -q"), ("无未提交临时文件", "git status --porcelain"), ("编译无错误", "python -m compileall workspace/ -q"), ] for name, cmd in checks: code, output = run_in_workspace(cmd) if code != 0: record_failure(name, output) return False return True这里的逻辑是每轮循环之后都跑一遍验收检查,通过才允许代理结束。注意我一个看似简单,实则关键的细节:验证器运行的环境必须是工作目录内部的干净环境,而不是宿主机环境,否则代理可能因为环境里正好预装了依赖而“蒙混过关”。评测结果同时写回日志,下次任务开始时作为上下文的一部分加载,让代理知道自己上一次在哪里翻了车。
4.2 代码回退:什么时候拍快照才不会后悔
代码回退是代理可靠性的第二根支柱,也是一枚“后悔药”。真实工程里,代理改到一半把某个文件的结构打乱了,而你又没法精确说出到底是哪一步改的,这是最常见的失控场景。harness 的解决策略是“逐步快照”,在代理执行每个关键动作之前自动提交一次快照。
# 每步动作前快照,记录当前状态 git add -A && git commit -m "snapshot before step $STEP" --allow-empty # 需要回退时,先看快照列表确定目标 git log --oneline --all | grep "snapshot before step" git reset --hard <snapshot_commit>快照时机是最容易出错的地方。如果你在任务全部结束后再拍快照,那回退等于没有回退,因为坏代码已经写进库里。正确做法是放在循环的入口处:每一次工具调用前,先把现状固化。这样无论代理后面怎么折腾,都能退到任意一步之前。回退操作本身要落日志,记录“从哪个快照回退、谁触发的回退”,这样反复实验时能看出代理是否在同一个坑里反复横跳。比较稳健的团队还会加一层策略:同一任务累计回退超过 5 次,直接中止代理,转人工介入。
4.3 权限与沙箱:别让代理在系统目录里为所欲为
权限沙箱决定阶段,也是很多新手完全没意识到的坑。harness 默认应该以最小权限运行,而不是用管理员或 root。真实工程里,代理需要的是写 workspace、执行构建命令、调用 git,它不需要碰系统目录,更不该有安装全局软件的能力。Linux 下常见做法是建一个专用系统账户跑 harness。
# Linux 下创建专用运行账户 sudo useradd -m -s /bin/bash harness-runner sudo -u harness-runner ./harness start # Windows 下避免以管理员身份运行 # 把 workspace 放在用户目录,不要放 C:\Program FilesWindows 上的权限问题更隐蔽。很多 harness 实现为了读写文件,会对目标目录设置安全描述符,调用 Windows 的SetNamedSecurityInfo接口。这个接口在系统保护目录、某些域策略管控的目录下会直接失败,表现就是代理能读文件但写不进,日志里抛一个含糊的 Win32 错误。破解方向不是绕过它,而是老实把 workspace 放在用户可写目录下,并检查目标目录的 ACL 是否给了当前用户修改权限。
5. Harness 避坑排查手册:5 个真实翻车现场
5.1 插件加载失败:web boot 入口不激活
现象:启动 harness 时日志出现web boot: 1 entry did not activate,插件没进激活列表,代理用不到插件里的任何工具。
原因:大多数情况是 entry.json 里的入口路径写错了。路径写成了相对路径,解析器却是从进程工作目录去找的,一旦工作目录不是插件目录,入口文件就找不到。另一类是插件依赖的 Python 包在环境里缺失,激活时初始化抛异常被静默吞掉。
解决:把 entry 路径改成插件自包含的相对定位,用脚本根据__file__所在目录拼接入口路径,不要依赖进程的工作目录。然后进入插件目录手动跑一遍入口脚本,看有没有缺依赖,把缺的包提前安装好再重启 harness。排查时先看日志里有没有更底层的异常栈,比在界面里反复重装高效。
5.2 skill 读文件报 setnamedsecurityinfo failed (Win32)
现象:skill 加载时读文件失败,日志里有setnamedsecurityinfo failed (win32),代理能正常连接模型,但一碰技能包里的资源就翻车。
原因:这是典型的 Windows ACL 问题。harness 尝试对目标文件设置安全描述符(通常是技能包内的脚本或数据文件),但该文件位于无权限修改 ACLL 的目录里,比如系统盘根目录、受企业策略保护的目录,或者被其他进程锁定的临时目录。
解决:把整个 workspace 和 skills 目录迁到用户目录下,避开受保护区域;不要用管理员身份运行 harness,管理员权限反而会触发另一些安全拦截;检查文件的 ACL 是否给当前用户授予了修改权限。如果你在一个受管企业域环境里,还要确认组策略没有禁止对可执行目录写入。
5.3 内网离线部署:无公网环境下 harness 起不来
现象:把项目和技能包拷到内网服务器后,harness 启动就卡在模型连接阶段,或是插件初始化时去拉取远程元数据,直接超时。
原因:配置里仍然保留着公网模型端点,启动时默认走了一次远端探测;另一个原因是技能包或插件里引用了带版本号的在线依赖文件,离线环境拉不下来。
解决:所有模型请求统一改指向内网推理服务的 base_url,注意这个地址要在离线环境的 DNS 和防火墙里都放通;技能包采用自包含部署,脚本和模板一起打包,不依赖在线资源。“部署 skill 到内网服务器”的标准流程应该是:打包 → 拷贝 → 改路径 → 重启 → 验证加载,四步走完才能算成功。
5.4 换模型后插件全部失效:解耦原则被破坏
现象:同一套 harness 和插件,从远程 API 模型换成本地 DeepSeek 后,插件行为完全走样:工具调用频率下降、不按步骤执行、经常漏参数。
原因:插件里的提示词是针对原模型的能力写的,默认它“什么都会”,而本地模型的指令跟随能力、工具调用格式和上下文利用方式不同。这不是 harness 坏了,是插件对模型能力做了隐含假设,直接把模型换掉,隐含假设全部落空。
解决:插件声明里加最小能力要求字段,例如requires: tool_calling、requires: context_window>=32K;切换模型前先跑一组自检样例,专门测插件最核心的几项工具调用路径。真正到位的做法是让插件与模型解耦——把指令写得足够机械和显式,少用“你应该知道怎么处理”这种模糊表述,这样换模型时影响面能被压到最小。
5.5 代码回退失灵:快照时机选错,后悔药变毒药
现象:代理改坏了多个文件,执行回退后代码反而更乱,甚至丢了代理刚完成的有效修改。
原因:快照不是在动作前拍的,而是在任务中或任务结束后拍的。代理在第五步就改坏了一个文件,到第十步才拍快照,快照里包含的已经是改坏的代码;回退时自然退到了坏状态。另外,回退命令直接reset --hard丢弃了所有后续修改,把有效改动一并清掉了。
解决:把snapshot_before_step: true真正开启,确保每一步工具调用前都有快照;回退时优先用git revert生成一条反向提交而不是硬重置,保全后续有效修改。保留最近 50 个快照,方便比对哪一步开始失控。
6. 进阶:一条命令做 harness 健康检查,把环境变成团队入口
走到这一步,你的 harness 已经不是单机玩具了,而是可以给团队用的工程基础设施。接下来我建议做一件事:写一条harness doctor健康检查命令,把环境状态彻底可视化。它解决的问题是“这个 harness 到底能不能用”,而不是“看起来启动了没有”。一个看起来正常的 harness,模型连接可能早已超时、技能索引没刷新、权限目录被切走,这些隐性问题靠人肉排查太慢,必须脚本化。
#!/bin/bash # harness-doctor:在跑任务前执行,检查 4 项关键健康状态 echo "== 1. 模型连通性 ==" harness model ping --timeout 5 echo "== 2. 工具执行能力 ==" harness tools exec 'echo ok' echo "== 3. 技能包加载状态 ==" harness skill list | grep -c "loaded" echo "== 4. 工作目录可写 ==" test -w "$WORKSPACE" && echo "workspace writable" || echo "workspace read-only"我个人的习惯是:任何新环境交付前,先跑一遍这套检查,再放代理进场;线上环境每次改过配置后也跑一遍,确认模型、工具、技能、权限四个维度同时在线。这个习惯帮我在很多次“日志看着正常,实际不能用”的场景里省下大量时间。Harness 工程做到最后,拼的不是模型能力,而是这套环境的可复用性和可诊断性。希望这些经验和踩过的坑对你有用,愿你的代理永远不失控。
本文还有配套的精品资源,点击获取