Files
Partner/doc/context/context-workspace.md

8.1 KiB
Raw Permalink Blame History

上下文工作空间

本文介绍 Partner 中 ContextWorkspace 机制的设计思路与工作流程。

Partner 是一个多模块、异步协作的智能体运行时,因此,“如何使得上下文能够在各个模块中有序参与、有序共享信息”,是维持智能体个体感、前后一致性的重要问题。

ContextWorkspace 将不同来源的上下文建模为按域划分、按域查询的 ContextBlock。一个 ContextBlock 持有三种展开级别的 BlockContent,支持时间衰减与替代衰减的活跃分数,并声明它本身对应的“聚焦域”。调用方在需要上下文时,不直接读取所有历史状态,而是向 ContextWorkspace 提交一组目标域,由工作空间负责筛选、激活、排序、聚合与渲染。

核心概念

什么是“域”

  • 域是一次上下文读取请求的目标语境,表示“这次调用方是为了哪类工作来索取上下文”。当前实现中域由 ContextBlock.FocusedDomain 枚举表示,包括 ACTIONMEMORYPERCEIVECOGNITIONCOMMUNICATION
  • resolve(domains) 接收的是一个有序域列表,而不是无序集合。列表中的第一个域会被视为主域;越靠前的域权重越高。工作空间会把域顺序转换为线性权重,并把匹配到多个目标域的 block 累积加权。
  • 域本身不是模块所有权,也不是存储分区。它更像一次读取上下文时的关注面:例如行动模块可以请求 ACTION 相关上下文,也可以在同一次请求中附带 MEMORYCOGNITION 作为辅助关注面。

什么是“聚焦域”

  • 聚焦域是 ContextBlock 生产方对该上下文块用途的声明,由 focusedOn: Set<FocusedDomain> 表示。它回答的是:“这个 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 的同源判断只看 blockNamesource,也就是 SourceKey(blockName, source)。这意味着两个 block 即使内容不同,只要来自同一个逻辑来源,就会被视为同源上下文。

当注册新的同源 block 时,旧 block 不会立即被无条件删除,而是先执行替代衰减:

  • 如果旧 block 衰减后分数归零,它会被移除。
  • 如果旧 block 仍有活跃分数,它会保留下来,后续在 resolve 时可能与新 block 聚合为同一个结果块。
  • 新 block 总是会被加入工作空间,代表该来源的最新状态。

这种设计允许上下文同时表达“最新快照”和“仍有价值的历史快照”,但不会让同源历史无限堆积;旧内容会随着替代衰减和时间衰减自然退出。

解析与排序

resolve(domains)ContextWorkspace 的核心读取流程。它不会简单返回所有 block而是执行一组选择规则

  1. 空域列表直接返回空结果。
  2. 根据目标域顺序计算域权重,第一个域权重最高。
  3. 对工作空间内所有 block 先执行时间衰减,分数归零的 block 立即移除。
  4. 只保留 focusedOn 与目标域有交集的 block。
  5. 根据是否命中主域判定 PRIMARYSECONDARY exposure。
  6. 对命中的 block 执行激活,并使用 exposure 选择最终渲染的投影内容。
  7. 结果先按累积域权重降序,再按活跃分数降序排序;分数相同或权重相同时,再用 blockNamesource 提供稳定顺序。
  8. 排序后按 SourceKey 分组:单个 block 直接输出,多个同源 block 聚合输出。

聚合输出由 AggregatedBlockContent 完成。它会把同源 block 包装成一个 XML block其中活跃分数最高的渲染结果标记为 <snapshot>,其他结果标记为 <history_snapshot>。聚合块的 urgency 取自已渲染投影中的最高 urgency而不是无条件取完整内容的 urgency。

注册、过期与追踪

ContextWorkspace 提供两个状态变更入口:

  • register(contextBlock):注册新的上下文块,并对同源旧块执行替代衰减。
  • expire(blockName, source):按 SourceKey 移除所有同源上下文块。

每次注册或过期导致状态变化时,工作空间会写入 TraceRecorder,事件名为 context-workspace。记录内容包括本次 action、受影响的 source key以及当前工作空间中所有 block 的快照渲染内容。这让上下文系统的状态变化可以被外部观察和调试。

工作流程

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 projectionContextWorkspace 在“保留历史”和“压缩上下文”之间提供了一个动态平衡。