diff --git a/docs/architecture/agent-runtime-services-design.md b/docs/architecture/agent-runtime-services-design.md index 61de3cafd..25610365e 100644 --- a/docs/architecture/agent-runtime-services-design.md +++ b/docs/architecture/agent-runtime-services-design.md @@ -1,14 +1,11 @@ # Agent Runtime SDK 与 Runtime Services 设计 本文是 [`core-decomposition.md`](core-decomposition.md) 的开发设计文档,描述目标模块、 -接口、crate 内部结构和迁移保护。`bitfun-runtime-services` 已建立 typed service bundle、 -builder、provider registry、capability availability 和 fake provider 基础;`bitfun-agent-runtime` -已创建并承接可独立构建的 scheduler/background delivery、thread goal runtime、 -subagent visibility / availability、round-boundary yield / injection state -和 turn-outcome queue policy 纯决策;`bitfun-harness` -已创建并承接 workflow descriptor、legacy route plan 和 provider registry contract。 -未迁移的 session manager、prompt loop、concrete agent registry loading、concrete scheduler lifecycle 和 -concrete workflow execution 不得被描述为已完成。 +接口、crate 内部结构和迁移保护。本文不作为 PR 进度记录;已完成项、待执行项、PR 范围和 +issue 状态由 [`core-decomposition-plan.md`](../plans/core-decomposition-plan.md)、 +[`core-decomposition-completed.md`](../plans/core-decomposition-completed.md) 和跟踪 issue 维护。 + +除非发现目标分层、接口归属、行为边界或关键风险判断需要修正,后续 PR 不应修改本文。 ## 1. 设计目标与边界 @@ -25,11 +22,11 @@ concrete workflow execution 不得被描述为已完成。 bitfun-core-types bitfun-events bitfun-runtime-ports -bitfun-runtime-services # PR1 基础壳层 +bitfun-runtime-services # typed service bundle / capability availability bitfun-agent-tools tool-runtime -bitfun-agent-runtime # 已创建,承接 scheduler/background、thread goal、registry visibility 与 round-boundary 决策 -bitfun-harness # 已创建,当前承接 workflow descriptor / registry contract +bitfun-agent-runtime # agent kernel contracts and portable runtime decisions +bitfun-harness # workflow descriptor / provider / registry contracts bitfun-services-core bitfun-services-integrations bitfun-product-domains @@ -94,11 +91,9 @@ bitfun-runtime-services 目标 crate 创建或继续扩展准入: - 只有当 owner 边界、旧路径兼容、focused tests、依赖收益和 boundary check 都能同时落地时,才创建新的目标 crate。 -- `bitfun-runtime-services` 已按该准入建立基础壳层;继续扩展时仍必须保持 typed builder、本地 service、remote service 和 fake provider 三类注入路径可测试。 -- `bitfun-agent-runtime` 已通过 scheduler/background delivery、thread goal runtime、subagent visibility / availability - 和 round-boundary 纯决策满足创建准入;继续扩展时仍必须保持旧路径 facade、focused tests 和 boundary check。 -- `bitfun-harness` 已按 Deep Review、DeepResearch、MiniApp 三个 legacy-facade provider - 满足创建准入;继续扩展时仍必须保持 descriptor/registry、旧路径兼容、focused tests 和 boundary check。 +- `bitfun-runtime-services` 的扩展必须保持 typed builder、本地 service、remote service 和 fake provider 三类注入路径可测试。 +- `bitfun-agent-runtime` 的扩展必须保持旧路径 facade、focused tests 和 boundary check,且不得吸收 concrete service、product surface 或平台实现。 +- `bitfun-harness` 的扩展必须保持 descriptor / registry、旧路径兼容、focused tests 和 boundary check,且不得把 provider 注册误写成 concrete workflow execution。 - 若目标 crate 只能承接单个 helper 或只能通过 `bitfun-core` 才能测试,继续留在迁移期 facade,不提前拆 crate。 ## 2. 稳定接口与运行时服务 @@ -163,7 +158,7 @@ pub trait WorkspacePort: Send + Sync { ### 2.2 Runtime Services -当前 crate:`bitfun-runtime-services`。 +目标 owner crate:`bitfun-runtime-services`。 职责: @@ -241,42 +236,31 @@ Remote ports 的边界: ### 3.1 Agent Runtime SDK -当前 crate:`bitfun-agent-runtime`。 - -当前已承接范围: - -- background delivery 状态决策:Processing 注入当前运行 turn;Missing / Idle / Error 提交 agent-session follow-up turn。 -- persisted thread goal 的 portable DTO、status、continuation plan 和 tool response contract 已在 - `bitfun-runtime-ports`。 -- thread goal runtime 决策:turn token / wall-clock accounting、goal mutation、 - continuation / budget-limit / objective-updated plan、tool response assembly - 和 usage-limit / retry / skip-accounting policy。 -- subagent registry 决策:query scope、visibility policy、availability reason、 - builtin / project / user override layering 和 frontend-facing availability facts。 -- scheduler round-boundary 决策:yield flag、round injection buffer、turn outcome status / - reply text / queue action。 +目标 owner crate:`bitfun-agent-runtime`。 -仍留在 `bitfun-core` 的范围: - -- concrete scheduler 生命周期、session manager、turn id 生成、submit 执行、prompt loop、 - concrete agent definition loading、custom subagent file IO / config adapter、 - thread goal metadata store、token subscriber、scheduler delivery adapter、 - goal `Tool` handler、event delivery 和 post-turn hook。 - -职责: +目标职责: - session 生命周期。 - dialog turn / model round 生命周期。 - scheduler / queue / cancellation。 - prompt loop 和 context assembly。 - prompt cache 协调。 -- subagent registry 查询和 delegation policy。 +- agent definition registry、subagent registry 查询和 delegation policy。 - fork context seeding。 - tool call 调度。 - permission 协调。 - runtime events。 - post-turn processor。 +迁移期约束: + +- `bitfun-agent-runtime` 只能依赖稳定契约、Tool Runtime、Runtime Services 接口和注入的 provider。 +- concrete scheduler 生命周期、session metadata store、token subscriber、event delivery、product `Tool` + handler、concrete prompt assembly、workspace / remote / config IO、custom subagent file IO 和平台 adapter + 在行为等价未证明前不得下沉到 runtime kernel。 +- prompt、event、thread goal、scheduler 或 subagent 的纯事实可以先迁移,但每次迁移必须删除旧 owner + 实现主体,保留旧路径兼容,并补 focused contract test 与 boundary check。 + 建议内部模块: ```text @@ -443,12 +427,20 @@ pub struct ToolExecutionContext { } ``` -当前已收敛范围: +目标职责: + +- provider-neutral manifest、catalog、permission gate、execution admission、tool hook、execution result + presentation 和 result artifact policy。 +- `GetToolSpec` catalog、detail、assistant result 和 collapsed-tool unlock observation。 +- workspace service、path policy、runtime artifact reference、remote path containment 和 tool context facts 的 + 稳定 contract。 + +迁移期约束: -- deterministic execution admission gate、manifest / catalog / snapshot facade 和 `GetToolSpecTool` product adapter 已有独立 owner。 -- workspace file/shell service contract 已归入 `bitfun-runtime-ports`;core 保留旧路径 re-export 和 local / remote concrete adapter。 - 迁移期该 contract 保留既有 `anyhow::Result` 和 `CancellationToken`,不把错误分类或取消语义变更混入 owner 迁移。 -- collapsed unlock 的 `GetToolSpec` observation adapter 已归入 product runtime owner,execution engine 只消费已归一化的 unlock list。 +- core 可以保留旧路径 facade、concrete tool adapter、state update、registry lookup、confirmation、actual + execution 和 filesystem persistence,直到对应 owner 迁移有等价测试保护。 +- workspace file/shell contract 保留既有错误与取消语义;不得把错误分类、取消语义或产品 tool exposure + 变更混入 owner 迁移。 设计约束: @@ -473,13 +465,13 @@ pub struct ToolExecutionContext { ### 3.3 Harness Layer -当前 crate:`bitfun-harness`。 +目标 owner crate:`bitfun-harness`。 职责: -- 把 SDD、DeepReview、DeepResearch、MiniApp、function-agent 等工作流从 runtime kernel - 中分离。当前只承接 workflow descriptor、route plan 和 provider registry。 -- 定义 workflow plan、step、policy、artifact、review gate、post-processor。具体执行在迁移前继续留在旧路径。 +- 把 SDD、DeepReview、DeepResearch、MiniApp、function-agent 等工作流从 runtime kernel 中分离。 +- 定义 workflow descriptor、route plan、provider registry、workflow plan、step、policy、artifact、 + review gate 和 post-processor。 - 通过 Agent Runtime SDK、Tool Runtime 和 service ports 编排。 建议内部模块: @@ -534,7 +526,8 @@ pub struct HarnessExecutionContext { - harness 不直接访问 concrete filesystem / Git / terminal。 - 产品命令只映射到 harness capability,不把命令展示逻辑下沉。 - 新 harness 通过 provider 注册,不改 Agent Runtime SDK 内核。 -- PR4 阶段的 legacy-facade provider 只生成 route plan,不执行 workflow;执行迁移必须在后续 PR 单独证明行为等价。 +- descriptor-only / legacy-facade provider 只能表达 route plan;不得被描述为 concrete workflow execution + 已迁移。执行迁移必须单独证明行为等价。 ## 4. 产品组装与扩展 @@ -829,7 +822,7 @@ pub trait BeforeToolExecution: Send + Sync { - hook 不得获取未声明的具体 service。 - 修改 prompt / manifest / output 的 hook 必须有 snapshot 测试。 -## 5. 质量保护与完成定义 +## 5. 质量保护与目标态判定 ### 5.1 鲁棒性设计 @@ -906,7 +899,7 @@ Product 测试: - MCP dynamic tool catalog。 - MiniApp 与 review workflow。 -### 5.4 完成定义 +### 5.4 目标态判定口径 - `bitfun-agent-runtime` 能在不依赖 `bitfun-core` 的情况下构建 runtime kernel。 - `bitfun-runtime-services` 提供 typed service injection,并由 boundary check 保护。 diff --git a/docs/architecture/core-decomposition.md b/docs/architecture/core-decomposition.md index a5797dcf5..b86edbabb 100644 --- a/docs/architecture/core-decomposition.md +++ b/docs/architecture/core-decomposition.md @@ -5,6 +5,9 @@ [`core-decomposition-plan.md`](../plans/core-decomposition-plan.md)。详细接口、crate 内部模块和 测试设计见 [`agent-runtime-services-design.md`](agent-runtime-services-design.md)。 +本文不作为 PR 进度记录。除目标分层、接口归属、行为边界或关键风险判断发生变化外,后续 PR +不应修改本文;完成状态由 plan、completed 归档和跟踪 issue 维护。 + ## 1. 背景与目标 BitFun 当前已经从 `bitfun-core` 中抽出了若干 owner crate,但 `bitfun-core` 仍承担兼容 facade、 @@ -241,32 +244,29 @@ Computer Use 等能力的组合边界。它负责定义一个产品能力需要 工作流编排层(Harness Layer)承载多步骤工作流和策略编排,例如 SDD、Deep Review、DeepResearch、MiniApp 生成或更新流程。 它可以调用 Agent Runtime SDK、Tool Runtime 和 Runtime Services,但不拥有 session manager 内部状态、 -具体 filesystem/Git/terminal manager 或产品 UI。`bitfun-harness` 已承接 workflow descriptor、legacy route plan -和 provider registry contract;当前 Deep Review、DeepResearch、MiniApp 仍通过 legacy-facade provider -指向既有 core/product 执行路径,concrete workflow execution 尚未外移。 +具体 filesystem/Git/terminal manager 或产品 UI。`bitfun-harness` 目标上承接 workflow descriptor、route +plan 和 provider registry contract;descriptor-only / legacy-facade provider 只能表达 route,不代表 +concrete workflow execution 已迁移。 ### 7.5 Agent 运行时 SDK(Agent Runtime SDK) Agent 运行时 SDK(Agent Runtime SDK)是可嵌入的 agent kernel,负责 session、turn、scheduler、prompt loop、subagent、 background task、permission coordination 和 runtime events。它只依赖稳定契约、tool runtime 和注入的 -service ports,不感知 Desktop、CLI、Remote、ACP、Tauri 或 Web UI。当前主体仍在 `bitfun-core`, -但 `bitfun-agent-runtime` 已承接可独立构建的 scheduler/background delivery 和 thread goal -runtime 纯决策,并承接 subagent visibility / availability、round-boundary yield / injection state -和 turn-outcome queue policy。concrete scheduler lifecycle、session manager、prompt loop、 -concrete agent definition loading、custom subagent file IO / config adapter、thread goal metadata store、 -token subscriber、scheduler delivery adapter、goal `Tool` handler、event delivery 和 post-turn hook -仍由 core 执行。 +service ports,不感知 Desktop、CLI、Remote、ACP、Tauri 或 Web UI。`bitfun-agent-runtime` 目标上承接 +agent kernel 的稳定事实、纯决策和运行时 contract;concrete scheduler lifecycle、session manager、 +concrete prompt assembly、concrete agent definition loading、custom subagent file IO / config adapter、 +metadata store、token subscriber、scheduler delivery adapter、product `Tool` handler、event delivery 和 +post-turn hook 在行为等价未证明前不得下沉。 ### 7.6 工具运行时(Tool Runtime) 工具运行时(Tool Runtime)负责工具 manifest、catalog、permission gate、execution pipeline、tool hook 和结果归一化。 它只消费 `ToolExecutionServices` 这类窄 service 视图,不直接创建 filesystem、Git、terminal、MCP 等具体实现。 当前相关 crate 包括 `tool-runtime`、`bitfun-agent-tools`、`bitfun-tool-packs` 以及 `bitfun-core` -中的 tool materialization 代码。deterministic execution admission gate 已由 `bitfun-agent-tools` 承接; -`GetToolSpecTool` concrete adapter、manifest/catalog facade 和 snapshot wrapper 已收敛到 `bitfun-core` 的 -product runtime owner;collapsed unlock 的 GetToolSpec observation adapter 也由 product runtime owner 承接。 -workspace service contract 已归入 `bitfun-runtime-ports`,core 只保留旧路径 re-export 与 local / remote concrete -adapter。`bitfun-core` 的 pipeline 仍负责状态更新、registry lookup、input validation、confirmation、实际执行和 hook。 +中的 tool materialization 代码。Tool Runtime 目标上承接 provider-neutral manifest、catalog、permission +gate、execution admission、result presentation、artifact policy 和 `GetToolSpec` catalog / detail contract。 +core 可以保留 concrete tool adapter、state update、registry lookup、input validation、confirmation、 +actual execution、hook 和旧路径 facade,直到对应迁移有等价测试保护。 ### 7.7 运行时服务层(Runtime Services) @@ -275,8 +275,8 @@ filesystem、workspace、session store、Git、terminal、network、MCP catalog 等端口,不执行产品命令, 不作为无类型 service locator,也不创建平台实现。当前相关 crate 包括 `bitfun-runtime-ports`、 `bitfun-runtime-services`、`bitfun-services-core`、`bitfun-services-integrations` 和 `bitfun-core` 中的 service 接线代码。 -其中 workspace file/shell service contract 已归入 `bitfun-runtime-ports`;`bitfun-core::agentic::workspace` 仅保留 -旧路径 re-export 和 local / remote concrete adapter。 +workspace file/shell、remote connection / projection、session store、Git、terminal、network 和 MCP catalog +等接口应以 stable port 表达;core 可在迁移期保留旧路径 facade 和 concrete adapter。 ### 7.8 具体实现层(Concrete Integrations) @@ -295,8 +295,8 @@ SSH、relay、本地隧道、远端 OS 差异和认证方式属于具体 Remote 稳定契约层提供跨层共享的数据结构和接口语言,包括 DTO、event、permission facts、artifact refs、identity 和 port traits。它只描述事实和能力,不包含 IO、网络、进程、UI、runtime manager 或产品策略。当前相关 crate 包括 `bitfun-core-types`、`bitfun-events` 和 `bitfun-runtime-ports`。 -当前 remote workspace facts、remote session metadata、remote workspace file projection DTO 和 remote workspace/projection -host trait 已归入 `bitfun-runtime-ports`,`bitfun-services-integrations::remote_connect` 保留旧路径 re-export。 +remote workspace facts、remote session metadata、remote workspace file projection DTO 和 remote workspace/projection +host trait 应归入稳定契约;integration crate 可保留旧路径 re-export 以维持兼容。 ## 8. 接口与实现关系 @@ -338,7 +338,7 @@ flowchart TB | 注册器 / 组装点 | 所属目标层级 | 目标或迁移期模块 | 注册内容 | |---|---|---|---| | `ProductAssembler` / `ProductAssemblyPlan` | 产品组装层(Product Assembly) | 迁移期在 `bitfun-core` facade 或产品入口;目标可收敛为 assembly owner | `DeliveryProfile`、`CapabilitySet`、feature group、provider 选择 | -| `RuntimeServicesBuilder` | 运行时服务层(Runtime Services) | `bitfun-runtime-services` PR1 基础壳层;迁移期连接 `bitfun-runtime-ports`、`bitfun-services-*` 和 `bitfun-core` service wiring | filesystem、workspace、session store、Git、terminal、network、MCP catalog、remote connection / workspace / projection port | +| `RuntimeServicesBuilder` | 运行时服务层(Runtime Services) | `bitfun-runtime-services`;连接 `bitfun-runtime-ports`、`bitfun-services-*` 和迁移期 service wiring | filesystem、workspace、session store、Git、terminal、network、MCP catalog、remote connection / workspace / projection port | | `ToolRuntimeBuilder` | 工具运行时(Tool Runtime) | `tool-runtime`、`bitfun-agent-tools`、`bitfun-tool-packs` | tool provider、tool pack、manifest、permission gate、tool hook | | `HarnessRegistryBuilder` | 工作流编排层(Harness Layer) | `bitfun-harness`;迁移期由 `bitfun-core::agentic::harness` 注册 legacy-facade provider | SDD、Deep Review、DeepResearch、MiniApp 等 harness provider | | `AgentDefinitionRegistry` | Agent 运行时 SDK(Agent Runtime SDK) | 目标 `bitfun-agent-runtime`;迁移期在 `bitfun-core` agent definition 代码中 | agent、subagent、prompt module、skill definition | @@ -367,7 +367,7 @@ flowchart TB | core 拆分后仍隐式绑定 Tauri | Tauri 只允许在 Desktop provider / adapter;向下层传递 typed port、DTO、event fact 和 capability availability | | 不同产品形态能力矩阵漂移 | Product Assembly 维护 capability matrix;减少或替换能力时补产品入口验证和 unsupported 行为测试 | | Tool、MCP、ACP 的 manifest、permission 或事件语义迁移后不等价 | 保留旧路径兼容 facade,增加 manifest snapshot、permission 决策和事件映射等价测试 | -| Harness provider 只做注册但被误认为已经迁移执行语义 | PR4 阶段 provider 只生成 legacy route plan,execute 明确返回 unsupported;后续执行迁移必须单独证明行为等价 | +| Harness provider 只做注册但被误认为已经迁移执行语义 | descriptor-only / legacy-facade provider 只能生成 route plan;执行迁移必须单独证明行为等价 | | `bitfun-core` 只是改名为新的巨型 runtime crate | 新 owner crate 必须有单一职责和最小依赖;产品能力、harness、service 实现不得继续堆入 agent kernel | | 目标 crate 先行创建但没有真实 owner | 只有 owner 边界、旧路径兼容、focused tests、依赖收益和 boundary check 同时成立时才创建 crate;否则继续留在 facade | diff --git a/docs/plans/core-decomposition-completed.md b/docs/plans/core-decomposition-completed.md index 4eec66937..68cd92f54 100644 --- a/docs/plans/core-decomposition-completed.md +++ b/docs/plans/core-decomposition-completed.md @@ -57,7 +57,8 @@ - `bitfun-agent-runtime` 已建立为可独立构建的 Agent Runtime SDK owner crate,当前承接 scheduler/background delivery 纯决策,thread goal runtime 的 turn accounting、goal mutation、continuation plan 和 tool response assembly, subagent query scope / visibility / availability 决策,以及 round-boundary yield / injection state 和 - turn-outcome queue policy。 + turn-outcome queue policy;prompt-loop 的 user-context policy 和 tool / skill / subagent listing reminder + ordering 也已归入该 crate,core 只保留旧路径 re-export。 - persisted thread goal 的 portable DTO、status、continuation plan 和 tool response contract 已归入 `bitfun-runtime-ports`;`get_goal` / `create_goal` / `update_goal` 已进入产品 tool registry。 - `bitfun-harness` 已建立为可独立构建的 Harness contract crate,当前承接 workflow descriptor、legacy route @@ -66,7 +67,7 @@ 明确未完成: -- `bitfun-agent-runtime` 不代表 session manager、prompt loop、concrete agent definition loading、scheduler 生命周期、 +- `bitfun-agent-runtime` 不代表 session manager、concrete prompt assembly、concrete agent definition loading、scheduler 生命周期、 event delivery 或 post-turn hook 已迁移。 - thread goal 的 metadata store、token subscriber、scheduler delivery adapter 和 goal `Tool` handler 仍在 `bitfun-core`;runtime 决策已经归属 `bitfun-agent-runtime`,后续不应再把它误归入普通 concrete tool IO。 diff --git a/docs/plans/core-decomposition-plan.md b/docs/plans/core-decomposition-plan.md index caee695d6..f3ee0e5f8 100644 --- a/docs/plans/core-decomposition-plan.md +++ b/docs/plans/core-decomposition-plan.md @@ -121,15 +121,17 @@ PR1 不迁移任何 concrete service owner,因此预期不会修改产品行 已承接范围包括 scheduler/background delivery 纯决策,thread goal 的 turn accounting、goal mutation、 continuation / budget-limit / objective-updated plan、tool response assembly 和 skip/retry/usage-limit policy, 以及 subagent query scope、visibility / availability、round-boundary yield / injection state 和 -turn-outcome queue policy。 +turn-outcome queue policy,并已承接 user-context policy 与 listing reminder ordering 这类 prompt-loop +纯事实。 仍不外移 concrete scheduler 生命周期:core 继续负责 running-turn injection delivery、submit、turn id、metadata、session -manager、metadata store、token subscriber、scheduler delivery adapter、goal `Tool` handler、prompt loop、 +manager、metadata store、token subscriber、scheduler delivery adapter、goal `Tool` handler、concrete prompt assembly、 concrete agent definition loading、custom subagent file IO / config adapter、event delivery 和 post-turn hook。 后续 Agent Runtime SDK 工作不得再把 thread goal runtime 当作普通 concrete tool IO;继续推进时应聚焦 -agent definition registry loading、permission coordination、runtime events、prompt loop facts 和 concrete -scheduler lifecycle 的受保护迁移。 +agent definition registry loading、permission coordination、runtime events、prompt module / prompt cache +contract 和 concrete scheduler lifecycle 的受保护迁移。user-context policy 与 listing reminder ordering +这类 prompt-loop 纯事实已归入 `bitfun-agent-runtime`,不得再回流到 core。 #### 4.2.3 本次 PR 验收 @@ -174,7 +176,7 @@ PR4 不迁移 concrete service IO、tool IO、surface command 语义、session m - 保留 mode-scoped visibility、hidden/custom/review grouping、background result delivery、running-turn injection、idle-session follow-up 和 persisted thread goal continuation 语义。 - thread goal runtime、subagent visibility/availability、round-boundary yield/injection 和 turn-outcome queue policy 已归入 `bitfun-agent-runtime`;后续只允许 core 继续作为 metadata store、config/file IO adapter、 - concrete scheduler lifecycle、scheduler delivery、event delivery 和 `Tool` adapter。 + concrete prompt assembly、concrete scheduler lifecycle、scheduler delivery、event delivery 和 `Tool` adapter。 - 验证 subagent availability、queue/preempt/cancel suppression、DeepResearch citation / post-turn hook、goal verification events、`get_goal` / `create_goal` / `update_goal` tool response wire shape。 ### 5.3 Product-Domain Runtime Owner diff --git a/scripts/check-core-boundaries.mjs b/scripts/check-core-boundaries.mjs index 0081b4fb6..b333d9d9b 100644 --- a/scripts/check-core-boundaries.mjs +++ b/scripts/check-core-boundaries.mjs @@ -718,6 +718,36 @@ const forbiddenContentRules = [ }, ], }, + { + path: 'src/crates/core/src/agentic/agents/prompt_builder/user_context.rs', + patterns: [ + { + regex: /\bpub\s+enum\s+UserContextSection\b/, + message: + 'core prompt builder must not own user-context section facts; use bitfun-agent-runtime prompt contracts', + }, + { + regex: /\bpub\s+struct\s+UserContextPolicy\b/, + message: + 'core prompt builder must not own user-context policy facts; use bitfun-agent-runtime prompt contracts', + }, + ], + }, + { + path: 'src/crates/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs', + patterns: [ + { + regex: /\bpub\s+struct\s+ToolListingSections\b/, + message: + 'core prompt builder must not own tool-listing reminder facts; use bitfun-agent-runtime prompt contracts', + }, + { + regex: /\bpub\s+struct\s+PrependedPromptReminders\b/, + message: + 'core prompt builder must not own prepended-reminder ordering facts; use bitfun-agent-runtime prompt contracts', + }, + ], + }, { path: 'src/crates/core/src/agentic/tools/framework.rs', patterns: [ @@ -2254,6 +2284,59 @@ const forbiddenContentUnderRules = [ ]; const requiredContentRules = [ + { + path: 'src/crates/agent-runtime/src/prompt.rs', + reason: + 'agent-runtime must own prompt-loop facts that do not require concrete workspace or product IO', + patterns: [ + { + regex: /\bpub enum UserContextSection\b/, + message: 'missing agent-runtime user-context section contract', + }, + { + regex: /\bpub struct UserContextPolicy\b/, + message: 'missing agent-runtime user-context policy contract', + }, + { + regex: /\bpub struct ToolListingSections\b/, + message: 'missing agent-runtime tool-listing section contract', + }, + { + regex: /\bpub struct PrependedPromptReminders\b/, + message: 'missing agent-runtime prepended-reminder contract', + }, + ], + }, + { + path: 'src/crates/agent-runtime/tests/prompt_contracts.rs', + reason: + 'agent-runtime prompt owner must keep behavior-equivalence contracts for user context and reminder ordering', + patterns: [ + { + regex: /\buser_context_policy_preserves_order_and_deduplicates_sections\b/, + message: 'missing user-context policy order regression', + }, + { + regex: /\btool_listing_sections_render_only_present_sections\b/, + message: 'missing tool-listing rendering regression', + }, + { + regex: /\bprepended_prompt_reminders_keep_runtime_injection_order\b/, + message: 'missing prompt reminder ordering regression', + }, + ], + }, + { + path: 'src/crates/core/src/agentic/agents/prompt_builder/user_context.rs', + reason: + 'core prompt_builder user_context path must stay a compatibility facade over agent-runtime', + patterns: [ + { + regex: /pub use bitfun_agent_runtime::prompt::\{UserContextPolicy, UserContextSection\};/, + message: 'missing agent-runtime user-context compatibility re-export', + }, + ], + }, { path: 'src/crates/services-core/src/filesystem/mod.rs', reason: @@ -7298,6 +7381,27 @@ function runManifestParserSelfTest() { 'prompt_and_tool_response_contracts_match_thread_goal_wire_shape', ], }, + { + path: 'src/crates/agent-runtime/src/prompt.rs', + contracts: [ + 'UserContextSection', + 'UserContextPolicy', + 'ToolListingSections', + 'PrependedPromptReminders', + ], + }, + { + path: 'src/crates/agent-runtime/tests/prompt_contracts.rs', + contracts: [ + 'user_context_policy_preserves_order_and_deduplicates_sections', + 'tool_listing_sections_render_only_present_sections', + 'prepended_prompt_reminders_keep_runtime_injection_order', + ], + }, + { + path: 'src/crates/core/src/agentic/agents/prompt_builder/user_context.rs', + contracts: ['bitfun_agent_runtime::prompt'], + }, { path: 'src/crates/core/src/agentic/subagent_runtime/mod.rs', contracts: [ diff --git a/src/crates/agent-runtime/AGENTS.md b/src/crates/agent-runtime/AGENTS.md index b0f4c3ac1..cd2a7cadf 100644 --- a/src/crates/agent-runtime/AGENTS.md +++ b/src/crates/agent-runtime/AGENTS.md @@ -15,7 +15,11 @@ and tested without `bitfun-core`. - Prefer pure facts and decisions first: queue policy, background delivery, thread-goal accounting/mutation/continuation decisions, cancellation routing, runtime event facts, registry visibility/availability, round-boundary - yield/injection state, and turn-outcome queue decisions. + yield/injection state, turn-outcome queue decisions, prompt-loop user-context + policy, and prompt listing reminder ordering. +- Keep concrete prompt assembly, workspace context IO, prompt cache + coordination, and dynamic environment collection outside this crate until a + reviewed migration proves behavior equivalence. - Add focused tests before moving any runtime decision into this crate. ## Verification diff --git a/src/crates/agent-runtime/src/lib.rs b/src/crates/agent-runtime/src/lib.rs index 914d1c515..83461d812 100644 --- a/src/crates/agent-runtime/src/lib.rs +++ b/src/crates/agent-runtime/src/lib.rs @@ -4,5 +4,6 @@ //! depending on `bitfun-core` concrete session or scheduler lifecycle. pub mod agents; +pub mod prompt; pub mod scheduler; pub mod thread_goal; diff --git a/src/crates/agent-runtime/src/prompt.rs b/src/crates/agent-runtime/src/prompt.rs new file mode 100644 index 000000000..6b5acf3f1 --- /dev/null +++ b/src/crates/agent-runtime/src/prompt.rs @@ -0,0 +1,175 @@ +//! Prompt-loop owner facts and reminder ordering. + +const SKILL_LISTING_TITLE: &str = "# Skill Listing"; +const SKILL_LISTING_GUIDANCE: &str = + "The following skills are available for use with the Skill tool:"; +const AGENT_LISTING_TITLE: &str = "# Agent Listing"; +const AGENT_LISTING_GUIDANCE: &str = "Available subagent types for the Task tool:"; +const COLLAPSED_TOOL_LISTING_TITLE: &str = "# Collapsed Tool Listing"; +const COLLAPSED_TOOL_LISTING_GUIDANCE: &str = r#"The folling tools are intentionally collapsed. Their listed descriptions are short summaries rather than full usage instructions. +Before calling a collapsed tool, call `GetToolSpec` with its exact tool name to read its full definition and input schema. +After reading the returned spec, call the real tool directly by its own name. +If a tool spec is already available in the current conversation, do not call `GetToolSpec` for it again."#; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UserContextSection { + WorkspaceContext, + WorkspaceInstructions, + WorkspaceMemoryFiles, + ProjectLayout, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UserContextPolicy { + pub sections: Vec, +} + +impl UserContextPolicy { + pub fn empty() -> Self { + Self { + sections: Vec::new(), + } + } + + pub fn with_section(mut self, section: UserContextSection) -> Self { + if !self.includes(section) { + self.sections.push(section); + } + self + } + + pub fn without_section(mut self, section: UserContextSection) -> Self { + self.sections.retain(|existing| *existing != section); + self + } + + pub fn with_workspace_context(self) -> Self { + self.with_section(UserContextSection::WorkspaceContext) + } + + pub fn with_workspace_instructions(self) -> Self { + self.with_section(UserContextSection::WorkspaceInstructions) + } + + pub fn with_workspace_memory_files(self) -> Self { + self.with_section(UserContextSection::WorkspaceMemoryFiles) + } + + pub fn with_project_layout(self) -> Self { + self.with_section(UserContextSection::ProjectLayout) + } + + pub fn includes(&self, section: UserContextSection) -> bool { + self.sections.contains(§ion) + } + + pub fn cache_scope_key(&self) -> String { + if self.sections.is_empty() { + return "empty".to_string(); + } + + self.sections + .iter() + .map(UserContextSection::cache_scope_label) + .collect::>() + .join("|") + } +} + +impl Default for UserContextPolicy { + fn default() -> Self { + Self::empty() + } +} + +impl UserContextSection { + fn cache_scope_label(&self) -> &'static str { + match self { + Self::WorkspaceContext => "workspace_context", + Self::WorkspaceInstructions => "workspace_instructions", + Self::WorkspaceMemoryFiles => "workspace_memory_files", + Self::ProjectLayout => "project_layout", + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ToolListingSections { + pub skill_listing: Option, + pub agent_listing: Option, + pub collapsed_tool_listing: Option, +} + +impl ToolListingSections { + pub fn is_empty(&self) -> bool { + self.skill_listing.is_none() + && self.agent_listing.is_none() + && self.collapsed_tool_listing.is_none() + } + + pub fn render_skill_listing_reminder(&self) -> Option { + self.skill_listing.as_deref().map(|skill_listing| { + Self::render_section( + SKILL_LISTING_TITLE, + skill_listing, + Some(SKILL_LISTING_GUIDANCE), + ) + }) + } + + pub fn render_agent_listing_reminder(&self) -> Option { + self.agent_listing.as_deref().map(|agent_listing| { + Self::render_section( + AGENT_LISTING_TITLE, + agent_listing, + Some(AGENT_LISTING_GUIDANCE), + ) + }) + } + + pub fn render_collapsed_tool_listing_reminder(&self) -> Option { + self.collapsed_tool_listing + .as_deref() + .map(|collapsed_tool_listing| { + Self::render_section( + COLLAPSED_TOOL_LISTING_TITLE, + collapsed_tool_listing, + Some(COLLAPSED_TOOL_LISTING_GUIDANCE), + ) + }) + } + + fn render_section(title: &str, body: &str, description: Option<&str>) -> String { + match description { + Some(description) => format!("{}\n{}\n\n{}", title, description, body.trim()), + None => format!("{}\n{}", title, body.trim()), + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct PrependedPromptReminders { + pub skill_listing: Option, + pub agent_listing: Option, + pub collapsed_tool_listing: Option, + pub user_context: Option, +} + +impl PrependedPromptReminders { + pub fn ordered_reminders(&self) -> Vec<&str> { + let mut reminders = Vec::new(); + if let Some(collapsed_tool_listing) = self.collapsed_tool_listing.as_deref() { + reminders.push(collapsed_tool_listing); + } + if let Some(skill_listing) = self.skill_listing.as_deref() { + reminders.push(skill_listing); + } + if let Some(agent_listing) = self.agent_listing.as_deref() { + reminders.push(agent_listing); + } + if let Some(user_context) = self.user_context.as_deref() { + reminders.push(user_context); + } + reminders + } +} diff --git a/src/crates/agent-runtime/tests/prompt_contracts.rs b/src/crates/agent-runtime/tests/prompt_contracts.rs new file mode 100644 index 000000000..cde00dc2e --- /dev/null +++ b/src/crates/agent-runtime/tests/prompt_contracts.rs @@ -0,0 +1,72 @@ +use bitfun_agent_runtime::prompt::{ + PrependedPromptReminders, ToolListingSections, UserContextPolicy, UserContextSection, +}; + +#[test] +fn user_context_policy_preserves_order_and_deduplicates_sections() { + let policy = UserContextPolicy::empty() + .with_workspace_context() + .with_workspace_instructions() + .with_workspace_context() + .with_project_layout() + .without_section(UserContextSection::ProjectLayout) + .with_workspace_memory_files(); + + assert_eq!( + policy.sections, + vec![ + UserContextSection::WorkspaceContext, + UserContextSection::WorkspaceInstructions, + UserContextSection::WorkspaceMemoryFiles, + ] + ); + assert_eq!( + policy.cache_scope_key(), + "workspace_context|workspace_instructions|workspace_memory_files" + ); +} + +#[test] +fn user_context_policy_default_and_empty_scope_are_empty() { + assert_eq!(UserContextPolicy::default(), UserContextPolicy::empty()); + assert!(UserContextPolicy::default().sections.is_empty()); + assert_eq!(UserContextPolicy::empty().cache_scope_key(), "empty"); +} + +#[test] +fn tool_listing_sections_render_only_present_sections() { + let sections = ToolListingSections { + skill_listing: Some("skill-a\nskill-b".to_string()), + agent_listing: None, + collapsed_tool_listing: Some("Search: summary".to_string()), + }; + + assert!(!sections.is_empty()); + assert!(sections + .render_skill_listing_reminder() + .expect("skill listing should render") + .starts_with("# Skill Listing\nThe following skills are available")); + assert!(sections.render_agent_listing_reminder().is_none()); + assert!(sections + .render_collapsed_tool_listing_reminder() + .expect("collapsed tool listing should render") + .starts_with("# Collapsed Tool Listing\n")); +} + +#[test] +fn prepended_prompt_reminders_keep_runtime_injection_order() { + let reminders = PrependedPromptReminders { + skill_listing: Some("skills".to_string()), + agent_listing: Some("agents".to_string()), + collapsed_tool_listing: Some("collapsed-tools".to_string()), + user_context: Some("user-context".to_string()), + }; + + assert_eq!( + reminders.ordered_reminders(), + vec!["collapsed-tools", "skills", "agents", "user-context"] + ); + assert!(PrependedPromptReminders::default() + .ordered_reminders() + .is_empty()); +} diff --git a/src/crates/core/AGENTS-CN.md b/src/crates/core/AGENTS-CN.md index 107b76665..938cf0725 100644 --- a/src/crates/core/AGENTS-CN.md +++ b/src/crates/core/AGENTS-CN.md @@ -44,6 +44,10 @@ SessionManager → Session → DialogTurn → ModelRound `bitfun-agent-runtime`。core 保留 concrete agent definition loading、 custom subagent file IO/config adapter、desktop API wiring、concrete scheduler lifecycle、submit execution 和 event delivery。 +- Prompt-loop 的 user-context policy 和 tool / skill / subagent listing + reminder ordering 归属 `bitfun-agent-runtime`。core 保留具体 prompt + assembly、workspace / remote / project-layout context IO、language/config + lookup、prompt cache 协调和旧路径兼容 re-export。 - Tool 相关轻量 contract、portable tool context facts/provider、纯 manifest/exposure contract、generic registry / static-provider / dynamic-provider container、file guidance marker、file-read freshness 比较策略和 oversized tool-result preview/rendering 纯策略归属 `bitfun-agent-tools`;core tool runtime 通过 `product_runtime.rs` 统一负责产品工具组装、`dyn Tool` 适配、snapshot decoration、runtime manifest assembly / context filtering、按需工具说明发现(`GetToolSpec`)执行,以及 collapsed unlock observation source。 - `ToolUseContext`、session file-read state storage、tool-result filesystem writes 与具体工具实现继续留在 core,除非已有评审过的 port/provider 方案和等价测试。 - Tool 迁移必须保持 expanded/collapsed exposure、prompt 可见 manifest、`ToolUseContext.unlocked_collapsed_tools`,以及 desktop/MCP/ACP tool catalog 行为等价。 diff --git a/src/crates/core/AGENTS.md b/src/crates/core/AGENTS.md index d2f0f72ef..b3b4be02e 100644 --- a/src/crates/core/AGENTS.md +++ b/src/crates/core/AGENTS.md @@ -50,6 +50,10 @@ SessionManager → Session → DialogTurn → ModelRound `bitfun-agent-runtime`. Core keeps concrete agent definition loading, custom subagent file IO/config adapters, desktop API wiring, concrete scheduler lifecycle, submit execution, and event delivery. +- Prompt-loop user-context policy and tool/skill/subagent listing reminder + ordering belong in `bitfun-agent-runtime`. Core keeps concrete prompt + assembly, workspace / remote / project-layout context IO, language/config + lookup, prompt cache coordination, and old-path compatibility re-exports. - For tools, keep lightweight contracts, pure manifest/exposure contracts, generic contextual prompt-manifest resolver contracts, generic catalog snapshot provider contracts, generic GetToolSpec catalog provider/detail/ diff --git a/src/crates/core/src/agentic/agents/prompt_builder/mod.rs b/src/crates/core/src/agentic/agents/prompt_builder/mod.rs index b2eebd749..972e54537 100644 --- a/src/crates/core/src/agentic/agents/prompt_builder/mod.rs +++ b/src/crates/core/src/agentic/agents/prompt_builder/mod.rs @@ -1,8 +1,8 @@ mod prompt_builder_impl; mod user_context; +pub use bitfun_agent_runtime::prompt::{PrependedPromptReminders, ToolListingSections}; pub use prompt_builder_impl::{ - build_prompt_context_for_workspace, PrependedPromptReminders, PromptBuilder, - PromptBuilderContext, RemoteExecutionHints, ToolListingSections, + build_prompt_context_for_workspace, PromptBuilder, PromptBuilderContext, RemoteExecutionHints, }; pub use user_context::{UserContextPolicy, UserContextSection}; diff --git a/src/crates/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs b/src/crates/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs index f340f74f2..00c325913 100644 --- a/src/crates/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs +++ b/src/crates/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs @@ -1,5 +1,4 @@ //! System prompts module providing main dialogue and agent dialogue prompts -use super::user_context::{UserContextPolicy, UserContextSection}; use crate::agentic::util::remote_workspace_layout::build_remote_workspace_layout_preview; use crate::agentic::workspace::WorkspaceBackend; use crate::agentic::WorkspaceBinding; @@ -16,6 +15,9 @@ use crate::service::remote_ssh::workspace_state::get_remote_workspace_manager; use crate::service::workspace::get_global_workspace_service; use crate::service::workspace::RelatedPath; use crate::util::errors::{BitFunError, BitFunResult}; +use bitfun_agent_runtime::prompt::{ + PrependedPromptReminders, ToolListingSections, UserContextPolicy, UserContextSection, +}; use log::{debug, warn}; use std::path::Path; @@ -29,98 +31,6 @@ const PLACEHOLDER_VISUAL_MODE: &str = "{VISUAL_MODE}"; const PLACEHOLDER_SESSION_ID: &str = "{SESSION_ID}"; const USER_CONTEXT_PROMPT: &str = "As you answer the user's questions, you can use the following context.\nNote: this is a snapshot captured at the start of the conversation and may not reflect real-time changes made afterward."; -const SKILL_LISTING_TITLE: &str = "# Skill Listing"; -const SKILL_LISTING_GUIDANCE: &str = - "The following skills are available for use with the Skill tool:"; -const AGENT_LISTING_TITLE: &str = "# Agent Listing"; -const AGENT_LISTING_GUIDANCE: &str = "Available subagent types for the Task tool:"; -const COLLAPSED_TOOL_LISTING_TITLE: &str = "# Collapsed Tool Listing"; -const COLLAPSED_TOOL_LISTING_GUIDANCE: &str = r#"The folling tools are intentionally collapsed. Their listed descriptions are short summaries rather than full usage instructions. -Before calling a collapsed tool, call `GetToolSpec` with its exact tool name to read its full definition and input schema. -After reading the returned spec, call the real tool directly by its own name. -If a tool spec is already available in the current conversation, do not call `GetToolSpec` for it again."#; - -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct ToolListingSections { - pub skill_listing: Option, - pub agent_listing: Option, - pub collapsed_tool_listing: Option, -} - -impl ToolListingSections { - pub fn is_empty(&self) -> bool { - self.skill_listing.is_none() - && self.agent_listing.is_none() - && self.collapsed_tool_listing.is_none() - } - - pub fn render_skill_listing_reminder(&self) -> Option { - self.skill_listing.as_deref().map(|skill_listing| { - Self::render_section( - SKILL_LISTING_TITLE, - skill_listing, - Some(SKILL_LISTING_GUIDANCE), - ) - }) - } - - pub fn render_agent_listing_reminder(&self) -> Option { - self.agent_listing.as_deref().map(|agent_listing| { - Self::render_section( - AGENT_LISTING_TITLE, - agent_listing, - Some(AGENT_LISTING_GUIDANCE), - ) - }) - } - - pub fn render_collapsed_tool_listing_reminder(&self) -> Option { - self.collapsed_tool_listing - .as_deref() - .map(|collapsed_tool_listing| { - Self::render_section( - COLLAPSED_TOOL_LISTING_TITLE, - collapsed_tool_listing, - Some(COLLAPSED_TOOL_LISTING_GUIDANCE), - ) - }) - } - - fn render_section(title: &str, body: &str, description: Option<&str>) -> String { - match description { - Some(description) => format!("{}\n{}\n\n{}", title, description, body.trim()), - None => format!("{}\n{}", title, body.trim()), - } - } -} - -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct PrependedPromptReminders { - pub skill_listing: Option, - pub agent_listing: Option, - pub collapsed_tool_listing: Option, - pub user_context: Option, -} - -impl PrependedPromptReminders { - pub fn ordered_reminders(&self) -> Vec<&str> { - let mut reminders = Vec::new(); - if let Some(collapsed_tool_listing) = self.collapsed_tool_listing.as_deref() { - reminders.push(collapsed_tool_listing); - } - if let Some(skill_listing) = self.skill_listing.as_deref() { - reminders.push(skill_listing); - } - if let Some(agent_listing) = self.agent_listing.as_deref() { - reminders.push(agent_listing); - } - if let Some(user_context) = self.user_context.as_deref() { - reminders.push(user_context); - } - reminders - } -} - /// SSH remote host facts for system prompt (workspace tools run here, not on the local client). #[derive(Debug, Clone)] pub struct RemoteExecutionHints { @@ -679,9 +589,7 @@ mod tests { use super::PromptBuilder; use super::PromptBuilderContext; use super::ToolListingSections; - use super::{ - AGENT_LISTING_TITLE, COLLAPSED_TOOL_LISTING_TITLE, SKILL_LISTING_TITLE, USER_CONTEXT_PROMPT, - }; + use super::USER_CONTEXT_PROMPT; use crate::agentic::agents::UserContextPolicy; use crate::service::workspace::RelatedPath; @@ -718,13 +626,13 @@ mod tests { .expect("collapsed tool listing reminder should build"); let user_context = reminders.user_context.expect("user context should build"); - assert!(skill_listing.contains(SKILL_LISTING_TITLE)); + assert!(skill_listing.contains("# Skill Listing")); assert!(skill_listing.contains("")); - assert!(!skill_listing.contains(AGENT_LISTING_TITLE)); - assert!(agent_listing.contains(AGENT_LISTING_TITLE)); + assert!(!skill_listing.contains("# Agent Listing")); + assert!(agent_listing.contains("# Agent Listing")); assert!(agent_listing.contains("")); - assert!(!agent_listing.contains(COLLAPSED_TOOL_LISTING_TITLE)); - assert!(collapsed_tool_listing.contains(COLLAPSED_TOOL_LISTING_TITLE)); + assert!(!agent_listing.contains("# Collapsed Tool Listing")); + assert!(collapsed_tool_listing.contains("# Collapsed Tool Listing")); assert!(collapsed_tool_listing.contains("")); assert!(user_context.contains("# User Context")); assert!(user_context.contains(USER_CONTEXT_PROMPT)); diff --git a/src/crates/core/src/agentic/agents/prompt_builder/user_context.rs b/src/crates/core/src/agentic/agents/prompt_builder/user_context.rs index 9b4ef4186..ba5960a43 100644 --- a/src/crates/core/src/agentic/agents/prompt_builder/user_context.rs +++ b/src/crates/core/src/agentic/agents/prompt_builder/user_context.rs @@ -1,124 +1 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum UserContextSection { - WorkspaceContext, - WorkspaceInstructions, - WorkspaceMemoryFiles, - ProjectLayout, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UserContextPolicy { - pub sections: Vec, -} - -impl UserContextPolicy { - pub fn empty() -> Self { - Self { - sections: Vec::new(), - } - } - - pub fn with_section(mut self, section: UserContextSection) -> Self { - if !self.includes(section) { - self.sections.push(section); - } - self - } - - pub fn without_section(mut self, section: UserContextSection) -> Self { - self.sections.retain(|existing| *existing != section); - self - } - - pub fn with_workspace_context(self) -> Self { - self.with_section(UserContextSection::WorkspaceContext) - } - - pub fn with_workspace_instructions(self) -> Self { - self.with_section(UserContextSection::WorkspaceInstructions) - } - - pub fn with_workspace_memory_files(self) -> Self { - self.with_section(UserContextSection::WorkspaceMemoryFiles) - } - - pub fn with_project_layout(self) -> Self { - self.with_section(UserContextSection::ProjectLayout) - } - - pub fn includes(&self, section: UserContextSection) -> bool { - self.sections.contains(§ion) - } - - pub fn cache_scope_key(&self) -> String { - if self.sections.is_empty() { - return "empty".to_string(); - } - - self.sections - .iter() - .map(UserContextSection::cache_scope_label) - .collect::>() - .join("|") - } -} - -impl Default for UserContextPolicy { - fn default() -> Self { - Self::empty() - } -} - -impl UserContextSection { - fn cache_scope_label(&self) -> &'static str { - match self { - Self::WorkspaceContext => "workspace_context", - Self::WorkspaceInstructions => "workspace_instructions", - Self::WorkspaceMemoryFiles => "workspace_memory_files", - Self::ProjectLayout => "project_layout", - } - } -} - -#[cfg(test)] -mod tests { - use super::{UserContextPolicy, UserContextSection}; - - #[test] - fn chain_builder_preserves_order_and_dedupes_sections() { - let policy = UserContextPolicy::empty() - .with_workspace_context() - .with_workspace_instructions() - .with_workspace_context() - .with_project_layout() - .without_section(UserContextSection::ProjectLayout) - .with_workspace_memory_files(); - - assert_eq!( - policy.sections, - vec![ - UserContextSection::WorkspaceContext, - UserContextSection::WorkspaceInstructions, - UserContextSection::WorkspaceMemoryFiles, - ] - ); - } - - #[test] - fn default_policy_is_empty() { - assert!(UserContextPolicy::default().sections.is_empty()); - } - - #[test] - fn cache_scope_key_preserves_section_order() { - let policy = UserContextPolicy::empty() - .with_workspace_context() - .with_workspace_instructions() - .with_workspace_memory_files(); - - assert_eq!( - policy.cache_scope_key(), - "workspace_context|workspace_instructions|workspace_memory_files" - ); - } -} +pub use bitfun_agent_runtime::prompt::{UserContextPolicy, UserContextSection};