Harness:面向生产级 Agent 的运行时技术设计文档

本文讨论的 Harness 不是某一家公司的专有产品,而是 Agent 系统中的一种运行时架构模式。公开框架对 Harness 没有统一规范;本文将其定义为一个可恢复、可观测、可治理的 Agent Runtime,并结合 LangGraph、OpenAI Agents SDK、Anthropic Tool Use、Mastra、CrewAI、AutoGen、Semantic Kernel 的公开设计推导实现。凡无官方定义处,均标注为“参考设计/架构推测”。

1. Harness 是什么

1.1 定义

Harness 是包裹 LLM 的执行控制平面:它接收用户意图,创建并持久化会话,组装上下文,调用模型,解释模型产生的 Action/Tool Call,执行工具,记录事件和状态,并在完成、失败、暂停、取消或恢复时保持一致性。

可以把它抽象为:


Harness = Model Adapter + Agent Loop + Tool Runtime + State/Checkpoint

          + Context/Memory + Policy/Sandbox + Event/Trace + Scheduler

它不是模型,也不是一个 Prompt 模板。模型负责“提出下一步”,Harness 负责“是否允许、在哪里执行、执行后如何继续、如何恢复和审计”。

1.2 解决的问题

单次 prompt -> completion 适合问答,不适合长任务。生产 Agent 至少要解决:

  • 多轮工具调用和工具结果回注;

  • 工具权限、参数校验、超时、重试、幂等和沙箱;

  • 上下文窗口限制、摘要、检索和敏感信息脱敏;

  • 长时间任务的队列化、并发、暂停、取消、Resume;

  • 状态、检查点、产物、日志、Trace 的持久化;

  • 模型输出不可信时的策略控制和人工审批;

  • 可观测性、成本统计、速率限制和租户隔离。

1.3 为什么会出现 Harness

早期 Agent 多为一个 while-loop:调用 LLM,若返回函数则执行函数,再把结果发回 LLM。随着工具数量、任务时长、并发量和合规要求增长,这个循环中的工程问题开始独立出来:状态不能只放内存,工具不能直接访问宿主机,异常不能靠 Prompt 解决,重启不能导致重复扣款或重复发布。于是出现了 Runtime/Orchestrator/Harness 这一层。

1.4 与传统概念的区别

| 概念 | 核心职责 | 是否天然支持恢复 | 控制粒度 |

|—|—|—😐—|

| 传统 Agent | 让模型循环决定下一步 | 通常否 | 一个 Agent/循环 |

| Workflow | 预先定义步骤与分支 | 取决于引擎 | 节点/边 |

| Pipeline | 面向构建、发布、数据处理的固定流水线 | 通常有 | Stage/Job |

| Harness | 运行 Agent 的通用控制平面 | 是,设计目标 | Session、Execution、Tool Call、Event |

Harness 位于用户应用与 LLM/工具之间:上接产品请求,下接模型、MCP、浏览器、代码执行器、数据库或企业系统。它可承载 Agent、Workflow 和 Pipeline,但不等同于它们。

2. 整体逻辑架构

Answer/Plan/Tool Call

用户/客户端

API Gateway

Harness

Session

Queue

Scheduler

Runner/Worker

Context Builder

Memory

Prompt

Planner

LLM

Executor

Tool Manager

Tool

MCP Client/Server

Sandbox

State

Checkpoint

Event Bus

Streaming

Trace/Telemetry/Log

Storage

Cache

Policy/Approval/Rate Limit

2.1 模块职责、必要性与生命周期

| 模块 | 负责什么 | 为什么需要 | 生命周期与交互 |

|—|—|—|—|

| Harness | 入口、编排、治理、统一协议 | 把模型循环变成可靠服务 | 应用级常驻;创建 Execution,驱动 Runner |

| Runner | 一次执行的实际驱动器 | 隔离并发任务和租户 | 每次 Run 创建,可重启、可迁移 |

| Session | 跨多轮请求的身份和边界 | 关联消息、状态、权限 | 多轮存活;结束/归档后只读 |

| Context | 当前可见事实集合 | 控制上下文窗口和数据边界 | 每轮重建,可摘要/裁剪 |

| Memory | 跨轮、跨 Session 的可检索信息 | 避免每次从零开始 | 长期存储,按租户/用户隔离 |

