1. Agent 反复选错工具、跳过验收,为什么改提示词补不回环境里缺失的约束
Agent 选错工具、跳过验收,是很多人在做自动化任务时最先撞上的墙。你可能会发现,同一个模型放进聊天窗口里回答问题挺正常,一旦放进仓库里让它改代码、跑测试、开 PR,它就开始乱来:该用grep的时候去读整个文件,该跑测试的时候直接宣布完成,该停下来问人的时候自己往下冲。这时候大多数人的第一反应是改提示词,加一句“请务必先运行测试”,再加一句“不要修改无关文件”,然后换一个更大的模型,再把上下文窗口开大一档。结果呢?它还是会忘掉上周已经拍板的决策,还是会拿错工具,还是会跳过验收,还是会在同一条死路上转圈。
我试过把同一套权重分别放进两个环境里跑。一个是纯聊天窗,只给任务描述;另一个是带终端、测试命令、项目记忆和隔离工作区的仓库。权重完全没变,但后者的表现明显更稳。差别不在模型聪不聪明,而在于模型周围那层环境有没有把“看见什么、碰到什么、留下什么、怎样才算过关、何时必须停”写成可执行的条件。提示词改进的是指令,Harness 改进的是指令被执行的现场。模型只是推理引擎,它能建议下一步,但不能凭空长出可靠的作业现场。
这里要引入一个核心概念:Harness 契约。你可以把它理解成给 Agent 准备的一份“作业现场说明书”,但它不是写给模型看的散文,而是写成代码能读、能检查、能强制执行的结构化对象。契约里要钉死几件事:目标是什么、非目标是什么、允许改哪些路径、禁止碰哪些系统、验收信号是什么、预算有多少。没有契约,Agent 可以做完另一份工作,仍然自称成功。契约保护的是任务定义,不是文采。
为什么改提示词补不回环境里缺失的约束?因为提示词是软约束,模型可以“理解”也可以“忽略”,尤其在上下文被截断之后,决策会从对话里蒸发。而 Harness 契约是硬约束,它写在模型外面,由 harness 负责授权和检查。模型可以建议动作,但 harness 决定这个动作能不能执行。昂贵、不可逆、会碰到别人的操作,尤其不能让同一个概率系统同时发明计划、批准风险、执行副作用。
还有一个常见误区:把项目知识全部塞进一个大文件里。OpenAI 用 Codex 做 Agent-first 代码库时也踩过“一份巨型 AGENTS.md”的坑,后来改成短地图加仓库内分册。大手册消耗上下文,小地图保存上下文。Agent 需要知道去哪找项目知识,不需要每次把所有文档灌进窗口。根目录留一份短指南当目录,细则贴在它治理的代码、工具或流程旁边,任务真正用到再加载。
工具也不是按钮清单,而是模型和世界之间的接口。每项工具都要有明确用途、可预测输出、显式失败态和权限边界。好工具在模型开始胡乱推理之前就降低歧义;坏工具逼它猜测刚才到底发生了什么。如果工具失败时只返回一段模糊字符串,模型就会开始猜,猜着猜着就选错了下一个工具。
对话也不是系统的账本。决策、产物、失败、未关闭风险要写到上下文窗口外面。下一次会话继承的是工作状态,不是一段失真的转述。这是 Agent 熬过窗口重置、进程崩溃和换人交接的方式。Anthropic 做长程编码时,靠结构化产物在会话之间交接,另设评估者给反馈,而不是让生成者给自己打分。
没有传感器就不要加自治。测试、linter、截图、日志、指标、schema 校验,把“感觉还行”变成证据。模型制造产物,环境产生关于产物的证据,harness 决定证据够不够继续。每次运行都要留下可读轨迹。没有轨迹,失败是谜;有轨迹,失败是下一次改 harness 的输入。重要规则应当写两遍:一遍给模型看懂理由,一遍写成它绕不开的机械检查。指南解释原因,检查守住边界。下一次 Agent 不必记住那次事故,harness 替它记住。
所以,当你发现 Agent 反复选错工具并跳过验收时,先别急着改提示词。先问自己:环境里有没有一份可执行的契约?工具接口有没有显式失败态?验收信号有没有写成机械检查?权限有没有放在模型外面?如果这些都没有,那缺的不是更漂亮的形容词,是现场。接下来我会用 TaoToken 统一 Key 通道,把 Harness 契约写进上下文,并给出可复制的配置片段和验证步骤。
2. TaoToken 前置:统一 Key 通道与 Harness 契约的接入准备
在动手写契约之前,先把 Key 通道统一起来。很多人做 Agent 实验时,Key 散落在各个脚本、环境变量和配置文件里,换一个模型就要改一遍,排查问题时根本不知道当前用的是哪个通道。TaoToken 在这里的作用是提供一个统一的 API 入口,让你把模型调用收敛到一处,这样 harness 里的授权、预算和轨迹记录才有统一的落点。
TaoToken 的官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。注意 API 地址后面不加 UTM 参数,保持干净。你需要先拿到一个 Key,然后把它写进环境变量,而不是硬编码在代码里。这一步看起来简单,但它是后面所有契约检查的基础:如果 Key 通道不统一,harness 就无法在授权环节判断这次调用属于哪个任务、哪个预算。
具体操作上,你可以先访问 API Keys 页面创建一个 Key,然后把它导出到 shell 环境里。我习惯用TAOTOKEN_API_KEY这个变量名,因为大多数 OpenAI 兼容的客户端都认这个前缀。如果你用的是 Claude Code 或者类似的编码 Agent,可以在它的配置里指定 Base URL 和 Key。这里要强调三件套:Base URL、Key、Model ID,缺一不可。Base URL 填https://taotoken.net/api,Key 填你创建的那串字符,Model ID 填你实际要用的模型名称。
对于长期编码和 Agent 任务,可以考虑 Coding Plan,它更适合需要持续调用、多轮修复的场景。如果你只是想先验证模型能不能按契约选工具,可以先用模型对话页面做一次快速测试。接入文档里有详细的参数说明,遇到报错可以先对照文档排查。
把 Key 通道准备好之后,下一步是设计 Harness 契约。契约不是写在提示词里的自然语言,而是一个结构化对象。你可以用 Python 的 dataclass 来定义它,也可以用 JSON 或 TOML 来存。关键是这个对象要被 harness 读取,并且在每次模型提议动作之后,由 harness 来检查动作是否在契约允许的范围内。契约里至少要包含:目标、非目标、允许改的路径、禁止碰的系统、验收信号、预算。预算可以是允许的修复轮次,也可以是 token 上限。
这里有一个容易忽略的点:契约要写在上下文里,但不是以“请遵守以下规则”的形式塞进去,而是以模型能读到、harness 能检查的双重形式存在。模型读到的是理由和边界,harness 读到的是机械检查条件。比如“不要修改config/prod.yaml”这句话,模型看到的是原因说明,harness 看到的是路径匹配规则。两者必须一致,否则模型会以为可以绕过去。
在接入 TaoToken 之后,你可以把契约的加载和 Key 的读取放在同一个初始化流程里。这样每次 Agent 启动时,它拿到的不只是一个 Key,还有一份明确的作业现场说明书。接下来我会给出可复制的配置片段,包括 JSON 格式的契约和 Python 的加载代码,你可以直接拿去改。
3. 可复制配置:把 Harness 契约写成 JSON 与 Python 加载片段
这一节给出可以直接复制的配置片段。先看契约的 JSON 表示。我把它放在项目根目录的harness/contract.json里,这样 harness 启动时可以从固定路径加载。字段设计上,goal是任务目标,out_of_scope是非目标列表,allowed_paths是允许修改的路径前缀,forbidden_paths是禁止触碰的路径,evidence是验收信号列表,budget是允许的修复轮次。
{ "goal": "修复用户登录接口在 token 过期时返回 500 的问题", "out_of_scope": [ "不重构认证模块的整体架构", "不修改数据库 schema", "不调整前端登录页面样式" ], "allowed_paths": [ "src/auth/", "tests/auth/" ], "forbidden_paths": [ "config/prod.yaml", "migrations/", ".env" ], "evidence": [ "pytest tests/auth/test_token_expiry.py -q", "ruff check src/auth/", "curl -s -o /dev/null -w '%{http_code}' http://localhost:8000/api/login" ], "budget": 3 }这个 JSON 里的evidence字段很关键。它不是给模型看的建议,而是 harness 要实际执行的命令。每次模型完成一轮修改,harness 就按顺序跑这些命令,收集输出,判断是否通过。budget设为 3 表示最多允许三轮修复,超过就升级给人工。
接下来是 Python 加载片段。你可以把它放在harness/loader.py里,用 dataclass 来承载契约,并加上路径检查函数。
import json import os from dataclasses import dataclass, field from pathlib import Path @dataclass class Contract: goal: str out_of_scope: list[str] allowed_paths: list[str] forbidden_paths: list[str] evidence: list[str] budget: int @classmethod def load(cls, path: str = "harness/contract.json") -> "Contract": raw = json.loads(Path(path).read_text(encoding="utf-8")) return cls( goal=raw["goal"], out_of_scope=raw["out_of_scope"], allowed_paths=raw["allowed_paths"], forbidden_paths=raw["forbidden_paths"], evidence=raw["evidence"], budget=raw["budget"], ) def path_allowed(self, target: str) -> bool: target = target.replace("\\", "/") for forbidden in self.forbidden_paths: if target.startswith(forbidden): return False return any(target.startswith(prefix) for prefix in self.allowed_paths) def to_context(self) -> str: lines = [ f"任务目标:{self.goal}", "非目标(不要做):" + ";".join(self.out_of_scope), "允许修改的路径:" + "、".join(self.allowed_paths), "禁止触碰的路径:" + "、".join(self.forbidden_paths), "验收信号:" + ";".join(self.evidence), f"修复预算:{self.budget} 轮", ] return "\n".join(lines)to_context()方法生成的是给模型看的自然语言版本,而path_allowed()是给 harness 用的机械检查。两者来自同一份 JSON,保证语义一致。加载契约之后,你可以在初始化模型客户端时把 Key 和 Base URL 一起配好。
import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url="https://taotoken.net/api", ) contract = Contract.load("harness/contract.json") system_prompt = ( "你是一个在受控环境中工作的编码 Agent。" "你只能提议动作,是否执行由 harness 决定。" "以下是本次任务的契约:\n" + contract.to_context() )如果你用的是 Claude Code 或 Cline 这类工具,配置方式类似,核心是把 Base URL 指向https://taotoken.net/api,Key 用环境变量注入,Model ID 填你实际使用的模型。Cline 的 MCP 配置里也可以把契约文件路径作为资源传入。Codex 的auth.json里同样需要 Base URL、Key、Model ID 三件套,缺一个都会导致 401 或连接失败。
配置完成后,不要急着跑完整任务。先做一次最小验证:让 Agent 提议一个动作,看 harness 能不能正确拒绝越界路径。比如你可以在测试里让它尝试修改config/prod.yaml,观察path_allowed()是否返回 False。这一步能提前暴露契约字段写错的问题。
4. 验证请求与成功结果:检查 Agent 是否按契约选工具、是否执行验收
配置写好之后,下一步是验证。验证分两个层面:一是 Agent 是否按契约选工具,二是 Agent 是否执行验收。很多人只检查最终答案,结果 Agent 跳过测试直接宣布完成,你还以为它真的做完了。所以验证动作必须独立于 Agent 的自我报告。
先看工具选择的验证。你可以在 harness 里加一个动作记录器,每次模型提议动作时,把动作类型、目标路径、参数写进轨迹文件。然后写一个检查函数,判断动作是否在契约允许范围内。
import json from datetime import datetime def record_action(action: dict, path: str = "harness/trace.jsonl"): entry = { "ts": datetime.utcnow().isoformat(), "action": action, } with open(path, "a", encoding="utf-8") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") def check_action(action: dict, contract: Contract) -> tuple[bool, str]: if action["type"] == "edit_file": target = action["path"] if not contract.path_allowed(target): return False, f"路径越界:{target}" if action["type"] == "run_command": cmd = action["command"] for forbidden in contract.forbidden_paths: if forbidden in cmd: return False, f"命令触碰禁区:{forbidden}" return True, "ok"跑一次任务后,打开harness/trace.jsonl,逐行检查动作。如果发现 Agent 尝试编辑migrations/下的文件,而契约里forbidden_paths包含migrations/,那说明契约生效了,harness 应该拒绝并记录。如果 Agent 反复尝试同一个越界动作,说明契约里的理由写得不够清楚,或者模型没有读到to_context()的内容。这时候要检查 system prompt 是否真的把契约文本传进去了。
再看验收执行的验证。验收信号是evidence里的命令,harness 必须在每轮修改后实际执行它们,而不是让 Agent 自己说“我跑过了”。你可以写一个collect_evidence函数,用subprocess跑命令并收集返回码和输出。
import subprocess def collect_evidence(contract: Contract) -> dict: results = {} for cmd in contract.evidence: proc = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=120 ) results[cmd] = { "returncode": proc.returncode, "stdout": proc.stdout[-2000:], "stderr": proc.stderr[-2000:], } return results def evidence_ok(results: dict) -> bool: return all(r["returncode"] == 0 for r in results.values())成功的结果是这样的:Agent 提议修改src/auth/token.py,harness 检查路径通过,执行修改,然后跑pytest tests/auth/test_token_expiry.py -q,返回码为 0,再跑ruff check src/auth/,返回码为 0,最后用curl检查登录接口返回 200。所有证据通过后,harness 生成一张收据,记录用了哪些工具、看到了哪些证据、跳过了哪些检查、花了多少预算。
如果某条证据失败,harness 把失败输出作为evidence.gap传回给模型,让模型针对这个缺口做下一轮修复。注意,循环属于 harness,不属于“一直试到通”。每轮重试都要有证据、有限次重试、预算和升级路径。模型决定怎么补眼前的洞,harness 决定还准不准再试一次。
你还可以加一个检查:Agent 是否在预算用尽时正确升级。把budget设为 1,然后故意让测试失败,观察 harness 是否在第二轮之前停止并输出升级信息。如果它继续无限重试,说明预算检查没有接进循环。
验证通过后,你会得到一份可读轨迹和一张短收据。这份收据是后续对比模型升级、归因回归问题的依据。模型升级才可比,回归才可归因,审计才可能,最终答案才藏不住一条坏掉的过程。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth 报错对照
接入过程中最容易撞上的几类报错,这里逐一对照。第一类是 401 Unauthorized。这个通常不是模型的问题,而是 Key 没有正确注入。检查三件事:环境变量TAOTOKEN_API_KEY是否在当前 shell 里生效,Base URL 是否写成了https://taotoken.net/api而不是带 UTM 的地址,Model ID 是否拼写正确。如果你用的是 Codex 的auth.json,确认里面同时有 Base URL、Key、Model ID 三个字段,缺一个就会 401。另外注意,Key 不要硬编码在代码里提交到仓库,用环境变量或密钥管理工具。
第二类是local proxy failed或连接被拒绝。这类报错通常出现在你本地起了代理但配置没对齐的时候。检查你的客户端是否错误地指向了本地端口,而实际应该直连https://taotoken.net/api。如果你在容器里跑,确认容器网络能访问外网,DNS 解析正常。还有一种情况是防火墙拦截了出站请求,这时候看 harness 的轨迹里有没有记录连接超时。把 Base URL 单独用curl测一下,能快速定位是网络问题还是配置问题。
第三类是reading choices相关报错,通常表现为解析响应时字段缺失。这多半是因为你用的客户端期望 OpenAI 格式的choices数组,但实际返回结构不匹配。检查你的 Model ID 是否对应正确的接口类型。如果你在用 Claude Code 或 Cline,确认它们的 API 模式设置是否正确。有些工具需要显式指定openai兼容模式,否则会按 Anthropic 原生格式解析,导致读不到choices。
第四类是 OAuth 报错。如果你用的是需要 OAuth 登录的工具,比如某些编码 Agent,报错可能出现在 token 刷新环节。检查你的 OAuth 配置里回调地址是否正确,以及是否把 API Key 和 OAuth 流程混用了。TaoToken 的 API Key 通道和 OAuth 是两条路径,不要在一个配置里同时填。如果你在 Claude Code 里遇到 OAuth 报错,先确认是不是应该改用 API Key 模式。
除了这四类,还有一个高频问题是契约字段写错导致 harness 误判。比如allowed_paths里写了src/auth但实际路径是src/auth/,前缀匹配就会失败。建议在加载契约后加一个自检:打印to_context()的内容,人工读一遍,确认路径和命令没有拼写错误。另外,evidence里的命令要在本地先手动跑通,再交给 harness 执行。如果命令本身依赖特定工作目录,记得在 harness 里先cd到项目根目录。
还有一个坑是上下文注入位置不对。契约文本如果放在用户消息的末尾,模型可能因为上下文截断而看不到。建议放在 system prompt 里,并且保持简短。如果契约太长,把细则拆到仓库内的分册文件里,system prompt 只留短地图和关键边界。这样即使窗口被截断,核心约束还在。
最后,如果你发现 Agent 反复选错工具但契约检查都通过了,那可能是工具接口本身有歧义。检查每个工具的用途描述、输出格式和失败态是否明确。坏工具逼模型猜测,好工具在模型开始胡乱推理之前就降低歧义。把工具返回的模糊字符串改成结构化 JSON,加上明确的error字段,往往能直接减少选错工具的次数。
6. 从提示词到 Harness:把失败写成契约字段与机械检查
回到最开始的问题:Agent 反复选错工具并跳过验收时,改提示词补不回环境里缺失的约束。因为提示词工程告诉模型做什么,上下文工程决定模型看见什么,而 Harness 工程建造模型行动的世界。下个月模型可能换掉,工具、测试、状态、策略和轨迹可以继续变厚。持久优势正在从提示词里搬出来,搬进它周围的系统。
你现在可以做的,是把最近一次 Agent 事故写成契约字段。比如它跳过了测试,那就在evidence里加一条测试命令,并让 harness 强制执行。它修改了不该改的文件,那就在forbidden_paths里加上那个路径,并让path_allowed()在动作执行前拦截。它反复重试不停止,那就把budget设小,并在预算用尽时升级。每一次失败都应该变成一条检查,而不是一段更长的提示词。
如果你还没有统一的 Key 通道,先去 API Keys 页面创建一个,把 Base URL 和 Key 配好。接入文档里有各客户端的配置示例,遇到报错对照上一节的排查清单。想先验证模型能不能按契约选工具,可以用模型对话做一次快速测试。长期跑编码和 Agent 任务,Coding Plan 更适合需要多轮修复和持续调用的场景。
跑完不要只留最终答案。留一张短收据:用了哪些工具、看到了哪些证据、跳过了哪些检查、花了多少预算。这张收据是你下一次改 harness 的输入。没有轨迹,失败是谜;有轨迹,失败是下一次改进的起点。重要规则写两遍,一遍给模型看懂理由,一遍写成它绕不开的机械检查。下一次 Agent 不必记住那次事故,harness 替它记住。