diff --git a/README.md b/README.md index 33d888ff..06f06429 100644 --- a/README.md +++ b/README.md @@ -140,10 +140,10 @@ Partner/ - [整体架构与运行流](doc/architecture/overview.md) - [配置中心](doc/config/configuration.md) - [模型提供商](doc/model/providers.md) +- [ContextWorkspace](doc/context/context-workspace.md) ### 待完成 -- [ContextWorkspace](doc/context/context-workspace.md) - [行动系统](doc/action/action.md) - [记忆存储与组织](doc/memory/memory.md) diff --git a/doc/context/context-workspace.md b/doc/context/context-workspace.md index e69de29b..abc17157 100644 --- a/doc/context/context-workspace.md +++ b/doc/context/context-workspace.md @@ -0,0 +1,105 @@ +# 上下文工作空间 + +本文介绍 Partner 中 `ContextWorkspace` 机制的设计思路与工作流程。 + +Partner 是一个多模块、异步协作的智能体运行时,因此,“如何使得上下文能够在各个模块中有序参与、有序共享信息”,是维持智能体个体感、前后一致性的重要问题。 + +`ContextWorkspace` 将不同来源的上下文建模为按域划分、按域查询的 `ContextBlock`。一个 `ContextBlock` 持有三种展开级别的 `BlockContent`,支持时间衰减与替代衰减的活跃分数,并声明它本身对应的“聚焦域”。调用方在需要上下文时,不直接读取所有历史状态,而是向 `ContextWorkspace` 提交一组目标域,由工作空间负责筛选、激活、排序、聚合与渲染。 + +## 核心概念 + +**什么是“域”** + +- 域是一次上下文读取请求的目标语境,表示“这次调用方是为了哪类工作来索取上下文”。当前实现中域由 `ContextBlock.FocusedDomain` 枚举表示,包括 `ACTION`、`MEMORY`、`PERCEIVE`、`COGNITION` 与 `COMMUNICATION`。 +- `resolve(domains)` 接收的是一个有序域列表,而不是无序集合。列表中的第一个域会被视为主域;越靠前的域权重越高。工作空间会把域顺序转换为线性权重,并把匹配到多个目标域的 block 累积加权。 +- 域本身不是模块所有权,也不是存储分区。它更像一次读取上下文时的关注面:例如行动模块可以请求 `ACTION` 相关上下文,也可以在同一次请求中附带 `MEMORY` 或 `COGNITION` 作为辅助关注面。 + +**什么是“聚焦域”** + +- 聚焦域是 `ContextBlock` 生产方对该上下文块用途的声明,由 `focusedOn: Set` 表示。它回答的是:“这个 block 对哪些语境有用?” +- 当调用方执行 `resolve(domains)` 时,工作空间会计算 `block.focusedOn` 与目标域列表的交集。没有交集的 block 不会进入本次结果;有交集的 block 才会继续参与激活、投影与排序。 +- 如果 block 命中了本次请求的第一个域,它会以 `PRIMARY` exposure 参与渲染;否则即使命中了后续辅助域,也只会以 `SECONDARY` exposure 参与渲染。这个区分决定了 block 最终能展开到多详细。 +- 同一个 block 可以聚焦多个域。例如某段 memory 既能支持 `MEMORY`,也能支持 `ACTION`,它就可以在行动决策和记忆整理两类请求中被激活。 + +**“展开层级”与“衰减分数”如何生效** + +- 每个 `ContextBlock` 内部有一个 `activationScore`,初始值为 `100.0`。当分数降到 `0.0` 时,该 block 会从工作空间中移除。 +- 分数会通过两类机制变化:其一是时间衰减,`timeFadeFactor` 按分钟折算到经过的秒数;其二是替代衰减,当新的 block 与旧 block 具有相同 `SourceKey(blockName, source)` 时,旧 block 会扣除 `replaceFadeFactor`。 +- 展开层级由当前活跃分数决定:低于 `30.0` 时为 `ABSTRACT`,低于 `70.0` 时为 `COMPACT`,否则为 `FULL`。 +- block 持有三份内容投影:`blockContent` 是完整内容,`compactBlock` 是压缩内容,`abstractBlock` 是摘要内容。缺省情况下,`compactBlock` 退化为完整内容,`abstractBlock` 退化为压缩内容。 +- `PRIMARY` exposure 至少会渲染到 compact:当 block 处于 full 层级时输出 `blockContent`,处于 compact 或 abstract 层级时输出 `compactBlock`。 +- `SECONDARY` exposure 不会渲染 full:当 block 处于 abstract 层级时输出 `abstractBlock`,处于 compact 或 full 层级时输出 `compactBlock`。 +- 每次命中目标域后,block 会根据 exposure 被激活。主域激活更强,辅助域激活更弱;同时实现通过 ceiling 限制层级跃迁,避免一个已经衰减到 abstract 的 block 在一次辅助命中中直接升回 compact 或 full。 + +## SourceKey 与同源快照 + +`ContextBlock` 的同源判断只看 `blockName` 与 `source`,也就是 `SourceKey(blockName, source)`。这意味着两个 block 即使内容不同,只要来自同一个逻辑来源,就会被视为同源上下文。 + +当注册新的同源 block 时,旧 block 不会立即被无条件删除,而是先执行替代衰减: + +- 如果旧 block 衰减后分数归零,它会被移除。 +- 如果旧 block 仍有活跃分数,它会保留下来,后续在 resolve 时可能与新 block 聚合为同一个结果块。 +- 新 block 总是会被加入工作空间,代表该来源的最新状态。 + +这种设计允许上下文同时表达“最新快照”和“仍有价值的历史快照”,但不会让同源历史无限堆积;旧内容会随着替代衰减和时间衰减自然退出。 + +## 解析与排序 + +`resolve(domains)` 是 `ContextWorkspace` 的核心读取流程。它不会简单返回所有 block,而是执行一组选择规则: + +1. 空域列表直接返回空结果。 +2. 根据目标域顺序计算域权重,第一个域权重最高。 +3. 对工作空间内所有 block 先执行时间衰减,分数归零的 block 立即移除。 +4. 只保留 `focusedOn` 与目标域有交集的 block。 +5. 根据是否命中主域判定 `PRIMARY` 或 `SECONDARY` exposure。 +6. 对命中的 block 执行激活,并使用 exposure 选择最终渲染的投影内容。 +7. 结果先按累积域权重降序,再按活跃分数降序排序;分数相同或权重相同时,再用 `blockName` 与 `source` 提供稳定顺序。 +8. 排序后按 `SourceKey` 分组:单个 block 直接输出,多个同源 block 聚合输出。 + +聚合输出由 `AggregatedBlockContent` 完成。它会把同源 block 包装成一个 XML block,其中活跃分数最高的渲染结果标记为 ``,其他结果标记为 ``。聚合块的 urgency 取自已渲染投影中的最高 urgency,而不是无条件取完整内容的 urgency。 + +## 注册、过期与追踪 + +`ContextWorkspace` 提供两个状态变更入口: + +- `register(contextBlock)`:注册新的上下文块,并对同源旧块执行替代衰减。 +- `expire(blockName, source)`:按 `SourceKey` 移除所有同源上下文块。 + +每次注册或过期导致状态变化时,工作空间会写入 `TraceRecorder`,事件名为 `context-workspace`。记录内容包括本次 action、受影响的 source key,以及当前工作空间中所有 block 的快照渲染内容。这让上下文系统的状态变化可以被外部观察和调试。 + +## 工作流程 + +```mermaid +flowchart TD + A["模块生成 BlockContent"] --> B["构造 ContextBlock\n声明 focusedOn 与衰减参数"] + B --> C["register(contextBlock)"] + C --> D{"是否存在同 SourceKey 旧块?"} + D -- "是" --> E["旧块 applyReplaceFade"] + E --> F{"旧块分数是否归零?"} + F -- "是" --> G["移除旧块"] + F -- "否" --> H["保留为历史快照候选"] + D -- "否" --> I["加入新块"] + G --> I + H --> I + I --> J["记录 context-workspace/register trace"] + + K["调用方提交有序目标域"] --> L["resolve(domains)"] + L --> M["时间衰减并移除失效块"] + M --> N["按 focusedOn 与目标域交集筛选"] + N --> O["判定 PRIMARY 或 SECONDARY exposure"] + O --> P["激活 block 并选择投影内容"] + P --> Q["按域权重、活跃分数、SourceKey 排序"] + Q --> R["同 SourceKey 聚合为 snapshot/history_snapshot"] + R --> S["返回 ResolvedContext"] +``` + +## 设计取向 + +`ContextWorkspace` 的重点不是保存尽可能多的历史,而是在多模块智能体运行时中提供一个可衰减、可聚焦、可解释的上下文选择层。模块只需要发布带有语义域声明的 `ContextBlock`;消费方只需要声明本次要处理的目标域。两者之间的筛选、降噪、投影、历史聚合与追踪,由工作空间统一承担。 + +这样可以避免两个常见问题: + +- 上下文过度展开:所有模块都读取完整历史,导致 prompt 噪音和状态漂移。 +- 上下文过早丢失:新状态覆盖旧状态后,仍有短期价值的历史信息无法参与后续推理。 + +通过活跃分数、聚焦域和 exposure-specific projection,`ContextWorkspace` 在“保留历史”和“压缩上下文”之间提供了一个动态平衡。 \ No newline at end of file