| Planner | 将意图变成下一步或 Plan | 降低盲目循环和成本 | 每个 Execution 多次调用或一次生成 |

| Executor | 执行动作并把 Observation 回填 | 分离决策和副作用 | 每个 Action 一个生命周期 |

| Tool | 可调用能力的声明与实现 | 让模型与外部世界交互 | 注册、授权、调用、释放 |

| Skill | 一组提示、工具和流程约束 | 复用领域能力 | 按请求加载,可版本化 |

| MCP | 工具/资源的标准化连接协议 | 解耦 Harness 与工具服务 | Session 级连接或请求级连接 |

| Sandbox | 限制代码、文件、网络和凭证 | 防止越权和破坏 | 每次 Tool Call 或 Run 创建销毁 |

| State | 当前执行的结构化事实 | 支持分支、恢复和一致性 | 随事件变化,持久化 |

| Event | 状态变化和外部通知 | 流式、审计、重放 | append-only,最终进入存储 |

| Queue/Scheduler | 排队、优先级、租户配额和调度 | 长任务不能占用请求线程 | Run 入队,Worker 领取,完成确认 |

| Storage | 状态、消息、检查点、产物和 Trace | 重启后仍可恢复 | 长期保存,按策略过期 |

| Cache | Prompt、工具结果、检索结果等短期复用 | 降低延迟和成本 | TTL/版本失效;不能代替真相存储 |

调用链可简化为:User -> Harness -> Session/Execution -> Queue -> Runner -> Context -> Planner -> LLM -> Executor -> Tool/MCP/Sandbox -> Observation -> State/Event/Checkpoint -> LLM -> Stream -> User

3. 核心名词字典

以下采用统一模板。为了避免把同一组字段重复几十次,先给出模板约定:每个对象的“输入/输出/内部/依赖/被依赖/伪代码/运行时/缺失后果/区别联系/误区/例子”均从其运行语义解释。

Harness

一句话定义:Agent 的运行时控制平面。

为什么存在/解决什么:统一生命周期、策略、工具、状态和可观测性;否则每个 Agent 都复制一套脆弱 while-loop。

生命周期:常驻初始化 Provider、Tool Registry、Storage;按请求创建 Session/Execution;结束后释放运行资源。

输入:用户消息、身份、策略、配置。输出:流式事件、最终答案、产物、执行状态。内部包含:Runner、Policy、ToolManager、StateStore、EventBus。依赖:LLM、Storage、Queue、Tool。被依赖:API、应用、SDK。

源码通常长这样:


class Harness:

    async def run(self, request):

        session = await self.sessions.get_or_create(request.session_id)

        execution = await self.execution.start(session, request)

        return await self.runner.drive(execution)

实际运行时它会消费事件并驱动状态机。没有它,模型、工具、数据库和 UI 之间没有稳定边界。它与 Agent 的区别是 Agent 是策略/角色,Harness 是承载 Agent 的运行时。

常见误区:认为 Harness 等于 Prompt 或等于某个模型。真实例子:用户要求总结 GitHub PR,Harness 负责浏览器/API 工具权限、分页、超时、checkpoint 和最终流式返回。

Runner / Execution / Run / Task

Runner 是执行驱动器;Execution 是一次可观测、可恢复的执行实体;Run 通常是 Execution 的产品/API称谓;Task 是被调度的工作单元。

它们存在是为了把“会话”与“这一轮具体执行”分开:一个 Session 可以有多个 Run,一个 Run 可以有多个 Tool Call。生命周期是 created -> queued -> running -> waiting_tool/waiting_approval/paused -> succeeded|failed|cancelled。输入是 Execution、State 和预算,输出是事件、状态和结果。伪代码:


while not execution.done:

    decision = planner.next(context(state))

    if decision.tool_call:

        obs = executor.invoke(decision.tool_call)

        state.apply(obs); checkpoint.save(state)

    else: execution.complete(decision.answer)

没有它就无法限流、重试和恢复。Runner 是“怎么跑”,Execution 是“跑的那一次”,Task 是“队列里的工作项”。例子:同一 Session 中用户追问“再比较上周的 PR”,生成第二个 Run。

Session / Conversation

