
1. 从 Demo 到稳定运行Agent 为什么总在第三步就崩如果你正在搭 Agent大概率经历过这个场景Demo 里模型乖乖调用一次工具、返回结果、任务完成看起来一切正常。可一旦把任务拉长到十几步它就开始重复调用同一个工具、忘记前面读过的文件内容、或者在某个报错后陷入死循环。问题往往不在模型本身而在包裹模型的那层工程脚手架——也就是 Harness。Harness 这个词直译是马具很形象模型是马力气大但方向感差Harness 是缰绳、鞍具和车架负责把模型的原始生成能力约束成一条能跑完任务的路径。它至少管五件事消息循环thinking → action → observation、工具注册与结果回灌、上下文压缩与记忆、权限沙箱、终止条件与预算。少了任何一环Agent 就会从能跑退化成能跑一次。这篇面向正在搭 Agent 的开发者先讲清 Harness 在大模型 Agent 工程里的角色再给出一套可复制的 settings.json / config.toml 骨架最后用 TaoToken 统一 Key 把链路真正跑通一次。适合已经写过 function calling、但被长任务稳定性折磨过的人。热词里的 Harness、大模型、Agent、LLM 都会落到具体配置上不停留在概念。2. TaoToken 前置一把 Key 打通模型调用层Harness 的第一职责是封装 LLM 调用。如果每个 Agent 项目都自己维护一套 base_url、鉴权、重试、模型名映射脚手架会迅速膨胀成不可维护的泥球。我的做法是把模型调用层抽出来统一走 TaoToken 的 API 入口Harness 只关心发消息、拿回复、解析工具调用不关心背后是哪个模型。TaoToken 在这里扮演的是统一接入层一个 API Key 对应多个模型base_url 固定Harness 的配置里只需要填一次。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数配置时别把推广参数拼进去。先拿到 Key。进入控制台创建 API Key路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 在 API Keys 页面新建一个复制出来。这个 Key 后面会同时出现在 settings.json 和 config.toml 里所以先存到环境变量别硬编码进仓库。export TAOTOKEN_API_KEYsk-你的key环境变量设好后Harness 读取时用process.env.TAOTOKEN_API_KEY或os.environ[TAOTOKEN_API_KEY]这样本地和 CI 都能复用同一套配置。如果你还没决定用哪个模型可以先去模型对话页面试一下不同模型的工具调用表现地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 确认哪个模型在你的任务上工具调用更稳再写进 Harness 配置。3. 可复制配置settings.json 与 config.toml 骨架Harness 的配置分两层一层是模型接入base_url、key、模型名、超时、重试一层是循环与工具最大步数、上下文窗口、工具白名单、沙箱路径。下面给两份骨架一份 JSON 给 Node/TypeScript 系 Harness一份 TOML 给 Python 系 Harness字段含义一致按你的技术栈选一份改。3.1 settings.json 骨架Node / TypeScript Harness{ llm: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, model: claude-sonnet-4-20250514, timeoutMs: 60000, maxRetries: 3, retryBackoffMs: 800 }, harness: { maxSteps: 25, maxTokensPerTask: 120000, contextWindow: 180000, compressThreshold: 0.75, memoryFile: ./MEMORY.md, stopOnRepeatedAction: true, repeatWindow: 3 }, tools: { allow: [read_file, write_file, run_shell, search], sandboxRoot: ./workspace, denyPaths: [/etc, ~/.ssh, ./.env], requireConfirm: [run_shell] }, observability: { traceDir: ./traces, logLevel: info, replayEnabled: true } }几个字段值得展开。compressThreshold是上下文压缩触发线当已用 token 达到窗口的 75% 时Harness 应该把早期 observation 摘要成一段短文本而不是直接截断——截断会丢工具调用结果摘要能保住关键信息。stopOnRepeatedAction配合repeatWindow是防死循环的兜底连续 3 步调用同一个工具且参数相同就强制中断并让模型重新规划。requireConfirm把 shell 执行标成需确认避免 Agent 在无人值守时跑出危险命令。3.2 config.toml 骨架Python Harness[llm] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model claude-sonnet-4-20250514 timeout_ms 60000 max_retries 3 retry_backoff_ms 800 [harness] max_steps 25 max_tokens_per_task 120000 context_window 180000 compress_threshold 0.75 memory_file ./MEMORY.md stop_on_repeated_action true repeat_window 3 [tools] allow [read_file, write_file, run_shell, search] sandbox_root ./workspace deny_paths [/etc, ~/.ssh, ./.env] require_confirm [run_shell] [observability] trace_dir ./traces log_level info replay_enabled true两份配置的base_url都指向 https://taotoken.net/api Harness 内部构造请求时拼成/v1/messages或/v1/chat/completions具体看你的 SDK。api_key_env只写环境变量名不写值这样配置可以进版本库。model字段换成你在模型对话里验证过的模型名即可。3.3 主循环伪代码把配置接进 Harness配置只是静态骨架真正让 Agent 跑起来的是主循环。下面这段伪代码展示 Harness 如何用上面的配置驱动一次任务重点看工具结果回灌和终止判断。import os, json, time from openai import OpenAI cfg load_toml(config.toml) client OpenAI( base_urlcfg[llm][base_url], api_keyos.environ[cfg[llm][api_key_env]], ) def run_agent(task: str): messages [{role: user, content: task}] steps 0 last_action None repeat_count 0 while steps cfg[harness][max_steps]: resp client.chat.completions.create( modelcfg[llm][model], messagesmessages, toolsload_tool_schemas(cfg[tools][allow]), timeoutcfg[llm][timeout_ms] / 1000, ) msg resp.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content # 模型认为任务完成 for call in msg.tool_calls: action_key (call.function.name, call.function.arguments) if action_key last_action: repeat_count 1 else: repeat_count 0 last_action action_key if repeat_count cfg[harness][repeat_window]: messages.append({ role: user, content: 检测到重复动作请重新规划下一步。 }) break result dispatch_tool(call, cfg[tools]) messages.append({ role: tool, tool_call_id: call.id, content: json.dumps(result, ensure_asciiFalse), }) steps 1 messages maybe_compress(messages, cfg[harness]) return 达到最大步数任务未完成这段代码里maybe_compress负责在 token 超阈值时摘要历史dispatch_tool负责沙箱校验和路径白名单。Harness 的价值就体现在这些看似琐碎的判断里——它们决定了 Agent 是跑三步就崩还是能稳定跑完二十几步。4. 验证请求一次完整的 Agent 调用配置写完别急着上复杂任务。先用一个最小任务验证链路让 Agent 读一个文件、改一个字段、写回去。这个任务包含读、写两类工具调用能同时验证工具注册、结果回灌和文件沙箱。准备一个测试文件mkdir -p workspace echo {name: demo, version: 0.1.0} workspace/pkg.json然后跑 Agent任务描述写成读取 workspace/pkg.json把 version 改成 0.2.0写回原文件最后告诉我改后的内容。预期行为是第一步模型发起read_file调用Harness 返回文件内容第二步模型发起write_file调用参数里带新内容第三步模型返回最终文本。如果这三步都正常说明 base_url、Key、工具 schema、结果回灌四条链路都通了。验证时重点看两处。一是请求头里的鉴权是否生效如果返回 401多半是环境变量没读到或 Key 复制时带了空格。二是工具调用的tool_call_id是否和回灌的role: tool消息对上对不上会导致模型下一轮报缺少工具结果。我试过在回灌时漏掉tool_call_id模型会反复重发同一个工具调用看起来像死循环其实是消息结构不合法。跑通后把 trace 打开看一眼cat traces/latest.json | jq .steps[] | {step: .index, tool: .tool_name, tokens: .usage.total_tokens}这条命令能列出每一步的工具名和 token 消耗。如果某一步 token 突然暴涨通常是 observation 太长没压缩回去调compressThreshold。如果步数卡在maxSteps附近说明任务拆解粒度太粗要么调大maxSteps要么在 prompt 里要求模型先规划再执行。5. 本篇常见错排查配置和验证过程中有几类错误反复出现单独列出来对照。第一类是base_url拼错。有人把推广参数拼进 API 地址变成https://taotoken.net/api?utm_source...请求直接 404。记住 API 地址就是 https://taotoken.net/api 不带任何查询参数。官网首页才带 UTM两者别混。第二类是模型名不存在。model字段写了一个当前账号没开通的模型返回 400 或 model not found。解决办法是先去模型对话页面确认可用模型再回填配置。模型对话入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。第三类是工具 schema 和实际 dispatch 不一致。schema 里声明了read_file有path参数dispatch 里却读file_path模型传参后 Harness 拿不到值工具返回空模型以为文件是空的接着写出错误内容。排查方法是把工具调用参数原样打日志和 schema 对一遍。第四类是上下文压缩把工具结果压没了。压缩策略如果只保留 assistant 消息、丢掉 tool 消息模型下一轮就不知道自己刚才读到了什么会重复调用。正确做法是压缩时保留最近 N 轮完整消息只对更早的 observation 做摘要。第五类是沙箱路径没生效。sandboxRoot设了./workspace但工具实现里用的是绝对路径拼接结果 Agent 能写到工作区外面。排查时故意让 Agent 写一个../outside.txt看是否被拒绝。如果没被拒绝说明路径校验漏了..归一化。第六类是超时设置太短。长任务里单次模型调用可能超过 30 秒timeoutMs设成 30000 会频繁超时重试看起来像网络问题。把超时放到 60000 以上重试次数控制在 3 次以内避免雪崩。6. 把 Harness 用起来从跑通到跑稳链路跑通只是起点。真正让 Agent 稳定靠的是把上面这些配置项当成可调参数而不是一次写死。我的习惯是每接一个新任务类型先跑 5 次看 trace 里的步数分布和 token 消耗再决定是调maxSteps、调压缩阈值还是收紧工具白名单。如果你接下来要长期跑编码类 Agent比如让 Agent 自己读仓库、改代码、跑测试建议把模型接入层固定下来用 TaoToken 的 Coding Plan 统一管理调用额度和模型切换入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这样 Harness 配置里的model字段可以随任务切换而 base_url 和 Key 不用动。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有针对不同 SDK 的请求示例配置base_url和鉴权头时对照一下能省掉不少调试时间。API Keys 管理页面还是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 需要轮换 Key 或给不同环境发不同 Key 时在这里操作。最后留一个实用技巧把MEMORY.md当成 Harness 的长期记忆文件每完成一个任务让 Agent 把关键决策写进去下一轮启动时读回来。这比单纯靠上下文压缩更稳因为压缩是有损的而记忆文件是显式的、可审查的。配置里memoryFile指向它主循环启动时读一次、结束时写一次Agent 的跨任务一致性会明显改善。