ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Agent工程底盘:Harness如何决定AI智能体的稳定性与护城河

Agent工程底盘:Harness如何决定AI智能体的稳定性与护城河

过去三个月,我和团队被同一个问题反复折腾:Agent在demo里很聪明,一上真实任务就掉链子。丢上下文、工具超时、进程崩溃、API抖动,任何一个意外都能让一个运行了二十分钟的任务一夜回到解放前。于是我把目光从模型排名榜移开,开始认真研究另一个东西——Harness。看完Anthropic在长时任务上的设计,又研究了一圈Google AX的声明式调度,我越来越确信:Agent的下一场竞争,不在模型智商,而在工程底盘。这篇文章聊聊我对Harness工程的理解,以及一路踩过来的一些坑。

1. Harness 是 Agent 的“底盘”不是“外壳”:先搞清楚我们在讨论什么

1.1 从模型到 Agent 的跨度:为什么单纯堆上下文不够

模型本身的任务很简单:接收一段文本,生成一段文本。但Agent任务完全不是一回事。你让一个Agent去调研竞品、查数据库、生成图表、发到内部系统,整个过程持续两三个小时,期间要调用几十次工具,这个任务就算“跑起来了”。

关键差异就在这里:模型对话是短连接,Agent任务却是长链路。每一次模型输出“我想调用工具A”,你的系统就得真的去执行工具A,把结果喂回去,它再决定下一步。这几十步链路里,任何一环出错——工具超时、进程被杀、API返回429、上下文塞满——整个任务就可能报废。

有人觉得上下文窗口大了就能解决问题,把1M token全塞进去,结果延迟和成本双双爆炸。而且工具返回的结果往往又长又糙,如果只是无脑堆积在上下文里,Agent很快就会“迷失重点”,开始胡言乱语。单纯堆上下文,只是在用算力掩盖架构上的懒惰。Agent需要的是一个能替它管理上下文、执行工具、保存进度、处理异常的工程层,这个层就是Harness。

1.2 三个层次:连接、状态、治理

我对Harness的理解,不是某个具体框架或某件工具,而是模型核心之外的那整套工程底座。它大致分三层。

第一层是连接与执行层。负责模型API的接入、密钥管理、多模型路由、工具注册,以及沙箱内的实际执行。这一层回答的问题是:模型说要调用工具,谁来跑?跑在哪?权限是什么?

第二层是状态与恢复层。负责会话状态持久化、checkpoint、断点续跑、上下文摘要。这一层回答的问题是:任务跑到一半挂了,还能不能从最近的位置接着跑?

第三层是治理与观测层。负责工具白名单、敏感操作审批、成本预算、审计日志、任务轨迹回放。这一层回答的问题是:这个Agent到底干了什么?有没有越权?花了多少钱?

这三层合在一起,才是Agent真正可交付、可运维、敢放到业务环境里去跑的形态。只调模型API,那不叫Agent工程,那叫脚本。

1.3 Agent框架和Harness不是一回事

现在市面上有很多Agent框架,LangGraph、Semantic Kernel,甚至一些国产的编排框架。很多人会把框架当成Harness,这是个误区。

Agent框架解决的是“任务怎么定义”的问题,比如节点、边、状态图、流转逻辑。而Harness解决的是“任务挂了怎么办、工具怎么安全执行、状态怎么保存”的运行时问题。框架是代码层的东西,Harness是运行时底座。你可以用了很高级的框架,却完全没有Harness;也可以自己手写一个很朴素的Harness,不依赖任何框架。

我用一个表格来对比这两者的关注点:

维度Agent框架Harness工程
核心问题任务流程怎么编排任务怎么稳定可靠地跑完
关注点图结构、节点、数据流沙箱、checkpoint、重试、审计
失败时表现告诉你任务失败了自动恢复、降级、可排查
主要资产可复用的代码逻辑运行数据、恢复机制、治理策略

护城河恰恰在后者。因为框架是开源的可选替代品,而Harness是你自己的工程实践,指的是你踩过多少坑、沉淀了多少恢复逻辑、积累了多少真实的运行轨迹。

2. Anthropic 长时任务设计里最值得抄的三个作业

2.1 长时任务不是把超时调大:中断恢复才是核心

Anthropic近两年的产品迭代里有一个明显趋势:把Claude从短对话助手推向长时任务执行者。Claude Code的会话恢复、云端任务的断点续跑,都在解决同一个问题——让Agent任务可以运行几十分钟甚至几小时而不中途夭折。