Session 是权限、租户、用户、变量和多轮执行的边界;Conversation 是消息语义上的对话记录。它们存在是为了让身份与执行可关联但不混为一谈。输入是 session_id、用户和配置;输出是消息流及 Run 引用;内部有 Message、State 引用、Memory namespace。生命周期可跨请求,关闭后仍可 Resume/审计。


session = Session(id, user_id, tenant_id, policy, conversation_id)

session.append(Message.user(text))

没有 Session 就无法权限隔离和多轮上下文。Conversation 关注“说了什么”,Session 关注“谁在什么边界里执行”。例子:同一个用户的两个项目 Session 不能共享 GitHub Token。

Context / Prompt / Context Window

Context 是本轮模型可见的结构化事实;Prompt 是发送给模型的序列化输入;Context Window 是模型一次可处理的 token 上限。存在原因是模型不能直接读取全系统数据,必须裁剪、排序和脱敏。


context = assemble(system, skill, recent_messages, memories, state, tools)

prompt = render(context, token_budget=12000)

输入来自 Session、Memory、Tool Schema、State;输出是 Prompt。没有 Context Builder 就会超窗、泄漏或把过期事实当真。Context 不等于 Memory:前者是本轮视图,后者是可长期保存的数据。例子:只把相关 PR 摘要、权限和工具 schema 放进 Prompt,而不是整个 GitHub 仓库。

Planner / Plan / Reasoning

Planner 产生下一步行动或有向 Plan;Plan 是可执行步骤;Reasoning 是模型内部推理或其摘要,不应默认持久化全部隐式思维链。存在是为了将目标拆解、排序并受预算约束。输入是目标、Context、能力和策略;输出是 Action/Tool Call/终止判断。


decision = llm.generate(plan_schema, context)

validate(decision, policy)

没有 Planner,复杂任务容易循环、遗漏依赖或并行错误。Planner 可由 LLM 实现,也可由确定性 DAG 实现;不要把“模型输出的解释”误认为可靠计划。例子:先列 PR,再抓取详情和评论,最后按模板总结。

Tool / Capability / Tool Call / Observation / Action

Tool 是可执行能力及其 schema;Capability 是更抽象的权限/能力集合;Tool Call 是一次调用请求;Action 是计划中的动作;Observation 是动作执行后的结果。


call = ToolCall(id="tc1", name="github.list_prs", args={"state":"open"})

obs = tool.invoke(call, timeout=10)

生命周期:注册 -> 暴露 schema -> 授权 -> 参数校验 -> 执行 -> 结果规范化 -> 记录。输入为结构化参数,输出为结果/错误/元数据。Tool 依赖 Sandbox、MCP 或外部 API;Executor 依赖 Tool。没有 Tool,Agent 只能生成文字;没有 Tool Call ID,结果无法匹配并发请求。常见误区是信任模型参数、把任意函数暴露给模型、把 Observation 原样拼入 Prompt。真实例子是 github.search_prs 的只读调用。

Memory / State / Checkpoint / Snapshot / Persistence

Memory 是跨轮的可检索知识;State 是当前 Run 的完整工作状态;Checkpoint 是某个可恢复边界的版本;Snapshot 是状态在某一时刻的物化快照;Persistence 是把数据写入可靠存储的总称。


state = State(messages, plan, tool_results, cursor)

checkpoint = store.put(run_id, state, version=7)

state = store.load_latest(run_id)

Memory 适合偏好、事实和历史摘要,State 适合当前执行;Checkpoint 要求版本、幂等和一致性。没有它们,进程重启会丢失上下文,Resume 只能重新执行副作用。例子:保存已处理的 PR URL,恢复时从 cursor 继续。

Event / Message / Streaming / Trace / Log / Span / Telemetry

Message 是对话内容;Event 是状态变化或系统通知;Streaming 是事件的实时传输;Trace 是一次请求的端到端树;Span 是 Trace 中的一个阶段;Log 是文本化诊断;Telemetry 是指标、日志、Trace 的总称。


emit(Event("tool.started", run_id, tool_call_id))

stream.publish(delta)

事件应包含 event_id、sequence、timestamp、run_id、type、payload,并可重放。没有事件总线,UI 无法实时展示、审计无法还原、失败定位困难。Message 是数据,Event 是变化通知;Log 不应作为状态真相。例子:tool.timeout 事件触发重试或暂停。

