跳转至

提示和会话

模型解析、提示词构建、最终化处理以及插件持有的持久会话状态。属于插件钩子指南的一部分。

调试运行时钩子

使用 before_model_resolve 在智能体回合中切换提供商或模型——它在模型解析之前运行。llm_output 在运行时发出时描述一次尝试的输出;assistantTexts 可能为空且 lastAssistant 不存在,因此仅凭该事件并不能证明最终答案成功。

若要核实实际生效的会话模型,请检查运行时注册信息,然后使用 openclaw sessions 或 Gateway 的会话/状态接口。要调试提供商的有效载荷,请使用 --raw-stream 和 --raw-stream-path <path> 启动 Gateway,将原始模型流事件写入 jsonl 文件。

提示词与模型钩子

新插件请使用各阶段专用的钩子:

  • before_model_resolve:仅接收当前提示词和附件元数据。返回 providerOverride 或 modelOverride。
  • agent_turn_prepare:接收当前提示词、已准备好的会话消息以及本次会话消耗的排队注入。返回 prependContext 或 appendContext。
  • before_prompt_build:接收已准备好的提示词和会话消息。宿主还可能提供 currentUserMessage——在进行历史/上下文投射之前的当前请求——以及 currentUserMessageId,即其原生准入标识。该 ID 在一次已准入请求的重建和重试过程中保持稳定,并在不同准入之间各不相同。在可用时,使用显式请求进行意图检测;prompt 可能包含重建后的历史。不要解析信封标记来还原请求边界。显式的空字符串表示没有文本请求,包括仅图像输入或没有保留请求的延续场景。它不得回退到历史记录。Codex 运行时刷新会保留原始记录器的文本和标识。没有记录器时,Codex 会提供当前文本但不提供准入 ID;仅凭相同文本和关联运行 ID 无法识别一次准入。省略的字段将保留现有宿主行为。 返回 prependContext、appendContext、systemPrompt、prependSystemContext、appendSystemContext 或 toolsAllow。toolsAllow 只能收窄当前回合由宿主解析出的工具面;[] 表示不提交任何可选工具,而省略该字段则保持现有工具面不变。多个钩子返回的限制条件取交集。嵌入式运行器和 Copilot 宿主会将此字段应用于其回合级提交的工具面。Codex 应用服务器宿主会拒绝限制性值,因为其动态工具是线程作用域的,且 Codex turn/start 没有工具面覆盖机制;当插件需要此策略时,请使用嵌入式或 Copilot 运行器。
  • before_prompt_build 搭配 { requiresToolAuthority: true }:在策略确定后的第二阶段运行。当提示词增强通过工具支持的能力读取数据,且同一回合必须被允许调用该工具时使用。参见授权的提示词增强。
  • heartbeat_prompt_contribution:仅在心跳回合中运行,返回 prependContext 或 appendContext。适用于需要在不变更用户发起的回合的情况下总结当前状态的后台监视器。

在嵌入式路径和 CLI 提示词准备路径上,顺序为:排空已排队的注入 → agent_turn_prepare → 心跳贡献(如适用)→ 常规 before_prompt_build → 最终确定的工具策略 → 授权的提示词增强。agent_turn_prepare 和排队注入排空未接入 Codex 或 Copilot 提示词路径。

对于多个注册,最先定义的提供商/模型覆盖和 systemPrompt 优先生效。上下文添加按优先级顺序拼接,工具限制取交集。当同一运行器上外层分发处于活动状态时,嵌套的常规 before_prompt_build 分发会被跳过;其他钩子族和独立回合仍然可用。

消费消息的提示词钩子会收到一份独立的模型上下文快照。修改嵌套消息不会改变调用方的历史,即使处理程序在返回后保留了其输入也是如此。一次分发内的注册按优先级顺序共享该快照;准备阶段、常规提示词构建、授权增强以及后续的提示词重建会分别收到独立的快照。仅用于存储的原生提示词文本和工具结果详情不包含在这些快照中。

处理程序生命周期

