很多做 Agent 应用的朋友在群里问过我同一个问题:Agent Harness 和 Agent Runtime 到底是不是一回事?为什么有些文章说 Harness 是“智能体工作台”,另一些又说是“运行时框架”,还有人直接用两个词指代同一个 SDK。
我先说我的结论:这是两个层的概念。Agent Harness 管的是“Agent 怎么想、怎么做决策”,Agent Runtime 管的是“Agent 跑在什么环境里、靠什么被调度、崩溃了怎么办、流量大了怎么扩容”。搞混它们,短期看不出毛病,一旦你开始做生产级部署,遇到问题会连该改代码还是该改配置都分不清。这篇文章我结合自己做客服 Agent、多智能体系统的经验,把这两个概念彻底拆开讲清楚。
1. 先把概念摆正:这两个词分别回答什么问题
1.1 Agent Runtime:承载 Agent 运行的“地基”
Agent Runtime 指物理上承载和执行 Agent 程序的基础设施层。注意我用的是“物理上”,因为 Runtime 通常由进程管理器、任务调度器、容器平台、状态存储、模型网关、沙箱等组件构成,它不关心你写的 Agent 用的什么提示词模板,也不关心你是用 ReAct 还是 Plan-and-Execute 模式。
Runtime 回答的问题是:这段 Agent 代码放在哪里跑、怎么被调度、内存不够了怎么办、进程崩溃后怎么恢复、日志指标从哪里采集、多个用户同时访问时怎么保证不互相踩踏。
举个例子方便理解。写后端服务时,Spring Boot 是你的程序框架,但把它跑起来的是 Tomcat 容器、是 K8s 集群、是 JVM 本身。Agent Runtime 就是 Agent 世界里的 Tomcat、K8s、JVM。LangGraph 写出来的 agent 图如果只是在本机调用,那你的 Runtime 就是一个 Python 进程;一旦部署成服务,Runtime 就得包含任务队列、工作流引擎、容器编排系统,甚至微虚拟机沙箱。
Agent Runtime 的典型代表包括但不限于:
- Temporal:它为 Agent 提供持久执行、定时重试、故障恢复能力,一个长时运行的 Agent 任务即使进程被杀掉,也能从上次停顿的地方继续跑。
- 各类 Serverless 容器平台:AWS 的 App Runner、Google Cloud Run、自建的 K8s Deployment,它们负责把 Agent 进程拉起来、做健康检查、根据流量扩缩容。
- 沙箱执行环境:比如 E2B 这类云端微虚拟机,专门隔离 Agent 调用工具时执行的不可信代码。
- 向量存储和记忆服务的运行集群:虽然很多人把它归为“基础设施”,但它确实是 Agent 在运行时依赖的外部状态服务。
Runtime 出问题时,表现通常是系统性的:突然超时、全部实例重启、内存飙升、请求堆积、状态丢失。它的故障和你的提示词好坏没有任何关系。
1.2 Agent Harness:驱动 Agent 决策的“操作台”
Agent Harness 指的是用于构建、编排和控制 Agent 行为的软件抽象层。它落在你写的代码仓库里,它处理的是 Agent 的决策循环、状态机、工具调用协议、记忆读写逻辑、人机确认机制。
Harness 回答的问题是:当用户输入到达时,Agent 应该经过哪些节点、以什么顺序调用工具、模型返回的哪些字段可以当作系统状态、出错后如何回退到人工、多步任务中间结果怎么缓存。
更直白地说,Harness 是你给 Agent 套上的“操作台”。我见过最好的类比是一位前同事说的:Harness 像赛车方向盘和中控面板,它决定了车手能看什么、能操作什么、自动辅助系统如何干预;而 Runtime 是发动机、变速箱、轮胎和赛道本身。方向盘再灵敏,发动机熄火你也跑不了;发动机再强,方向盘逻辑混乱一样冲出赛道。
工程上,Harness 层的产物通常包括:
- Agent 的图定义(StateGraph、节点、边)
- ReAct 循环里的 prompt 模板和 parser
- 工具调用的 schema、白名单、重试参数
- 短期上下文与长期记忆的读写逻辑
- human-in-the-loop 的暂停、审批、恢复机制
- 不同 LLM 之间切换的适配器
LangChain、LangGraph、Semantic Kernel、CrewAI、AutoGen、Pydantic AI 这些框架,大部分时候你说的其实是它们的 Harness 能力。它们把“让模型完成多步任务”的工程问题抽象成了几个对象,至于这些 Agent 部署后如何保持长期运行,那是另一层的问题。
1.3 一句话抓住本质区别
| 对比维度 | Agent Harness | Agent Runtime |
|---|---|---|
| 核心职责 | 决策编排与行为控制 | 进程调度与资源保障 |
| 关注代码 | agent 状态、节点、工具路由 | 副本数、线程池、持久化、重试 |
| 故障表现 | Agent 答非所问、不调工具、决策死循环 | 请求超时、任务丢失、进程崩溃、无法扩容 |
| 修改方式 | 改代码、改提示词、改图结构 | 改部署配置、加资源、换调度策略、升级集群 |
| 典型例子 | LangGraph 图、OpenAI Agents SDK 的 AgentLoop | Temporal、K8s Deployment、沙箱执行器 |
| 类比 | 飞机的自动驾驶逻辑 | 飞机的液压与航电系统 |
心里记住一句话就行:Harness 处理“下一步该做什么”,Runtime 处理“这一步凭什么能执行成功”。
2. 为什么大家总是搞混这两个概念
2.1 SDK 把 Harness 和 Runtime 打包得太严密了
很多主流 SDK 为了提升体验,把开发框架和服务端执行环境做成了同一个产品。最典型的是 OpenAI Assistants API:你用 SDK 创建 Assistant、定义 Tool、发起 Run,这部分是 Harness;但 Run 实际上跑在服务端托管的执行环境里,线程状态、工具调用、模型调度都不暴露给你,这部分是 Runtime。
当产品经理说“Agent SDK 就是运行时”的时候,他确实没有错,因为在那个上下文里 SDK 确实把两个层都包进去了。但对你这种要写生产代码的人来说,就会产生一个错觉:所有 Agent 行为异常都可以通过换提示词或改框架参数解决,可惜完全不是这样。
我见过一个团队,线上 Agent 每隔几小时就丢一次任务,他们在 Harness 层折腾了一周,反复修改状态定义和工具调用逻辑,最后才发现是消息队列消费者没做确认确认机制,进程重启后 offset 没提交,任务从头执行。这就是典型的 Runtime 问题被误判成了 Harness 问题。
2.2 “Agent 框架”这个词本身语义就漂移了
早期 LangChain 出来的时候,大家都叫它 Agent 框架,主要管逻辑编排。后来 LangGraph 更进一步把状态机做到框架里。但云服务商和平台厂商也在说“Agent 框架”,他们说的是托管执行环境、模型路由、沙箱服务和可观测性。
同一个名词,一类人用来指 Harness,另一类人用来指 Runtime,不混乱才怪。更麻烦的是成熟的框架往往两头都做一部分。LangGraph 生态里既有 StateGraph 这种纯 Harness 组件,也有 LangGraph Platform 这种负责服务化部署的 Runtime 组件。Semantic Kernel 里既有规划器这类 Harness 组件,也有部署到 Azure 后的托管运行时。
遇到这类横跨两层的框架,你最好的办法不是纠结它属于哪一类,而是把它拆开看:它提供给我的是“写逻辑的 API”还是“跑服务的能力”。写逻辑的归 Harness,跑服务的归 Runtime。
2.3 单机 Debug 掩盖了 Runtime 的存在
大多数人第一次写 Agent 是在 Jupyter Notebook 或本地脚本里完成的。在那个场景下,Runtime 约等于你本机的 Python 解释器,你完全感知不到它的存在。模型调用失败就重新运行一次,状态丢失就再问一遍用户,所有“执行保障”都是你手动完成的。
但生产环境里,用户不会因为你进程崩溃就体贴地重试一遍。生产 Agent 需要并发隔离、任务持久化、自动扩缩容、故障恢复,这些没有一个是 Harness 能提供的,全部要依赖 Runtime 层去解决。很多团队上线后才发现,自己只写了一个精美的 Harness,却完全没有设计 Runtime。
所以我建议,从你决定做生产级 Agent 的第一天起,就要在架构图上把 Harness 和 Runtime 画成两条泳道,泳道不属于同一个负责人也要分开设计。
3. 上实例:把同一个 Agent 的 Harness 与 Runtime 拆出来看
3.1 一个客服工单 Agent 的 Harness 长什么样
假设我们要做一个客服 Agent:用户描述问题后,Agent 先判断问题类型,然后检索知识库,如果需要创建工单就调用 CRM 接口,最后生成回复。
从 Harness 视角看,我用 LangGraph 风格写一个简化版本的状态图示意。这里不追求可运行代码,而是让你看清哪些代码属于 Harness。
# agent_harness.py(示意代码) from langgraph.graph import StateGraph, END from typing import TypedDict, Literal class AgentState(TypedDict): user_message: str problem_type: str kb_contexts: list[str] ticket_id: str | None final_answer: str # 通俗说,这三个函数就是 Agent 的“思考节点” def classify_problem(state: AgentState) -> AgentState: # 调用 LLM,将用户问题分成"退款""技术故障""咨询"等类型 state["problem_type"] = llm_classify(state["user_message"]) return state def retrieve_knowledge(state: AgentState) -> AgentState: # 根据 problem_type 检索向量知识库 state["kb_contexts"] = vector_search(state["problem_type"], top_k=3) return state def open_ticket(state: AgentState) -> AgentState: # 只有技术故障类才创建工单,其他直接回复 if state["problem_type"] == "technical_issue": state["ticket_id"] = crm_create_ticket(state["user_message"]) return state # 这里是 Harness 的核心:状态图定义 graph = StateGraph(AgentState) graph.add_node("classify", classify_problem) graph.add_node("retrieve", retrieve_knowledge) graph.add_node("ticket", open_ticket) graph.set_entry_point("classify") graph.add_edge("classify", "retrieve") graph.add_edge("retrieve", "ticket") graph.add_edge("ticket", END) agent_app = graph.compile()上面这段代码里,问题分类逻辑、知识库检索顺序、是否创建工单的判断条件、回复组合方式,全都在 Harness 层。你可以随时把classify_problem的提示词改得更好,也可以把“ticket”节点挪到“retrieve”之前,这些修改不会影响 Agent 跑在什么环境里。
3.2 同一个 Agent 的 Runtime 需要哪些配置
到了生产环境,这个客服 Agent 不可能作为单个 Python 函数裸跑。你需要让它能同时服务 1000 个用户,某一个用户调 CRM 接口卡住了不能拖垮别人;进程在凌晨三点崩溃后,正在运行中的任务第二天要能恢复。
一个务实的 Runtime 方案是把上面编译好的 agent 包装成一个 Temporal Workflow,让 Temporal 负责持久执行,再用 K8s Deployment 承载 Worker。
# agent_runtime_worker.py(示意代码) from temporalio.worker import Worker from temporalio.client import Client async def main(): client = await Client.connect("temporal-server:7233") worker = Worker( client, task_queue="agent-tasks", workflows=[run_customer_agent], # 这里把 Harness 包装成 Workflow activities=[call_crm, search_kb], ) await worker.run()上面这段代码意味着:用户请求会变成 task queue 里的一个任务,Temporal Runtime 负责保证任务不丢、失败重试、按需调度;真正执行业务逻辑时,才会调用你在 Harness 里定义的状态图。
同时,K8s 层的 Deployment 描述了这个 Agent Worker 的运行资源:
apiVersion: apps/v1 kind: Deployment metadata: name: agent-worker spec: replicas: 3 template: spec: containers: - name: worker image: registry.example.com/customer-agent:1.2.0 resources: requests: cpu: 500m memory: 1Gi limits: memory: 2Gi env: - name: OPENAI_API_KEY valueFrom: secretKeyRef: name: llm-secret key: api_key这里你可能注意到了:K8s Deployment 和 Temporal Worker 都不关心你的 Agent 是 ReAct 模式还是 Plan-and-Execute 模式,它们只负责“任务有没有在跑”“资源够不够”“宕机后能不能自动拉起新副本”。
3.3 一次请求从进入到返回,两边各做了什么
假设一个用户发来消息:“我昨天扣款成功了但没收到会员权益,帮我看看。”
完整的处理路径大体是这么走的:
- 网关层收到请求后,把内容封装成一条任务,放进 Temporal 的 task queue。这一步是 Runtime 在干活。
- Temporal Worker 取出任务,调用你编译好的 Harness 状态图。状态图的第一个节点执行问题分类,这里会发起一次 LLM 调用。
- LLM 返回结果后,Harness 内部分支判断走知识库检索,检索完成后需要调用 CRM 系统确认订单。这步如果 CRM 接口很慢,Runtime 层的 activity timeout 会介入,不会让你无限等下去。
- Harness 根据检索和查询结果生成最终回复,Worker 把回复写入结果队列。
- 如果中间 Worker 崩溃,Temporal 会在另一个健康 Worker 上重新调度这个任务,并且从最近完成的 activity 之后继续执行,而不是让用户重新描述问题。
整个过程中,Harness 负责完成“从用户问题到回答”的推理拼图,Runtime 负责保证这块拼图在恶劣的分布式环境下仍然能拼完。
4. 一张对照表解决选型中的 90% 困惑
下面这张表我总结了很多次评审经验。每次团队里因为概念争论不下时,我就拿出它来对齐。建议你保存下来,遇到具体问题时先查表,再决定找谁排查。
| 判断维度 | Agent Harness | Agent Runtime |
|---|---|---|
| 修改周期 | 代码版本每次发布都可能变 | 相对稳定,配置变更频率低 |
| 核心数据 | 用户消息、上下文、工具结果 | 任务队列、执行历史、容器状态 |
| 垂直扩展方式 | 优化推理步骤、换更强的模型、精简工具链 | 扩容 Worker、增加沙箱资源、加队列消费者 |
| 水平扩展方式 | 多 Agent 拆分职责 | 多副本负载均衡、分区队列 |
| 可测试性 | 单元测试、提示词回归、工具 mock | 混沌工程、故障注入、压测 |
| 典型性能指标 | 决策延迟、工具调用成功率、上下文有效利用率 | worker 吞吐、任务积压量、错误恢复时间 |
| 对成本的影响 | token 消耗量由它决定 | 资源占用、并发许可证由它决定 |
| 失败后副作用 | 回答错误、生成垃圾信息 | 任务丢失、流量超时、下游重复执行 |
| 调试入口 | LLM trace、状态图节点日志 | 进程日志、任务队列 backlog、容器事件 |
| 团队角色 | Agent 工程师、算法工程师 | 平台工程师、SRE |
| 框架举例 | LangGraph、CrewAI、AutoGen、Pydantic AI | Temporal、K8s、E2B、托管 Workflow |
配一个实战判断方法:上线后出现问题,先问自己一句——如果把所有 Agent 相关代码替换成一个最简单的 echo 服务,只保留你这个 Agent 依赖的外部系统调用,问题还会不会发生?
如果会,那是 Runtime 问题,比如外部系统依赖、进程管理、网络超时、队列积压。如果不会,那大概率是 Harness 问题,比如状态设计错误、工具选择不合理、提示词导致模型误判。
我还有第二个判断方法:把同样的问题手动用脚本模拟一遍,绕过 Harness 直接调用底层工具。如果脚本稳定成功,说明你的工具链没问题,问题出在 Harness 的编排逻辑;如果脚本本身也不稳定,那要从 Runtime 的稳定性去查。
5. 工程落地建议:你在不同阶段该关注什么
5.1 先用 Harness 快速验证,再把 Runtime 当正式产品搭
我见过很多团队把顺序搞反了。项目刚起步时,他们花大量精力搭 K8s、接监控、设计高可用,结果 Harness 的 Agent 逻辑还很粗糙,连用户意图都分不清,一套复杂的生产环境只会拖慢迭代速度。
我的建议很直白:在 PMF 验证阶段,用轻量 Harness 加一个最简单的容器部署就够。把注意力放在提示词、状态图、工具调用是否真的对用户有帮助上。确认业务逻辑跑通后,再引入 Temporal 这类持久执行引擎,把高可用和故障恢复补齐。Runtime 是给规模做准备的,不是给想法做验证的,你不需要在实验阶段就背上运维包袱。
5.2 Harness 层最容易忽略的四个设计细节
第一个是状态显式化。不要把所有临时变量都塞进一个全局字典,Agent 跑几轮之后状态变得不可预测。我在实际项目里吃过亏:两个不同租户的消息因为共享一个 key,导致跨租户串数据。Harness 层必须明确定义状态 schema,哪些字段会被持久化、哪些只在本轮有效,一分一秒都不能含糊。
第二个是工具调用抽象。Agent 要访问 CRM、订单系统、知识库,不要让 Harness 代码直接拼 HTTP 请求。每个工具都应该有清晰的名字、描述、输入输出 schema,这样模型才能正确选择工具,后续替换底层实现也更容易。
第三个是 human-in-the-loop 接口。不是所有操作都适合让 Agent 自动完成,尤其是创建工单、发邮件、退款这类动作。Harness 必须预留暂停点,让 Agent 在关键操作前停下来,把上下文提交给人工确认。这个接口如果后期补会非常痛苦,因为状态图每个节点都要改。
第四个是模型适配层。不要把 prompt 和某个具体模型的 API 格式绑死。早先我用某个模型做 function calling 时效果很好,后来因为成本和性能需要换另一个模型,Harness 里的工具调用解析逻辑全部重写了一遍。后来我改成统一 ModelAdapter 接口,任何模型进来都转成同一套 tool call 协议,切换模型时间从几天缩短到两小时。
5.3 把 Runtime 当作运维资产来管理,而不是业务代码
Runtime 层改进通常不是靠写业务代码完成的,而是靠配置、资源、调度策略、观测体系。你需要为 Agent 建立三件套:日志、指标、分布式 Trace。尤其是当你接入了 Temporal 或 K8s,一定要把 Workflow 执行历史、队列积压量、Worker 内存使用量、任务重试次数接入统一监控面板。
我建议给每个 Agent 任务分配一个可追踪的 request_id,从用户请求进入消息队列开始,一直贯穿 Harness 的每个节点和 Runtime 的每次调度。没有这个贯穿 ID,出了问题你会在 Harness 日志和 Runtime 日志之间来回复制粘贴,非常消耗生命。
另外,Runtime 层的故障恢复机制要主动测试。不要等线上任务丢了再查,你可以在预发环境手动 kill 掉 Worker 进程,观察任务能否恢复、恢复位置是否准确、有没有重复执行。这类演练做两三次之后,你对 Runtime 的信任感会完全不同。
5.4 团队协作时的职责边界划分
如果你们团队大于五个人,强烈建议在分工上把 Harness 和 Runtime 分开。Agent 工程师专注 Harness 层,负责状态图、工具链、提示词、模拟测试;平台工程师专注 Runtime 层,负责工作流服务、容器集群、沙箱、监控告警、容量规划。
两个角色需要协作的地方是接口定义,尤其是任务超时、重试策略、幂等键、限流参数的设计。我踩过最大的坑是消息重试没有做幂等,Harness 里生成的工单重复创建了好几个。后来我们约定:所有外部写入操作都必须带一个业务幂等键,由 Harness 生成并传到底层接口,Runtime 重试时不会产生副作用。
6. 常见问题与排查技巧实录
6.1 为什么我的 Agent 本地测试正常,一上线就超时
这是最常见的问题,大概率出在工具调用没有设置超时,或者 Runtime 层的 Worker 并发不够。本地测试时你只面对一个请求,外部接口慢一点也无所谓;线上有几百个并发请求时,一个外部 API 卡住会导致整个 Worker 线程池耗尽,后面的任务全部排队。
排查思路是从外到内一层层剥。先看任务队列积压量,确定瓶颈在 Worker 还是下游;然后看 Agent trace,找到具体卡在哪一个工具调用上;最后检查这个工具的 timeout 和重试策略。修复时一般要同时改两层:Harness 层给模型可调的工具设置超时参数,Runtime 层给 Workflow 设置整体执行超时和隔板隔离。
6.2 任务重试后,为什么下游系统被重复执行
Agent 在调用支付、发消息、建工单这类非幂等接口时,Runtime 的任务重试会导致同一个请求被执行两次。这不是 Harness 状态图的问题,但它需要 Harness 来治。
解决办法是在 Agent 状态里增加一个幂等键字段,每次用户会话生成唯一 request_id,所有外部写入型工具都必须携带这个 key。下游接口需要支持按 key 去重,如果接口不支持,你就得在 Harness 里加一层写入去重表,先查再写的原子操作可以通过 Redis 或数据库唯一索引实现。
6.3 Agent 运行几轮后“失忆”,状态丢得到底算谁的问题
多数情况是 Harness 层的状态设计缺陷,也就是该持久化的中间状态没有持久化。比如你在状态图里把上下文放在局部变量里,节点执行完后变量就没了,模型下一轮当然不记得。这是典型的 Harness 问题。
另一种情况是 Runtime 重启后任务状态没恢复。这常见于直接跑在 K8s Pod 中的 Agent,没有引入持久化工作流引擎,Pod 一重启进程内上下文就全丢了。这种情况属于 Runtime 选型问题,修复方案是换用 Temporal 这类支持持久执行的工作流引擎。区分方法很简单:你在本地单步调试时如果状态也会丢,那是 Harness;只有部署成多副本或重启进程才丢,那是 Runtime。
6.4 Agent 决策质量不稳定,该换模型还是换框架
先别急着换模型,也不要马上推翻框架。我建议先看 Harness 的工具调用和上下文管理是否合理。模型决策质量差,往往是因为关键信息没有传给模型,或者工具描述模糊导致模型选错了工具。
检查一遍提示词里的工具描述:是否明确说明了工具是为了完成什么目标、输入参数有哪些限制、什么时候不该调用。很多时候,把工具描述从一句话扩充到三句话并给出使用实例,准确率提升效果远大于切换模型。如果工具描述已经很完善了,再考虑换更强的模型,或者增加一个校验节点,让模型输出先经过简单规则验证再执行。
6.5 我应该用托管 Runtime 还是自己搭基础设施
对绝大多数中小团队,我建议优先用云厂商或开源社区已经验证过的托管 Runtime,比如 Temporal Cloud、托管沙箱平台,或者云平台自带的 Agent 服务。自己搭 K8s 加工作流引擎,意味着你还要维护集群版本升级、节点故障自愈、网络策略、存储备份,这些工作会消耗掉你本来应该花在业务创新上的精力。
自建 Runtime 只适合两种情况:一是数据合规要求严格,核心数据不能出特定环境;二是你的多 Agent 系统规模大到托管方案费用完全不可控。即便如此,我也不建议你从零造轮子,用开源的 Temporal、K8s、沙箱组件组合出来会是更稳妥的选择。
7. 最后分享两个快速判断口诀
我这些年评审太多 Agent 项目,最后沉淀出两句口头禅,分享给大家。第一句:决策层靠 Harness,保障层靠 Runtime。遇到任何 Agent 问题,先在脑子里归类,再决定去哪里排查。
第二句:Harness 错了是答错,Runtime 错了是跑不了。如果你在排查一个问题时发现 Agent 回答质量变差,但你最近没有改动 Harness 的代码,那要赶紧检查模型是不是被限流了、知识库是不是连不上了。反过来,Agent 直接崩溃或请求堆积,你改一千行提示词也是白费力气。
最后一个小技巧:我在做技术评审时一定会问对方这样一个问题——“如果这个 Agent 现在从集群里完全消失,用户会感知到什么?”答“任务丢了、服务不可用”的人,基本理解 Runtime;答“不能对话了、没有自动回复了”的人,往往还停留在 Harness 层思维。让整个团队都能正确回答这个问题,你们的 Agent 系统离生产级就更近一步了。