
1. 从一行报错说起harness 和 runtime 为什么值得较真如果你最近在折腾 Agent 开发很可能见过这么一行报错error: agent harness runtime codex is unavailable because its plugin registry...我第一次看到这行报错的时候第一反应是“某个依赖坏了重装一遍就行”。但折腾了半个小时重装了所有能重装的东西之后我才意识到这行字远不是一个简单的环境问题——它直接把 Agent 体系内部的结构层次摆在了我面前报错的主语是 agent harness报错的对象是 runtime而且 harness 是通过 plugin registry 去寻找 runtime 的。这三个名词出现在同一句话里说明它们是三个不同的东西。但问题来了市面上讲 Agent 开发的文章大多把“运行时”“框架”“编排层”混着说今天叫 runtime明天叫 harness后天叫 agent framework再加上 LangChain、CrewAI、OpenAI Agents SDK、Claude Code、Codex CLI 这些项目各自的叫法新手很容易被绕晕。而且这不是一个纯粹的理论问题——它直接关系到你怎么定位报错、怎么选型技术栈、怎么给团队解释“我们的 Agent 架构到底长什么样”。如果你把 harness 当成 runtime 去排查问题或者选型时买了一个“runtime 很弱但 harness 很强”的方案后面会踩很多冤枉坑。这篇文章我打算用一整篇的篇幅把这两个概念从职责、边界、交互方式、排错方法到选型思路全部拆开来讲。不绕弯子直接从架构分层开始。2. 先建立共识Agent 从配置到执行中间到底隔了几层要理解 harness 和 runtime 的区别得先退一步看一个 Agent 应用的整体架构。很多人觉得 Agent 大模型 工具调用这没错但太粗了。从代码真正跑起来的角度看一个完整的 Agent 应用至少应该拆出下面几层层级职责典型组件举例模型层提供推理能力接收消息并生成文本/工具调用GPT、Claude、Gemini、本地开源模型Runtime 层实际执行模型推理循环、调用工具的沙箱、管理进程与资源代码解释器沙箱、容器运行时、浏览器自动化环境Harness 层装配提示词、注册工具、注入策略、协调观测与插件发现各类 Agent SDK 的编排内核、自定义的 tool registry应用层面向用户的界面与业务逻辑调用 harness 的入口CLI、Web 应用、客服机器人的后端服务注意我的措辞harness 在上、runtime 在下。这不是我发明的分层而是现代 Agent 框架在工程上实际形成的结构。模型层是最底层的“大脑”runtime 是“手脚和肌肉”harness 是“神经中枢和控制系统”应用层才是“对外表现出的那个人”。你可以用另一个类比来感受把 runtime 想成一台通用机床它擅长切削但不知道今天要削一个齿轮还是一个法兰。harness 是机床旁边的控制台和工艺卡它知道零件的设计图、加工顺序、什么时候该换刀具、什么时候该停下来等人确认。至于应用层就是车间主任它只负责说“我要一个齿轮”剩下的交给控制台和机床。这个类比最关键的一点是机床runtime可以被不同控制台harness驱动。同一个 Python 沙箱你可以用 A 框架去编排它也可以用 B 框架去编排它。反过来同一个控制台也可以驱动不同型号的机床。这就是为什么我们有必要把这两个概念分开——它们本来在工程上就是可替换的两个独立组件。但为什么很多人还是混着说因为市面上很多 Agent 框架把这两层揉在了一起。框架提供一个Agent类你传入模型、工具、系统提示词它内部既帮你管理工具调用的循环这是 runtime 的活也帮你装配系统提示词和策略这是 harness 的活。框架的设计者为了简化用户心智故意不暴露这个分层。这在快速原型阶段没问题可一旦你开始排查诡异的线上问题或者想把底层沙箱换掉这个被隐藏的分层就会突然跳出来找你麻烦。所以接下来的两章我把 runtime 和 harness 分别拎出来仔细讲。3. 逐层拆解 Agent Runtime执行能力从哪里来3.1 Runtime 的本质是“执行循环”而不是“运行环境”很多人把 runtime 理解成“代码运行的环境”这是一个常见的误解。Python runtime、Node.js runtime 确实是这个意思但 Agent Runtime 不太一样。Agent Runtime 的核心是一个循环loop模型生成回复 → 如果回复里包含工具调用请求 → runtime 去执行这个工具 → 把执行结果返回给模型 → 模型继续生成 → 直到模型不再请求调用任何工具。这个循环在行业里通常被称为 agent loop 或者 tool-calling loop。伪代码示意 while model_wants_to_call_tools: response model.generate(messages) if response.has_tool_call: result runtime.execute_tool(response.tool_call) messages.append(roletool, contentresult) else: return response如果你把这个循环写出来就会发现它其实不复杂。复杂的部分是循环里的每一个环节在真实世界里会遇到的问题工具执行超时了怎么办沙箱内存爆了怎么办模型连续调用同一个工具 50 次形成了死循环怎么办工具执行产生的外部副作用是否需要回滚所以runtime 的实际职责比“跑代码”要广得多。一套完整的 Agent Runtime 至少要处理这些事模型推理调用封装对模型 API 的请求处理流式输出、重试、超时。工具协议解析把模型输出的 JSON 形式的工具调用OpenAI function calling 格式或其他格式转换成真实的函数调用。沙箱执行工具代码在一个受限环境里跑限制网络、文件系统、CPU 和内存。上下文管理维护消息历史处理超过上下文窗口的截断与摘要。状态保持多轮对话之间保持会话状态同一个 runtime 实例或不同的实例。安全策略执行敏感操作前需要人工确认这通常由 runtime 提供 hook但策略内容由上层 harness 决定。3.2 Runtime 的隔离级别决定了你的工具能疯到什么程度工具的执行环境是 runtime 里最要命的决策点。你让 Agent 调用一个 Python 函数算个数学题那直接在进程里调就行了。但如果你的 Agent 要执行任意用户输入的代码或者要跑一个可能删除文件的 shell 命令那隔离级别直接决定了出事后你还能不能睡得着觉。我实际接触过的隔离方案大致有四档隔离级别实现方式适合场景风险进程级直接在 Agent 主进程里调函数工具都是自研可信代码代码写崩了就全崩子进程级用 subprocess 或 node worker 跑工具工具偶尔需要独立进程资源隔离弱容易互相影响容器级Docker 或 gVisor 起一个临时容器要执行不可信代码性能开销大镜像管理复杂解释器/VM 级沙箱化 Python 解释器、Wasm VM轻量代码执行、多租户场景兼容性有限有些库跑不了选哪种隔离不只是安全团队说了算它直接影响 runtime 的性能和可用性。我见过一个做数据分析 Agent 的团队为了绝对安全把每个工具调用都塞进一个新起的 Docker 容器结果是单轮对话的工具调用延迟从 200ms 涨到了 3 秒用户根本等不起。后来他们改成容器常驻池 容器内进程级隔离延迟才降回来。3.3 Runtime 的边界它不管“为什么”只管“怎么安全地执行”这是 runtime 和 harness 最本质的分界线。Runtime 不关心 Agent 当前的任务目标是什么不关心你用哪个系统提示词不关心这个工具该不该调用、调用的权限审批流程是什么。它只关心既然上层决定要调用这个工具我怎么把它跑得又快又稳又安全。这个边界划分在工程上很有价值。因为“怎么执行”和“要不要执行、怎么组织执行计划”是两类完全不同的复杂度把它们分开你才能独立地升级每一侧。比如你的模型从 OpenAI 换成了 Claude工具调用协议从 function calling 换成了 tool use那大概率只需要动 harness 的适配层runtime 的执行引擎不用变。反过来你想把沙箱从 Docker 换成 Firecracker只要保持工具调用接口不变harness 完全不受影响。理解了 runtime 的边界之后我们来看到它头顶上的那一层。4. 再看 Agent Harness编排与治理的“外壳”4.1 Harness 最核心的一件事把模型、工具、策略“装配”成一个可用的 Agent如果说 runtime 是执行循环那 harness 就是装配与治理层。它的读者不是工具代码而是“整个 Agent”这个对象。harness 回答的问题是这个 Agent 由哪些工具组成它的系统提示词是什么遇到敏感操作时怎么打断调用失败了几次就放弃整个过程怎么被观测和审计具体拆开harness 的职责可以列得很长但核心逃不出这五块工具注册与 schema 管理你要给 Agent 暴露哪些工具每个工具的 JSON Schema 是什么哪些工具只在特定场景下可见这个清单是动态的harness 负责在每一轮推理前把它转换成模型能理解的函数定义。提示词装配系统提示词、few-shot 示例、工具使用说明、当前会话的目标描述这些内容按什么模板拼到一起哪些是固定的哪些是动态注入的策略注入与审批流程读文件可以自动执行删除文件必须人工确认调外部 API 需要额外审计——这些策略的实体是 harness 管理的运行时由 harness 根据策略决定是直接放行、挂起等待审批、还是直接拒绝。观测与追踪每一轮推理的输入输出、工具调用的耗时和 token 消耗、失败重试的记录——这些观测数据由 harness 统一采集并送往日志系统或 tracing 系统。Runtime 的发现与加载这就是 plugin registry 发挥作用的地方。harness 在启动时需要知道“我要驱动哪个 runtime”它去 plugin registry 里查找已注册的 runtime 插件加载对应的适配器然后才开始跑任务。4.2 Plugin registry 在 harness 里扮演什么角色回到开头那行报错。agent harness runtime codex is unavailable because its plugin registry...这句话翻成人话就是harness 在启动时试图从它的插件注册表里找一个叫 codex 的 runtime结果没找到。为什么会找不到最常见的几个原因对应的 runtime 插件没安装某些 Agent 框架把运行时作为插件发布主程序只包含 harnessruntime 需要单独安装。版本不匹配插件注册表里记录了旧版本的 runtime新版本改了注册名或命名空间harness 按老名字找不到。配置路径问题插件注册表文件存在某个自定义路径下而当前环境的配置指向了另一个路径。环境变量缺失runtime 插件依赖某些环境变量比如 API Key、沙箱根目录环境变量缺失时插件在加载阶段就静默失败了。我为什么说这个报错是个很好的教学案例因为它精准地暴露了分层结构报错的发出方是 harness我在找 runtime报错的内容是 plugin registry我通过插件注册表找报错的对象是 runtime我要找的东西。你要是把这三层混成一个概念那你连这个报错的排查方向都会找错——比如你会去重新安装大模型的 SDK但这个问题跟模型 SDK 一点关系都没有。4.3 Harness 与 Runtime 的一次完整交互为了把这两层的配合讲清楚我描述一次真实的交互过程假设我们用的是一个典型的 Agent 框架1. 用户输入帮我看看当前目录下有哪些 Python 文件 2. Harness 装配提示词系统提示词你是一个文件管理助手 用户消息 工具清单list_files, read_file 3. Harness 把装配好的请求发给模型通过 runtime 的模型调用能力 4. 模型返回一个工具调用list_files(directory.) 5. Runtime 解析这个工具调用在沙箱里执行 list_files 6. Runtime 把执行结果[a.py, b.py]作为 tool 角色的消息返回给模型 7. 模型基于结果生成最终回复当前目录下有 a.py 和 b.py 两个文件 8. Harness 把最终回复返回给应用层同时记录整轮的 trace 数据在这个流程里第 2 步和第 3 步主要是 harness 的活第 4 到第 6 步是 runtime 的活第 7 步又回到了模型推理通过 runtime 调用第 8 步是 harness 的收尾工作。注意一个细节Harness 在启动时就去 plugin registry 找到了 runtime所以在第 7 步之前runtime 早就被加载到内存里了。这就是那个报错发生在“启动阶段”而不是“执行阶段”的原因——harness 根本走不到第 3 步在第 0 步就卡住了。5. 不是竞争关系一张对比表看懂两者的分工5.1 核心维度对比很多人在网上搜“harness 和 agent 的区别”潜台词其实是“我都用 Agent 框架了为什么还要关心这些底层概念”。我先直接回答因为你不关心报错来了就抓瞎。维度Agent HarnessAgent Runtime一句话定位编排与治理层执行与循环层核心问题这个 Agent 怎么做对、做得可控这个任务怎么跑起来、跑得稳主要职责提示词装配、工具注册、策略审批、观测采集、插件发现推理循环、工具执行、沙箱隔离、上下文管理交互方向调用 runtime 提供的执行能力被 harness 调度向上返回执行结果故障典型表现配置错误、工具不可见、策略不生效、找不到 runtime执行超时、沙箱崩溃、上下文溢出、OOM开发者接触方式配置文件、策略代码、工具定义API 接口、沙箱参数、环境变量类比驾驶舱、控制台、工艺卡发动机、机床、虚拟机这个表里的“故障典型表现”值得仔细看因为实际排错时你通常不是直接从概念出发而是从一个异常现象出发反向定位。5.2 两层循环Runtime 管单步工具的循环Harness 管整个任务的循环还有一个理解这两层关系的好视角——循环嵌套。Runtime 内部的 agent loop 解决的是“当前这一步要不要调用工具、调用哪个、结果如何反馈给模型”。它循环的粒度是单步决策。Harness 层面的循环解决的是“整个任务分几步完成、每步需要哪些工具、中途要不要切换策略、要不要让用户确认”。它循环的粒度是任务执行。我举个实际场景你让 Agent“把这个 CSV 文件里的数据清洗一遍然后画一张分布图”。Harness 可能会把任务拆成先检查文件结构再确认清洗规则然后执行清洗最后画图。每一步 Harness 都会根据当前状态动态调整工具清单——第一步只需要“读文件”类工具最后一步需要“绘图”类工具。而 Runtime 只在 Harness 拆好的某一步内做它的工具调用循环比如在“执行清洗”这一步Runtime 循环 3 次调用清洗函数 → 发现数据缺失 → 再调用填充函数 → 重跑清洗。这种两层循环的设计不是什么新东西但大部分 Agent 框架的文档不会直接告诉你。你是从“这个 Agent 为什么做了这么多多余的推理轮次”这种性能问题里慢慢悟出来的。6. 回到报错现场codex runtime unavailable 的完整排查链路6.1 逐字拆解报错信息我还是第一次看到这行报错时的反应“好像跟 codex 有关系是不是 Codex CLI 没装好”但如果你把报错拆开看信息量其实非常大error: agent harness runtime codex is unavailable because its plugin registry...agent harness报错的来源——这是一个 Agent 编排层在报错。runtime codex抱怨的对象——一个叫 codex 的执行运行时。is unavailable状态——不可用。because its plugin registry...原因——插件注册表层面出问题了。注意“因为插件注册表不可用”和“插件注册表里没有 codex”是两种不同的原因后者更常见。后缀被截断了但我们做排错的时候不能只盯着一截内存要把完整报错打出来看。6.2 根因分类我遇到的和别人遇到的根据我在几个不同 Agent 框架里遇到的情况这类“runtime unavailable because plugin registry”的根因大概分四类根因类别具体表现解决方向插件未安装重新部署了环境只装了主程序runtime 插件没装上检查插件安装命令单独安装 runtime插件已安装但未注册插件文件存在但注册表配置里没有对应条目执行插件注册命令或修改配置文件版本不匹配框架升级后runtime 的注册名改了旧名字找不到新插件升级 runtime 插件或调整配置里的名称加载失败被跳过插件加载时抛异常harness 把它标记为“不可用”看完整日志找到插件内部异常第四类最坑。它表面上看起来是 “registry 的问题”实际上插件在启动加载阶段就因为缺依赖或者环境变量不对而崩了harness 只能把它的状态标记成不可用。如果你只盯着 registry 改配置永远解决不了问题。6.3 一步一步怎么查我建议按下面的顺序来排查而不是从网上随手抄一条命令确认框架和插件的版本对应关系很多 runtime 插件对框架主版本有强依赖大版本之间注册名、配置 schema 都可能变。先查一下你当前安装的框架版本对应的 runtime 插件版本是什么。查看插件注册表内容一般插件的安装命令都会往注册表里写条目注册表可能是一个配置文件也可能是一个本地存储目录。找到它确认里面有没有 codex 这个名称。没有 → 插件没注册有但状态是 disabled → 可能是升级时被禁用了。寻找完整的加载日志如果注册表里有条目但状态异常去看启动日志里插件加载阶段的详细信息。重点看有没有异常堆栈——很多时候真正的错误信息藏在这一段比如“import error: module x not found”或者“invalid config field: y”。检查 runtime 插件依赖的环境和路径比如代码沙箱依赖某个特定的目录是否存在、Python 版本是否满足要求、环境变量是否被正确注入。这些配置在 CI 环境里尤其容易丢失。重装插件而不是重装主程序我见过很多人一上来就npm install或者pip install重装整个框架其实框架的 harness 代码完全没问题重装它一点用都没有。把 runtime 插件单独重装一次让它重新执行注册逻辑往往就能解决。验证修复重装后用框架自带的命令查看已注册的 runtime 列表确认 codex 出现在列表里且状态为 available再跑一遍启动流程。6.4 修复之后这堂课才算真正上完修好了报错之后我强烈建议你把这次的排查过程记下来尤其是你最后定位到的根因。因为同一个“runtime unavailable”报错在不同环境下可能是完全不同的问题——在一个人这里是因为插件没装另一个人那里可能因为版本不匹配第三个人那里是插件加载阶段静默失败。你把根因记下来下次团队里有人遇到同样的报错你就能在五分钟内给出方向而不是重新走一遍整条链路。这恰恰解释了“为什么要分清 harness 和 runtime”——排查这个报错的每一步本质上都是在“harness 的配置表现”和“runtime 插件的内部状态”之间来回切换视角。你脑子里没有这两层的模型就容易被“codex”这个名字带偏以为问题出在 Codex 本身上。7. 落到选型上你的项目该先关心 runtime 还是先关心 harness7.1 绝大多数团队的误区先选框架再被框架绑架每次有朋友问我“我要做个 Agent 应用该用哪个框架”我一般会先反问一句你的工具代码准备在哪跑是直接在自己服务端进程里调还是要执行用户上传的不可信代码这个问题才是真正的分岔口。很多人的选型路径是这样的看到一个 Agent 框架社区活跃、例子多、一键 demo 很惊艳就直接入了。做了两周后发现内置的代码执行沙箱性能不行想换一个结果框架的 runtime 和 harness 深度耦合工具注册、消息格式、提示词模板全绑在一起一换等于重写。这就是典型的“被框架绑架”。反过来如果你一开始就明白 runtime 和 harness 是两个独立模块你会在选型时多问一句这个框架的 runtime 能不能单独替换它有没有暴露标准的工具执行接口它的 plugin registry 是开放的吗7.2 Runtime 选型三个问题定方向给项目选 runtime我建议先回答这三个问题你的工具是可信代码还是不可信代码如果工具都是你自己团队写的一个子进程级 runtime 就够了没必要为沙箱隔离付出太多性能代价。如果要执行外部输入直接往容器级隔离走。你的工具主要是函数调用还是自由代码执行函数调用比如调 Slack API、查数据库对 runtime 的要求很轻重点在协议解析和错误处理。自由代码执行比如用户上传一段 Python 脚本让 Agent 跑对 runtime 的要求重得多重点在隔离、资源限制和超时控制。你的延迟敏感度有多高每一步工具调用多花 2 秒在离线分析场景里没人管在客服对话场景里用户早就流失了。先测一下 runtime 的典型工具调用延迟再决定要不要上重隔离方案。7.3 Harness 选型也看三个问题select harness 的时候也问自己三个问题策略的复杂度有多高如果只是“列出工具清单全自动执行”随便一个轻量 harness 都够。但如果你需要分级审批、敏感操作拦截、审计日志、按用户维度给不同的工具可见性那 harness 的策略扩展能力就是最重要的选型指标。观测系统是不是已有的基建Harness 把 trace 往哪里送是只能送它自家的仪表盘还是能对接你现有的日志和监控体系在你已经有一套成熟的可观测平台的情况下接不通永远是硬伤。社区和插件生态活跃度怎么样Runtime 像是标准零件Harness 像是整个驾驶舱的设计。驾驶舱好不好用很大程度上看你有没有足够多的“仪表”可以选——预置工具、连接器、模板。生态活跃的 harness 能帮你省掉大量从零写装配逻辑的时间。7.4 一个终极自检方法你选完技术栈之后可以用这个方法来验证你的分层是否合理如果明天我要把 runtime 从 A 换成 B我的 harness 层代码需要改多少如果答案是“需要改工具注册格式、改消息协议、改提示词模板”说明框架把两层揉得太紧了你的可替换性堪忧。如果答案是“只需要改一个适配器配置”说明你选对了。反过来也一样如果明天要换一个 harness 实现我的 runtime 能否继续在用能说明你的 runtime 层足够通用和标准。这两个问题看着简单但很少有人在项目启动前问自己。我见过太多团队在项目中期想换执行环境结果发现 harness 层写满了对特定 runtime 的强依赖最后只能推倒重来。最后说几句题外话回到开头那行报错。如果你现在再看到类似的信息应该能条件反射地意识到这是一个 harness 在启动时找不到或加载不了 runtime 的典型信号不是“框架坏了”也不是“大模型配置错了”而是位于中间这一层插件发现机制出了问题。顺着这个思路去查多半能快速定位。我自己踩过几次这个坑之后养成了一个习惯拿到任何 Agent 相关的报错先问一句“这行报错是哪个组件发出的”如果报错里带了组件名就先搞清楚这个组件在整个链路里的位置——它在 harness 上面在 harness 里面还是在 runtime 里。很多时候答案就藏在报错信息的主语里。