# LangBot Host 与 SDK 基础设施设计

本文档描述 LangBot 作为 agent host 的内部能力与分层架构，以及 Host 内部模型。

- SDK ↔ Host 的协议数据结构（`AgentRunContext`、`AgentRunnerManifest`、`AgentRunResult`、`AgentRunAPIProxy` 等）的**唯一定义在** [PROTOCOL_V1.md](./PROTOCOL_V1.md)；本文只引用，不重抄。
- 实现进度见 [PROGRESS.md](./PROGRESS.md)。
- 本文定义的 Host 内部模型（`AgentEventEnvelope`、`AgentBinding`、`AgentRunnerDescriptor`）不属于 SDK 协议字段。

## 1. 目标

LangBot 要转为 agent host，而不是内置 runner 容器：

- 接收 IM、WebUI、API 和未来 EventRouter 产生的事件。
- 根据事件、bot、workspace、scope 解析应该调用的 Agent / agent binding。
- 发现、校验和调用插件提供的 AgentRunner。
- 为每次 run 提供受限资源、状态、存储、上下文引用和生命周期控制。
- 接收 AgentRunner 返回的事件流，投递到 IM、WebUI 或其他 output surface。

## 2. 非目标

- 不把 Pipeline 当作长期架构中心。
- 不要求所有 AgentRunner 依赖 LangBot 的上下文管理。
- 不要求官方 local-agent 的旧行为反向塑造 host 协议。
- 不在 host 中实现通用 agentic prompt assembler。
- 不强制 runner 使用 LangBot state / storage；只提供可选、受控的寄宿能力。
- 不实现 EventGateway：它是 future integration point，由外部 event branch 提供。本分支只定义 host-side envelope/binding models 和 `run(event, binding)` 入口。

## 3. 分层架构

```text
IM / WebUI / API / EventRouter (future)
        |
        v
Event Gateway (future - external event branch)
        |
        v
AgentBindingResolver
        |
        v
AgentRunOrchestrator
        |-- AgentRunnerRegistry
        |-- AgentResourceBuilder
        |-- AgentContextBuilder
        |-- AgentRunSessionRegistry
        |-- PersistentStateStore / EventLogStore / TranscriptStore / ArtifactStore
        v
Plugin Runtime / AgentRunner
        |
        v
AgentRunResult stream
        |
        v
Delivery / Renderer / Platform API
```

目标产品模型、单绑定调度、Agent 复用、插件实例无状态和 fan-out 边界以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) §13 为准。本文只说明 Host 如何把当前入口投影为内部模型。当前 Pipeline 只应接入在 Query entry adapter 位置：它可以继续产生 `message.received` 并投影出临时 `AgentConfig` / `AgentBinding`，但不应再拥有 runner 选择、上下文裁剪和业务 agent 执行的核心语义。EventGateway 由外部 event branch 实现。

## 4. LangBot 侧能力

### 4.1 Event Gateway（Future Integration Point）

> EventGateway 由外部 event branch 实现，不在本分支范围。本分支只预留 event-first 入口和 envelope/binding models。

Event Gateway 将把入口统一成 host event（IM 平台消息、WebUI debug chat、API 触发、后续非消息事件），输出稳定的 `AgentEventEnvelope`（Host 内部模型）：

```python
class AgentEventEnvelope(BaseModel):
    event_id: str
    event_type: str
    event_time: int | None
    source: str
    bot_id: str | None
    workspace_id: str | None
    conversation_id: str | None
    thread_id: str | None
    actor: ActorRef | None
    subject: SubjectRef | None
    input: AgentInput          # 见 PROTOCOL_V1 §5.6
    delivery: DeliveryContext  # 见 PROTOCOL_V1 §5.7
    raw_ref: RawEventRef | None
    metadata: dict[str, Any] = {}
```

`AgentEventEnvelope` 是 Host 内部入口模型；投影给 runner 的是 `ctx.event`（PROTOCOL_V1 §5.4）。原始平台 payload 存为 raw event 或 artifact ref，不扩散到 runner 协议顶层。

**当前 adapter source**：`QueryEntryAdapter.query_to_event(query)` 从 Query 生成 `AgentEventEnvelope`。

### 4.2 AgentConfig 与 AgentBinding

`AgentConfig` 是迁移期的 Host 内部 Agent 配置投影（不暴露给 SDK）。当前 Query entry adapter 从 Pipeline config 投影出它；未来持久 Agent 也应先投影成这个运行期配置，再由 BindingResolver 结合事件和 scope 解析为 `AgentBinding`。

```python
class AgentConfig(BaseModel):
    agent_id: str | None = None
    runner_id: str
    runner_config: dict[str, Any] = {}
    resource_policy: ResourcePolicy = ResourcePolicy()
    state_policy: StatePolicy = StatePolicy()
    delivery_policy: DeliveryPolicy = DeliveryPolicy()
    event_types: list[str] = ["message.received"]
    enabled: bool = True
    metadata: dict[str, Any] = {}
```

