refactor(agent-runner): align config with agent semantics

This commit is contained in:
huanghuoguoguo
2026-06-02 17:43:44 +08:00
parent d0383e146e
commit f2153f736c
23 changed files with 94 additions and 126 deletions

View File

@@ -14,7 +14,7 @@
-`AgentRunAPIProxy.state` — get/set/delete API -`AgentRunAPIProxy.state` — get/set/delete API
- ✅ EventLog / Transcript / ArtifactStore — host 事实源 - ✅ EventLog / Transcript / ArtifactStore — host 事实源
- ✅ PersistentStateStore — 持久化状态存储 - ✅ PersistentStateStore — 持久化状态存储
-`max-round` / host-side history window 已从 LangBot Host/Pipeline 语义中移除;如某 runner 仍需要类似参数,应由该 runner 自己解释配置 -Host-side history window 已从 LangBot Host 语义中移除runner 自己管理 working context
- ✅ 外部 harness context projection 已用 Claude Code runner 做 MVP 验证context 文件、skill 投影、MCP 配置和 host-owned resume state - ✅ 外部 harness context projection 已用 Claude Code runner 做 MVP 验证context 文件、skill 投影、MCP 配置和 host-owned resume state
## 1. 设计原则 ## 1. 设计原则
@@ -35,11 +35,10 @@
- 可投影给外部 harness 的 scoped context、MCP、skill 和 resource refs。 - 可投影给外部 harness 的 scoped context、MCP、skill 和 resource refs。
- payload hard cap 和权限 guardrail。 - payload hard cap 和权限 guardrail。
### 1.2 不再把 `max-round` 作为目标设计 ### 1.2 Host 不定义通用历史窗口
`max-round` 这类历史窗口参数不应继续作为 AgentRunner 协议或 Pipeline adapter 的核心概念。 历史窗口策略不应继续作为 AgentRunner 协议或 Pipeline adapter 的核心概念。
Host 只提供 history pull API、cursor、hard cap 和权限边界runner 自己决定是否读取、读取多少、如何截断和压缩。
如果某个 runner 仍需要“最多读取多少轮历史”这样的策略参数,应由该 runner 在自己的 manifest/config schema 中声明,并作为 binding config 存到 `ctx.config` / `runner_config`。Host 只提供 history pull API、cursor、hard cap 和权限边界runner 自己决定是否读取、读取多少、如何截断和压缩。
当前 official local-agent 方向是通过 Host history API 拉取 transcript并由 runner 自己管理模型上下文。它不依赖 Pipeline adapter 下发历史窗口。 当前 official local-agent 方向是通过 Host history API 拉取 transcript并由 runner 自己管理模型上下文。它不依赖 Pipeline adapter 下发历史窗口。
@@ -100,7 +99,7 @@ class AgentRunContext(BaseModel):
- delivery 能力,例如是否支持 streaming、reply target、平台限制。 - delivery 能力,例如是否支持 streaming、reply target、平台限制。
- 已授权资源列表。 - 已授权资源列表。
- context cursors 和可用 API 能力。 - context cursors 和可用 API 能力。
- runner binding config。 - Agent/runner config。
这些是 agent 决定下一步需要的最低信息。 这些是 agent 决定下一步需要的最低信息。
@@ -324,7 +323,7 @@ LangBot core 不应内置官方 agent 的业务流程:
**已完成(当前分支)** **已完成(当前分支)**
-`max-round` 不再是协议字段,也不再是 Host / Pipeline 通用语义 -Host 不再定义通用历史窗口字段或策略
- ✅ 新 runner 默认不收到历史窗口 - ✅ 新 runner 默认不收到历史窗口
-`AgentRunContext` 增加 `context` / cursor / access capabilities -`AgentRunContext` 增加 `context` / cursor / access capabilities
-`AgentRunAPIProxy` 增加 history / events / artifacts / state API -`AgentRunAPIProxy` 增加 history / events / artifacts / state API

View File

@@ -393,7 +393,7 @@ Proxy 是 runner 访问 host 能力的唯一入口:
-`PipelineAdapter` — Query → Event + Binding -`PipelineAdapter` — Query → Event + Binding
-`AgentBinding` 抽象 -`AgentBinding` 抽象
-`AgentEventEnvelope` 抽象 -`AgentEventEnvelope` 抽象
-`max-round` 从目标协议中移除;类似历史窗口参数若仍需要,应由具体 runner 的 manifest/config schema 暴露为 binding config -Host 不定义通用历史窗口字段或策略runner 自己管理 working context
-`PersistentStateStore` — 持久化状态存储 -`PersistentStateStore` — 持久化状态存储
-`EventLogStore` / `TranscriptStore` / `ArtifactStore` -`EventLogStore` / `TranscriptStore` / `ArtifactStore`
- ✅ history / artifact / event 的受限拉取 API - ✅ history / artifact / event 的受限拉取 API

View File

