mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-29 20:05:25 +08:00
chore: rename internal docs/ to dev-docs/ and prepare docs site merge
Move the 27 internal design docs (87 tracked files) from docs/ to dev-docs/ to make room for the public documentation site, adjust Docker/fnOS ignores, refresh the README/AGENTS/ARCHITECTURE references, and land the docs workflows ahead of the subtree import so they cannot diverge from mirror. Co-Authored-By: Mika (Multica) <[email protected]> Co-authored-by: multica-agent <[email protected]>
This commit is contained in:
co-authored by
Mika
multica-agent
parent
a89ddf12e1
commit
0582d1b3d4
@@ -0,0 +1,149 @@
|
||||
# Agent-owned Context 协议设计
|
||||
|
||||
本文档描述插件化 Runner 场景下的上下文边界**设计理由**。结论先行:LangBot 不应成为最终 agentic context manager;它提供 context substrate,Runner 或其背后的 runtime 自己决定如何管理历史、压缩、召回和 KV cache。
|
||||
|
||||
> 涉及的数据结构(`RunnerContext`、`ContextAccess`、`RunnerAPIProxy` 等)唯一定义在 [PROTOCOL_V1.md](./PROTOCOL_V1.md)。本文只讲语义和约束,不重抄 schema。
|
||||
|
||||
## 1. 设计原则
|
||||
|
||||
### 1.1 Agent 拥有上下文策略
|
||||
|
||||
不同 runner 背后的 runtime 差异很大:
|
||||
|
||||
- 官方 local-agent 可能依赖 LangBot 的模型、工具、知识库和存储。
|
||||
- Claude Code SDK / Codex 类 runtime 有自己的 session、transcript、tool loop 和上下文压缩。
|
||||
- Pi Agent SDK 或外部 agent 平台可能只需要当前事件和一个外部 conversation key。
|
||||
|
||||
因此 LangBot 不应强行决定最终传给模型的历史窗口。Host 只提供:当前事件的完整结构化信息、稳定身份和会话引用、可授权读取的 history / event / state API、sandbox/workspace 文件能力、可投影给外部 harness 的 scoped context / SDK-owned MCP bridge / resource handles、payload hard cap 和权限 guardrail。
|
||||
|
||||
### 1.2 Host 不定义通用历史窗口
|
||||
|
||||
历史窗口策略不是 Runner 协议或 Query entry adapter 的核心概念。Host 只提供 history pull API、cursor、hard cap 和权限边界;runner 自己决定是否读取、读取多少、如何截断和压缩。
|
||||
|
||||
正确的问题不是"LangBot 每轮裁几轮历史给 agent",而是:
|
||||
|
||||
- 这类 runner 是否自管 context?
|
||||
- 事件到来时 host 应 inline 哪些最小信息?
|
||||
- agent 需要更多上下文时通过什么 API 拉取?
|
||||
- host 如何保证安全、可审计和可分页?
|
||||
|
||||
### 1.3 Host 保存事实源,Agent 管理 working context
|
||||
|
||||
三类数据要分开:
|
||||
|
||||
- `EventLog`: Host 保存原始事件、工具调用、投递结果、错误和系统事件。
|
||||
- `Transcript`: Host 从 EventLog 投影出的对话视图,用于 UI、审计和按需历史读取。
|
||||
- `Working context`: Agent 本轮实际送进模型或 runtime 的上下文,由 Runner 决定。
|
||||
|
||||
LangBot 不提供 host-side inline history window。简单 runner 如果需要历史窗口,应在 runner 内部通过 Host history API 拉取并裁剪。
|
||||
|
||||
## 2. Event 到来时传什么
|
||||
|
||||
默认 `RunnerContext`(PROTOCOL_V1 §5.2)应尽量小且稳定。默认规则:
|
||||
|
||||
- Host MUST NOT inline full history by default.
|
||||
- Host SHOULD inline only current event / input and context handles.
|
||||
- Runner owns working-context assembly.
|
||||
- Runner MAY use Host history / event / state / storage API and sandbox/workspace file tools when authorized.
|
||||
- Official runners MUST consume Host infrastructure through the same public API as third-party runners.
|
||||
|
||||
### 2.1 必须 inline 的内容
|
||||
|
||||
当前 event 的类型/id/时间/source;当前输入文本和结构化内容;附件/文件/图片的 metadata、path 或 URL;actor / subject / conversation / thread / bot / workspace;delivery 能力;已授权资源列表;context cursors 和可用 API 能力;Agent/runner config。这些是 agent 决定下一步所需的最低信息。
|
||||
|
||||
### 2.2 默认不 inline 的内容
|
||||
|
||||
完整历史消息、大文件全文、大工具结果、全量知识库内容、平台原始 payload 大对象、每轮重新生成的大段 summary。这些会破坏跨进程序列化成本、泄露范围、KV cache 稳定性,也会迫使 host 替 agent 做 context 策略。
|
||||
|
||||
### 2.3 不提供 Host Inline History Window
|
||||
|
||||
`RunnerContext` 不包含 `bootstrap` 字段。Host 不下发历史窗口,也不通过 Pipeline 配置决定窗口大小。runner 若需要类似 `recent_tail` 的策略,应在自己的 manifest/config schema 中声明参数,并在 runner 内部通过 history API 读取、裁剪和压缩。Host 只负责权限、分页、hard cap 和事实源。
|
||||
|
||||
## 3. ContextAccess 的作用
|
||||
|
||||
`ContextAccess`(PROTOCOL_V1 §5.8)是 host 交给 agent 的上下文读取入口描述,告诉 agent:当前事件位于哪条 conversation / thread、若需要更多历史从哪个 cursor 开始拉、host inline 了什么没 inline 什么、当前 run 有哪些 context API 权限。
|
||||
|
||||
## 4. Agent 如何获取更多上下文
|
||||
|
||||
所有 API 都走 `RunnerAPIProxy`(PROTOCOL_V1 §8),由 host 用 `run_id` 校验。
|
||||
|
||||
外部 harness 不能直接访问 LangBot 资源。无论是 history、event、state、model、tool、knowledge base,还是 LangBot skills,都必须通过 SDK runtime 转发到 Host API,并由 Host 按 active `run_id`、runner identity、binding resource policy 和 caller plugin identity 校验。当前运行文件进入授权 sandbox/workspace 后,再由 runner 用 read/write/exec 类工具按需访问。harness 自己的 native tools 只属于 harness 执行环境,不能绕过 SDK runtime 访问 LangBot 内部资源。
|
||||
|
||||
### 4.1 History
|
||||
|
||||
```python
|
||||
await api.history_page(conversation_id=ctx.context.conversation_id,
|
||||
before_cursor=ctx.context.latest_cursor,
|
||||
limit=50, direction="backward", include_attachments=False)
|
||||
```
|
||||
|
||||
返回 `HistoryPage`(schema 见 PROTOCOL_V1 §8)。
|
||||
|
||||
约束:`limit` 有 host hard cap;默认只能读当前 conversation / thread;跨会话读取需 binding policy / run authorization snapshot 授权;可返回 attachment ref,不默认返回大文件内容。
|
||||
|
||||
### 4.2 Search
|
||||
|
||||
```python
|
||||
await api.history_search(query="用户之前提到的数据库连接信息",
|
||||
filters={"conversation_id": ..., "event_types": ["message.received"]},
|
||||
top_k=10)
|
||||
```
|
||||
|
||||
Search 可先用数据库全文索引,后续接 embedding recall。它是 host 检索能力,不等于 agent 的长期记忆策略。
|
||||
|
||||
### 4.3 Event / State
|
||||
|
||||
- Event API(`events.get` / `events.page`)用于读取非消息事件、工具事件、系统事件。Agent 不应把所有事件都当成 user/assistant message。
|
||||
- State API(`state.get` / `set`)是可选寄宿能力。自管 runtime 可以完全不用;依附 LangBot 的官方 runner 可以使用,例如 `external.session_id`、`summary.checkpoint`。
|
||||
|
||||
### 4.4 大文件与工具协作
|
||||
|
||||
大文件、多模态输入和工具产物不要内联进 prompt 或 tool result:message/content 里只放小文本和必要摘要;当前事件附件由 Host staged 到授权 sandbox/workspace,并在 input attachment 中给出轻量 metadata/path。工具之间传递大结果时传 sandbox path 或 attachment ref,不传完整 blob。Host 只保证当前 run 授权范围,默认不允许插件直接读任意本地路径;临时文件由 sandbox 生命周期和清理机制管理。
|
||||
|
||||
### 4.5 External harness context projection
|
||||
|
||||
外部 harness 的总体边界以 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md) §4.8 为准。本节只描述 context projection 的推荐形态。
|
||||
|
||||
Claude Code、Codex、Kimi Code 这类 runtime 通常已有自己的 session、工具 loop、MCP 加载、上下文压缩和工作目录。LangBot 不应把它们改造成"host prompt assembler",而应提供可审计的事件和资源投影。推荐 projection 形态:
|
||||
|
||||
- `agent-context.json`:结构化 JSON,包含 `run_id`、`event`、`actor`、`subject`、`input`、`delivery`、`resources`、`context`、`state`、`runtime`。
|
||||
- `LANGBOT_CONTEXT.md`:人类可读摘要。
|
||||
- `resources`:只包含本次 run 授权后的资源句柄和能力摘要,不暴露 Host 内部私有对象、secret 或资源内容。
|
||||
- `skills`:LangBot skills 不是直接投影给 harness native tool loop 的文件能力,而是**一组被授权的 tool**。发现走 `list_skills`(或 `langbot_list_assets` 增加 skills 一类),激活/注册走 `activate` / `register_skill`,包内操作走 native exec/read/write,统一通过 `ctx.resources.tools`、`RunnerAPIProxy` 或 SDK-owned MCP bridge 暴露。Host 不向 prompt 注入 skill 索引(无 progressive-disclosure 注入);harness 通过调用发现工具主动查询 skill 清单。`agent-context.json` 的 `skills` 字段仅作发现工具的数据来源与可选 `suggested_skill_prompt` 的输入。
|
||||
- `MCP config`:只投影 per-run、scoped 的 SDK-owned bridge 或外部 MCP 连接配置;LangBot 资源访问必须回到 SDK runtime / Host API,不允许 harness 通过自带 MCP/native tool 直接读 Host 内部资源。
|
||||
- `state pointers`:外部 session id、working directory、checkpoint 等小型 JSON 状态通过 Host state API 保存。
|
||||
|
||||
当前官方外部 harness 路径由 ACP / Claude Code / Codex 等 runner 插件承担(现状见 OFFICIAL_RUNNER_PLUGINS §7)。这类 projection 是"把 LangBot 事实源和授权资源句柄交给 harness",不是"把 LangBot 资源本体或内部权限交给 harness",也不是"由 LangBot 决定最终模型上下文"。
|
||||
|
||||
## 5. Runner 上下文边界
|
||||
|
||||
Host 只给当前事件、当前输入和 context handles。Runner 是否能拉取历史、事件、state 或 storage、是否能访问 sandbox/workspace 文件,以运行时 `ctx.context.available_apis` 和工具授权为准;runner 自己决定是否拉取历史、是否搜索、何时摘要、如何构造最终 prompt。
|
||||
|
||||
## 6. KV cache 友好的上下文管理
|
||||
|
||||
支持 Claude Code SDK、Codex、Pi Agent SDK 等 runtime 时,必须避免每轮由 LangBot 重组大块 prompt:
|
||||
|
||||
- 稳定 session key:`workspace/bot/binding/runner/conversation/thread`。
|
||||
- 每轮只传 delta:当前 event、attachment refs/path、少量 runtime metadata。
|
||||
- 历史 append-only:不要每轮改写同一段 history 文本。
|
||||
- Summary checkpoint 稳定:只有压缩发生时产生新 checkpoint。
|
||||
- 大文件和工具结果写入 sandbox/workspace。
|
||||
- Tool/context API schema 稳定,数据通过 API 拉取而非塞入 prompt。
|
||||
- 对自管 runtime,优先让它复用自身 session/cache,而不是强制 LangBot 每轮重放 transcript。
|
||||
- 模型窗口元信息应作为 resource/runtime metadata 暴露给 runner,由 runner 决定预算和压缩策略。
|
||||
|
||||
稳定 session key 的用途是隔离外部 runtime 的 resume/cache/state,不是改变 PROTOCOL_V1 §13 定义的 Agent 复用和 dispatch 边界。只有当某个外部 harness 的同一 native session 不支持并发 turn 时,runner 或 future runtime control plane 才应按 external session key 做 turn-level 串行化。
|
||||
|
||||
对长期运行的 external harness / daemon,推荐运行形态是 reader 与 writer 分离:一个 session reader 独占读取 stdout/SSE/native event stream,并把 native event 转成 `RunnerResult` 或 task progress;用户输入只作为 turn write 进入该 session。当前一次性 CLI subprocess runner 可以继续在单次 `run(ctx)` 内同步收集 stdout,但后续改成长连接时不应让多个 request 同时读取同一 native stream。
|
||||
|
||||
## 7. Host guardrail
|
||||
|
||||
Agent 自管 context 不代表无限制访问。LangBot 仍必须控制:每次 run 的 active `run_id`、runner identity、当前 binding 的 resource policy、conversation / actor / subject scope、page size / sandbox file read size / API rate limit、跨会话读取权限、数据脱敏和敏感变量过滤、审计日志。Host 不负责"最佳上下文策略",但负责"不越权、不爆内存、不不可审计"。
|
||||
|
||||
外部 harness 的 native tools、shell、MCP 或 skill 机制不构成 LangBot 资源授权边界。只要访问的是 LangBot 持有的资源,就必须经 SDK runtime 转发并接受 Host 校验;完整边界见 HOST_SDK §4.8。
|
||||
|
||||
## 8. 官方 runner 与业务编排边界
|
||||
|
||||
官方 runner 插件可以把状态寄宿在 LangBot,但必须和第三方 runner 一样通过公开 Host API 消费。LangBot core 不内置官方 agent 的业务流程(prompt 组装、tool loop、RAG 编排、summary/compaction、"local-agent 专用"状态字段)。
|
||||
|
||||
官方 local-agent 应作为"依附 LangBot 基础设施的复杂 runner 参考实现":transcript/history 通过 `api.history_page()` / `api.history_search()` 读取,summary/checkpoint/外部 session id/用户偏好通过 `api.state_get()` / `api.state_set()` 或 storage 方法保存,图片/文件/工具大结果通过 sandbox/workspace read/write 工具访问,模型/工具/知识库通过 `api.invoke_llm()` / `api.call_tool()` / `api.retrieve_knowledge()` 调用。这样 LangBot 保持为通用 agent host,不变成内置 agent 框架。具体迁移要求见 [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md)。
|
||||
@@ -0,0 +1,259 @@
|
||||
# Runner QA 指南
|
||||
|
||||
本文档是 agent-runner 插件化下一轮测试的唯一 QA 入口。它合并并取代旧的 Phase 1 验收矩阵与 2026-05-18 / 2026-05-29 两份本地 QA 报告。
|
||||
|
||||
目标不是保留完整历史流水账,而是指导测试 agent 用最小但高价值的路径判断当前分支是否仍然健康。
|
||||
|
||||
## 1. 测试边界
|
||||
|
||||
当前主线验证的是 Runner Protocol v1:
|
||||
|
||||
```text
|
||||
event -> binding -> runner.run(ctx) -> result stream
|
||||
```
|
||||
|
||||
本指南验证:
|
||||
|
||||
- Host 能通过当前 Query entry adapter 进入 event-first `run(event, binding)` 主链路。
|
||||
- Runner 来自插件 registry,而不是旧内置 runner 分支。
|
||||
- `local-agent` 能消费 Host 模型、工具、知识库、history、state、sandbox 文件等基础设施。
|
||||
- 外部 harness runner(ACP / Claude Code / Codex 等直接 runner 插件)能消费 event-first context,并把外部 session 指针写回 host-owned state。
|
||||
- 错误、权限裁剪、无输出、timeout 等路径不会破坏主聊天流程。
|
||||
|
||||
本指南不验证:
|
||||
|
||||
- 完整外部 harness daemon 管控和分布式业务队列;已实现的 run ledger / heartbeat / claim 原语仍应执行定向回归。
|
||||
- 尚未实现的通用事件订阅和定时自动化;已集成的 Bot 路由、独立 Agent 和 Pipeline 必须纳入当前产品验收。
|
||||
- 发布级 path isolation、secret filtering、MCP allowlist、资源配额和 workspace cleanup。
|
||||
- 所有外部服务 runner 的真实凭据联调。
|
||||
|
||||
这些属于后续能力或发布门槛,分别见 [RUNTIME_CONTROL_PLANE_V2.md](./RUNTIME_CONTROL_PLANE_V2.md) 与 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md)。
|
||||
|
||||
## 2. 状态定义
|
||||
|
||||
测试报告只使用以下状态:
|
||||
|
||||
| 状态 | 含义 |
|
||||
| --- | --- |
|
||||
| PASS | 按步骤执行,用户可见行为和日志证据都满足通过条件。 |
|
||||
| FAIL | 环境可用,但行为不满足通过条件。 |
|
||||
| BLOCKED | 凭据、CLI、外部服务、测试数据或本地配置缺失导致无法执行。必须写清阻塞原因。 |
|
||||
| N/A | 当前 runner 或平台明确不支持该能力。必须引用 manifest、文档或配置说明。 |
|
||||
|
||||
不能使用“看起来正常”“大概通过”“基本没问题”等模糊状态。
|
||||
|
||||
## 3. 执行顺序
|
||||
|
||||
2026-09-05 更新:先记录 Core/SDK/Runner 提交、发行版元数据与实际 import 路径,区分 editable 工作区和正式包安装。当前定向测试及前端未通过断言见 [STATUS.md](./STATUS.md)。本指南的步骤不是已执行记录。
|
||||
|
||||
本轮发布验收至少覆盖:
|
||||
|
||||
- 空白实例从市场安装 Runner,创建 Agent 与 Pipeline,并绑定到 Bot。
|
||||
- 同一 Bot 的消息事件走 Pipeline,非消息事件走独立 Agent;dry-run 与保存后的路由结果一致。
|
||||
- `event_*` 使用冻结目标;未授权 `platform_*`、额外参数和失效机器人调用被拒绝;SDK/Python 与 MCP gateway 均走 Host 工具授权。
|
||||
- `interaction.requested` 回调恢复原处理器,重复/过期/跨作用域提交被拒绝。
|
||||
- 分别记录合成事件、mock provider、真实平台和真实 provider 的结果,不能相互替代。
|
||||
|
||||
推荐按以下顺序执行,前一层失败时不要继续扩大测试面:
|
||||
|
||||
1. Host / SDK / runner 单测。
|
||||
2. WebUI 登录与 Pipeline Debug Chat 基础 smoke。
|
||||
3. `local-agent` 高价值场景。
|
||||
4. 外部 code-agent harness smoke。
|
||||
5. 权限和错误路径补充检查。
|
||||
6. 汇总 PASS / FAIL / BLOCKED,并给出下一步建议。
|
||||
|
||||
用户可见流程必须通过 WebUI 或真实消息平台验证。API / curl 只能作为诊断证据,不能单独让 UI case PASS。
|
||||
|
||||
## 4. 必跑基线
|
||||
|
||||
### 4.1 单测基线
|
||||
|
||||
在 LangBot 仓库运行:
|
||||
|
||||
```bash
|
||||
uv run --frozen pytest tests/unit_tests/agent
|
||||
```
|
||||
|
||||
如果本次改动只触及默认配置或 API service,也至少补跑相关目标测试,例如:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/unit_tests/api/test_pipeline_service_defaults.py
|
||||
```
|
||||
|
||||
通过条件:
|
||||
|
||||
- agent 单测全 PASS,或失败项已确认与本次 agent-runner 路径无关。
|
||||
- 若失败来自 `context_builder`、`orchestrator`、`session_registry`、`resource_builder`、`plugin/handler.py` 的 run action 权限路径,不应进入 UI smoke。
|
||||
|
||||
### 4.2 环境基线
|
||||
|
||||
用 `langbot-skills` 做环境检查:
|
||||
|
||||
```bash
|
||||
cd "$LANGBOT_SKILLS_REPO"
|
||||
bin/lbs env doctor
|
||||
bin/lbs case list
|
||||
```
|
||||
|
||||
`LANGBOT_SKILLS_REPO` 指向当前工作区里的 `langbot-skills` 仓库。优先使用已有 case,而不是临时发明测试路径。
|
||||
|
||||
推荐首批 case:
|
||||
|
||||
- `webui-login-state`
|
||||
- `pipeline-debug-chat`
|
||||
- `local-agent-basic-debug-chat`
|
||||
- `local-agent-rag-debug-chat`(改动涉及 RAG / knowledge)
|
||||
- `local-agent-plugin-tool-call-debug-chat`(改动涉及 tool / resource policy)
|
||||
|
||||
## 5. WebUI 主链路 Smoke
|
||||
|
||||
### 5.1 Runner registry
|
||||
|
||||
步骤:
|
||||
|
||||
1. 打开 WebUI Pipeline 配置页。
|
||||
2. 查看 AI runner 下拉列表。
|
||||
3. 选择 `plugin:langbot-team/LocalAgent/default`。
|
||||
4. 保存并刷新页面。
|
||||
|
||||
通过条件:
|
||||
|
||||
- runner 选项来自插件 registry。
|
||||
- 保存后配置仍为 `ai.runner.id` + `ai.runner_config[id]`。
|
||||
- `runner_config` 表示 Agent/runner config,不表示插件实例状态。
|
||||
- 不读取或回写旧 `ai.runner.runner` 字段。
|
||||
- 不出现旧内置 runner stage 名(例如裸 `local-agent`)作为当前选中项或配置 surface。
|
||||
- 插件没有循环重启或 metadata 加载失败。
|
||||
|
||||
### 5.2 主聊天路径
|
||||
|
||||
步骤:
|
||||
|
||||
1. 使用绑定 `plugin:langbot-team/LocalAgent/default` 的 Pipeline。
|
||||
2. 在 Debug Chat 发送确定性普通文本。
|
||||
3. 查看 WebUI 回复和后端日志。
|
||||
|
||||
通过条件:
|
||||
|
||||
- 用户可见回复正常。
|
||||
- 后端日志显示走 `AgentRunOrchestrator` / `RUN_RUNNER`。
|
||||
- 不走旧内置 local-agent 主执行分支。
|
||||
- conversation transcript 写入用户消息和助手消息。
|
||||
|
||||
## 6. `local-agent` 高价值测试
|
||||
|
||||
只保留最能覆盖架构边界的场景。
|
||||
|
||||
| ID | 场景 | 操作 | 通过条件 |
|
||||
| --- | --- | --- | --- |
|
||||
| LA-01 | 绑定 prompt | 配置 system prompt 后发送文本。 | runner 使用 `ctx.config.prompt`,不读取 `ctx.adapter.extra["prompt"]`;回复体现绑定 prompt。 |
|
||||
| LA-02 | history API | 连续两轮对话,第二轮引用第一轮 marker。 | runner 通过 Host history API 或自管上下文读取历史,不依赖 inline history window。 |
|
||||
| LA-03 | 流式 / 非流式 | 分别用支持流式和关闭流式的路径发送文本。 | 流式 UI 不重复、不空白;非流式只输出最终消息。 |
|
||||
| LA-04 | 工具调用 | 绑定测试工具,发送会触发工具的 prompt。 | `ctx.resources.tools` 只包含授权工具;工具调用 started/completed;最终回复包含工具结果。 |
|
||||
| LA-05 | RAG | 绑定测试知识库,发送命中文档的 prompt。 | `ctx.resources.knowledge_bases` 包含所选知识库;runner 通过授权 API 检索;回复使用检索内容。 |
|
||||
| LA-06 | 多模态 | 发送图片输入。 | `ctx.input.contents` 保留图片;支持视觉模型时正常处理,不支持时受控失败。 |
|
||||
| LA-07 | fallback / 错误 | 模拟 primary 模型失败或 runner 抛错。 | fallback 或 `run.failed` 行为受控;后续请求不受影响。 |
|
||||
| LA-08 | 无输出保护 | 测试 runner 完成但不产出消息。 | 不产生空白成功回复;按受控失败或明确缺陷处理。 |
|
||||
| LA-09 | steering / 运行中追加消息 | 使用支持 steering 的 runner,第一条消息触发长 run;run 未结束时在同 conversation 追加第二条消息。 | 第二条消息被 active run claim,不启动并发 run;runner 通过 `steering_pull` 看到追加输入;EventLog 有 `queued` -> `steering.injected`,若未消费则有 `steering.dropped` 终态;后续普通消息仍可处理。 |
|
||||
|
||||
Rerank、remove-think、文件输入等场景只在本次改动直接涉及时补测,不作为每轮必跑项。
|
||||
|
||||
## 7. Code-agent Harness Smoke
|
||||
|
||||
这些测试用于验证 ACP、Claude Code、Codex 这类自管 runtime 能走同一条 Host 协议路径。若目标 harness 没有 CLI/daemon、登录态、代理配置或远端 workspace,标记 BLOCKED,不要伪造 PASS。
|
||||
|
||||
Smoke 前应优先保留一层轻量单测或 fixture 测试:session 创建/复用、消息发送、结果解析、`run_id` 注入和 LangBot MCP gateway 必须有稳定测试覆盖。WebUI smoke 证明真实链路可用,但不能替代转换层和错误映射测试。
|
||||
|
||||
### 7.1 外部 harness runner
|
||||
|
||||
步骤:
|
||||
|
||||
1. 确认目标 harness(例如 ACP daemon、Claude Code 或 Codex)在对应机器上可执行且已登录。
|
||||
2. 绑定目标 runner,例如 `plugin:langbot-team/ACPRunner/default`、`plugin:langbot-team/ClaudeCodeAgent/default` 或 `plugin:langbot-team/CodexAgent/default`。
|
||||
3. 配置 runner 必要字段,例如 remote target、workspace、provider、startup timeout、reuse session 等。
|
||||
4. 在 Debug Chat 执行一次确定性真实 smoke。
|
||||
5. 检查 LangBot MCP gateway、`run_id` 回填和 host-owned state。
|
||||
|
||||
通过条件:
|
||||
|
||||
- WebUI 可见回复包含预期 sentinel。
|
||||
- 发送给 harness 的消息包含当前 LangBot `run_id` 和可访问资源摘要。
|
||||
- Harness 通过 gateway 调用 `langbot_history_page`、`langbot_retrieve_knowledge` 或 `langbot_call_tool` 时必须携带正确 `run_id`;错误 run id 被拒绝。
|
||||
- `external.session_id` 写入 host-owned state。
|
||||
- 外部 harness 错误、timeout、empty output 都转成受控 `run.failed`。
|
||||
- resume 到同一 external session 时,全局锁边界符合 PROTOCOL_V1 §13。
|
||||
|
||||
### 7.2 API 型外部 runner
|
||||
|
||||
Dify、n8n、Coze、DashScope、Langflow、Tbox 等外部服务 runner 不作为每轮必跑项。只有在本次改动触及对应 runner 或凭据已经可用时执行 smoke。
|
||||
|
||||
通过条件:
|
||||
|
||||
- runner 可选,配置可保存。
|
||||
- 请求成功,或外部服务错误被清晰返回。
|
||||
- 外部服务凭据缺失时标记 BLOCKED,并记录缺失项。
|
||||
|
||||
## 8. 权限与隔离补充
|
||||
|
||||
以下优先用单测 / targeted fixture 覆盖,不要求每次通过 UI 人工构造恶意 runner。
|
||||
|
||||
| 场景 | 推荐证据 |
|
||||
| --- | --- |
|
||||
| 未授权模型调用被拒绝 | `plugin/handler.py` run action 权限测试或目标单测。 |
|
||||
| 未授权工具调用被拒绝 | `ctx.resources.tools` 与 host action 拒绝日志。 |
|
||||
| 未授权知识库检索被拒绝 | `ctx.resources.knowledge_bases` 与 host action 拒绝日志。 |
|
||||
| run_id 结束后复用被拒绝 | session registry 注销测试。 |
|
||||
| 插件身份不匹配被拒绝 | `caller_plugin_identity` mismatch 测试。 |
|
||||
| 绑定插件身份的 run_id 省略 caller identity 被拒绝 | `_validate_run_authorization(..., caller_plugin_identity=None)` 返回错误。 |
|
||||
| 未注册 Runtime 连接伪造插件身份被剥离 | SDK runtime forwarding 测试:请求自带 `caller_plugin_identity` 时,未注册连接转发前必须 `pop`,已注册连接必须覆盖为真实插件身份。 |
|
||||
| storage/state scope 越权被拒绝 | state/storage proxy 单测。 |
|
||||
| steering claim 异常不杀 consumer loop | controller 单测:无效 runner / registry 异常只让当前消息回到普通 session 槽位路径,消息消费循环继续。 |
|
||||
| steering queue 未消费有终态 | session registry / orchestrator 单测:队列有上限;run unregister 时未 pull 项写 `steering.dropped` 审计。 |
|
||||
|
||||
如果这些单测失败,不能用 WebUI 正常回复替代。
|
||||
|
||||
## 9. 证据要求
|
||||
|
||||
每轮测试报告至少记录:
|
||||
|
||||
- LangBot commit、SDK commit、相关 runner 插件 commit。
|
||||
- Pipeline UUID/name、runner id、关键 runner config 摘要。
|
||||
- WebUI 截图或 Playwright 操作记录。
|
||||
- 后端日志中对应 query id / run id 的关键行。
|
||||
- `langbot-skills` case/report 路径。
|
||||
- 外部 harness runner 的 context 文件、session id、working directory、CLI 错误摘要。
|
||||
- FAIL/BLOCKED 的复现步骤和归属仓库建议。
|
||||
|
||||
报告结论必须回答:
|
||||
|
||||
- 是否建议继续进入下一阶段测试。
|
||||
- 是否存在主聊天路径阻塞。
|
||||
- 是否只是凭据 / 外部服务 / 本机 CLI 缺失导致 BLOCKED。
|
||||
- 是否需要进入 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md) 的发布级验收。
|
||||
|
||||
## 10. 历史高价值记录
|
||||
|
||||
历史高价值记录与当前 runner 验收状态见 [STATUS.md](./STATUS.md)。本指南只保留可重复执行的测试步骤和证据要求。
|
||||
|
||||
### Event debug execution trace
|
||||
|
||||
The Agent workbench uses `POST /api/v1/agents/{uuid}/debug/stream` (NDJSON, `runtime.operate`). Verify incremental text and provider-returned reasoning, tool call arguments/results in execution order, automatic scrolling, and preservation of partial output on errors. Thinking is only shown when returned by the runner. Disconnecting cancels the debug task. The existing `/debug` endpoint and MCP `debug_agent` return final text and a bounded `execution_events` snapshot; they are not live transports.
|
||||
|
||||
Platform tools now use Host-owned mock adapters in synthetic debug runs. The model really invokes the authorized tool, validates its parameters, and receives a result; the Host does not call a live platform adapter. Event tools retain their frozen targets and platform tools retain explicit targets. Native, plugin and MCP tools continue to execute normally. A successful mock platform result is completion of that action in the debug run; runners must communicate that context to the model to avoid repeated attempts at real delivery.
|
||||
|
||||
The optional `mock` payload field (also editable under **Mock 场景(JSON)**) supports:
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": {"event_reply": "Simulated permission denied"},
|
||||
"results": {"event_get_actor": {"id": "user-42", "nickname": "Fixture User"}},
|
||||
"unsupported_apis": ["delete_message"]
|
||||
}
|
||||
```
|
||||
|
||||
`errors` and `results` cannot both override the same tool. Unknown tools/APIs and malformed options are rejected before model execution. Default read fixtures follow SDK `User`, `UserGroup`, `UserGroupMember` and `MessageReceivedEvent` structures; list reads return a synthetic fixture list (override with `[]` to test emptiness). `unsupported_apis` participates in the normal capability intersection, so unsupported tools are not offered to the model. Event payloads should include the intended group/member/request IDs; the built-in presets supply examples.
|
||||
|
||||
Verify a welcome event shows `event_reply`, the expected text and frozen target, and **模拟执行成功 · Mock**. Plain text alone must not count as a successful platform action. A simulated failure must show **模拟执行失败 · Mock**. Also verify **停止调试** retains the partial trace and allows the next run, and that entering a new draft while streaming does not erase it on completion.
|
||||
|
||||
Full event/Mock regression evidence: [2026-09-05 follow-up](./EVENT_DEBUG_FULL_QA_2026-09-05.md).
|
||||
@@ -0,0 +1,96 @@
|
||||
# Event Based Agent 接入设计
|
||||
|
||||
> 更新:2026-09-05。EBA 平台事件、Bot 路由和独立 Agent 已集成到 `dev/4.11.x`。通用事件订阅、通知与定时自动化仍是后续扩展。
|
||||
>
|
||||
> 数据结构唯一定义在 [PROTOCOL_V1.md](./PROTOCOL_V1.md)(runner 可见)与 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md)(Host 内部模型);本文只讲 EBA 语义,不重抄 schema。
|
||||
> 与当前 runner 外化分支、后续 Agent Platform / Runtime Control Plane 的边界见 [EXTENSION_SCOPE_MATRIX.md](./EXTENSION_SCOPE_MATRIX.md)。
|
||||
|
||||
本文描述当前事件如何进入 LangBot、如何在平级的 Pipeline / Agent 之间路由,以及 Agent 如何复用插件化 Runner。路由逻辑由 `pkg/platform/botmgr.py::RuntimeBot` 承担;文中的 EventRouter 表示职责,不代表独立进程或同名类。
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
- 消息、撤回、入群、好友申请、定时任务、API 调用都能抽象为 host event。
|
||||
- EventRouter 可以根据 event type、bot、workspace、conversation、actor、subject 选择一个 Pipeline 或 Agent 处理器。
|
||||
- Pipeline 目标执行完整消息 Stage 链;Agent 目标通过统一 orchestrator 调用 Runner。
|
||||
- 非消息事件不伪造成用户文本消息。
|
||||
- 平台动作通过已授权的语义工具执行;结构化交互通过 `action.requested` 中的 `interaction.requested` 白名单执行。
|
||||
|
||||
## 2. 事件不是消息
|
||||
|
||||
`message.received` 只是事件的一种。协议不应假设:一定有用户文本、一定有 conversation history、一定要返回一条聊天消息、actor 一定等于 sender、subject 一定等于当前消息。
|
||||
|
||||
| event_type | actor | subject | input |
|
||||
| --- | --- | --- | --- |
|
||||
| `message.received` | 发消息的人 | 当前消息 | 文本、图片、文件等 |
|
||||
| `message.deleted` | 撤回操作者,未知时为系统 | 被删除消息 | 通常为空 |
|
||||
| `group.member_joined` | 新成员或邀请人 | 群/成员关系 | 通常为空 |
|
||||
| `friend.request_received` | 申请人 | 好友申请 | 验证消息或申请理由 |
|
||||
| `schedule.triggered` | 系统 | 定时任务 | 任务 payload |
|
||||
| `api.invoked` | API caller | API request | request payload |
|
||||
|
||||
## 3. 稳定事件名
|
||||
|
||||
当前平台事件名示例(定时任务与 API 事件示例仅表示未来入口;实际能力以 SDK 实体和适配器声明为准):
|
||||
|
||||
- `message.received`
|
||||
- `message.deleted`
|
||||
- `group.member_joined`
|
||||
- `friend.request_received`
|
||||
|
||||
平台原始事件名只能进入 `ctx.event.source_event_type` / `raw_ref`,不能成为 `ctx.event.event_type` 的公共契约。
|
||||
|
||||
## 4. Event Envelope 与 Binding
|
||||
|
||||
- 入口事件用 `AgentEventEnvelope`(HOST_SDK §4.1)承载;顶层字段使用 LangBot 稳定协议名,平台原始事件名和原始 payload 放 `metadata` / `raw_ref`。
|
||||
- EBA 持久路由通过 `event_pattern`、`filters`、`target_type` 和 `target_uuid` 选择处理器。只有 `target_type=agent`,或 Pipeline AI Stage 需要调用 runner 时,才进一步解析 `AgentBinding`(HOST_SDK §4.2)。
|
||||
|
||||
EBA 每个事件只选择一个有效处理器;Runner 调用的基数、Agent 复用和 fan-out 边界以 PROTOCOL_V1 §13 为准。
|
||||
|
||||
路由 scope 示例:workspace 全局、bot 级、platform channel 级、conversation / group / thread 级、user / actor 级。Pipeline 是 `message.*` 场景的一等处理器,适合需要预处理、AI、后处理、扩展和输出控制的消息链路;Agent 是 runner 驱动的一等处理器,可处理其声明支持的消息与非消息事件。二者都不会被转换成对方。
|
||||
|
||||
Event Source 可包括:`platform_adapter`(飞书、QQ、微信、Telegram 等)、`webui`、`http_api`、`scheduler`、`system`。EventRouter 不应写死平台 adapter 的类名。
|
||||
|
||||
## 5. EventRouter 调用链
|
||||
|
||||
```text
|
||||
Platform Adapter canonical event
|
||||
-> RuntimeBot record adapter event
|
||||
-> Plugin EventListener observer broadcast
|
||||
-> RuntimeBot match saved event_bindings and resolve one Processor target
|
||||
-> target_type=pipeline: MessageAggregator -> QueryPool -> Pipeline stages
|
||||
-> target_type=agent: resolve AgentBinding -> AgentRunOrchestrator
|
||||
-> RunnerContextBuilder -> PluginRuntimeConnector.run_runner()
|
||||
-> RunnerResult stream
|
||||
-> Host result delivery / authorized platform tool
|
||||
```
|
||||
|
||||
约束:Pipeline 和 Agent 是 EventRouter 的平级目标;Pipeline 仅接受消息事件,Agent 受其事件能力声明约束。任何 Runner 调用都必须复用现有 orchestrator,不能为 EBA 单独实现另一套 plugin runner 协议;非消息事件不能绕过 resource authorization;delivery 和 platform action 走统一权限模型;外部 harness runner 也通过同一套 envelope/binding/context/result 协议接入。observer / fan-out / parallel arbitration 的额外语义仍按 PROTOCOL_V1 §13 处理。
|
||||
|
||||
## 6. 平台动作执行
|
||||
|
||||
平台动作走统一工具入口,详见 [PLATFORM_ACTION_TOOLS.md](./PLATFORM_ACTION_TOOLS.md):
|
||||
|
||||
- `event_*` 的目标由 Host 从当前事件冻结;Agent 只填写动作参数。
|
||||
- `platform_*` 允许填写目标,必须在 `allowed_platform_tools` 中显式选择。
|
||||
- 最终资源与 Runner 权限、适配器能力及事件目标求交,执行时再校验运行身份和当前机器人。
|
||||
- SDK/Python `call_tool` 和 scoped MCP gateway 使用同一 Host 授权;原始 `call_platform_api` 不作为 Agent 工具开放。
|
||||
- `action.requested` 只执行白名单 `interaction.requested`,用于持久化交互和回调恢复;其它 action 仍是 telemetry,不能用于执行好友审核等平台动作。
|
||||
|
||||
事件可能没有默认 reply target;Host 不为缺少目标的事件猜测投递对象。Runner 只能使用当前授权允许的工具和投递能力(`DeliveryContext` 见 PROTOCOL_V1 §5.7)。
|
||||
当前 Host 会把 adapter 声明的通用 API 投影到
|
||||
`DeliveryContext.platform_capabilities.supported_apis`,并据此设置
|
||||
`supports_edit` / `supports_reaction`。该投影只供 runner 选择输出形态,不构成
|
||||
平台动作授权;合成测试 adapter 会移除副作用能力并抑制实际出站调用。
|
||||
|
||||
## 7. 与 Context 协议的关系
|
||||
|
||||
EBA 事件进入 Runner 时仍遵循 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md):inline 当前事件、大 payload 用 raw/staged file ref、不默认 inline 完整 history、agent 按需通过 API 拉取、Host 保留 EventLog 和权限 guardrail。非消息事件可以被投影进 Transcript,但不能强制伪装为 user message;Runner 根据 event type 自己决定是否纳入模型上下文。
|
||||
|
||||
## 8. 当前集成状态
|
||||
|
||||
当前分支已完成 EventRouter、Pipeline / Agent 平级处理器路由、Bot
|
||||
`event_bindings` 持久化与 WebUI、AgentBinding 投影、路由 dry-run、合成测试事件、
|
||||
运行状态和真实 OneBot 非消息事件到 Agent 的闭环。Pipeline 消息链和独立 Agent
|
||||
均复用同一个 Runner orchestrator / context / result 协议。
|
||||
|
||||
平台动作授权和结构化交互已实现,但真实平台/provider 验收不等同于单测通过。SDK 的 `platform_tools` 分类发现于 2026-09-05 检视时仍是未提交工作区改动。剩余发布工作和历史验证边界见 [STATUS.md](./STATUS.md)。通用订阅、Scheduler、Workflow 和多 Agent 串并联仍未作为产品交付。
|
||||
@@ -0,0 +1,74 @@
|
||||
# Event debug end-to-end regression — 2026-09-05
|
||||
|
||||
Environment: Windows, Vite :3000, Core :5300, standalone Plugin Runtime :5400/:5401, standalone Box :5410, Docker backend. Tests use browser controls in Edge and an isolated unbound Agent `E2E Debug 0905` (`49e52ae0-6a99-46ce-975e-70164b2786ec`). Local plugin maintenance uses the authenticated localhost management API because Edge file upload is disabled.
|
||||
|
||||
## Coverage inventory
|
||||
|
||||
- Message input: empty input, plain text, multi-turn context, draft edits during streaming.
|
||||
- Structured events: group join/leave/ban, friend request/add, feedback, bot state, message edit/delete/reaction, platform and custom events.
|
||||
- Payload validation: invalid JSON, non-object JSON, Unicode and nested fields.
|
||||
- Execution trace: intermediate/final text, returned thinking, tool arguments/results, tool failure, no-output failure, completion without duplication.
|
||||
- Lifecycle: save-and-run, provider failure and recovery, long output/autoscroll, switch Agent during run, reconnect.
|
||||
- Visual checks: scroll containment, long tool JSON, error card, narrow viewport.
|
||||
|
||||
## Findings and fixes
|
||||
|
||||
1. Synthetic debug Query omitted public Workspace fields. Native tools failed during skill mount lookup. Project trusted ExecutionContext fields onto synthetic queries; real Docker exec returned `E2E_TOOL_OK` after repair.
|
||||
2. LocalAgent context assembly omitted structured event data. Add a bounded user-role event facts message before current input, preserving ordinary empty-data message behavior. Browser join/leave/friend/feedback returned unique JSON probes after installing the patched local plugin.
|
||||
3. A model failure before visible output left an empty Agent card. Hide output entries with no visible execution steps.
|
||||
4. A completed message request cleared a newly edited draft. Clear only the submitted input value; the browser retained `NEXT_DRAFT_SHOULD_REMAIN`.
|
||||
5. Tool transport completion was shown as success even with `result.ok=false`. Treat explicit failed tool results as failure; real exec exit 7/stderr is preserved.
|
||||
6. Native file tools resolved a Core host path before selecting remote Box execution. Choose Box first for remote/no-host-root deployments, validate virtual path boundaries, and cover all five file tools.
|
||||
7. This local legacy config had no `box.local.host_root`, so Docker had no writable `/workspace` mount. Add an explicit local development root and keep the read-only root filesystem enabled.
|
||||
|
||||
8. LocalAgent repeats previous turns in cumulative chunks after tools. Strip the already displayed prefix for those chunks; real thought text now appears once across write/read.
|
||||
9. Remote grep generated Python `include = null` when no filter was supplied. Serialize it as a Python literal and test optional/quoted values.
|
||||
|
||||
## External configuration observations
|
||||
|
||||
`gpt-4.1-mini` under LangBot Models returned no available channel; NewAPI rockchin returned invalid token. These are reported by the trace; tests continued with working `claude-opus-4-8` and `deepseek-v4-flash` models. The test Agent now uses the latter. No credentials were changed.
|
||||
|
||||
## Results
|
||||
|
||||
### Browser outcomes
|
||||
|
||||
All 15 preset event types and one named custom event were executed through the UI. These exercise synthetic debug event dispatch, not delivery from real messaging platforms.
|
||||
|
||||
| Events | Observed outcome |
|
||||
| --- | --- |
|
||||
| `message.received` | Plain text marker, multi-turn tool execution and recovery succeeded. |
|
||||
| `message.edited`, `message.deleted`, `message.reaction` | Returned `EDIT_10`, `DELETE_11`, `REACTION_12`. |
|
||||
| `group.member_joined`, `group.member_left`, `group.member_banned` | Returned Unicode member data/`JOIN_42`, `LEFT_7`, `BANNED_20`. |
|
||||
| `friend.request_received`, `friend.added` | Returned `FRIEND_8`, `ADDED_19`. |
|
||||
| `bot.muted`, `bot.unmuted`, `bot.invited_to_group`, `bot.removed_from_group` | Returned `MUTED_13`, `UNMUTED_16`, `INVITED_17`, `REMOVED_18`. |
|
||||
| `feedback.received` | Returned `FEEDBACK_9` and numeric rating from JSON. |
|
||||
| `platform.specific`, `custom.e2e_probe` | Read nested JSON; returned `PLATFORM_14` and custom event type/`CUSTOM_15`. |
|
||||
|
||||
- Empty message, malformed JSON and non-object JSON did not start execution. Unicode, nested arrays/objects and an empty event object were exercised. Empty custom event name validation was not conclusively verified.
|
||||
- Real Docker `exec`, `write`, `read`, `edit`, `glob`, and `grep` all succeeded after fixes. Files were limited to `/workspace/e2e-debug-0905`. Final grep without `include` returned `probe.txt`, line 1, `E2E_EDIT_OK`, total 1.
|
||||
- Nonzero exec exit preserved exit code 7/stderr and displayed failure. `sleep 3` with `timeout_sec=1` returned `timed_out`, `ok=false`, about 1037 ms, and displayed failure with the actual timeout message.
|
||||
- Provider-returned thinking, intermediate text, tool arguments/results and final text appeared in order. A repeated prior thought appeared once after the cumulative-prefix fix. Models that do not return thinking are not expected to display it.
|
||||
- Two browser tabs ran independent debug sessions without mixed transcripts. While `exec sleep 30` showed running, switching Agents cleared the old transcript/running state. Returning and sending a new request produced `AFTER_CANCEL_OK`. This verifies UI cancellation/recovery; termination timing of the already launched container command was not independently measured.
|
||||
- Core restart and standalone Plugin Runtime reconnection recovered successfully. The development services remain running.
|
||||
- At the 1280 x 800 test viewport, the final transcript measured height 315, scroll height 5203, distance from bottom 0, and client/scroll widths both 333: automatic scrolling and horizontal containment passed. Screenshot inspected; viewport override reset afterward.
|
||||
|
||||
### Automated verification
|
||||
|
||||
- Core: 130 passed, 24 skipped across orchestrator, execution context, debug controller/service, native tools and skill tools. Skips are POSIX secure-host-filesystem cases unavailable on Windows; they still require Linux verification. Remote Box routing is covered on both capability branches.
|
||||
- LocalAgent full suite: 185 passed with `PYTHONUTF8=1`.
|
||||
- Frontend trace reducer: 5 passed; TypeScript `tsc --noEmit` passed.
|
||||
- Ruff checks on changed Python implementation/tests and Git whitespace checks passed. Existing Pydantic deprecation warnings remain.
|
||||
|
||||
The isolated unbound Agent `E2E Debug 0905` is retained for reproduction. No real platform messages were sent. This run does not claim coverage of every provider, model fallback policy, Linux host file operations, or live platform adapter ingress.
|
||||
|
||||
## Follow-up: mock platform actions
|
||||
|
||||
The original run did not verify platform action tools: the synthetic envelope had no supported platform APIs or reply target. This was a coverage gap. Debug now supplies mock adapter capabilities and a synthetic reply target while retaining tool selection, event compatibility and parameter validation. Event targets remain frozen by the Host. Only platform operations are simulated; native tools still execute normally.
|
||||
|
||||
Verified in the browser on the user's `localagent test` Agent (`0dc5d7d3-07b2-4c4c-bb2b-a4f8e40e3a76`) without changing its welcome system prompt: `group.member_joined` caused a real `event_reply` tool call with `text: Hello,Debug User`. Its mock result contained `api: send_message`, `target_type: group`, `target_id: debug-group`, `mock: true`, and `delivery: simulated`. The UI labels this as mock execution rather than text output or real delivery, and shows the tool count after completion.
|
||||
|
||||
Mock platform responses are deterministic fixtures, not evidence of real adapter support or delivery. Read operations return synthetic information; list operations return empty fixture lists. Service/platform-tool regressions: 44 passed, including mock reply, explicit-target send, identity/group lookup, request rejection, validation and assertions that the real bot manager is never accessed. TypeScript and Ruff checks passed.
|
||||
|
||||
The same browser run subsequently exposed repeated `event_reply` calls after successful mock results, and one invalid `event_get_actor` call with an unexpected `_call` parameter. The invalid call was rejected and shown as failure. The run was cancelled by switching Agents. Therefore this verifies the mock call/result path, but does not establish that this configured model completes the welcome workflow exactly once. At this checkpoint the cause had not been determined.
|
||||
|
||||
For the subsequent investigation, updated SDK-shaped Mock fixtures (including non-empty default lists), cancellation fix, complete event matrix, Linux verification and remaining provider/stream-pressure limits, see [the full follow-up report](./EVENT_DEBUG_FULL_QA_2026-09-05.md). Its results supersede the checkpoint counts and fixture description above.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Event debug full QA and release verification — 2026-09-05
|
||||
|
||||
## Scope and inventory
|
||||
|
||||
This follow-up investigates repeated welcome actions, verifies the debug surface with standalone Box/Plugin Runtime, and checks the changes before committing/pushing Core, SDK, and LocalAgent. Real messaging-platform effects must remain isolated from debug runs.
|
||||
|
||||
| Area | Required checks | Evidence |
|
||||
| --- | --- | --- |
|
||||
| Root cause | Follow-up messages/tool IDs preserved; direct model replay before/after mock guidance; original welcome prompt | Sanitized local traces, browser tool count |
|
||||
| Events | All 15 built-in event types plus custom; nested/Unicode data and identity/target mapping | Browser results and parameterized service tests |
|
||||
| Platform actions | Every catalog tool; frozen and explicit targets; permission/event/API intersection | Parameterized host boundary tests, browser reply/query/moderation/request cases |
|
||||
| Mock scenarios | Success, error, query fixture, unsupported API; malformed options rejected | Browser and service tests |
|
||||
| Input | Empty message/name, invalid/non-object JSON, unsupported event, actor/subject validation | Browser and request tests |
|
||||
| Trace | Thinking/text/tool ordering, final snapshot deduplication, status/error correctness, no-call summary | Browser and reducer tests |
|
||||
| Lifecycle | Cancel retains partial record; rerun; tabs/Agent switching; provider failure/fallback; reconnect | Browser and transport/runner tests |
|
||||
| Native tools | Exec/file operations, nonzero exit, timeout and path escape | Browser and native-tool tests |
|
||||
| Layout | Long output/parameters, automatic bottom scroll, narrow view, KB card spacing | DOM geometry and screenshots |
|
||||
| Release | Python/SDK suites, frontend build/lint, whitespace checks, change review, commit and push | Commands, counts and remote SHAs |
|
||||
|
||||
## Investigation
|
||||
|
||||
The real follow-up request contained the assistant tool call and successful tool result with matching IDs. No loss occurred at the LocalAgent → SDK → Host model boundary. Direct replay to the configured provider, bypassing the runner, reproduced `event_reply` after success. Adding explicit mock completion semantics stopped further calls in two direct replays. The previous LocalAgent system context omitted the debug/mock semantics although the tool result said no real platform operation occurred.
|
||||
|
||||
The configured upstream `claude-opus-4-8` also returned an unsolicited CLI identity statement in direct replay. This originates in the provider response, not the debug renderer. It is separate from preserving tool results; it must not be presented as normal LangBot-generated status.
|
||||
|
||||
## Final results
|
||||
|
||||
### Browser event matrix
|
||||
|
||||
Executed through the real Edge WebUI, against Core `:5300`, Vite `:3000`, standalone Plugin Runtime `:5400/:5401`, and Docker-backed standalone Box `:5410`. The unbound `E2E Debug 0905` Agent uses `deepseek-v4-flash`; the user's original Agent and welcome prompt remain unchanged and use `claude-opus-4-8`.
|
||||
|
||||
| Event | Observed result in this pass |
|
||||
| --- | --- |
|
||||
| `bot.invited_to_group` | Actor/group queries and `event_respond_group_invite(approve=true)` succeeded; request ID frozen to `debug-group-request`. |
|
||||
| `bot.muted`, `bot.removed_from_group` | One `event_get_group` call each, correct group target. |
|
||||
| `bot.unmuted` | One actor query; correct user target. |
|
||||
| `feedback.received` | One mock reply, `FEEDBACK_MOCK_OK`, person target. |
|
||||
| `friend.added` | One mock reply, `FRIEND_ADDED_OK`, person target. |
|
||||
| `friend.request_received` | Acceptance and separate explicit rejection exercised; rejection preserved `approve=false`, Unicode remark and frozen request ID. |
|
||||
| `group.member_banned` | One member lookup, SDK-compatible nested `user` and `group_id`. |
|
||||
| `group.member_left` | One group lookup, SDK-compatible `id`/`name`. |
|
||||
| `group.member_joined` | Welcome reply, configured failure, query fixture, unsupported API, and mute/unmute/kick scenarios. Original-model repeat caveat below. |
|
||||
| `message.deleted` | One actor lookup, SDK-compatible `id`/`nickname`. |
|
||||
| `message.edited` | One simulated deletion; frozen group, chat and message IDs. |
|
||||
| `message.reaction` | One reply, `REACTION_OK`, and correct `👍` event data. |
|
||||
| `message.received` | Real six-tool file/exec chain, plain-text recovery, cancellation, draft retention, nonzero exit and timeout. |
|
||||
| `platform.specific` | Preserved nested arrays, booleans, null and `测试🙂`; no tools called. |
|
||||
| `custom.event`, `custom.e2e` | Empty object and named custom event with `CUSTOM_中文🙂`; no tools called. |
|
||||
|
||||
The first invitation/default friend-request runs submitted the preset text; they are recorded as default-behavior cases, not as explicit one-call/rejection tests. The rejection was separately rerun with the actual submitted instruction verified. Browser automation reads the controlled input after filling before treating a scenario as submitted.
|
||||
|
||||
### Mock and execution behavior
|
||||
|
||||
- Platform Mock runs the actual model/tool selection, authorization, parameter validation and frozen-target resolution; only the adapter boundary is simulated. Tests assert that mock execution never accesses the real bot manager. Native tools retain real Box behavior.
|
||||
- The platform catalog contains 24 tools. Parameterized tests exercise success and configured failure for all 24, plus permission/event/API filtering, explicit/frozen targets, SDK fixture shapes and invalid options. This is full catalog contract coverage, not 24 separate browser clicks or proof of live adapter support.
|
||||
- `errors.event_reply = "E2E permission denied"` produced one failed call and the **模拟执行失败 · Mock** status. The model reported the actual error without retrying in this scenario.
|
||||
- `results.event_get_actor` returned the configured `fixture-user-77` / `测试用户🙂`; the model used these values instead of the original event identity. Default query results now serialize SDK platform models; default lists contain one synthetic entry and can be overridden with `[]`.
|
||||
- Disabling `send_message` removed `event_reply` from the model's available tools. It returned `REPLY_UNAVAILABLE`, with the explicit no-tool-call summary. Clearing Mock options restored action availability; the three moderation calls succeeded with the correct group/member IDs.
|
||||
- Mock `[]`, unknown tool names, whitespace-only message/custom name, malformed event JSON and event JSON `[]` were rejected. Browser transcript counts did not increase for client-side validation failures. Backend tests additionally cover falsey/non-object actor, subject and data values, conflicting outcomes and invalid API names.
|
||||
- Thinking returned by the provider, intermediate text, tool parameters, results and final text remain distinct and ordered. Missing thinking is not fabricated. Cumulative final snapshots and prior tool-turn prefixes are deduplicated without hiding actual repeated tool calls.
|
||||
|
||||
### Lifecycle, native tools and layout
|
||||
|
||||
- A browser cancellation regression exposed a stuck **停止调试** button: the `finally` block skipped resetting state for aborted requests. It now clears the matching controller and resets running state even after user cancellation. Retest retained partial thinking/tool arguments, marked the unfinished call **未返回结果**, showed the cancellation notice and restored **运行测试**.
|
||||
- Two subsequent recovery requests encountered the bounded-stream error below. A later request in the same tab/session returned `42` successfully; further real tool runs also completed. Cancellation and recovery are therefore verified, but immediate model success after cancellation is not guaranteed.
|
||||
- Editing `NEXT_DRAFT_FINAL_0905` while a `sleep 5` run was still awaiting its final response preserved the draft after completion.
|
||||
- Real `write → read → edit → glob → grep → exec` succeeded. `final.txt` changed from `FINAL_WRITE` to `FINAL_EDIT`; grep without `include` found one match; exec returned `FINAL_EXEC` with exit code 0. Writes stayed under `/workspace/e2e-debug-0905`.
|
||||
- `printf E2E_ERROR >&2; exit 7` preserved stderr and exit code 7 and displayed failure. `sleep 3` with `timeout_sec=1` returned `timed_out` in 1025 ms and displayed failure. Neither was retried.
|
||||
- Separate tabs retained separate transcripts during concurrent runs. Agent switching reset the debug surface. File workspace sharing remains intentional; transcript isolation does not mean separate Box filesystems.
|
||||
- The final transcript measured 674 px high with 16,630 px scroll height, distance from bottom 0, and equal client/scroll widths of 468 px. Visual inspection confirmed contained parameters/results and accessible input controls. The earlier pass also verified 1280 × 800; the final attempted viewport override did not change this tab, so that attempt is not counted as an additional narrow-screen result. Temporary overrides were reset.
|
||||
- Knowledge-base card geometry: retrieval card bottom 1413 px, danger card top 1437 px, giving the expected **24 px** gap. Both adjacent card gaps measured 24 px.
|
||||
- Core health check returned `ok`; Plugin Runtime was connected and all development service ports remained listening. Previous restart/reconnect evidence is in the earlier report.
|
||||
|
||||
### Automated checks
|
||||
|
||||
| Suite | Result |
|
||||
| --- | --- |
|
||||
| Core agent unit directory plus debug service/controller, native/skill tools and model conversion | 659 passed, 24 Windows-only skips |
|
||||
| Final changed platform/debug service/controller checks | 133 passed |
|
||||
| Linux Docker: native tools, skill tools and all platform tools | 150 passed, 0 skipped; includes the 24 POSIX cases skipped on Windows |
|
||||
| SDK API suite plus runtime I/O handler | 411 passed |
|
||||
| LocalAgent full suite (`PYTHONUTF8=1`) | 187 passed |
|
||||
| Frontend unit suite | 69 passed |
|
||||
| Skills CLI suite | 122 passed |
|
||||
| Frontend TypeScript/Vite build and changed-file ESLint | Passed; existing large-bundle advisory remains |
|
||||
| Changed Python Ruff; Git whitespace checks | Passed |
|
||||
| Skills index generation, validate and index consistency | Passed |
|
||||
|
||||
Counts overlap where a final focused/Linux run repeats an earlier suite; do not add them as unique tests. Existing Pydantic deprecation warnings remain. This is the relevant subsystem regression set, not the entire repository/integration matrix. Provider fallback has deterministic LocalAgent tests; live fallback after a partially emitted response is deliberately unsupported and was not claimed as a successful browser fallback.
|
||||
|
||||
The full regression also found Windows portability problems in the skills tooling: LF-only frontmatter parsing, `/cases/` detection and native path separators in generated references. These are fixed and covered by the CLI suite. Two frontend source-contract expectations were stale after the existing three-tab workbench/layout changes; they were updated to the actual product structure.
|
||||
|
||||
### Remaining limits and reproduction
|
||||
|
||||
1. **Original provider is still nondeterministic.** With the latest LocalAgent guidance, one rerun made exactly one successful `event_reply`; another made repeated successful replies before stopping. The trace faithfully records each call. Earlier invalid `_call` arguments were rejected. Direct provider replay proved that repeated calls can originate upstream even with a valid matching tool result. Prompt guidance reduces ambiguity but does not establish exactly-once actions. No silent deduplication or fabricated success was added.
|
||||
2. **Bounded stream pressure can fail long responses.** Two recovery runs emitted thinking and then `Streaming action consumer is too slow; response buffer full`; later recovery and six-tool runs succeeded. The error originates in the SDK's existing 128-frame response queue and is preserved in the UI. It is not a missing-tool-result or 30-second HTTP timeout. Full end-to-end flow control under sustained overload remains open; queue limits were not removed to conceal the failure.
|
||||
3. Mock results verify debug behavior, not real platform credentials, permissions, delivery, or every adapter/event Cartesian combination. Already launched external tool termination timing was not independently measured. A development-branch push does not mean these remaining production release gates passed.
|
||||
|
||||
For reproduction, use the retained test Agent, the Mock examples in [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md), and the original welcome Agent. Local diagnostic traces and fixture artifacts are excluded from Git; they contain model responses and local runtime state. This report contains outcomes rather than raw provider reasoning or secrets.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Runner 与产品扩展边界
|
||||
|
||||
更新:2026-09-05,适用于 `dev/4.11.x`。EBA、独立 Agent、Bot 事件绑定和处理器 UI 已与 Runner 插件化合并。当前状态和测试证据以 [STATUS.md](./STATUS.md) 为准;runner 可见 schema 与调度基数以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) 为准。
|
||||
|
||||
## 当前职责
|
||||
|
||||
| 层 | 已实现职责 | 边界 |
|
||||
| --- | --- | --- |
|
||||
| LangBot 产品层 | 独立 Agent CRUD、Pipeline、处理器工作台、Bot 事件绑定、Runner 安装与调试 | Agent 与 Pipeline 各自持久化;聚合列表不转换实体 |
|
||||
| 平台层 | 适配器事件转换、能力声明、observer 广播、路由匹配、平台 API 与回复 | 路由逻辑在 RuntimeBot;不为每个入口重建 runner 协议 |
|
||||
| Host Agent 底座 | envelope/binding 投影、统一编排、资源授权、run session、EventLog/Transcript/State、run/result ledger | SDK 不持有 Host 私有 Query 或数据库 |
|
||||
| SDK / Plugin Runtime | typed contract、Runner 组件和脚手架、proxy、MCP bridge、结果流转发、installation worker 管理 | 具体 Agent 执行策略由 Runner 插件承担 |
|
||||
| Box Runtime | 沙盒会话、文件、托管进程、Skill、资源限制与作用域 | 不等于外部 harness 的通用托管承诺;存储统计不等于硬配额 |
|
||||
|
||||
## 已有能力与后续扩展
|
||||
|
||||
| 能力 | 当前状态 | 后续工作与接入点 |
|
||||
| --- | --- | --- |
|
||||
| Agent / binding | Agent 表、API、配置 UI、Bot event_bindings 已存在;AgentBinding 是运行投影 | 新产品模型复用现有投影,不把 Pipeline 持久化成 Agent |
|
||||
| 事件路由 | observer 广播后按 pattern/filter/priority 选择一个 Pipeline、Agent 或 discard | 通用订阅、通知和其他事件源仍需单独设计 |
|
||||
| 平台动作 | event_* 冻结目标,platform_* 显式授权;通过 Host 工具调用 | 新动作先定义语义、schema 和授权,不开放任意原始 action |
|
||||
| 结构化交互 | interaction.requested 白名单、持久回调关联、TTL/作用域/幂等、原处理器恢复 | 补真实 provider/platform 验收;其它 action.requested 仍仅 telemetry |
|
||||
| Run / runtime | 持久 AgentRun/AgentRunEvent、取消/结果/终态、heartbeat/claim/reconcile 原语 | 业务队列、任务生产、唤醒、跨 Host 执行和运维产品面 |
|
||||
| Plugin worker | 独立安装进程、依赖环境、supervisor、退避及重启协调器 | 最终部署故障注入、出站网络策略及硬存储配额 |
|
||||
| External harness | 通过 Runner 消费协议、按 run 访问 Host 资源 | 通用 daemon supervisor、登录态诊断、分布式调度;不要与 Plugin worker 混淆 |
|
||||
| History / state / storage | Host 事实源、按需读取、state/checkpoint、sandbox 文件能力 | EventLog/Transcript 的定时 retention 接入和完整文件生命周期 |
|
||||
| Scheduler / Automation | 仅保留可扩展的事件入口 | 用户定时任务必须走事件、授权和运行记录链路,不直调插件绕过 Host |
|
||||
| Workflow / 多 Agent | 尚无完整产品实现 | 先定义串并联、失败恢复、投递与状态冲突语义 |
|
||||
| Solution | 尚无导出/导入实现 | 处理器、路由模板、依赖、变量与文档;不导出凭据或已安装 UUID |
|
||||
| 长期 memory 产品 | 提供 history/state/storage 基础 | 由 Runner 或后续产品定义召回策略,不把全量 memory 默认塞入 context |
|
||||
| Cloud | 作用域与运行时隔离底座已合入;OSS 为单 Workspace 多成员 | 生产激活独立通过网络、硬配额、事务代次切换和部署验收 |
|
||||
|
||||
平台动作详见 [PLATFORM_ACTION_TOOLS.md](./PLATFORM_ACTION_TOOLS.md),控制面规划详见 [RUNTIME_CONTROL_PLANE_V2.md](./RUNTIME_CONTROL_PLANE_V2.md),Cloud 门禁见 [剩余验证清单](../multi-tenant/cloud-v2-pending-verification.md)。
|
||||
|
||||
## 扩展规则
|
||||
|
||||
- 新入口构造 Host event 和有效 binding,继续调用统一 orchestrator;Pipeline AI Stage 使用 QueryEntryAdapter。
|
||||
- Host 保持 run/result、授权、事件、状态和历史的事实源;插件负责自己的执行策略及 provider 私有 continuation。
|
||||
- 新增业务表、调度或 UI 不要求修改 runner 可见协议;需要协议扩展时先更新 canonical spec,再同步 SDK、Runtime、模板与测试。
|
||||
- fan-out、并行仲裁和自动重试必须明确副作用、幂等、状态冲突和审计语义,不能通过多个隐式回复者实现。
|
||||
- 外部 harness 自带 shell、文件系统和网络权限由部署环境负责;manifest permissions 约束的是 LangBot 持有的资源。
|
||||
@@ -0,0 +1,266 @@
|
||||
# LangBot Host 与 SDK 基础设施设计
|
||||
|
||||
本文档描述 LangBot 作为 agent host 的内部能力与分层架构,以及 Host 内部模型。
|
||||
|
||||
- SDK ↔ Host 的协议数据结构(`RunnerContext`、`RunnerManifest`、`RunnerResult`、`RunnerAPIProxy` 等)的**唯一定义在** [PROTOCOL_V1.md](./PROTOCOL_V1.md);本文只引用,不重抄。
|
||||
- 测试执行入口和 smoke 记录见 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md);安全发布门槛见 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md)。
|
||||
- 本文定义的 Host 内部模型(`AgentEventEnvelope`、`AgentBinding`、`RunnerDescriptor`)不属于 SDK 协议字段。
|
||||
|
||||
## 1. 目标
|
||||
|
||||
LangBot 要转为 agent host,而不是内置 runner 容器:
|
||||
|
||||
- 接收 IM、WebUI、API 和当前 RuntimeBot 事件路由产生的事件。
|
||||
- 接收 EBA 选中的 Agent 处理器,并根据事件、bot、workspace、scope 解析 Runner binding。
|
||||
- 发现、校验和调用插件提供的 Runner。
|
||||
- 为每次 run 提供受限资源、状态、存储、上下文引用和生命周期控制。
|
||||
- 接收 Runner 返回的事件流,投递到 IM、WebUI 或其他 output surface。
|
||||
|
||||
## 2. 非目标
|
||||
|
||||
- 不定义 Pipeline 的 Stage 编排语义;Pipeline 是 EBA 的同级处理器,其 AI Stage 只在需要 runner 时接入本 Host 边界。
|
||||
- 不要求所有 Runner 依赖 LangBot 的上下文管理。
|
||||
- 不要求官方 local-agent 的旧行为反向塑造 host 协议。
|
||||
- 不在 host 中实现通用 agentic prompt assembler。
|
||||
- 不强制 runner 使用 LangBot state / storage;只提供可选、受控的寄宿能力。
|
||||
- 不在 runner 底座重复实现平台路由;当前分支的 RuntimeBot 已承担 observer 广播和 Bot event_bindings 匹配,runner 层消费其 envelope/binding 投影。
|
||||
|
||||
## 3. 分层架构
|
||||
|
||||
```text
|
||||
IM platform event
|
||||
|
|
||||
v
|
||||
RuntimeBot event normalization / observer broadcast
|
||||
|
|
||||
v
|
||||
RuntimeBot event_bindings -> one Processor target
|
||||
|-- target_type=pipeline -> Pipeline Stage chain
|
||||
|
|
||||
`-- target_type=agent -> AgentBindingResolver
|
||||
|
|
||||
v
|
||||
AgentRunOrchestrator
|
||||
|-- RunnerRegistry
|
||||
|-- AgentResourceBuilder
|
||||
|-- AgentContextBuilder
|
||||
|-- AgentRunSessionRegistry
|
||||
|-- PersistentStateStore / EventLogStore / TranscriptStore
|
||||
|-- Sandbox / workspace file tools
|
||||
v
|
||||
Plugin Runtime / Runner
|
||||
|
|
||||
v
|
||||
RunnerResult stream
|
||||
|
|
||||
v
|
||||
Delivery / Renderer / Platform API
|
||||
```
|
||||
|
||||
Pipeline 与 Agent 是事件路由的平级目标。Agent 直接解析 `AgentBinding`;Pipeline 执行完整 Stage 链,仅在 AI Stage 调用 runner 时通过 QueryEntryAdapter 构造一次性运行投影,不会持久化成 Agent。WebUI/API 调试按服务入口构造调试事件或 Query,再使用同一编排器;不要求绕行真实 Bot 回调。调度基数、Agent 复用和 fan-out 边界以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) §13 为准。
|
||||
|
||||
## 4. LangBot 侧能力
|
||||
|
||||
### 4.1 事件入口与路由(已集成)
|
||||
|
||||
> 2026-09-05:平台转换、Bot event_bindings、独立 Agent 和配置 UI 已集成。源码入口为 `pkg/platform/botmgr.py`、`pkg/api/http/service/agent.py` 和 `pkg/agent/runner/`。通用订阅与定时自动化仍是后续能力。
|
||||
|
||||
各入口将消息或非消息输入投影为 `AgentEventEnvelope`。以下为概念字段摘要,精确字段及默认值以 `pkg/agent/runner/host_models.py` 为准:
|
||||
|
||||
```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 或 staged file reference,不扩散到 runner 协议顶层。
|
||||
|
||||
**当前 adapter source**:`QueryEntryAdapter.query_to_event(query)` 从 Query 生成 `AgentEventEnvelope`。
|
||||
|
||||
### 4.2 AgentConfig 与 AgentBinding
|
||||
|
||||
`AgentConfig` 是 Host 内部的一次 Runner 调用配置投影(不暴露给 SDK)。独立 Agent 从自己的持久配置生成它;Pipeline 只在 AI Stage 调用 runner 时,由 Query entry adapter 从该 Stage 的当前配置生成它。两种来源随后都由 BindingResolver 结合事件和 scope 解析为 `AgentBinding`。Pipeline 本身不是 `AgentConfig`,该调用投影也不会创建或更新持久 Agent。
|
||||
|
||||
```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` 是"什么事件调用哪个 Runner、带什么 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)`
|
||||
只在 Pipeline AI Stage 调用 runner 时,把 current config 临时投影为运行期 `AgentConfig`,再由
|
||||
`AgentBindingResolver.resolve_one(event, [agent_config])` 解析出唯一
|
||||
`AgentBinding`。该调用从 Pipeline 取得 AI runner config
|
||||
→ runner_config、extension preference → resource_policy、output settings →
|
||||
delivery_policy,但 Pipeline 仍执行并拥有完整 Stage/config 语义。该适配不会把 Pipeline 持久化为 Agent;独立 Agent 由用户自行新增和绑定。
|
||||
|
||||
### 4.3 RunnerRegistry
|
||||
|
||||
Registry 收集 runner descriptor(来自插件 runtime、开发期本地插件):
|
||||
|
||||
```python
|
||||
class RunnerDescriptor(BaseModel):
|
||||
id: str
|
||||
source: Literal["plugin"]
|
||||
label: I18nObject
|
||||
description: I18nObject | None = None
|
||||
plugin_author: str
|
||||
plugin_name: str
|
||||
runner_name: str
|
||||
capabilities: RunnerCapabilities # 见 PROTOCOL_V1 §4.3
|
||||
permissions: RunnerPermissions # 见 PROTOCOL_V1 §4.4
|
||||
config_schema: list[DynamicFormItemSchema]
|
||||
plugin_version: str | None = None
|
||||
raw_manifest: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
职责:调用 `plugin_connector.list_runners()` 拉取 runner、校验 typed `RunnerManifest`、输出 descriptor、缓存 discovery 结果并提供 `refresh()`。单个插件 manifest 失败只记 warning,不影响其它 runner。`plugin:author/name/runner` 是稳定 id 格式;插件实例边界见 PROTOCOL_V1 §13。
|
||||
|
||||
Host 内置 runner / adapter 不能作为 `RunnerDescriptor.source` 绕过插件
|
||||
runtime、`run_id`、`ctx.resources` 和 `RunnerAPIProxy` 权限链。若需要
|
||||
开发期调试 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)
|
||||
-> RunnerRegistry.resolve(runner_id)
|
||||
-> AgentResourceBuilder.freeze_snapshot(binding, event)
|
||||
-> AgentRunSessionRegistry.register(run_id, runner_id, snapshot)
|
||||
-> AgentContextBuilder.build(event, binding, snapshot)
|
||||
-> PluginRuntimeConnector.run_runner(ctx)
|
||||
-> RunnerAPIProxy action
|
||||
-> validate active run session + caller identity + snapshot
|
||||
-> Host API / Store
|
||||
<- RunnerResult stream
|
||||
-> apply state.updated to PersistentStateStore
|
||||
-> write message.completed to Transcript
|
||||
-> keep current-run files and large tool outputs in sandbox/workspace
|
||||
-> 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),来自 manifest permissions 与 binding policy 的交集:
|
||||
|
||||
1. `descriptor.permissions` 声明 runner 需要的 LangBot 资源访问上限。
|
||||
2. binding / resource policy 允许的资源范围。
|
||||
3. Agent/runner config 中选择的模型、知识库、文件等资源。
|
||||
4. 当前 event / actor / bot / workspace 的实际权限。
|
||||
5. `ctx.context.available_apis` 暴露的 pull API 能力。
|
||||
|
||||
这次裁剪结果必须冻结为 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 才是安全边界。`spec.capabilities` 只帮助 Host 判断 runner 是否需要 tool / knowledge 等资源投影,不能替代 permissions 或 binding policy。skill 不由独立 capability 决定是否投影——它通过统一 tool 授权(`resource_policy.allowed_tool_names`)消费,`skill_authoring` 仅作为「一键授权这组 skill tool + sandbox」的便捷开关。
|
||||
|
||||
资源裁剪应通用,不写死 local-agent。selector 与资源的映射示例:`model-fallback-selector` → primary/fallback LLM、`llm-model-selector` → LLM、`rerank-model-selector` → rerank 模型、`knowledge-base-multi-selector` → 知识库;新增 selector 时在 resource builder 中统一扩展。
|
||||
|
||||
构造 `ctx.resources.tools` 时,Host 尽可能一次提供完整 schema(`ToolResource.parameters`),减少逐个 detail 查询。Runner 仍需兼容 `parameters=None` 并按需调用 `get_tool_detail`。平台动作以 `tool_type=platform` 使用同一资源面,授权与冻结目标见 [PLATFORM_ACTION_TOOLS.md](./PLATFORM_ACTION_TOOLS.md)。
|
||||
|
||||
执行/文件/skill/MCP 等能力的接入方向:先由 Host / sandbox 封装成普通 scoped tool,再通过 `ctx.resources.tools` 和 SDK runtime 转发进入 runner;runner 不应识别或硬编码执行环境 provider。外部 harness 的 native tools 不能直接访问 LangBot 资源。skill 的整个生命周期都走统一 tool:发现走 `list_skills` / `langbot_list_assets`,激活/注册走 `activate` / `register_skill`,包内操作走 native exec/read/write——runner 不需要独立的 skill 渲染或门控。
|
||||
|
||||
### 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 只能作为过渡实现,不能作为正式生产语义。
|
||||
|
||||
部分 host-owned state 由 Host 自身直接写:例如 `activate` tool 在 Host 侧执行时,把已激活 skill 写入 conversation scope 的 `host.activated_skills`。host 直接写与 runner `state.updated` 写到同一 key 时按 **last-write-wins** 合并,runner 可覆盖。
|
||||
|
||||
### 4.7 EventLog / Transcript / Sandbox Files(事实源)
|
||||
|
||||
- `EventLog`: durable append-only,保存原始事件、系统事件、工具调用、投递结果、错误。
|
||||
- `Transcript`: 从 EventLog 投影出的对话视图,用于 UI、审计和按需历史读取。
|
||||
- `Sandbox / workspace files`: 当前 run 的上传文件、平台附件、工具大结果和临时产物。Host 负责 staging 与授权边界,runner 通过 read/write/exec 类工具按需访问。
|
||||
|
||||
三类数据与 working context 的边界、读取约束见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。Runner 可读取这些能力,但不被迫使用 LangBot 作为唯一记忆系统。
|
||||
|
||||
### 4.8 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、sandbox/workspace 文件边界和审计;Host 或 binding policy 决定哪些 MCP bridge、skill-backed tool、sandbox path、history/state 句柄可投影给 runner;runner plugin 把 scoped projection 转成目标 harness 可消费形式;所有 LangBot 资源访问必须经 SDK runtime / `RunnerAPIProxy` / SDK-owned MCP bridge 转发并接受 Host 校验;外部 harness 负责自己的 native session、tool loop、压缩、权限模式和 resume,但不能用 native tools 绕过 Host 授权。
|
||||
|
||||
投影的具体形态(context 文件、resource handles、LangBot MCP gateway、state pointers)见 AGENT_CONTEXT_PROTOCOL §4.5;当前 code-agent harness runner 形态见 OFFICIAL_RUNNER_PLUGINS §7。发布级隔离要求见 SECURITY_HARDENING。
|
||||
|
||||
## 5. SDK 侧协议
|
||||
|
||||
SDK 组件入口如下;所有数据结构定义见 PROTOCOL_V1。
|
||||
|
||||
```python
|
||||
class Runner(BaseComponent):
|
||||
__kind__ = "Runner"
|
||||
|
||||
@classmethod
|
||||
def get_config_schema(cls) -> list[dict]: ...
|
||||
|
||||
async def run(self, ctx: RunnerContext) -> AsyncGenerator[RunnerResult, None]: ...
|
||||
# ctx: PROTOCOL_V1 §5.2 ; RunnerResult: PROTOCOL_V1 §7
|
||||
```
|
||||
|
||||
- Manifest / capabilities / effective access:PROTOCOL_V1 §4。Capabilities 来自组件 manifest 的 `spec.capabilities`,不是 SDK 基类 classmethod。
|
||||
- `RunnerContext`:PROTOCOL_V1 §5.2。`messages` / `bootstrap` 不是协议字段。
|
||||
- `RunnerResult`:PROTOCOL_V1 §7。
|
||||
- `RunnerAPIProxy`:PROTOCOL_V1 §8,是 runner 访问 host 能力的唯一入口,所有请求带 `run_id`。
|
||||
@@ -0,0 +1,138 @@
|
||||
# 官方 Runner 插件迁移计划
|
||||
|
||||
本文档描述内置 `RequestRunner` 迁出 LangBot 后,官方 runner 插件如何组织、迁移和验收。它是 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md) 和 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) 的下游落地计划,不是 LangBot 宿主协议的设计前提。QA 入口和 smoke 记录见 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md)。
|
||||
|
||||
官方 `local-agent` 可以外移,也可以重写。设计重点不是保留旧内置 runner 的内部结构,而是验证一个依附 LangBot host 基础设施的官方 agent 能否完整工作。同时,LangBot host 协议必须服务 Claude Code SDK、Codex、Pi Agent SDK、外部 Agent 平台等自管 context/runtime 的 runner,不能被官方插件的实现细节绑死。
|
||||
|
||||
## 1. 仓库组织
|
||||
|
||||
官方 runner 插件与 LangBot 主仓库、SDK 仓库以不同节奏迭代:LangBot 主仓库只维护宿主协议和调度,SDK 仓库维护 Runner 组件和 runtime 协议,官方 runner 插件承载业务 runner 的具体实现和第三方平台适配。
|
||||
|
||||
当前推荐"官方插件可独立发布,必要时共享 SDK helper"。开发期采用本地多目录布局:
|
||||
|
||||
```text
|
||||
langbot-app/
|
||||
langbot-local-agent/ # plugin:langbot-team/LocalAgent/default
|
||||
manifest.yaml
|
||||
components/runner/default.{yaml,py}
|
||||
langbot-agent-runner/ # 外部服务 runner 仓库
|
||||
acp-agent-runner/ claude-code-agent/ codex-agent/ dify-agent/ n8n-agent/ ...
|
||||
```
|
||||
|
||||
后续可聚合进 monorepo,也可继续独立发布——这个选择不影响协议设计。重复逻辑优先沉淀到 SDK 或明确的共享 helper 包,不要把宿主私有结构泄漏给插件。旧 `src/langbot/pkg/provider/runners/*` 只作为历史行为对齐基准;当前未发布分支不提供旧内置 runner 的运行时 fallback。
|
||||
|
||||
## 2. 插件命名和 runner id
|
||||
|
||||
| 旧 runner | 官方插件 | runner id |
|
||||
| --- | --- | --- |
|
||||
| `local-agent` | `langbot-team/LocalAgent` | `plugin:langbot-team/LocalAgent/default` |
|
||||
| `dify-service-api` | `langbot-team/DifyAgent` | `plugin:langbot-team/DifyAgent/default` |
|
||||
| `n8n-service-api` | `langbot-team/N8nAgent` | `plugin:langbot-team/N8nAgent/default` |
|
||||
| `coze-api` | `langbot-team/CozeAgent` | `plugin:langbot-team/CozeAgent/default` |
|
||||
| - | `langbot-team/ACPRunner` | `plugin:langbot-team/ACPRunner/default` |
|
||||
| - | `langbot-team/ClaudeCodeAgent` | `plugin:langbot-team/ClaudeCodeAgent/default` |
|
||||
| - | `langbot-team/CodexAgent` | `plugin:langbot-team/CodexAgent/default` |
|
||||
| `dashscope-app-api` | `langbot-team/DashScopeAgent` | `plugin:langbot-team/DashScopeAgent/default` |
|
||||
| `deerflow-api` | `langbot-team/DeerFlowAgent` | `plugin:langbot-team/DeerFlowAgent/default` |
|
||||
| `langflow-api` | `langbot-team/LangflowAgent` | `plugin:langbot-team/LangflowAgent/default` |
|
||||
| `tbox-app-api` | `langbot-team/TboxAgent` | `plugin:langbot-team/TboxAgent/default` |
|
||||
| `weknora-api` | `langbot-team/WeKnoraAgent` | `plugin:langbot-team/WeKnoraAgent/default` |
|
||||
|
||||
每个插件可后续提供多个 runner,但迁移目标的默认 runner 统一叫 `default`。
|
||||
|
||||
## 3. 迁移批次
|
||||
|
||||
- **Batch 1(打通协议)**:`local-agent`(能力最完整基准)、`acp-agent-runner` / `claude-code-agent` / `codex-agent`(外部 code-agent harness 路径)、`dify-agent`(传统 service API runner)。
|
||||
- **Batch 2(外部 workflow)**:`n8n-agent`、`langflow-agent`(webhook/workflow 输入输出、timeout、外部 conversation id)。
|
||||
- **Batch 3(平台 Agent API)**:`coze-agent`、`dashscope-agent`、`tbox-agent`、`deerflow-agent`、`weknora-agent`(平台特有响应格式、引用资料、文件/图片输入、外部 thread/session 状态)。
|
||||
|
||||
## 4. 每个官方插件的组件要求
|
||||
|
||||
每个插件至少包含一个 `Runner` 组件,manifest 示例:
|
||||
|
||||
```yaml
|
||||
apiVersion: langbot/v1
|
||||
kind: Runner
|
||||
metadata:
|
||||
name: default
|
||||
label: { en_US: Dify Agent, zh_Hans: Dify Agent }
|
||||
description:
|
||||
en_US: Run a Dify application as a LangBot Runner.
|
||||
zh_Hans: 将 Dify 应用作为 LangBot Runner 运行。
|
||||
spec:
|
||||
config: []
|
||||
capabilities: # 字段语义见 PROTOCOL_V1 §4.3
|
||||
streaming: true
|
||||
execution:
|
||||
python: { path: ./main.py, attr: DefaultRunner }
|
||||
```
|
||||
|
||||
## 5. local-agent 插件方向
|
||||
|
||||
`local-agent` 是官方插件中能力最完整的消费者,但不是宿主协议的设计中心。它需要证明:一个主要依附 LangBot host 能力的 agent runner 可以通过公开协议完成模型、工具、知识库、状态、history、sandbox 文件访问、上下文压缩和消息投递。
|
||||
|
||||
迁移或重写需覆盖旧内置 runner 的用户可见能力:model primary/fallback 选择、prompt、knowledge-bases、rerank-model、rerank-top-k、function calling、streaming、multimodal input、conversation history、monitoring metadata。
|
||||
|
||||
责任边界与 Host API 消费方式见 AGENT_CONTEXT_PROTOCOL §8。关键约束:
|
||||
|
||||
- 从 `ctx.config` 读取静态绑定 `prompt`,**不**读取 `ctx.adapter.extra["prompt"]`;不消费 Query entry adapter 生成的历史窗口。
|
||||
- 通过 `RunnerAPIProxy.history` 拉取 transcript,而不是依赖 host 每轮强塞历史窗口。
|
||||
- `ctx.input.contents` 保留图片/文件等多模态内容;RAG 只替换/插入文本部分,不丢图片/文件。
|
||||
- 不能绕过 `ctx.resources` 调用未授权模型、工具或知识库。
|
||||
- manifest 声明功能能力、LangBot 资源 permissions 和配置表单;实际授权来自 manifest permissions 与 binding resource policy、runner config、`ctx.context.available_apis` 和 Host run session snapshot 的交集。
|
||||
|
||||
### 5.1 Native Execution / Skills 后续接入
|
||||
|
||||
本阶段不把 sandbox/skills 做成 Runner 协议字段。后续 sandbox/skills 分支合并后,命令执行、文件操作、skill、MCP managed process 应先由 Host / sandbox 封装成 scoped tools,再通过 `ctx.resources.tools` 和 SDK runtime 转发暴露给 runner。这让 local-agent 只消费授权后的 Host 基础设施,而不是直接持有宿主机执行能力。
|
||||
|
||||
## 6. 外部 runner 插件要求
|
||||
|
||||
外部平台 runner 迁移遵循:旧配置字段尽量保持同名便于 migration 复制;输出统一转换为 `RunnerResult`;外部 API timeout 从 runner config 读取;平台 conversation id 存 plugin storage 或 context runtime state,不依赖 LangBot 内置 conversation uuid 私有结构;流式按平台能力声明,没有流式就只发 `message.completed`。
|
||||
|
||||
### 6.1 Code-agent harness runner
|
||||
|
||||
Claude Code、Codex、Kimi Code 这类 runner 不一定通过 LangBot 的模型/工具 loop 执行,可以依赖自己的 harness,但仍必须遵守统一 Host 边界。总体边界见 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md) §4.8;context projection 形态见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) §4.5;发布级要求见 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md)。
|
||||
|
||||
本文件只补充官方 runner 的实现要求:输入来自 `ctx.event` / `ctx.input`,不依赖 Pipeline 私有 `Query`;外部 session id / workspace / checkpoint 写入 Host state 或 plugin storage;插件实例边界见 PROTOCOL_V1 §13;CLI / subprocess runner 必须处理 timeout、取消、空输出、非零退出和 stderr 映射。
|
||||
|
||||
实现结构应把 provider-native output 解析与 LangBot result stream 组装分开:Claude stream-json、Codex JSONL、Kimi / OpenCode 事件等只在 runner adapter 内解析,输出统一归一为 `RunnerResult`(`message.completed` / `message.delta`、`state.updated`、`run.completed` / `run.failed`)。文件和工具大结果留在当前 run 的 sandbox/workspace,通过消息 metadata、attachment ref 或 path 指向。未知 native event 不应导致 run 崩溃;应记录诊断 metadata 或 warning。新增 harness 时优先补 native fixture -> `RunnerResult` 的转换测试,再接 WebUI smoke。
|
||||
|
||||
并发约束应按外部 session 粒度表达,而不是按 Agent / runner id / 插件实例表达;Agent 复用和全局锁边界见 PROTOCOL_V1 §13。若 runner 使用 `external.session_id` / `thread_id` resume 到同一 native session,且该 harness 不支持并发 turn,runner 应按稳定 external session key 串行写入;一次性 subprocess runner 可以只在单次 `run(ctx)` 内处理,长连接/daemon runner 则应采用 reader 独占 native stream、turn writer 串行写入的结构。
|
||||
|
||||
### 6.2 LangBot MCP gateway
|
||||
|
||||
外部 harness 不能直接持有进程内的 `plugin_runtime_handler`,也不能用自己的 native tools 直接访问 LangBot 资源。外部 harness runner 应通过稳定 HTTP MCP gateway 或 SDK-owned bridge 把 harness 的工具请求转回 SDK runtime / Host API:
|
||||
|
||||
- Gateway 由 runner 插件启动,暴露稳定的 `langbot_history_page`、`langbot_retrieve_knowledge`、`langbot_call_tool` 等最小工具面。
|
||||
- Harness 每次调用必须携带当前 LangBot `run_id`;Host 仍按 run session、caller identity 和授权快照校验。
|
||||
- Gateway 只转发 LangBot 资产访问,不承担外部 harness 的文件、进程或 native tool 权限边界。
|
||||
|
||||
第一批工具保持很小:history page、knowledge retrieve、authorized tool call。新增工具必须先有 Host action 权限与 run-scoped authorization,再由 gateway 投影。
|
||||
|
||||
## 7. Code-agent harness runner 当前形态
|
||||
|
||||
外部 code-agent harness 由直接 runner 插件承接,例如 `acp-agent-runner`、`claude-code-agent`、`codex-agent`,每个 runner 负责把目标 harness 的 native session、workspace、MCP bridge 和输出事件转换为统一 `RunnerResult`。本地 smoke 验收入口与记录见 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md)。
|
||||
|
||||
当前形态:
|
||||
|
||||
- Runner ID 示例:`plugin:langbot-team/ACPRunner/default`、`plugin:langbot-team/ClaudeCodeAgent/default`、`plugin:langbot-team/CodexAgent/default`。
|
||||
- Runner 可通过 ACP、远端 daemon、本机 subprocess 或外部 HTTP API 调用 harness;harness 的安装、登录态、workspace 和 provider-native 权限由该运行环境负责。
|
||||
- Runner 会把当前 LangBot `run_id`、可访问资源摘要和 gateway 使用规则注入本次消息;harness 通过 gateway 回填 `run_id` 后访问 LangBot 资产。
|
||||
- 外部 session id / workspace / checkpoint 写回 Host state 或 plugin storage,后续轮次可复用目标 harness 会话。
|
||||
|
||||
### 7.1 当前限制
|
||||
|
||||
这不是发布级安全边界实现;LangBot 只约束 LangBot 持有资产的访问,外部 harness 的文件、进程、workspace、provider-native MCP 和模型凭据由对应 runner 的运行环境承担。当前 `run_id` 可由系统提示词、ACP metadata 或 runner 自有 session metadata 传递给 harness 并由 gateway 校验。runtime 管控面方向见 [RUNTIME_CONTROL_PLANE_V2.md](./RUNTIME_CONTROL_PLANE_V2.md)。
|
||||
|
||||
## 8. 发布和安装策略
|
||||
|
||||
最终 LangBot 安装/升级时需保证官方 runner 插件可用,可选方案:首次启动检测缺失并提示安装,或由用户从 marketplace 安装。当前分支未发布,因此不保留历史 Pipeline Agent 配置兼容、旧内置 runner fallback,也不把旧 Pipeline 内的 Agent 配置迁移成独立 Agent。4.x 只读取 `ai.runner.id` 与 `ai.runner_config[id]`;升级后由用户选择或安装需要的 Runner。
|
||||
|
||||
## 9. 验收标准
|
||||
|
||||
- 每个目标 runner 都有对应官方 Runner 插件和稳定 runner id;当前配置只使用 `ai.runner.id` + `ai.runner_config[id]`。
|
||||
- LangBot 主聊天路径不再通过 `RequestRunner` 执行业务 runner。
|
||||
- 官方插件测试覆盖非流式、流式、错误、timeout、配置缺失。
|
||||
- `local-agent` 能完成模型 fallback、tool calling、知识库检索、多模态输入、静态绑定 prompt 消费、history API 拉取、rerank。
|
||||
- 外部 code-agent harness runner 能消费 event-first context、投影 scoped resources、保存 external session state,并通过 WebUI Debug Chat smoke。
|
||||
- `local-agent` 覆盖旧内置 runner 的用户可见核心能力;代码结构和运行路径不需要相同。
|
||||
@@ -0,0 +1,75 @@
|
||||
# Agent 工具权限
|
||||
|
||||
Agent 配置页展示同一次运行中可能投射给 Runner 的完整工具目录:
|
||||
|
||||
- 事件级工具由 Agent 选择的事件范围自动启用。
|
||||
- `allowed_platform_tools` 管理需要 Agent 自行指定目标的平台级动作。
|
||||
- `allowed_tools` 管理沙盒内置工具、MCP 工具、插件工具和技能工具。
|
||||
|
||||
Host 会按当前 Workspace 实时解析工具来源。未安装的插件、未连接的 MCP、不可用的 Box
|
||||
沙盒以及名称存在歧义的工具不会进入可选目录。旧 Agent 若尚未保存 `allowed_tools`,继续
|
||||
沿用运行器原有的工具策略;一旦在配置页保存,就转为明确的顶层白名单。
|
||||
|
||||
Agent 不直接持有平台适配器,也不能调用任意原始平台接口。每次运行时,Host 根据当前
|
||||
事件自动加入兼容的事件级工具,并加入 `allowed_platform_tools` 中选择的平台级工具,
|
||||
再与 Runner 权限、当前适配器声明的 API、当前事件能够安全绑定的目标取交集,
|
||||
得到 `ctx.resources.tools` 中 `tool_type=platform` 的最终工具集合。
|
||||
|
||||
## 两类工具
|
||||
|
||||
- 事件级工具以 `event_` 开头。用户、群组、消息或请求标识由 Host 从当前事件冻结,
|
||||
Agent 只能填写回复文本、审核结果、禁言时长等动作参数。
|
||||
- 平台级工具以 `platform_` 开头。Agent 可以填写目标用户、群组或消息标识,因此权限
|
||||
更宽,配置页将其与事件级工具分开展示。
|
||||
|
||||
当前事件级工具包括:回复当前会话、删除当前消息、查询事件发起者或相关群组/成员、
|
||||
禁言/解除禁言/移出相关成员、同意或拒绝好友请求、同意或拒绝入群邀请。
|
||||
|
||||
当前平台级工具包括:发送/查询/删除消息,查询群组、群列表、群成员,修改群名称,
|
||||
禁言/解除禁言/移出成员、退出群组,以及查询用户和好友列表。
|
||||
|
||||
`call_platform_api` 不在 Agent 工具目录中。平台私有透传接口必须先在 Host 中定义为
|
||||
具有固定名称、JSON Schema、风险级别和授权规则的语义工具,不能让 Agent 自行传入
|
||||
原始 action 名称。
|
||||
|
||||
## 运行时投射
|
||||
|
||||
```text
|
||||
current event type ── compatible event tools
|
||||
Agent.allowed_platform_tools ── selected platform tools
|
||||
│
|
||||
├─ Runner capability tool_calling is enabled
|
||||
├─ Runner manifest permissions.tools contains call
|
||||
├─ current adapter.get_supported_apis()
|
||||
└─ current event type and frozen target are compatible
|
||||
│
|
||||
▼
|
||||
ctx.resources.tools[tool_type=platform]
|
||||
│
|
||||
├─ Local Agent: RunnerAPIProxy.call_tool
|
||||
└─ External Runner: langbot_list_assets / langbot_get_tool_detail /
|
||||
langbot_call_tool (MCP Asset Gateway)
|
||||
│
|
||||
▼
|
||||
Host revalidates run_id, runner plugin identity, operation and frozen source
|
||||
│
|
||||
▼
|
||||
current bot adapter semantic API
|
||||
```
|
||||
|
||||
本地和外部 Runner 因此使用同一个工具名、参数 Schema 和 Host 授权快照。外部
|
||||
平台不会获得适配器对象或长期凭据;MCP 网关中的 run token 和 Host 中的 run session
|
||||
都只对应当前运行。
|
||||
|
||||
## 失败语义
|
||||
|
||||
- Agent 未选择当前事件:不会触发运行,也不会生成事件级工具。
|
||||
- 平台级工具未在 Agent 配置中选择:不进入运行资源。
|
||||
- Runner 未启用 `tool_calling` 或没有 `tools.call` 权限:所有平台动作均不可用。
|
||||
- 当前适配器不声明对应 API:该工具记入 `platform_capabilities.unavailable_tools`,不投射。
|
||||
- 事件类型不匹配或缺少可冻结目标:事件级工具不投射。
|
||||
- 调用期间机器人下线或适配器能力变化:Host 拒绝执行并返回具体错误。
|
||||
- 参数包含 Schema 之外的字段:Host 拒绝执行。
|
||||
|
||||
这些规则保证配置白名单不是唯一防线;真正的执行授权始终由单次运行快照和执行时检查
|
||||
共同决定。
|
||||
@@ -0,0 +1,820 @@
|
||||
# LangBot Runner Protocol v1
|
||||
|
||||
本文档是 LangBot Host 与插件 SDK / Runtime / Runner 之间协议合同的**唯一规范来源(single source of truth)**。
|
||||
|
||||
- 本文件描述当前 Protocol v1 稳定合同,不混入验收流水。当前实现状态见 [STATUS.md](./STATUS.md),测试执行入口见 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md),安全发布门槛见 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md)。
|
||||
- 本文件之外的任何文档**不得重新定义这里的数据结构**,只能引用,例如"见 PROTOCOL_V1 §4.2"。
|
||||
- Host 内部模型(`AgentEventEnvelope`、`AgentBinding`、Descriptor、各 Store)不属于 SDK 协议,定义在 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md)。
|
||||
|
||||
## 1. 协议目标
|
||||
|
||||
Protocol v1 只解决四件事:
|
||||
|
||||
- LangBot 如何发现插件提供的 Runner。
|
||||
- LangBot 如何把一次事件调用封装成 `RunnerContext`。
|
||||
- Runner 如何以事件流形式返回运行结果。
|
||||
- Runner 如何通过受限 API 访问 LangBot host 能力。
|
||||
|
||||
Protocol v1 **不定义**:
|
||||
|
||||
- LangBot 内部如何持久化 `AgentBinding`(见 HOST_SDK)。
|
||||
- Runner 内部如何组装 prompt、压缩历史、管理 memory(见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md))。
|
||||
- 官方 runner 的具体实现(见 [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md))。
|
||||
- Pipeline 的长期配置模型。
|
||||
- 发布级安全 hardening 的完整实现(见 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md))。
|
||||
|
||||
## 2. 参与方
|
||||
|
||||
| 名称 | 职责 |
|
||||
| --- | --- |
|
||||
| LangBot Host | 事件入口、绑定解析、权限、资源、存储、生命周期、结果投递。 |
|
||||
| Plugin Runtime | 加载插件,响应 Host 的 runner discovery 和 run 调用。 |
|
||||
| Runner | 插件提供的 agent 执行组件。 |
|
||||
| RunnerAPIProxy | Runner 访问 Host 能力的受限 API。 |
|
||||
| AgentBinding | Host 内部的事件到 runner 绑定配置,不直接暴露给 SDK(见 HOST_SDK §4.2)。 |
|
||||
|
||||
产品层同时保留 Pipeline 与独立 `Agent`:现有 Pipeline 不迁移为 Agent;
|
||||
用户可以新建 Agent 并绑定到 bot / IM channel,一个 Agent 可以被多个 bot / channel 复用。Host 内部的
|
||||
`AgentBinding` 是一次事件运行前解析出的有效绑定,只影响 Host 构造出的
|
||||
`ctx.config`、`ctx.resources`、`ctx.context` 和 `ctx.delivery`。SDK 不需要知道
|
||||
Agent / binding 的持久化形态。
|
||||
|
||||
外部 harness runner(Claude Code、Codex、Kimi Code 等)也是 `Runner`:它们消费 event-first `RunnerContext`、返回 `RunnerResult`,并通过 Host 授权的 state/storage API 保存跨轮次指针;当前运行文件和工具大结果进入 sandbox/workspace。它们内部可以继续使用自己的 session、tool loop、MCP、上下文压缩和权限模型。
|
||||
|
||||
## 3. 协议演进
|
||||
|
||||
当前 Runner 合同不暴露显式 `protocol_version` 字段。协议演进先按字段级兼容规则处理:
|
||||
|
||||
- 新增可选字段保持向后兼容。
|
||||
- 删除字段或改变既有字段语义,需要在 SDK 发布前完成;发布后应走新的显式兼容方案。
|
||||
- 结果流演进:Host **必须忽略未知 result type 并记录 warning**(除非该 type 明确要求强校验)。SDK envelope 接收入站未知 `type` 字符串,runner 侧可按原字符串转发或忽略;新增 result type 不提升大版本。
|
||||
- SDK 入站 context 类实体偏宽松,用于兼容 Host 附加的非核心字段;Host 返回 DTO(history/event/steering/run/runtime/stats/error 等)忽略未知字段,保证 Host 增加可选返回字段时旧 SDK 不会解析失败;manifest、runner result payload 等由插件/runner 提交给 Host 的输入合同仍偏严格。安全边界仍在 Host,SDK 校验只提升开发体验。
|
||||
|
||||
## 4. Discovery 协议
|
||||
|
||||
### 4.1 LIST_RUNNERS
|
||||
|
||||
Host 调用 Plugin Runtime 获取当前插件暴露的 runner 列表,请求无额外 payload。返回:
|
||||
|
||||
```python
|
||||
class ListRunnersResponse(BaseModel):
|
||||
runners: list[RunnerDiscovery]
|
||||
|
||||
class RunnerDiscovery(BaseModel):
|
||||
plugin_author: str
|
||||
plugin_name: str
|
||||
runner_name: str
|
||||
manifest: RunnerManifest
|
||||
```
|
||||
|
||||
`manifest` 是 SDK typed `RunnerManifest`,由 Runtime 从插件组件 manifest 解析并校验后返回。`plugin_author` / `plugin_name` / `runner_name` 保留为 transport 寻址字段;Host 以它们生成稳定 runner id,并把 `manifest.id` 校验为 `plugin:author/name/runner`。单个 runner manifest 解析失败时 Runtime/Host 记录 warning 并跳过该 runner,不影响同一插件或其它插件的 runner discovery。
|
||||
|
||||
### 4.2 RunnerManifest
|
||||
|
||||
这里的 manifest 指 Runtime 返回给 Host 的 typed runner manifest:
|
||||
|
||||
```python
|
||||
class RunnerManifest(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
label: I18nObject
|
||||
description: I18nObject | None = None
|
||||
capabilities: RunnerCapabilities = RunnerCapabilities()
|
||||
permissions: RunnerPermissions = RunnerPermissions()
|
||||
config_schema: list[DynamicFormItemSchema] = []
|
||||
metadata: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
- runner id 由 Host 生成,格式 `plugin:author/name/runner`。
|
||||
- `name` 是插件内 runner 名称,例如 `default`。
|
||||
- `config_schema` 只描述绑定配置表单,不代表插件实例状态。
|
||||
- `capabilities` 是 Host 用于 UI 和资源投影的 typed bool model;它不是权限授予。
|
||||
- `permissions` 是 runner 申请的 LangBot 资源访问上限;实际授权仍必须与 binding policy 求交。
|
||||
- `metadata` 只放展示、诊断、非稳定扩展信息。
|
||||
|
||||
### 4.3 Capabilities
|
||||
|
||||
```python
|
||||
class RunnerCapabilities(BaseModel):
|
||||
streaming: bool = False
|
||||
tool_calling: bool = False
|
||||
knowledge_retrieval: bool = False
|
||||
multimodal_input: bool = False
|
||||
skill_authoring: bool = False
|
||||
interrupt: bool = False
|
||||
steering: bool = False
|
||||
interactions: bool = False
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
```
|
||||
|
||||
- `streaming`: runner 可以返回 `message.delta`。
|
||||
- `tool_calling`: runner 可能调用 Host tool API。
|
||||
- `knowledge_retrieval`: runner 可能调用 Host knowledge API。
|
||||
- `multimodal_input`: runner 可以处理非纯文本 input / attachment。
|
||||
- `skill_authoring`:(降级为便捷开关,非访问硬前提)声明该 runner 期望使用 LangBot skill 工具链。skill 本身通过**统一 tool 授权**获得——发现走 `list_skills` / `langbot_list_assets`,激活/注册走 `activate` / `register_skill`,操作走 native exec/read/write,全部计入 `resource_policy.allowed_tool_names`。该 capability 仅作为「一键授权这组 skill tool + sandbox」的便捷开关,不再单独决定 skill 是否可用。
|
||||
- `interrupt`: runner 支持取消或中断。
|
||||
- `steering`: runner 支持在 turn 边界通过 Host pull API 消费同 conversation 在途追加消息。
|
||||
- `interactions`: runner 可以请求 Host 展示结构化交互,并处理后续 `interaction.submitted` 事件。
|
||||
|
||||
Capabilities 字段全部是 `bool`,未知 key 禁止进入 typed manifest。早期草案里的上下文/会话类 capability 已删除;对应语义由 event-first context 和 runner-owned context 原则表达。
|
||||
|
||||
### 4.4 Permissions 与 Effective Access
|
||||
|
||||
```python
|
||||
class RunnerPermissions(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"]] = []
|
||||
storage: list[Literal["plugin", "workspace"]] = []
|
||||
files: list[Literal["config", "knowledge"]] = []
|
||||
interactions: list[Literal["request"]] = []
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
```
|
||||
|
||||
通用交互投递是当前唯一允许执行的 `action.requested` 白名单动作。Runner 必须同时声明
|
||||
`capabilities.interactions=true` 和 `permissions.interactions=["request"]`,Host 还必须将其与
|
||||
当前 binding delivery policy、run authorization snapshot 和 adapter delivery capability 求交。
|
||||
其它 `action.requested` 动作仍只记录 telemetry,不得作为任意平台动作执行器。平台语义动作已经通过 `ctx.resources.tools` 中 `tool_type="platform"` 的工具提供:Runner 需具备 `tool_calling` capability 和 `permissions.tools` 的 `call` 操作,Host 再与 Agent 工具策略、适配器能力和当前事件目标求交。`event_*` 的目标由 Host 冻结,`platform_*` 需显式选择;调用仍走统一 `call_tool`。这不新增 manifest permission 字段,规则见 [PLATFORM_ACTION_TOOLS.md](./PLATFORM_ACTION_TOOLS.md)。
|
||||
|
||||
Runner 实际可用 LangBot 资源来自 Host 在 run 前冻结的授权快照:
|
||||
|
||||
```text
|
||||
effective_access = manifest.permissions ∩ binding.resource_policy ∩ current scope/config
|
||||
```
|
||||
|
||||
具体落地:
|
||||
|
||||
1. `AgentResourceBuilder` 先用 manifest permissions 与 binding resource policy / runner config 求交,生成 `ctx.resources`。
|
||||
2. `AgentContextBuilder` 用 manifest permissions 与 binding state/storage policy 求交,生成 `ctx.context.available_apis`。
|
||||
3. `AgentRunSessionRegistry` 冻结 run-scoped resources 与 available APIs。
|
||||
4. Runtime handler / `RunnerAPIProxy` 按 active `run_id`、runner identity、caller plugin identity、resource id、scope、payload size、rate limit 和 deadline 校验每次调用。
|
||||
|
||||
反承诺:manifest permissions **只约束 LangBot 持有的资源访问**。它不承诺限制外部 harness 的 native shell、文件系统、CLI、MCP、网络或本机权限;这些能力由 operator/runtime/sandbox 另行约束,见 HOST_SDK §4.8 与 SECURITY_HARDENING。
|
||||
|
||||
默认原则:
|
||||
|
||||
- Host 不得默认 inline 全量历史。
|
||||
- Host 只 inline 当前 event / input 和 context handles。
|
||||
- Runner 拥有 working context assembly。
|
||||
- Runner 可在授权后通过 Host history / event / state API 拉取更多上下文,并通过授权 sandbox/workspace 工具访问当前运行文件。
|
||||
- 历史窗口策略不属于 Protocol v1 字段,也不属于 Host 通用语义。
|
||||
|
||||
context 边界的设计理由见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。
|
||||
|
||||
## 5. Run 协议
|
||||
|
||||
### 5.1 RUN_RUNNER
|
||||
|
||||
Host 调用 Runtime:
|
||||
|
||||
```python
|
||||
class AgentRunRequest(BaseModel):
|
||||
runner_id: str
|
||||
runner_name: str
|
||||
context: RunnerContext
|
||||
```
|
||||
|
||||
Runtime 返回 `RunnerResult` 异步流。底层 transport 可继续用 `plugin_author` / `plugin_name` / `runner_name` 定位组件,但协议语义以 `runner_id` 和 `context` 为准。
|
||||
|
||||
### 5.2 RunnerContext
|
||||
|
||||
这是 SDK 看到的**唯一权威 context 定义**。
|
||||
|
||||
```python
|
||||
class RunnerContext(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
|
||||
|
||||
```python
|
||||
class AgentTrigger(BaseModel):
|
||||
type: str
|
||||
source: Literal["platform", "webui", "api", "scheduler", "system", "host_adapter"]
|
||||
timestamp: int | None = None
|
||||
```
|
||||
|
||||
`trigger.type` 应与 `event.event_type` 一致或更粗粒度。例如入口适配器触发消息时:
|
||||
|
||||
```json
|
||||
{ "type": "message.received", "source": "host_adapter" }
|
||||
```
|
||||
|
||||
### 5.4 AgentEventContext
|
||||
|
||||
```python
|
||||
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](./EVENT_BASED_AGENT.md)。
|
||||
- 平台原始事件名放入 `source_event_type`。
|
||||
- 大型原始 payload 必须放入 `raw_ref` 或 staged file,不应直接塞入 `data`。
|
||||
|
||||
### 5.5 Conversation / Actor / Subject
|
||||
|
||||
```python
|
||||
class ConversationContext(BaseModel):
|
||||
conversation_id: str | None = None
|
||||
thread_id: str | None = None
|
||||
launcher_type: str | None = None
|
||||
launcher_id: str | None = None
|
||||
sender_id: str | None = None
|
||||
bot_id: str | None = None
|
||||
workspace_id: str | None = None
|
||||
session_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 可以是 system,subject 是 schedule。
|
||||
|
||||
### 5.6 AgentInput
|
||||
|
||||
```python
|
||||
class AgentInput(BaseModel):
|
||||
text: str | None = None
|
||||
contents: list[ContentElement] = []
|
||||
attachments: list[InputAttachment] = []
|
||||
interaction: InteractionSubmission | None = None
|
||||
```
|
||||
|
||||
- 文本、多模态、附件都属于当前 event input。
|
||||
- 大文件、图片、音频、工具大结果应进入授权 sandbox/workspace,input attachment 只携带轻量 metadata/path/url/content。
|
||||
- 平台原始消息链不属于 SDK `AgentInput`;需要诊断时放在 Host 内部 envelope 或 `ctx.adapter.extra` 的一次性兼容字段中,不作为长期 runner 合同。
|
||||
- `interaction` 只在 `event.event_type == "interaction.submitted"` 时出现,承载经过 Host 校验和归一化的用户提交;平台原始 callback payload 不进入该字段。
|
||||
|
||||
### 5.7 DeliveryContext
|
||||
|
||||
```python
|
||||
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
|
||||
interactions: InteractionDeliveryCapabilities | None = None
|
||||
platform_capabilities: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
Runner 可参考 delivery 能力决定返回 `message.delta`、`message.completed` 或交互请求。
|
||||
`interactions is not None` 表示当前 surface 可以投递结构化交互;其中的 field/action 能力
|
||||
决定 Host 可原生渲染的范围。Runner 仍必须提供 `fallback_text`,供不支持富交互或投递失败时降级。
|
||||
平台事件进入独立 Agent 时,Host 会从当前 adapter 的 `get_supported_apis()` 投影
|
||||
`supports_edit`、`supports_reaction`,并把去重后的 API 名称写入
|
||||
`platform_capabilities.supported_apis`。这些字段只描述当前投递表面的能力,不授予
|
||||
任意平台动作权限;交互请求按 §5.8 和 §7.3 的白名单约束执行。合成路由测试使用的
|
||||
adapter 会过滤所有已知副作用 API,因此测试事件不会向 runner 宣告真实出站能力。
|
||||
|
||||
### 5.8 Structured Interaction
|
||||
|
||||
```python
|
||||
InteractionFieldType = Literal[
|
||||
"text", "textarea", "select", "multiselect", "number", "boolean", "file"
|
||||
]
|
||||
|
||||
class InteractionOption(BaseModel):
|
||||
value: str
|
||||
label: str
|
||||
description: str | None = None
|
||||
|
||||
class InteractionField(BaseModel):
|
||||
id: str
|
||||
label: str
|
||||
type: InteractionFieldType
|
||||
required: bool = False
|
||||
options: list[InteractionOption] = []
|
||||
placeholder: str | None = None
|
||||
default: JSONValue | None = None
|
||||
|
||||
class InteractionAction(BaseModel):
|
||||
id: str
|
||||
label: str
|
||||
style: Literal["default", "primary", "danger"] = "default"
|
||||
|
||||
class InteractionRequest(BaseModel):
|
||||
interaction_id: str
|
||||
kind: Literal["form", "confirmation", "choice"] = "form"
|
||||
title: str
|
||||
description: str | None = None
|
||||
fields: list[InteractionField] = []
|
||||
actions: list[InteractionAction] = []
|
||||
expires_at: int | None = None
|
||||
fallback_text: str
|
||||
|
||||
class InteractionSubmission(BaseModel):
|
||||
interaction_id: str
|
||||
action_id: str | None = None
|
||||
values: dict[str, JSONValue] = {}
|
||||
submitted_at: int | None = None
|
||||
|
||||
class InteractionDeliveryCapabilities(BaseModel):
|
||||
field_types: list[InteractionFieldType] = []
|
||||
action_styles: list[Literal["default", "primary", "danger"]] = []
|
||||
supports_updates: bool = False
|
||||
max_fields: int | None = None
|
||||
```
|
||||
|
||||
Runner 使用 `RunnerResult.interaction_requested()` 生成
|
||||
`action.requested(action="interaction.requested")`。Host 只能把请求投递到当前 run 冻结的
|
||||
delivery target,Runner 不得通过 `target` 改写 bot、conversation 或用户。Host 为请求保存
|
||||
`interaction_id -> processor/binding/conversation/expiry` 关联;平台 callback 必须先经过签名、
|
||||
目标、过期和幂等校验,再生成 `interaction.submitted` 事件。
|
||||
|
||||
Provider 私有 continuation token、workflow id、凭据或原始 callback payload 不属于该协议。
|
||||
Runner 应把这些值保存到受授权的 Host state 或 plugin storage,并只用 `interaction_id` 关联提交。
|
||||
Host 必须使用服务器接收时间判断 callback 是否过期,并覆盖 `InteractionSubmission.submitted_at`;
|
||||
平台事件时间只可作为原始事件诊断信息,不能控制 TTL 或进入 Runner 的可信提交字段。
|
||||
|
||||
### 5.9 ContextAccess
|
||||
|
||||
```python
|
||||
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):
|
||||
prompt_get: bool = False
|
||||
history_page: bool = False
|
||||
history_search: bool = False
|
||||
event_get: bool = False
|
||||
event_page: bool = False
|
||||
state: bool = False
|
||||
storage: bool = False
|
||||
steering_pull: bool = False
|
||||
```
|
||||
|
||||
`ContextAccess` 告诉 runner:Host inline 了什么、没 inline 什么、需要更多上下文时走哪些 API。它是 runner 按需读取上下文的入口说明,不是 Host 的业务上下文编排策略。
|
||||
|
||||
### 5.9 AgentRuntimeContext
|
||||
|
||||
```python
|
||||
class AgentRuntimeContext(BaseModel):
|
||||
langbot_version: str | None = None
|
||||
trace_id: str | None = None
|
||||
deadline_at: float | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
### 5.10 AgentRunState
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
class ToolResource(BaseModel):
|
||||
tool_name: str
|
||||
tool_type: str | None = None
|
||||
description: str | None = None
|
||||
parameters: dict[str, Any] | None = None # 完整 JSON schema,由 Host 一次塞齐
|
||||
operations: list[Literal["detail", "call"]] = []
|
||||
|
||||
class SkillResource(BaseModel):
|
||||
skill_name: str
|
||||
display_name: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
class AgentResources(BaseModel):
|
||||
models: list[ModelResource] = []
|
||||
tools: list[ToolResource] = []
|
||||
knowledge_bases: list[KnowledgeBaseResource] = []
|
||||
skills: list[SkillResource] = []
|
||||
storage: StorageResource = StorageResource()
|
||||
platform_capabilities: dict[str, Any] = {}
|
||||
```
|
||||
|
||||
`tools` 携带每个授权工具的完整 schema(`parameters`),由 Host 在构造 `ctx.resources` 时一次塞齐,runner 不需再逐个调用 `get_tool_detail` 拉取,减少 N 次往返。
|
||||
|
||||
`skills` 是本次 run 中 pipeline-visible 的 skill facts(`skill_name`、`display_name`、`description`)。**skill 通过统一 tool 形式消费,不是独立资源类别**:发现走 `list_skills` tool(或 `langbot_list_assets` 增加 skills 一类),激活走 `activate`,操作走 native exec/read/write。Host **不**把 skill 索引注入 system prompt,也不做 progressive-disclosure 注入;LLM 通过调用发现工具主动查询 skill 清单。Host **可选**在 ctx 提供预渲染的 `suggested_skill_prompt`(首轮延迟优化,runner 可忽略 / override),但它不是访问前提。`skills` 字段本身仅作为发现工具的数据来源与该可选预渲染的输入。
|
||||
|
||||
资源列表是本次 run 的授权结果。History / Event / State / Storage 访问通过 `ctx.context.available_apis` 和 Host 侧 run session 校验控制,不作为可枚举 resource list 暴露。Runner 只能通过 `RunnerAPIProxy` 访问这些能力。当前事件的文件和工具大结果优先进入授权 sandbox/workspace,由 runner 通过 read/write/exec 类工具按需读取。
|
||||
|
||||
## 7. Result Stream
|
||||
|
||||
### 7.1 RunnerResult envelope
|
||||
|
||||
```python
|
||||
JSONValue = str | int | float | bool | None | list["JSONValue"] | dict[str, "JSONValue"]
|
||||
|
||||
ResultType = Literal[
|
||||
"message.delta",
|
||||
"message.completed",
|
||||
"tool.call.started",
|
||||
"tool.call.completed",
|
||||
"state.updated",
|
||||
"action.requested",
|
||||
"run.completed",
|
||||
"run.failed",
|
||||
]
|
||||
|
||||
class RunnerResult(BaseModel):
|
||||
run_id: str
|
||||
type: RunnerResultType | str
|
||||
data: dict[str, Any] = {}
|
||||
usage: LLMTokenUsage | None = None
|
||||
sequence: int | None = None
|
||||
timestamp: int | None = None
|
||||
```
|
||||
|
||||
SDK 当前实现是单一 envelope:`type` 枚举 + `data` dict。Payload 由 SDK typed model 构造并 dump,但 wire 不改成 discriminated union;这样新旧版本偏斜时 Host 仍可按 §3 忽略未知 `type`。
|
||||
|
||||
`usage` 是 runner 可选上报的 token 使用量,沿用 SDK `LLMTokenUsage`:
|
||||
|
||||
```python
|
||||
class LLMTokenUsage(BaseModel):
|
||||
prompt_tokens: int | None = None
|
||||
completion_tokens: int | None = None
|
||||
total_tokens: int | None = None
|
||||
# provider-specific detail/cached/reasoning counters are preserved as extra fields
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 运行时能观测到 provider/runtime usage 时,SHOULD 在 terminal `run.completed.usage` 上报本次 run 的最终聚合 token usage。
|
||||
- `run.failed.usage` MAY 上报失败前已经产生的部分 usage。
|
||||
- 不能观测 usage 的 runner 合法地省略该字段;缺失表示 unknown,Host 不得按 0 处理。
|
||||
- ACP 等外部协议不保证统一 usage;ACP runner 只能在具体 provider/native event 提供 usage 时填充本字段。
|
||||
- cost 不作为 runner result 的权威字段。Host 后续应基于 usage、model identity、时间和自身价格表计算账单成本;provider 原始 cost 如需保留,可放在 `usage` extra 字段中作为非权威 telemetry。
|
||||
|
||||
Host 边界分级校验:
|
||||
|
||||
- `message.delta`、`message.completed`、`state.updated`、`action.requested`、`run.completed`、`run.failed` 属于会影响投递或 Host 副作用的严格 payload;校验失败时丢弃该 result 并记录 warning。
|
||||
- `tool.call.started`、`tool.call.completed` 当前只作为 telemetry,payload 宽松兼容。
|
||||
- 未知 `type` 忽略并记录 warning。
|
||||
|
||||
### 7.2 稳定 result payloads
|
||||
|
||||
| type | `data` payload |
|
||||
| --- | --- |
|
||||
| `message.delta` | `{ "chunk": MessageChunk }` |
|
||||
| `message.completed` | `{ "message": Message }` |
|
||||
| `tool.call.started` | `{ "tool_call_id": str, "tool_name": str, "parameters": dict }` |
|
||||
| `tool.call.completed` | `{ "tool_call_id": str, "tool_name": str, "result": dict \| None, "error": str \| None }` |
|
||||
| `state.updated` | `{ "scope": "conversation" \| "actor" \| "subject" \| "runner", "key": str, "value": JSONValue }` |
|
||||
| `action.requested` | `{ "action": str, "target": dict \| None, "payload": dict \| None }` |
|
||||
| `run.completed` | `{ "finish_reason": str, "message"?: Message }` |
|
||||
| `run.failed` | `{ "code": str, "error": str, "retryable": bool }` |
|
||||
|
||||
Runner 生成的大文件、工具输出和临时产物不通过 result event 回传;应写入当前 run 的授权 sandbox/workspace,再用消息文本、metadata 或 attachment reference 指向它们。
|
||||
|
||||
### 7.3 稳定 result types
|
||||
|
||||
| type | 说明 | 当前消费 |
|
||||
| --- | --- | --- |
|
||||
| `message.delta` | 流式消息片段。 | ✅ |
|
||||
| `message.completed` | 完整消息。 | ✅ |
|
||||
| `tool.call.started` | 工具调用开始的可观测事件。 | telemetry |
|
||||
| `tool.call.completed` | 工具调用完成的可观测事件。 | telemetry |
|
||||
| `state.updated` | runner 请求更新 host-owned state。 | ✅ |
|
||||
| `action.requested` | runner 请求 Host 执行受控动作。 | `interaction.requested` 可执行;其它动作仅 telemetry |
|
||||
| `run.completed` | run 正常结束。 | ✅ |
|
||||
| `run.failed` | run 失败。 | ✅ |
|
||||
|
||||
`action.requested` 仍是严格白名单协议表面。当前 Host 只执行
|
||||
`action="interaction.requested"`,并要求 payload 可校验为 `InteractionRequest`、Runner 声明
|
||||
interaction capability/permission、当前 binding 允许交互且 delivery surface 支持交互。
|
||||
Host 忽略 Runner 提供的外部 target,只使用 run authorization snapshot 中冻结的 delivery target。
|
||||
其它 action 只记 telemetry,不执行;通用 platform action executor 仍不属于本协议。
|
||||
|
||||
Host 必须校验 `state.updated` 的 scope、key、value 大小和 JSON 可序列化性。Host 还必须限制
|
||||
交互字段/动作数量、字符串长度、payload 总大小、过期时间和 interaction id 唯一性,并对 callback
|
||||
应用默认 30 分钟、最长 24 小时的有效期;request 与 submission payload 均不得超过 256 KiB。
|
||||
执行目标绑定、签名、幂等和过期校验。
|
||||
|
||||
除 runner 经 `state.updated` 写之外,Host 自身也可直接写部分 host-owned state。例如 `activate` tool 在 Host 侧执行时,直接把已激活 skill 写入 conversation scope 的 `host.activated_skills` 快照。当 host 直接写与 runner `state.updated` 写到同一 key 时,按 **last-write-wins** 合并——runner 可以覆盖 host 写的快照。
|
||||
|
||||
### 7.4 Stream delivery semantics
|
||||
|
||||
- Host 按 Runtime stream 顺序消费 result。当前 v1 不定义跨连接 replay,也不承诺 at-least-once;从 Host 视角,收到的 result 最多应用一次。
|
||||
- `sequence` 是单个 `run_id` 内的结果序号。in-process / stdio 这类天然有序的在线 stream 可以省略;任何会缓冲、重放、跨进程队列或 runtime-managed task 的 transport 必须提供从 1 开始严格递增的 `sequence`。
|
||||
- Host 看到已提供 `sequence` 的 result 时,应按 `(run_id, sequence)` 做重复检测,并在缺号或乱序时记录 warning;除非 transport 明确声明 replay 语义,Host 不应自行等待缺失序号重排用户可见输出。
|
||||
- `run.failed.data.retryable` 只表示整次 run 理论上可由上层重试;Protocol v1 不自动重试 run,也不自动重试 proxy action。
|
||||
- History / Event / Transcript cursor 是 opaque token。runner 不得解析 cursor,也不得假设 cursor 在不同 API、conversation、thread 或 retention window 之间可比较;当前实现即使返回数字字符串,也只是实现细节。
|
||||
|
||||
### 7.5 示例
|
||||
|
||||
```json
|
||||
{ "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": "interaction.requested", "payload": { "interaction_id": "form_1", "kind": "choice", "title": "Approve?", "actions": [{"id": "approve", "label": "Approve", "style": "primary"}], "fallback_text": "Reply approve or reject." } } }
|
||||
```
|
||||
|
||||
## 8. RunnerAPIProxy
|
||||
|
||||
所有 proxy action 必须携带 `run_id`。Host 必须校验:active run session 存在、caller plugin identity 匹配、resource 在本次 `ctx.resources` 中授权、scope 不越界、payload size / rate limit / deadline 合法。
|
||||
|
||||
```python
|
||||
# Model
|
||||
await api.invoke_llm(llm_model_uuid, messages, funcs=None, extra_args=None)
|
||||
await api.invoke_llm_with_usage(llm_model_uuid, messages, funcs=None, extra_args=None)
|
||||
async for chunk in api.invoke_llm_stream(llm_model_uuid, messages, funcs=None, extra_args=None):
|
||||
...
|
||||
async for event in api.invoke_llm_stream_events(llm_model_uuid, messages, funcs=None, extra_args=None):
|
||||
...
|
||||
await api.invoke_rerank(rerank_model_id, query, documents, top_k=None)
|
||||
|
||||
# Tool
|
||||
await api.get_tool_detail(tool_name)
|
||||
await api.call_tool(tool_name, parameters)
|
||||
|
||||
# Knowledge
|
||||
await api.retrieve_knowledge(kb_id, query_text, top_k=5, filters=None)
|
||||
|
||||
# History(返回 Transcript projection,不返回原始平台 payload)
|
||||
await api.get_prompt()
|
||||
await api.history_page(conversation_id=None, before_cursor=None, after_cursor=None,
|
||||
limit=50, direction="backward", include_attachments=False)
|
||||
await api.history_search(query, filters=None, top_k=10)
|
||||
|
||||
# Event(返回稳定 event envelope 或受限 raw ref,不默认返回大 payload)
|
||||
await api.event_get(event_id)
|
||||
await api.event_page(conversation_id=None, event_types=None, before_cursor=None, limit=50)
|
||||
await api.steering_pull(mode="all", limit=None)
|
||||
|
||||
# State / Storage
|
||||
await api.state_get(scope, key); await api.state_set(scope, key, value); await api.state_delete(scope, key)
|
||||
await api.state_list(scope, prefix=None, limit=100)
|
||||
await api.get_plugin_storage(key); await api.set_plugin_storage(key, value); await api.delete_plugin_storage(key)
|
||||
await api.get_plugin_storage_keys()
|
||||
await api.get_workspace_storage(key); await api.set_workspace_storage(key, value); await api.delete_workspace_storage(key)
|
||||
await api.get_workspace_storage_keys()
|
||||
|
||||
# Host info
|
||||
await api.get_langbot_version()
|
||||
```
|
||||
|
||||
`invoke_llm()` / `invoke_llm_stream()` 的第一个参数在 SDK 中命名为
|
||||
`llm_model_uuid`,wire payload 字段也是 `llm_model_uuid`。该值对 runner
|
||||
仍是 opaque identifier,不应解析其内部格式。
|
||||
|
||||
`invoke_llm()` 和 `invoke_llm_stream()` 保持兼容:前者返回 `Message`,后者只
|
||||
yield `MessageChunk`。需要 provider 真实 token 计量的 runner 应使用
|
||||
`invoke_llm_with_usage()` 或 `invoke_llm_stream_events()`。Host response 可在
|
||||
原有 `{message: ...}` / `{chunk: ...}` 外额外携带可选 `usage` 字段;streaming
|
||||
场景允许在所有 chunk 之后追加一个 usage-only event。`usage` 至少保留
|
||||
OpenAI-compatible 的 `prompt_tokens`、`completion_tokens`、`total_tokens`,
|
||||
若 provider 返回 `prompt_tokens_details` / `completion_tokens_details` 或
|
||||
cache token counters,Host / SDK 不应丢弃这些字段。没有 usage 的 provider
|
||||
必须继续返回成功响应,SDK 将 usage 置为 `None`。
|
||||
|
||||
`get_prompt()` 返回当前 query-backed run 的 Host effective prompt messages:
|
||||
`list[Message]` 的 JSON 形式。该能力只在 `ctx.context.available_apis.prompt_get`
|
||||
为 true 时可用;没有 query 缓存、prompt 已过期或非 query entry run 时 Host
|
||||
可以返回错误或空列表。Runner 应在不可用时回退到自己的 config/prompt 策略。
|
||||
|
||||
`steering_pull(mode="all")` 是推荐默认:Host 按 claim 顺序返回全部 pending steering 输入并清空对应队列。`mode="one-at-a-time"` 仅用于 runner 主动节流,每次返回一条。Host 不合并多条用户消息;runner 负责在 turn 边界决定模型侧格式。
|
||||
|
||||
Steering 审计使用 EventLog 而不是 Transcript schema 扩展:被 active run 吸收的原始 `message.received` 事件保留原事件类型,并在 `metadata.steering` 标记 `status="queued"`、`trigger_behavior="absorbed_into_active_run"`、`claimed_by_run_id`、`claimed_runner_id`、`claimed_at`。Runner 成功 pull 后,Host 追加 `steering.injected` EventLog 记录,`metadata.steering.status="injected"` 并引用 `source_event_id`。若 run 结束时仍有已 claim 但未 pull 的 steering 输入,Host 追加 `steering.dropped` EventLog 记录,`metadata.steering.status="dropped"` 并引用 `source_event_id`;这不是用户消息事实的删除,只是 dispatch 终态。Transcript 继续只表示会话事实,不承担 dispatch 行为标记。
|
||||
|
||||
`state` 与 `storage` 的建议边界:`state` 放小型 JSON(conversation / actor / subject / runner),`storage` 放 blob 或较大数据(插件私有数据、workspace 数据、checkpoint)。
|
||||
|
||||
Compaction checkpoint 的推荐 state 约定:
|
||||
|
||||
- scope: `conversation`
|
||||
- key: `runner.compaction.checkpoint`
|
||||
- value:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "langbot.local_agent.compaction_checkpoint.v1",
|
||||
"summary": "<conversation_summary>...</conversation_summary>",
|
||||
"covers_until": "transcript-cursor-or-seq",
|
||||
"tokens_before": 12345,
|
||||
"created_at": 1710000000,
|
||||
"conversation_id": "conv-..."
|
||||
}
|
||||
```
|
||||
|
||||
`covers_until` 是摘要覆盖到的 transcript 游标锚点。Runner 读取 checkpoint 后应只拉取该游标之后的 transcript;若 checkpoint 缺失、schema 不匹配、conversation 不匹配或游标不可用,应回退到无 checkpoint 的尾部历史拉取行为。
|
||||
|
||||
Proxy 返回数据结构也属于本协议:
|
||||
|
||||
```python
|
||||
class TranscriptItem(BaseModel):
|
||||
transcript_id: str
|
||||
event_id: str
|
||||
conversation_id: str | None = None
|
||||
thread_id: str | None = None
|
||||
role: str
|
||||
item_type: str = "message"
|
||||
content: str | None = None
|
||||
content_json: dict[str, Any] | None = None
|
||||
attachment_refs: list[dict[str, Any]] = []
|
||||
seq: int | None = None
|
||||
cursor: str | None = None
|
||||
created_at: int | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
|
||||
class HistoryPage(BaseModel):
|
||||
items: list[TranscriptItem] = []
|
||||
next_cursor: str | None = None
|
||||
prev_cursor: str | None = None
|
||||
has_more: bool = False
|
||||
total_count: int | None = None
|
||||
|
||||
class HistorySearchResult(BaseModel):
|
||||
items: list[TranscriptItem] = []
|
||||
total_count: int | None = None
|
||||
query: str
|
||||
|
||||
class AgentEventRecord(BaseModel):
|
||||
event_id: str
|
||||
event_type: str
|
||||
event_time: int | None = None
|
||||
source: str
|
||||
bot_id: str | None = None
|
||||
workspace_id: str | None = None
|
||||
conversation_id: str | None = None
|
||||
thread_id: str | None = None
|
||||
actor_type: str | None = None
|
||||
actor_id: str | None = None
|
||||
actor_name: str | None = None
|
||||
subject_type: str | None = None
|
||||
subject_id: str | None = None
|
||||
input_summary: str | None = None
|
||||
input_ref: str | None = None
|
||||
raw_ref: str | None = None
|
||||
seq: int | None = None
|
||||
cursor: str | None = None
|
||||
created_at: int | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
|
||||
class EventPage(BaseModel):
|
||||
items: list[AgentEventRecord] = []
|
||||
next_cursor: str | None = None
|
||||
prev_cursor: str | None = None
|
||||
has_more: bool = False
|
||||
total_count: int | None = None
|
||||
|
||||
class SteeringInputItem(BaseModel):
|
||||
claimed_run_id: str
|
||||
runner_id: str
|
||||
claimed_at: int | None = None
|
||||
event: AgentEventContext
|
||||
input: AgentInput
|
||||
conversation: ConversationContext | None = None
|
||||
actor: ActorContext | None = None
|
||||
subject: SubjectContext | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
|
||||
class SteeringPullResult(BaseModel):
|
||||
items: list[SteeringInputItem] = []
|
||||
```
|
||||
|
||||
## 9. 错误模型
|
||||
|
||||
```python
|
||||
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 或下游能力错误。 |
|
||||
|
||||
SDK runner-facing proxy 在 Host 返回结构化错误或畸形响应时抛出 `AgentAPIException`,其中 `error` 字段为 `AgentAPIError`。Legacy transport 只返回字符串错误时,SDK 使用 `host.action_error` 包装,避免 runner 继续依赖裸 `KeyError` 或字符串匹配。
|
||||
|
||||
Runner 失败使用 `run.failed`:
|
||||
|
||||
```json
|
||||
{ "type": "run.failed", "data": { "code": "runner.error", "error": "failed to call external agent", "retryable": false } }
|
||||
```
|
||||
|
||||
## 10. Timeout 与 Cancellation
|
||||
|
||||
- Host 在 `ctx.runtime.deadline_at` 下发总 deadline;SDK proxy 必须用该 deadline 限制单次 action timeout。
|
||||
- Host 可以取消 active run;Runtime 应尽力中断 runner。
|
||||
- Protocol v1 的 run 绑定当前 Host 进程和当前 runtime channel,不保证跨 Host 重启恢复。Host 重启、runtime channel 断开或 run session 丢失时,Runtime / external harness connector 必须 fail-fast 并尽力取消仍在执行的 runner,不得继续使用旧 `run_id` 调用 Host API。
|
||||
- Runner 支持中断时应返回或触发 `run.failed`,code 为 `cancelled`。
|
||||
- Host 必须 unregister active run session。
|
||||
|
||||
## 11. Security 与 Guardrail(协议层)
|
||||
|
||||
Protocol v1 的安全边界在 Host:
|
||||
|
||||
- Runner 不能直接访问未授权 model/tool/kb/history/storage/sandbox。
|
||||
- SDK 本地校验只提升开发体验,不能替代 Host 校验。
|
||||
- 所有 resource id 对 runner 来说都是 opaque。
|
||||
- 默认只能访问当前 conversation / thread 的 history;跨会话、workspace 级访问必须额外授权。
|
||||
- 大 payload 不应塞进 result event;当前 run 的文件和工具大结果应进入授权 sandbox/workspace,由 read/write/exec 类工具按需访问。
|
||||
- Host 必须记录 run_id、runner_id、action、resource、scope、result。
|
||||
|
||||
Host 不负责业务编排:不拼接全量历史、不替 runner 做 prompt assembly、不内置 agent memory / tool loop / 上下文压缩策略。这些由官方或第三方 Runner 插件实现。
|
||||
|
||||
外部 harness runner 的边界统一见 HOST_SDK §4.8。简言之:harness native permission mode、allowed/disallowed tools、shell/MCP 权限只是额外执行约束,不能替代 Host 对 LangBot 资源的授权。
|
||||
|
||||
> 发布级路径隔离、MCP allowlist、secret redaction、配额、workspace 清理等**不属于** v1 协议闭环,是生产默认启用前的 release gate,见 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md)。
|
||||
|
||||
## 12. Pipeline AI Stage Adapter 边界
|
||||
|
||||
Pipeline 与 Agent 是 EBA 中平级的处理器:Pipeline 处理消息事件并执行完整
|
||||
Stage 链,Agent 处理其声明支持的消息或非消息事件。本协议只约束 Runner
|
||||
调用,因此 Pipeline 仅在 AI Stage 调用 runner 时进入 Query entry adapter;
|
||||
该适配不会把 Pipeline 变成 Agent,也不会创建或更新持久 Agent。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 策略。
|
||||
- `ctx.adapter.extra` 只允许承载一次性、JSON-safe、入口相关的非核心元数据,例如 `params`;不得承载 `prompt`、history window、RAG 结果、tool schema 或授权资源。
|
||||
- 静态绑定 prompt 属于 `ctx.config.prompt`。preprocessing / hook 后的动态有效指令不通过 `ctx.adapter.extra` 主动推送;后续如需要保留这类能力,应通过 Host prompt/instruction pull API 暴露(占位见 HOST_SDK §4.8)。
|
||||
- 新 runner 不应长期依赖 `adapter`,应只依赖 event-first context 和 Host API。
|
||||
|
||||
## 13. 已确认约束
|
||||
|
||||
- EBA 路由层是 `one event -> one Processor target (Pipeline | Agent)`;同一 bot / channel 可以让不同事件绑定不同类型的处理器。
|
||||
- 进入 Runner Protocol 后,调用基数是 `one AgentBinding -> one run_id -> one runner`。这既适用于独立 Agent,也适用于 Pipeline AI Stage 的单次 runner 调用。
|
||||
- 一个 Agent 可以被多个 bot / channel 复用。如果 Agent 分支出现多个匹配 binding,BindingResolver 必须按明确规则选出一个或拒绝配置,不应默认 fan-out。
|
||||
- observer agent、多 runner fan-out、并行裁决、result 合并等能力需要单独设计 delivery、state、platform action 和 audit 语义,不属于当前 v1 契约。
|
||||
- `RunnerDescriptor.source` 只允许 `plugin`;Host 内置 adapter 不能作为 runner source 绕过插件/runtime/proxy 权限链。
|
||||
- `ctx.resources` 与 proxy action 校验必须来自同一个 run authorization snapshot;runtime handler 不应重新执行资源裁剪。
|
||||
- v1 不要求 Agent、Runner 插件实例或 runner id 全局串行。多个 bot / channel 可复用同一个 Agent;并发隔离依赖 `run_id`、binding、conversation / thread scope 和 Host authorization snapshot。
|
||||
- 外部 harness runner 当前是 MVP / dev path,证明协议可接入,不代表发布级安全边界或 Docker 生产可用性完成。
|
||||
|
||||
## 14. 开放问题
|
||||
|
||||
- `AgentBinding` 是否需要进入 SDK 文档作为只读诊断信息,还是完全 Host 内部。
|
||||
- State 与 Storage 的边界是否需要更强类型。
|
||||
- platform action 的审批模型如何表达。
|
||||
- Host 侧 scoped MCP / workspace projection 是否需要从 runner config 上移为一等 resource projection API。(skill 一项已收敛:skill 全 tool 化,作为被授权 tool 暴露,不再是独立 projection。)
|
||||
@@ -0,0 +1,156 @@
|
||||
# Runner 插件化文档入口
|
||||
|
||||
本文档是 agent-runner 插件化工作的路由页。具体设计拆到独立文档中维护,避免把 LangBot 宿主架构、SDK 协议、上下文管理、EBA 接入边界和官方 runner 迁移混在同一份 README 里。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
旧 runner 路径主要围绕 Pipeline / Query 和 `pkg/provider/runners` 内置实现展开,扩展外部 agent runtime 时容易把 runner 选择、上下文裁剪、资源授权和消息投递绑在同一条聊天链路里。这个分支要把 LangBot 收敛成 Agent Host:Host 负责事件、绑定、授权、事实源和结果投递;Runner 作为插件或外部 harness 消费统一协议并自主管理 prompt / history / memory。
|
||||
|
||||
## 文档维护原则(单一事实源)
|
||||
|
||||
- **协议数据结构(schema)唯一定义在 [PROTOCOL_V1.md](./PROTOCOL_V1.md)。** 其他文档不得重抄 schema,只能引用,例如"见 PROTOCOL_V1 §4.2"。
|
||||
- 当前实现状态、spec 差距与 runner 验收状态归 [STATUS.md](./STATUS.md);测试执行入口归 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md),安全发布门槛归 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md)。
|
||||
- Host 内部模型(`AgentEventEnvelope`、`AgentBinding`、Descriptor、各 Store)定义在 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md),不属于 SDK 协议。
|
||||
- 其余专题文档只讲"为什么/边界/怎么用",避免重复叙述。
|
||||
|
||||
## 本分支目标
|
||||
|
||||
**本分支目标:Runner 外化 / 插件化基础设施**
|
||||
|
||||
本分支只做 LangBot 作为 Agent Host 的基础能力建设,让现有 Pipeline 与用户新建的独立 `Agent` 都能调用插件化 Runner;不负责把两者做持久化迁移:
|
||||
|
||||
- LangBot 与 SDK 的稳定协议合同(Protocol v1)
|
||||
- Host-side `AgentEventEnvelope` / `AgentBinding` 模型
|
||||
- `run(event, binding)` event-first 入口
|
||||
- `QueryEntryAdapter`:Query → AgentEventEnvelope + AgentBinding
|
||||
- EventLog / Transcript / PersistentStateStore
|
||||
- History / Event / State pull APIs
|
||||
- Sandbox/workspace read/write/exec 文件能力,用于当前 run 的上传文件、工具大结果和临时产物
|
||||
- SDK runtime forwarding pull APIs + `caller_plugin_identity` 验证路径
|
||||
|
||||
## 当前已集成与后续扩展
|
||||
|
||||
截至 2026-09-05,`dev/4.11.x` 已合并 EBA 与 Runner 插件化。下面按当前代码划分实现边界:
|
||||
|
||||
- **已实现的平台事件路由**:`RuntimeBot` 负责事件转换后的 observer 广播、`event_bindings` 匹配和 Pipeline / Agent / discard 单目标分派;EventRouter 是逻辑职责,不是另一个独立服务。
|
||||
- **已实现的持久化与 UI**:独立 Agent、Bot 事件绑定、处理器工作台、Runner 市场安装、事件范围与工具权限、路由诊断和调试入口。
|
||||
- **后续的通用事件订阅与通知**:不把当前适配器回调和 Bot 路由理解成通用订阅产品。
|
||||
- **后续的 Scheduler / Background event source**:用户可配置的定时自动化和后台任务入口。
|
||||
- **完整 Agent Platform / daemon control plane**:Host-owned `AgentRun` / `AgentRunEvent`、run control primitives、最小 runtime heartbeat/claim lease 已作为 v2 foundation 落地;业务队列、Platform UI、daemon supervisor、runtime wakeup channel 和分布式 runtime 管控仍不属于 Protocol v1 主线。
|
||||
|
||||
平台事件、Agent 配置及 Pipeline AI Stage 已共同使用 host-side envelope/binding models 和 `run(event, binding)`。后续入口应复用这条链路。当前实现与发布验收分别见 [STATUS.md](./STATUS.md),平台动作授权见 [PLATFORM_ACTION_TOOLS.md](./PLATFORM_ACTION_TOOLS.md)。
|
||||
|
||||
本分支与外部 EBA / Agent Platform / Runtime Control Plane 的扩展边界见 [EXTENSION_SCOPE_MATRIX.md](./EXTENSION_SCOPE_MATRIX.md)。
|
||||
|
||||
## 目标产品模型
|
||||
|
||||
产品层同时保留 Pipeline 与独立 `Agent`。现有 Pipeline 保持原实体和执行链,也可继续被 bot 绑定;新建 Agent 则携带 runner id、runner config、resource/state/delivery policy 等配置,并可被多个 bot / IM channel 复用。统一产品列表可以聚合展示两类处理器,但不会把 Pipeline 或其内嵌 runner 配置自动迁移成 Agent;用户需要 Agent 时自行新增并绑定。
|
||||
|
||||
调度基数、Agent 复用、插件实例无状态、Pipeline adapter 和 fan-out 边界的规范来源是 [PROTOCOL_V1.md](./PROTOCOL_V1.md) §13;README 不复写这些约束。
|
||||
|
||||
## Pipeline 与 Runner 的关系
|
||||
|
||||
**Pipeline 与 Agent 是 EBA 中平级的处理器;`QueryEntryAdapter` 只适配 Pipeline 内部的 Runner 调用。**
|
||||
|
||||
EBA 先根据 `target_type` 选择 Pipeline 或 Agent。Pipeline 目标执行完整 Stage 链;当 Pipeline 的 AI Stage 调用 runner 时,`run_from_query()` 经 `QueryEntryAdapter` 把 `Query` 转换为 `AgentEventEnvelope` + `AgentBinding`,再委托到统一的 `run(event, binding, ...)`。Agent 目标则直接从自己的持久配置构造 binding。两条路径可以复用同一套 Runner Host capabilities,但 Pipeline 本身不会被投影或持久化为 Agent。
|
||||
|
||||
下一轮测试路径、状态定义和 smoke 记录见 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md)。
|
||||
|
||||
## 术语表
|
||||
|
||||
| 术语 | 含义 |
|
||||
| --- | --- |
|
||||
| Protocol v1 | Host 调用 Runner 的 runner 可见合同:discovery、`RunnerContext`、result stream、Host pull API 和错误模型。 |
|
||||
| Processor | EBA 的处理器上位概念;当前平级类型为 Pipeline 与 Agent。 |
|
||||
| Agent | 目标产品层配置对象,保存 runner id、runner config 和资源/状态/投递策略;不等于插件实例。 |
|
||||
| AgentConfig | Host 内部的单次 Runner 调用配置投影,可由 Pipeline AI Stage 或持久 Agent 生成;投影本身不会创建 Agent。 |
|
||||
| AgentBinding / binding | Host 在一次事件运行前解析出的有效绑定,决定调用哪个 runner 以及带什么策略。 |
|
||||
| envelope | Host 内部事件封装,即 `AgentEventEnvelope`;runner 看到的是由它投影出的 `ctx.event`。 |
|
||||
| descriptor / manifest | runner discovery 的能力和配置描述;manifest 来自插件,descriptor 是 Host 校验后的注册表视图。 |
|
||||
| EBA | Event Based Agent;平台事件路由已集成,定时任务等通用事件源仍是后续扩展。 |
|
||||
| harness runner | ACP、Claude Code、Codex 等已有自身 session / tool loop / MCP / 压缩机制的外部 runtime adapter。 |
|
||||
| projection | Host 把内部事实源、授权资源或配置裁剪成 runner / harness 可消费视图的过程。 |
|
||||
| Runtime Control Plane | v2 Host 能力层,当前已落地 Host-owned run/result ledger、run control primitives、最小 runtime heartbeat/claim lease;完整 daemon worker 管控、task wakeup 和 Agent Platform 产品形态不是 Protocol v1 主线。 |
|
||||
|
||||
## 设计文档
|
||||
|
||||
| 文档 | 关注点 |
|
||||
| --- | --- |
|
||||
| [PROTOCOL_V1.md](./PROTOCOL_V1.md) | **🔒 唯一 schema 事实源**。LangBot Host 与 SDK / Runtime / Runner 的协议合同:版本协商、discovery、run context、result stream、proxy actions、错误和 adapter 边界。 |
|
||||
| [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md) | LangBot 宿主能力与分层架构、Host 内部模型(`AgentEventEnvelope` / `AgentBinding` / Descriptor / 各 Store)、runner 发现、绑定、资源授权、状态、存储、生命周期和调用链。 |
|
||||
| [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) | Agent-owned context 方向:事件到来时 LangBot 传什么,agent 如何按需拉取更多历史 / state、如何访问 sandbox/workspace 文件,以及如何支持 KV cache 友好的上下文管理。 |
|
||||
| [EXTENSION_SCOPE_MATRIX.md](./EXTENSION_SCOPE_MATRIX.md) | Runner 外化与外部 EBA / Agent Platform / Runtime Control Plane 的扩展边界矩阵,说明哪些是本分支底座、哪些由外部分支接入。 |
|
||||
| [EVENT_BASED_AGENT.md](./EVENT_BASED_AGENT.md) | 已集成的事件路由、处理器分派、平台工具和结构化交互边界。 |
|
||||
| [eba-productization-release.md](./eba-productization-release.md) | EBA 适配器与 Runner 插件化合并后的产品化 / 发布计划,说明非技术用户快速上手差距、Bot 与处理器边界、未来 Solution 分发标的,以及多 namespace SaaS 支持要求。 |
|
||||
| [RUNTIME_CONTROL_PLANE_V2.md](./RUNTIME_CONTROL_PLANE_V2.md) | Agent Platform v2 / runtime 管控面决策:`AgentRun` / `AgentRunEvent` / run control 已作为 Host 事实源落地,最小 runtime heartbeat/claim lease 已落地;完整 runtime registry / daemon 管控仍是后续可选阶段。 |
|
||||
| [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md) | 官方 runner 插件迁移,包括 local-agent 和外部 runner。它是下游落地计划,不是 LangBot 基础能力设计的前置约束。 |
|
||||
| [RUN_STEERING_AND_CHECKPOINT.md](./RUN_STEERING_AND_CHECKPOINT.md) | 运行中消息注入(steering / follow-up)与压缩摘要持久化(compaction checkpoint)的设计与落地状态记录;schema 仍以 PROTOCOL_V1 为准。 |
|
||||
| [STATUS.md](./STATUS.md) | 当前实现状态、spec 与实现已知差距、runner 验收状态和历史高价值记录。 |
|
||||
| [PLATFORM_ACTION_TOOLS.md](./PLATFORM_ACTION_TOOLS.md) | 当前事件工具、平台动作和普通工具的配置、投射及执行授权。 |
|
||||
| [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md) | Runner QA 指南:保留最高价值测试路径,指导 agent 开展下一轮 WebUI / runner smoke 验证。 |
|
||||
| [SECURITY_HARDENING.md](./SECURITY_HARDENING.md) | 安全发布级 hardening 的后续发布门槛:路径隔离、权限边界、secret、资源配额、MCP / skill 投影和审计。 |
|
||||
|
||||
## 工作拆分
|
||||
|
||||
### 1. LangBot + SDK 基础设施
|
||||
|
||||
目标是把 LangBot 从内置 runner 执行器变成 agent host:
|
||||
|
||||
- LangBot 与 SDK 的稳定协议合同
|
||||
- runner manifest / descriptor / registry
|
||||
- Agent / binding 配置解析
|
||||
- run orchestration 和生命周期管理
|
||||
- resource authorization 与 `run_id` 级权限校验
|
||||
- host-owned state / storage / event log / transcript 能力
|
||||
- sandbox/workspace 文件 staging 与 read/write/exec 能力
|
||||
- SDK `Runner`、`RunnerContext`、`RunnerResult`、`RunnerAPIProxy`
|
||||
|
||||
协议合同详见 [PROTOCOL_V1.md](./PROTOCOL_V1.md)。
|
||||
|
||||
详见 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md)。
|
||||
|
||||
### 2. Agent-owned context
|
||||
|
||||
LangBot 不应成为最终 agentic context manager。它应提供事实源、默认上下文引用和按需读取 API;agent 或其背后的 runtime 负责历史剪裁、摘要、召回和 KV cache 策略。
|
||||
|
||||
Host 不定义通用历史窗口字段或策略;runner 通过 Host pull API 按需拉取历史并自行管理 working context。
|
||||
|
||||
详见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。
|
||||
|
||||
### 3. Event Based Agent(已集成)
|
||||
|
||||
消息只是事件的一种。当前平台适配器中的 `message.received`、`message.deleted`、`group.member_joined`、`friend.request_received` 等事件通过统一事件 envelope 进入处理器路由,具体支持范围以适配器能力声明为准。
|
||||
|
||||
EBA dispatch 的基数和 fan-out 边界仍以 PROTOCOL_V1 §13 为准;新增事件源复用以下入口点。
|
||||
|
||||
**当前共同使用的执行底座:**
|
||||
- event-first envelope (`AgentEventEnvelope`)
|
||||
- AgentBinding model
|
||||
- `run(event, binding)` 入口
|
||||
- QueryEntryAdapter(当前 AgentEventEnvelope / AgentBinding 的 Query entry adapter source)
|
||||
|
||||
详见 [EVENT_BASED_AGENT.md](./EVENT_BASED_AGENT.md)。
|
||||
|
||||
### 4. 官方 runner 插件
|
||||
|
||||
官方 `local-agent` 和外部 runner 迁移是下游工作。它们需要依附 LangBot 提供的宿主能力,但不应反过来决定宿主协议。
|
||||
|
||||
`local-agent` 可以外移,也可以重写。验收重点是它能完整消费 LangBot 的模型、工具、知识库、存储、事件、history API 和 result stream,而不是保留旧内置 runner 的内部结构。
|
||||
|
||||
详见 [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md)。
|
||||
|
||||
### 5. Runtime Control Plane v2(Foundation Partial)
|
||||
|
||||
当前 Runner v1 主线仍以 `event -> binding -> runner.run(ctx) -> result stream` 为 runner 可见合同。Host 侧已经新增持久 `AgentRun` / `AgentRunEvent`、result persistence、cancel/finalize/query 等通用 run control primitives,并提供受权限保护的最小 runtime register/heartbeat/list、claim/renew/release 和 reconcile 原语。
|
||||
|
||||
在这些 Host 能力之上,可以构建独立 agent 管控面插件;插件负责 UI、策略和编排体验,runtime/task 的事实源仍由 Host 持有。完整 daemon supervisor、任务唤醒/长轮询/WebSocket、跨 Host 分布式锁、provider 登录态诊断和产品化业务队列仍是后续工作。
|
||||
|
||||
详见 [RUNTIME_CONTROL_PLANE_V2.md](./RUNTIME_CONTROL_PLANE_V2.md)。
|
||||
|
||||
## 约束事实源
|
||||
|
||||
本分支已确认约束不在 README 重写:
|
||||
|
||||
- Runner 可见协议、result stream 和调度边界见 [PROTOCOL_V1.md](./PROTOCOL_V1.md)。
|
||||
- Host 内部 `AgentConfig` / `AgentBinding` 投影见 [HOST_SDK_INFRASTRUCTURE.md](./HOST_SDK_INFRASTRUCTURE.md)。
|
||||
- 外部 EBA / Agent Platform / Runtime Control Plane 接入边界见 [EXTENSION_SCOPE_MATRIX.md](./EXTENSION_SCOPE_MATRIX.md)。
|
||||
@@ -0,0 +1,544 @@
|
||||
# Agent Platform / Runtime Control Plane Decision Note
|
||||
|
||||
本文档记录 Runner 插件化之后,LangBot 如何继续演进成 Agent Platform 基础设施层。这里讨论的是 Host capability layer,不是 `Runner Protocol v2`,也不是把某个具体 Agent Platform 产品写进 LangBot core。
|
||||
|
||||
> 本文是当前决策版。协议数据结构仍以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) 为准;测试执行入口见 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md);扩展边界见 [EXTENSION_SCOPE_MATRIX.md](./EXTENSION_SCOPE_MATRIX.md)。
|
||||
>
|
||||
> 实现状态说明:本文描述的是 Runtime Control Plane v2 的目标能力和分阶段落地建议。当前 Runner 插件化主线已经具备 event-first context、run-scoped authorization、EventLog / Transcript / State / sandbox 文件等 Host capability,并已落地持久 `AgentRun` / `AgentRunEvent` ledger、run control actions、最小 runtime heartbeat/claim lease 和 admin reconcile 原语。完整 Agent Platform 产品形态、daemon supervisor、runtime wakeup channel 和分布式 runtime 管控仍未完成。当前实现状态以 [STATUS.md](./STATUS.md) 为准。
|
||||
|
||||
## 1. 当前决策
|
||||
|
||||
LangBot 后续定位应更像 **Agent Host / infrastructure provider / transfer layer**,而不是把某个完整 Agent Platform 产品固化进 core。
|
||||
|
||||
结论:
|
||||
|
||||
- **Agent Platform 产品形态做成插件**。插件负责 agent 管理、策略、业务队列、UI、编排、多 agent 协作和产品体验。
|
||||
- **Agent Platform 所需的基础事实源做进 Host**。当前 Host 已保存 event、state、transcript、sandbox 文件边界、active run 权限快照、持久 run/result ledger、审计关联和通用控制状态。
|
||||
- **最小 runtime registry / heartbeat / claim lease 已作为 Host 原语落地,但不等于完整 daemon worker 管控**。远程 harness / daemon 的进程托管、wakeup channel、provider 登录态诊断和分布式调度仍可以先由 Runner 插件和 SDK remote layer 自己维护。
|
||||
- **不把业务调度写进 Host**。Host 提供通用 run/result/control primitives,Platform 插件决定哪些事件触发哪些 agent、如何排队、如何分配、是否 fan-out。
|
||||
|
||||
推荐分层:
|
||||
|
||||
```text
|
||||
LangBot Host
|
||||
Current base: EventLog / runtime AgentBinding / State / Transcript / sandbox files / active run authorization
|
||||
Current v2 foundation: Run / RunEvent / audit / result persistence / control primitives / minimal runtime heartbeat and claim lease
|
||||
Current product: persisted Agent / Bot event_bindings / processor UI / event routing
|
||||
Planned: external harness daemon supervisor / wakeup channel / distributed runtime operations
|
||||
|
||||
Agent Platform plugin
|
||||
Agent management UI / project-task model / event routing policy
|
||||
Business queue / multi-agent orchestration / runtime selection policy
|
||||
|
||||
Runner plugin / external harness runtime
|
||||
Connects ACP / remote daemon / local subprocess / HTTP API
|
||||
Executes and converts provider-native events to RunnerResult
|
||||
```
|
||||
|
||||
## 2. Platform 与非 Platform 的区别
|
||||
|
||||
当前 LangBot 已经具备 Agent Host 的核心特征:
|
||||
|
||||
- 抹平不同 Runner。
|
||||
- 从 IM / Pipeline 入口触发 runner。
|
||||
- 有 event-first context 方向。
|
||||
- 有 Host-owned EventLog / Transcript / State 和 sandbox/workspace 文件边界。
|
||||
- 有 runner config 下发和 active run-scoped authorization。
|
||||
- 有 `run_id` 串联 event、transcript、state、sandbox 文件和内存授权上下文。
|
||||
|
||||
这还不是完整 Agent Platform。完整 Platform 至少还需要:
|
||||
|
||||
- 可管理的 agent 资产:agent profile、binding、resource policy、runner config、可用状态。
|
||||
- 可观察的执行生命周期:run status、result stream、失败原因、文件引用、审计、回放。
|
||||
- 可运营的控制面:取消、重试、排队、并发、超时、恢复、诊断。
|
||||
- 可产品化的调度体验:事件订阅、路由策略、任务板、多 agent 协作、项目/工作区视图。
|
||||
|
||||
因此,区别不只是“有没有调度”,而是是否具备:
|
||||
|
||||
```text
|
||||
managed agent assets + observable run lifecycle + operational run control
|
||||
```
|
||||
|
||||
Host 负责这些能力的通用事实源和安全边界;Platform 插件负责把它们组装成具体产品。
|
||||
|
||||
### 2.1 当前实现边界
|
||||
|
||||
当前代码中的 `run_id` 已经连接 active run 授权、持久 run ledger 和多个 Host 事实源:
|
||||
|
||||
- `EventLog` 保存输入事件和审计入口,并记录 `run_id` / `runner_id`。
|
||||
- `Transcript` 保存对话历史投影,并用 `run_id` 关联 assistant 输出。
|
||||
- Sandbox/workspace 保存当前运行输入文件和 runner 产物,并用 `run_id` 做访问边界的一部分。
|
||||
- `PersistentStateStore` 保存 runner state,但不等同于 run lifecycle。
|
||||
- `AgentRunSessionRegistry` 保存 active run 的内存态授权快照,用于 proxy action 校验;进程结束或 run 结束后不作为可回放事实源。
|
||||
- `AgentRun` 保存 run lifecycle、scope、authorization snapshot、queue/claim 状态、cancel intent、usage/cost 和 metadata。
|
||||
- `AgentRunEvent` 保存 runner/result/admin event stream,按 `run_id + sequence` 做可回放分页。
|
||||
- `AgentRuntime` 保存最小 runtime registry / heartbeat 事实,用于 runtime list、stale mark 和 claim lease reconcile。
|
||||
|
||||
因此本文后续提到的 `AgentRun` / `AgentRunEvent`、`run_append_result`、`run_finalize`、`run_cancel`、`runtime_register`、`runtime_heartbeat`、`run_claim` 等基础原语已经存在。2026-09-05 核对:独立 Agent 持久模型、Bot `event_bindings` 和处理器 UI 也已实现;`AgentBinding` 仍是单次运行投影,不是独立配置表。仍未完成的是独立 platform `run_create` action、业务队列产品形态、外部 harness daemon supervisor、runtime wakeup channel、跨 Host 分布式锁和 provider/runtime 诊断面。
|
||||
|
||||
SDK Plugin Runtime 已有 installation worker 的 supervisor、重启退避和 Runtime 重启协调器;这与这里规划的外部 Agent harness 进程托管不同。当前模型和状态以 [STATUS.md](./STATUS.md) 为准,本文后续阶段是能力拆分,不能作为尚未实现功能的清单。
|
||||
|
||||
## 3. 基础概念
|
||||
|
||||
### 3.1 Event
|
||||
|
||||
Event 表示“发生了什么”:
|
||||
|
||||
```text
|
||||
message.received
|
||||
github.issue.opened
|
||||
scheduler.tick
|
||||
user.approved
|
||||
system.webhook.received
|
||||
```
|
||||
|
||||
EBA 负责把外部输入标准化成 event。Event 本身不是 queue,也不等同于一次 agent 执行。当前 `EventLog` 记录的是输入事件和审计事实;未来 `AgentRunEvent` 记录的是某次 run 的输出事件流,二者不能混用。
|
||||
|
||||
### 3.2 Run
|
||||
|
||||
Run 表示“某个 agent / binding / runner 针对某个 event 的一次执行”。
|
||||
|
||||
Run 应由 Host 持久化,成为执行状态、结果、权限和审计的事实源:
|
||||
|
||||
```text
|
||||
run_id
|
||||
event_id
|
||||
agent_id / binding_id
|
||||
runner_id
|
||||
status
|
||||
created_at / started_at / finished_at
|
||||
error / failure_reason
|
||||
delivery target
|
||||
metadata
|
||||
```
|
||||
|
||||
当前 `AgentRunSessionRegistry` 只保存 active run 的内存态授权信息,不足以支撑 Platform 的回放、审计、取消、重试和异步执行。
|
||||
|
||||
### 3.3 RunEvent / RunResult
|
||||
|
||||
RunEvent 是一次 run 过程中产生的结果事件流,对应 runner 返回的 `RunnerResult`。它不同于 EBA/EventLog 的输入事件:
|
||||
|
||||
```text
|
||||
message.delta
|
||||
message.completed
|
||||
tool.call.started
|
||||
tool.call.completed
|
||||
state.updated
|
||||
action.requested
|
||||
run.completed
|
||||
run.failed
|
||||
```
|
||||
|
||||
Host 应保存这些输出事件,按 `run_id + sequence` 可回放。Transcript、State 可以由这些 result event 触发写入现有 store,并保留能回溯到 `AgentRunEvent` 的关联。文件和工具大结果留在当前 run 的 sandbox/workspace 中,不作为 result event blob 回传。
|
||||
|
||||
### 3.4 Queue
|
||||
|
||||
Queue 不是 EBA 的替代品。
|
||||
|
||||
EBA 负责产生 event;queue 负责处理“这个 event 对应的执行 work item 何时执行、谁来执行、如何取消/重试/恢复”。
|
||||
|
||||
队列可以分两层:
|
||||
|
||||
- **业务队列**:由 Platform 插件管理,例如项目任务、优先级、agent team、workflow、人工审批。
|
||||
- **执行队列 / run queue**:可选 Host 原语,例如 queued / running / completed / failed / cancelled、claim lease、dispatch timeout、orphan recovery。
|
||||
|
||||
第一阶段不要求 Host 内置完整执行队列。Platform 插件可以先管理业务队列;在 Phase 1 / Phase 2 能力落地前,插件仍只能通过现有 `AgentRunOrchestrator.run(...)` 同步执行路径和现有 Host stores 获得有限的 run 关联能力。
|
||||
|
||||
### 3.5 Runtime / Daemon
|
||||
|
||||
Runtime / daemon 表示执行位置或执行能力,例如某台机器上的 Claude Code / Codex CLI。
|
||||
|
||||
当前决策:
|
||||
|
||||
- Host 不在第一阶段维护完整 runtime registry。
|
||||
- Runner 插件可以通过 SDK remote layer 与 daemon 保持连接、心跳和执行通道。
|
||||
- 外部 harness / agent 不应直接访问 LangBot Host 或数据库。访问 LangBot 资源必须通过 daemon / Runner plugin / SDK runtime / `RunnerAPIProxy` / scoped MCP bridge,并接受 run-scoped authorization 校验。
|
||||
- 如果后续多个插件都需要共享 runtime 状态,再把薄的 `RuntimeLease` / registry 下沉为 Host 通用能力。
|
||||
|
||||
## 4. Host 应新增的最小能力
|
||||
|
||||
第一阶段最重要的不是 daemon registry,而是让 Host 成为 run/result 的事实源。
|
||||
|
||||
### 4.1 AgentRun Store
|
||||
|
||||
新增持久 `AgentRun`:
|
||||
|
||||
```text
|
||||
id / run_id
|
||||
event_id
|
||||
agent_id
|
||||
binding_id
|
||||
runner_id
|
||||
conversation_id / thread_id
|
||||
workspace_id / bot_id
|
||||
status
|
||||
status_reason
|
||||
created_at / started_at / finished_at / updated_at
|
||||
deadline_at
|
||||
cancel_requested_at
|
||||
usage_json
|
||||
cost_json
|
||||
metadata_json
|
||||
```
|
||||
|
||||
建议 status 至少包含:
|
||||
|
||||
```text
|
||||
created
|
||||
running
|
||||
completed
|
||||
failed
|
||||
cancelled
|
||||
timeout
|
||||
```
|
||||
|
||||
如果后续加执行队列,再引入:
|
||||
|
||||
```text
|
||||
queued
|
||||
claimed
|
||||
dispatching
|
||||
```
|
||||
|
||||
### 4.2 AgentRunEvent Store
|
||||
|
||||
新增持久 `AgentRunEvent`:
|
||||
|
||||
```text
|
||||
id
|
||||
run_id
|
||||
sequence
|
||||
type
|
||||
data_json
|
||||
usage_json
|
||||
created_at
|
||||
source
|
||||
metadata_json
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 同一 `run_id` 内 `sequence` 单调递增。
|
||||
- append 必须幂等,支持远程 daemon / plugin 重试。
|
||||
- 未知 result type 可保存但 Host 只对已知类型执行副作用。
|
||||
- 大 payload 仍应进入 sandbox/workspace,不直接塞入 result event。
|
||||
- `usage_json` 保存 `RunnerResult.usage` 原样结构;缺失表示 unknown,不等于 0。
|
||||
|
||||
### 4.3 Run Control API
|
||||
|
||||
Host 提供通用控制原语:
|
||||
|
||||
```text
|
||||
run.create
|
||||
run.get
|
||||
run.list
|
||||
run.events.page
|
||||
run.cancel
|
||||
run.append_result
|
||||
run.finalize
|
||||
```
|
||||
|
||||
语义:
|
||||
|
||||
- `run.create` 创建 Host-owned run 和授权快照。
|
||||
- `run.append_result` 只允许受信 SDK/runtime 路径调用,必须绑定 run 创建时固化的授权快照,写入 `AgentRunEvent` 并触发 transcript/state/delivery 副作用。
|
||||
- `run.finalize` 关闭 run,更新 terminal status。
|
||||
- `run.cancel` 设置取消意图;同步 runner 通过 context/deadline 感知,远程 runner 通过插件/daemon 通道感知。
|
||||
|
||||
第一阶段可以只暴露给插件 runtime action,不一定先做公开 HTTP API。
|
||||
|
||||
### 4.4 Result Persistence In Orchestrator
|
||||
|
||||
当前 `AgentRunOrchestrator.run()` 已经处理:
|
||||
|
||||
```text
|
||||
event -> binding -> context -> runner invocation -> result normalization
|
||||
```
|
||||
|
||||
需要补齐:
|
||||
|
||||
- run 开始时创建 `AgentRun`。
|
||||
- 每个 `RunnerResult` 进入 `AgentRunEvent`。
|
||||
- `run.completed` / 正常 generator 结束时标记 completed。
|
||||
- `run.failed` / exception / timeout 标记 failed 或 timeout。
|
||||
- terminal result 携带 usage 时,写入 `AgentRunEvent.usage_json` 并汇总到 `AgentRun.usage_json`。
|
||||
- `state.updated`、transcript 写入继续走现有 journal,但应与 `AgentRunEvent` 有可追踪关系。
|
||||
|
||||
### 4.5 Usage / Cost Accounting
|
||||
|
||||
SDK 侧 `RunnerResult` 已提供可选 `usage` 字段,用于把不同 runner / external harness / provider-native event 的 token usage 归一到同一个 run result envelope。
|
||||
|
||||
语义:
|
||||
|
||||
- `run.completed.usage` SHOULD 表示本次 run 的最终聚合 token usage。
|
||||
- `run.failed.usage` MAY 表示失败前已知的部分 token usage。
|
||||
- 没有 usage 表示 upstream runtime 没有报告或 adapter 暂未接入;Host 不得按 0 计费或按 0 判断上下文消耗。
|
||||
- Host 应把 event-level usage 原样写入 `AgentRunEvent.usage_json`,并在 terminal event 或 finalize 阶段汇总到 `AgentRun.usage_json`。
|
||||
- cost 应由 Host 根据 usage、runner/model identity、发生时间和价格表计算,写入 `AgentRun.cost_json`;runner/provider 上报的 cost 只能作为非权威 telemetry 保留在 metadata 或 usage extra 中。
|
||||
|
||||
这层约束先解决协议位置和持久化位置;具体 ACP、remote daemon、local subprocess runner 如何从 native event 中抽取 usage,可在各插件后续适配。
|
||||
|
||||
### 4.6 Authorization Snapshot
|
||||
|
||||
异步或远程执行时,run 创建时必须固化授权快照:
|
||||
|
||||
- runner identity
|
||||
- binding identity
|
||||
- caller plugin identity
|
||||
- resource policy
|
||||
- allowed tools/models/files/knowledge bases/storage scopes
|
||||
- state scopes
|
||||
- conversation/thread/workspace scope
|
||||
|
||||
后续 append result、state API、history API 和 sandbox/workspace 文件访问都以这个 snapshot 校验,不重新扩大权限。
|
||||
|
||||
## 5. SDK 侧应新增的最小能力
|
||||
|
||||
SDK 不需要马上定义完整 daemon registry,但需要让插件和 runner 使用 Host run/result 能力。
|
||||
|
||||
### 5.1 Entities
|
||||
|
||||
新增或补齐:
|
||||
|
||||
```text
|
||||
AgentRun
|
||||
AgentRunStatus
|
||||
AgentRunEvent
|
||||
RunEventPage
|
||||
RunCreateRequest / RunCreateResult
|
||||
RunAppendResultRequest
|
||||
```
|
||||
|
||||
这些是 Host control primitives,不替代 `RunnerContext` / `RunnerResult`。
|
||||
|
||||
### 5.2 Proxy Methods
|
||||
|
||||
在 SDK proxy 中提供:
|
||||
|
||||
```python
|
||||
create_run(...)
|
||||
get_run(run_id)
|
||||
list_runs(...)
|
||||
page_run_events(run_id, cursor=None, limit=...)
|
||||
cancel_run(run_id)
|
||||
append_run_result(run_id, result, sequence=None)
|
||||
finalize_run(run_id, status, error=None)
|
||||
```
|
||||
|
||||
访问边界:
|
||||
|
||||
- 普通 Runner 在同步 `run(ctx)` 内不一定需要直接调用这些 API;Host orchestrator 可自动记录。
|
||||
- Platform 插件可以创建/查询/取消 run。
|
||||
- Runner 插件或 daemon bridge 可以 append/finalize 自己负责的 run。
|
||||
- 外部 harness 仍不能直接调用 Host;必须经 SDK runtime / proxy / bridge。
|
||||
|
||||
### 5.3 Plugin-Daemon Heartbeat
|
||||
|
||||
远程 daemon 的初始心跳可以是 SDK / Runner plugin 私有能力:
|
||||
|
||||
```text
|
||||
daemon <-> Runner plugin / SDK remote layer <-> LangBot plugin runtime <-> Host
|
||||
```
|
||||
|
||||
Host 第一阶段只需要知道:
|
||||
|
||||
- 相关插件是否在线。
|
||||
- run 是否有 progress/result。
|
||||
- run 是否超时或取消。
|
||||
|
||||
如果后续需要跨插件共享 daemon 可用性,再把 heartbeat/registry 下沉为 Host 能力。
|
||||
|
||||
## 6. Platform 插件应负责什么
|
||||
|
||||
Agent Platform 插件可以负责:
|
||||
|
||||
- 管理哪些 agent 可用。
|
||||
- 维护产品层 agent profile、项目、任务板、workflow、team。
|
||||
- 订阅 EBA event,决定哪些 event 触发哪些 agent。
|
||||
- 维护业务 queue:优先级、重试策略、人工审批、分配规则。
|
||||
- 选择 runner / runtime / daemon。
|
||||
- 在 Run Control API 落地后,调用 Host run API 创建、取消、查询执行。
|
||||
- 展示 run status、result stream、文件引用、失败原因和审计。
|
||||
|
||||
Platform 插件不应负责:
|
||||
|
||||
- 在 Host Run Ledger 落地后,私有保存通用 run/result 事实源。
|
||||
- 绕过 Host 直接写 transcript/state 或越权访问 sandbox/workspace 文件。
|
||||
- 让外部 harness 直接访问 LangBot DB 或 Host 内部资源。
|
||||
- 把某个业务队列语义强塞进 Runner Protocol v1。
|
||||
|
||||
## 7. 与 EBA 的关系
|
||||
|
||||
EBA 做好后,事件流可以进入两种路径。
|
||||
|
||||
直接执行路径:
|
||||
|
||||
```text
|
||||
EventGateway
|
||||
-> EventRouter resolves AgentBinding
|
||||
-> AgentRunOrchestrator.run(event, binding)
|
||||
-> Host records AgentRun / AgentRunEvent (after Run Ledger lands)
|
||||
-> delivery
|
||||
```
|
||||
|
||||
Platform 插件编排路径:
|
||||
|
||||
```text
|
||||
EventGateway
|
||||
-> Platform plugin receives/subscribes event
|
||||
-> plugin applies policy / business queue
|
||||
-> plugin creates Host run (after Run Control API lands)
|
||||
-> runner/plugin/daemon executes
|
||||
-> Host records result and state
|
||||
-> plugin displays / Host delivers
|
||||
```
|
||||
|
||||
这两条路径最终应共享 Host run/result/state 事实源和 sandbox/workspace 文件边界。当前阶段可共享的是 event/transcript/state、sandbox 文件和同步执行链路;持久 run/result ledger 需要 Runtime Control Plane v2 Phase 1 补齐。区别在于是否有 Platform 插件参与产品化调度和业务队列。
|
||||
|
||||
## 8. 与 Runner Protocol v1 的关系
|
||||
|
||||
本设计不改变 v1 的 runner 可见合同:
|
||||
|
||||
```text
|
||||
RunnerContext -> Runner.run(ctx) -> RunnerResult stream
|
||||
```
|
||||
|
||||
必须保持:
|
||||
|
||||
- `RunnerContext` 不塞入 daemon/worker/pod 细节。
|
||||
- `RunnerResult` 仍是 runner 输出的统一事件流。
|
||||
- 普通 runner 不需要知道 task queue / runtime registry。
|
||||
- 远程 harness 可以自管 session、tool loop、MCP、上下文压缩,但访问 LangBot 资源必须通过 SDK proxy / bridge。
|
||||
- Runtime-managed execution 是 placement / transport 选择,不是普通 runner 协议的强制概念。
|
||||
|
||||
## 9. 分阶段实施建议
|
||||
|
||||
### Phase 1: Run Ledger(Foundation Implemented)
|
||||
|
||||
目标:Host 成为执行状态和结果事实源。
|
||||
|
||||
范围:
|
||||
|
||||
- `AgentRun` 表。
|
||||
- `AgentRunEvent` 表。
|
||||
- Orchestrator 自动创建/更新 run。
|
||||
- Journal 持久化每个 `RunnerResult`。
|
||||
- Run 查询和事件分页 API。
|
||||
- SDK entities + proxy 方法。
|
||||
|
||||
复杂度:中等。
|
||||
|
||||
预计改动:
|
||||
|
||||
```text
|
||||
Host: 12-20 个文件
|
||||
SDK: 4-8 个文件
|
||||
Tests: 8-15 个文件
|
||||
```
|
||||
|
||||
### Phase 2: Platform Plugin Queue On Host Run Primitives(Control Primitives Partially Implemented; Product Queue Pending)
|
||||
|
||||
目标:Platform 插件管理业务 queue,Host 提供 run/result/cancel 原语。
|
||||
|
||||
范围:
|
||||
|
||||
- `run.create`
|
||||
- `run.cancel`
|
||||
- `run.append_result`
|
||||
- `run.finalize`
|
||||
- result append 的 sequence/idempotency。
|
||||
- 受权限保护的远程 append/finalize。
|
||||
- Platform 插件可基于 Host run 构建任务板和调度体验。
|
||||
|
||||
复杂度:中等偏高。
|
||||
|
||||
预计改动:
|
||||
|
||||
```text
|
||||
Host: 20-35 个文件
|
||||
SDK: 8-14 个文件
|
||||
Tests: 15-25 个文件
|
||||
```
|
||||
|
||||
### Phase 3: Optional Host Execution Queue / Claim Lease(Claim Lease Primitive Implemented; Full Queue Pending)
|
||||
|
||||
目标:当多个插件重复实现 claim/cancel/retry/recovery 时,再下沉执行队列到 Host。
|
||||
|
||||
范围:
|
||||
|
||||
- `queued/running/completed/failed/cancelled` 状态机扩展。
|
||||
- `claim_run` / `lease_until`。
|
||||
- dispatch timeout。
|
||||
- retry / orphan recovery。
|
||||
- cancel propagation。
|
||||
- 并发 claim 防重。
|
||||
|
||||
复杂度:高。
|
||||
|
||||
预计改动:
|
||||
|
||||
```text
|
||||
Host: 35-55 个文件
|
||||
SDK: 12-20 个文件
|
||||
Tests: 25-40 个文件
|
||||
```
|
||||
|
||||
### Phase 4: Optional Runtime Registry(Minimal Registry Implemented; Full Daemon Control Pending)
|
||||
|
||||
目标:当 Host 需要统一管理多个 daemon / worker 时,再引入 runtime registry。
|
||||
|
||||
范围:
|
||||
|
||||
- runtime register / heartbeat / deregister。
|
||||
- capability report:provider、version、login status、workspace access、slot。
|
||||
- runtime online/offline。
|
||||
- runtime scoped auth。
|
||||
- runtime audit。
|
||||
- runtime gone recovery。
|
||||
- task wakeup / long polling / websocket。
|
||||
- 多 Host 实例下的 relay / distributed lock。
|
||||
|
||||
复杂度:很高。
|
||||
|
||||
预计改动:
|
||||
|
||||
```text
|
||||
Host: 55-80+ 个文件
|
||||
SDK: 18-30 个文件
|
||||
Tests: 40+ 个文件
|
||||
```
|
||||
|
||||
不建议现在直接进入此阶段。
|
||||
|
||||
## 10. 设计原则
|
||||
|
||||
- 先把 run/result 事实源做进 Host,再谈完整 runtime control plane。
|
||||
- Agent Platform 产品做插件;Host 做基础设施。
|
||||
- Host 不写业务调度策略,但要保存通用状态、结果、权限和审计。
|
||||
- EBA event 不是 queue;queue 是执行生命周期问题。
|
||||
- 业务 queue 可以先在 Platform 插件里;执行 queue 只有在复用需求明确后再下沉 Host。
|
||||
- Daemon registry 不应污染 Runner Protocol v1。
|
||||
- 外部 harness 不直接访问 LangBot Host 或 DB。
|
||||
- 所有 LangBot 资源访问必须走 SDK runtime / `RunnerAPIProxy` / scoped MCP bridge。
|
||||
- Docker / remote / local subprocess 只是 runtime placement,不是 runner 协议差异。
|
||||
|
||||
## 11. 非目标
|
||||
|
||||
当前阶段不做:
|
||||
|
||||
- 完整 Multica 式 runtime registry。
|
||||
- Host 内置项目管理、任务板、agent team、workflow 产品逻辑。
|
||||
- 把 daemon heartbeat / worker liveness 放进 `RunnerContext`。
|
||||
- 把业务 queue 定义为 Runner Protocol 字段。
|
||||
- 让 Platform 插件私有保存 run/result 事实源。
|
||||
- 让外部 agent/harness 直连 Host 内部资源。
|
||||
|
||||
## 12. 待定问题
|
||||
|
||||
- 已确认:Agent 与 Pipeline 都持久存在,并由 EBA 处理器 binding 指向其中之一;Pipeline 仅在 AI Stage 调用 runner 时投影一次性 `AgentBinding`,不会充当持久 Agent 的替代物。
|
||||
- Platform 插件创建 run 时,是否传完整 `AgentBinding` snapshot,还是引用 Host-owned binding id。
|
||||
- `AgentRunEvent` 与现有 `EventLog` / `Transcript` 的查询关系:直接 join,还是通过专门 view 聚合。
|
||||
- `run.append_result` 的认证粒度:runner plugin identity、run token、scoped capability token,或 SDK runtime 内部 channel。
|
||||
- 取消语义:同步 runner、external harness runtime/session 如何统一感知 cancel。
|
||||
- 何时把插件私有 daemon heartbeat 提升为 Host `RuntimeLease`。
|
||||
- 若未来 Host 做 claim lease,Platform 插件业务 queue 与 Host execution queue 如何避免双队列混乱。
|
||||
@@ -0,0 +1,154 @@
|
||||
# Run Steering 与 Compaction Checkpoint(Design Note)
|
||||
|
||||
本文档记录两项 Host/runner 协作能力:**运行中消息注入(steering / follow-up)**和
|
||||
**压缩摘要持久化(compaction checkpoint)**。两者来自官方 local-agent 对照
|
||||
Pi agent harness(`pi-mono/packages/agent`,下称 pi-agent-core)的差距分析:
|
||||
local-agent 已移植 Pi 的事件生命周期、并行工具语义、hook 扩展点和压缩预算模型,
|
||||
这两项需要 Host 协议、授权与 runner turn 边界协同才能闭环。
|
||||
|
||||
> 本文是设计备忘,不是 schema 事实源。涉及的数据结构最终落到
|
||||
> [PROTOCOL_V1.md](./PROTOCOL_V1.md);上下文边界语义以
|
||||
> [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) 为准;
|
||||
> run 持久化与控制原语以 [RUNTIME_CONTROL_PLANE_V2.md](./RUNTIME_CONTROL_PLANE_V2.md) 为准。
|
||||
|
||||
## 1. Run Steering / Follow-up(运行中消息注入)
|
||||
|
||||
### 1.1 问题
|
||||
|
||||
IM 场景下用户在 agent 运行中追加消息非常常见(补充信息、纠正方向、"算了别查了")。
|
||||
EBA 先按事件选择一个 Pipeline 或 Agent 处理器;进入 Runner 后,当前调用链是 `one AgentBinding -> one run_id -> one runner`
|
||||
(PROTOCOL_V1 §13):同会话的新消息要么等待当前 run 结束后触发新 run,
|
||||
要么并发触发独立 run。两种行为都无法把新消息送进**正在执行的 tool loop**,
|
||||
用户体验是"agent 自顾自跑完过期任务,然后才看到新消息"。
|
||||
|
||||
cancel(PROTOCOL_V1 §10)不解决这个问题:cancel 丢弃已完成的工作;
|
||||
steering 是在保留当前进度的前提下改变后续方向。
|
||||
|
||||
### 1.2 Pi 的参考语义
|
||||
|
||||
pi-agent-core 区分两个队列,注入时机都在 turn 边界,不打断进行中的模型流或工具执行:
|
||||
|
||||
- **steering**:运行中插入。当前 assistant 消息的全部 tool call 完成后、
|
||||
下一次模型调用前,注入排队的用户消息;模型在下一 turn 看到它们。
|
||||
- **follow-up**:排队后续工作。仅当没有 pending tool call 且没有 steering 消息、
|
||||
run 即将自然结束时检查;若有排队消息则注入并继续下一 turn,而不是结束 run。
|
||||
|
||||
两个队列各自支持 `one-at-a-time`(每次注入一条)和 `all`(一次注入全部)模式。
|
||||
|
||||
### 1.3 设计方向
|
||||
|
||||
职责划分遵循既有原则:Host 拥有事件路由和会话事实源,runner 拥有 turn 边界。
|
||||
|
||||
- **Host 侧**:BindingResolver / dispatch 层识别"同 conversation 存在 active run
|
||||
且 runner 声明支持 steering"的新消息事件,将其写入 run-scoped steering queue,
|
||||
并标记该事件已被在途 run 认领(不再触发新 run,避免破坏 §13 的基数约束)。
|
||||
事件仍照常进 EventLog / Transcript(事实源不变,改变的只是触发行为)。
|
||||
- **Runner 侧**:在 turn 边界(tool batch 完成后、下一次模型调用前,以及 run
|
||||
即将自然结束前)通过 run-scoped pull API 拉取 pending steering 输入,
|
||||
注入 working context。local-agent 的 `AgentLoopHooks.prepare_next_turn` /
|
||||
`should_stop_after_turn` 已预留了对应的注入点。
|
||||
- **能力协商**:runner manifest 声明 `steering` capability(参照 PROTOCOL_V1 §4.3);
|
||||
未声明的 runner 保持现状(新消息按现有规则另起 run)。
|
||||
- **回执**:被 steering 消费的事件通过 EventLog 审计。原始 `message.received`
|
||||
记录在 `metadata.steering` 标记 queued/absorbed 与 `claimed_by_run_id`;
|
||||
runner 成功 pull 后,Host 追加 `steering.injected` 记录并引用源事件。
|
||||
run 结束时仍未被 pull 的已 claim 输入,Host 追加 `steering.dropped` 记录作为
|
||||
dispatch 终态;原始 Transcript 事实不删除。
|
||||
Transcript 继续只表示会话事实,不扩展 dispatch 行为字段。
|
||||
|
||||
已落地的协议面(最终定义归 PROTOCOL_V1):
|
||||
|
||||
1. `ContextAccess.available_apis` 增加 steering pull 能力位。
|
||||
2. `RunnerAPIProxy` 增加 steering 拉取 action:默认 `mode=all`,Host 保序返回全部
|
||||
pending 输入;`one-at-a-time` 仅作为 runner 主动节流选项。
|
||||
3. dispatch 层的"认领"规则:`message.received` 可被同 conversation 的 active run
|
||||
吸收,原事件写 EventLog / Transcript,dispatch 行为写入 EventLog metadata。
|
||||
4. Host 对单 run steering queue 设置内存上限,队列满时不再 claim 新消息,消息回到
|
||||
正常 dispatch 路径,避免 active run 无限吞入同会话输入。
|
||||
|
||||
### 1.4 边界
|
||||
|
||||
- 不引入 Host 替 runner 做 prompt 拼接:Host 只递队列,注入位置和格式由 runner 决定。
|
||||
- 不与 observer / fan-out 混淆:steering 仍是单 run 内的输入补充,不产生第二个 runner。
|
||||
- 远程 / 外部 harness runner(claude-code、codex 等)若其底层 session 自带
|
||||
steering 能力,adapter 可以直接转发;协议面保持一致。
|
||||
|
||||
## 2. Compaction Checkpoint 持久化
|
||||
|
||||
### 2.1 问题
|
||||
|
||||
local-agent 当前是无状态 runner:每次 run 重新拉取 transcript 尾部
|
||||
(默认 50 条)、重新估算 token、重新生成压缩摘要。后果:
|
||||
|
||||
- 长会话中每 run 重复压缩计算,摘要每次重新生成,不同 run 之间措辞漂移,
|
||||
对 provider KV cache 不友好(AGENT_CONTEXT_PROTOCOL §"Summary checkpoint 稳定"
|
||||
已写明期望:只有压缩发生时才产生新 checkpoint)。
|
||||
- 历史一旦超过 fetch limit,更早的内容永久不可见——没有 checkpoint 记录
|
||||
"已压缩到哪里、压缩出了什么"。
|
||||
|
||||
pi-agent-core 把 compaction 条目持久化进 session tree:摘要带
|
||||
`tokensBefore` 和覆盖范围,后续 turn 直接复用,只在再次越过阈值时增量压缩。
|
||||
|
||||
### 2.2 现状盘点
|
||||
|
||||
协议面和主消费路径已具备:
|
||||
|
||||
- State / Storage API 已定义(PROTOCOL_V1 §8 "State / Storage"),
|
||||
且 AGENT_CONTEXT_PROTOCOL 已点名 `summary.checkpoint` 是 state 的预期用法。
|
||||
- Host 会根据 binding state policy 暴露 `ContextAccess.available_apis.state`。
|
||||
- local-agent 会在 state API 可用时读取/写入 `runner.compaction.checkpoint`;
|
||||
缺失、schema 不匹配、conversation 不匹配或游标失败时回退尾部历史拉取。
|
||||
- LLM 生成摘要**不依赖**本项 Host 能力——runner 用已授权的 `invoke_llm`
|
||||
即可生成;checkpoint 只解决"存下来、下次复用"。
|
||||
|
||||
### 2.3 设计方向
|
||||
|
||||
- **存放位置**:state,scope=`conversation`(小 JSON,符合 PROTOCOL_V1 §8
|
||||
对 state/storage 的边界建议)。若未来摘要膨胀,超出部分放 storage 并在
|
||||
state 中留引用。
|
||||
- **key 约定**:`runner.compaction.checkpoint`(runner 命名空间内)。
|
||||
- **内容约定**(schema 落 PROTOCOL_V1 或 runner 文档,此处只列语义):
|
||||
- `schema_version`
|
||||
- `summary`:压缩摘要文本(LLM 生成或确定性生成)
|
||||
- `covers_until`:已被摘要覆盖的 transcript 游标(seq / message id),
|
||||
是增量压缩和"从哪继续拉历史"的锚点
|
||||
- `tokens_before` / `created_at`:诊断与失效判断
|
||||
- **消费流程**:run 开始时读 checkpoint → 只拉取 `covers_until` 之后的
|
||||
transcript → 压缩触发时基于旧摘要增量生成新摘要、写回新 checkpoint。
|
||||
checkpoint 缺失或解析失败时回退到现行为(全量拉尾部),保证向后兼容。
|
||||
- **失效规则**:`covers_until` 在 Host transcript 中不存在(会话被清理 / 重置)
|
||||
即作废;runner 不得信任跨 conversation 的 checkpoint。
|
||||
- **授权**:Host 对声明需要 state 的 runner binding 开启
|
||||
`available_apis.state`;校验沿用现有 run-scoped state 校验
|
||||
(scope、key、value 大小、JSON 可序列化,见 PROTOCOL_V1 §7.2 对
|
||||
`state.updated` 的要求)。
|
||||
|
||||
### 2.4 相关但独立的工作
|
||||
|
||||
- **tokenizer / usage metadata 透传**:runner 目前用 chars/4 启发式估 token,
|
||||
对 CJK 偏低 3-4 倍,压缩触发系统性偏晚。Host 应在模型响应或
|
||||
`ctx.runtime.metadata` 透传 provider usage(prompt/completion tokens)与
|
||||
model context window(LiteLLM model-info 工作)。该项不阻塞 checkpoint
|
||||
落地,但决定压缩触发的准确性。
|
||||
|
||||
## 3. 实施拆分
|
||||
|
||||
| 项 | 归属 | 依赖 |
|
||||
| --- | --- | --- |
|
||||
| steering queue、事件认领、基础审计 | LangBot Host(dispatch / binding 层) | 已落地,含队列上限与未消费 dropped 终态 |
|
||||
| steering pull API + capability 位 | PROTOCOL_V1 + SDK proxy | 已落地 |
|
||||
| turn 边界拉取与注入 | langbot-local-agent | 已落地 |
|
||||
| local-agent 对 state API 的 checkpoint 读写 | langbot-local-agent | 已落地 |
|
||||
| checkpoint key / 内容 / 失效约定 | PROTOCOL_V1 + local-agent README | 已落地 |
|
||||
| LLM 压缩摘要生成 | langbot-local-agent | 已落地(`invoke_llm`,失败回退确定性摘要) |
|
||||
| usage / context-window metadata 透传 | LangBot Host(model 层) | LiteLLM model-info |
|
||||
|
||||
剩余工作应优先补 usage / context-window metadata。streaming delivery 衔接依赖
|
||||
`ctx.delivery` 编辑/追加语义,不建议在协议能力缺失时硬编码。
|
||||
|
||||
## 4. 开放问题
|
||||
|
||||
- streaming delivery 下 steering 注入后,前序 turn 已流出的内容与新 turn
|
||||
输出在 IM 消息编辑面的衔接(涉及 `ctx.delivery` 能力,待 delivery 演进定)。
|
||||
- checkpoint 是否需要 Host 侧主动失效通知(如会话清空时删除对应 state key)。
|
||||
当前实现靠 runner 读取时校验并回退,功能不阻塞。
|
||||
@@ -0,0 +1,211 @@
|
||||
# Runner Security Boundary
|
||||
|
||||
本文档记录 agent-runner 插件化后的安全边界和最小护栏。
|
||||
|
||||
## 状态
|
||||
|
||||
**当前结论:不采用高强度监管模型。**
|
||||
|
||||
LangBot 的目标不是托管一个强隔离、不可信 code runner 平台。Runner 插件,尤其是 ACP / Claude Code / Codex / OpenCode / Kimi Code 这类外部 harness,默认视为 **operator-owned execution**:用户或部署者显式配置并承担其文件系统、进程、网络、workspace、provider 登录态和 native tool 风险。
|
||||
|
||||
LangBot 需要负责的是保护 **LangBot 自己持有的资源**,包括模型、知识库、LangBot tools、history、event、state、plugin/workspace storage、sandbox/workspace 文件访问等。只要这些资源访问是 run-scoped、permission-scoped、可校验、可诊断的,当前阶段即可接受。
|
||||
|
||||
这意味着:
|
||||
|
||||
- 不要求 LangBot 在应用层实现完整 OS sandbox、VM、cgroup、seccomp、CPU / memory / network quota。
|
||||
- 不要求为 ACP runner 做复杂审批流;用户选择 ACP runner 即表示显式 opt-in。
|
||||
- 不要求在非 Docker 进程部署里做强监管;只要文档明确风险归属即可。
|
||||
- Docker / K8s 可以提供部署级隔离,但不是 LangBot agent-runner 协议发布的前置条件。
|
||||
- 不能宣传 LangBot 已经提供 managed sandbox;除非未来真的提供受管执行环境。
|
||||
|
||||
## 责任边界
|
||||
|
||||
### LangBot Host 负责
|
||||
|
||||
- **资源授权**:根据 runner manifest permissions、binding resource policy、run scope 生成本次 run 可访问的资源快照。
|
||||
- **运行期校验**:所有带 `run_id` 的 SDK / Host action 必须校验 active run session、caller plugin identity、resource id 和 operation。
|
||||
- **Scoped projection**:只把授权后的资源摘要、MCP server config、context、attachment/path ref、state snapshot 投影给 runner。
|
||||
- **LangBot 文件路径约束**:LangBot 自己 staged 和读取的文件必须限制在声明 root 内,防止 path escape。
|
||||
- **基础 secret 策略**:不要主动把 LangBot 持有的 API key / token / secret 投影给 runner;日志和错误里做常见 secret 字段脱敏。
|
||||
- **基础运行约束**:提供 timeout、取消传播、输出大小限制或错误映射的基础能力。
|
||||
- **audit-lite**:记录 event、run id、runner id、binding、资源授权摘要、关键失败、state/file/transcript 事实。
|
||||
|
||||
### Runner Plugin 负责
|
||||
|
||||
- 遵守 Host 下发的 `ctx.resources`、`ctx.context.available_apis`、runner config 和 state policy。
|
||||
- 把 LangBot 资源投影成目标平台可消费的形式,例如 MCP config、context prompt、HTTP header、run token。
|
||||
- 不绕过 SDK / Host action 直接访问 LangBot 内部资源。
|
||||
- 对自己启动的外部进程做合理封装,包括参数构造、timeout、取消、输出解析和错误映射。
|
||||
- 清楚记录自身 README 中的 provider 风险、部署假设和限制。
|
||||
|
||||
### 部署者 / 用户负责
|
||||
|
||||
- ACP / external harness 的 workspace 内容、文件系统访问、进程权限、网络访问、provider-native tool 权限。
|
||||
- Docker / K8s 的 image、volume、secret、network policy、resource limit、namespace、service account 配置。
|
||||
- 本机进程部署时的 OS 用户权限、PATH、HOME、CLI 登录态、全局配置和外部 MCP 配置。
|
||||
- 是否允许 runner 对某个目录执行真实写操作。
|
||||
|
||||
### 外部 Harness 负责
|
||||
|
||||
Claude Code、Codex、OpenCode、Kimi Code、Gemini CLI 等外部工具继续使用自己的权限模型、MCP 加载策略、session/resume、sandbox 或 approval 能力。LangBot 不承诺约束这些工具对其所在容器或宿主 OS 用户本来可访问资源的能力。
|
||||
|
||||
## 部署场景策略
|
||||
|
||||
| 场景 | LangBot 策略 | 不由 LangBot 承担 |
|
||||
| --- | --- | --- |
|
||||
| 普通进程部署 | 文档提示 operator-owned execution;Host 只保护 LangBot 资源。 | 阻止外部 CLI 读取同一 OS 用户可访问的文件、进程、HOME、全局 CLI 配置。 |
|
||||
| Docker / K8s 部署 | 继续使用相同 Host 资源边界;容器隔离由部署环境提供。 | 应用层重复实现容器/VM/cgroup/seccomp/network quota。 |
|
||||
| ACP runner | 用户显式选择 runner 和 workspace;LangBot 注入 scoped MCP / run token。 | ACP CLI native tools、workspace 写入、provider 登录态和外部 MCP 行为。 |
|
||||
| 外部 SaaS runner,例如 Dify | LangBot 通过 run token / gateway 限制 LangBot 资产访问。 | SaaS 平台内部 agent 执行策略、模型工具消息格式、平台侧日志。 |
|
||||
| 未来 managed runner | 只有当 LangBot 明确提供受管执行环境时,才需要单独定义强隔离 SLA。 | 当前协议闭环不承诺 managed sandbox。 |
|
||||
|
||||
## 最小护栏
|
||||
|
||||
以下是当前阶段需要维持的最小要求。它们是保护 LangBot 资源边界的要求,不是完整监管外部进程的要求。
|
||||
|
||||
### Resource Permission Boundary
|
||||
|
||||
每次 run 前必须冻结授权快照:
|
||||
|
||||
- runner manifest permissions 是资源访问上限。
|
||||
- binding resource policy / runner config 决定本次实际授权。
|
||||
- runtime action 按 `run_id` + `caller_plugin_identity` + resource id + operation 校验。
|
||||
- manifest permissions 只约束 LangBot 持有资源,不约束 external harness native tools。
|
||||
|
||||
当前实现方向是正确的:`AgentRunSessionRegistry` 保存 run-scoped snapshot,`plugin/handler.py` 对模型、工具、知识库、history、state、storage 等 action 做运行期校验,sandbox/workspace 文件访问由 scoped tool 边界控制。
|
||||
|
||||
**Skill 读写门控(不可弱化)**:pipeline-visible 的 skill 一次性以 `rw` 挂进同一 sandbox,mount 层不区分「可见」与「已激活」;写类 native 操作(write/edit/exec)只放行 activated skill,读类放行 visible + activated——这层区分等同资产授权语义,必须保留。skill 全 tool 化后尤其注意:「都是 tool」不等于「只控资产授权即可」,native 层的 visible/activated 门控不能砍。可弱化的只是 realpath 越界字符串检查(有 chroot/namespace 兜底)。
|
||||
|
||||
### MCP / Asset Gateway Boundary
|
||||
|
||||
LangBot MCP / asset gateway 只暴露当前 run 授权的工具面:
|
||||
|
||||
- `langbot_list_assets`
|
||||
- `langbot_get_current_event`
|
||||
- `langbot_history_page`
|
||||
- `langbot_retrieve_knowledge`
|
||||
- `langbot_get_tool_detail`
|
||||
- `langbot_call_tool`
|
||||
|
||||
外部平台需要使用短期 `run_token` 或 Authorization bearer token。token 缺失、错误或过期时必须拒绝访问。
|
||||
|
||||
不要求当前阶段实现 admin 级 MCP allowlist、dangerous tool approval 或复杂审批流。是否注册外部 MCP provider 是部署者/用户行为。
|
||||
|
||||
### Workspace / Path Boundary
|
||||
|
||||
LangBot 只需要约束自己管理的路径:
|
||||
|
||||
- Host staged 文件必须校验 `realpath` 和 root containment。
|
||||
- Attachment/file metadata 不应暴露 Host-only storage key / host path。
|
||||
- Context 文件、sandbox/workspace 文件如由 LangBot 创建,应放在可清理的位置。
|
||||
|
||||
用户配置给 ACP runner 的 workspace 不属于 LangBot 的强监管范围。Docker/K8s 下依赖 volume 挂载边界;普通进程部署下依赖 OS 用户权限和用户自担风险。
|
||||
|
||||
### Secret Handling
|
||||
|
||||
这里的 secret 指 API key、provider token、run token、MCP token、platform secret、数据库密码等。
|
||||
|
||||
当前阶段只要求基础策略:
|
||||
|
||||
- LangBot 不主动把自己持有的 secret 投影给 runner,除非这是 runner config 明确需要的外部服务凭据。
|
||||
- run token 是短期、run-scoped 的,不应长期保存。
|
||||
- 日志、错误、transcript、attachment/file metadata 尽量避免打印常见 secret 字段。
|
||||
- 配置 UI / API 返回时继续沿用现有 secret masking 规则。
|
||||
|
||||
不要求当前阶段实现完整 DLP、全链路敏感数据追踪、secret lineage 或自动轮换体系。
|
||||
|
||||
### Process / Runtime Bounds
|
||||
|
||||
LangBot 需要提供基本可控性:
|
||||
|
||||
- Host run deadline / runner timeout。
|
||||
- runner 侧请求 timeout。
|
||||
- generator close / cancel 传播。
|
||||
- 输出和 inline payload size 上限。
|
||||
- 错误映射为受控 runner failure。
|
||||
|
||||
不要求 LangBot 为外部 harness 实现 CPU、内存、磁盘、网络、进程树强隔离。需要这些能力时由 Docker/K8s、systemd、容器平台或用户机器策略提供。
|
||||
|
||||
### UI / Admin Surface
|
||||
|
||||
前端可以展示 runner 权限摘要,但它是信息披露,不是审批系统。
|
||||
|
||||
权限摘要指 runner manifest 声明的 LangBot 资源权限,例如:
|
||||
|
||||
- `tools.detail`
|
||||
- `tools.call`
|
||||
- `knowledge_bases.retrieve`
|
||||
- `history.page`
|
||||
- `storage.plugin`
|
||||
|
||||
当前阶段不要求强制弹窗、管理员审批、dangerous tool approval 或生产禁用开关。可以在 runner 配置区展示简短提示:此 runner 能访问哪些 LangBot 资源,外部 harness 执行风险由用户/部署者承担。
|
||||
|
||||
### Audit Lite
|
||||
|
||||
需要记录足够排查问题的事实:
|
||||
|
||||
- run id、runner id、binding、event。
|
||||
- 授权资源摘要。
|
||||
- state update、file write/read event、transcript message。
|
||||
- MCP / pull API 拒绝时的 warning。
|
||||
- steering queued / injected / dropped。
|
||||
|
||||
不要求当前阶段建立独立安全审计产品、审批记录系统或 SIEM 级事件模型。
|
||||
|
||||
## 降级后的检查表
|
||||
|
||||
| 项目 | 当前要求 | 状态判断 |
|
||||
| --- | --- | --- |
|
||||
| Path isolation | 只约束 LangBot 管理的 context/sandbox 文件路径;runner workspace 归用户/部署环境。 | Minimal required |
|
||||
| Permission boundary | 必须保护 LangBot 资源;不约束外部 CLI native 能力。 | Required |
|
||||
| Secret handling | 基础不投影、基础 masking、run token 短期化。 | Basic required |
|
||||
| MCP policy | run-scoped token + scoped tool surface;无复杂审批。 | Required |
|
||||
| Skill access policy | skill 通过 Host 授权 tool 暴露(发现 / activate / register / native exec 走统一 tool 授权);**native 层 visible(只读)vs activated(可写)门控不可弱化**——所有 pipeline-visible skill 以 `rw` 挂进同一 sandbox,读写区分全靠 native 层;harness-native skill 文件不作为 LangBot 安全边界。 | Required |
|
||||
| Process isolation | 由 Docker/K8s/用户机器负责。 | Out of scope |
|
||||
| State lifecycle | scope 隔离、JSON size limit、基础 cleanup primitive。 | Basic required |
|
||||
| Audit | 记录运行事实和拒绝原因。 | Audit-lite |
|
||||
| UI / Admin control | 权限摘要可展示;不要求审批流。 | Optional |
|
||||
| Test matrix | 覆盖 run auth、MCP token、permission deny、timeout、sandbox path、state size。 | Focused tests |
|
||||
|
||||
## 当前实现快照
|
||||
|
||||
截至 2026-06-15,已有实现覆盖:
|
||||
|
||||
- SDK typed Runner manifest、capabilities、permissions。
|
||||
- Host resource builder 按 manifest permissions 和 binding policy 生成 `ctx.resources`。
|
||||
- Active run session snapshot 和 `caller_plugin_identity` 校验。
|
||||
- History / event / state / tool / knowledge runtime action 的 run-scoped 校验。
|
||||
- Sandbox file path `realpath` + root containment。
|
||||
- Persistent state scope 隔离和 JSON size limit。
|
||||
- SDK-owned MCP bridge 和 long-lived asset gateway。
|
||||
- Dify / ACP runner 对 LangBot asset gateway 的接入。
|
||||
- Runner timeout、Dify HTTP timeout、ACP startup / initialize / request timeout。
|
||||
|
||||
仍可继续优化但不阻塞当前发布的事项:
|
||||
|
||||
- 前端展示 runner LangBot 资源权限摘要。
|
||||
- 常见 secret 字段 redaction 收敛成统一 helper。
|
||||
- Context/sandbox file TTL cleanup 调度。
|
||||
- 更完整的 MCP 调用 audit。
|
||||
- 更好的文档提示:ACP runner 是 operator-owned execution。
|
||||
|
||||
## 非目标
|
||||
|
||||
以下不属于当前 agent-runner pluginization 的安全目标:
|
||||
|
||||
- 防止 ACP / external harness 修改其 workspace。
|
||||
- 防止外部 CLI 读取同一容器或 OS 用户本来可读的文件。
|
||||
- 管控 external harness 的 provider-native tools、approval、MCP、browser、shell。
|
||||
- 在 LangBot 应用层实现 VM / container / cgroup / seccomp / network policy。
|
||||
- 为 Docker/K8s 部署替代平台自身的 secret、volume、network、resource limit 管理。
|
||||
- 实现企业级审批系统、SIEM、DLP 或安全运营面板。
|
||||
|
||||
## 发布口径
|
||||
|
||||
可以对外说明:
|
||||
|
||||
> Runner 插件通过 run-scoped authorization 和 scoped MCP gateway 保护 LangBot 持有资源。外部 code harness 的执行环境由用户或部署平台负责隔离;LangBot 当前不提供 managed sandbox。
|
||||
|
||||
不能对外说明:
|
||||
|
||||
> LangBot 已经安全沙箱化 Claude Code / Codex / OpenCode 等外部 runner。
|
||||
@@ -0,0 +1,87 @@
|
||||
# Runner Pluginization Status
|
||||
|
||||
本文档是 `dev-docs/agent-runner-pluginization/` 的状态事实源。协议 schema 仍以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) 为准;测试步骤以 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md) 为准;安全发布门槛以 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md) 为准。
|
||||
|
||||
状态快照日期:2026-09-05。代码基线为 LangBot `dev/4.11.x` / `a4d36aa2d` 和 SDK `dev/4.11.x` / `f1da058`;本文是本地检视快照,不代表远端最新状态或正式发布批准。
|
||||
|
||||
## 当前版本与验收边界
|
||||
|
||||
- Core 声明版本 `4.11.0`,`pyproject.toml` / `uv.lock` 仍依赖 `langbot-plugin==0.5.3`;SDK 源码声明版本 `0.5.5`。本机 Core 实际导入旁边 SDK 的可编辑源码,不能用这个组合的测试通过证明 registry 安装可复现。
|
||||
- 检视时 SDK 有四个未提交文件:`api/agent_tools/asset_gateway.py`、`api/agent_tools/external_tools.py`、`api/entities/builtin/runner/resources.py`(均位于 `src/langbot_plugin/`),以及 `tests/api/test_agent_tools_mcp_bridge.py`。这些改动增加 `platform_tools` 分类发现、可选 schema 和 gateway 引导。下面的 SDK 测试包含这些工作区改动;正式配套版本尚需冻结。
|
||||
- 本轮仅重新执行下列定向测试与 TypeScript 检查,没有重跑全量 backend、真实平台、provider、浏览器 E2E 或 Cloud 部署门禁。后文旧日期的成功记录仍是历史证据。
|
||||
|
||||
| 2026-09-05 验证 | 结果 |
|
||||
| --- | --- |
|
||||
| Core:`tests/unit_tests/agent`、`tests/unit_tests/api/service/test_agent_service.py`、`tests/unit_tests/platform/test_routing_rules.py`、`tests/unit_tests/api/service/test_maintenance_service.py` | 561 passed,74 warnings |
|
||||
| SDK:`tests/api/entities/builtin/runner`、`tests/api/proxies`、`tests/api/test_agent_tools_mcp_bridge.py`、`tests/runtime/plugin/test_mgr_runner.py`、`tests/runtime/plugin/test_dependency_environment.py`、`tests/runtime/plugin/test_restart_coordinator.py` | 362 passed,10 warnings |
|
||||
| Web:`pnpm exec tsc --noEmit` | pass |
|
||||
| Web:`pnpm test:unit` | 62 passed,2 failed |
|
||||
|
||||
前端失败为源码形状断言:`oss-cloud-ui-privacy.test.mjs` 仍要求 fieldset 的精确旧 class;`processor-detail-workbench.test.mjs` 仍要求 Agent 的旧四分区。当前表单已增加布局 class,并使用运行器、运行器配置、事件与工具分区。发布前需按当前设计更新断言或修正实现,不能记为全绿。
|
||||
|
||||
## 近期已集成内容
|
||||
|
||||
- **EBA 与独立 Agent**:Bot 事件绑定、observer 广播、Pipeline / Agent / discard 单目标分派、Agent CRUD、处理器工作台与路由诊断已在同一分支。逻辑路由器实现在 `pkg/platform/botmgr.py::RuntimeBot`,不是外部独立 EventRouter 服务。
|
||||
- **事件感知工具权限**:Core 9 月 4 日提交已实现 `event_*` 自动事件工具、`allowed_platform_tools` 和 `allowed_tools`。Host 冻结事件目标,结合 Runner 权限、适配器能力和运行快照授权,执行时再次检查。详见 [PLATFORM_ACTION_TOOLS.md](./PLATFORM_ACTION_TOOLS.md)。SDK 分类发现的提交状态见上文。
|
||||
- **产品流程**:Runner 市场内联安装、安装恢复和健康状态、事件范围选择、统一详情工作台、Agent 调试及 Bot 平台事件调试已实装。Agent 自身已无 enabled 开关;Bot 路由仍有 enabled。具体页面形态见 [处理器页面](../event-based-agents/08-agent-page-and-event-orchestration.md)。
|
||||
- **Runtime 与存储**:SDK 已加入 artifact 对应的独立依赖环境、Windows worker 连接路径、安装期间响应性测试;Core/Plugin Runtime/Box 分别报告拥有的存储目录。存储分析是观测,不提供硬配额。
|
||||
- **工作空间**:OSS 单工作空间多人协作和资源作用域已合入。Cloud 隔离基础与生产激活分开验收,见 [Cloud 剩余事项](../multi-tenant/cloud-v2-pending-verification.md)。
|
||||
|
||||
## 实现状态
|
||||
|
||||
| 领域 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| SDK manifest schema | Done | `RunnerManifest` 包含 typed `capabilities` / `permissions`;未知 capability / permission key 禁止进入 typed model。 |
|
||||
| Runner discovery | Done | Runtime 返回 typed manifest;Host registry 校验单个 runner,失败 warning + skip,不影响其它 runner。 |
|
||||
| Host resource authorization | Done | `ctx.resources` 和 `ctx.context.available_apis` 由 manifest permissions 与 binding policy / run scope 求交后生成。 |
|
||||
| Run authorization snapshot | Done | active run session 冻结 run-scoped resources 与 available APIs;runtime handler 按 snapshot 校验 pull API。 |
|
||||
| Result payload validation | Done | Wire 保持 `{type, data}`;Host 对投递/副作用类 payload 严格校验,tool-call telemetry 宽松,未知 type 忽略并 warning。 |
|
||||
| Old built-in runners | Done | 旧 `src/langbot/pkg/provider/runners/*` 与 `RequestRunner` 路径已从本分支删除。 |
|
||||
| Official runner manifests | Done | `local-agent`、ACP / Claude Code / Codex 外部 harness runner、外部服务 runner 已重新声明真实生效的 LangBot resource permissions。 |
|
||||
| Skill 链路 | Unit-pass; WebUI E2E pass | 已按 **skill 全 tool 化** 收敛:发现走 `list_skills` / `langbot_list_assets` 和 skill resources;`activate` / `register_skill` 走统一 tool 授权;`skill_authoring` capability 降级为便捷开关。`activate` 会 best-effort 写入 conversation-scope `host.activated_skills`,后续 run 通过当前 pipeline-visible skill cache 恢复。新注册 Skill 在当前 Query 内立即获得临时可见性;Docker `exec` 产生的宿主侧不可写文件由 `write` / `edit` 回退到 Box 执行。2026-07-15 真实 LocalAgent Debug Chat 已完成创建、注册、同 Query 激活、编辑和执行闭环;非流式 runner turn 只向下游 Pipeline 产出一次,工具中间结果不再拆成额外 Bot 气泡。 |
|
||||
| Runtime Control Plane v2 foundation | Partial | Host-owned `AgentRun` / `AgentRunEvent` ledger、orchestrator 自动建账、result event persistence、run get/list/event page/cancel/append/finalize actions 已落地;`agent_run:admin` / `runtime:admin` 控制权限、最小 runtime register/heartbeat/list/reconcile 和 run claim/renew/release 原语已落地。完整 Agent Platform 产品形态、daemon supervisor、任务唤醒/长轮询/WebSocket、分布式 runtime 管控仍未完成。 |
|
||||
| Security boundary | Done | 当前口径降级为轻量边界:LangBot 保护自身持有资源;external harness 的 OS / process / network / workspace 风险由用户或部署环境承担;managed sandbox 不是当前承诺。 |
|
||||
| Steering control path | Done | claim 异常不再逃逸 consumer loop;queue 有上限;未 pull 的 claimed 输入在 run 结束时写 `steering.dropped` 审计终态。 |
|
||||
| SDK v1 contract closure | Done | SDK 提供 `AgentAPIError` / `AgentAPIException`、typed `SteeringPullResult`、未知 result type 宽容解析、result `sequence` 注入与取消传播。 |
|
||||
| EBA processor routing | Done; release gate 5/5 pass | Bot `event_bindings`、Pipeline / Agent 平级路由、WebUI dry-run / 合成测试 / 状态、OneBot 非消息事件到 Agent 及平台回复已闭环;隔离空白实例已验证从 Space 安装并注册 LocalAgent。 |
|
||||
| Structured interactions | Cross-repo unit-pass; provider E2E pending | Host 已完成 `interaction.requested` 白名单、持久化 callback correlation、TTL/作用域/幂等校验和 Pipeline/Agent 原处理器恢复;六个平台已接入按钮/单选投递,Lark 和 DingTalk 进一步支持原生单字段 `text` / `textarea` / `number` / `select` 控件。SDK typed contract、通用 Runner 脚手架和 DifyAgent `workflow_paused` plugin-storage continuation 已落入对应仓库。 |
|
||||
|
||||
## Spec 与实现已知差距
|
||||
|
||||
- `action.requested` 是严格白名单协议面:当前只执行 `interaction.requested`;其它 action 仍只记录 telemetry。平台语义动作通过已授权的 `event_*` / `platform_*` 工具执行,不通过任意 result action 或原始 `call_platform_api`。
|
||||
- 结构化交互 SDK typed contract 与 DifyAgent continuation 已实现;SDK 正式发布、真实 Dify 凭据 E2E,以及需要长驻双向进程的 Claude Code 权限确认仍是后续验收项。Host 不持有 provider 私有 token。
|
||||
- State 与 storage 的长期类型边界仍可继续收窄;当前合同只要求 JSON-safe state 与受控 storage API。
|
||||
- `ToolResource.parameters` 已作为 best-effort full schema 由 Host 在构造 `ctx.resources` 时一次塞齐;无 schema 时 runner 仍需兼容 `parameters=None` 或按需调用 detail API。
|
||||
- EventLog / Transcript 已提供显式 cleanup primitive;长期 retention 默认值、TTL 调度接入和 sandbox/workspace 文件清理仍是运维收尾项,应在 Runtime Control Plane 产品化前补齐。
|
||||
- External harness 的 native shell / filesystem / CLI / MCP 权限不受 manifest permissions 约束;manifest permissions 只约束 LangBot 持有的资源访问。
|
||||
- LangBot 当前不承诺 managed sandbox;external harness 的 OS/process/network quota、workspace GC、provider-native tool 权限由用户或部署环境承担。
|
||||
- Runtime Control Plane v2 已有 Host 事实源和控制原语,独立 Agent 与处理器 UI 也已存在;仍缺业务任务队列、外部 harness daemon 托管、wakeup channel、跨 Host 分布式锁及 provider 登录态诊断。SDK installation worker 的 supervisor/重启协调已实现,不能与外部 harness 管控混淆。
|
||||
|
||||
## Runner 历史验收记录(未在本轮重跑)
|
||||
|
||||
| Runner | 状态 | 最近证据 |
|
||||
| --- | --- | --- |
|
||||
| `plugin:langbot-team/LocalAgent/default` | Unit-pass; Marketplace UI pass; Debug Chat E2E pass | 2026-07-12 隔离 first-run 实例从真实 Runner catalog 安装 `langbot-team/LocalAgent` 0.1.0,Host 注册 `plugin:langbot-team/LocalAgent/default`,Wizard 自动选中并解锁后续操作。2026-07-15 `2026-07-15-08-44-10-770-08-00-sandbox-skill-authoring-edit-existing-e2e` 使用真实 `gpt-5.5` 完成 Skill 创建、注册、同 Query 激活、已激活包编辑与脚本执行;三阶段 UI、浏览器诊断和结构化文件系统检查全部通过,每阶段恰好新增一个 Bot 气泡,p95 14.6 秒、错误率 0。 |
|
||||
| `plugin:langbot-team/ACPRunner/default` | Unit-pass; Debug Chat E2E pass | 2026-07-15 从本地 0.1.4 发布包安装并注册 PascalCase runner,remote-ssh Claude ACP 通过反向隧道调用 run-scoped `langbot_get_current_event`,97.8 秒返回可见结果;Host 将增量 delta 和 `message.completed` 聚合为一个完整 Bot 气泡。 |
|
||||
| `plugin:langbot-team/ClaudeCodeAgent/default` / `plugin:langbot-team/CodexAgent/default` | Unit-pass; E2E pending | 通过 runner 仓库单测覆盖 session、run_id 注入和 LangBot MCP gateway;真实 harness E2E 取决于对应运行环境、CLI/daemon 可用性和 provider 登录态。 |
|
||||
| Dify | Human-input unit-pass; credential E2E pending | `langbot-agent-runner/dify-agent` 已实现 `workflow_paused`、原子字段/确认交互、plugin-storage continuation、Dify submit/events 恢复与再次暂停;真实 Dify 凭据 E2E 待执行。 |
|
||||
| n8n / Coze / DashScope / Langflow / Tbox / DeerFlow / WeKnora | Unit-pass; credential smoke optional | 2026-06-13 plugin layout / parser tests 通过;真实服务凭据 smoke 非每轮必跑。 |
|
||||
|
||||
## Host / SDK 历史验收记录
|
||||
|
||||
| 范围 | 状态 | 最近证据 |
|
||||
| --- | --- | --- |
|
||||
| LangBot Runtime Control Plane v2 foundation | Unit-pass; EBA release gate 5/5 pass; Runner preflight pass | 2026-07-12 `eba-functional-20260712-release-gate-rerun` 通过 Quick Start 场景筛选、隔离实例 Runner Marketplace 安装、Runner 健康状态、事件路由 dry-run / 合成派发,以及真实 OneBot `group.member_joined` → Agent → `send_group_msg` 链路。2026-07-15 Runner release preflight 16 项通过、0 warning;fixture contract、5 类 behavior matrix、ledger schema / async DB readiness / 100-run stress / 120-run 8-worker contention / claim-lease-auth concurrency、SDK runtime chaos 探针全部通过。 |
|
||||
| Host Skill / native tool integration | Unit-pass; WebUI E2E pass | 2026-07-15 provider / native / Skill / monitoring 定向测试 67 项通过,Pipeline / Chat / Wrapper 定向测试 61 项通过,Skills CLI 105 项通过;真实 Debug Chat 验证 `register_skill` 后同 Query `activate` 成功,监控工具调用不再把 SQL 行误取为字符串,结构化 JSON 文件检查不依赖格式空格,非流式多阶段 runner 结果只生成一个最终 Bot 气泡。 |
|
||||
| SDK Runner control entities / proxy | Unit-pass | 2026-06-23 SDK `tests/api/entities/builtin/runner`、`tests/api/proxies`、`tests/api/test_agent_tools_mcp_bridge.py`、`tests/runtime/plugin/test_mgr_runner.py`、`tests/runtime/test_pull_api_handlers.py`、`tests/runtime/io/handlers/test_plugin_handler.py`、EBA event entities 和 message tests 通过,覆盖 typed entities、RunnerAPIProxy、MCP bridge、runtime manager 与 pull API handlers。 |
|
||||
|
||||
## 历史高价值记录
|
||||
|
||||
历史报告已合并为本状态页和 QA 指南,不再保留单独进度文档。后续若需要追溯,优先查看 `langbot-skills/reports/` 下的原始执行报告。
|
||||
|
||||
截至 2026-05-29,已有本地 smoke 证明:
|
||||
|
||||
- `local-agent` 可以通过 Pipeline Debug Chat 走插件化 `AgentRunOrchestrator` 主链路。
|
||||
- 外部 harness runner 可以通过同一条 `run(event, binding)` 路径执行;当前官方实现已收敛到 ACP / Claude Code / Codex 等直接 runner 插件。
|
||||
|
||||
这些记录只证明本地协议闭环可用,不代表 LangBot 提供 managed sandbox 或 external harness OS 级隔离。
|
||||
@@ -0,0 +1,66 @@
|
||||
# EBA 产品化与发布计划
|
||||
|
||||
更新:2026-09-05,适用于合并后的 `dev/4.11.x`。本文维护产品边界和剩余交付顺序;具体实现、提交与验证结果统一记录在 [STATUS.md](./STATUS.md)。原 2026-07-01 草案中的功能缺口和 Phase 编号不再作为当前排期。
|
||||
|
||||
## 产品模型
|
||||
|
||||
- **Bot**:平台连接、凭据和事件路由入口。用户在机器人上决定“发生什么时,使用哪个处理器”。
|
||||
- **Processor**:可复用处理逻辑的上位概念,当前类型为 Agent 与 Pipeline。
|
||||
- **Pipeline**:保留完整 Stage 链的消息处理器,提供预处理、AI、后处理、扩展和输出控制。
|
||||
- **Agent**:独立配置对象,选择 Runner 插件并配置事件范围、运行器与工具权限,可被多个 Bot 引用。
|
||||
- **Workflow**:后续编排方向,当前尚无完整执行产品。
|
||||
- **Solution**:后续分发单元,包含处理器、路由模板、依赖、变量和文档。
|
||||
|
||||
处理器入口聚合 Agent 与 Pipeline,不转换实体,不复制旧 Pipeline runner 配置生成 Agent。EBA 是内部术语,主要产品流程使用机器人、事件、处理器、流水线和工具等名称。
|
||||
|
||||
## 已完成的产品化基础
|
||||
|
||||
| 用户流程 | 当前实现 |
|
||||
| --- | --- |
|
||||
| 创建处理器 | Agent/Pipeline 类型选择、独立配置与统一详情工作台 |
|
||||
| 选择运行器 | 已安装 Runner 动态 metadata、市场安装入口、安装进度/恢复和可用性反馈 |
|
||||
| 配置 Agent | 运行器、运行器配置、事件与工具;基础信息在详情入口编辑 |
|
||||
| 配置事件 | 分组事件选择、兼容目标过滤、路由排序、冲突提示和兜底匹配说明 |
|
||||
| 验证行为 | 路由 dry-run、Agent 调试、Bot 配置中的平台事件调试;合成派发仍需遵循抑制真实出站的后端边界 |
|
||||
| 理解故障 | 路由匹配/失败轨迹、Runner 状态、模型测试与调试错误反馈 |
|
||||
| 首次使用 | 场景引导与插件化 Runner 安装流程 |
|
||||
| 工具权限 | 自动事件工具、显式平台动作、普通工具白名单,运行时再次授权 |
|
||||
| 存储诊断 | Core、Plugin Runtime、Box 分别报告拥有的目录与不可用状态 |
|
||||
|
||||
这张表表示代码已实现,不代表所有平台/provider 和首次安装组合均完成当前版本真实验收。页面契约见 [处理器与事件编排](../event-based-agents/08-agent-page-and-event-orchestration.md),动作授权见 [平台工具](./PLATFORM_ACTION_TOOLS.md)。
|
||||
|
||||
## 4.11 发布收尾
|
||||
|
||||
| 顺序 | 工作 | 完成条件 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 冻结跨仓库依赖 | 提交/确认 SDK 平台工具发现改动,固定配套 SDK 和 Runner 包,更新 Core 声明及 lock;验证 registry 或精确 commit 的干净安装 |
|
||||
| 2 | 收敛自动化 | 按当前页面设计处理前端旧断言,执行 backend/SDK 定向测试、前端单测、类型检查及发布所需 lint/build/E2E |
|
||||
| 3 | 验证用户路径 | 空白实例安装 Runner、创建 Agent/Pipeline、连接 Bot、保存路由、消息/非消息执行、工具允许与拒绝、错误诊断 |
|
||||
| 4 | 补真实交互与平台证据 | 当前版本的选定平台媒体/回调、Dify continuation、外部 harness;记录支持、未支持、阻塞及未执行 |
|
||||
| 5 | 完成生命周期 | EventLog/Transcript retention 调度、状态/文件清理、取消和重启后的行为有明确策略与测试 |
|
||||
|
||||
验收记录必须写明 Core/SDK/Runner 版本、操作系统、Runtime 连接方式、Box backend 和是否使用 editable 源码。真实平台测试、合成事件与 mock provider 测试分别记账。过去的成功报告不能自动作为当前 HEAD 的通过记录。
|
||||
|
||||
## Cloud 独立交付边界
|
||||
|
||||
当前 OSS 支持单 Workspace 多成员及固定 RBAC;Cloud 目录和计费归控制面,Core 负责资源作用域与执行边界。不能用 edition 字段或普通配置开启 OSS 多 Workspace。
|
||||
|
||||
当前业务隔离单位是 Workspace 与 execution generation;不要求依据旧草案额外创建 Tenant/Workspace/Namespace 三层同构表。目录、安装绑定、存储与执行作用域以 [多租户架构](../multi-tenant/workspace-multi-user-architecture.md) 为准。
|
||||
|
||||
生产激活仍需关闭:
|
||||
|
||||
- 插件和可配置出站目标的网络/SSRF 策略。
|
||||
- Plugin installation 与 Box 各存储面真正的 byte/inode 硬配额。
|
||||
- 普通业务写入贯穿提交的 generation fence、业务 outbox 和持久对象引用切换保障。
|
||||
- 最终 Linux/cgroup、持久卷、数据库权限、容量、故障恢复与 24 小时 soak 验收。
|
||||
|
||||
完整清单见 [Cloud 剩余验证](../multi-tenant/cloud-v2-pending-verification.md)。存储统计、局部压力测试或 OSS 功能验收不能替代这些门禁。
|
||||
|
||||
## 后续产品方向
|
||||
|
||||
- **完整 Agent 管控面**:业务任务队列、唤醒、外部 harness daemon 管理、provider 登录态诊断和分布式执行。已有 run ledger/heartbeat/claim 是底座。
|
||||
- **Workflow 与多 Agent**:先确定独立处理器或 Runner 扩展形式,再定义串并联、失败恢复、状态冲突和副作用幂等。
|
||||
- **Solution 导入导出**:使用逻辑依赖和路由模板;导入时选择 Bot、模型和资源,不能保留源实例 UUID、凭据、token 或租户密钥。包格式与更新策略待定。
|
||||
- **模型能力与评估**:国内 Provider reasoning 专项回归、reasoning token/生效策略记录、质量与延迟成本基线,见 [模型思考控制](../review/model-reasoning-control-design.md)。
|
||||
|
||||
这些方向不自动构成 4.11 OSS 发布前置条件,也不承诺旧草案中的 5.0/GA 版本号或日期。
|
||||
Reference in New Issue
Block a user