Easton Notes
八股Agent 应用开发Agent 应用开发专题

OpenClaw 架构与运行链路

返回 Agent 应用开发总览

45. OpenClaw 章节前置说明

openclaw前置

回答重点

直接调 API 就是"一问一答",你发一条 prompt,模型回一条 response,结束。

AI Agent 完全不同,它是一个有状态的循环决策系统,能感知环境、做规划、调用工具执行动作、观察结果,然后自己决定下一步干什么,循环往复直到任务完成。

本质区别有三点:

1)Agent 有工具调用能力,能操作外部世界,比如读写文件、执行代码、查数据库、调第三方接口。单次 API 调用只能返回文本,啥也干不了。

2)Agent 有记忆和上下文,知道自己之前干了什么、拿到了什么结果。单次 API 调用是无状态的,每次都从零开始。

3)Agent 有自主决策循环,自己规划步骤、迭代推进。单次 API 调用是被动的,你问一句它答一句,不会主动行动。

  • 单次 API 调用流程:用户发送 prompt → LLM 处理 → 返回 response,结束。
  • Agent 运行流程:用户提交任务 → Agent 规划下一步 → 调用工具执行 → 观察执行结果 → 判断任务是否完成 → 未完成则回到规划步骤继续循环 → 完成后返回最终结果给用户。

扩展知识

为什么需要 Agent

单次 API 调用能力有限,大模型只能根据你给的 prompt 生成文本,没法真正"做事"。你让 deepseek 帮你改一个 Bug,它能告诉你思路,但没法自己打开文件、定位代码、跑测试、验证修复。

Agent 的出现就是为了弥补这个缺口,让大模型从一个"只会说话的顾问"变成一个"能动手干活的助手"。

Agent 的核心架构

一个典型的 Agent 系统由三大核心模块组成:

1)大模型作为"大脑",负责理解任务、制定计划、决定调用什么工具。OpenClaw 支持多家大模型。

2)工具集作为"手脚",让 Agent 能操作外部世界。常见工具包括文件读写、终端命令执行、浏览器操作、代码搜索等。OpenClaw 内置了文件读写、Shell 执行、浏览器控制、Web 搜索、记忆检索等 25 个核心工具。

3)记忆系统作为"笔记本",维护整个任务的上下文。短期记忆就是当前对话历史,长期记忆可以是向量数据库或者文件系统里的持久化信息。

Agent 的运行循环

拿 OpenClaw 的实现来说,一次 Agent 运行不是简单的请求响应,是一个完整的 turn looprunEmbeddedPiAgent() 启动 Agent Session 后,会在循环中不断解析 LLM 的输出:如果模型说"我需要读一个文件",系统就执行 read 工具,把结果喂回模型,模型再决定下一步。循环持续到模型输出最终文本回复或触发 context overflow 为止。

整个过程就像一个人完成任务:想想要干嘛 → 动手做 → 看看结果 → 再想想 → 继续做。

单次 API 调用更像"问一个问题、拿一个答案",没有这种迭代决策过程。

Function Calling 是 Agent 的关键基础设施

Agent 能调用工具,靠的是大模型的 Function Calling 能力。OpenAI 在 2023 年 6 月给 GPT 加了这个功能,Claude、Gemini 后来也都跟进了。

原理很直接:你在请求里声明一组工具的 JSON Schema,描述每个工具的名称、参数、用途。模型推理时如果觉得需要调工具,就会输出一个结构化的工具调用请求,包含工具名和参数。你的程序拿到这个请求后执行对应工具,再把结果拼回对话历史,让模型继续推理。

这套机制让 Agent 的实现从"靠 prompt 黑魔法解析文本"变成了"结构化地声明和调用",可靠性提升了一个量级。

Agent 的常见坑点

1)Token 消耗巨大。每轮循环都要把完整对话历史发给模型,10 轮循环下来可能吃掉 3-15 万 token。OpenClaw 一次复杂任务跑下来,光 API 费用可能就 2-5 美元。

2)幻觉导致死循环。模型有时候会"幻觉"一个不存在的工具调用,或者反复执行同一个操作停不下来。好的 Agent 框架都会设置最大循环次数和超时机制来兜底。

3)上下文窗口溢出。对话历史越滚越长,早期的关键信息可能被截断。常见的解决方案是做上下文压缩,把早期对话摘要化,只保留关键信息。

面试官追问

提问:Agent 的工具调用失败了怎么办?它会自己处理错误吗?