每个 before_prompt_build 处理程序在常规和授权的提示词阶段都会收到只读的 ctx.hookInvocation.assertActive() 能力。当运行器停止等待该处理程序(因超时、返回或出错)后,会在下一个处理程序开始之前抛出异常。请在等待(await)的工作完成后、紧接着在某个同步副作用之前调用它,该副作用的结果必须仍然有资格用于本次钩子调用。其他处理程序拥有独立的能力,即使在同一提示词分发内也是如此。

该能力仅检查处理程序结果的可接受时限。它不授予任何工具授权,不取消底层工作,也不保证后续的模型调用会消费该上下文。该字段为可选,以兼容 SDK;需要它的插件必须显式处理不支持的宿主,而不能自行复制一份超时预算来替代。

授权的提示词增强

当插件必须在检索上下文之前核验最终确定的每回合工具策略时,请使用 requiresToolAuthority: true 注册 before_prompt_build:

api.on(
  "before_prompt_build",
  async (event, ctx) => {
    const authority = ctx.toolAuthority;
    if (!authority?.allows("memory_search")) {
      return;
    }

    const recalledContext = await recallForPrompt(event.prompt);
    authority.assertActive();
    return { prependContext: recalledContext };
  },
  { requiresToolAuthority: true },
);

主机将该处理程序排除在常规提示构建阶段之外。在所有常规钩子和工具限制确定之后,受支持的运行时使用 ctx.toolAuthority 调用它,该值绑定到当前确切的活动回合和最终化的工具表面。嵌入式(Embedded)、CLI、Copilot 和 Codex 运行时支持此阶段。如果运行时无法证明该权限,则会跳过该处理程序。

将 toolAuthority 视为一种临时能力:

  • allows(toolName) 针对最终化的工具表面检查规范工具 ID,并验证该能力是否仍然处于活动状态。
  • assertActive() 在中止、取消、运行替换、生命周期轮换或钩子分发完成之后会拒绝调用。请在等待的工作完成之后、提交插件拥有的副作用之前调用它。
  • fingerprint 是不透明的缓存分区输入。它不是承载令牌或授权证明;切勿将其作为权限进行持久化、传输或比较。
  • 此阶段仅返回 prependContext 或 appendContext。在策略确定之后,它不能替换系统提示或更改 toolsAllow。

主机会在每个等待的处理程序之后重新验证权限,并丢弃过期的增强内容。保留的 toolAuthority 对象在分发后以失败关闭方式拒绝访问。

此选项要求主机实现策略后阶段。已发布的插件必须将 package.json 中的 openclaw.compat.pluginApi 设置为一个范围,该范围从插件针对此契约构建所基于的第一个 OpenClaw 版本开始。较旧的主机在发现期间会跳过不兼容的包,并拒绝不兼容的安装或更新。请勿发布使用此选项但同时声称兼容较旧插件 API 的包;否则,较旧的主机可能会将未知选项视为普通的策略前钩子。

在嵌入式(embedded)和 CLI 运行器上,before_agent_run 在提示构建之后、模型提交之前运行,包括 llm_input 观察。在嵌入式路径上,它也在提示本地图像加载之前运行。它接收当前用户输入作为 prompt,以及加载到 messages 中的会话历史和活动系统提示。返回 { outcome: "block", reason, message? } 可在模型读取提示之前停止运行。reason 是内部信息;message 是面向用户的替代文本。仅支持 pass 和 block 结果;不支持的分支形状将按失败关闭模式处理。

当运行被阻止时,OpenClaw 仅在 message.content 中存储替代文本以及非敏感的阻止元数据,例如阻止插件的 ID 和时间戳。原始用户文本不会保留在记录或未来上下文中。内部阻止原因被视为敏感信息,并从记录、历史记录、广播、日志和诊断负载中排除。可观测性应使用经过清理的字段,例如阻止者 ID、结果、时间戳或安全类别。

暴露 event.runId 的钩子(如 agent_end 和 before_agent_finalize)会在 OpenClaw 能够识别活动运行时收到该值;同样的值也存在于 ctx.runId 上。提示构建钩子并非都带有事件 runId 字段,因此请使用其类型化上下文进行关联。Cron 驱动的运行还可以在发射器提供时暴露 ctx.jobId(来源 cron 作业的 ID),以便钩子可以将指标、副作用或状态限定到特定的计划作业。不要假设每个代理事件都携带该值。ctx.jobId 不属于 before_tool_call 工具上下文的一部分。

