OpenClaw 源码导读(三):Agent Harness — 为"每一家 LLM 都能兜住"而生的执行管线
系列第一篇把 OpenClaw 的架构地图摊开,第二篇深入 Gateway 控制平面。这一篇,我们钻进整个项目的心脏——src/agents/,看 OpenClaw 怎么处理最复杂的一件事:当 Gateway 接到一条用户消息之后,怎么把它变成一轮真正的 LLM turn + Tool 循环。 这一层代码的体量和复杂度非常惊人: src/agents/pi-embedded-runner/run.ts 单文件 2160 行,是整个 agent 循环的驱动器。 src/agents/pi-embedded-runner/compact.ts 1148 行,专门处理上下文压缩。 整个 src/agents/ 目录有 830+ 个文件,光 anthropic-*.ts 就有近 20 个(为了 Messages API 的各种 edge case)。 同时支持 Anthropic、OpenAI、Google Gemini、Bedrock、Vertex、OpenRouter、Z.AI/GLM、MiniMax、Qwen、Ollama、Kilocode 等十多个 provider。 但这些"量"的背后,是一个只有一个接口的核心抽象:AgentHarness。本文就从这个抽象出发,一路看到 2160 行循环如何处置真实世界里十几类失败。 一、Harness 抽象:只有一个 runAttempt 1. 为什么不用 LangChain 风格的"Agent Framework" 读过 LangChain 或 CrewAI 的人第一眼会期望看到 AgentExecutor、AgentChain、Memory、Callback、Tool 这些类。OpenClaw 里一个都没有。 它的选择是:把整个 agent 循环当成一个黑盒,对外只暴露一个接口: export type AgentHarness = { id: string; label: string; pluginId?: string; supports(ctx: AgentHarnessSupportContext): AgentHarnessSupport; runAttempt(params: AgentHarnessAttemptParams): Promise<AgentHarnessAttemptResult>; compact?(params: AgentHarnessCompactParams): Promise<AgentHarnessCompactResult | undefined>; reset?(params: AgentHarnessResetParams): Promise<void> | void; dispose?(): Promise<void> | void; }; 五个方法,其中三个可选: ...