`AgentBinding` 是"什么事件调用哪个 AgentRunner、带什么 Agent 配置"的 Host 内部运行投影（不暴露给 SDK）。它是 EventRouter / 当前 QueryEntryAdapter 在一次运行前解析出的有效绑定。

```python
class AgentBinding(BaseModel):
    binding_id: str
    enabled: bool
    scope: BindingScope
    event_types: list[str]
    filters: list[EventFilter] = []   # EBA 阶段使用，见 EVENT_BASED_AGENT
    runner_id: str
    runner_config: dict[str, Any]
    resource_policy: ResourcePolicy
    state_policy: StatePolicy
    delivery_policy: DeliveryPolicy
```

BindingResolver 的基数、fan-out 和冲突处理约束见 PROTOCOL_V1 §13；本节只定义 Host 内部投影形态。

**当前 adapter source**：`QueryEntryAdapter.config_to_agent_config(query, runner_id)`
先把 current config 投影为迁移期 `AgentConfig`，再由
`AgentBindingResolver.resolve_one(event, [agent_config])` 解析出唯一
`AgentBinding`。Pipeline 当前只是迁移期 Agent config source（AI runner config
→ runner_config、extension preference → resource_policy、output settings →
delivery_policy），但新设计不再把这些字段命名为 Pipeline 专属概念。

### 4.3 AgentRunnerRegistry

Registry 收集 runner descriptor（来自插件 runtime、开发期本地插件）：

```python
class AgentRunnerDescriptor(BaseModel):
    id: str
    source: Literal["plugin"]
    label: I18nObject
    description: I18nObject | None = None
    protocol_version: str = "1"
    capabilities: AgentRunnerCapabilities    # 见 PROTOCOL_V1 §4.3
    permissions: AgentRunnerPermissions      # 见 PROTOCOL_V1 §4.4
    config_schema: list[DynamicFormItemSchema]
    plugin: PluginRef | None = None
```

职责：调用 `plugin_connector.list_agent_runners()` 拉取 runner、校验 manifest（`kind == AgentRunner`、`metadata.name/label` 存在、`protocol_version` 兼容、`spec.*` 类型正确）、输出 descriptor、缓存 discovery 结果并提供 `refresh()`。单个插件 manifest 失败只记 warning，不影响其它 runner。`plugin:author/name/runner` 是稳定 id 格式；插件实例边界见 PROTOCOL_V1 §13。

Host 内置 runner / adapter 不能作为 `AgentRunnerDescriptor.source` 绕过插件
runtime、`run_id`、`ctx.resources` 和 `AgentRunAPIProxy` 权限链。若需要
开发期调试 adapter，应放在 Host 内部测试入口，不进入可选 runner 列表。

刷新触发点：插件安装/卸载/升级/重启后；Pipeline metadata 请求时发现缓存为空；可选 TTL（优先保证正确性）。

### 4.4 AgentRunOrchestrator

Orchestrator 是唯一运行入口：

```text
run(event, binding)
  -> resolve runner descriptor
  -> build resources
  -> build context
  -> register run session
  -> call plugin runtime
  -> normalize result stream
  -> update state
  -> unregister run session
```

它负责：`run_id` 生成和生命周期、timeout/deadline/cancellation、插件异常隔离、result schema 校验和大小限制、`state.updated` 处理、delivery backpressure 和 telemetry。

典型 run 时序：

```text
QueryEntryAdapter / EventRouter
  -> AgentRunOrchestrator.run(event, binding)
  -> AgentRunnerRegistry.resolve(runner_id)
  -> AgentResourceBuilder.freeze_snapshot(binding, event)
  -> AgentRunSessionRegistry.register(run_id, runner_id, snapshot)
  -> AgentContextBuilder.build(event, binding, snapshot)
  -> PluginRuntimeConnector.run_agent(ctx)
       -> AgentRunAPIProxy action
          -> validate active run session + caller identity + snapshot
          -> Host API / Store
       <- AgentRunResult stream
  -> apply state.updated to PersistentStateStore
  -> write message.completed / artifact.created to Transcript / ArtifactStore
  -> render delivery or raise RunnerExecutionError
  -> AgentRunSessionRegistry.unregister(run_id)
```

`run_from_query()` 保留为 Query entry adapter 入口，但内部转换成 event + binding 后走统一 `run()`。约束：`ChatMessageHandler` 不解析 `plugin:*`、不实例化 wrapper、不知道 runner 组件细节；`PipelineService` 从 registry 读取 metadata，不直接访问插件 runtime；跨请求持久化状态必须走授权 storage / 外部服务。

### 4.5 Resource Authorization（三层裁剪）

LangBot 在每次 run 前生成 `ctx.resources`（PROTOCOL_V1 §6），来自三层约束：