@@ -75,7 +75,7 @@ SDK Runtime RUN_AGENT -> plugin AgentRunner.run()
- `PipelineService.get_pipeline_metadata()` 不直接访问插件 runtime而是读取 registry。 - `PipelineService.get_pipeline_metadata()` 不直接访问插件 runtime而是读取 registry。
-`RequestRunner` 只作为迁移参考,不作为最终运行路径。 -`RequestRunner` 只作为迁移参考,不作为最终运行路径。
- `AgentRunOrchestrator` 是 LangBot 侧运行编排层:负责 runner 绑定解析、资源授权、context envelope provisioning、run scope 注册、插件调用和结果归一化;不负责决定 Agent 的最终 prompt/window/压缩策略。 - `AgentRunOrchestrator` 是 LangBot 侧运行编排层:负责 runner 绑定解析、资源授权、context envelope provisioning、run scope 注册、插件调用和结果归一化;不负责决定 Agent 的最终 prompt/window/压缩策略。
- 插件是无状态执行单元:多个 Pipeline 可以绑定同一个 runner id并分别保存自己的 `ai.runner_config[id]`;运行时 LangBot 只把当前绑定配置放入 `ctx.config` 转发给同一个插件 runner。 - 插件是无状态执行单元:多个 Agent 可以绑定同一个 runner id并分别保存自己的 `ai.runner_config[id]`;运行时 LangBot 只把当前 Agent/runner config 放入 `ctx.config` 转发给同一个插件 runner。
- 禁止按 Pipeline 或 runner config 创建多个插件实例。需要跨请求持久化的状态必须走明确授权的 plugin storage / workspace storage / 外部服务,不能隐式保存在 per-pipeline 插件对象里。 - 禁止按 Pipeline 或 runner config 创建多个插件实例。需要跨请求持久化的状态必须走明确授权的 plugin storage / workspace storage / 外部服务,不能隐式保存在 per-pipeline 插件对象里。
- EBA 只做字段预留,不在本轮实现 EventBus、EventRouter、平台动作执行。 - EBA 只做字段预留,不在本轮实现 EventBus、EventRouter、平台动作执行。
@@ -191,7 +191,7 @@ Pipeline adapter 的 `prompt` 和公开业务变量不进入顶层协议字段
- filtered params -> `ctx.adapter.extra["params"]` - filtered params -> `ctx.adapter.extra["params"]`
- legacy/effective prompt 可以暂存到 `ctx.adapter.extra["prompt"]`,但 official - legacy/effective prompt 可以暂存到 `ctx.adapter.extra["prompt"]`,但 official
runner 不应把它当作行为契约 runner 不应把它当作行为契约
- LangBot Host 不生成 `bootstrap.messages``adapter_messages` 或 context packaging 元数据 - LangBot Host 不生成 bootstrap history payload 或 context packaging 元数据
现阶段不要把新的压缩或 token-budget 裁剪塞回 Pipeline stage。Pipeline 只负责入口适配;完整历史和长期上下文由 EventLog / Transcript / pull APIs / future ContextCompressor 支撑。 现阶段不要把新的压缩或 token-budget 裁剪塞回 Pipeline stage。Pipeline 只负责入口适配;完整历史和长期上下文由 EventLog / Transcript / pull APIs / future ContextCompressor 支撑。
@@ -216,7 +216,7 @@ ContextCompressor
- 完整历史属于 LangBot host不属于插件实例。插件仍是 singleton/stateless。 - 完整历史属于 LangBot host不属于插件实例。插件仍是 singleton/stateless。
- `ctx.bootstrap.messages` 不是 Host 默认下发的 working context。 - `ctx.bootstrap.messages` 不是 Host 默认下发的 working context。
- 每轮不能全量复制/序列化完整历史给插件 runtime否则长会话会产生 O(n) 成本和跨进程 payload 膨胀。 - 每轮不能全量复制/序列化完整历史给插件 runtime否则长会话会产生 O(n) 成本和跨进程 payload 膨胀。
- `max-round` 或类似窗口规则不属于 LangBot Host / Pipeline 语义。 - 通用历史窗口规则不属于 LangBot Host 语义。
- LiteLLM 接入后,模型窗口元信息应作为 resource/runtime metadata 暴露给 runner由 runner 决定预算和压缩策略。 - LiteLLM 接入后,模型窗口元信息应作为 resource/runtime metadata 暴露给 runner由 runner 决定预算和压缩策略。
- `ContextCompressor` 生成的是派生 summary/checkpoint不能覆盖或删除 raw history。 - `ContextCompressor` 生成的是派生 summary/checkpoint不能覆盖或删除 raw history。
- 重启恢复依赖持久化 store 和 summary checkpoint不依赖 `SessionManager` 里的进程内 conversation list。 - 重启恢复依赖持久化 store 和 summary checkpoint不依赖 `SessionManager` 里的进程内 conversation list。
@@ -470,7 +470,7 @@ async def run_from_query(query: pipeline_query.Query) -> AsyncGenerator[Message
- SDK `AgentRunContext` 保持 event-first`event/input/delivery/resources/context/state/runtime/config/bootstrap/adapter` - SDK `AgentRunContext` 保持 event-first`event/input/delivery/resources/context/state/runtime/config/bootstrap/adapter`
- LangBot context builder 只从 `AgentEventEnvelope + AgentBinding` 写入稳定协议字段。 - LangBot context builder 只从 `AgentEventEnvelope + AgentBinding` 写入稳定协议字段。
- Pipeline adapter 可以把公开业务变量写入 `ctx.adapter.extra["params"]`legacy/effective prompt 若保留在 `ctx.adapter.extra["prompt"]`,也只属于 adapter metadata。 - Pipeline adapter 可以把公开业务变量写入 `ctx.adapter.extra["params"]`legacy/effective prompt 若保留在 `ctx.adapter.extra["prompt"]`,也只属于 adapter metadata。
- 保持 `ctx.config` 只表达静态绑定配置 - 保持 `ctx.config` 只表达静态 Agent/runner config
### Step 2增强宿主 AgentRun proxy action ### Step 2增强宿主 AgentRun proxy action
@@ -489,7 +489,7 @@ async def run_from_query(query: pipeline_query.Query) -> AsyncGenerator[Message
### Step 4local-agent parity ### Step 4local-agent parity
- 使用静态绑定配置 `ctx.config["prompt"]`,不读取 `ctx.adapter.extra["prompt"]` - 使用静态 Agent/runner config `ctx.config["prompt"]`,不读取 `ctx.adapter.extra["prompt"]`
- 通过 Host history API 拉取 transcript不读取 `ctx.bootstrap.messages` 或 adapter window 字段。 - 通过 Host history API 拉取 transcript不读取 `ctx.bootstrap.messages` 或 adapter window 字段。
- 当前 user message 从 `ctx.input.contents` 构造,保留多模态内容。 - 当前 user message 从 `ctx.input.contents` 构造,保留多模态内容。
- RAG 只替换/插入文本部分,不丢图片/文件。 - RAG 只替换/插入文本部分,不丢图片/文件。
@@ -544,7 +544,7 @@ async def run_from_query(query: pipeline_query.Query) -> AsyncGenerator[Message
- `ChatMessageHandler` 不包含插件 runner 解析和 wrapper。 - `ChatMessageHandler` 不包含插件 runner 解析和 wrapper。
- `PipelineService` 不直接拼插件 runner metadata。 - `PipelineService` 不直接拼插件 runner metadata。
- 所有 runner 配置使用 `ai.runner.id` + `ai.runner_config` - 所有 runner 配置使用 `ai.runner.id` + `ai.runner_config`
- 插件 runtime 不为每个 Pipeline 或 runner 配置创建插件实例;`runner_config` 只作为绑定配置`ctx.config` 传入。 - 插件 runtime 不为每个 Agent 或 runner 配置创建插件实例;`runner_config` 只作为 Agent/runner config `ctx.config` 传入。
- 主聊天路径不再通过旧内置 runner 执行业务 runner。迁移期间旧文件可以保留。 - 主聊天路径不再通过旧内置 runner 执行业务 runner。迁移期间旧文件可以保留。
- 插件只能访问 `ctx.resources` 授权的模型、工具、知识库和文件。 - 插件只能访问 `ctx.resources` 授权的模型、工具、知识库和文件。
- 宿主 action 能为 AgentRunner 调用恢复必要 Query 语义,插件不需要拿裸 Query。 - 宿主 action 能为 AgentRunner 调用恢复必要 Query 语义,插件不需要拿裸 Query。

View File

@@ -265,7 +265,7 @@ Claude Code / Codex 这类外部 harness 不能直接持有 Python 进程内的
- 默认输出:`message.completed` + `run.completed` - 默认输出:`message.completed` + `run.completed`
- 默认权限:`permission-mode=plan``max-turns=1``disallowedTools=AskUserQuestion` - 默认权限:`permission-mode=plan``max-turns=1``disallowedTools=AskUserQuestion`
- 默认状态:如果 Claude Code 返回 `session_id`runner 通过 `state.updated` 写回 `external.session_id` - 默认状态:如果 Claude Code 返回 `session_id`runner 通过 `state.updated` 写回 `external.session_id`
- 工作目录:优先使用 binding config 的 `working-directory`,其次使用 Host state 中的 `external.working_directory` - 工作目录:优先使用 Agent/runner config 的 `working-directory`,其次使用 Host state 中的 `external.working_directory`
### 8.2 Context / skill / MCP 投影 ### 8.2 Context / skill / MCP 投影
@@ -274,8 +274,8 @@ Claude Code runner 当前把 LangBot event-first context 投影给外部 harness
- 写入 `agent-context.json`schema 为 `langbot.agent_runner.external_harness_context.v1` - 写入 `agent-context.json`schema 为 `langbot.agent_runner.external_harness_context.v1`
- 写入 `LANGBOT_CONTEXT.md`,作为人类可读摘要 - 写入 `LANGBOT_CONTEXT.md`,作为人类可读摘要
- 将 prompt prefix 指向 context 文件路径 - 将 prompt prefix 指向 context 文件路径
- 可把 binding 提供的 `skills-json` 写入 Claude Code 原生 `.claude/skills/<name>/SKILL.md` - 可把 Agent/runner config 提供的 `skills-json` 写入 Claude Code 原生 `.claude/skills/<name>/SKILL.md`
- 可把 binding 提供的 `mcp-config-json` 写成每次 run 的 MCP config并通过 `--mcp-config` / `--strict-mcp-config` 传给 Claude Code - 可把 Agent/runner config 提供的 `mcp-config-json` 写成每次 run 的 MCP config并通过 `--mcp-config` / `--strict-mcp-config` 传给 Claude Code
- 可通过 `enable-langbot-mcp=true` 启用 SDK-owned per-run LangBot MCP bridge使 Claude Code 通过 MCP 调用受限的 `AgentRunAPIProxy` 能力 - 可通过 `enable-langbot-mcp=true` 启用 SDK-owned per-run LangBot MCP bridge使 Claude Code 通过 MCP 调用受限的 `AgentRunAPIProxy` 能力
这些投影目前由 runner adapter 完成;长期更理想的形态是 LangBot Host 负责生成 scoped resource projectionrunner 只负责适配 Claude Code 的原生目录和 CLI 参数。 这些投影目前由 runner adapter 完成;长期更理想的形态是 LangBot Host 负责生成 scoped resource projectionrunner 只负责适配 Claude Code 的原生目录和 CLI 参数。
@@ -287,12 +287,12 @@ Claude Code runner 当前把 LangBot event-first context 投影给外部 harness
- WebUI Debug Chat 能通过 Pipeline adapter 调用 `claude-code-agent` - WebUI Debug Chat 能通过 Pipeline adapter 调用 `claude-code-agent`
- Claude Code 能读取 LangBot context 文件并按指令输出 sentinel - Claude Code 能读取 LangBot context 文件并按指令输出 sentinel
- Skill 文件可以投影到 `.claude/skills/` - Skill 文件可以投影到 `.claude/skills/`
- MCP config 可以通过 binding config 投影为 Claude Code CLI 参数 - MCP config 可以通过 Agent/runner config 投影为 Claude Code CLI 参数
- SDK-owned per-run LangBot MCP bridge 可以被真实 Claude Code CLI 调用,并通过 `langbot_get_current_event` 读取当前 run_id - SDK-owned per-run LangBot MCP bridge 可以被真实 Claude Code CLI 调用,并通过 `langbot_get_current_event` 读取当前 run_id
- `external.session_id``external.working_directory` 可以写入 host-owned state用于后续 resume - `external.session_id``external.working_directory` 可以写入 host-owned state用于后续 resume
- `codex-agent` 可通过 WebUI Debug Chat 调用本机 Codex CLI读取 LangBot event context并把 Codex `thread_id` 写入 host-owned state - `codex-agent` 可通过 WebUI Debug Chat 调用本机 Codex CLI读取 LangBot event context并把 Codex `thread_id` 写入 host-owned state
- SDK-owned per-run LangBot MCP bridge 可以被真实 Codex CLI 调用,并通过 `langbot_get_current_event` 读取当前 run_id - SDK-owned per-run LangBot MCP bridge 可以被真实 Codex CLI 调用,并通过 `langbot_get_current_event` 读取当前 run_id
- 对需要代理的本地运行环境,`codex-agent` 可通过 binding config 的 `environment-json` 显式传递非 secret 环境变量 - 对需要代理的本地运行环境,`codex-agent` 可通过 Agent/runner config 的 `environment-json` 显式传递非 secret 环境变量
下一轮测试入口见 [PHASE1_QA_ACCEPTANCE_MATRIX.md](./PHASE1_QA_ACCEPTANCE_MATRIX.md)。 下一轮测试入口见 [PHASE1_QA_ACCEPTANCE_MATRIX.md](./PHASE1_QA_ACCEPTANCE_MATRIX.md)。

View File

@@ -111,7 +111,7 @@ bin/lbs case list
- runner 选项来自插件 registry。 - runner 选项来自插件 registry。
- 保存后配置仍为 `ai.runner.id` + `ai.runner_config[id]` - 保存后配置仍为 `ai.runner.id` + `ai.runner_config[id]`
- `runner_config` 表示 binding config不表示插件实例状态。 - `runner_config` 表示 Agent/runner config不表示插件实例状态。
- 插件没有循环重启或 metadata 加载失败。 - 插件没有循环重启或 metadata 加载失败。
### 5.2 主聊天路径 ### 5.2 主聊天路径
@@ -175,7 +175,7 @@ Rerank、remove-think、文件输入等场景只在本次改动直接涉及时
1. 确认 `codex` CLI 在 LangBot runtime host 上可执行。 1. 确认 `codex` CLI 在 LangBot runtime host 上可执行。
2. 绑定 `plugin:langbot/codex-agent/default` 2. 绑定 `plugin:langbot/codex-agent/default`
3. 如需要代理,使用 binding config 的 `environment-json` 显式传入。 3. 如需要代理,使用 Agent/runner config 的 `environment-json` 显式传入。
4. 在 Debug Chat 执行一次真实 smoke。 4. 在 Debug Chat 执行一次真实 smoke。
5. 检查 JSONL 事件、last message、host-owned state。 5. 检查 JSONL 事件、last message、host-owned state。

View File

@@ -11,7 +11,7 @@
- ✅ Host 支持 `run_id` session authorization - ✅ Host 支持 `run_id` session authorization
- ✅ Host 能从当前 Pipeline 入口生成 event-first context - ✅ Host 能从当前 Pipeline 入口生成 event-first context
-`messages` 降级为 optional bootstrap -`messages` 降级为 optional bootstrap
-`max-round` 不出现在协议实体中,也不属于 Host / Pipeline 语义;类似参数若存在,由 runner 自己解释 `ctx.config` -Host 不定义通用历史窗口字段或策略runner 自己管理 working context
- ✅ Proxy 覆盖 model、tool、knowledge、state/storage - ✅ Proxy 覆盖 model、tool、knowledge、state/storage
- ✅ History / Event / Artifact / State API 已落地 - ✅ History / Event / Artifact / State API 已落地
- ✅ EventLog / Transcript / ArtifactStore / PersistentStateStore 已落地 - ✅ EventLog / Transcript / ArtifactStore / PersistentStateStore 已落地
@@ -148,7 +148,7 @@ Host 不使用该声明给 runner inline 历史窗口。默认原则:
- Host 只 inline 当前 event / input 和 context handles。 - Host 只 inline 当前 event / input 和 context handles。
- Runner 拥有 working context assembly。 - Runner 拥有 working context assembly。
- Runner 可在授权后通过 Host history / event / artifact / state APIs 拉取更多上下文。 - Runner 可在授权后通过 Host history / event / artifact / state APIs 拉取更多上下文。
- `max-round` 或类似窗口参数不属于 Protocol v1 字段,也不属于 Pipeline / Host 通用语义;如果某个 runner 需要,应由 runner 自己解释 `ctx.config` - 历史窗口策略不属于 Protocol v1 字段,也不属于 Host 通用语义
## 4. Run 协议 ## 4. Run 协议
@@ -194,8 +194,8 @@ class AgentRunContext(BaseModel):
- `event` 是必选字段Protocol v1 是 event-first。 - `event` 是必选字段Protocol v1 是 event-first。
- `input` 表示当前事件的主输入,不等于历史消息。 - `input` 表示当前事件的主输入,不等于历史消息。
- `bootstrap` 是可选字段LangBot Host 默认不填历史窗口。 - `bootstrap` 是可选字段LangBot Host 默认不填历史窗口。
- `adapter` 只放 Pipeline adapter 字段runner 不应依赖它做长期能力。 - `adapter` 只放入口 adapter 的非核心元数据runner 不应依赖它做长期能力。
- `config`Host binding config不是插件实例状态。 - `config`Agent/runner config不是插件实例状态。
### 4.3 AgentTrigger ### 4.3 AgentTrigger
@@ -345,7 +345,7 @@ class BootstrapContext(BaseModel):
- `bootstrap.messages` 不是 LangBot Host 的默认行为。 - `bootstrap.messages` 不是 LangBot Host 的默认行为。
- 自管 context runner 默认应收到空 bootstrap。 - 自管 context runner 默认应收到空 bootstrap。
- Host 不应为了”帮 agent 更聪明”而自动拼接完整 transcript。 - Host 不应为了”帮 agent 更聪明”而自动拼接完整 transcript。
- 类似历史窗口策略应由具体 runner 自己解释 binding config,并通过 Host history API 拉取历史new/official runners 不应依赖 Pipeline adapter 下发历史窗口。 - 历史窗口策略 runner 自己管理,并通过 Host history API 按需拉取历史new/official runners 不应依赖入口 adapter 下发历史窗口。
### 4.10 RuntimeContext ### 4.10 RuntimeContext
@@ -661,14 +661,14 @@ Pipeline 是当前入口 adapter不是协议中心。
-`PipelineAdapter.query_to_event(query)` — 从 `Query` 构造 `AgentEventEnvelope` -`PipelineAdapter.query_to_event(query)` — 从 `Query` 构造 `AgentEventEnvelope`
-`PipelineAdapter.pipeline_config_to_binding(query, runner_id)` — 从 Pipeline config 构造临时 AgentBinding -`PipelineAdapter.pipeline_config_to_binding(query, runner_id)` — 从 Pipeline config 构造临时 AgentBinding
-`run_from_query()` 委托到 `run(event, binding)` -`run_from_query()` 委托到 `run(event, binding)`
-runner-specific config 从 Pipeline 当前绑定配置透传到 `AgentBinding.runner_config` / `ctx.config` -Agent/runner config 从当前配置容器透传到 `AgentBinding.runner_config` / `ctx.config`
- ✅ Query-only 字段放入 `adapter` context - ✅ Query-only 字段放入 `adapter` context
Pipeline adapter 负责: Pipeline adapter 负责:
-`Query` 构造 `AgentEventContext` -`Query` 构造 `AgentEventContext`
- Pipeline config 构造临时 AgentBinding。 -当前配置容器构造临时 AgentBinding。
- 从当前 runner binding config 构造 `ctx.config` - 从当前 Agent/runner config 构造 `ctx.config`
- 保留必要的 legacy adapter metadata但不定义历史窗口、prompt 组装或 agentic context 策略。 - 保留必要的 legacy adapter metadata但不定义历史窗口、prompt 组装或 agentic context 策略。
- 后续若需要传递 preprocessing / hook 后的有效指令,应通过 Host prompt/instruction - 后续若需要传递 preprocessing / hook 后的有效指令,应通过 Host prompt/instruction
package pull API 暴露能力位和引用,而不是继续把 prompt 推入 `ctx.adapter.extra` package pull API 暴露能力位和引用,而不是继续把 prompt 推入 `ctx.adapter.extra`
@@ -685,7 +685,7 @@ Protocol v1 已在当前分支完成:
- ✅ Host 支持 `run_id` session authorization - ✅ Host 支持 `run_id` session authorization
- ✅ Host 能从当前 Pipeline 入口生成 event-first context - ✅ Host 能从当前 Pipeline 入口生成 event-first context
-`messages` 降级为 optional bootstrap -`messages` 降级为 optional bootstrap
-`max-round` 不出现在协议实体中,也不属于 Host / Pipeline 语义 -Host 不定义通用历史窗口字段或策略
- ✅ Proxy 至少覆盖 model、tool、knowledge、state/storage - ✅ Proxy 至少覆盖 model、tool、knowledge、state/storage
- ✅ History / event / artifact API 已落地 - ✅ History / event / artifact API 已落地
- ✅ EventLog / Transcript / ArtifactStore / PersistentStateStore 已落地 - ✅ EventLog / Transcript / ArtifactStore / PersistentStateStore 已落地

View File

@@ -80,7 +80,7 @@ Pipeline path 已获得 event-first host capabilities
LangBot 不应成为最终 agentic context manager。它应提供事实源、默认上下文引用和按需读取 APIagent 或其背后的 runtime 负责历史剪裁、摘要、召回和 KV cache 策略。 LangBot 不应成为最终 agentic context manager。它应提供事实源、默认上下文引用和按需读取 APIagent 或其背后的 runtime 负责历史剪裁、摘要、召回和 KV cache 策略。
`max-round` 这类历史窗口参数不应作为目标协议继续扩展;如果某个 runner 仍需要类似策略,应由该 runner 的 manifest/config schema 暴露为 binding config Host 不定义通用历史窗口字段或策略runner 通过 Host pull API 按需拉取历史并自行管理 working context
详见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。 详见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。

View File

@@ -38,7 +38,7 @@ v2 只是在 Host 上新增一层可选能力。需要管控面的 runner 或管
- EventLog / Transcript / ArtifactStore / PersistentStateStore - EventLog / Transcript / ArtifactStore / PersistentStateStore
- History / Event / Artifact / State / Storage pull APIs - History / Event / Artifact / State / Storage pull APIs
- AgentRunner result stream 和受控错误回流 - AgentRunner result stream 和受控错误回流
- binding config 与 host-owned state - Agent/runner config 与 host-owned state
这些能力足够支持一次 `runner.run(ctx)` 内的安全执行,但不足以承担完整 runtime 管控面。 这些能力足够支持一次 `runner.run(ctx)` 内的安全执行,但不足以承担完整 runtime 管控面。

View File

@@ -23,7 +23,7 @@
### Runner Plugin 负责 ### Runner Plugin 负责
- 遵守 LangBot 下发的 binding config、授权资源和运行约束。 - 遵守 LangBot 下发的 Agent/runner config、授权资源和运行约束。
- 将 LangBot 资源投影成目标 runner 可消费的形式,例如 context 文件、MCP 配置、环境变量或 CLI 参数。 - 将 LangBot 资源投影成目标 runner 可消费的形式,例如 context 文件、MCP 配置、环境变量或 CLI 参数。
- 不把长期状态保存在插件实例内;需要跨轮次保存的外部 session id / working directory 等状态应写入 host-owned state。 - 不把长期状态保存在插件实例内;需要跨轮次保存的外部 session id / working directory 等状态应写入 host-owned state。
- 对外部进程做最小必要封装,包括命令参数构造、超时、取消、输出解析和错误映射。 - 对外部进程做最小必要封装,包括命令参数构造、超时、取消、输出解析和错误映射。
@@ -70,4 +70,3 @@ Claude Code、Codex、Kimi Code 等外部 harness 可以继续使用自身的权
- Codex / Kimi runner 全量接入。 - Codex / Kimi runner 全量接入。
- EBA 分支完整迁移和联调。 - EBA 分支完整迁移和联调。
- 发布级安全 hardening 的完整实现。 - 发布级安全 hardening 的完整实现。

View File

@@ -25,7 +25,7 @@ class ConfigMigration:
Responsibilities: Responsibilities:
- Resolve runner ID from new ai.runner.id or old ai.runner.runner - Resolve runner ID from new ai.runner.id or old ai.runner.runner
- Map old built-in runner names to official plugin runner IDs - Map old built-in runner names to official plugin runner IDs
- Extract runtime runner config from ai.runner_config - Extract current Agent/runner config from ai.runner_config
- Migrate old ai.<runner-name> blocks into ai.runner_config - Migrate old ai.<runner-name> blocks into ai.runner_config
""" """
@@ -74,7 +74,7 @@ class ConfigMigration:
pipeline_config: dict[str, typing.Any], pipeline_config: dict[str, typing.Any],
runner_id: str, runner_id: str,
) -> dict[str, typing.Any]: ) -> dict[str, typing.Any]:
"""Resolve runner binding configuration from pipeline configuration. """Resolve Agent/runner configuration from pipeline configuration.
Runtime code should only read the migrated format. Legacy Runtime code should only read the migrated format. Legacy
ai.<runner-name> blocks are handled by migration helpers, not by the ai.<runner-name> blocks are handled by migration helpers, not by the
@@ -114,9 +114,6 @@ class ConfigMigration:
if old_runner_name: if old_runner_name:
old_config = ai_config.get(old_runner_name, {}) old_config = ai_config.get(old_runner_name, {})
if old_config: if old_config:
old_config = dict(old_config)
if runner_id == OLD_RUNNER_TO_PLUGIN_RUNNER_ID['local-agent']:
old_config.pop('max-round', None)
return ConfigMigration.normalize_runner_config_for_migration(runner_id, old_config) return ConfigMigration.normalize_runner_config_for_migration(runner_id, old_config)
return {} return {}
@@ -126,7 +123,7 @@ class ConfigMigration:
runner_id: str, runner_id: str,
runner_config: dict[str, typing.Any], runner_config: dict[str, typing.Any],
) -> dict[str, typing.Any]: ) -> dict[str, typing.Any]:
"""Normalize released legacy runner config before storing binding config. """Normalize released legacy runner config before storing Agent/runner config.
Runtime code should not carry aliases. This helper is intentionally used Runtime code should not carry aliases. This helper is intentionally used
only by config migration so AgentRunner implementations can consume the only by config migration so AgentRunner implementations can consume the

