ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

你不知道的 Claude Code 进阶篇:用 Harness 与上下文工程让 Agent 真正跑稳

你不知道的 Claude Code 进阶篇:用 Harness 与上下文工程让 Agent 真正跑稳 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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 注意 API 地址不带 UTM 参数配置时别把查询串带进去。先拿 Key。打开控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 在 API Keys 页面创建一个命名建议带上用途比如claude-code-local方便后面按 Agent 粒度做限额。创建后立刻复制页面刷新就看不到了。注意Key 只显示一次建议直接写进系统的密钥管理工具不要贴在聊天记录或截图里。拿到 Key 之后先做一次最小连通性验证确认通道没问题再往下搭 Harness。用 curl 打一发export TAOTOKEN_API_KEYsk-你的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/archivelazy_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 要协调多个工具才能完成一个目标。第二代是 ACIAgent-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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 确认 Key 没过期、没被删。接入细节可以参考文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 快速对比不同模型在同一 prompt 下的表现确认切换模型后工具调用是否还稳定。如果你在跑长期编码任务或者多 Agent 编排Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里有针对 Agent 场景的配额和通道配置比按次调用更适合持续跑的任务。最后一条经验看到 Agent 表现下降先查评测和环境再动 Agent 本身。很多时候不是模型退化是评测环境脏了或者上下文组织出了问题。基于失真的信号去改 Agent方向可能从一开始就是错的。
返回列表