MCP / Plugin / Skill

MCP 是连接模型应用与工具/资源服务器的协议;Plugin 是可安装的扩展包;Skill 是面向任务的提示、知识、工具和流程组合。MCP 解决协议互操作,Plugin 解决部署扩展,Skill 解决能力复用。


server = MCPClient.connect(url)

tools = server.list_tools()

registry.register(tools, namespace="github")

MCP 生命周期通常是 initialize -> capability negotiation -> list/read/call -> close。没有 MCP,每个集成都要写专有适配器;没有 Skill,领域约束散落在代码中。Skill 不等于 Tool:Skill 可能包含多个 Tool 和规则。例子:GitHub Skill 加载 PR 查询工具、总结模板和引用要求。

Executor / Sandbox / Policy / Approval

Executor 把 Action 变成副作用;Sandbox 限制执行环境;Policy 决定允许什么;Approval 将高风险动作交给人确认。它们存在是因为 LLM 是不可信决策源。


policy.check(call, actor)

with sandbox(network=["api.github.com"], read_only=True):

    return tool.invoke(call)

输入 Action,输出 Observation 或 waiting_approval。没有治理层,Agent 可能删除数据、泄露密钥或无限消费。例子:读取 PR 可自动执行,合并 PR 必须人工审批。

Queue / Worker / Scheduler / Retry / Timeout / Rate Limit / Cache

Queue 保存待执行任务;Worker 消费任务;Scheduler 负责时间、优先级和配额;Retry 处理瞬时失败;Timeout 限制等待;Rate Limit 保护供应商和租户;Cache 复用可验证结果。


task = queue.claim(run_id, lease=30)

try: worker.run(task)

except TransientError: queue.retry(task, backoff="exponential")

这些对象将同步 HTTP 请求变为可伸缩、可恢复系统。Cache 不能保存带权限的未隔离结果,也不能替代 Checkpoint。例子:GitHub API 429 时按 Retry-After 延迟,工具超时则保存 checkpoint 后重试一次。

Artifact / Workflow / Node / Graph / DAG

Artifact 是执行产生的可引用文件或结构化结果;Workflow 是预定义过程;Node 是步骤;Graph 是节点与边;DAG 是无环依赖图。它们适合将“可变的 Agent Loop”与“确定性的业务流程”组合。没有 Artifact,大结果只能塞进消息;没有 DAG,复杂依赖难以并行和审计。


graph.add_edge("list_prs", "fetch_details")

graph.add_edge("fetch_details", "summarize")

例子:把 PR 原始 JSON 保存为 Artifact,摘要节点只消费其引用。

4. 对象关系图

Harness

Runner

ToolManager

Scheduler

StateStore

EventBus

Execution

Session

Planner

Executor

State

Context

Memory

Checkpoint

Tool

Skill

MCPClient

Sandbox

LLM

Storage

Queue

Worker

组合关系:Harness 拥有基础设施,Execution 拥有本轮 Planner/Executor/State;依赖关系:Runner 依赖 LLM、ToolManager、StateStore;生命周期关系:Execution 结束,短生命周期 Runner/Sandbox 释放,但 Session/Memory/Artifact 可保留;调用关系:Planner 决策、Executor 执行、EventBus 广播;数据流是 Message -> Context -> Prompt -> LLM -> Tool Call -> Observation -> State;控制流是 Policy/Approval/Scheduler 对 Runner 的允许、暂停、恢复和取消。

5. 完整请求执行:总结 GitHub 最新 PR