View File

@@ -22,7 +22,7 @@ class AgentTrigger(typing.TypedDict):
"""Agent trigger information.""" """Agent trigger information."""
type: str type: str
source: str # 'pipeline' or 'event_router' source: str
timestamp: int | None timestamp: int | None
@@ -37,7 +37,6 @@ class ConversationContext(typing.TypedDict):
bot_id: str | None bot_id: str | None
workspace_id: str | None workspace_id: str | None
session_id: str | None session_id: str | None
pipeline_uuid: str | None
class AgentInput(typing.TypedDict): class AgentInput(typing.TypedDict):
@@ -128,8 +127,9 @@ class AgentRunContextPayload(typing.TypedDict):
Protocol v1 structure - matches SDK AgentRunContext. Protocol v1 structure - matches SDK AgentRunContext.
Note: The 'config' field contains the binding config from ai.runner_config[runner_id], Note: The 'config' field contains the current Agent/runner config
which is Pipeline's configuration for this specific runner binding (not plugin instance config). from ai.runner_config[runner_id] while Pipeline remains the temporary
configuration container. It is not plugin instance config.
""" """
run_id: str run_id: str
@@ -144,9 +144,9 @@ class AgentRunContextPayload(typing.TypedDict):
context: dict[str, typing.Any] # ContextAccess - REQUIRED for Protocol v1 context: dict[str, typing.Any] # ContextAccess - REQUIRED for Protocol v1
state: AgentRunState state: AgentRunState
runtime: AgentRuntimeContext runtime: AgentRuntimeContext
config: dict[str, typing.Any] # Binding config from ai.runner_config[runner_id] config: dict[str, typing.Any] # Agent/runner config from ai.runner_config[runner_id]
bootstrap: dict[str, typing.Any] | None # Optional bootstrap context bootstrap: dict[str, typing.Any] | None # Optional bootstrap context
adapter: dict[str, typing.Any] | None # Pipeline adapter context adapter: dict[str, typing.Any] | None # Entry adapter context
metadata: dict[str, typing.Any] # Additional metadata metadata: dict[str, typing.Any] # Additional metadata
@@ -160,7 +160,7 @@ class AgentRunContextBuilder:
- Build input from event - Build input from event
- Build state snapshot from PersistentStateStore - Build state snapshot from PersistentStateStore
- Build runtime context with host info, trace_id, deadline - Build runtime context with host info, trace_id, deadline
- Set config from runner binding configuration. - Set config from current Agent/runner configuration.
Pipeline Query adaptation belongs to PipelineAdapter, not this builder. Pipeline Query adaptation belongs to PipelineAdapter, not this builder.
""" """
@@ -184,7 +184,7 @@ class AgentRunContextBuilder:
Args: Args:
event: Event envelope event: Event envelope
binding: Agent binding configuration binding: Agent binding
descriptor: Runner descriptor descriptor: Runner descriptor
resources: Built resources resources: Built resources
@@ -205,7 +205,7 @@ class AgentRunContextBuilder:
conversation: ConversationContext | None = None conversation: ConversationContext | None = None
if event.conversation_id: if event.conversation_id:
conversation = { conversation = {
'session_id': None, # Pipeline adapter field 'session_id': None,
'conversation_id': event.conversation_id, 'conversation_id': event.conversation_id,
'thread_id': event.thread_id, 'thread_id': event.thread_id,
'launcher_type': None, # Will be filled from actor/subject if needed 'launcher_type': None, # Will be filled from actor/subject if needed
@@ -213,7 +213,6 @@ class AgentRunContextBuilder:
'sender_id': event.actor.actor_id if event.actor else None, 'sender_id': event.actor.actor_id if event.actor else None,
'bot_id': event.bot_id, 'bot_id': event.bot_id,
'workspace_id': event.workspace_id, 'workspace_id': event.workspace_id,
'pipeline_uuid': binding.pipeline_uuid, # Pipeline adapter field
} }
# Build event context (Protocol v1 event-first) # Build event context (Protocol v1 event-first)
@@ -277,7 +276,7 @@ class AgentRunContextBuilder:
'model_context_window_tokens': None, 'model_context_window_tokens': None,
# TODO(model-info): populate model_context_window_tokens after # TODO(model-info): populate model_context_window_tokens after
# LiteLLM/model metadata lands. Runners fall back to their # LiteLLM/model metadata lands. Runners fall back to their
# binding config until Host can provide the real window. # ctx.config until Host can provide the real window.
}, },
} }
@@ -295,7 +294,6 @@ class AgentRunContextBuilder:
# Build adapter context (empty for event-first) # Build adapter context (empty for event-first)
adapter_context = { adapter_context = {
'query_id': None, 'query_id': None,
'pipeline_uuid': binding.pipeline_uuid,
'extra': {}, 'extra': {},
} }

