拓冰建站拓冰建站
首页 / 资讯中心 / 正文

JSON-RPC 2.0为何成为AI Agent Harness通信首选?协议原理与实战解析

1. 老协议的新甲方AI Agent 这轮热潮为什么偏偏带火了 JSON-RPC 2.0我第一次意识到 JSON-RPC 2.0 又火了是在翻某个 AI Agent 开源项目的源码时。代码仓库里没有我想象中的 REST 接口设计取而代之的是一排排类似{jsonrpc: 2.0, method: tools.call, params: {...}, id: 7}的消息。说实话那一瞬间我有种穿越感——这玩意儿不是 2010 年就该被 REST 拍死在沙滩上的老古董吗怎么现在成了 Agent 生态里最眼熟的面孔这不是偶然。从 Language Server ProtocolLSP到 Model Context ProtocolMCP再到各种 Agent Harness 内部把工具调用、技能注册、会话管理串起来的通信层JSON-RPC 2.0 的身影无处不在。它原本诞生在 XML-RPC 和 SOAP 混战的年代却在十几年后等来了一个全新的甲方AI Agent。这篇文章是漫话 Agent Harness系列的前置篇。在聊 harness 本身之前我们必须先把这套通信语言聊透——因为在几乎所有 Agent Harness 里JSON-RPC 2.0 就是那个连接智能决策和具体工具的脐带。你不搞懂它后面看 harness 的源码就像看一台没有说明书的机器。1.1 先理清一个高频热词harness 和 agent 到底什么关系最近harness 和 agent 区别这个搜索词一直在涨说明很多人被这两个概念绕晕了。我从实战视角给一个直白的划分agent 是那颗会思考的脑子harness 是支撑脑子运转的整个驾驶舱。agent 负责规划、推理、决定下一步调用什么工具它本身不关心消息怎么编码、进程怎么拉起、插件怎么注册、超时怎么处理。而 harness 管的是这些脏活累活启动和销毁运行时、维护工具注册表、把 agent 的意图翻译成具体协议消息、隔离单个工具崩溃造成的损害、收集日志和轨迹供调试。举个实际的例子很多人搜过这么一条报错agent harness runtime codex is unavailable because its plugin registration failed。注意关键词——plugin registration。这是 harness 层面的问题某个插件在注册阶段挂了导致整个 runtime 不可用。这时候 agent 本身可能毫不知情它还在那儿等工具返回结果呢。这就是典型的脑子没坏驾驶舱出了故障。而 JSON-RPC 2.0恰好就是驾驶舱里各个仪表盘、操纵杆和控制台之间通用的语言。你按下一个按钮method 调用仪表盘亮起一个数值result 返回所有设备之间都用统一格式的指令卡交流。这就是为什么聊 Agent Harness 之前必须先聊这套协议。1.2 为什么 2025 年回头看这个协议反而成了最优解在 AI Agent 爆发之前JSON-RPC 2.0 最广为人知的就业岗位是 LSP——Visual Studio Code 里那个让编辑器懂得各种语言智能提示的协议。前端开发者天天用它却很少有人意识到自己其实一直在跟 RPC 打交道。Agent 热潮起来之后情况完全不同了。工具调用function calling、技能注册skill registration、MCP 服务器、Agent SDK 的运行时通信全都需要一个共同的东西一种纯文本、无状态、能双向发起调用、并且天然带请求-响应关联机制的消息格式。翻遍现有的协议JSON-RPC 2.0 几乎是唯一一个各方面都不需要做伤筋动骨改造的老将。后面我会展开讲它到底哪里适配先记住这个结论它不是被时代淘汰后又被挖出来的化石而是一把本来就在只是之前没被人用在刀刃上的钥匙。2. JSON-RPC 2.0 的语法全景三种句型加一张错误码表不管你在哪个 Agent 项目里见到 JSON-RPC 2.0它翻来覆去其实就那几种消息。学它不需要看几百页规范把下面这套句型吃透就够用了。2.1 Request / Response / Notification 三件套Request请求是所有调用的起点。它长这样{ jsonrpc: 2.0, method: tool.get_weather, params: { city: Beijing, unit: celsius }, id: 1 }四个字段各司其职。jsonrpc 固定是 2.0用来告诉接收方按哪个版本的规范理解我。method 是被调用方法的名称在 Agent 生态里它对应的通常就是某个工具名、某个技能名、或者某个插件的公开接口。params 是参数可以是数组按位置传参也可以是对象按名称传参我个人强烈建议在 Agent 场景一律用对象——因为 LLM 生成的参数天然是键值对按位置传参很容易被模型搞错顺序。id 是这个请求的唯一标识响应靠它来对上号。Response响应是对请求的回应。成功时返回 result{ jsonrpc: 2.0, result: { temperature: 23, condition: sunny }, id: 1 }失败时返回 error{ jsonrpc: 2.0, error: { code: -32602, message: Invalid params: missing required field city, data: { field: city } }, id: 1 }注意 error 对象的三个子字段code 是错误码message 是人类可读的说明data 是可选的附加信息。在 Agent Harness 里data 这个字段特别有用——你可以把 LLM 需要拿到的结构化错误信息塞进去让模型下一次调用时能自我纠正。Notification通知是特殊形态的请求它不带 id。{ jsonrpc: 2.0, method: agent.log, params: { level: info, message: tool call finished } }协议规范规定通知不需要也不允许有响应。接收方处理完通知绝不能回消息。这个设计的价值在于日志、进度上报、取消信号这类说了就行、不用等回复的场景。但在实际工程项目里通知也是最容易被误用的地方——我后面专门有坑要讲。2.2 错误对象与保留 code 区间JSON-RPC 2.0 规范保留了下面几个标准错误码实现方必须遵守code含义什么时候出现-32700Parse errorJSON 都解析不了通常发生在传输层拿到一串乱码-32600Invalid Request消息结构不合法比如缺 method、jsonrpc 版本不对-32601Method not found方法名在注册表里查不到-32602Invalid params参数类型或数量不对-32603Internal error方法内部抛了没兜住的异常-32000 到 -32099Server error给服务端实现自定义错误用的保留区间从 -32768 到 -32000 这个范围是协议预留的除了标准错误码你可以在合规的前提下自定义。在 Agent Harness 里一个很常见的做法是用 -32000 区间定义自己的语义比如 -32001 表示工具执行超时-32002 表示模型上下文过长。2.3 Batch 机制一次投递一叠请求Batch 指的是把多个请求打包成一个 JSON 数组一次发送[ {jsonrpc: 2.0, method: tool.list, id: 1}, {jsonrpc: 2.0, method: tool.add, params: [1, 2], id: 2}, {jsonrpc: 2.0, method: agent.log, params: {msg: batch}} ]响应则是一个数组里面只包含那些带 id 的请求的响应。也就是说上例中第三个通知在响应数组里不会出现。这个数量不一定对等的细节坑过无数人。Batch 在 Agent 场景里最常见的用法是并行工具调用Agent 一次决定要同时查询三个城市的天气harness 可以把三个工具调用打包成一批发出去再集中收结果。这样能显著降低进程间通信的往返次数。2.4 跟 REST、gRPC、原生 WebSocket 的一次正面比较既然要做选型就得知道为什么 Agent Harness 不选 REST 也不选 gRPC。我给几个关键维度的对比维度JSON-RPC 2.0RESTgRPC原生 WebSocket消息模型命令式 RPC资源式 CRUD强类型 RPC任意消息响应关联内建 id 字段天然一一对应调用上下文无内建机制流式支持规范不支持靠 SSE 补丁原生支持天然支持耦合程度极低无代码生成中高需生成桩代码低可读性好纯 JSON好差二进制视消息而定服务发现不管不管需要配套生态不管REST 的核心思想是对资源进行增删改查但 Agent 世界里你面对的是请你执行一个动作——查天气、跑代码、搜知识库这些动词化语义硬套 REST 要么变成一堆自定义 action 端点要么变成 POST 一个万能 body最后只会比现在更乱。gRPC 的问题是太重你得先定义 .proto 文件、生成客户端和服务端代码、处理 HTTP/2 连接管理这对一个可能只有几十个方法的 Agent 工具层来说性价比太低了。而且 gRPC 的响应是二进制的你没法拿一条消息喂给 LLM 做人肉调试。JSON-RPC 2.0 正好在中间足够结构化又足够轻。纯文本意味着任何语言、任何环境都能解析无状态意味着会话上下文完全由 harness 自己管理协议层不需要记忆任何东西id 机制让并发调用和批次调用天然可控。3. 为什么 Agent Harness 偏偏选中它从协议特性反推设计动机技术选型从来不是看功能列表有多长而是看几个核心痛点是否被精确命中。JSON-RPC 2.0 能被 Agent 生态反复 pick靠的其实就是四个恰好。3.1 method 名跟工具、技能、插件天然一一对应Agent Harness 要解决的核心问题之一是把一堆零散的工具、技能、插件整理成统一的调用入口。JSON-RPC 2.0 的 method 字段就是一个字符串——它可以是tool.get_weather、skill.code_review、plugin.git_operations.push。这种带命名空间的字符串在注册、过滤、鉴权上都非常干净。我来给你看一下实际工程里的映射关系。一个 Agent 技能skill通常是一段预定义的提示词加上一个可执行函数。当 harness 启动时它会扫描所有已注册的技能把它们登记到方法注册表里methods {} for skill in skills: methods[fskill.{skill.name}] skill.executeLLM 在推理时输出的工具调用经过解析之后变成这样的格式Call: {method: skill.code_review, params: {repo_path: /tmp/demo, language: python}}harness 拿到这个结构把 method 拆解成命名空间和函数名去注册表里找到对应处理函数然后执行。你可以把 JSON-RPC 2.0 的 method 字段理解成函数指针的名字——整个工具注册表就是一个 string-to-function 的映射没有任何多余概念。3.2 无状态加显式关联让上下文由外部管理JSON-RPC 2.0 本身不维护任何会话状态。每个请求都是独立的靠 id 字段完成响应关联发起方自己管理这个请求还在不在等待。这个设计在 Agent 场景里反而是优点。因为 Agent Harness 需要极其精细地控制上下文生命周期一个 Agent 会话可能包含系统提示词、工具调用历史、token 统计、用户偏好等多层状态如果协议层也掺和进来搞 session、搞 cookie状态管理的复杂度会爆炸。让协议保持纯粹把上下文全权交给 harness这是一个非常干净的分层。这么说吧JSON-RPC 2.0 负责把消息准确送到harness 负责记住一切。协议层笨一点上层才好控制。3.3 双向调用角色模糊才是 Agent 通信的常态传统 C/S 架构里角色是清晰的客户端请求服务端响应。但在 Agent 生态里调用关系是双向的、动态的。harness 可以调用 agent 的决策接口比如请根据以下工具结果做出下一步计划agent 需要调用工具获取环境信息工具执行过程中可能通过 notification 向 harness 上报进度甚至工具可以反过来调用一个回调型方法请求 harness 提供更多上下文。JSON-RPC 2.0 对谁来发起请求没有预设它只定义了消息格式和关联规则这让任何一端都能成为调用方。MCPModel Context Protocol就是最典型的例子。它的整个会话流程——initialize、notifications/initialized、tools/list、tools/call——全部是 JSON-RPC 2.0 风格的消息。客户端可以调服务端的工具服务端也可以通过 notification 主动推送资源变更。这种自由度REST 那种客户端永远主动的模式根本给不了。3.4 短板也很明显流式、二进制、服务发现它全不管说完了优点必须泼盆冷水。JSON-RPC 2.0 规范本身就那么几页纸它把所有高级功能都留给了传输层或者上层实现。具体来说有三大短板。第一是流式响应。规范里没有流式这个概念一个请求只能对应一个完整响应。但 Agent 场景里LLM 生成令牌本身就是流式的工具执行过程的中间输出也是流式的。怎么办社区的实际做法是在 JSON-RPC 2.0 之上做扩展比如 MCP 用 notification 来传递流式片段或者干脆把 transport 换成长连接用多条递增 id 的消息模拟流。说白了这是一层补丁。第二是服务发现。JSON-RPC 2.0 不回答对方有哪些方法可以调用这个问题。所以 MCP 里专门设计了tools/list这个类方法harness 要主动查询能力列表。这其实是个巧妙的补充——把发现机制本身也做成了 RPC 调用。第三是安全性。因为协议没有内置鉴权暴露一个裸 JSON-RPC 端点就等于把你的所有方法公之于众。我见过不止一个团队把 agent 的 JSON-RPC 端口直接绑到公网method 名随便猜都能被调用。这种错误很致命你想想一个shell.run方法如果被匿名调用会发生什么。后面我会专门讲防护的做法。4. 手写一个最小 JSON-RPC 2.0 端点并塞进 Agent 调用链光说不练假把式。我带你从零写一个能跑的最小实现然后把它接进一层极简的 harness 骨架里。这个实现我用 Python 标准库就能写完不用装任何第三方包你可以直接抄。4.1 先定传输层我选标准输入/标准输出JSON-RPC 2.0 协议本身不规定传输方式HTTP、WebSocket、stdio、Unix socket 都行。在本地 Agent Harness 场景里最常用的做法是子进程 stdio——harness 启动一个工具服务器子进程往它的 stdin 写单行 JSON 消息从它的 stdout 读单行 JSON 响应。为什么选 stdio因为它是进程间通信里最简单、最不容易受环境变量和端口冲突影响的方式。你不需要开端口、不需要处理 CORS、不需要担心防火墙子进程被 harness 拉起当 harness 退出时子进程也跟着结束生命周期管理天然对齐。MCP 的本地模式走的就是这条路。4.2 实现服务端核心注册表与分发逻辑下面这个文件保存为rpc_server.py它构建了一个极简但完整的 JSON-RPC 2.0 服务端。#!/usr/bin/env python3 极简 JSON-RPC 2.0 服务端基于 stdin/stdout 传输。 import json import sys class RpcError(Exception): def __init__(self, code, message, dataNone): super().__init__(message) self.code code self.message message self.data data class RpcServer: def __init__(self): self._methods {} def method(self, name): 注册方法把函数名映射到调用名字符串。 def decorator(fn): self._methods[name] fn return fn return decorator def _dispatch(self, request): if not isinstance(request, dict): raise RpcError(-32600, Invalid Request) if request.get(jsonrpc) ! 2.0 or method not in request: raise RpcError(-32600, Invalid Request) method request[method] params request.get(params, []) handler self._methods.get(method) if handler is None: raise RpcError(-32601, fMethod not found: {method}) if isinstance(params, dict): return handler(**params) if isinstance(params, list): return handler(*params) return handler() def _handle_one(self, request): is_notification id not in request try: result self._dispatch(request) except RpcError as e: error {code: e.code, message: e.message, data: e.data} except Exception as e: # 业务中没兜住的异常统一转成 -32603 error {code: -32603, message: Internal error, data: str(e)} else: error None if is_notification: return None response {jsonrpc: 2.0, id: request.get(id)} if error is not None: response[error] error else: response[result] result return response def handle_line(self, line): try: payload json.loads(line) except json.JSONDecodeError: # 解析失败时 id 无从得知规范要求 id 为 null return {jsonrpc: 2.0, id: None, error: {code: -32700, message: Parse error}} if isinstance(payload, list): # Batch 响应只保留带 id 的请求的响应 responses [] for item in payload: if isinstance(item, dict) and id in item: r self._handle_one(item) if r is not None: responses.append(r) return responses or None if isinstance(payload, dict): return self._handle_one(payload) return {jsonrpc: 2.0, id: None, error: {code: -32600, message: Invalid Request}} def main(): server RpcServer() server.method(tool.add) def tool_add(a, b): return a b server.method(tool.list) def tool_list(): return [tool.add, tool.get_status] server.method(agent.log) def agent_log(level, message): # 这是通知型方法的典型用法写入日志后不产生实际结果 print(f[{level}] {message}, filesys.stderr) return None for line in sys.stdin: line line.strip() if not line: continue result server.handle_line(line) if result is not None: print(json.dumps(result, ensure_asciiFalse), flushTrue) if __name__ __main__: main()这段代码的骨架在真实项目里可以直接套用。几个设计要点_handle_one里先判断是不是 notification没有 id是的话不管成功失败都不返回响应。这个判断必须放在错误处理之前否则一个出错的 notification 会被错误地返回一个 error 响应违反协议。内部异常全部兜底转成 -32603避免堆栈信息直接泄漏到协议层。你可以把详细堆栈打进 stderr 日志但返回给调用方的 message 要干净。Batch 处理时跳过 notification 的响应保证响应数组里每一项都能跟请求 id 对上。4.3 用一条命令验证服务端把上面的文件存好后直接测试printf {jsonrpc:2.0,method:tool.add,params:{a:3,b:4},id:1}\n | python3 rpc_server.py输出{jsonrpc: 2.0, id: 1, result: 7}再测一个错误场景printf {jsonrpc:2.0,method:tool.nope,params:{},id:2}\n | python3 rpc_server.py输出{jsonrpc: 2.0, id: 2, error: {code: -32601, message: Method not found: tool.nope}}4.4 接一层极简 harness让 LLM 的意图变成协议调用服务端只是半个故事另一半是 harness 这侧的调用逻辑。下面是一个不能再简的 harness 骨架它做的事情是接收 LLM 输出的工具调用指令转成 JSON-RPC 请求发送给服务端子进程再把响应结果返回给 model loop#!/usr/bin/env python3 极简 Agent Harness 骨架负责把 agent 的意图翻译成 JSON-RPC 调用。 import json import subprocess import sys class ToolClient: 连接 RPC 服务端的客户端负责请求生命周期管理。 def __init__(self, proc): self.proc proc self._next_id 0 self._pending {} def start(): proc subprocess.Popen( [sys.executable, rpc_server.py], stdinsubprocess.PIPE, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, textTrue, ) return ToolClient(proc) def call(self, method, paramsNone, timeout10.0): self._next_id 1 req_id self._next_id request {jsonrpc: 2.0, method: method, params: params or {}, id: req_id} self.proc.stdin.write(json.dumps(request) \n) self.proc.stdin.flush() # 同步读一行响应真实 harness 会并发读取并建立 id - future 映射 line self.proc.stdout.readline() if not line: raise RuntimeError(RPC server closed unexpectedly) response json.loads(line) if response.get(id) ! req_id: raise RuntimeError(fid mismatch: expected {req_id}, got {response.get(id)}) if error in response: raise RuntimeError(response[error][message]) return response.get(result) def notify(self, method, paramsNone): 发送一个不需要响应的通知。 request {jsonrpc: 2.0, method: method, params: params or {}} self.proc.stdin.write(json.dumps(request) \n) self.proc.stdin.flush()在真实的模型循环里调用链是这样的LLM 输出一个带 tool_calls 的回复 → harness 解析出method和params→ 调用client.call(...)→ 拿到 result → 把 result 拼回对话上下文 → 再次把上下文交给 LLM。就这么个循环已经构成了 Agent 最基本的行为闭环。我特别想强调一下 id 管理这是很多初学者最容易忽略的地方。上面的实现用了一个单调递增的计数器看起来简单但一旦你要支持并发调用、超时重试、或者把调用分流到多个服务端进程光靠一个递增数字就不够了。现实的方案是生成 UUID 作为 id同时本地维护一张 id → future 的映射表响应一进来就查到对应 future 并 set 结果。我在第五部分会细讲这个坑。5. 集成过程中我实际踩过的四个坑无论你是在写工具服务器、MCP 服务器还是自己的 Agent Harness下面这四个坑我都在真实项目里踩过每一个都花过我至少一个下午来排查。5.1 坑一id 重复所有响应全部串线第一次写多请求并行时我图省事把请求 id 直接用了 LLM 返回的消息序号。结果模型从 0 开始计数harness 上一次会话也从 0 开始两边一交叉id: 3的响应被匹配到了完全不同的请求上工具结果张冠李戴agent 对着北京的天气做出了东京适合带伞的决策。排查了很久才发现问题不在模型也不在工具逻辑而是 id 根本不是一个全局唯一标识。这个坑的根本原因是 id 的语义由调用方自己保证。JSON-RPC 2.0 规范只要求发起方用自己的方式保证 id 唯一但这个唯一性在多客户端、多轮次、多会话场景下非常容易被打破。我的经验是任何跨进程通信中的请求 id一律用 UUID 或毫秒时间戳 随机数的组合不要用自增数字。自增数字看起来清晰但它只在一个进程内、一个时刻线上有意义。5.2 坑二把 Notification 当请求发等回应等到天荒地老这个坑特别隐蔽因为它发生在你调用别人写的工具服务器时。某个方法文档里写着支持 log 通知你看着像是个普通方法于是发了这么一条{jsonrpc: 2.0, method: agent.log, params: {...}, id: 42}注意你在消息里加了id: 42但服务端把它当成普通请求处理了——等等问题恰恰相反。真正隐蔽的坑是文档说某个方法通常作为 notification 发送你照做了但没意识到这个方法其实有返回值。举例来说很多工具服务器有一个session.heartbeat方法作用是告诉服务端我还活着。它在协议定义里是 notification无 id服务端收到后不需要回复。但你如果以为所有方法都应该有响应发完就阻塞等待那就会一直卡住直到超时。处理这个问题的唯一可靠办法是在 harness 的客户端层强制区分 call 和 notify 两种 API。只有真正需要响应的方法才能调 call明确标记为通知的方法只能走 notify。收到一个没有 id 的响应消息时直接丢弃并打 warning因为那本身就违反了协议。5.3 坑三-32700 Parse error 的 id 是 null你根本不知道谁挂了JSON-RPC 2.0 规范规定当请求消息连 JSON 都解析不了的时候服务端应当返回{jsonrpc: 2.0, id: null, error: {code: -32700, message: Parse error}}id 强制是 null因为服务端压根不知道原始消息里写了什么。这在单连接、串行通信时问题不大——反正只有一条管道出错了我直接把这条响应当成刚才那条请求的失败结果就行。但一旦你开始用并发、用 Batch、用多个连接问题就来了一个 -32700 响应进来你不知道它对应哪条请求也不知道是哪个客户端发来的。更麻烦的是如果客户端因为网络抖动把一条完整消息拆成了两半服务端会在第一条看到\n之后就尝试解析必然报 -32700。我的修为在于-32700 不应该是客户端主处理逻辑的一部分而应该是日志系统的一部分。收到 -32700 的客户端要做两件事一是把堆栈现场完整打成日志二是触发主动超时兜底——让所有 pending 的请求在超时后统一报错重试。不要妄图从 -32700 里推断出是哪条请求坏了那不现实。5.4 坑四Batch 请求里沉默的响应和部分成功陷阱Batch 的问题我在 2.3 提过一句通知在响应数组里不占位。如果你写的客户端拿到响应数组后按请求数组的下标去对齐恭喜你下一次通知多了的时候数据全错位。正确的对齐方式只有一个遍历响应数组按每一项里的 id 字段去找对应的请求绝不能用位置去匹配。记住位置匹配在 JSON-RPC 2.0 的 Batch 场景里永远都是错的因为协议从一开始就没承诺响应顺序与原请求顺序一致。另外 Batch 还有另一个隐蔽点部分成功。一批请求发过去可能其中三个成功、两个失败、一个是通知。响应数组里就是三个 result 一个 error顺序还是乱的。你的客户端必须能够容忍这一批没全成功的情况逐个按 id 处理结果该重试的重试该回滚的回滚。6. 这个前置到底前置了什么向 Agent Harness 正篇过渡读到这里你可能已经写出了一个能跑的 JSON-RPC 2.0 服务端和客户端也对协议的各种边角细节有了感觉。但容我提醒一句这些还只是地基Agent Harness 真正复杂的东西全在协议层之上。6.1 通信协议是协议运行框架是框架两码事JSON-RPC 2.0 解决的只是两个进程之间怎么交头接耳。它不告诉你 agent 的生命周期怎么管理不告诉你技能文件怎么组织和加载不告诉你工具注册失败时整个 runtime 该不该停摆不告诉你上下文窗口快满的时候该做摘要还是裁剪。而这些恰恰是 harness 的核心职责。还记得开头那条 codex 报错吗agent harness runtime codex is unavailable because its plugin registration failed。注册失败、runtime 不可用、插件隔离——这些概念全都在 JSON-RPC 2.0 的视野之外。harness 要做的是把这些工程问题全部收敛起来让上层 agent 只管做决策。我的建议是你可以把整个 Agent Harness 理解成三层。最底层是传输与协议层JSON-RPC 2.0 就在这一层中间是运行时与注册层插件扫描、方法注册、故障隔离、生命周期管理最顶层是决策与编排层prompt 组装、工具调用循环、上下文管理、与用户的交互。很多 Agent 项目在这三层之间边界模糊代码变成一锅粥——这是因为协议层和框架层被混在了一起。6.2 从协议往上爬你还会撞上这些设计问题如果你准备深入 Agent Harness下面几个问题是绕不开的它们都在正篇里展开插件注册的失败策略某个插件加载失败是整体失败还是降级跳过codex 那条报错选择的是整体失败但并不是所有 harness 都该这么做。工具调用的超时与取消LLM 发起一个工具调用如果工具卡死了harness 是等待还是强制中断怎么向服务端传递取消信号很多实现靠的是一个tool.cancel方法参数里带着原请求的 id。上下文管理工具返回的结果要不要全部塞回模型上下文怎么判断哪些结果值得保留、哪些只值得写日志这直接决定 token 消耗和推理质量。安全边界哪些方法允许 LLM 调用哪些必须经过用户确认JSON-RPC 2.0 的 method 只是个字符串得靠 harness 的鉴权层把住口子。6.3 三条继续深入的路看你的目标选如果你想让这系列前置篇真正落地我建议你根据自己需求选一条路走路线一实现一个自己的工具服务器。用第 4 节的骨架往里面注册你自己的业务工具比如搜索、计算器、读取本地笔记。目标不是写得多花哨而是把 JSON-RPC 2.0 的请求、响应、通知、Batch 全部跑通体会协议给你带来的约束和自由。路线二深入观察一个成熟 harness 的源码。重点看它的插件注册机制、方法分发器、以及超时取消的实现。找到那行runtime xxx is unavailable because its plugin registration failed对应的代码路径看看一个插件注册失败是怎么冒泡到 runtime 层的这对理解故障隔离非常有用。路线三研究 MCP 规范。MCP 是 JSON-RPC 2.0 在 Agent 生态里最成功的应用它把协议和工具发现机制结合得很好。你去看它的 initialize 握手、tools/list、tools/call 这套流程能直观感受到为什么当初要选 JSON-RPC 2.0。我个人带了这么多项目最大的体会是一个协议的流行程度从来不取决于它的新而取决于它是否恰好卡在某个时代痛点的时间窗口上。JSON-RPC 2.0 是一个 2010 年的老家伙但它极简到不适合做大事的缺陷在 Agent Harness 这个极度需要轻量、灵活、可插拔的战场上反而成了最大优势。这系列的正篇咱们就从这个矛盾与和解开始讲。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门