5.1 主流程

  1. 收到请求:API 将文本、用户身份、仓库范围和幂等键交给 Harness。创建 request_id,校验租户和 GitHub 权限。

  2. 创建 Session:若无 session_id,创建 Session;Conversation 追加 User Message。状态为 CREATED

  3. 创建 Execution:生成 run_id,绑定版本化 Skill、模型、预算和策略,状态转 QUEUED,写入 Queue。

  4. 初始化 Context:加载最近消息、历史摘要、GitHub Token 的能力声明、工具 schema 和任务约束;密钥只进入 Tool Runtime,不进入 Prompt。

  5. 构建 Prompt:System Prompt 定义输出格式和引用要求;Skill 提供“最新”的时间语义;User Prompt 保持原意;Context Builder 截断无关内容。

  6. LLM 推理:Runner 发送 Prompt。模型可能直接回答,也可能返回 tool_use。Harness 不把模型文本当作已执行事实。

  7. Planner 输出 Plan:结构化计划可能是:list PRs -> fetch changed files/comments -> normalize -> summarize -> cite URLs。Policy 检查计划范围。

  8. Executor 执行:执行 github.search/list_prs。参数按 JSON Schema 校验,检查只读权限、超时、速率限制和缓存。

  9. MCP 通信:若 GitHub 能力由 MCP Server 提供,MCP Client 发送 tools/call,Server 调 API,返回结构化结果和元数据。Harness 把 MCP 错误映射为统一 Tool Error。

  10. Observation 回填:将结果摘要、分页 cursor、来源 URL 写入 State;原始响应作为 Artifact,不把无限正文塞入 Context。

  11. 循环推理:Planner 根据 Observation 决定是否并行抓取详情;每一步都有事件和 span。达到预算、成功条件或模型返回最终答案时退出。

  12. Streaming 返回:Harness 发布 run.started、plan.created、tool.started、tool.completed、answer.delta、run.completed;客户端可实时显示进度,但只把最终版本标记为答案。

  13. Checkpoint 保存:每个有副作用或等待边界前保存 State、事件序号、工具调用 ID、cursor 和版本。采用 CAS/乐观锁避免并发覆盖。

  14. Memory 更新:保存“用户偏好/仓库摘要”等允许长期保存的事实;不默认保存 Token、完整网页或未经验证的模型臆测。

  15. Session 结束:Run 转 SUCCEEDED,Session 可继续对话;清理 Sandbox 临时文件,保留 Trace、Artifact 和审计记录。

5.2 异常、暂停与恢复

  • LLM 失败:可对连接错误重试;模型拒答、预算耗尽或 schema 错误进入 FAILED/降级路径。

  • Tool 超时:取消底层请求,写入 tool.timeout;若工具声明幂等则指数退避重试,否则进入人工/用户确认。

  • 429/限流:读取服务端 Retry-After,Scheduler 延迟 Task,不让 HTTP 线程长期阻塞。

  • 工具返回脏数据:Schema 校验失败,保留原始 Artifact,向 Planner 注入可解释的 Tool Error。

  • 暂停:状态写入 PAUSED,保存 checkpoint,释放 Worker/Sandbox;禁止仅靠进程内 Future 表示暂停。

  • Resume:按 run_id + checkpoint_version 加载最新一致状态,重新取得租约,从未确认完成的 Action 开始;工具调用必须带幂等键,避免重复发布/扣款。

  • 取消:状态先 CAS 为 CANCELLING,向 Worker/Sandbox 发取消信号,等待安全点后 CANCELLED;不可撤销的外部副作用必须记录结果。

6. 源码设计(参考实现)

以下是根据主流开源框架推测的接口,不声称与某个 Harness 源码一致。


harness/

  core/        harness.py runner.py execution.py session.py events.py

  model/       provider.py messages.py structured_output.py

  planning/    planner.py plan.py policies.py

  tools/       registry.py tool.py executor.py mcp.py sandbox.py

  context/     builder.py truncation.py summarizer.py retrieval.py

  state/       state.py store.py checkpoint.py reducers.py

  runtime/     queue.py scheduler.py worker.py retry.py cancellation.py

  memory/      short_term.py long_term.py embeddings.py

  observability/trace.py metrics.py logs.py

  artifacts/   store.py references.py

  api/         http.py websocket.py schemas.py


class Execution:

    def __init__(self, session, state_store, planner, executor, events): ...

    async def resume(self):

        self.state = await self.state_store.load_latest(self.id)

        while not self.state.terminal:

            self.events.emit("step.started", self.state.version)

            decision = await self.planner.decide(self.context())

            result = await self.executor.execute(decision)

            self.state = reduce(self.state, result)

            await self.state_store.checkpoint(self.id, self.state)