对于 before_prompt_build,ctx.inputProvenance 携带主机分类的回合用户角色输入的来源,适用于嵌入式(embedded)、CLI、Codex 和 Copilot 提示路径。其 kind 为 external_user、inter_session 或 internal_system。可选来源字段为 originSessionId、sourceSessionKey、sourceChannel 和 sourceTool。

该字段是可选的。当生产者未提供分类时(包括某些路径上的普通人类回合),该字段不存在。字段缺失并不证明是人类来源。即使提示钩子收到了该字段,其他钩子事件也可能省略它。

ctx.trigger === "user" 是运行触发器,而不是来源分类。诸如 sessions_send、subagent_settle 和 subagent_announce 之类的会话间投递可以保留该触发器。请使用类型化来源来区分这些输入;不要解析提示前缀。来源描述的是来源,并不授予使用工具或访问另一个会话的权限。

对于源自频道的运行,ctx.channel 和 ctx.messageProvider 标识提供商表面,例如 discord 或 telegram,而 ctx.channelId 是会话目标标识符,当 OpenClaw 可以从会话密钥或投递元数据中推导出该标识符时。

当发送者身份可用时,代理钩子上下文还包括:

  • ctx.senderId —— 频道范围内的发送者 ID(例如飞书 open_id、Discord 用户 ID)。当运行源自具有已知发送者元数据的用户消息时填充。
  • ctx.chatId —— 传输层原生的会话标识符(例如飞书 chat_id、Telegram chat_id)。当来源频道提供原生会话 ID 时填充。
  • ctx.channelContext.sender.id —— 与 ctx.senderId 相同的发送者 ID,位于频道拥有的对象下,插件可以使用频道特定字段进行扩展。
  • ctx.channelContext.chat.id —— 与 ctx.chatId 相同的会话 ID,位于频道拥有的对象下,插件可以使用频道特定字段进行扩展。

核心仅定义了嵌套的 id 字段。通过入站辅助函数传递更丰富发送者或聊天元数据的频道插件,可以增强 openclaw/plugin-sdk/channel-inbound 中的 PluginHookChannelSenderContext 或 PluginHookChannelChatContext:

declare module "openclaw/plugin-sdk/channel-inbound" {
  interface PluginHookChannelSenderContext {
    unionId?: string;
    userId?: string;
  }
}

频道插件通过入站 SDK 辅助函数传递这些字段:

buildChannelInboundEventContext({
  // ...
  channelContext: {
    sender: { id: senderOpenId, unionId, userId },
    chat: { id: chatId },
  },
});

这些字段是可选的,并且对于源自系统的运行(心跳、cron、exec-event)不存在。

ctx.senderExternalId 作为旧的源兼容字段保留,供旧插件使用。核心不会填充它;新的渠道特定发送者身份应通过模块增强置于 ctx.channelContext.sender 之下。

agent_end 是一个观察钩子。基于渠道的路径通常在回合结束后以即发即弃的方式运行它,而本地一次性路径可以在进程清理前等待钩子 promise,以便受信任的插件刷新终态可观测性或捕获状态。钩子运行器对每个处理器应用 30 秒的默认超时,因此卡死的插件或嵌入端点不会让钩子 promise 永远挂起。超时会被记录,OpenClaw 继续执行;除非插件也使用自己的中止信号,否则超时不会取消插件拥有的网络工作。

若需要不包含原始提示词、历史、响应、请求头、请求体或提供方请求 ID 的提供方调用遥测,请使用 model_call_started 和 model_call_ended。这些钩子包含稳定元数据,如 runId、callId、provider、model、可选的 api/transport、终态 durationMs/outcome,以及 upstreamRequestIdHash(当 OpenClaw 能推导出有界的提供方请求 ID 哈希时)。当运行时已解析上下文窗口元数据时,钩子事件和上下文还包括 contextTokenBudget(即在模型配置、固定模型契约和运行时发现之后确定的有效令牌预算),以及 contextWindowSource 和 contextWindowReferenceTokens(当应用了更低上限时)。

