
过去一年里我做 AI Agent 相关项目最深的感触是让大模型想出来不难让它稳定地跑完一整套任务才难。早期我用裸脚本调 API写一个 while 循环让模型调工具、拿结果、再调下一个工具单看 demo 很惊艳一上真实业务就翻车——要么在某个工具调用上反复横跳要么上下文越积越长把推理速度拖垮要么一个非法 JSON 直接让整个流程崩溃。后来接触到 Harness 工程这个概念我才意识到问题不在模型而在模型外面的那层工程壳。所谓 Harness直译是马具在 Agent 体系里指套在模型推理循环之外的一整套运行框架生命周期管理、工具契约、上下文控制、权限沙箱、可观测性。它和 Agent 的关系有点像操作系统和应用程序Agent 负责思考Harness 负责让它别跑飞、别超预算、别把系统搞坏并且在出错时给出可解释的回退路径。这篇文章我想把这些机制掰开揉碎讲清楚再带一段可以直接抄作业的实操过程。无论是正在搭第一个 Agent 的初学者还是已经被线上 Agent 折磨过几轮的负责人应该都能在里面找到对应的那部分答案。1. 为什么说 Agent 的稳定性问题本质上是工程问题很多团队把 Agent 不稳定归因于模型能力不够我的观点正好相反绝大多数翻车现场问题都出在 Agent 外面那层壳太薄。模型负责的是从输入到输出的概率推断而稳定运行所需要的停止条件、资源约束、失败恢复、安全边界模型本身并不关心也没能力保证。1.1 裸跑 Agent 的四个痛点第一个痛点是循环不可控。最朴素的 Agent 实现就是一个 while 循环把用户任务拼进 prompt调模型模型决定调用哪个工具把工具结果拼回对话再调模型。问题在于循环什么时候结束往往依赖模型自觉。模型在同一个失败工具上反复尝试、在多个工具之间绕圈、甚至在已经完成任务后又继续补充发挥这些场景我用一句话总结模型没有义务保证收敛它只负责每一步都看起来合理。第二个痛点是上下文无限膨胀。对话历史的长度会随工具调用次数线性增长而模型窗口是有限的。一旦超过窗口要么直接被截断要么早期的关键信息被挤出。更隐蔽的是上下文一长模型对中间细节的注意力会被稀释开始出现前后矛盾、重复提问、遗忘指令的现象。这时候你很难判断是模型傻了还是上下文脏了。第三个痛点是工具调用没有约束。裸脚本里的工具调用通常只是把模型返回的字符串拿去 exec等于让模型能执行任意命令。模型可能请求读取它不该读的文件、写出格式错误的参数、调用一个根本不存在的方法。Demo 阶段这些都能忍生产环境每一项都是事故。第四个痛点是黑盒不可观测。裸脚本跑挂了你只知道它挂了不知道在哪一步挂的、为什么挂、当时模型看到了什么、工具返回了什么。没有 trace、没有状态持久化想复现问题都难更别提让 Agent 具备从失败中恢复的能力。1.2 Harness 是什么Agent 的操作系统马具的作用不是让马变聪明而是让马按照骑手的方向走防止它乱跑、乱跳、脱缰。Harness 在 Agent 里的定位一模一样给模型套上缰绳和鞍具把自由发挥变成在边界内发挥。一个完整的 Harness 至少提供五类能力生命周期管理把 Agent 的任务执行拆成明确的阶段并在阶段之间注入约束工具契约规定模型能调什么、参数怎么校验、结果怎么返回上下文管理负责裁剪、摘要、持久化对话状态确保模型永远在可控的 token 预算内工作容错机制对瞬时错误、永久错误、死循环分别给出不同的处理策略可观测性记录每一步的输入输出、耗时、成本让 Agent 的行为可以被审计和复现。社区讨论度很高的 DeepSeek Harness 就是这个思路的一个典型实现它把模型推理循环包装起来外面再挂上技能Skill、插件、状态存储和配置中心用户主要跟 Harness 配置打交道而不是直接跟模型对话。类似地还有很多项目在做同一件事只是叫法不同有的叫 Agent 运行时有的叫编排框架有的叫推理外壳。不管叫什么核心思路一致把不可控的模型行为变成可控的工程流程。1.3 Harness 与 Agent 的边界划分经常有人问我 Harness 和 Agent 到底有什么区别。我一般这么答Agent 是认知单元Harness 是运行单元。Agent 关心的是理解任务、拆解步骤、选择工具、生成回复这些能力来自模型本身和提示词设计Harness 关心的是这个 Agent 进程什么时候启动、什么时候必须停止、最多能跑多少步、能访问哪些资源、每一步干了什么、失败了怎么降级这些能力来自代码、配置和基础设施。可以说Agent 负责智力Harness 负责治理。这两者不一定要拆成两个独立服务很多实现里它们就在同一个进程内但职责必须拆开。我在架构评审时最看重的一点就是Agent 核心代码里不能出现资源限制、权限校验、日志审计这类治理逻辑这些必须由 Harness 统一注入。否则十个人写十个样最后稳定性无从谈起。2. Harness 的核心机制拆解稳定性的来源这一节是全文的重点。我理解的 Harness 工程不是某一个单一组件而是一组围绕模型循环的护栏。本节拆开来看每个护栏为什么存在、怎么工作、以及我自己在落地时踩过的坑。2.1 生命周期管理把逃跑的循环关进状态机Agent 的执行过程看着是自由对话实际上应该是一台严格的状态机。我常用的状态集合是IDLE、PARSING、PLANNING、EXECUTING、OBSERVING、REFLECTING、COMPLETED、FAILED、CANCELLED。模型只在部分状态里有发言权比如 PLANNING 时决定下一步调用哪个工具REFLECTING 时判断任务是否完成而状态之间的迁移由 Harness 控制。这带来几个关键约束。一是最大步数限制也就是 max_steps。模型说我还要继续做如果步数已到上限Harness 直接终止并把当前进展整理成报告交还用户而不是无脑继续烧 token。二是单步超时。工具可能卡在网络请求或数据库锁上Harness 必须在超时后收回控制权。三是总 token 预算超过后先触发上下文压缩压缩后仍不够就结束任务。伪代码大概是这个骨架state INITIALIZING step 0 while state in (PLANNING, EXECUTING, OBSERVING, REFLECTING): if step config.max_steps: state FAILED break if total_tokens config.max_total_tokens: context.compress() # 触发摘要压缩 result model.step(context, tools) state result.next_state if state EXECUTING: tool_result execute_tool(result.tool_call) context.add_observation(tool_result) step 1我自己的体会是状态机的价值不只是防止跑飞更在于让每条日志都有明确的阶段归属。线上排查时看到一条日志处于 EXECUTING 阶段就能立刻知道接下来该查工具执行链路而不是去猜模型意图。2.2 工具契约与权限控制别让 Agent 什么都敢调模型输出工具调用本质上是一段文本。Harness 要做的第一步是把它从文本变成可执行且有边界的行为。工具契约通常包含三块内容工具名与描述、入参 JSON Schema、副作用等级。副作用等级我习惯分三级只读操作、写操作、高危操作。只读操作可以放行写操作需要二次确认或在隔离区执行高危操作删文件、执行任意 shell 命令、改配置默认拒绝除非显式在配置里开通。入参校验是关键一环。模型经常把参数类型写错比如数字字段传了字符串、必填字段漏传。Harness 在调用工具前用 JSON Schema 校验一次不合法就直接把校验错误返回给模型让它自己修正。这比把错误参数传进工具、等工具抛异常要高很多——因为工具抛异常时模型拿到的是一段堆栈它很难从中推断出到底该怎么改。一个工具定义大概长这样{ name: read_document, description: 读取指定路径的文档内容支持 txt、md、pdf 格式, side_effect: read_only, parameters: { type: object, properties: { path: {type: string, description: 文档绝对路径}, encoding: {type: string, enum: [utf-8, gbk]} }, required: [path] } }权限上要有白名单思维模型只能调用注册过的工具不能通过自然语言绕过。很多时候模型会尝试用 read_document 去读系统配置文件Harness 要在工具实现层再做一层路径检查和目录限制。双保险别指望模型守规矩。2.3 上下文与记忆管理守住 token 预算Agent 的上下文管理远比把长对话截断复杂。截断只是最后一个手段在此之前应该有一整套分级策略。我的默认做法是保留最近几轮完整对话把更早的内容转成摘要把任务目标、约束条件、关键中间结论放到一个永不裁剪的固定区域。具体拆成三层第一层是固定上下文放系统提示词、任务目标、安全规则这部分始终保留第二层是滑动窗口最近 N 轮对话原样保留保证模型能看到最新的工具结果第三层是历史压缩区超过窗口的内容由 Harness 定期生成摘要并把摘要按旧到新的顺序压缩成一个或几个段落。如果 Agent 需要跨任务记忆Harness 还要接记忆存储。最简单的方案是 SQLite 存键值复杂一点用向量库做语义检索。注意记忆不等于全量历史而是关键事实 引用来源。我踩过的坑是早期把整段对话都塞进向量库结果检索出来的片段上下文割裂反而误导模型。token 预算这件事不能等逼近上限才处理。我习惯在做每一步之前先摸一轮估算当前已用 token、接下来可能一次性投入的工具结果大小、模型输出的预估量。工具返回结果过大的要截断或抽页避免一次就把预算打满。2.4 容错与降级断掉的链条怎么续稳定系统不是不出错而是出错后行为可预测。工具调用失败分两类瞬时错误和永久错误。瞬时错误比如网络超时、模型服务 503这类值得重试但要用指数退避加抖动防止并发风暴永久错误比如参数非法、工具不存在重试多少次都不会成功正确的做法是把错误信息作为观察结果返回给模型让它调整计划。死循环检测是另一个重要模块。模型可能在搜索 A、发现结果不足、再搜索 A之间来回打转。我常用的检测方法是对工具调用序列做特征哈希如果同一个特征在一段时间内出现两次以上就强制打断提醒模型换策略。更严格一点可以引入独立的任务完成度评判步骤让一个专门的评判模型不看执行过程只看最终结果是否满足用户目标。熔断与降级也必不可少。连续失败次数达到阈值时Harness 应该停止让模型继续尝试转而进入降级模式要么把部分完成的成果先交付用户要么直接转人工。生产环境里一个 Agent 卡死可能比 Agent 完全不可用更可怕因为前者会持续消耗资源且不产生价值。2.5 可观测性稳定性的前提是看得见没有可观测性的 Agent无论离线测试多漂亮都不该上线。我要求 Harness 必须输出结构化 trace每条 trace 带唯一请求 ID记录模型请求、工具调用、工具结果摘要、耗时、token 消耗、状态迁移。一个理想的 trace 片段长这样{ request_id: req_8f2a, step: 4, state: EXECUTING, model_call: { prompt_tokens: 4321, completion_tokens: 156, latency_ms: 1800 }, tool_call: { name: search_docs, args: {query: 订单退款流程}, status: success, result_truncated: true, latency_ms: 320 }, decision: continue }日志之外还要有可审计性。运维同学需要知道谁在什么时间让 Agent 执行了什么操作尤其是高危操作。这一步没有太多技巧就是把关键事件回写到一个独立的审计表中不要在排查时才后悔没留。3. 实操搭建一个内网可用的 Harness 服务理论讲完来点能直接落地的。下面这套流程我按社区里常见的开源 Harness 实现来写很多项目包括 DeepSeek Harness 及其同类框架的目录结构和配置字段都大同小异具体路径以你安装的那个版本为准。核心思路比命令更重要。3.1 环境准备与模型服务接入第一步准备环境。我建议用 Linux 服务器Ubuntu 22.04 或 Debian 12 都行Python 版本 3.10 以上。Harness 本身不挑硬件但如果你要在内网跑本地模型需要一块能承载推理的 GPU或者至少规划好 CPU 推理的资源预算。第二步接模型服务。很多 Harness 支持 OpenAI 兼容接口这意味着你可以用任意提供 /v1/chat/completions 接口的服务来替代官方云端模型只要模型本身的对话能力足够。内网环境下通常的做法是在同一台或另一台内网机器上部署模型服务平台把模型权重加载进去然后给出一个 base_url 让 Harness 去连。这里有个容易踩坑的点base_url 到底带不带 /v1。有的 Harness 要求你在配置里写全路径有的会自动补 /v1不一致就会导致 404。我的习惯是先拿 curl 直接打一次接口确认端点可用再填进配置。至于模型怎么加载可以用 vLLM 这类的高性能推理框架也可以用更轻量的 Ollama选型取决于你的模型尺寸和并发需求。对多数内部工具型 Agent几十并发以内vLLM 能扛如果只是个人实验Ollama 更省事。3.2 配置骨架一个最小可用的 Harness 配置很多 Harness 项目核心就是一份 YAML 配置。给你一个最小骨架字段含义写在注释里model: provider: openai_compatible base_url: http://127.0.0.1:8000/v1 # 内网模型服务地址 api_key: local-key # 内网服务一般只要非空即可 model_name: deepseek-chat # 与模型服务平台注册名一致 agent: system_prompt: 你是一个谨慎的助手。只使用已提供的工具不猜测不编造。 max_steps: 20 max_total_tokens: 16000 step_timeout: 60 harness: log_level: info trace_dir: ./traces state_store: sqlite:///./agent_state.db allowed_tools: - search_docs - read_document - write_reportmax_steps 别设太大我一般从 20 起步如果任务确实需要长链路放在 30 到 40 之间再配合死循环检测。max_total_tokens 设太大会让单次任务烧掉大量算力设太小又会导致模型还没干完就被截断需要根据你的任务复杂度做几次实验。state_store 用 SQLite 就够了它的作用是持久化任务执行状态这样 Harness 进程重启后可以从中断处恢复。这在长任务场景里非常重要否则一次服务重启等于任务白跑。3.3 技能Skill与插件机制的使用Skill 是 Harness 里比较有特色的概念。简单说技能是一组针对特定任务的工具编排 提示词片段比如写综述技能会按顺序调用搜索、文档读取、大纲生成、报告写入等工具。相比让模型自由发挥技能把常见工作流固化下来减少模型每一步的决策负担稳定性自然上来了。一个典型的技能目录结构长这样skills/ research/ SKILL.md tools/ search.py read_doc.py write_file.pySKILL.md 里写清楚这个技能适合什么场景、调用哪个工具、需要注意什么边界。Harness 在启动时扫描 skills 目录把每个技能展开成工具定义和提示词片段合并进模型可用的工具列表。插件的机制类似但更偏技术插件是一个可以被 Harness 动态加载的 Python 模块通常需要实现一个入口比如名为 activate 的初始化函数返回工具列表或技能列表。插件化让团队可以把新工具独立开发和测试不用改动 Harness 主进程。我在实际中特别提醒一点技能和插件也要做版本管理。不要把它们当作写在服务器上的零散脚本否则某天某个人改了搜索逻辑全部 Agent 都跟着变排查时根本不知道哪个版本在跑。3.4 启动、验证与日志观察配置写好后启动一般就是一条命令。以典型的 Harness CLI 为例harness run --config config.yaml首次启动先别急着上复杂任务我建议按三个级别验证。第一级启动日志中确认插件全部 activate 成功、模型端点连通、技能目录加载完成。第二级跑一个最简单的任务比如读取 docs/ 目录下的文件清单确认 Harness 输出 trace 和最终报告。第三级跑一个需要多步工具调用的任务比如基于 docs 目录写一份项目周报此时重点观察步骤数、token 消耗、是否有循环。日志观察的重点有三个地方模型请求响应耗时、工具调用成功率、上下文压缩触发频率。如果在任务执行中途看到频繁的上下文压缩说明这个任务超过了默认预算要么拆分任务要么调大 max_total_tokens要么优化工具的返回结果长度。4. 常见问题与排查实录从日志到根因做 Agent 运维半年我攒了一堆线上问题。这里按出现频率从高到低挑几个典型场景给出症状、原因和处理方式。4.1 死循环与停不下来症状是日志中出现大量重复的工具调用序列比如每隔几个 step 就搜一次同名关键词或在一个失败工具上反复重试。根因有两个一是模型对是否完成的判断不稳定二是 Harness 缺少循环检测。处理方式分三层。第一层设置 max_steps 硬上限保证资源不失控第二层在 Harness 里加重复模式检测连续出现相似调用就中断并提示模型更换策略第三层如果业务允许加一个独立的完成度评判模型让主模型只负责干活评判模型负责喊停。评判模型可以很小关键是它的视角独立不受执行过程中沉没成本影响。4.2 上下文爆炸与 token 耗尽症状是任务跑到后半段响应越来越慢输出质量肉眼可见地下降日志中 token 数逼近上限。根因通常是每轮工具结果都原封不动地塞回上下文没有做摘要和裁剪。处理方式检查工具的返回结果大小对长文档做分页或摘要打开 Harness 的上下文压缩功能确认它会在预设阈值处自动触发如果任务本身需要大量资料不要一次性全塞给模型改成检索到什么就用什么。我见过的最极端案例是一次任务里工具返回了整本 PDF 的文本直接把 32K 窗口吃完。抽象地说这不是模型问题是数据管线问题。4.3 工具调用失败与 JSON 解析崩溃症状是模型输出偶尔出现非法 JSON或者参数校验不过Harness 报 parse error。出现频率不高但一旦出现就是整轮中断。处理方式分两步。第一步Harness 层要做容错解析从模型输出中先提取最外层 JSON 块如果解析失败做轻量修复补齐花括号、去注释、去掉多余尾字符修复后重新校验。第二步如果某类工具频繁被模型传错参数调大工具描述中的参数说明或把参数名改得更直白。注意这里不要无限重试我一般限定 2 到 3 次再失败就结束本轮把错误信息返回给上层。4.4 插件加载与文件权限问题社区里常有人报错插件启动失败日志里有类似 failed to load plugins: 1 entry did not activate 的信息。这种问题绝大多数是插件的 activate 入口没被正确发现要么入口函数名和 Harness 要求的不一致要么目录路径没写对要么插件依赖的第三方库没安装。排查思路是先看日志中列出的具体插件名再单独 import 该模块直接在 Python 里调用它的入口函数绕过 Harness 快速定位。另一类是文件权限问题。在 Windows 环境下技能模块读取文件时报 SetNamedSecurityInfoW failed 这类权限错误本质是当前进程对目标目录或文件没有足够权限ACL 配置不对。我的建议是不要让 Harness 以管理员权限运行来绕过问题那样等于把所有技能都提升成了高危权限。正确做法是给运行 Harness 的账号授予技能目录的最小所需权限普通读目录用只读权限就够了。4.5 内网部署的模型接入问题内网场景最常见的报错是连接被拒、401、404。连接被拒多半是模型服务没启动或端口不对401 是 API key 不匹配404 最常见base_url 少写了 /v1 或模型名注册得不对。排查顺序建议先 curl 模型服务确认裸接口可用再检查 Harness 配置里的 base_url 和 model_name 是否和 curl 时一致最后看 Harness 日志里真正发出的请求 URL很多框架会在日志里打印出它拼出来的完整地址一眼就能看出哪里有差异。还有一个容易忽视的是并发限制内网模型服务默认并发开得很小Agent 多步循环同时发出多个请求时容易被限流返回 429。此时要么调大服务端并发要么在 Harness 侧加请求排队。5. 实践心得把稳字焊进默认设计最后聊一点更主观的东西是我在多个项目里攒下来的经验判断。我越来越觉得Agent 稳定性不是可以在上线前测试出来的而是由设计之初的边界决定的。你允许模型做什么、不允许做什么、最多做多少步、失败了怎么退这些写在 Harness 配置里的东西才是稳定性的真正来源。比如一个没有白名单的 Agent无论测试跑得多好我都不会让它碰生产数据而一个每步都记录 trace 的 Agent哪怕能力弱一点出了问题也能在十分钟内定位这本身就是稳定。我的另一个心得是调提示词不如调机制。以前 Agent 表现不好我下意识就去改系统提示词加一堆请谨慎请确保不要乱做之类的废话效果有限。后来换成在 Harness 层面加工具校验、加步骤上限、加完成度评判稳定性提升反而立竿见影。提示词只能影响模型的表达倾向机制才能约束模型的行为边界。还有一个实操技巧值得分享每次给 Agent 新增一个工具先在 Harness 外面把工具函数单独测通再注册进去。我见过太多浪费在到底是 Agent 调错了还是工具本身坏了上的排查时间。先保证每个零件是好的再谈组装出来的机器稳不稳。AI Agent 的下一站大概率不是更聪明的模型而是更靠谱的马具。模型负责想象力Harness 负责把想象力变成可交付的结果。希望这篇文章能帮你少踩几个坑把 Agent 从能跑带到敢上线的那一边。