View File

@@ -15,7 +15,7 @@ class RunnerNotFoundError(AgentRunnerError):
class RunnerNotAuthorizedError(AgentRunnerError): class RunnerNotAuthorizedError(AgentRunnerError):
"""Runner not authorized for this pipeline.""" """Runner not authorized for this binding."""
def __init__(self, runner_id: str, bound_plugins: list[str] | None): def __init__(self, runner_id: str, bound_plugins: list[str] | None):
self.runner_id = runner_id self.runner_id = runner_id
self.bound_plugins = bound_plugins self.bound_plugins = bound_plugins
@@ -34,4 +34,4 @@ class RunnerExecutionError(AgentRunnerError):
def __init__(self, runner_id: str, message: str, retryable: bool = False): def __init__(self, runner_id: str, message: str, retryable: bool = False):
self.runner_id = runner_id self.runner_id = runner_id
self.retryable = retryable self.retryable = retryable
super().__init__(f'Agent runner {runner_id} execution failed: {message}') super().__init__(f'Agent runner {runner_id} execution failed: {message}')

View File

@@ -73,11 +73,11 @@ class AgentEventEnvelope(pydantic.BaseModel):
class BindingScope(pydantic.BaseModel): class BindingScope(pydantic.BaseModel):
"""Scope for agent binding.""" """Scope for agent binding."""
scope_type: typing.Literal["bot", "pipeline", "workspace", "global"] = "pipeline" scope_type: typing.Literal["agent", "bot", "workspace", "global"] = "agent"
"""Scope type.""" """Scope type."""
scope_id: str | None = None scope_id: str | None = None
"""Scope identifier (bot_uuid, pipeline_uuid, etc.).""" """Scope identifier (agent_id, bot_uuid, etc.)."""
class ResourcePolicy(pydantic.BaseModel): class ResourcePolicy(pydantic.BaseModel):
@@ -153,7 +153,7 @@ class AgentBinding(pydantic.BaseModel):
"""Runner ID to invoke.""" """Runner ID to invoke."""
runner_config: dict[str, typing.Any] = pydantic.Field(default_factory=dict) runner_config: dict[str, typing.Any] = pydantic.Field(default_factory=dict)
"""Runner binding configuration.""" """Current Agent/runner configuration."""
resource_policy: ResourcePolicy = pydantic.Field(default_factory=ResourcePolicy) resource_policy: ResourcePolicy = pydantic.Field(default_factory=ResourcePolicy)
"""Resource policy.""" """Resource policy."""
@@ -167,6 +167,5 @@ class AgentBinding(pydantic.BaseModel):
enabled: bool = True enabled: bool = True
"""Whether binding is enabled.""" """Whether binding is enabled."""
# Fields for Pipeline adapter agent_id: str | None = None
pipeline_uuid: str | None = None """Host-side Agent/config identifier for this binding."""
"""Pipeline UUID (for Pipeline adapter)."""

