开头不用太多铺垫,直接进主题。我最近在做一整套 Agent 基础设施的工程化改造,涉及从 Agent 循环到上下文管理、代码沙箱、结果验证、长时间运行任务调度,踩了不少坑,也沉淀了一些设计取舍。这篇文章就基于这个标题展开,聊清楚一个“正经”的 Agent 系统,到底该由哪些核心环节组成,每个环节卡住你的问题通常是什么,以及我实际是怎么解决的。适合已经能跑通 Demo、准备把 Agent 推到真实业务的开发者参考。
1. Harness 的定位:Agent 不是跑模型,而是跑一套工程系统
1.1 从“写提示词”到“搭工程”的转变
早期做 Agent 应用,大家容易陷入一个误区:把精力全部放在提示词上,觉得只要 prompt 写得好,模型就能稳定输出。但真实场景里,任何超过五个步骤的 Agent 自动化任务,都会暴露出系统层面的问题——模型回复错了怎么办、工具调用超时怎么办、上一步的结果塞不塞得进上下文、代码执行会不会污染宿主机、跑了两小时的任务断了要不要从头再来。
这些问题的共性在于:它们是工程问题,不是模型问题。模型能力只是这个系统里最小的一块积木。围绕模型搭起来的“脚手架”,才是真正决定 Agent 能不能落地的关键,这个脚手架就是 Harness 工程。
你可以把 Harness 理解成给 Agent 穿的“全套装备”:它负责给 Agent 提供行动能力(工具)、记忆(上下文)、安全边界(沙箱)、质量保障(验证)、以及耐力(长时间运行支持)。模型本身只负责“想”,怎么“做”、做得对不对、做不完怎么办,全部由 Harness 兜底。
1.2 一套 Harness 需要承担的职责清单
我梳理了自己在实际项目里用到 Harness 的职责边界,一共五个层次:
| 层次 | 职责 | 对应模块 |
|---|---|---|
| 行动层 | 让 Agent 能调用外部工具、执行代码、读写文件 | 工具注册中心、代码解释器 |
| 记忆层 | 管理对话历史、中间结果、长期知识 | 上下文窗口管理器、向量存储 |
| 安全层 | 隔离代码执行环境,防止恶意或错误操作 | 沙箱、网络白名单、资源配额 |
| 质量层 | 检查 Agent 每一步/最终输出是否合格 | 验证器、回归测试集 |
| 持久层 | 让任务跨进程存活,可暂停、可恢复 | 任务队列、快照、心跳机制 |
这五个层次不是各自孤立的。实际运行过程中,上下文管理会直接影响沙箱里的执行结果,验证器又会决定要不要把新结果写回上下文。所以 Harness 更像是一个“循环系统”,核心不是某一个模块,而是模块之间的协作链路。
2. Agent 循环:推理、行动与观察的节奏控制
2.1 循环的基本结构:推理 → 行动 → 观察
所有 Agent 系统的内核,本质上都是一个循环:模型根据当前状态做出决策,然后执行一个动作,再把动作的结果拿回来作为下一步决策的依据。这个循环拆开就是三件事:
- 推理(Reasoning):模型基于现有信息决定下一步干什么。这个阶段通常是一次模型调用,输入是系统提示词加全部上下文,输出是“下一步动作”的描述。
- 行动(Action):把模型输出的动作意图翻译成真实操作。比如模型说“我需要读取 config.yaml”,Harness 就调用文件读取工具;模型说“我要执行这段 Python”,Harness 就把代码扔进沙箱。
- 观察(Observation):把行动结果整理成模型能理解的文本,比如成功还是失败、返回了什么数据、报了什么错,然后追加进上下文,进入下一轮循环。
看起来很简单,工程化的坑全藏在“翻译”和“整理”这两个过程里。模型输出的动作描述往往是不规范的,有时候命令写错,有时候参数漏了,有时候干脆编造一个不存在的工具。Harness 在行动阶段必须做一层强校验,不能直接拿模型说的话去执行——宁可让这次循环失败,也不能让错误动作产生副作用。
2.2 循环里的状态机与中断处理
真实的 Agent 任务不会是线性的,它会分叉、会失败、会需要用户介入。所以我习惯把循环内部设计成一个小型状态机,而不是单纯的 while 循环。
我常定义的状态包括:
运行中:正常执行循环,模型每次可以提出一个新动作。等待用户确认:动作涉及敏感操作(删除文件、修改线上数据、执行高权限命令),先暂停循环,等人工确认再继续。等待外部事件:Agent 已经发出请求(比如等待一个异步任务完成),此时不能空转,要挂起循环。失败重试:动作执行失败,把错误信息反馈给模型,让它修正方案,限定重试次数。终止完成:模型判断任务已解决,或者到达最大循环次数,结束任务。
这个状态机带来的最大好处是循环可以被安全地打断和恢复。特别是等待外部事件的场景,如果没有状态机,Agent 一般会傻乎乎地轮询,白白消耗模型调用次数和费用。设计成挂起后,可以让出控制权,等外部回调再唤醒循环。
这里有个我踩过的坑:模型在“等待用户确认”状态下,如果长时间没人处理,上下文里的“未决问题”会一直占着窗口,还会干扰后续循环。后来我加了超时机制——超过设定时间未确认,默认按取消处理,并在上下文中明确追加“该操作因超时未获批准,已被取消”,模型就能干净地切换下一步策略。
2.3 循环的“呼吸感”与频率控制
模型调用是有成本的,循环越快烧钱越快。真实项目里要控制循环的“呼吸感”——该停顿的时候停顿,该重试的时候控制退避节奏。
我常用的频率控制策略:
- 连续失败时,采用指数退避,第一次失败等 1 秒,第二次 2 秒,最多等 30 秒。
- 涉及外部 API 调用时,强制加入最小间隔,防止短时间高频请求触发对方限流。
- 每轮循环结束前,让模型做一次“是否需要继续”的轻量判断,能用一次调用的机会直接避免后续十次无效调用。
提示:控制循环节奏不是为了快,而是为了稳。很多线上事故不是因为模型太笨,而是因为循环跑得太疯,把下游系统打爆了。
3. 上下文管理:Agent 的记忆与注意力
3.1 上下文窗口的物理限制是逃不掉的
不管模型宣称支持多长的上下文,实际用起来都会面临三个现实约束:
- 费用:上下文越长,单次模型调用越贵,长任务跑几十轮叠加下来费用非常可观。
- 性能:超长上下文的处理延迟显著增加,用户体验直线下降。
- 注意力稀释:中间塞进去的无关信息越多,模型对关键信息的关注度越低。这个在做多轮 agentic 任务时尤其致命——早期的工具输出可能淹没在海量日志里,模型后期根本找不到关键数据。
所以我不建议把“全程无删减的完整历史”一直带在上下文里。正确做法是:分层管理。
3.2 上下文分层:核心区、工作区、归档区
我把上下文管理划分为三个层级:
- 核心区:任务目标、约束条件、关键中间结论。这部分信息必须始终完整保留,是模型决策的锚点。
- 工作区:最近几轮的工具输出、观察结果、临时数据。保留最近 N 轮的内容,超过就压缩或归档。
- 归档区:早期历史、大段日志、重复性信息。这部分不直接塞进上下文,而是摘要化或存入外部存储,等模型主动检索。
归档区我用的是外部检索的方案,给每一轮观察结果自动做个摘要,存进带时间戳的存储里。模型需要回顾某个早期结果时,不是直接翻上下文,而是通过一个“检索归档记录”的工具去查。这样既保留了可追溯性,又不占上下文窗口。
这个方案在实践里有一段时间效果不稳定——摘要的质量直接决定了归档区有没有用。如果摘要太粗,模型查回去发现找不到细节,会反复尝试查询浪费轮次。后来我要求摘要必须保留“数据值、来源文件、时间戳、结论状态”四要素,查询准确率才稳定上去。
3.3 证据链与可追溯性
上下文管理不光是“塞进去”的问题,还有“拿出来说清楚”的问题。Agent 执行完一个任务,用户问“这个结论是怎么来的”,如果 Harness 答不上来,这个 Agent 就不具备可解释性。
我在设计时要求每一条写到归档区的信息,都附带完整的来源路径:来自哪个工具、在哪个沙箱环境执行、对应哪次会话、当时的原始输出片段是什么。这样无论任务跑多久,都能从最终结论反查到每一步的证据。
具体实现方式是在工具调用层加一层“记录拦截器”,每次工具输入输出都自动生成结构化记录,包含:调用 ID、时间、工具名、参数摘要、输出摘要、关联状态。这些记录双写——一份进归档区,一份进审计日志。归档区服务模型记忆,审计日志服务人工排查。
4. 沙箱:把风险关进笼子
4.1 为什么要沙箱:模型会犯各种离谱的错误
Agent 一旦能执行代码,风险等级就完全不一样了。模型生成的代码在常规场景里能跑通,但在边界情况下经常出现危险操作:删除系统目录、修改权限配置、读取敏感文件、拉取依赖时执行恶意脚本。最经典的场景是,模型只是想让用户“清理临时文件”,结果生成了rm -rf /tmp/*,但/tmp路径解析在沙箱内外不一致,直接删到了宿主机目录。这类事故我不止一次听过。
沙箱的核心目标不是防“恶意模型”,而是防“不可控行为”。模型本身没有恶意,但它可能误解用户意图,可能产生幻觉,可能被间接提示注入引导执行危险操作。把执行环境彻底隔离,是 Harness 最基础也最重要的安全底线。
4.2 沙箱的分层设计思路
我实践的沙箱方案分三层:
- 进程层隔离:代码在独立的用户空间运行,没有宿主目录的写权限,掉落的临时文件全部重定向到临时目录。
- 网络层隔离:默认关闭所有对外网络访问,需要联网的操作走代理网关,网关注册白名单域名列表,其他请求一律拒绝。
- 系统层隔离:对关键系统调用做拦截,比如删除操作必须经过虚拟文件系统层,而不是直接映射真实目录。
只做进程层隔离,挡不住网络层面的风险;只做网络层隔离,挡不住文件层面的破坏。三层叠加才能把事故面控制到最小。实际项目里,网络层隔离的优先级比很多人想的要高——模型拉取依赖、调用 API 是高频需求,完全禁网不现实,但放出去的网络请求如果不可控,等于给外部威胁开了门。
4.3 网络白名单与资源配额
网络白名单的维护是沙箱里最繁琐但最重要的部分。我维护了一个域名白名单配置,比如允许访问公共包仓库、官方 API 域名、内部服务域名,其余域名全部拦截。配置要支持按任务级别覆盖——有些任务确实需要访问特殊域名,就单独放开,但必须在任务配置里显式声明,事后可审计。
资源配额要设四个限制:
- CPU 时间上限:防止死循环代码吃满 CPU。
- 内存上限:防止模型生成的代码一次性申请超大内存搞崩系统。
- 执行超时:单段代码执行超过 30 秒直接强制终止。
- 临时文件大小:防止 Agent 反复写入垃圾文件占满磁盘。
注意:沙箱不是防火墙,它防的是“内部不可控行为”,而不是“外部攻击”。如果任务需要处理不可信的外部输入,沙箱外面还要再加一层独立的隔离环境。分层的意义在于每一层都能独立失效而不导致整体失守。
5. 验证体系:Agent 的质检闸门
5.1 验证什么:从最终输出到中间步骤
很多 Agent 项目只验证最终输出,中间步骤完全不设防。这是很大的浪费——中间步骤如果错了,最终输出大概率是错的,等跑完才发现问题,修复成本高得多。
我把验证分成两级:
- 中间步骤级验证:循环每执行完一个动作,立刻验证观察结果是否符合预期。比如模型生成了文件,就检查文件是否存在、格式是否正确、内容是否符合约束。不符合就立刻把验证失败信息反馈给模型,让它修正。
- 最终结果级验证:任务完成后,对交付物做整体验收。比如数据处理任务,要验证输出行数是否符合预期、关键字段是否齐全、是否包含异常值。这一步相当于质检闸门,不通过就不允许进入下一步发布流程。
中间级验证的收益非常大。有一次做数据清洗任务,模型在早期把日期格式全部解析错了,如果等到最后才发现,整批数据都得重来。加了中间验证之后,第一轮清洗结束就检测到格式异常,直接触发模型调整解析逻辑,后面所有数据都干净了,整个任务耗时反而缩短了 40%。
5.2 验证器怎么设计:规则优先,模型兜底
验证器我不建议一上来就全用模型判官,成本高且不稳定。我的习惯是“三层验证器栈”:
第一层是规则验证器:用确定性代码检查硬性指标。文件存在性、行数、大小、格式、必填字段、数值范围、正则匹配,这些能写死的全部写死,执行快、结果可靠、零成本。
第二层是结构验证器:检查数据结构和语义约束。比如 JSON Schema 校验、数据库约束校验、接口响应格式校验。
第三层才是模型验证器:用模型判断“软性”指标。比如生成的文章是否贴合主题、代码是否清晰可维护、结论是否逻辑自洽。这一类判断没有确定性标准,适合模型兜底。
我强烈建议把前两层做厚,第三层做薄。能用规则解决的就不要用模型,这是控制成本、稳定性的核心原则。有一次我统计过,规则验证器拦截的问题占到了 82%,只有剩下 18% 需要模型判官介入。
5.3 验证失败后的纠正循环
验证的意义不只是“挑错”,更重要的是“纠错”。验证失败后,Harness 要把失败原因转化为模型可理解的反馈,然后重新进入 Agent 循环,这个过程我称为“自纠循环”。
这里的关键点是反馈信息的质量。直接告诉模型“验证失败”几乎没用,必须告诉它失败的具体细节。比如:
- 期望读到 100 行数据,实际读到 87 行,缺失部分在哪几行。
- JSON 解析报错,报错位置在第 3 行第 14 个字符,原因是多了一个逗号。
- 输出文件命名不符合规范,预期格式是
report_YYYYMMDD.csv,实际是Report_v1.txt。
反馈信息越具体,模型的自纠成功率越高。同时要限制自纠循环的次数,我默认最多 3 次,超过后状态机切到等待用户介入,避免模型无限重试烧钱。
6. 长时运行:从分钟级到小时级的工程化改造
6.1 长时任务面临的三大挑战
Agent 任务只要跑超过几分钟,就会面临三个新问题:
第一是进程不稳定。跑着跑着机器重启了、进程被 kill 了、容器被调度走了,任务就断了。如果没有恢复机制,几十分钟的工作全部白费。
第二是状态丢失。Agent 循环里的上下文、工具结果、沙箱环境状态都存在内存里,进程一死全没了。重启之后如果只能从头开始,大概率第二次还会挂在同样的地方。
第三是资源膨胀。长任务会在上下文、临时文件、日志层面不断累积数据。不控制的话,跑两个小时的任务,临时目录能堆几个 G,上下文窗口早就被塞满,模型开始“失忆”。
6.2 快照、恢复与心跳机制
我给长时任务设计了一个“快照 + 恢复 + 心跳”三位一体的方案。
快照的粒度是关键。不做太细,那样磁盘开销太大;不做太粗,恢复时要重跑太多步骤。我采用“每完成一个核心阶段打一次快照”的策略。快照内容包含:当前状态机状态、上下文摘要、已完成步骤列表、临时结果的持久化路径。快照存在独立的持久层,和运行进程解耦。
恢复流程就是根据快照重建状态机,把上下文摘要重新灌进工作区,把临时结果路径挂回沙箱,然后从断点继续循环,而不是重头开始。这套方案实测下来,对于跑 1 小时以上的任务,恢复后的续跑成功率接近九成——剩下的失败主要原因是外部依赖状态变化了,比如第三方 API 协议变更、依赖包版本更新,这些属于不可控因素。
心跳机制是防止“假死”。任务进程可能在正常运行,但循环卡在某个外部调用上迟迟没有返回,从外面看就是任务挂了。实现方式是每个循环周期更新一次心跳时间戳,监控模块定期检查。心跳超时就触发进程重启或人工介入检查。
6.3 成本控制与预算熔断
长时任务最容易被忽视的问题是真金白银的消耗。模型调用费用、虚拟化资源费用、外部 API 费用,跑两小时可能顶得上短任务的几十倍。所以 Harness 必须内置预算控制。
我的方案是两层:
- 可用预算设定:每个任务创建时,显式设定预算上限。上限可以按费用计算,也可以按模型调用次数计算。
- 熔断机制:循环每次执行前,先计算当前累计成本,如果超过预算的 80%,提示模型需要简化后续步骤;超过 95%,进入“只读模式”,只允许做验证和汇报,不允许再发起新动作;达到 100%,直接熔断,终止任务并通知管理员。
熔断设计要放在循环的最前面,而不是等一次模型调用完成后再查消耗,否则一次高消耗调用就能把预算打穿。有一次跑爬虫任务,模型连续发起了大量网页请求,如果没有熔断,预算预计 30 分钟就烧完了。熔断触发后,任务转入只读模式,我人工介入检查,发现是模型对网页解析失败后陷入了错误的重复抓取循环。
7. 我在实际落地中的几点体会
Harness 工程这条路走了大半年,最大的体会是:Agent 系统的稳定性不是靠模型聪明,而是靠工程兜底。模型早晚会犯错,没有验证机制的错误会被放大成任务失败,有了验证机制的错误就是一次普通的循环迭代。一个设计良好的循环,应该允许模型犯十次错,但每次犯错都能快速发现、快速纠正、控制成本,最后仍然能稳定交付结果。
上下文管理是最容易被低估的一环。很多项目初期跑 Demo 很顺,一上长任务就崩,十有八九是上下文管理没做好——早期工具输出把窗口塞满了,模型后期注意力完全跑偏。建议所有准备做长任务的项目,提前把归档和摘要机制设计进去,不要等出问题再补。
沙箱和验证是两把保护伞,但保护方向不同。沙箱防的是“动作有危害”,验证防的是“结果不正确”。一个管过程安全,一个管结果质量,缺任何一个都不完整。
我最后的建议是:不要一上来就追求全自动的长时无人值守。哪怕 Harness 设计得再完善,也要保留“关键节点人工确认”的机制。宁可牺牲一点自动化率,也要保住对系统的控制力。等循环、验证、恢复这套机制跑熟了,再逐步放开人工介入环节,一步步把无人化比例提上去。这样打磨出来的 Harness,才能真正扛住真实业务的压力。