回答:好的 Agent 框架都有错误处理机制。工具调用失败后,错误信息会被当作观察结果喂回大模型,模型会根据错误信息决定是重试、换个方式操作,还是放弃当前路径换一条思路。比如 OpenClaw 里你让它编辑一个文件,如果 diff apply 失败了,它会看到报错,然后尝试用不同方式重新编辑。但模型也不是万能的,连续失败 3-5 次后一般会设重试上限,避免无限循环烧 token。

提问:Agent 和 RAG 有什么关系?能结合使用吗?

回答:RAG 本质上可以看作 Agent 的一个工具。RAG 解决的是"让模型获取外部知识"的问题,Agent 解决的是"让模型执行复杂任务"的问题。一个 Agent 完全可以把向量检索当作自己的工具之一,任务中需要查资料时调一下 RAG,拿到相关文档再继续推理。LangChain 里的 Retriever 就是这么用的,它就是 Agent 工具箱里的一把工具。

提问:多个 Agent 协作的时候,怎么防止它们互相冲突?比如两个 Agent 同时改一个文件?

回答:Multi-Agent 系统里资源冲突是绕不开的问题。常见做法有几种: 1)用消息队列串行化,所有对共享资源的操作都排队执行。 2)分配明确的职责边界,每个 Agent 只能操作自己负责的文件或模块。 3)加锁机制,类似数据库的悲观锁或乐观锁。 AutoGen 的做法比较简单粗暴,用轮流发言机制,同一时刻只有一个 Agent 在执行,天然避免了并发冲突。CrewAI 则是通过 Task 粒度的分配来隔离,每个 Task 绑定一个 Agent,Task 之间通过依赖关系串联。

提问:Agent 的 token 消耗问题有什么好的优化思路?

回答:核心思路就是减少每轮循环送进模型的 token 数。几个方向: 1)上下文压缩,把早期的对话轮次用摘要替代,只保留关键信息和最近 2-3 轮的完整内容。OpenClaw 就用了类似策略,会对历史消息做裁剪。 2)工具结果精简,工具返回的原始数据可能很大,比如读一个 1000 行的文件,可以只截取相关片段喂给模型。 3)分层调度,简单的工具调用决策用小模型,复杂推理才上大模型。 4)缓存,相同的工具调用结果缓存起来,避免重复执行和重复消耗 token。


46. OpenClaw 是什么?

回答重点

OpenClaw 是一个开源的本地 AI Agent 运行平台

它解决的问题恰好是 ChatGPT、豆包、Claude Code 这些产品各自没覆盖到的地方。

ChatGPT / 豆包这类对话产品,它们本质是"聊天机器人",你问它答,但它不能真正"动手干活"。让它帮你改一个文件、跑一条命令、定时监控一个服务,它做不到,它只能输出文字。

Claude Code 这类 Agent 产品,它确实能动手干活(读写文件、跑终端命令),但它是一个一次性的终端工具。你在命令行里启动它,用完就退出了。它不能 7×24 小时常驻运行,不能对接多个聊天平台,不能定时执行任务,不能主动巡检。

OpenClaw 解决的就是这两个缺口

既有 Agent 的执行能力(能调用工具、能操作系统),又有 Gateway 提供的基础设施能力:常驻运行、多平台接入、定时调度、主动巡检。而且模型随便换,数据全在本地。

核心能力从这个定位出发,分五块:

1)Gateway 常驻网关:这是 OpenClaw 区别于所有终端 Agent 工具的核心。7×24 小时运行的守护进程,崩溃自动拉起,支持心跳巡检(定期主动检查待办事项)和 Cron 定时调度(标准 cron 表达式的定时任务)。这让 AI 从一个"你问它才答"的被动工具,变成了一个"能主动干活"的基础设施。

2)多渠道统一接入:WhatsApp、Telegram、Discord、Slack、iMessage 等 15+ 渠道开箱即用。每个渠道有独立的适配器,消息进来后统一归一化成标准格式,Agent 不需要关心消息来自哪个平台。新增渠道只需写一个适配器插件,核心逻辑零改动。

3)模型无关的 Agent 执行引擎:内置 ReAct 风格的推理循环(LLM 思考 → 调用工具 → 拿到结果 → 继续思考),支持 Anthropic、OpenAI、Google、Ollama 等所有主流模型,切换只改配置不改代码。还支持 fallback 机制,主模型挂了自动切备用模型。

4)插件化扩展体系:工具、渠道、Hook、Provider 都可以通过插件注册,第三方开发者可以在不动核心代码的前提下扩展能力。社区技能通过 ClawHub 分发,一行命令就能安装。

5)本地优先 + 隐私安全:Gateway 跑在你自己的机器上,API Key 自己管,对话历史存本地磁盘,数据完全不经过第三方服务器。


47. OpenClaw 的五层组件架构是怎样的?

回答重点

OpenClaw 把整个 Agent 平台拆成了五层组件,各司其职又首尾相连。

Channel Plugins 是最外层,每个消息平台一个插件,Telegram、Discord、Slack 等各一套,干的活就是协议转换,把平台私有格式翻译成 OpenClaw 内部统一的消息结构。

Gateway 是整个系统的中枢,所有请求都从这过。鉴权、限流、幂等去重全在这一层搞定,然后把消息往下游分发。

Routing 是路由层,拿到消息后根据配置规则决定交给哪个 Agent 处理,同时生成 Session Key 来追踪会话。

Agent Runner 是执行引擎,管理 LLM 调用、工具执行、结果回传这个循环,一个 Agent 跑起来的所有脏活累活都在这。

Context Engine 管对话历史,摄入、组装、压缩全包了,接口化设计,随时可以插拔替换成自定义实现。

一条消息的完整链路:Channel 收到用户消息 → Gateway 鉴权限流后分发 → Routing 匹配到目标 Agent → Agent Runner 驱动 LLM 执行 → Context Engine 维护上下文 → 回复原路返回,经 Gateway 交回 Channel 发出。

扩展知识

为什么要分这么多层

很多人第一反应是"搞这么多层不累吗",但你想想一个 Agent 平台要同时对接 Telegram、Discord、Slack、Web 等,每个平台的消息格式、回调方式、鉴权机制都不一样。如果不抽出 Channel 层做协议转换,核心逻辑里到处都是 if-else 判断平台类型,加一个新渠道得把代码翻个底朝天。

Gateway 单独拎出来也是同理。鉴权、限流、幂等这些横切关注点如果散落在各个模块里,维护成本会爆炸。集中到 Gateway 统一处理,下游组件不用操心这些事。

Routing 独立出来是因为 Agent 系统往往不止一个 Agent。比如一个企业部署了客服 Agent、运维 Agent、数据分析 Agent,一条消息进来得先判断交给谁处理。

OpenClaw 在 src/routing/resolve-route.ts 里实现了路由核心逻辑,支持按 peer(具体用户/聊天)、guild(Discord 服务器)+ 角色、team(Slack 工作区)、account、channel 类型等维度做 binding 匹配。


48. OpenClaw 的入站-路由-执行-出站全链路是怎样的?

回答重点

整条链路可以分成 入站、路由、执行、出站 四个阶段,一共 8 步。

用户在 Telegram、Discord、Slack 这些渠道发了一条消息,首先命中的是对应的渠道适配器,它负责接收原始消息,然后把平台私有格式转换成统一的 MsgContext 结构,包含发送者信息、渠道类型、群组 ID 这些字段,屏蔽掉各渠道的格式差异。

转换完成后进入 dispatchInboundMessage() 做入站上下文补全,然后 resolveAgentRoute() 按优先级匹配到目标 Agent 和 Session Key。

路由匹配完,系统先检查消息是不是斜杠命令,像 /new/reset 这类指令直接在这一层处理掉,不进 Agent。

如果是普通消息,就进入 Agent 执行循环:runReplyAgent()runAgentTurnWithFallback()runEmbeddedPiAgent()

Agent 执行的核心是一个 LLM + 工具调用的循环:先构建系统提示和历史上下文,调用 LLM 拿到输出,解析输出看有没有工具调用请求,有的话执行工具拿到结果再喂回 LLM,如此往复直到 LLM 给出最终回复。

最后通过 ReplyDispatcher 把回复投递回原渠道。

一条用户消息的完整链路,从左到右流转:

用户发送消息 → 渠道适配器接收原始消息 → 转换为统一 MsgContext → dispatchInboundMessage 入站补全 → resolveAgentRoute 路由匹配 → 斜杠命令检查(是命令则直接处理返回)→ runReplyAgent 启动 Agent → LLM + 工具循环(构建提示 → 调 LLM → 解析输出 → 执行工具 → 结果回传,循环直到最终回复)→ ReplyDispatcher 投递回复到原渠道