View File

@@ -93,9 +93,9 @@ class AgentRunOrchestrator:
Args: Args:
event: Event envelope from event gateway event: Event envelope from event gateway
binding: Agent binding configuration binding: Agent binding
bound_plugins: Optional list of bound plugin identities for authorization bound_plugins: Optional list of bound plugin identities for authorization
adapter_context: Optional adapter context from Pipeline adapter adapter_context: Optional context from an entry adapter
Yields: Yields:
Message or MessageChunk for pipeline response Message or MessageChunk for pipeline response
@@ -125,12 +125,12 @@ class AgentRunOrchestrator:
resources=resources, resources=resources,
) )
# Merge adapter context if provided (for Pipeline adapter) # Merge adapter context if provided
if adapter_context: if adapter_context:
# Merge params into adapter.extra # Merge params into adapter.extra
if 'params' in adapter_context: if 'params' in adapter_context:
context['adapter']['extra']['params'] = adapter_context['params'] context['adapter']['extra']['params'] = adapter_context['params']
# Merge prompt into adapter.extra for Pipeline adapter consumers. # Merge prompt into adapter.extra for transitional adapter consumers.
if 'prompt' in adapter_context: if 'prompt' in adapter_context:
context['adapter']['extra']['prompt'] = adapter_context['prompt'] context['adapter']['extra']['prompt'] = adapter_context['prompt']
# Set query_id if provided # Set query_id if provided
@@ -420,7 +420,7 @@ class AgentRunOrchestrator:
Args: Args:
result_dict: Raw result dict with type='state.updated' result_dict: Raw result dict with type='state.updated'
event: Event envelope event: Event envelope
binding: Agent binding configuration binding: Agent binding
descriptor: Runner descriptor descriptor: Runner descriptor
""" """
data = result_dict.get('data', {}) data = result_dict.get('data', {})

