Files
LangBot/docs/agent-runner-pluginization/PROTOCOL_V1.md
2026-06-03 17:43:49 +08:00

20 KiB
Raw Blame History

LangBot AgentRunner Protocol v1

本文档是 LangBot Host 与插件 SDK / Runtime / AgentRunner 之间协议合同的唯一规范来源single source of truth

  • 本文件描述"稳定接口应是什么",是 normative spec不混入实现进度。实现状态见 PROGRESS.md
  • 本文件之外的任何文档不得重新定义这里的数据结构,只能引用,例如"见 PROTOCOL_V1 §4.2"。
  • Host 内部模型(AgentEventEnvelopeAgentBinding、Descriptor、各 Store不属于 SDK 协议,定义在 HOST_SDK_INFRASTRUCTURE.md

1. 协议目标

Protocol v1 只解决四件事:

  • LangBot 如何发现插件提供的 AgentRunner。
  • LangBot 如何把一次事件调用封装成 AgentRunContext
  • AgentRunner 如何以事件流形式返回运行结果。
  • AgentRunner 如何通过受限 API 访问 LangBot host 能力。

Protocol v1 不定义

2. 参与方

名称 职责
LangBot Host 事件入口、绑定解析、权限、资源、存储、生命周期、结果投递。
Plugin Runtime 加载插件,响应 Host 的 runner discovery 和 run 调用。
AgentRunner 插件提供的 agent 执行组件。
AgentRunAPIProxy AgentRunner 访问 Host 能力的受限 API。
AgentBinding Host 内部的事件到 runner 绑定配置,不直接暴露给 SDK见 HOST_SDK §4.2)。

AgentBinding 只影响 Host 构造出的 ctx.configctx.resourcesctx.contextctx.delivery。SDK 不需要知道 binding 的持久化形态。

外部 harness runnerClaude Code、Codex、Kimi Code 等)也是 AgentRunner:它们消费 event-first AgentRunContext、返回 AgentRunResult,并通过 Host 授权的 state/storage/artifact API 保存跨轮次指针。它们内部可以继续使用自己的 session、tool loop、MCP、上下文压缩和权限模型。

3. 版本协商

  • AgentRunnerManifest.protocol_version 声明 runner 实现的协议大版本,当前为 "1"
  • AgentRuntimeContext.protocol_versionctx.runtime.protocol_version)声明 Host 下发的协议大版本。
  • Host 发现 runner 时校验 protocol_version 兼容性;不兼容的 runner 不进入可用列表,只记 warning。
  • 字段级演进规则:新增可选字段不提升大版本;删除或改语义需要提升大版本。
  • 结果流演进Host 必须忽略未知 result type 并记录 warning(除非该 type 明确要求强校验)。新增 result type 不提升大版本。

4. Discovery 协议

4.1 LIST_AGENT_RUNNERS

Host 调用 Plugin Runtime 获取当前插件暴露的 runner 列表,请求无额外 payload。返回

class ListAgentRunnersResponse(BaseModel):
    runners: list[AgentRunnerManifest]

4.2 AgentRunnerManifest

class AgentRunnerManifest(BaseModel):
    id: str
    name: str
    label: I18nObject
    description: I18nObject | None = None
    protocol_version: str = "1"
    capabilities: AgentRunnerCapabilities
    permissions: AgentRunnerPermissions
    context: AgentRunnerContextPolicy
    config_schema: list[DynamicFormItemSchema] = []
    metadata: dict[str, Any] = {}
  • id 必须稳定,格式 plugin:author/name/runner
  • name 是插件内 runner 名称,例如 default
  • config_schema 只描述绑定配置表单,不代表插件实例状态。
  • metadata 只放展示、诊断、非稳定扩展信息。

4.3 Capabilities

class AgentRunnerCapabilities(BaseModel):
    streaming: bool = False
    tool_calling: bool = False
    knowledge_retrieval: bool = False
    multimodal_input: bool = False
    event_context: bool = True
    platform_api: bool = False
    interrupt: bool = False
    stateful_session: bool = False
    self_managed_context: bool = True

语义:

  • streaming: runner 可以返回 message.delta
  • tool_calling: runner 可能调用 Host tool API。
  • knowledge_retrieval: runner 可能调用 Host knowledge API。
  • multimodal_input: runner 可以处理非纯文本 input / artifact。
  • event_context: runner 理解 event-first 输入。
  • platform_api: runner 可能请求平台动作。
  • interrupt: runner 支持取消或中断。
  • stateful_session: runner 可能维护跨 run 会话状态。
  • self_managed_context: runner 自己管理 working contextHost 不应默认 inline 历史。

Capabilities 字段全部是 bool。runner 是否寄宿 host-owned state 不在 capabilities 表达,而通过 permissions.storage 声明(见 §4.4),避免出现非 bool 取值。

4.4 Permissions

class AgentRunnerPermissions(BaseModel):
    models: list[Literal["invoke", "stream", "rerank"]] = []
    tools: list[Literal["detail", "call"]] = []
    knowledge_bases: list[Literal["list", "retrieve"]] = []
    history: list[Literal["page", "search"]] = []
    events: list[Literal["get", "page"]] = []
    artifacts: list[Literal["metadata", "read"]] = []
    storage: list[Literal["plugin", "workspace", "binding"]] = []
    files: list[Literal["config", "knowledge"]] = []
    platform_api: list[str] = []

Manifest permissions 是 runner 需要的最大能力。实际可用资源还要经过 Host binding policy 和当前 run scope 裁剪(三层裁剪见 HOST_SDK §4.5)。

4.5 Context Policy

class AgentRunnerContextPolicy(BaseModel):
    supports_history_pull: bool = True
    supports_history_search: bool = False
    supports_artifact_pull: bool = True
    owns_compaction: bool = True
    wants_static_context_refs: bool = True

Host 不使用该声明给 runner inline 历史窗口。默认原则:

  • Host 不得默认 inline 全量历史。
  • Host 只 inline 当前 event / input 和 context handles。
  • Runner 拥有 working context assembly。
  • Runner 可在授权后通过 Host history / event / artifact / state API 拉取更多上下文。
  • 历史窗口策略不属于 Protocol v1 字段,也不属于 Host 通用语义。

context 边界的设计理由见 AGENT_CONTEXT_PROTOCOL.md

5. Run 协议

5.1 RUN_AGENT

Host 调用 Runtime

class AgentRunRequest(BaseModel):
    runner_id: str
    runner_name: str
    context: AgentRunContext

Runtime 返回 AgentRunResult 异步流。底层 transport 可继续用 plugin_author / plugin_name / runner_name 定位组件,但协议语义以 runner_idcontext 为准。

5.2 AgentRunContext

这是 SDK 看到的唯一权威 context 定义

class AgentRunContext(BaseModel):
    run_id: str
    trigger: AgentTrigger
    event: AgentEventContext
    conversation: ConversationContext | None = None
    actor: ActorContext | None = None
    subject: SubjectContext | None = None
    input: AgentInput
    delivery: DeliveryContext
    resources: AgentResources
    context: ContextAccess
    state: AgentRunState
    runtime: AgentRuntimeContext
    config: dict[str, Any] = {}
    adapter: AdapterContext | None = None
    metadata: dict[str, Any] = {}

核心约束:

  • event 是必选字段Protocol v1 是 event-first。
  • input 表示当前事件的主输入,不等于历史消息。
  • bootstrap / messages 不是协议字段Host 不内联历史窗口。
  • adapter 只放入口 adapter 的非核心元数据runner 不应依赖它做长期能力。
  • config 是 Agent/runner config不是插件实例状态。

5.3 AgentTrigger

class AgentTrigger(BaseModel):
    type: str
    source: Literal["platform", "webui", "api", "scheduler", "system", "host_adapter"]
    timestamp: int | None = None

trigger.type 应与 event.event_type 一致或更粗粒度。例如入口适配器触发消息时:

{ "type": "message.received", "source": "host_adapter" }

5.4 AgentEventContext

class AgentEventContext(BaseModel):
    event_id: str
    event_type: str
    event_time: int | None = None
    source: str
    source_event_type: str | None = None
    raw_ref: RawEventRef | None = None
    data: dict[str, Any] = {}
  • event_type 使用 LangBot 稳定协议名,例如 message.received。稳定事件名清单见 EVENT_BASED_AGENT.md
  • 平台原始事件名放入 source_event_type
  • 大型原始 payload 必须放入 raw_ref 或 artifact不应直接塞入 data

5.5 Conversation / Actor / Subject

class ConversationContext(BaseModel):
    conversation_id: str | None = None
    thread_id: str | None = None
    launcher_type: str | None = None
    launcher_id: str | None = None
    bot_id: str | None = None
    workspace_id: str | None = None

class ActorContext(BaseModel):
    actor_type: str
    actor_id: str | None = None
    actor_name: str | None = None
    metadata: dict[str, Any] = {}

class SubjectContext(BaseModel):
    subject_type: str
    subject_id: str | None = None
    data: dict[str, Any] = {}

示例:

  • 消息事件actor 是发消息的人subject 是当前消息。
  • 入群事件actor 是新成员或邀请人subject 是群/成员关系。
  • 定时事件actor 可以是 systemsubject 是 schedule。

5.6 AgentInput

class AgentInput(BaseModel):
    text: str | None = None
    contents: list[ContentElement] = []
    attachments: list[ArtifactRef] = []
    message_chain: dict[str, Any] | None = None
  • 文本、多模态、附件都属于当前 event input。
  • 大文件、图片、音频、工具大结果应以 artifact ref 传递。
  • message_chain 是平台兼容字段,不应成为长期稳定依赖。

5.7 DeliveryContext

class DeliveryContext(BaseModel):
    surface: str
    reply_target: dict[str, Any] | None = None
    supports_streaming: bool = False
    supports_edit: bool = False
    supports_reaction: bool = False
    max_message_size: int | None = None
    platform_capabilities: dict[str, Any] = {}

Runner 可参考 delivery 能力决定返回 message.deltamessage.completedaction.requested

5.8 ContextAccess

class ContextAccess(BaseModel):
    conversation_id: str | None = None
    thread_id: str | None = None
    latest_cursor: str | None = None
    event_seq: int | None = None
    transcript_seq: int | None = None
    has_history_before: bool = False
    inline_policy: InlineContextPolicy
    available_apis: ContextAPICapabilities

class InlineContextPolicy(BaseModel):
    mode: Literal["none", "current_event", "recent_tail", "summary_tail"]
    delivered_count: int = 0
    source_total_count: int | None = None
    messages_complete: bool = False
    reason: str | None = None

class ContextAPICapabilities(BaseModel):
    history_page: bool = False
    history_search: bool = False
    event_get: bool = False
    event_page: bool = False
    artifact_metadata: bool = False
    artifact_read: bool = False
    state: bool = False
    storage: bool = False

ContextAccess 告诉 runnerHost inline 了什么、没 inline 什么、需要更多上下文时走哪些 API。它是 runner 按需读取上下文的入口说明,不是 Host 的业务上下文编排策略。

5.9 AgentRuntimeContext

class AgentRuntimeContext(BaseModel):
    host: str = "langbot"
    protocol_version: str = "1"
    langbot_version: str | None = None
    trace_id: str
    deadline_at: float | None = None
    locale: str | None = None
    timezone: str | None = None
    static_refs: dict[str, StaticContextRef] = {}
    metadata: dict[str, Any] = {}

static_refs 用于 KV cache 友好的静态上下文引用system policy、tool schema、resource manifest 的 hash/version。理由见 AGENT_CONTEXT_PROTOCOL §6。

5.10 AgentRunState

class AgentRunState(BaseModel):
    conversation: dict[str, Any] = {}
    actor: dict[str, Any] = {}
    subject: dict[str, Any] = {}
    runner: dict[str, Any] = {}

State 是可选 host-owned snapshot。Runner 也可以完全自管状态。

6. Resources

class AgentResources(BaseModel):
    models: list[ModelResource] = []
    tools: list[ToolResource] = []
    knowledge_bases: list[KnowledgeBaseResource] = []
    files: list[FileResource] = []
    storage: StorageResource = StorageResource()
    platform_capabilities: dict[str, Any] = {}

资源列表是本次 run 的授权结果。History / Event / Artifact 访问通过 permissions、ctx.context.available_apis 和 Host 侧 run session 校验控制,不作为可枚举 resource list 暴露。Runner 只能通过 AgentRunAPIProxy 访问这些能力。

7. Result Stream

7.1 AgentRunResult

class AgentRunResult(BaseModel):
    run_id: str
    type: str
    data: dict[str, Any] = {}
    sequence: int | None = None
    timestamp: int | None = None

7.2 稳定 result types

type 说明 当前消费
message.delta 流式消息片段。
message.completed 完整消息。
tool.call.started 工具调用开始的可观测事件。 telemetry
tool.call.completed 工具调用完成的可观测事件。 telemetry
artifact.created runner 生成 artifact。
state.updated runner 请求更新 host-owned state。
action.requested runner 请求 Host 执行平台动作。 reserved / 仅 telemetry不执行
run.completed run 正常结束。
run.failed run 失败。

action.requested 是为 EBA 和 platform API 预留的协议表面:当前阶段 Host 收到后只记 telemetry不执行runner 作者不应依赖其副作用。执行模型见 EVENT_BASED_AGENT §6。

7.3 示例

{ "type": "message.delta",     "data": { "chunk": { "role": "assistant", "content": "hel" } } }
{ "type": "message.completed", "data": { "message": { "role": "assistant", "content": "hello" } } }
{ "type": "state.updated",     "data": { "scope": "conversation", "key": "external.session_id", "value": "abc" } }
{ "type": "action.requested",  "data": { "action": "message.edit", "target": {"message_id": "..."}, "payload": {"text": "..."} } }

Host 必须校验 state.updated 的 scope、key、value 大小和 JSON 可序列化性。

8. AgentRunAPIProxy

所有 proxy action 必须携带 run_id。Host 必须校验active run session 存在、caller plugin identity 匹配、resource 在本次 ctx.resources 中授权、scope 不越界、payload size / rate limit / deadline 合法。

# Model
await api.models.invoke(model_id, messages, tools=None, extra_args=None)
await api.models.stream(model_id, messages, tools=None, extra_args=None)
await api.models.rerank(model_id, query, documents, top_k=None)

# Tool
await api.tools.get_detail(tool_name)
await api.tools.call(tool_name, parameters)

# Knowledge
await api.knowledge.retrieve(kb_id, query_text, top_k=5, filters=None)

# History返回 Transcript projection不返回原始平台 payload
await api.history.page(conversation_id=None, before_cursor=None, after_cursor=None,
                       limit=50, direction="backward", include_artifacts=False)
await api.history.search(query, filters=None, top_k=10)

# Event返回稳定 event envelope 或受限 raw ref不默认返回大 payload
await api.events.get(event_id)
await api.events.page(before_cursor=None, limit=50)

# Artifact必须支持大小限制、MIME 校验、过期时间和授权范围)
await api.artifacts.metadata(artifact_id)
await api.artifacts.read_range(artifact_id, offset=0, length=65536)
await api.artifacts.open_stream(artifact_id)

# State / Storage
await api.state.get(scope, key);   await api.state.set(scope, key, value);   await api.state.delete(scope, key)
await api.storage.get(area, key);  await api.storage.set(area, key, value)
await api.storage.delete(area, key); await api.storage.list(area, prefix=None)