扩展知识

幂等性保护

所谓幂等,就是"同一个操作执行一次和执行多次,效果一样"。分布式系统里最怕的就是重复执行。用户网络抖了一下,同一条消息可能被渠道推送 2-3 次过来。如果不做幂等处理,Agent 会对同一条消息执行多次,可能产生副作用,比如重复扣费、重复发消息。

OpenClaw 在 Gateway 层给每个请求分配了 idempotencyKey,重复请求直接返回缓存结果,Agent 压根不会被二次触发。

这个设计在 Stripe、支付宝这类支付系统里也很常见,核心思路就是"同一个 key 只执行一次"。

排队机制

Agent 运行不是来一条消息就立刻执行的,中间有两层排队:enqueueSessionenqueueGlobal

enqueueSession 保证同一个 session 内的消息串行执行。想象一下用户连续发了 3 条消息,如果 Agent 同时处理这 3 条,上下文会乱套,回复可能互相矛盾。串行执行确保每条消息都能看到前一条的完整对话历史。

enqueueGlobal 是全局限流,防止突发流量把 LLM API 打爆。比如同时有 200 个 session 都在排队,全局队列控制并发数在一个合理范围内,避免触发 OpenAI 的 rate limit。

排队机制的两层结构:

用户消息进入后,先进 enqueueSession(session 级别队列,保证同一 session 串行),再进 enqueueGlobal(全局队列,控制总并发数)。

Session A 的消息 1、2、3 在 session 队列里排队等待串行执行。Session B 的消息同理。

多个 session 的任务汇入全局队列,受全局并发数限制。最终从全局队列出来的任务才真正调用 LLM API。

Fallback 机制

runAgentTurnWithFallback() 这个函数名已经暗示了它的核心能力。

主模型调用失败时,系统自动切换到配置的 fallback 候选重试。

注意 fallback 只切换 provider 和 model,系统提示词、工具列表等 Agent 配置保持不变,确保切换后的行为语义一致。比如主模型用 GPT-5,fallback 切到 Claude Opus 4.6。

这在生产环境非常实用。OpenAI 偶尔抽风、某个 region 的 API 超时,如果没有 fallback,用户就只能看到一个错误提示。

有了自动切换,用户几乎感知不到后端出了问题,回复可能慢了 1-2 秒,但至少不会断。

Hook 插入点

链路中埋了多个 Hook 可以介入执行过程,类似 Webpack 的 plugin 机制:

1)before_model_resolve 在模型选择之前触发,可以根据用户身份、消息内容动态切换模型。比如复杂的走 Claude Opus 4.6,简单的走免费模型。

2)before_prompt_build 在构建提示词之前触发,可以注入额外的上下文信息。比如从外部知识库拉取相关文档塞进去,做 RAG 增强。

3)llm_input 在 LLM 调用之前触发,可以拦截并修改最终发给 LLM 的完整输入。适合做日志审计、敏感词过滤这类横切逻辑。

流式输出

对于支持流式的渠道,Agent 的回复是边生成边推送的,通过 WebSocket 实时下发 token,不用等全部生成完再发。

用户看到的效果就是"打字机"一样一个字一个字蹦出来,体验比等 5-10 秒后突然弹出一大段文字好得多。

不过不是所有渠道都支持流式。

Telegram 没有原生的 WebSocket 流式推送,OpenClaw 用了两种模拟策略:优先使用 Telegram Bot API 较新的草稿消息接口(sendMessageDraft),如果不可用则 fallback 到先发一条消息再用 editMessageText 循环更新内容,逐步追加生成内容来模拟流式效果。

面试官追问

提问:如果用户连续快速发了 5 条消息,Agent 会怎么处理?会不会每条都触发一次完整的 LLM 调用?

回答:不会每条都独立跑一遍。session 级别的排队机制保证同一个 session 串行执行,第 1 条消息在 Agent 里跑的时候,后面 4 条在队列里排着。等第 1 条处理完,第 2 条才会进入 Agent,这时候第 2 条已经能看到第 1 条的完整对话历史了。不过具体策略可以优化,比如设一个 500ms 的 debounce 窗口,把短时间内的多条消息合并成一条再处理,减少 LLM 调用次数。

提问:fallback 切换模型后,系统提示词和工具列表会不会跟着变?