View File

@@ -114,12 +114,11 @@ class PipelineAdapter:
pipeline_config = query.pipeline_config or {} pipeline_config = query.pipeline_config or {}
ai_config = pipeline_config.get('ai', {}) ai_config = pipeline_config.get('ai', {})
runner_config = ai_config.get('runner_config', {}).get(runner_id, {}) runner_config = ai_config.get('runner_config', {}).get(runner_id, {})
pipeline_uuid = getattr(query, 'pipeline_uuid', None) agent_id = getattr(query, 'pipeline_uuid', None)
# Build scope
scope = BindingScope( scope = BindingScope(
scope_type="pipeline", scope_type="agent",
scope_id=pipeline_uuid, scope_id=agent_id,
) )
# Build resource policy from pipeline config # Build resource policy from pipeline config
@@ -142,7 +141,7 @@ class PipelineAdapter:
) )
return AgentBinding( return AgentBinding(
binding_id=f"pipeline_{pipeline_uuid or 'default'}_{runner_id}", binding_id=f"agent_{agent_id or 'default'}_{runner_id}",
scope=scope, scope=scope,
event_types=[runner_events.MESSAGE_RECEIVED], event_types=[runner_events.MESSAGE_RECEIVED],
runner_id=runner_id, runner_id=runner_id,
@@ -151,7 +150,7 @@ class PipelineAdapter:
state_policy=state_policy, state_policy=state_policy,
delivery_policy=delivery_policy, delivery_policy=delivery_policy,
enabled=True, enabled=True,
pipeline_uuid=pipeline_uuid, agent_id=agent_id,
) )
@classmethod @classmethod
@@ -335,9 +334,6 @@ class PipelineAdapter:
# Handle bot_uuid # Handle bot_uuid
bot_uuid = getattr(query, 'bot_uuid', None) bot_uuid = getattr(query, 'bot_uuid', None)
# Handle pipeline_uuid
pipeline_uuid = getattr(query, 'pipeline_uuid', None)
return ConversationContext( return ConversationContext(
conversation_id=str(conversation_id) if conversation_id is not None else None, conversation_id=str(conversation_id) if conversation_id is not None else None,
thread_id=None, thread_id=None,
@@ -347,7 +343,6 @@ class PipelineAdapter:
bot_id=bot_uuid, bot_id=bot_uuid,
workspace_id=None, workspace_id=None,
session_id=session_id, session_id=session_id,
pipeline_uuid=pipeline_uuid,
) )
@classmethod @classmethod
@@ -398,7 +393,6 @@ class PipelineAdapter:
"launcher_id": getattr(query, 'launcher_id', None), "launcher_id": getattr(query, 'launcher_id', None),
"sender_id": str(getattr(query, 'sender_id', '')) if getattr(query, 'sender_id', None) else None, "sender_id": str(getattr(query, 'sender_id', '')) if getattr(query, 'sender_id', None) else None,
"bot_uuid": getattr(query, 'bot_uuid', None), "bot_uuid": getattr(query, 'bot_uuid', None),
"pipeline_uuid": getattr(query, 'pipeline_uuid', None),
}, },
) )