# Platform受限能力默认不开放需 manifest + binding policy + 用户审批同时允许)
await api.platform.request_action(action, target, payload)

statestorage 的建议边界:state 放小型 JSONconversation / actor / runner / bindingstorage 放 blob 或较大数据插件私有数据、workspace 数据、checkpoint

返回数据结构(如 HistoryPage、artifact metadata见 AGENT_CONTEXT_PROTOCOL §4。

9. 错误模型

class AgentAPIError(BaseModel):
    code: str
    message: str
    retryable: bool = False
    details: dict[str, Any] = {}
code 说明
unauthorized 未授权访问资源或 scope。
not_found 资源不存在或对当前 runner 不可见。
deadline_exceeded 超过 run deadline。
payload_too_large 请求或响应过大。
rate_limited Host 限流。
invalid_argument 参数错误。
runtime_error Host 或下游能力错误。

Runner 失败使用 run.failed

{ "type": "run.failed", "data": { "code": "runner.error", "message": "failed to call external agent", "retryable": false } }

10. Timeout 与 Cancellation

  • Host 在 ctx.runtime.deadline_at 下发总 deadlineSDK proxy 必须用该 deadline 限制单次 action timeout。
  • Host 可以取消 active runRuntime 应尽力中断 runner。
  • Runner 支持中断时应返回或触发 run.failedcode 为 cancelled
  • Host 必须 unregister active run session。

11. Security 与 Guardrail协议层

Protocol v1 的安全边界在 Host

  • Runner 不能直接访问未授权 model/tool/kb/history/artifact/storage。
  • SDK 本地校验只提升开发体验,不能替代 Host 校验。
  • 所有 resource id 对 runner 来说都是 opaque。
  • 默认只能访问当前 conversation / thread 的 history跨会话、workspace 级访问必须额外授权。
  • 大 payload 必须 artifact 化。
  • Host 必须记录 run_id、runner_id、action、resource、scope、result。

Host 不负责业务编排:不拼接全量历史、不替 runner 做 prompt assembly、不内置 agent memory / tool loop / 上下文压缩策略。这些由官方或第三方 AgentRunner 插件实现。

对外部 harness runnerHost 在调用前完成 binding/resource policy 裁剪、路径策略、secret 过滤和审计runner plugin 把授权后的 context/resource projection 适配为目标 harness 的形式harness 的 native permission mode、allowed/disallowed tools 只是额外执行约束,不能替代 Host 授权。

发布级路径隔离、MCP allowlist、secret redaction、配额、workspace 清理等不属于 v1 协议闭环,是生产默认启用前的 release gateSECURITY_HARDENING.md

12. Pipeline Adapter 边界

Pipeline 是当前入口 adapter不是协议中心。Query entry adapter 负责:

  • Query 构造 AgentEventContext 和临时 AgentBinding(见 HOST_SDK §4.2)。
  • 从当前 Agent/runner config 构造 ctx.config
  • 将 Query-only 字段放入 ctx.adapter,例如 filtered params 放 ctx.adapter.extra["params"]

约束:

  • adapter 定义历史窗口、prompt 组装或 agentic context 策略。
  • preprocessing / hook 后的有效指令不通过 ctx.adapter.extra 主动推送;后续应通过 Host prompt/instruction pull API 暴露(占位见 HOST_SDK §4.8)。
  • 新 runner 不应长期依赖 adapter,应只依赖 event-first context 和 Host API。

13. 开放问题

  • AgentBinding 是否需要进入 SDK 文档作为只读诊断信息,还是完全 Host 内部。
  • TranscriptItem 的最小字段集如何定义。
  • ArtifactStore 是否复用现有 BinaryStorage backend还是引入独立实体。
  • State 与 Storage 的边界是否需要更强类型。
  • platform_api action 的审批模型如何表达。
  • 多 runner 并发处理同一 event 时result delivery 的冲突策略如何定义。
  • Host 侧 scoped MCP / skill / workspace projection 是否需要从 runner config 上移为一等 resource projection API。