我见过很多团队面对长时任务时的第一反应:把HTTP超时从30秒调到300秒,把重试次数从3次改成10次。看起来是“为长时间运行做了优化”,实际上只是让失败变得更昂贵。超时调大后,每次失败都要等更久;重试调多后,同一个错误会砸好几遍,白白烧token。

真正长时任务的设计哲学是“随时可中断、中断可恢复”。我常拿电梯做类比:短任务像电梯正常上行,长任务设计则要求电梯即使停电,也得有应急电源和手动盘车,把人安全放到最近楼层,而不是让人困在轿厢里等救援。

具体到Agent,这意味着三个方面:每个工具调用都要被记录在案、每个关键步骤都要有检查点、每个动作都要有幂等设计。只有这三件事做好了,任务中断才真正“不可怕”。

2.2 Agent需要持久化什么:四个必须保存的东西

设计checkpoint的时候,最容易犯的错是“过度持久化”——把整个上下文原封不动存下来,恢复时候全文加载。这样既慢又贵,而且上下文里塞满了中间过程的无用信息。

按我这段时间的实践,Agent真正需要持久化的东西只有四类。

第一类是对话历史的摘要加关键原文。完整历史不可能长期留着,要在每轮做完后用模型做一次摘要压缩,同时保留最近几轮的原文,保证Agent的短期记忆是准确的。

第二类是执行轨迹。也就是它调用过哪些工具、传了什么参数、返回了什么结果、每个动作的时间戳。这既是恢复的依据,也是事后审计的线索。

第三类是中间产物。Agent生成的代码、处理过的临时文件、计算结果缓存,这些要落到对象存储或者本地目录,并且按任务ID组织,不能被其他任务污染。

第四类是执行位置。当前Agent推进到任务的第几个节点、子任务栈是什么、还有哪些待办。

这四类状态不一定都要放数据库。小任务一个JSON文件就够,中等并发用Redis,大规模、长链路再用对象存储加向量库。核心原则是分层:热状态在内存,温状态在Redis,冷状态在对象存储。

2.3 工具调用治理:从 Claude Code 的实战思路说起

社区里流传的那本《Claude Code实战 Harness工程之道》,很多人只把它当工具教程看,我却觉得里面最有价值的部分是工具治理思路。Claude Code本质上是一个CLI形式的Agent,而它自带的那套执行机制,就是一套Harness参考实现。

它做到了几件事:第一,工具注册表是显式的,它知道自己能用Read、Edit、Bash等哪几个工具;第二,敏感操作有审批,很多写操作默认要人工确认;第三,执行在本地沙箱里,不是让模型直接裸跑命令;第四,会话支持resume,断线后能恢复。

我从里面提炼出一个工具调用的四原则:

  • 工具要有清晰的Schema,输入输出明确,模型才不会乱传参数。
  • 调用要有超时与重试策略,但重试要区分错误类型,4xx别傻重试,5xx才值得重试。
  • 返回结果要校验,工具返回异常时,要有机制让Agent感知并纠正,而不是硬着头皮继续。
  • 写操作、删除操作、高权限操作必须有审批流,不能把所有工具都设成自动放行。

这一点我是真吃过亏的。之前让Agent自动跑SQL,它为了凑一张报表里的某个数字,顺手把一个测试订单记录删掉了。事后我加了一道审批流,所有写操作必须人工确认才允许执行。从那以后,凡是涉及删除、写入、转账、发布的工具,我一律默认加审批,不商量。

3. Google AX 的声明式调度:把 Agent 的决定权上收

3.1 命令式循环的问题:谁来决定下一步

如果不用Harness,不用调度器,自己裸写一个Agent,通常会长这个样子:

while not task_completed: action = model.decide(state) result = execute(action) state.update(result)

这个命令式循环看起来很简洁,但放到真实业务里,你会立刻发现几个致命问题。

第一,没有控制点。模型说下一步要执行什么,系统就只能跟着执行,开发者和运维者完全插不上手。想在某一步加个审批?你只能改模型提示词,让它“尽量”在危险操作前停下来——这根本不叫控制。

第二,没有恢复点。任务跑到第18步崩溃了,整个循环从头再来,前面17步的工具调用全部白做。你甚至说不清楚它到底跑到哪一步了。

第三,没有预算限额。Agent可能在一个错误分支上反复打转,token烧完才发现路径根本不对。命令式循环对这种浪费没有约束力。

命令式循环适合demo,不适合生产。生产环境需要的是:把“下一步做什么”的决定权从模型手里部分上收,交给一个更可控、更可观测的执行体系。

3.2 声明式任务怎么描述:一个调度配置长什么样

Google AX这类声明式调度体系,我不好说它的每个内部实现细节,但从它对外传达的“声明式调度”思路来看,核心做法是一致且有借鉴价值的:把任务依赖、重试、超时、并发、审批要求全部写成配置,调度器负责按配置执行,而不是让Agent自己在每一轮自由发挥。

一个声明式任务配置大概长这样:

tasks: - id: research tool: web_search timeout: 30s retry: max_retries: 3 backoff: exponential depends_on: [] output: research_summary - id: summarize tool: llm_call timeout: 60s max_retries: 2 depends_on: - research input: research_summary - id: report tool: generate_doc depends_on: - summarize requires_approval: true

声明式的核心好处是:任务之间的依赖关系是显式的,执行顺序和重试策略是提前声明的,单步失败后调度器可以在节点粒度重试,而不是整个任务重来。

你可以明显感受到这和Kubernetes处理容器是一脉相承的思路。容器不关心自己被调度到哪台机器,K8s根据声明状态不断调谐。Agent任务也一样——它不该自己决定全局调度,它应该被一个控制面管理,它只负责在自己被分配到的那个节点范围内干活。

这是工程上的一次权力上收。模型仍然决定“这一步怎么做”,但“整个任务怎么组织、哪步能并行、哪步要审批、失败了怎么办”由声明式调度层说了算。

3.3 Harness做运行时,声明式调度做控制面

如果你问我是该先搞Harness,还是先搞声明式调度,我的答案是:它俩是搭档,不是二选一。

Harness管的是“怎么跑”,负责沙箱、工具执行、状态持久化、安全。声明式调度管的是“跑什么、什么时候跑、失败了怎么办”,负责依赖编排、重试策略、并发控制、预算。两者角色完全不同。

层次Harness(运行时)声明式调度(控制面)
管什么工具执行、沙箱、状态任务依赖、重试、并发、预算
回答的问题这一步怎么执行整个任务怎么组织
失败时提供恢复状态决定是否重试、降级
关键指标单步成功率、恢复耗时任务完成率、SLA达成率

这套组合拳,才是Agent工程新范式的完整形态。先让单步执行可靠,再用调度层把整个任务组织可靠。缺了任何一环,长时任务都跑不稳。

4. 从零搭一套带 Harness 的 Agent:四层架构与真实接线

4.1 最小可用 Harness 的四层骨架

我在团队内部搭过一版最小可用的Harness,当时的原则是:别一上来就上K8s、上向量库、上微服务,先跑通,再加复杂度。那版Harness一共四层。

连接层:做模型API适配,统一成OpenAI兼容的Chat Completions接口格式,同时处理多密钥轮换、429限流重试、按任务类型做模型路由。任务简单就上便宜模型,任务难就切贵模型。

执行层:所有工具调用不进宿主进程,统一丢到Docker沙箱里执行。内置工具包括shell命令、文件读写、代码解释器、HTTP请求。每个工具都有明确的超时时间,超过就直接砍掉,把超时错误返回给模型。

状态层:checkpoint写入Redis,以session_id加步骤号作为键。每次工具调用完成,就把最新消息序列和摘要写进去。恢复的时候读取最近的checkpoint,从那里继续。

治理层:工具白名单、单任务成本上限、敏感操作审批、完整审计日志。审计日志记录每一次工具调用的输入输出、耗时、费用,方便事后排查。

这个四层骨架很朴素,但足够支撑大多数真实业务场景。很多Agent项目死就死在第一版就想做全套,最后连demo都跑不通。

4.2 把 Claude、DeepSeek 这类模型接进 Harness 的接线方式

很多人以为Harness绑死了某个模型,其实不是。市面上主流模型API大多兼容OpenAI的Chat Completions格式,虽然Anthropic原生API格式和OpenAI不完全一致,但通过网关适配层可以统一成OpenAI兼容协议。社区里那个DSH(DeepSeek Harness)工作流,本质上就是把DeepSeek接进Harness的一套实践。

接线方式其实不复杂。第一步:注册工具清单,每个工具都写清楚JSON Schema,描述参数、返回值、用途。第二步:组装系统提示词,把任务目标和工具列表给到模型。第三步:进入循环,模型返回tool_calls时,Harness去执行对应工具,把结果回填消息列表。第四步:每步执行完就写checkpoint。

核心代码骨架大概是这个意思:

import json class MinimalHarness: def __init__(self, model_client, model_name): self.client = model_client self.model_name = model_name self.tools = {} def register(self, name, schema, fn): self.tools[name] = {"schema": schema, "fn": fn} def _tool_defs(self): return [{"type": "function", "function": t["schema"]} for t in self.tools.values()] def run(self, session_id, task): messages = [{"role": "system", "content": task}] while True: resp = self.client.chat.completions.create( model=self.model_name, messages=messages, tools=self._tool_defs(), ) msg = resp.choices[0].message messages.append(msg) if not msg.tool_calls: break for tc in msg.tool_calls: fn = self.tools[tc.function.name]["fn"] result = fn(**json.loads(tc.function.arguments)) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": json.dumps(result)[:2000], }) self._checkpoint(session_id, messages) return messages[-1].content

这段代码只是骨架,生产环境必须补上几件事:沙箱执行不能让工具函数直接跑在Harness进程里、工具返回要截断防止上下文爆炸、重试时要区分错误类型、写敏感操作前要挂审批。跑通这个骨架后,再去接沙箱和调度器,心里就不慌了。

4.3 并发问题:Agent 扛并发,核心是状态隔离

很多人一聊到Agent并发,第一反应是加线程池、加异步、上消息队列。但Agent任务和普通Web请求有一个本质差别:每个任务有独立的长期状态、独立的工具副作用、独立的上下文成本。

你没法像处理无状态HTTP请求那样,随便把请求分发到任何一台机器。Agent任务一旦开始,它的状态就和特定的session绑定,换一台机器或者换个进程,状态就丢了。

所以并发问题的第一原则是会话隔离。每个任务一个session_id,状态存储按session分片。我踩过最典型的坑就是用全局Redis List存所有任务的checkpoint,任务一多,恢复的时候互相覆盖,最后全乱套。改成session_id哈希分片后,问题立刻消失。

第二是沙箱隔离。并发任务绝不能在同一个文件系统或者同一个进程中执行工具调用,一个任务删除临时文件,另一个任务可能就被拖累。每个任务单独起一个沙箱容器是基本要求。

第三是API限流。模型API的并发数是有上限的,Harness要用令牌桶控制并发,不然一堆任务同时打模型API,全被限流,等于没有并发。

第三是checkpoint写入的并发控制。不同任务写自己的分片,永远不要用一个全局锁,全局锁会在高并发下变成性能瓶颈。

Agent扛并发,从来不是加几个线程的事,而是状态、资源、API三个层面的隔离和限流。

5. Harness 常见故障实录:插件、网关、内网,一个比一个坑

5.1 插件加载失败:harness failed to load plugins 的排查路径

社区里最常见的Harness报错之一就是harness failed to load plugins。我第一次遇到时一脸懵,后来顺手还见过更细的变体,比如web boot: 1 entry did not activate。这种报错看着吓人,本质其实是某个插件没能成功启动。

排查路径我总结成三步。第一步,看是不是依赖冲突。两个插件如果依赖了同一个库的不同版本,启动时很容易加载失败。解决办法是锁版本,把插件依赖统一到同一套版本上。

第二步,查工作目录。Harness经常用相对路径去加载插件目录,如果你换了启动目录、换了工作路径,插件就会找不到。检查启动日志里打印的路径,和插件实际位置对不对得上。

第三步,做二分禁用。那句“1 entry did not activate”其实是个线索——不是所有插件挂掉,而是某一个入口没有激活。先把插件全部禁用,然后一批批加回来,定位到具体是哪个插件,再单独看它的依赖。

这个坑给我的教训是:别一次性加载所有插件。先最小集跑通,再逐个加。凡是插件系统出问题,二分法永远比瞎猜快。

5.2 API抖动与网关路由错误:从连接到模型名的双重坑

长时任务最怕API抖动。你正跑着第18步,上游API突然返回连接失败,Harness断线,整个任务泡汤。这个坑绕不开,只能靠恢复机制兜底。

连接失败的排查链路,我是按这个顺序来的:先看客户端到API服务的基础网络通不通,DNS解析正不正常;再用curl直接打API的健康检查端点,看HTTP层是否可达;第三步检查TLS握手;第四步看错误码,429是限流,5xx是服务端故障,连接超时要单独处理。

重试策略必须按错误码区分。429要做退避重试,5xx可以用指数退避,连接超时要考虑切换备用endpoint。恢复后,再从checkpoint续跑,而不是从零开始。

除了连接失败的坑,还有一种更隐蔽的坑是网关路由错误。日志里可能出现“doesn't look like an anthropic model: expected a gateway model route”这类提示。这个问题的本质是:请求里的模型名在网关路由表里不存在,或者类型不匹配。比如你把Harness配置里写了一个自定义模型别名,但网关端只识别官方模型名,校验就直接拒绝了。

这种问题在很多人那里被误判成“API挂了”,但其实不是。排查办法是检查网关的路由表,确认模型名是官方名称还是自定义别名,两边对齐后再跑。统一模型命名,是避免这个坑最有效的手段。

5.3 内网部署、代码回退与沙箱更新

企业里落地Harness,绕不开内网。我接过一个项目,要求把DeepSeek这类模型部署到内网,Harness也要在离线环境跑起来。离线环境下,依赖安装、插件分发、skill资源包全都成了难题。

内网部署的建议是:提前把依赖包、插件包、模型权重全部下载好,做成内部私有制品库或者镜像仓库;安装时走离线镜像方式,不要让安装脚本去外网拉东西;skill和工作流插件要作为版本化资源,带上校验信息,部署时统一分发。大型模型推理用vLLM或者Ollama起内网服务,Harness只做API转发,不把模型进程塞进Agent进程里。

代码回退是另一个容易被忽视的点。Harness配置和Agent代码必须做版本管理,checkpoint要带schema版本号。否则你回退了Harness版本,老的checkpoint读不进来,恢复机制直接失效。schema版本号不一致时,要能把旧checkpoint迁移到新格式,或者安全丢弃。

沙箱镜像升级也讲究套路。镜像版本升级前,先把旧版本还在运行的任务腾挪干净,别让运行中的任务突然被换掉底层镜像。升级后要留一个旧镜像的备份,遇到兼容性问题能立即回滚。

6. 当模型越来越同质化,Harness 就是那条护城河

6.1 模型能力会摊平,工程能力不会

这两年我的一个明显感受是:不同模型之间的效果差距在快速缩小。开源模型追得很猛,API模型彼此打分接近,你在选模型上花的心思,边际收益越来越低。

但你的Agent在真实业务里能不能稳定跑,这个差距却非常大。同一套模型API,有的团队能跑出95%的任务完成率,有的团队连demo都撑不过十分钟。差别不在模型,而在工程底座。

护城河来自三个地方:一是稳定性,长时任务成功率的积累,靠的是把一个个异常修复掉;二是工具生态,每接入一个业务系统,你的Agent就多一分对业务的理解,别人要重接一遍;三是运行数据,Agent执行轨迹是下一轮调优的最好燃料,没有这些数据,模型调得再强也是无根之木。

6.2 接下来两三年,Harness 会往哪走

我判断,未来两三年的演进路径会比较清晰。

第一,运行时的稳定性会从“加分项”变成“默认项”。没有checkpoint、没有工具幂等、没有沙箱的Agent,根本不好意思拿到生产环境去说事。

第二,控制面会从任务编排走向多Agent协同。现在声明式调度管的是单任务流程,下一步会管多个Agent之间的分工、通信、资源配额。

第三,可观测性会从日志走向完整轨迹回放。出了事故,你能像看录像一样,逐步回放Agent当时为什么调用这个工具、这个工具返回了什么、模型为什么走到错误分支。

第四,治理会从规则走向自动审计。模型控制工具,工具行为受策略管理,审计系统自动检查每一次动作是否符合预期。

这套演进的核心方向只有一个:让Agent从“聪明的demo”变成“可靠的劳动力”。聪明是模型给的,可靠是Harness给的。

6.3 用五个问题判断你的 Agent 有没有护城河

如果你在评估自己的Agent工程,或者评估一个Agent产品到底有没有护城河,我建议直接拿这五个问题去问。

第一,任务跑到一半崩溃了,它能自动恢复吗?不是重新问一遍,而是从断点接着跑。

第二,工具调用失败了,它能自我纠错吗?不是死循环重试同一个动作,而是能感知失败原因、换个路径再试。

第三,所有执行过程都可审计吗?出了事故,你能指出它哪一步走错了吗?

第四,并发跑十个任务,状态会串吗?每个任务有自己的完整状态,不互相干扰吗?

第五,API故障的时候,它是优雅降级还是直接崩溃?你有没有为断连设计过恢复路径?

这五个问题里,只要有一个答不上来,所谓“护城河”就还是空的。

最后说点实在的。我在这条路上交过不少学费:被Agent误操作拖累过数据库,被checkpoint互相覆盖坑过整个团队,被API波动毁过一整个批处理任务。但恰恰是这些事故,逼着我把Harness越做越扎实——不是重得跑不动,而是重到能兜住所有不该由模型负责的意外。护城河从来不在模型排行榜上,而在你处理过的每一次异常、沉淀下来的每一条恢复流程里。如果你也在搭Agent,我建议从今天起,至少把checkpoint和工具幂等这两件事补上,哪怕别的什么都不做。

返回列表