View File

@@ -23,7 +23,7 @@ class AgentResourceBuilder:
- Apply 3-layer permission filtering: - Apply 3-layer permission filtering:
1. Runner manifest declared permissions 1. Runner manifest declared permissions
2. Pipeline extensions_preference (bound plugins/MCP servers) 2. Pipeline extensions_preference (bound plugins/MCP servers)
3. Runner binding config selected resources 3. Agent/runner config selected resources
- Build models list from authorized models - Build models list from authorized models
- Build tools list from bound plugins/MCP servers - Build tools list from bound plugins/MCP servers
- Build knowledge_bases list from config - Build knowledge_bases list from config
@@ -68,7 +68,7 @@ class AgentResourceBuilder:
# Layer 2: Binding resource policy # Layer 2: Binding resource policy
resource_policy = binding.resource_policy resource_policy = binding.resource_policy
# Layer 3: Runner binding config # Layer 3: Agent/runner config
runner_config = binding.runner_config runner_config = binding.runner_config
# Build each resource category # Build each resource category
@@ -112,7 +112,7 @@ class AgentResourceBuilder:
# Get additional model UUID grants from resource policy. # Get additional model UUID grants from resource policy.
allowed_uuids = resource_policy.allowed_model_uuids allowed_uuids = resource_policy.allowed_model_uuids
# Add model resources from binding config schema # Add model resources from Agent/runner config schema
await self._append_config_declared_model_resources( await self._append_config_declared_model_resources(
models=models, models=models,
seen_model_ids=seen_model_ids, seen_model_ids=seen_model_ids,

View File

@@ -36,7 +36,6 @@ class TestMigratePipelineConfig:
assert 'plugin:langbot/local-agent/default' in migrated['ai']['runner_config'] assert 'plugin:langbot/local-agent/default' in migrated['ai']['runner_config']
assert migrated['ai']['runner_config']['plugin:langbot/local-agent/default']['knowledge-bases'] == ['kb-uuid'] assert migrated['ai']['runner_config']['plugin:langbot/local-agent/default']['knowledge-bases'] == ['kb-uuid']
assert 'knowledge-base' not in migrated['ai']['runner_config']['plugin:langbot/local-agent/default'] assert 'knowledge-base' not in migrated['ai']['runner_config']['plugin:langbot/local-agent/default']
assert 'max-round' not in migrated['ai']['runner_config']['plugin:langbot/local-agent/default']
# Expire-time preserved # Expire-time preserved
assert migrated['ai']['runner']['expire-time'] == 0 assert migrated['ai']['runner']['expire-time'] == 0
@@ -270,7 +269,7 @@ class TestResolveRunnerConfig:
"""resolve_runner_config should not read old ai.local-agent at runtime.""" """resolve_runner_config should not read old ai.local-agent at runtime."""
config = { config = {
'ai': { 'ai': {
'local-agent': {'max-round': 15, 'custom-option': 20}, 'local-agent': {'custom-option': 20},
}, },
} }
runner_config = ConfigMigration.resolve_runner_config(config, 'plugin:langbot/local-agent/default') runner_config = ConfigMigration.resolve_runner_config(config, 'plugin:langbot/local-agent/default')
@@ -280,7 +279,7 @@ class TestResolveRunnerConfig:
"""resolve_legacy_runner_config should read old ai.local-agent for migration.""" """resolve_legacy_runner_config should read old ai.local-agent for migration."""
config = { config = {
'ai': { 'ai': {
'local-agent': {'max-round': 15, 'custom-option': 20}, 'local-agent': {'custom-option': 20},
}, },
} }
runner_config = ConfigMigration.resolve_legacy_runner_config(config, 'plugin:langbot/local-agent/default') runner_config = ConfigMigration.resolve_legacy_runner_config(config, 'plugin:langbot/local-agent/default')
@@ -293,7 +292,7 @@ class TestResolveRunnerConfig:
'runner_config': { 'runner_config': {
'plugin:langbot/local-agent/default': {'custom-option': 25}, 'plugin:langbot/local-agent/default': {'custom-option': 25},
}, },
'local-agent': {'max-round': 10, 'custom-option': 10}, # Old, should be ignored 'local-agent': {'custom-option': 10}, # Old, should be ignored
}, },
} }
runner_config = ConfigMigration.resolve_runner_config(config, 'plugin:langbot/local-agent/default') runner_config = ConfigMigration.resolve_runner_config(config, 'plugin:langbot/local-agent/default')

View File