回答:不会变。OpenClaw 的 fallback 是 model-level 的,只切换 provider 和 model,系统提示词、工具列表等其余 Agent 配置全部保持不变。这个设计是有意为之,fallback 的目的是应对 provider 故障,不应该改变 Agent 的行为语义,否则用户会感知到前后不一致。

提问:幂等性 key 是怎么生成的?如果两个不同用户恰好发了一模一样的消息内容,会不会被误判为重复?

回答:不会。idempotencyKey 不是根据消息内容生成的,通常是用渠道推送过来的消息 ID,比如 Telegram 的 message_id、Discord 的 message snowflake。这些 ID 在渠道层面就是全局唯一的,跟消息内容无关。两个用户发了一模一样的文字,message_id 完全不同,不会触发幂等拦截。


49. OpenClaw 的 Agent Runner 是如何调度的?

回答重点

Agent Runner 是 OpenClaw 的核心调度器,可以理解为"指挥中心",负责协调 LLM 调用、工具执行、错误处理等所有环节。

一次完整的 Agent 运行从用户发消息到最终输出,大致经历以下阶段:

1)排队,先进 session 级队列(保证同一会话串行),再进全局队列(控制总并发),防止资源被打满

2)准备,解析 workspace、provider/model、thinking level 等基础参数

3)插件 + Hook,加载运行时插件后,触发 before_model_resolvebefore_agent_start 钩子,插件可以在模型解析之前动态覆盖 provider 和 model

4)模型解析 + 鉴权,根据(可能被 Hook 修改过的)配置确定模型定义、上下文窗口大小,并按优先级选出可用的 API Key

5)尝试执行(核心,可重试):

  • 创建或恢复 Session,加载历史消息
  • 注册工具集(统一走 customTools 路径,保证沙箱和策略过滤一致性)
  • 根据 Provider 设置流式引擎(Ollama 直连、OpenAI WebSocket、通用 HTTP 等)
  • 触发执行循环:LLM 调用 → 工具执行 → 结果回传 → 再调 LLM,直到模型认为任务完成

6)溢出降级,如果上下文超限:先 compaction 压缩历史 → 再截断超大 tool result → 都不行就报错引导用户开新会话

整个流程的设计思路是每个阶段都可插拔。插件通过 Hook 介入、模型和 Provider 可动态切换、工具集按需组合。

扩展知识

attempt + fallback 容错机制

Agent Runner 不是跑一次就完事,容错分两层:

1)Auth Profile 轮转:如果一次尝试因为 auth 失败、限流或服务过载挂了,Runner 会自动切到同 Provider 的下一个 API Key 重试。比如配了三个 OpenAI Key,第一个被限流就自动换第二个。

2)模型级 Fallback:如果所有 Key 都轮完还是失败,Runner 向外层抛出 FailoverError,外层的 model-fallback 层会切到配置的备用模型。比如 Claude 整体不可用就降级到 GPT-4o,用户几乎感知不到切换。

重试有上限(根据 profile 数量动态计算,范围 32-160 次),不会无限重试。遇到服务过载还会加指数退避,避免继续打爆上游。

工具调用的双层包装

每个工具在注册时会经过两层包装:

1)Hook 拦截层:插件可以在工具执行前异步检查参数、做权限校验,甚至直接阻止执行。这一层还内置了循环检测,防止 LLM 反复调用同一工具陷入死循环。

2)取消机制层:把外部的 AbortSignal 和工具自带的信号合并。当用户发了新消息、超时了、或手动停止时,正在执行的工具可以被中断,不用干等到超时。

流式处理的 Provider 适配

Runner 默认用通用的 streamSimple 做流式输出,但不同 Provider 的流式 API 差异很大,所以会根据 Provider 类型动态替换流式引擎:

  • Ollama:走原生 /api/chat 直连,绕过通用路径以获得更可靠的 streaming 和工具调用
  • OpenAI:支持 WebSocket 通道,减少 HTTP 开销
  • Google:额外 Gemini 特有的 thinking 字段

所有 Provider 还会统一做工具名称规范化(有些模型输出的工具名带空格或前缀),确保工具分发能精确匹配

这层适配做完后,执行循环的代码不用管底下是哪家 Provider,调同一个接口就行。新增 Provider 也只需要写一个流式适配函数。

Context 溢出的三级降级

执行循环跑着跑着 context 可能会超限,特别是工具返回了大量内容的时候。

Runner 对此做了三级自动降级:

1)先尝试 compaction,调用 Context Engine 压缩历史消息,腾出 token 空间

2)compaction 还不够的话,截断超大 tool result

截断策略是动态的:先检测尾部是否包含错误信息或结果摘要,如果尾部重要就保留首尾、砍掉中间;否则只保留开头。截断位置会插入说明提示模型内容被截断了。单个 tool result 最多占上下文窗口的 30%

3)前两步都救不回来,报错降级,告诉用户 context 太长了,建议开新会话

整个过程对用户透明,尽最大努力保证对话能继续下去。

面试官追问

提问:你说排队执行保证并发安全,那如果用户快速连发两条消息会怎样?后面那条是排队等还是直接丢弃?

回答:后面那条消息会进入 session 级队列排队等,不会丢弃也不会并发执行。设计上是嵌套两级队列:先进 session 队列(保证同 session 串行),再进全局队列(控制总并发)。等前一条消息的 Agent 运行完成后才处理下一条。用户体验上是第二条消息会等一会儿才开始响应。

提问:fallback 机制切换模型之后,之前的对话历史格式兼容吗?不同模型的消息格式不一样怎么办?

回答:历史消息以统一的中间格式存储在 session 文件中。切换模型时用的是同一份 session file,新的 attempt 启动时会根据目标 Provider 的特性做格式适配。比如 Gemini 和 Anthropic 的 turn 交替规则不同、thinking block 处理不同,这些都在 session 历史清洗阶段自动处理。所以 fallback 切换对历史消息是透明的,不需要手动做格式迁移。

提问:工具的 Hook 拦截层会不会引入性能问题?每次工具调用都多走两层包装,延迟能接受吗?

回答:两层包装本身的开销可以忽略不计,就是几个函数调用和 Promise 包装,微秒级别。真正可能有性能影响的是 Hook 里的具体逻辑,比如某个插件在 beforeToolCall 里做了一次网络请求做权限校验,那这个延迟是插件自己的问题,不是框架的问题。没注册 Hook 的话,拦截层会直接透传到原始工具函数,几乎零开销。

提问:Context 溢出的时候截断 tool result,截断策略是什么?会不会截掉关键信息?

回答:截断策略是动态的。它会先检测 tool result 尾部是否包含错误信息、结果摘要或 JSON 闭合结构。如果尾部重要,采用"头+尾"策略保留首尾、砍掉中间并插入省略标记;如果尾部不重要,只保留头部。截断后会追加说明告诉模型内容被截断了,模型可以决定是否需要重新调用工具分段读取。当然会有丢关键信息的风险,这是工程上的折中,总比直接报错中断对话要好。


On this page

45. OpenClaw 章节前置说明回答重点扩展知识为什么需要 AgentAgent 的核心架构Agent 的运行循环Function Calling 是 Agent 的关键基础设施Agent 的常见坑点面试官追问提问:Agent 的工具调用失败了怎么办?它会自己处理错误吗?提问:Agent 和 RAG 有什么关系?能结合使用吗?提问:多个 Agent 协作的时候,怎么防止它们互相冲突?比如两个 Agent 同时改一个文件?提问:Agent 的 token 消耗问题有什么好的优化思路?46. OpenClaw 是什么?回答重点47. OpenClaw 的五层组件架构是怎样的?回答重点扩展知识为什么要分这么多层48. OpenClaw 的入站-路由-执行-出站全链路是怎样的?回答重点扩展知识幂等性保护排队机制Fallback 机制Hook 插入点流式输出面试官追问提问:如果用户连续快速发了 5 条消息,Agent 会怎么处理?会不会每条都触发一次完整的 LLM 调用?提问:fallback 切换模型后,系统提示词和工具列表会不会跟着变?提问:幂等性 key 是怎么生成的?如果两个不同用户恰好发了一模一样的消息内容,会不会被误判为重复?49. OpenClaw 的 Agent Runner 是如何调度的?回答重点扩展知识attempt + fallback 容错机制工具调用的双层包装流式处理的 Provider 适配Context 溢出的三级降级面试官追问提问:你说排队执行保证并发安全,那如果用户快速连发两条消息会怎样?后面那条是排队等还是直接丢弃?提问:fallback 机制切换模型之后,之前的对话历史格式兼容吗?不同模型的消息格式不一样怎么办?提问:工具的 Hook 拦截层会不会引入性能问题?每次工具调用都多走两层包装,延迟能接受吗?提问:Context 溢出的时候截断 tool result,截断策略是什么?会不会截掉关键信息?