这些提供方调用钩子仅由嵌入式模型调用路径发出。暴露 llm_input / llm_output 的执行框架不会自动暴露相同的提供方调用遥测。在外部执行框架中,LLM 事件描述的是适配器可见的输入和输出,不一定是原始提供方请求或完整的原生历史。

before_agent_finalize 仅在执行框架即将接受自然的最终助手回答时运行。它不是 /stop 取消路径,也不会在用户中止回合时运行。返回 { action: "revise", reason } 请求执行框架在最终确定前再进行一次模型传递,返回 { action: "finalize", reason? } 强制最终确定,或者不返回结果以继续。处理器有 15 秒的默认预算;超时时,OpenClaw 会记录失败并保留其他处理器的决策。如果没有修订决策,正常最终确定将继续。多个 revise 原因会被合并;任何 finalize 决策都会覆盖修订请求。此钩子需要最终确定集成:嵌入式运行器和原生钩子中继提供该集成,但 Copilot 执行框架不会分发它。Codex 原生 Stop 钩子会作为 OpenClaw before_agent_finalize 决策被中继到该钩子。

当返回 action: "revise" 时,插件可以包含 retry 元数据来限制运行内的重复修订请求:

type BeforeAgentFinalizeRetry = {
  instruction: string;
  idempotencyKey?: string;
  maxAttempts?: number;
};

instruction 会附加到发送给执行框架的修订原因中。idempotencyKey 让宿主在同一运行内的等效最终确定决策之间统计重试次数;没有 key 时,它会哈希 instruction。maxAttempts 默认为该 key 提供一次额外模型传递。使用插件特定的 key 可以避免与其他插件共享预算。执行框架可以应用更严格的整体修订限制;嵌入式运行器最多允许三次。

对话访问和提示词变更具有独立的权限门;在启用这些钩子之前,请参阅权限与作用域。

会话扩展与下一轮注入

工作流插件可以通过 api.session.state.registerSessionExtension(...) 持久化小型 JSON 兼容会话状态,并通过 Gateway 的 sessions.pluginPatch 方法更新它。会话行通过 pluginExtensions 呈现已注册的扩展状态,让 Control UI 和其他客户端无需了解插件内部细节即可渲染插件拥有的状态。api.registerSessionExtension(...) 仍然可用,但已弃用,推荐使用 api.session.state 命名空间。

当插件需要为下一次提示词构建排队持久上下文时,请使用 api.session.workflow.enqueueNextTurnInjection(...)(顶层 api.enqueueNextTurnInjection(...) 是行为相同的弃用别名)。在嵌入式路径和 CLI 提示词准备路径上,OpenClaw 会在提示词钩子之前排空排队的注入。它会丢弃已过期的条目,以及插件不活跃或已禁用提示词注入的条目。idempotencyKey 针对同一插件和会话去重未过期的待处理条目;key 在消费后可复用。排空的条目会在当前运行内的重试之间复用,但消费条目并不是模型已看到它的凭证:后续失败可能阻止提交。对于审批恢复、策略摘要、后台监控增量以及命令延续——这些内容应让模型在下一轮可见,但不应成为永久的系统提示词文本——这是正确的接入点。

当配置了多个代理时,请传递 agentId 并配合无作用域的 sessionKey(如 global)。入队、消费和插件会话状态都保留在该代理的存储中;拥有者选择器不属于持久化注入的一部分。

消费仍绑定到所选存储会话。重置或冲突的旧版与限定身份会阻止排空流程消费另一个会话的队列。读取或消费旧版别名不会重命名其存储的会话 key。

清理语义是契约的一部分。会话扩展清理和运行时生命周期清理回调会收到 reset、delete、disable 或 restart。对于 reset/delete/disable,宿主会移除拥有插件的持久会话扩展状态和待处理的下一轮注入;restart 保留持久会话状态,同时清理回调让插件为旧运行时世代释放调度任务、运行上下文和其他带外资源。

禁用清理会保留由该插件的 harness 拥有的模型锁定会话。重启会保留扩展状态和待注入项,但可能会清除过期的已提升顶层会话字段。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw