diff --git a/docs/agent-runner-pluginization/AGENT_CONTEXT_PROTOCOL.md b/docs/agent-runner-pluginization/AGENT_CONTEXT_PROTOCOL.md index 41531284..9be0e463 100644 --- a/docs/agent-runner-pluginization/AGENT_CONTEXT_PROTOCOL.md +++ b/docs/agent-runner-pluginization/AGENT_CONTEXT_PROTOCOL.md @@ -14,7 +14,7 @@ - ✅ `AgentRunAPIProxy.state` — get/set/delete API - ✅ EventLog / Transcript / ArtifactStore — host 事实源 - ✅ 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 ## 1. 设计原则 @@ -35,11 +35,10 @@ - 可投影给外部 harness 的 scoped context、MCP、skill 和 resource refs。 - payload hard cap 和权限 guardrail。 -### 1.2 不再把 `max-round` 作为目标设计 +### 1.2 Host 不定义通用历史窗口 -`max-round` 这类历史窗口参数不应继续作为 AgentRunner 协议或 Pipeline adapter 的核心概念。 - -如果某个 runner 仍需要“最多读取多少轮历史”这样的策略参数,应由该 runner 在自己的 manifest/config schema 中声明,并作为 binding config 存到 `ctx.config` / `runner_config`。Host 只提供 history pull API、cursor、hard cap 和权限边界;runner 自己决定是否读取、读取多少、如何截断和压缩。 +历史窗口策略不应继续作为 AgentRunner 协议或 Pipeline adapter 的核心概念。 +Host 只提供 history pull API、cursor、hard cap 和权限边界;runner 自己决定是否读取、读取多少、如何截断和压缩。 当前 official local-agent 方向是通过 Host history API 拉取 transcript,并由 runner 自己管理模型上下文。它不依赖 Pipeline adapter 下发历史窗口。 @@ -100,7 +99,7 @@ class AgentRunContext(BaseModel): - delivery 能力,例如是否支持 streaming、reply target、平台限制。 - 已授权资源列表。 - context cursors 和可用 API 能力。 -- runner binding config。 +- Agent/runner config。 这些是 agent 决定下一步需要的最低信息。 @@ -324,7 +323,7 @@ LangBot core 不应内置官方 agent 的业务流程: **已完成(当前分支)**: -- ✅ `max-round` 不再是协议字段,也不再是 Host / Pipeline 通用语义 +- ✅ Host 不再定义通用历史窗口字段或策略 - ✅ 新 runner 默认不收到历史窗口 - ✅ `AgentRunContext` 增加 `context` / cursor / access capabilities - ✅ `AgentRunAPIProxy` 增加 history / events / artifacts / state API diff --git a/docs/agent-runner-pluginization/HOST_SDK_INFRASTRUCTURE.md b/docs/agent-runner-pluginization/HOST_SDK_INFRASTRUCTURE.md index 78b3f075..b58571ef 100644 --- a/docs/agent-runner-pluginization/HOST_SDK_INFRASTRUCTURE.md +++ b/docs/agent-runner-pluginization/HOST_SDK_INFRASTRUCTURE.md @@ -393,7 +393,7 @@ Proxy 是 runner 访问 host 能力的唯一入口: - ✅ `PipelineAdapter` — Query → Event + Binding - ✅ `AgentBinding` 抽象 - ✅ `AgentEventEnvelope` 抽象 -- ✅ `max-round` 从目标协议中移除;类似历史窗口参数若仍需要,应由具体 runner 的 manifest/config schema 暴露为 binding config +- ✅ Host 不定义通用历史窗口字段或策略;runner 自己管理 working context - ✅ `PersistentStateStore` — 持久化状态存储 - ✅ `EventLogStore` / `TranscriptStore` / `ArtifactStore` - ✅ history / artifact / event 的受限拉取 API diff --git a/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md b/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md index daee03a3..d80ca750 100644 --- a/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md +++ b/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md @@ -75,7 +75,7 @@ SDK Runtime RUN_AGENT -> plugin AgentRunner.run() - `PipelineService.get_pipeline_metadata()` 不直接访问插件 runtime,而是读取 registry。 - 旧 `RequestRunner` 只作为迁移参考,不作为最终运行路径。 - `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 插件对象里。 - EBA 只做字段预留,不在本轮实现 EventBus、EventRouter、平台动作执行。 @@ -191,7 +191,7 @@ Pipeline adapter 的 `prompt` 和公开业务变量不进入顶层协议字段 - filtered params -> `ctx.adapter.extra["params"]` - legacy/effective prompt 可以暂存到 `ctx.adapter.extra["prompt"]`,但 official 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 支撑。 @@ -216,7 +216,7 @@ ContextCompressor - 完整历史属于 LangBot host,不属于插件实例。插件仍是 singleton/stateless。 - `ctx.bootstrap.messages` 不是 Host 默认下发的 working context。 - 每轮不能全量复制/序列化完整历史给插件 runtime;否则长会话会产生 O(n) 成本和跨进程 payload 膨胀。 -- `max-round` 或类似窗口规则不属于 LangBot Host / Pipeline 语义。 +- 通用历史窗口规则不属于 LangBot Host 语义。 - LiteLLM 接入后,模型窗口元信息应作为 resource/runtime metadata 暴露给 runner,由 runner 决定预算和压缩策略。 - `ContextCompressor` 生成的是派生 summary/checkpoint,不能覆盖或删除 raw history。 - 重启恢复依赖持久化 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`。 - LangBot context builder 只从 `AgentEventEnvelope + AgentBinding` 写入稳定协议字段。 - 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 @@ -489,7 +489,7 @@ async def run_from_query(query: pipeline_query.Query) -> AsyncGenerator[Message ### Step 4:local-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 字段。 - 当前 user message 从 `ctx.input.contents` 构造,保留多模态内容。 - RAG 只替换/插入文本部分,不丢图片/文件。 @@ -544,7 +544,7 @@ async def run_from_query(query: pipeline_query.Query) -> AsyncGenerator[Message - `ChatMessageHandler` 不包含插件 runner 解析和 wrapper。 - `PipelineService` 不直接拼插件 runner metadata。 - 所有 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。迁移期间旧文件可以保留。 - 插件只能访问 `ctx.resources` 授权的模型、工具、知识库和文件。 - 宿主 action 能为 AgentRunner 调用恢复必要 Query 语义,插件不需要拿裸 Query。 diff --git a/docs/agent-runner-pluginization/OFFICIAL_RUNNER_PLUGINS.md b/docs/agent-runner-pluginization/OFFICIAL_RUNNER_PLUGINS.md index 3541c247..f48b3e00 100644 --- a/docs/agent-runner-pluginization/OFFICIAL_RUNNER_PLUGINS.md +++ b/docs/agent-runner-pluginization/OFFICIAL_RUNNER_PLUGINS.md @@ -265,7 +265,7 @@ Claude Code / Codex 这类外部 harness 不能直接持有 Python 进程内的 - 默认输出:`message.completed` + `run.completed` - 默认权限:`permission-mode=plan`、`max-turns=1`、`disallowedTools=AskUserQuestion` - 默认状态:如果 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 投影 @@ -274,8 +274,8 @@ Claude Code runner 当前把 LangBot event-first context 投影给外部 harness - 写入 `agent-context.json`,schema 为 `langbot.agent_runner.external_harness_context.v1` - 写入 `LANGBOT_CONTEXT.md`,作为人类可读摘要 - 将 prompt prefix 指向 context 文件路径 -- 可把 binding 提供的 `skills-json` 写入 Claude Code 原生 `.claude/skills//SKILL.md` -- 可把 binding 提供的 `mcp-config-json` 写成每次 run 的 MCP config,并通过 `--mcp-config` / `--strict-mcp-config` 传给 Claude Code +- 可把 Agent/runner config 提供的 `skills-json` 写入 Claude Code 原生 `.claude/skills//SKILL.md` +- 可把 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` 能力 这些投影目前由 runner adapter 完成;长期更理想的形态是 LangBot Host 负责生成 scoped resource projection,runner 只负责适配 Claude Code 的原生目录和 CLI 参数。 @@ -287,12 +287,12 @@ Claude Code runner 当前把 LangBot event-first context 投影给外部 harness - WebUI Debug Chat 能通过 Pipeline adapter 调用 `claude-code-agent` - Claude Code 能读取 LangBot context 文件并按指令输出 sentinel - 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 - `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 - 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)。 diff --git a/docs/agent-runner-pluginization/PHASE1_QA_ACCEPTANCE_MATRIX.md b/docs/agent-runner-pluginization/PHASE1_QA_ACCEPTANCE_MATRIX.md index 948b5542..bc8db4bb 100644 --- a/docs/agent-runner-pluginization/PHASE1_QA_ACCEPTANCE_MATRIX.md +++ b/docs/agent-runner-pluginization/PHASE1_QA_ACCEPTANCE_MATRIX.md @@ -111,7 +111,7 @@ bin/lbs case list - runner 选项来自插件 registry。 - 保存后配置仍为 `ai.runner.id` + `ai.runner_config[id]`。 -- `runner_config` 表示 binding config,不表示插件实例状态。 +- `runner_config` 表示 Agent/runner config,不表示插件实例状态。 - 插件没有循环重启或 metadata 加载失败。 ### 5.2 主聊天路径 @@ -175,7 +175,7 @@ Rerank、remove-think、文件输入等场景只在本次改动直接涉及时 1. 确认 `codex` CLI 在 LangBot runtime host 上可执行。 2. 绑定 `plugin:langbot/codex-agent/default`。 -3. 如需要代理,使用 binding config 的 `environment-json` 显式传入。 +3. 如需要代理,使用 Agent/runner config 的 `environment-json` 显式传入。 4. 在 Debug Chat 执行一次真实 smoke。 5. 检查 JSONL 事件、last message、host-owned state。 diff --git a/docs/agent-runner-pluginization/PROTOCOL_V1.md b/docs/agent-runner-pluginization/PROTOCOL_V1.md index 82abbc7b..9dfc786f 100644 --- a/docs/agent-runner-pluginization/PROTOCOL_V1.md +++ b/docs/agent-runner-pluginization/PROTOCOL_V1.md @@ -11,7 +11,7 @@ - ✅ Host 支持 `run_id` session authorization - ✅ Host 能从当前 Pipeline 入口生成 event-first context - ✅ `messages` 降级为 optional bootstrap -- ✅ `max-round` 不出现在协议实体中,也不属于 Host / Pipeline 语义;类似参数若存在,由 runner 自己解释 `ctx.config` +- ✅ Host 不定义通用历史窗口字段或策略;runner 自己管理 working context - ✅ Proxy 覆盖 model、tool、knowledge、state/storage - ✅ History / Event / Artifact / State API 已落地 - ✅ EventLog / Transcript / ArtifactStore / PersistentStateStore 已落地 @@ -148,7 +148,7 @@ Host 不使用该声明给 runner inline 历史窗口。默认原则: - Host 只 inline 当前 event / input 和 context handles。 - Runner 拥有 working context assembly。 - Runner 可在授权后通过 Host history / event / artifact / state APIs 拉取更多上下文。 -- `max-round` 或类似窗口参数不属于 Protocol v1 字段,也不属于 Pipeline / Host 通用语义;如果某个 runner 需要,应由 runner 自己解释 `ctx.config`。 +- 历史窗口策略不属于 Protocol v1 字段,也不属于 Host 通用语义。 ## 4. Run 协议 @@ -194,8 +194,8 @@ class AgentRunContext(BaseModel): - `event` 是必选字段,Protocol v1 是 event-first。 - `input` 表示当前事件的主输入,不等于历史消息。 - `bootstrap` 是可选字段;LangBot Host 默认不填历史窗口。 -- `adapter` 只放 Pipeline adapter 字段,runner 不应依赖它做长期能力。 -- `config` 是 Host binding config,不是插件实例状态。 +- `adapter` 只放入口 adapter 的非核心元数据,runner 不应依赖它做长期能力。 +- `config` 是 Agent/runner config,不是插件实例状态。 ### 4.3 AgentTrigger @@ -345,7 +345,7 @@ class BootstrapContext(BaseModel): - `bootstrap.messages` 不是 LangBot Host 的默认行为。 - 自管 context runner 默认应收到空 bootstrap。 - 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 @@ -661,14 +661,14 @@ Pipeline 是当前入口 adapter,不是协议中心。 - ✅ `PipelineAdapter.query_to_event(query)` — 从 `Query` 构造 `AgentEventEnvelope` - ✅ `PipelineAdapter.pipeline_config_to_binding(query, runner_id)` — 从 Pipeline config 构造临时 AgentBinding - ✅ `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 Pipeline adapter 负责: - 从 `Query` 构造 `AgentEventContext`。 -- 从 Pipeline config 构造临时 AgentBinding。 -- 从当前 runner binding config 构造 `ctx.config`。 +- 从当前配置容器构造临时 AgentBinding。 +- 从当前 Agent/runner config 构造 `ctx.config`。 - 保留必要的 legacy adapter metadata,但不定义历史窗口、prompt 组装或 agentic context 策略。 - 后续若需要传递 preprocessing / hook 后的有效指令,应通过 Host prompt/instruction package pull API 暴露能力位和引用,而不是继续把 prompt 推入 `ctx.adapter.extra`。 @@ -685,7 +685,7 @@ Protocol v1 已在当前分支完成: - ✅ Host 支持 `run_id` session authorization - ✅ Host 能从当前 Pipeline 入口生成 event-first context - ✅ `messages` 降级为 optional bootstrap -- ✅ `max-round` 不出现在协议实体中,也不属于 Host / Pipeline 语义 +- ✅ Host 不定义通用历史窗口字段或策略 - ✅ Proxy 至少覆盖 model、tool、knowledge、state/storage - ✅ History / event / artifact API 已落地 - ✅ EventLog / Transcript / ArtifactStore / PersistentStateStore 已落地 diff --git a/docs/agent-runner-pluginization/README.md b/docs/agent-runner-pluginization/README.md index 2359ba9b..a3bcf76a 100644 --- a/docs/agent-runner-pluginization/README.md +++ b/docs/agent-runner-pluginization/README.md @@ -80,7 +80,7 @@ Pipeline path 已获得 event-first host capabilities: LangBot 不应成为最终 agentic context manager。它应提供事实源、默认上下文引用和按需读取 API;agent 或其背后的 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)。 diff --git a/docs/agent-runner-pluginization/RUNTIME_CONTROL_PLANE_V2.md b/docs/agent-runner-pluginization/RUNTIME_CONTROL_PLANE_V2.md index c7e496fb..1887c1f2 100644 --- a/docs/agent-runner-pluginization/RUNTIME_CONTROL_PLANE_V2.md +++ b/docs/agent-runner-pluginization/RUNTIME_CONTROL_PLANE_V2.md @@ -38,7 +38,7 @@ v2 只是在 Host 上新增一层可选能力。需要管控面的 runner 或管 - EventLog / Transcript / ArtifactStore / PersistentStateStore - History / Event / Artifact / State / Storage pull APIs - AgentRunner result stream 和受控错误回流 -- binding config 与 host-owned state +- Agent/runner config 与 host-owned state 这些能力足够支持一次 `runner.run(ctx)` 内的安全执行,但不足以承担完整 runtime 管控面。 diff --git a/docs/agent-runner-pluginization/SECURITY_HARDENING.md b/docs/agent-runner-pluginization/SECURITY_HARDENING.md index 0f1319e0..437bad4a 100644 --- a/docs/agent-runner-pluginization/SECURITY_HARDENING.md +++ b/docs/agent-runner-pluginization/SECURITY_HARDENING.md @@ -23,7 +23,7 @@ ### Runner Plugin 负责 -- 遵守 LangBot 下发的 binding config、授权资源和运行约束。 +- 遵守 LangBot 下发的 Agent/runner config、授权资源和运行约束。 - 将 LangBot 资源投影成目标 runner 可消费的形式,例如 context 文件、MCP 配置、环境变量或 CLI 参数。 - 不把长期状态保存在插件实例内;需要跨轮次保存的外部 session id / working directory 等状态应写入 host-owned state。 - 对外部进程做最小必要封装,包括命令参数构造、超时、取消、输出解析和错误映射。 @@ -70,4 +70,3 @@ Claude Code、Codex、Kimi Code 等外部 harness 可以继续使用自身的权 - Codex / Kimi runner 全量接入。 - EBA 分支完整迁移和联调。 - 发布级安全 hardening 的完整实现。 - diff --git a/src/langbot/pkg/agent/runner/config_migration.py b/src/langbot/pkg/agent/runner/config_migration.py index a9c8065e..ea64bebc 100644 --- a/src/langbot/pkg/agent/runner/config_migration.py +++ b/src/langbot/pkg/agent/runner/config_migration.py @@ -25,7 +25,7 @@ class ConfigMigration: Responsibilities: - Resolve runner ID from new ai.runner.id or old ai.runner.runner - 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. blocks into ai.runner_config """ @@ -74,7 +74,7 @@ class ConfigMigration: pipeline_config: dict[str, typing.Any], runner_id: str, ) -> 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 ai. blocks are handled by migration helpers, not by the @@ -114,9 +114,6 @@ class ConfigMigration: if old_runner_name: old_config = ai_config.get(old_runner_name, {}) 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 {} @@ -126,7 +123,7 @@ class ConfigMigration: runner_id: str, runner_config: 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 only by config migration so AgentRunner implementations can consume the diff --git a/src/langbot/pkg/agent/runner/context_builder.py b/src/langbot/pkg/agent/runner/context_builder.py index f918315b..1c35ad2b 100644 --- a/src/langbot/pkg/agent/runner/context_builder.py +++ b/src/langbot/pkg/agent/runner/context_builder.py @@ -22,7 +22,7 @@ class AgentTrigger(typing.TypedDict): """Agent trigger information.""" type: str - source: str # 'pipeline' or 'event_router' + source: str timestamp: int | None @@ -37,7 +37,6 @@ class ConversationContext(typing.TypedDict): bot_id: str | None workspace_id: str | None session_id: str | None - pipeline_uuid: str | None class AgentInput(typing.TypedDict): @@ -128,8 +127,9 @@ class AgentRunContextPayload(typing.TypedDict): Protocol v1 structure - matches SDK AgentRunContext. - Note: The 'config' field contains the binding config from ai.runner_config[runner_id], - which is Pipeline's configuration for this specific runner binding (not plugin instance config). + Note: The 'config' field contains the current Agent/runner config + from ai.runner_config[runner_id] while Pipeline remains the temporary + configuration container. It is not plugin instance config. """ run_id: str @@ -144,9 +144,9 @@ class AgentRunContextPayload(typing.TypedDict): context: dict[str, typing.Any] # ContextAccess - REQUIRED for Protocol v1 state: AgentRunState 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 - 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 @@ -160,7 +160,7 @@ class AgentRunContextBuilder: - Build input from event - Build state snapshot from PersistentStateStore - 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. """ @@ -184,7 +184,7 @@ class AgentRunContextBuilder: Args: event: Event envelope - binding: Agent binding configuration + binding: Agent binding descriptor: Runner descriptor resources: Built resources @@ -205,7 +205,7 @@ class AgentRunContextBuilder: conversation: ConversationContext | None = None if event.conversation_id: conversation = { - 'session_id': None, # Pipeline adapter field + 'session_id': None, 'conversation_id': event.conversation_id, 'thread_id': event.thread_id, '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, 'bot_id': event.bot_id, 'workspace_id': event.workspace_id, - 'pipeline_uuid': binding.pipeline_uuid, # Pipeline adapter field } # Build event context (Protocol v1 event-first) @@ -277,7 +276,7 @@ class AgentRunContextBuilder: 'model_context_window_tokens': None, # TODO(model-info): populate model_context_window_tokens after # 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) adapter_context = { 'query_id': None, - 'pipeline_uuid': binding.pipeline_uuid, 'extra': {}, } diff --git a/src/langbot/pkg/agent/runner/errors.py b/src/langbot/pkg/agent/runner/errors.py index ee3223dc..1755eff9 100644 --- a/src/langbot/pkg/agent/runner/errors.py +++ b/src/langbot/pkg/agent/runner/errors.py @@ -15,7 +15,7 @@ class RunnerNotFoundError(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): self.runner_id = runner_id self.bound_plugins = bound_plugins @@ -34,4 +34,4 @@ class RunnerExecutionError(AgentRunnerError): def __init__(self, runner_id: str, message: str, retryable: bool = False): self.runner_id = runner_id self.retryable = retryable - super().__init__(f'Agent runner {runner_id} execution failed: {message}') \ No newline at end of file + super().__init__(f'Agent runner {runner_id} execution failed: {message}') diff --git a/src/langbot/pkg/agent/runner/host_models.py b/src/langbot/pkg/agent/runner/host_models.py index 96e578ff..b98ff365 100644 --- a/src/langbot/pkg/agent/runner/host_models.py +++ b/src/langbot/pkg/agent/runner/host_models.py @@ -73,11 +73,11 @@ class AgentEventEnvelope(pydantic.BaseModel): class BindingScope(pydantic.BaseModel): """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_id: str | None = None - """Scope identifier (bot_uuid, pipeline_uuid, etc.).""" + """Scope identifier (agent_id, bot_uuid, etc.).""" class ResourcePolicy(pydantic.BaseModel): @@ -153,7 +153,7 @@ class AgentBinding(pydantic.BaseModel): """Runner ID to invoke.""" 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.""" @@ -167,6 +167,5 @@ class AgentBinding(pydantic.BaseModel): enabled: bool = True """Whether binding is enabled.""" - # Fields for Pipeline adapter - pipeline_uuid: str | None = None - """Pipeline UUID (for Pipeline adapter).""" + agent_id: str | None = None + """Host-side Agent/config identifier for this binding.""" diff --git a/src/langbot/pkg/agent/runner/orchestrator.py b/src/langbot/pkg/agent/runner/orchestrator.py index f479d4cc..f7c43129 100644 --- a/src/langbot/pkg/agent/runner/orchestrator.py +++ b/src/langbot/pkg/agent/runner/orchestrator.py @@ -93,9 +93,9 @@ class AgentRunOrchestrator: Args: event: Event envelope from event gateway - binding: Agent binding configuration + binding: Agent binding 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: Message or MessageChunk for pipeline response @@ -125,12 +125,12 @@ class AgentRunOrchestrator: resources=resources, ) - # Merge adapter context if provided (for Pipeline adapter) + # Merge adapter context if provided if adapter_context: # Merge params into adapter.extra if 'params' in adapter_context: 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: context['adapter']['extra']['prompt'] = adapter_context['prompt'] # Set query_id if provided @@ -420,7 +420,7 @@ class AgentRunOrchestrator: Args: result_dict: Raw result dict with type='state.updated' event: Event envelope - binding: Agent binding configuration + binding: Agent binding descriptor: Runner descriptor """ data = result_dict.get('data', {}) diff --git a/src/langbot/pkg/agent/runner/pipeline_adapter.py b/src/langbot/pkg/agent/runner/pipeline_adapter.py index 6a5df3d5..7480499f 100644 --- a/src/langbot/pkg/agent/runner/pipeline_adapter.py +++ b/src/langbot/pkg/agent/runner/pipeline_adapter.py @@ -114,12 +114,11 @@ class PipelineAdapter: pipeline_config = query.pipeline_config or {} ai_config = pipeline_config.get('ai', {}) 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_type="pipeline", - scope_id=pipeline_uuid, + scope_type="agent", + scope_id=agent_id, ) # Build resource policy from pipeline config @@ -142,7 +141,7 @@ class PipelineAdapter: ) return AgentBinding( - binding_id=f"pipeline_{pipeline_uuid or 'default'}_{runner_id}", + binding_id=f"agent_{agent_id or 'default'}_{runner_id}", scope=scope, event_types=[runner_events.MESSAGE_RECEIVED], runner_id=runner_id, @@ -151,7 +150,7 @@ class PipelineAdapter: state_policy=state_policy, delivery_policy=delivery_policy, enabled=True, - pipeline_uuid=pipeline_uuid, + agent_id=agent_id, ) @classmethod @@ -335,9 +334,6 @@ class PipelineAdapter: # Handle bot_uuid bot_uuid = getattr(query, 'bot_uuid', None) - # Handle pipeline_uuid - pipeline_uuid = getattr(query, 'pipeline_uuid', None) - return ConversationContext( conversation_id=str(conversation_id) if conversation_id is not None else None, thread_id=None, @@ -347,7 +343,6 @@ class PipelineAdapter: bot_id=bot_uuid, workspace_id=None, session_id=session_id, - pipeline_uuid=pipeline_uuid, ) @classmethod @@ -398,7 +393,6 @@ class PipelineAdapter: "launcher_id": getattr(query, 'launcher_id', None), "sender_id": str(getattr(query, 'sender_id', '')) if getattr(query, 'sender_id', None) else None, "bot_uuid": getattr(query, 'bot_uuid', None), - "pipeline_uuid": getattr(query, 'pipeline_uuid', None), }, ) diff --git a/src/langbot/pkg/agent/runner/resource_builder.py b/src/langbot/pkg/agent/runner/resource_builder.py index b4df99e5..161c08fa 100644 --- a/src/langbot/pkg/agent/runner/resource_builder.py +++ b/src/langbot/pkg/agent/runner/resource_builder.py @@ -23,7 +23,7 @@ class AgentResourceBuilder: - Apply 3-layer permission filtering: 1. Runner manifest declared permissions 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 tools list from bound plugins/MCP servers - Build knowledge_bases list from config @@ -68,7 +68,7 @@ class AgentResourceBuilder: # Layer 2: Binding resource policy resource_policy = binding.resource_policy - # Layer 3: Runner binding config + # Layer 3: Agent/runner config runner_config = binding.runner_config # Build each resource category @@ -112,7 +112,7 @@ class AgentResourceBuilder: # Get additional model UUID grants from resource policy. 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( models=models, seen_model_ids=seen_model_ids, diff --git a/tests/unit_tests/agent/test_config_migration_full.py b/tests/unit_tests/agent/test_config_migration_full.py index 37303a0f..87586af9 100644 --- a/tests/unit_tests/agent/test_config_migration_full.py +++ b/tests/unit_tests/agent/test_config_migration_full.py @@ -36,7 +36,6 @@ class TestMigratePipelineConfig: 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 '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 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.""" config = { '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') @@ -280,7 +279,7 @@ class TestResolveRunnerConfig: """resolve_legacy_runner_config should read old ai.local-agent for migration.""" config = { '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') @@ -293,7 +292,7 @@ class TestResolveRunnerConfig: 'runner_config': { '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') diff --git a/tests/unit_tests/agent/test_context_builder_state.py b/tests/unit_tests/agent/test_context_builder_state.py index b69246af..33ee4182 100644 --- a/tests/unit_tests/agent/test_context_builder_state.py +++ b/tests/unit_tests/agent/test_context_builder_state.py @@ -8,7 +8,7 @@ Tests focus on: from __future__ import annotations 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.host_models import AgentEventEnvelope, AgentBinding, BindingScope, StatePolicy @@ -67,7 +67,7 @@ class TestContextAccessStateDetermination: binding = AgentBinding( binding_id='binding_001', 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=True, state_scopes=['conversation', 'actor'], @@ -88,7 +88,7 @@ class TestContextAccessStateDetermination: binding = AgentBinding( binding_id='binding_001', 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=[], @@ -109,7 +109,7 @@ class TestContextAccessStateDetermination: binding = AgentBinding( binding_id='binding_001', 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=True, state_scopes=[], # Empty scopes - state not available @@ -177,7 +177,7 @@ class TestContextAccessStateDetermination: binding = AgentBinding( binding_id='binding_003', 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=True, state_scopes=['conversation', 'actor', 'subject', 'runner'], @@ -226,7 +226,7 @@ class TestBindingWithStatePolicy: binding = AgentBinding( binding_id='binding_001', 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=True, state_scopes=['conversation'], @@ -260,7 +260,7 @@ class TestContextAccessOtherAPIs: binding = AgentBinding( binding_id='binding_001', 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=[]), ) @@ -288,7 +288,7 @@ class TestContextAccessOtherAPIs: binding = AgentBinding( binding_id='binding_001', 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=[]), ) @@ -316,7 +316,7 @@ class TestContextAccessOtherAPIs: binding = AgentBinding( binding_id='binding_001', 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=[]), ) @@ -342,7 +342,7 @@ class TestContextAccessOtherAPIs: binding = AgentBinding( binding_id='binding_001', 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=[]), ) diff --git a/tests/unit_tests/agent/test_context_validation.py b/tests/unit_tests/agent/test_context_validation.py index 0ea003a8..cb090525 100644 --- a/tests/unit_tests/agent/test_context_validation.py +++ b/tests/unit_tests/agent/test_context_validation.py @@ -66,11 +66,11 @@ class TestContextValidation: """Create a test binding.""" return AgentBinding( 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"], runner_id="plugin:test/plugin/runner", runner_config={"timeout": 300}, - pipeline_uuid="pipeline_1", + agent_id="pipeline_1", enabled=True, ) diff --git a/tests/unit_tests/agent/test_event_first_protocol.py b/tests/unit_tests/agent/test_event_first_protocol.py index 8f311429..c5b12688 100644 --- a/tests/unit_tests/agent/test_event_first_protocol.py +++ b/tests/unit_tests/agent/test_event_first_protocol.py @@ -144,17 +144,9 @@ class TestPipelineConfigToBinding: 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 - - 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") - + assert binding.agent_id == mock_query.pipeline_uuid class TestAgentRunContextProtocolV1: """Test AgentRunContext Protocol v1 behavior.""" @@ -238,23 +230,14 @@ class TestAgentRunContextProtocolV1: assert ctx.bootstrap is None or isinstance(ctx.bootstrap.messages, list) -class TestHostContextWindowNotInProtocol: - """Test that Host-side context window controls are not in Protocol v1.""" +class TestHostManagedHistoryNotInProtocol: + """Test that Host-managed history payloads are not in Protocol v1.""" - def test_context_window_not_in_sdk_context(self): - """AgentRunContext should not expose Host-side window controls.""" + def test_messages_not_in_sdk_context_top_level(self): + """AgentRunContext should not expose top-level history messages.""" ctx_fields = AgentRunContext.model_fields.keys() - assert "max_round" 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") + assert "messages" not in ctx_fields class TestSDKCapabilitiesProtocolV1: diff --git a/tests/unit_tests/agent/test_event_log_transcript.py b/tests/unit_tests/agent/test_event_log_transcript.py index e6fb8d6a..3425c4c1 100644 --- a/tests/unit_tests/agent/test_event_log_transcript.py +++ b/tests/unit_tests/agent/test_event_log_transcript.py @@ -56,7 +56,7 @@ def make_binding(runner_id: str = "plugin:test/plugin/runner") -> AgentBinding: """Create a test binding.""" return AgentBinding( 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"], runner_id=runner_id, runner_config={}, diff --git a/tests/unit_tests/agent/test_orchestrator_integration.py b/tests/unit_tests/agent/test_orchestrator_integration.py index 97227a95..a984c401 100644 --- a/tests/unit_tests/agent/test_orchestrator_integration.py +++ b/tests/unit_tests/agent/test_orchestrator_integration.py @@ -347,7 +347,7 @@ async def test_orchestrator_does_not_package_query_messages_into_context(clean_a ap = FakeApplication(plugin_connector, db_engine) orchestrator = AgentRunOrchestrator(ap, FakeRegistry(descriptor)) 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 = [ provider_message.Message(role="user", content="message 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 context = plugin_connector.contexts[0] - assert context["config"]["agent-window"] == 2 + assert context["config"]["custom-option"] == 2 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 [message.content for message in query.messages] == [ "message 1", @@ -538,7 +538,7 @@ class TestPipelineCompatibilityQueryIdInSession: ) binding = AgentBinding( 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"], runner_id=RUNNER_ID, runner_config={}, diff --git a/tests/unit_tests/agent/test_state_store.py b/tests/unit_tests/agent/test_state_store.py index 05166110..2c917521 100644 --- a/tests/unit_tests/agent/test_state_store.py +++ b/tests/unit_tests/agent/test_state_store.py @@ -82,8 +82,8 @@ class FakeBinding: self, binding_id: str = 'binding_001', state_policy: StatePolicy | None = None, - scope_type: str = 'pipeline', - scope_id: str = 'pipeline_001', + scope_type: str = 'agent', + scope_id: str = 'agent_001', ): self.binding_id = binding_id self.scope = BindingScope(scope_type=scope_type, scope_id=scope_id)