1. runner manifest 声明的 `permissions`（最大能力）。
2. binding / resource policy 允许的资源范围。
3. 当前 event / actor / bot / workspace 的实际权限。

这次裁剪结果必须冻结为 run-scoped authorization snapshot，并由
`AgentRunSessionRegistry` 按 `run_id` 保存。`ctx.resources` 是投影给 runner
看的同一份授权结果；运行期每个 proxy action 只依据该 snapshot 校验 active
run session、caller plugin identity、resource id、scope、payload size、rate
limit 和 deadline。Handler 不应重新执行三层裁剪，否则 build-time 与 runtime
授权逻辑会漂移。

SDK 侧本地校验只用于开发体验，host 侧 run authorization snapshot 才是安全边界。

资源裁剪应通用，不写死 local-agent。selector 与资源的映射示例：`model-fallback-selector` → primary/fallback LLM、`llm-model-selector` → LLM、`rerank-model-selector` → rerank 模型、`knowledge-base-multi-selector` → 知识库；新增 selector 时在 resource builder 中统一扩展。

执行/文件/skill/MCP 等能力的接入方向：先由 Host 封装成普通 tool，再通过 `ctx.resources.tools` 进入 runner；runner 不应识别或硬编码执行环境 provider。

### 4.6 State / Storage

LangBot 可提供 host-owned state 让 runner 寄宿状态（conversation / actor / subject / runner / binding / workspace state），但**不是强制**。Host 只需提供：授权开关、scope key、get/set/list/delete API（见 PROTOCOL_V1 §8）、持久化 backend、审计和清理策略。外部 agent runtime 可维护自己的 session 和 memory。进程内 state store 只能作为过渡实现，不能作为正式生产语义。

### 4.7 EventLog / Transcript / Artifact（事实源）

- `EventLog`: durable append-only，保存原始事件、系统事件、工具调用、投递结果、错误。
- `Transcript`: 从 EventLog 投影出的对话视图，用于 UI、审计和按需历史读取。
- `ArtifactStore`: 保存大文件、多模态输入、工具大结果、平台附件。

三类数据与 working context 的边界、读取约束见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。AgentRunner 可读取这些能力，但不被迫使用 LangBot 作为唯一记忆系统。

### 4.8 Prompt / Instruction Package（占位）

当前 Query 入口不把 preprocessing 后的有效 prompt 放进 adapter metadata。目标形态是 Host 保存或生成一个 run-scoped instruction package，runner 通过 Host API 拉取：

- Host 记录静态绑定 prompt、host hook / user plugin 产生的 instruction fragment、来源和审计信息。
- `ctx.context.available_apis` 增加 `prompt_get` 能力位表示拉取是否可用。
- Runner 拉取后仍由自己决定如何与 history、RAG、tool 结果、memory 和当前输入组装最终 prompt。
- Host 不实现通用 agentic prompt assembler，也不把 Query entry adapter prompt 作为长期业务输入契约。

### 4.9 External harness resource projection

Claude Code、Codex、Kimi Code 等外部 harness runner 可能不直接调用 LangBot 的 model/tool loop，而是把 LangBot 事件和授权资源投影到自己的 harness 执行。Host 侧仍保持统一边界：Host 负责构造 event-first context、资源授权、state/storage、EventLog/Transcript/ArtifactStore 和审计；Host 或 binding policy 决定哪些 MCP server、skill、artifact、history/state 句柄可投影给 runner；runner plugin 把 scoped projection 转成目标 harness 可消费形式；外部 harness 负责自己的 native session、tool loop、压缩、权限模式和 resume。

投影的具体形态（context 文件、skill 目录、MCP config、state pointers）见 AGENT_CONTEXT_PROTOCOL §4.5；Claude Code / Codex 当前实现见 OFFICIAL_RUNNER_PLUGINS §7。发布级隔离要求见 SECURITY_HARDENING。

## 5. SDK 侧协议

SDK 组件入口如下；所有数据结构定义见 PROTOCOL_V1。

```python
class AgentRunner(BaseComponent):
    __kind__ = "AgentRunner"

    @classmethod
    def get_capabilities(cls) -> AgentRunnerCapabilities: ...   # PROTOCOL_V1 §4.3

    @classmethod
    def get_config_schema(cls) -> list[dict]: ...

    async def run(self, ctx: AgentRunContext) -> AsyncGenerator[AgentRunResult, None]: ...
    # ctx: PROTOCOL_V1 §5.2 ; AgentRunResult: PROTOCOL_V1 §7
```

- Manifest / capabilities / permissions / context policy：PROTOCOL_V1 §4。
- `AgentRunContext`：PROTOCOL_V1 §5.2。`messages` / `bootstrap` 不是协议字段。
- `AgentRunResult`：PROTOCOL_V1 §7。
- `AgentRunAPIProxy`：PROTOCOL_V1 §8，是 runner 访问 host 能力的唯一入口，所有请求带 `run_id`。
