☰
你不知道的 Claude Code 进阶篇:用 Harness 与上下文工程让 Agent 真正跑稳
2026/9/26 14:11:16 网站建设 项目流程

1. 为什么你的 Claude Code Agent 总在第三步就崩了

如果你已经在本地把 Claude Code 跑起来过,大概率经历过这个场景:前两轮工具调用顺风顺水,第三轮开始模型开始重复读同一个文件,第五轮上下文里塞满了ls和cat的输出,第七轮它信誓旦旦说"已完成修改",你一看代码根本没动。这不是模型变笨了,是 Harness 和上下文工程没搭好。

Claude Code 本身是一个 Agent 运行时,它把「感知—决策—行动—反馈」这个循环封装好了,但循环之外的东西——工具怎么定义、上下文怎么分层、状态往哪放、失败怎么回退——全都得你自己设计。我见过太多项目把精力全花在换模型和调 prompt 上,结果 Agent 依然跑不稳,根因往往在工程侧。

这篇面向的是已经跑通最小 Agent、但频繁中断的开发者。我会从 Harness 编排、上下文工程、工具设计三个角度拆解,给出可以直接复制的settings.json和config.toml骨架,配一套 TaoToken 统一 Key 通道的接入配置,最后给一组可执行的稳定性验证动作。全程不聊虚的,每一步都能落地。

2. 先把 TaoToken 通道接上,别让 Key 管理拖后腿

在讲 Harness 之前,得先解决一个前置问题:你的 Agent 要调模型,Key 从哪来、怎么管。本地跑 Agent 最常见的坑是 Key 散落在环境变量、.env、shell 配置里,换一个模型就要改一遍代码,多 Agent 并行时更是灾难。

TaoToken 在这里的角色是统一通道:一个 Key 走通多家模型,Agent 侧只认一个base_url和一个api_key,切换模型只改model字段。官网入口在 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。打开控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在 API Keys 页面创建一个,命名建议带上用途,比如claude-code-local,方便后面按 Agent 粒度做限额。创建后立刻复制,页面刷新就看不到了。

注意:Key 只显示一次,建议直接写进系统的密钥管理工具,不要贴在聊天记录或截图里。

拿到 Key 之后,先做一次最小连通性验证,确认通道没问题再往下搭 Harness。用 curl 打一发:

export TAOTOKEN_API_KEY="sk-你的key" curl -s https://taotoken.net/api/v1/messages \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 128, "messages": [{"role": "user", "content": "只回复两个字:通了"}] }'

返回里能看到content数组带文本,就说明通道正常。这一步别跳过,后面所有 Harness 配置都建立在这条链路之上,链路不通时排查 Harness 是浪费时间。

3. 可复制的 settings.json 与 config.toml 骨架

Claude Code 的配置分两层:settings.json管运行时行为,config.toml管模型通道和工具注册。很多人只配了前者,后者空着,结果工具集和上下文策略全是默认值,Agent 自然跑不稳。

先看settings.json,放在项目根目录的.claude/下:

{ "model": "claude-sonnet-4-5", "maxTokens": 8096, "temperature": 0, "contextManagement": { "compactThreshold": 0.5, "compactStrategy": "branch-summarization", "preserveOrder": [ "architecture-decisions", "modified-files", "verification-status", "open-todos", "tool-outputs" ] }, "harness": { "maxIterations": 40, "toolTimeoutMs": 30000, "retryOnToolError": 2, "requireVerification": true }, "permissions": { "allow": ["Read", "Grep", "Glob"], "ask": ["Bash", "Write", "Edit"], "deny": ["Bash(rm -rf *)", "Bash(git push --force*)"] } }

几个关键点值得展开。temperature设 0 是为了让工具调用稳定,Agent 场景不需要创意。compactThreshold设 0.5 意味着上下文用到一半就触发压缩,别等到 0.9 才动手,那时候关键信息已经被噪声淹没了。preserveOrder是压缩时的保留优先级,架构决策排第一,工具输出排最后——这条顺序直接决定压缩后 Agent 还记不记得自己为什么这么改。

再看config.toml,管模型通道:

[provider] name = "taotoken" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" default_model = "claude-sonnet-4-5" [provider.fallback] enabled = true models = ["claude-sonnet-4-5", "claude-haiku-4-5"] [tools] registry = "./tools/registry.ts" max_definitions = 12 lazy_discovery = true [memory] file = "./MEMORY.md" consolidate_threshold = 0.5 archive_dir = "./.claude/archive"

lazy_discovery = true对应的是工具按需发现,别把十几个工具定义一次性塞进系统提示,那是上下文预算的头号杀手。max_definitions卡在 12 是个经验值,超过这个数模型选错工具的概率明显上升。fallback段是给模型服务抖动兜底的,主模型 503 时自动切下一个,不用人盯。

提示:api_key_env指向环境变量名而不是 Key 本身,配置文件可以进版本库,Key 留在本地环境里。

4. Harness 编排:让循环之外的东西扛住稳定性

Harness 这个词被用得很泛,落到工程上就是四件事:验收基线、执行边界、反馈信号、回退手段。模型负责推理,Harness 负责让推理结果可验证、可回退。

先看验收基线。每个任务在启动前要有一个机器可判断的完成标准,不能是"改好了"这种自然语言。比如重构任务,基线就是npm test全绿加tsc --noEmit无报错。把这条写进任务描述里,Agent 每轮结束自己跑一遍:

interface TaskBaseline { taskId: string; description: string; verify: () => Promise<{ pass: boolean; detail: string }>; } const baseline: TaskBaseline = { taskId: "refactor-auth", description: "把 auth 模块从 session 迁移到 JWT", verify: async () => { const test = await runCommand("npm test -- auth"); const typecheck = await runCommand("tsc --noEmit"); return { pass: test.code === 0 && typecheck.code === 0, detail: `test=${test.code} tsc=${typecheck.code}`, }; }, };

执行边界靠权限配置兜底,上面settings.json里的permissions段就是干这个的。读操作放开,写操作和 Bash 走确认,危险命令直接 deny。别指望模型自己判断"这个 rm 该不该执行",把边界写进配置,一次写进去到处生效。

反馈信号是 Harness 里最容易被忽略的一环。Agent 执行完一步,得有个明确的信号告诉它"这步过了还是没过"。工具返回值不要只给字符串,给结构化结果:

type ToolResult = | { status: "ok"; data: unknown } | { status: "error"; code: string; suggestion: string };

error分支带suggestion字段,模型拿到之后知道下一步怎么修,而不是原地重试绕圈。实测下来,加了suggestion之后,工具调用失败后的自愈率提升很明显。

回退手段对应的是状态外化。每完成一步,把进度写到磁盘:

interface TaskState { taskId: string; status: "pending" | "in-progress" | "completed" | "failed"; completedSteps: string[]; currentStep: string; lastUpdated: number; } async function saveProgress(state: TaskState) { const path = `.claude/tasks/${state.taskId}.json`; await fs.writeFile(path, JSON.stringify(state, null, 2)); }

崩溃之后从resumeTask(taskId)读回来,从断点继续。进度放在文件里,不放在上下文里,这是长任务能跨 session 续跑的前提。

5. 上下文工程:分层、压缩、缓存三件事

上下文工程的核心目标只有一个:让关键信号不被噪声稀释。Transformer 的注意力是 O(n²),上下文越长,每个 token 分到的注意力越少,无关内容一旦占大头,决策质量就往下掉。

分层是第一刀。按信息的使用频率和稳定性分五层:

层级内容加载时机典型文件
常驻层身份、项目约定、禁止项每次会话SOUL.md
按需层Skills、领域知识触发时注入skills/*.md
运行时层时间、渠道、用户偏好每轮拼入动态生成
记忆层跨会话经验需要时读取MEMORY.md
系统层确定性逻辑不进上下文Hooks

常驻层要短、硬、可执行。我见过把几百行工作手册塞进系统提示的,结果模型对真正的约束视而不见。约定留提示,知识移 Skills,这是基本分工。

压缩策略选哪种,取决于任务类型。滑动窗口成本最低但会丢早期决策,适合短对话;LLM 摘要保留决策丢细节,适合长任务;工具结果替换用占位符换掉原始输出,适合工具调用密集的场景。实际项目里通常是组合用:

async function compact(messages: Message[], strategy: string) { if (strategy === "branch-summarization") { const summary = await llmSummarize(messages, { preserve: ["architecture-decisions", "open-todos", "constraints"], }); await appendToMemory(summary); return [{ role: "user", content: summary }]; } if (strategy === "tool-result-replace") { return messages.map((m) => m.role === "tool_result" && isOld(m) ? { ...m, content: "[tool output archived]" } : m ); } }

压缩时有个坑必须避开:不要改动标识符。UUID、hash、端口、文件名、PR 编号这些值必须原样保留,改错一位后续工具调用直接失效。在CLAUDE.md里显式写一条"压缩时标识符不得修改",比事后排查省事得多。

Prompt Caching 是省钱的,也是稳上下文的。命中的前提是精确前缀匹配,系统提示、工具定义这些多轮不变的内容天然适合缓存,动态信息放后面。所以常驻层越稳定,缓存命中率越高,边际成本越低。反直觉的地方在于:稳定的大系统提示,比频繁变动的小提示实际成本更低,因为写入成本只付一次,后续读取折扣能到九成。

6. 工具设计:ACI 原则与结构化错误

上下文决定模型能看到什么,工具决定模型能做什么。工具定义的质量比数量关键得多,五个 MCP 服务器就可能带来几万 token 的定义开销,还没开始对话就吃掉近三成预算。

工具设计经历了三代演进。第一代是把 API 端点直接封装成工具,粒度过细,Agent 要协调多个工具才能完成一个目标。第二代是 ACI(Agent-Computer Interface),工具对应 Agent 的目标而不是底层操作——不要分别暴露create_file、write_content、set_permissions,直接给一个create_script(path, content, executable)。第三代是在 ACI 之上优化发现和调用方式,包括动态工具发现、代码编排、示例驱动。

落到代码上,差的设计和好的设计差距很直观:

// 差:参数模糊,错误只返回字符串 const badTool = { name: "update_post", input_schema: { properties: { post_id: { type: "string" }, content: { type: "string" }, }, }, }; // 出错时 return "Error: update failed"; // 好:定义与实现绑定,错误结构化 const goodTool = betaZodTool({ name: "update_post", description: "更新已有文章内容,不适合创建新文章", inputSchema: z.object({ post_id: z.string().describe("文章 ID,纯数字字符串,如 '12345678'"), content_markdown: z.string().describe("Markdown 格式正文"), }), run: async (input) => { const post = await getPost(input.post_id); if (!post) { throw new ToolError("文章 ID 不存在", { error_code: "POST_NOT_FOUND", suggestion: "请先调用 list_posts 获取有效的 post_id", }); } return await updatePost(input.post_id, input.content_markdown); }, });

差的设计里,工具只说自己能做什么,不说明什么时候该用、什么时候不该用,结果是 Agent 选错工具、填错参数、报错后不断重试。好的设计边界清楚,结构化错误给出修正建议,Agent 一次选对,失败后也能快速修正。

工具数量要克制。能用 Shell 处理的、只需静态知识的、更适合 Skill 的,都不需要新增工具。调试 Agent 时先检查工具定义,大多数工具选择错误的原因出在描述不准确,不在模型能力。

7. 验证请求与成功结果长什么样

配置搭完,得有一套可执行的验证动作,确认 Agent 真的跑稳了,而不是"看起来能跑"。

第一步,验证通道。用第 2 节的 curl 命令打一发,确认返回正常。这一步失败的话,后面全白搭。

第二步,验证工具注册。启动 Claude Code,输入/tools查看已注册工具列表,确认数量在max_definitions以内,且没有重复功能的工具。如果列表里出现十几个功能重叠的工具,回去合并。

第三步,跑一个带验收基线的任务。比如让 Agent 修一个已知的测试失败:

claude "修复 tests/auth.test.ts 里失败的用例,修完跑 npm test -- auth 确认全绿"

观察执行过程。正常的轨迹是:读测试文件 → 定位失败原因 → 改源码 → 跑测试 → 确认通过 → 报告完成。如果出现反复读同一个文件、改了不跑测试就说完成、或者跑测试失败后不分析原因直接重试,说明 Harness 或工具设计有问题。

第四步,验证压缩。故意跑一个长任务,让上下文超过compactThreshold,观察压缩后 Agent 是否还记得架构决策。可以在任务中途问它"你刚才为什么选择这个方案",答得上来说明preserveOrder配对了。

第五步,验证回退。任务跑到一半手动 kill 进程,重启后看能否从.claude/tasks/里的状态文件恢复。恢复不了的话,检查saveProgress是不是每步都调用了。

成功的结果长这样:任务在maxIterations以内完成,验收基线通过,压缩后关键决策保留,崩溃后能从断点续跑。任何一条不满足,回去查对应的配置段。

8. 本篇常见错误排查

报错一:401 Unauthorized或invalid api key

先确认TAOTOKEN_API_KEY环境变量在当前 shell 里生效,echo $TAOTOKEN_API_KEY能看到值。再看config.toml里api_key_env拼写是否一致。如果都对还是 401,去控制台 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 确认 Key 没过期、没被删。接入细节可以参考文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

报错二:context length exceeded

不是窗口不够长,是信息密度不对。检查常驻层是不是塞了太多东西,Skills 是不是全量加载了。把lazy_discovery打开,compactThreshold调低到 0.4,再跑一次。

报错三:Agent 反复调用同一个工具

大概率是工具返回值没有给模型足够的反馈信号。检查工具返回是不是只有"ok"或"failed",改成结构化结果,带上suggestion。另外确认retryOnToolError没设太大,重试两次还不行就该让 Agent 换策略,而不是死磕。

报错四:压缩后 Agent 忘了之前的决策

preserveOrder没配对。架构决策必须排第一,工具输出排最后。另外检查压缩时有没有误改标识符,改错一个 hash 后续全乱。

报错五:多 Agent 并行时状态互相覆盖

每个子 Agent 要有独立的 worktree 和独立的messages[],只回传摘要给主 Agent。共享文件系统的话,用.worktrees/隔离,别让两个 Agent 同时写同一个文件。

报错六:模型服务 503 导致任务中断

config.toml里的fallback段没启用。打开之后,主模型挂了自动切下一个,任务不中断。这个在高峰期特别有用。

9. 下一步:把稳定性变成可度量的东西

Agent 跑稳不是一次配置就完事,得有一套持续验证的机制。建议从第一个真实失败案例开始建评测,把它转成测试用例,每次改配置或换模型都跑一遍。评测不用等体系完整,二十到五十个真实案例就够启动。

验证模型行为时,可以用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 快速对比不同模型在同一 prompt 下的表现,确认切换模型后工具调用是否还稳定。

如果你在跑长期编码任务或者多 Agent 编排,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里有针对 Agent 场景的配额和通道配置,比按次调用更适合持续跑的任务。

最后一条经验:看到 Agent 表现下降,先查评测和环境,再动 Agent 本身。很多时候不是模型退化,是评测环境脏了或者上下文组织出了问题。基于失真的信号去改 Agent,方向可能从一开始就是错的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询