@@ -8,7 +8,7 @@ Tests focus on:
from __future__ import annotations from __future__ import annotations
import pytest import pytest
from unittest.mock import MagicMock, AsyncMock, patch from unittest.mock import MagicMock
from langbot.pkg.agent.runner.context_builder import AgentRunContextBuilder from langbot.pkg.agent.runner.context_builder import AgentRunContextBuilder
from langbot.pkg.agent.runner.host_models import AgentEventEnvelope, AgentBinding, BindingScope, StatePolicy from langbot.pkg.agent.runner.host_models import AgentEventEnvelope, AgentBinding, BindingScope, StatePolicy
@@ -67,7 +67,7 @@ class TestContextAccessStateDetermination:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy( state_policy=StatePolicy(
enable_state=True, enable_state=True,
state_scopes=['conversation', 'actor'], state_scopes=['conversation', 'actor'],
@@ -88,7 +88,7 @@ class TestContextAccessStateDetermination:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy( state_policy=StatePolicy(
enable_state=False, enable_state=False,
state_scopes=[], state_scopes=[],
@@ -109,7 +109,7 @@ class TestContextAccessStateDetermination:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy( state_policy=StatePolicy(
enable_state=True, enable_state=True,
state_scopes=[], # Empty scopes - state not available state_scopes=[], # Empty scopes - state not available
@@ -177,7 +177,7 @@ class TestContextAccessStateDetermination:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_003', binding_id='binding_003',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy( state_policy=StatePolicy(
enable_state=True, enable_state=True,
state_scopes=['conversation', 'actor', 'subject', 'runner'], state_scopes=['conversation', 'actor', 'subject', 'runner'],
@@ -226,7 +226,7 @@ class TestBindingWithStatePolicy:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy( state_policy=StatePolicy(
enable_state=True, enable_state=True,
state_scopes=['conversation'], state_scopes=['conversation'],
@@ -260,7 +260,7 @@ class TestContextAccessOtherAPIs:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy(enable_state=False, state_scopes=[]), state_policy=StatePolicy(enable_state=False, state_scopes=[]),
) )
@@ -288,7 +288,7 @@ class TestContextAccessOtherAPIs:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy(enable_state=False, state_scopes=[]), state_policy=StatePolicy(enable_state=False, state_scopes=[]),
) )
@@ -316,7 +316,7 @@ class TestContextAccessOtherAPIs:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy(enable_state=False, state_scopes=[]), state_policy=StatePolicy(enable_state=False, state_scopes=[]),
) )
@@ -342,7 +342,7 @@ class TestContextAccessOtherAPIs:
binding = AgentBinding( binding = AgentBinding(
binding_id='binding_001', binding_id='binding_001',
runner_id='plugin:test/runner/default', runner_id='plugin:test/runner/default',
scope=BindingScope(scope_type='pipeline', scope_id='conv_001'), scope=BindingScope(scope_type='agent', scope_id='conv_001'),
state_policy=StatePolicy(enable_state=False, state_scopes=[]), state_policy=StatePolicy(enable_state=False, state_scopes=[]),
) )

View File

@@ -66,11 +66,11 @@ class TestContextValidation:
"""Create a test binding.""" """Create a test binding."""
return AgentBinding( return AgentBinding(
binding_id="binding_1", binding_id="binding_1",
scope=BindingScope(scope_type="pipeline", scope_id="pipeline_1"), scope=BindingScope(scope_type="agent", scope_id="pipeline_1"),
event_types=["message.received"], event_types=["message.received"],
runner_id="plugin:test/plugin/runner", runner_id="plugin:test/plugin/runner",
runner_config={"timeout": 300}, runner_config={"timeout": 300},
pipeline_uuid="pipeline_1", agent_id="pipeline_1",
enabled=True, enabled=True,
) )

View File

@@ -144,17 +144,9 @@ class TestPipelineConfigToBinding:
mock_query, "plugin:test/plugin/runner" mock_query, "plugin:test/plugin/runner"
) )
assert binding.scope.scope_type == "pipeline" assert binding.scope.scope_type == "agent"
assert binding.scope.scope_id == mock_query.pipeline_uuid assert binding.scope.scope_id == mock_query.pipeline_uuid
assert binding.agent_id == mock_query.pipeline_uuid
def test_config_to_binding_does_not_add_host_context_window(self, mock_query):
"""Pipeline binding should not define Host-side context window controls."""
binding = PipelineAdapter.pipeline_config_to_binding(
mock_query, "plugin:test/plugin/runner"
)
assert not hasattr(binding, "max_round")
class TestAgentRunContextProtocolV1: class TestAgentRunContextProtocolV1:
"""Test AgentRunContext Protocol v1 behavior.""" """Test AgentRunContext Protocol v1 behavior."""
@@ -238,23 +230,14 @@ class TestAgentRunContextProtocolV1:
assert ctx.bootstrap is None or isinstance(ctx.bootstrap.messages, list) assert ctx.bootstrap is None or isinstance(ctx.bootstrap.messages, list)
class TestHostContextWindowNotInProtocol: class TestHostManagedHistoryNotInProtocol:
"""Test that Host-side context window controls are not in Protocol v1.""" """Test that Host-managed history payloads are not in Protocol v1."""
def test_context_window_not_in_sdk_context(self): def test_messages_not_in_sdk_context_top_level(self):
"""AgentRunContext should not expose Host-side window controls.""" """AgentRunContext should not expose top-level history messages."""
ctx_fields = AgentRunContext.model_fields.keys() ctx_fields = AgentRunContext.model_fields.keys()
assert "max_round" not in ctx_fields assert "messages" not in ctx_fields
assert "maxRound" not in ctx_fields
def test_binding_has_no_context_window_field(self, mock_query):
"""Pipeline adapter should not attach context window policy to binding."""
binding = PipelineAdapter.pipeline_config_to_binding(
mock_query, "plugin:test/plugin/runner"
)
assert not hasattr(binding, "max_round")
class TestSDKCapabilitiesProtocolV1: class TestSDKCapabilitiesProtocolV1:

View File

@@ -56,7 +56,7 @@ def make_binding(runner_id: str = "plugin:test/plugin/runner") -> AgentBinding:
"""Create a test binding.""" """Create a test binding."""
return AgentBinding( return AgentBinding(
binding_id="binding_1", binding_id="binding_1",
scope=BindingScope(scope_type="pipeline", scope_id="pipeline_1"), scope=BindingScope(scope_type="agent", scope_id="pipeline_1"),
event_types=["message.received"], event_types=["message.received"],
runner_id=runner_id, runner_id=runner_id,
runner_config={}, runner_config={},

View File

@@ -347,7 +347,7 @@ async def test_orchestrator_does_not_package_query_messages_into_context(clean_a
ap = FakeApplication(plugin_connector, db_engine) ap = FakeApplication(plugin_connector, db_engine)
orchestrator = AgentRunOrchestrator(ap, FakeRegistry(descriptor)) orchestrator = AgentRunOrchestrator(ap, FakeRegistry(descriptor))
query = make_query() query = make_query()
query.pipeline_config["ai"]["runner_config"][RUNNER_ID]["agent-window"] = 2 query.pipeline_config["ai"]["runner_config"][RUNNER_ID]["custom-option"] = 2
query.messages = [ query.messages = [
provider_message.Message(role="user", content="message 1"), provider_message.Message(role="user", content="message 1"),
provider_message.Message(role="assistant", content="response 1"), provider_message.Message(role="assistant", content="response 1"),
@@ -361,9 +361,9 @@ async def test_orchestrator_does_not_package_query_messages_into_context(clean_a
assert len(messages) == 1 assert len(messages) == 1
context = plugin_connector.contexts[0] context = plugin_connector.contexts[0]
assert context["config"]["agent-window"] == 2 assert context["config"]["custom-option"] == 2
assert context["bootstrap"] is None assert context["bootstrap"] is None
assert "adapter_messages" not in context["adapter"] assert set(context["adapter"]) == {"query_id", "extra"}
assert "context_packaging" not in context["runtime"]["metadata"] assert "context_packaging" not in context["runtime"]["metadata"]
assert [message.content for message in query.messages] == [ assert [message.content for message in query.messages] == [
"message 1", "message 1",
@@ -538,7 +538,7 @@ class TestPipelineCompatibilityQueryIdInSession:
) )
binding = AgentBinding( binding = AgentBinding(
binding_id="binding_001", binding_id="binding_001",
scope=BindingScope(scope_type="pipeline", scope_id="pipeline_001"), scope=BindingScope(scope_type="agent", scope_id="pipeline_001"),
event_types=["message.received"], event_types=["message.received"],
runner_id=RUNNER_ID, runner_id=RUNNER_ID,
runner_config={}, runner_config={},

View File

@@ -82,8 +82,8 @@ class FakeBinding:
self, self,
binding_id: str = 'binding_001', binding_id: str = 'binding_001',
state_policy: StatePolicy | None = None, state_policy: StatePolicy | None = None,
scope_type: str = 'pipeline', scope_type: str = 'agent',
scope_id: str = 'pipeline_001', scope_id: str = 'agent_001',
): ):
self.binding_id = binding_id self.binding_id = binding_id
self.scope = BindingScope(scope_type=scope_type, scope_id=scope_id) self.scope = BindingScope(scope_type=scope_type, scope_id=scope_id)