资讯动态

AI Agent运行时架构解析:Harness如何约束模型行为与上下文管理

发布时间:2026/10/22 4:10:29 来源:尧图企业网站定制
1. 从一次 Agent 失控说起为什么运行时设计比模型能力更值得关注去年冬天我接手了一个内部代码辅助工具的重构任务。当时团队里已经有一个基于大模型 API 的代码生成脚本功能很简单把用户需求拼成 prompt发给模型拿到返回的代码片段写进文件。跑单轮任务时一切正常但只要涉及多文件修改、需要读取项目上下文、或者模型想先看看某个目录下有什么文件整个流程就崩了。模型会一本正经地幻觉出一个不存在的文件路径然后基于这个假路径继续生成代码最后写出来的东西根本编译不过。这个问题困扰了我很久。我试过在 prompt 里反复强调不要编造文件路径试过把项目目录树硬塞进上下文试过让模型先输出一个计划再执行。效果都不稳定。直到我开始研究 Claude Code 这类成熟 AI Agent 产品的运行时架构才意识到问题的根源根本不在 prompt 工程而在于缺少一个可靠的运行时层来约束和支撑模型的行为。Claude Code 是 Anthropic 推出的命令行 AI 编程助手它能在真实项目里读写文件、执行命令、搜索代码、运行测试。很多人第一次用会觉得不就是把模型接了个终端吗但真正拆开看它的运行时设计你会发现里面有一整套精密的机制在运转。这套机制在社区里常被称作Harness 架构——Harness这个词本意是马具、挽具引申为约束和引导系统。放在 AI Agent 语境下它指的是包裹在模型外面、负责管理工具调用、上下文、权限、错误恢复的那一层运行时框架。这篇文章不打算复述官方文档而是从源码结构和实际运行逻辑出发把 Harness 架构拆成几个关键问题来讲模型和真实世界之间的边界在哪里工具调用是怎么被调度和约束的上下文窗口快满了怎么办权限和安全性怎么保证我会结合自己在构建类似系统时踩过的坑把每个设计决策背后的为什么讲清楚。如果你正在做 AI Agent 相关的开发或者单纯好奇 Claude Code 这类工具内部是怎么跑起来的这篇内容应该能给你一些可以直接参考的思路。2. Harness 到底管什么模型与真实世界之间的那层翻译官2.1 一个容易被忽略的事实模型本身没有手也没有眼睛很多人对 AI Agent 的理解停留在模型很聪明所以它能干活。但严格来说模型只是一个文本进、文本出的函数。它没有文件系统访问能力没有网络请求能力没有执行命令的能力。它唯一能做的就是根据输入的 token 序列预测下一个 token。那 Claude Code 为什么能读文件、改代码、跑测试因为 Harness 层在中间做了大量工作。当模型输出一段特定格式的文本比如表示我想读取某个文件Harness 会解析这段文本识别出这是一个工具调用请求然后代替模型去执行真正的文件读取操作再把读取结果作为新的输入喂回给模型。模型看到的始终是文本但文本背后对应的真实世界操作全部由 Harness 代理完成。这个设计的关键在于模型和真实世界之间必须有一层明确的协议。模型不能直接说把那个文件改了它必须输出结构化的工具调用指令Harness 校验通过后才执行。这层协议就是 Harness 的核心价值之一。2.2 工具调用的完整生命周期我把一次典型的工具调用拆成六个阶段这样你能清楚看到 Harness 在每个环节做了什么阶段模型侧动作Harness 侧动作关键约束意图生成输出工具名和参数解析文本提取结构化调用参数格式必须符合 schema权限校验无检查该工具是否被允许、路径是否在允许范围内越权操作直接拒绝执行调度无调用真实系统接口读文件、执行命令等超时、资源限制结果捕获无收集 stdout、stderr、退出码、文件内容结果截断策略结果格式化无把原始结果转成模型能理解的文本控制 token 消耗回注上下文接收新输入把格式化结果追加到对话历史上下文窗口管理这张表里最容易被低估的是权限校验和结果格式化。权限校验决定了 Agent 能不能被信任去操作真实项目结果格式化决定了长输出比如一个几千行的日志文件会不会瞬间把上下文窗口撑爆。2.3 为什么不能把工具调用逻辑直接写进 prompt我早期做原型时犯过一个典型错误把所有工具的使用说明、参数格式、返回格式全部写进 system prompt然后指望模型自觉按照格式输出。结果模型经常在格式上出错——少个括号、参数名拼错、把该传字符串的地方传了数字。更麻烦的是当工具数量增加到十几个时prompt 变得极其臃肿模型开始混淆不同工具的用途。Harness 架构的做法完全不同工具定义是结构化的 schema不是自然语言描述。模型在训练或微调阶段就见过这种结构化调用格式输出时遵循的是模式匹配而非阅读理解。Harness 拿到输出后做严格的 schema 校验格式不对直接返回错误让模型重试而不是硬着头皮执行。这个差异带来的稳定性提升是巨大的。我后来把原型改成 schema 驱动的工具调用后格式错误率从大概三成降到了个位数百分比。2.4 Harness 不是胶水代码而是运行时系统很多人第一次接触这个概念时会觉得 Harness 就是把模型输出和系统调用粘起来的胶水。这个理解太浅了。胶水代码是被动的、无状态的而 Harness 是有状态的运行时系统它需要维护对话历史、跟踪已执行的操作、管理上下文预算、处理并发和超时、在出错时决定是重试还是回滚。举个具体例子当模型请求读取一个文件但文件不存在时Harness 不能简单地把文件不存在这个错误抛回去就完事。它需要考虑这个错误信息怎么格式化才能让模型理解模型看到错误后大概率会尝试列出目录Harness 要不要提前把目录信息准备好如果模型连续三次尝试读取不存在的文件是不是应该触发某种熔断机制这些决策都不是胶水能承担的它们构成了一个完整的运行时系统。理解这一点是理解整个 Harness 架构的起点。3. 工具调度的核心机制从 schema 定义到执行沙箱3.1 工具 schema 的设计哲学约束越紧模型越稳Claude Code 里每个工具都有严格的 schema 定义。以文件读取为例它不会让模型自由发挥说帮我看看那个配置文件而是要求模型输出类似这样的结构化调用{ tool: read_file, parameters: { path: /project/src/config.ts, offset: 0, limit: 200 } }注意这里的几个设计细节。path是必填的offset和limit是可选的并且有默认值。这种设计让模型在大多数情况下只需要提供最核心的参数降低了出错概率。同时limit的存在意味着 Harness 可以控制单次读取的最大行数防止模型一次性拉取一个超大文件把上下文撑爆。我在自己实现类似系统时一开始把 schema 设计得很宽松觉得给模型更多自由度会更好。实测下来完全相反参数越少、约束越紧模型输出越稳定。后来我把每个工具的参数控制在三个以内必填参数只保留一个整体成功率明显提升。3.2 执行沙箱为什么 Agent 不能直接碰真实系统Claude Code 在执行命令时并不是简单地把模型给的命令字符串丢给 shell。它有一层执行沙箱负责几件事工作目录锁定所有相对路径都相对于项目根目录解析模型不能通过../../跳出项目范围命令白名单/黑名单某些危险命令比如格式化磁盘、删除系统目录会被直接拦截超时控制任何命令执行超过设定时间就被强制终止防止模型跑出一个死循环输出截断命令输出超过一定长度后只保留头尾中间部分用省略标记替代这几点里输出截断是最容易被忽视但影响最大的。我踩过一次坑让 Agent 跑一个测试命令结果测试框架输出了上万行日志全部塞进上下文后模型直接失忆了——它把前面的对话内容全忘了开始胡言乱语。后来我加了输出截断策略只保留前 100 行和后 50 行中间用... [truncated N lines] ...标记问题就解决了。提示输出截断的阈值需要根据模型上下文窗口大小动态调整。窗口越大可以保留的输出越多但永远不要假设窗口够大就不用截断。3.3 工具之间的依赖与编排单个工具调用好处理难的是多个工具之间的依赖关系。比如模型想找到所有使用了某个函数的文件然后逐个检查这就涉及搜索工具和读取工具的串联。Harness 处理这种编排的方式是让模型自己决定调用顺序但 Harness 负责维护状态。模型输出第一个工具调用搜索Harness 执行后把结果回注模型看到搜索结果后输出第二个工具调用读取某个文件Harness 继续执行。整个过程是串行的、有状态的。这里有个实际经验不要让 Harness 替模型做太多智能编排。我早期尝试过在 Harness 里加一些如果模型搜索到了文件就自动帮它读取前三个的逻辑结果反而干扰了模型的判断——它本来可能只想读其中一个被强行喂了三个文件的内容后注意力被分散了。后来我把编排权完全交还给模型Harness 只负责执行和状态维护效果反而更好。3.4 并发调用的处理策略有些场景下模型会一次性输出多个独立的工具调用比如同时读取三个不相关的文件。这时候 Harness 面临一个选择串行执行还是并行执行串行执行简单、状态清晰但慢并行执行快但需要处理结果顺序、错误隔离等问题。Claude Code 在这方面的策略是默认串行特定只读工具允许并行。读取文件、搜索代码这类无副作用的操作可以并行写文件、执行命令这类有副作用的操作必须串行。这个区分很重要。如果两个写操作并行执行可能会出现竞态条件——比如两个调用同时修改同一个文件最后写入的结果取决于执行顺序这是不可接受的。我在自己的系统里也遵循了这个原则只读操作并行、写操作串行稳定性提升明显。4. 上下文窗口的攻防战Harness 如何对抗记忆溢出4.1 上下文窗口是 Agent 最稀缺的资源做过 Agent 开发的人都有一个共同体会上下文窗口永远不够用。模型能力再强一旦上下文被塞满它就会开始遗忘早期内容行为变得不可预测。Claude Code 这类工具面临的上下文压力尤其大。一次完整的编程任务可能涉及读取多个文件、执行多次命令、查看大量输出、多轮对话交互。这些内容全部累积在对话历史里很快就会逼近窗口上限。Harness 层必须主动管理上下文而不是被动等待溢出。我见过一些实现是等快满了再压缩这种策略在简单场景下能用但一旦任务复杂就容易出问题——压缩时机太晚模型已经因为上下文混乱而做出了错误决策。4.2 上下文预算的分配逻辑一个成熟的 Harness 会把上下文窗口当成一个需要精打细算的预算来管理。我参考 Claude Code 的思路把上下文分成几个区域系统提示区工具定义、行为规范、安全约束这部分通常固定不变占用相对稳定任务上下文区当前任务相关的文件内容、搜索结果这部分是动态的也是最大的变量对话历史区用户和模型的多轮交互记录工具结果区最近几次工具调用的返回内容关键在于给每个区域设定软上限。比如工具结果区最多占窗口的 40%超过后触发压缩对话历史区保留最近 N 轮更早的做摘要处理。这样即使某个区域突然膨胀也不会瞬间挤占其他区域的空间。4.3 压缩策略摘要、截断还是丢弃当上下文接近上限时Harness 有几种处理方式各有适用场景策略做法适用场景风险摘要压缩用模型把长内容概括成短摘要对话历史、大段文档摘要可能丢失关键细节硬截断直接砍掉超出部分工具输出、日志可能砍掉重要信息选择性丢弃按优先级丢弃低价值内容早期对话、已完成的子任务可能丢失上下文连贯性外部存储把内容存到外部需要时再检索大型代码库、历史记录增加检索开销和复杂度Claude Code 在实际运行中会组合使用这些策略。比如工具输出用硬截断对话历史用摘要压缩已完成的子任务结果选择性丢弃。我在自己的系统里也采用了类似组合实测下来比单一策略稳定得多。注意摘要压缩本身也要消耗 token 和一次模型调用所以不能频繁触发。我的经验是设置一个压缩阈值比如窗口使用率达到 75% 时才启动避免每轮都压缩导致开销过大。4.4 一个真实的上下文事故复盘说一个我踩过的坑。有一次我让 Agent 处理一个重构任务它需要读取大概二十个文件。我当时的实现是每读一个文件就把完整内容追加到上下文没有做任何预算控制。前十个文件还好到第十五个左右模型开始出现异常它把两个不同文件的函数名搞混了生成的代码引用了错误的模块。我一开始以为是模型能力问题换了更大的模型结果只是把崩溃点往后推了几个文件。后来才意识到问题不在模型在于上下文里塞了太多无关信息模型的注意力被稀释了。修复方案是读取文件时只保留与当前任务相关的片段而不是整个文件同时给工具结果区设置硬上限超过后自动丢弃最早的读取结果。改完之后同样的任务在更小的模型上也能稳定完成。这个经历让我深刻理解了一件事上下文管理的质量直接决定了 Agent 的可靠性上限。5. 权限、安全与错误恢复让 Agent 可信地操作真实项目5.1 权限模型最小必要原则让一个 AI Agent 操作你的真实项目这件事本身就带有风险。它可能误删文件、执行危险命令、把敏感信息发送到不该去的地方。Harness 的权限模型就是用来控制这些风险的。Claude Code 的权限设计遵循最小必要原则默认情况下Agent 只能执行只读操作任何写操作或命令执行都需要显式授权。授权可以是一次性的本次允许也可以是会话级的本次会话内都允许还可以是持久化的写入配置文件。这个分层设计很实用。我在实际使用中对于读取和搜索类操作基本全部放行因为它们是只读的、可逆的对于写文件和执行命令则保持每次确认除非是在一个隔离的测试环境里。5.2 路径与命令的双重校验权限校验不是简单的允许/拒绝开关而是需要细粒度的路径和命令校验。路径校验的核心是规范化后比对。模型可能输出./src/../src/config.ts这种带相对路径的写法Harness 需要先把它规范化成绝对路径再检查是否在允许的目录范围内。这个步骤不能省否则模型可能通过路径拼接绕过限制。命令校验则更复杂。简单的做法是维护一个黑名单但黑名单永远不够全。更可靠的做法是白名单 参数校验只允许执行已知安全的命令并且对参数做格式检查。比如允许npm test但不允许npm test -- --grep .*这种可能被滥用的形式。5.3 错误恢复重试、回滚还是上报Agent 执行任务时出错是常态关键是怎么处理。Harness 的错误恢复策略通常分三层自动重试对于临时性错误网络超时、文件被占用自动重试若干次回滚对于写操作失败尝试恢复到操作前的状态上报对于无法自动处理的错误把详细信息返回给模型让它决定下一步这里有个容易踩的坑不要无脑重试。我早期实现过一个失败就重试三次的逻辑结果遇到一个必然失败的操作比如读取一个权限不足的文件重试三次只是浪费时间和 token。后来我加了错误分类只有明确可恢复的错误才重试其他直接上报。5.4 操作审计与可追溯性一个成熟的 Harness 会记录所有工具调用的完整日志谁发起的、什么时间、什么参数、执行结果如何。这些日志在排查问题时极其有用。我印象很深的一次排查Agent 生成的代码里有一个微妙的 bug我怀疑是它读取了错误的文件版本。翻出审计日志后发现它确实读取了正确的文件但在后续的编辑操作中参数里的行号偏移了一位。如果没有详细日志这种问题几乎不可能定位。审计日志的另一个价值是安全回溯。如果发现 Agent 执行了不该执行的操作可以通过日志确认它是怎么绕过权限校验的如果有的话从而修补漏洞。6. 自己动手构建一个最小可用 Harness 的关键决策点6.1 从零开始的架构选型如果你打算自己实现一个类似 Claude Code 的 Agent 运行时有几个关键决策需要提前想清楚第一工具调用协议用什么格式可以选择模型原生支持的 function calling 格式也可以自定义一套文本协议。原生格式的好处是模型训练时就见过输出稳定自定义协议的好处是灵活可以针对特定场景优化。我的建议是优先用原生格式除非你有非常特殊的需求。第二状态存在哪里对话历史、工具调用记录、上下文预算这些状态可以存在内存里也可以持久化到磁盘。内存方案简单但重启即丢持久化方案复杂但支持断点续跑。对于生产级系统持久化几乎是必须的。第三执行环境怎么隔离是在宿主机上直接执行还是用容器/沙箱隔离直接执行简单但风险高隔离方案安全但增加复杂度。我的经验是开发阶段可以直接执行生产环境必须隔离。6.2 核心循环的伪代码实现抛开细节Harness 的核心循环其实不复杂。下面是我自己实现时用的简化版本def agent_loop(user_input, max_turns50): context build_initial_context(user_input) for turn in range(max_turns): # 检查上下文预算 if context.usage_ratio() 0.75: context.compress() # 调用模型 response call_model(context) # 解析工具调用 tool_calls parse_tool_calls(response) if not tool_calls: # 没有工具调用说明模型给出了最终回答 return response.text # 执行工具调用 for call in tool_calls: if not check_permission(call): result Permission denied else: result execute_tool(call) context.append_tool_result(call, result) return Max turns reached这个循环看起来简单但每个函数背后都有大量细节。check_permission要做路径规范化和命令校验execute_tool要处理超时和输出截断context.compress要实现摘要或截断策略。把这些细节都填好就是一个可用的 Harness。6.3 实测中的性能与稳定性调优我在自己的系统上线后做了一轮性能调优有几个发现值得分享工具调用的解析开销不可忽视。如果模型输出的是 JSON 格式的工具调用解析本身要花时间。当一轮对话里有多个工具调用时解析开销会累积。优化方法是缓存解析结果避免重复解析。上下文压缩的时机很关键。压缩太早浪费 token压缩太晚模型已经受影响。我最后采用的策略是当使用率超过 70% 时开始准备压缩超过 80% 时强制执行。这个阈值可以根据实际任务类型微调。错误信息的格式影响重试成功率。同样是文件不存在返回Error: file not found和返回File not found: /path/to/file. Available files in directory: [...]模型的重试成功率完全不同。后者给了模型足够的信息去修正前者只能让它瞎猜。6.4 哪些场景不适合用 Agent最后说一个反直觉的观点不是所有任务都适合用 Agent。我见过一些团队把简单的文本处理任务也包装成 Agent结果引入了大量不必要的复杂度和不确定性。适合 Agent 的场景通常有这几个特征任务需要多步操作、需要根据中间结果动态调整策略、涉及真实系统交互。如果任务可以用一个固定的脚本完成那就用脚本别用 Agent。Agent 的价值在于处理不确定性而不是替代确定性流程。7. 写在最后Harness 架构给 Agent 开发者的三点启示折腾了这么久我对 Harness 架构最大的体会是AI Agent 的可靠性很大程度上不取决于模型有多强而取决于运行时层设计得有多扎实。模型能力是上限Harness 是下限。一个设计良好的 Harness 能让中等能力的模型稳定完成复杂任务一个粗糙的 Harness 会让最强模型也频繁翻车。第二点体会是关于约束的价值。我早期总想着给模型更多自由后来发现恰恰相反——清晰的边界、严格的 schema、明确的权限这些约束反而让模型表现更好。约束不是限制能力而是把能力引导到正确的方向上。第三点上下文管理是 Agent 开发的核心竞争力。工具调用、权限校验这些机制社区里有很多现成方案可以参考但上下文预算怎么分配、压缩策略怎么组合、什么时候该丢弃什么内容这些决策高度依赖具体场景没有通用答案。把这块做扎实Agent 的稳定性会有质的提升。如果你正在构建自己的 Agent 系统我的建议是先把 Harness 的核心循环跑通哪怕功能很简陋然后逐步加上权限校验、上下文管理、错误恢复这些机制。每加一层你都会发现系统的稳定性上一个台阶。这个过程急不得但每一步都值得。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