Harness.run() 创建 Execution;Runner.drive() 负责循环;Planner.decide() 只产生结构化决策;Executor.execute() 负责权限、超时、重试和工具调用;StateStore.checkpoint() 是恢复真相;EventBus 面向 UI 和 Trace。设计重点是依赖倒置:模型、队列、数据库和 MCP 都通过接口替换,测试时可使用 Fake Provider/Fake Tool。

7. 框架对比

| 维度 | Harness参考设计 | LangGraph | OpenAI Agents SDK | Claude/Anthropic | Mastra | CrewAI | AutoGen | Semantic Kernel |

|—|—|—|—|—|—|—|—|—|

| 架构 | 通用运行时控制平面 | 图+状态图运行时 | Agent、Tool、Handoff、Runner | 模型 API + Tool Use/Claude Code | TypeScript Agent/Workflow | 多 Agent Crew/Process | 多 Agent 对话/消息 | Kernel+Plugin+Process |

| 运行方式 | 循环、图或 Workflow | 节点/边,支持循环 | Runner 驱动 agent loop | 客户端执行工具并回传结果 | Agent loop 与 workflow | 角色协作/任务编排 | 对话驱动 | 函数调用与流程 |

| 状态/恢复 | 一等公民 | checkpointer、durable execution | Session/Tracing,恢复能力依实现 | Claude Code 有 continue/resume;API 侧由应用管理 | workflow state,能力随版本变化 | 任务上下文,持久化依实现 | 状态依应用/组件 | 状态与 memory 依实现 |

| Tool | Registry、Policy、Sandbox、MCP | 节点/工具函数 | function tools、MCP 等 | client/server tools,tool_use/tool_result | tools、MCP | tools | function tools/agents | native/plugin |

| Streaming | 事件级 | 支持流节点与 token | Runner events/tracing | Messages streaming | 支持 | 支持程度依版本 | 支持 | 支持 |

| 优点 | 生产治理与可恢复性完整 | 图控制清晰、适合长任务 | SDK 体验好、抽象轻 | Tool 协议清晰、模型能力强 | TS 全栈友好 | 上手快、角色表达直观 | 多 Agent 实验灵活 | 企业 .NET/插件生态 |

| 局限 | 设计复杂、需要基础设施 | 图建模成本、需理解状态 | 深层编排需自行设计 | 不是完整通用 Orchestrator | 生态和版本变化快 | 强依赖 prompt/角色纪律 | 生产一致性需自行补足 | Agent 自主性与调试体验依场景 |

| 适合场景 | 企业级 Agent 平台 | 长任务、分支、可恢复流程 | 应用内单/多 Agent | Claude 工具型应用、编码 | TS 应用、workflow | 内容/业务多角色协作 | 研究和多 Agent 对话 | 企业插件与 .NET 系统 |

LangGraph 官方强调持久化、durable execution、human-in-the-loop 和 memory;OpenAI Agents SDK 的核心是 Agent、工具、handoff、guardrail、session 与 tracing;Anthropic Tool Use 明确了 tool_use -> 执行 -> tool_result -> 继续推理 循环。Harness 可以把这些能力提升到统一 Runtime 层,而不是替代它们。

8. 设计原则与落地检查清单

  1. 把所有外部副作用建模为可审计 Tool Call,并设置幂等键。

  2. State 是恢复真相,Event 是可重放记录,Log 只是诊断文本。

  3. Planner 输出必须结构化并经 Policy 校验,不能直接执行自由文本。

  4. Context、Memory、Artifact 分层;原始大数据不要塞进 Prompt。

  5. Tool 默认最小权限、网络白名单、凭证隔离和超时。

  6. 任何等待点都要 checkpoint:工具、审批、队列租约、人工输入。

  7. Cache 必须带租户、权限、工具版本和输入哈希,避免越权复用。

  8. 取消、重试、Resume 都要考虑外部系统已成功但本地未收到响应的情况。

  9. Trace 至少关联 session_id、run_id、plan_id、tool_call_id、checkpoint_version

  10. 以失败注入测试验证:Worker 崩溃、LLM 超时、重复消息、乱序事件、MCP 断线、状态冲突。

9. 总结脑图

渲染错误: Mermaid 渲染失败: Parse error on line 3: mindmap root((Agent Harnes -------^ Expecting 'SPACELINE', 'NL', 'EOF', got 'SPACELIST'

参考资料

加粗样式

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