Agent Platform · Detail Design

Memory / Context 设计

预检、召回、压缩、写回、隔离五个动作,把"上下文装配"从一个步骤升级为独立子系统,让模型带着正确上下文执行。

返回文档目录平台首页

预检先行

所有 LLM 调用前先做 token budget 检查,超长 prompt 永远不送进模型

压缩可证

压缩后必须保留用户目标、约束、工具关键结果、HITL 决策;信息丢失则拒绝

写回受控

只有成功 Run 才写长期记忆;失败、拒绝、取消路径绝不污染长期经验

详细设计说明

DESIGN DOCUMENT · MEMORY & CONTEXT

让模型带着"正确上下文"执行,而不是带着"所有历史"执行

这份文档把 Memory / Context 从"上下文装配的一个步骤"提升为独立子系统。重点回答五个动作(预检 / 召回 / 压缩 / 写回 / 隔离)如何在 token 预算、隐私边界、长期经验三个约束下协同工作,避免超长上下文、跨租户串数据、危险经验沉淀这三类典型故障。

设计主张

Memory 的核心不是"存得多",而是在合适的边界内召回必要信息,并能证明压缩后关键目标、约束和决策没有丢失。读路径要可预算,写路径要受控,scope 要硬隔离。

PrecheckRetrievalCompressionWritebackIsolation

评审关注点

  • 超长 prompt 是否在到 LLM 之前就被拦截
  • 压缩是否保留用户目标 / 约束 / 工具关键结果 / HITL 决策
  • 失败 / 拒绝 / 取消路径是否真的不写长期记忆
  • tenant / user / session 三层 scope 是否强隔离
  • OAuth token / cookie 等敏感字段是否会进长期 store
D1 预检先行LLM 前必检 token
D2 相关召回只带任务相关记忆
D3 压缩可证关键约束不能丢
D4 写回受控失败拒绝不入库
D5 多层隔离scope 硬切 + PII 过滤

1. 定位与概念

SECTION GOAL

Memory 子系统在每次 LLM 调用前后做五件事——召回 / 预检 / 压缩 / 写回 / 隔离。它不是 Context Assembler 的子模块:装配是"拼 prompt",Memory 是"上下文治理"——按 scope 控制召回什么、在 token 预算内决定丢什么、终态后决定写回什么。

1.1 总体架构图

Memory 跨三层存在:T1 配置(保留时长 / 写回开关 / scope 策略),T2 长期 + 团队共享(跨 Run 召回),T3 短期 + 装配(Run 内)。五个动作按 token 预算调度,外层受 scope 隔离硬约束。点色块跳章节。

Memory Subsystem · 五动作 + 三层 store + token 预算环
SCOPE ISOLATION · tenant_id / user_id / session_id MEMORY STORE · BY TIER T1 · CONFIG STORE System prompt · Persona Persona 配置 / 团队配置 / Agent 元数据 永远保留 · 不参与压缩 · 不参与写回 T2 · LONG-TERM + SHARED user_memory · agent_memory team_memory(共享) 长期偏好 / 项目事实 / 团队经验 需召回 · 需压缩 · 需写回控制 T3 · SHORT-TERM session_history · scratch 最近 N 轮对话 · 工具结果暂存 高频读写 · 压缩首选目标 · session 结束可丢 FIVE ACTIONS · CONTEXT ASSEMBLY PIPELINE ① 预检 Precheck token budget 估算 触发后续动作决策 ② 召回 Retrieval scope 维度过滤 优先级 + 相关性排序 ③ 压缩 Compression ▼ 超预算才触发 关键约束一票否决 LLM Call 已预算 · 已脱敏 · 已隔离的 ctx ④ 写回 Writeback 仅 SUCCEEDED 写长期 PII / token 过滤 ⑤ 隔离 + 安全过滤 Isolation tenant / user / session 三层 scope · OAuth / cookie / 临时密钥不入长期 store 作用于读路径(召回过滤)+ 写路径(写前剔除) TOKEN BUDGET ×0.8 window 上限 留 20% 给响应 仅成功终态 LEGEND T1 配置(不参与压缩 / 写回) T2 长期 + 共享(核心写回目标) T3 短期(压缩首选) 主路径 通过 / 写回 条件触发(超预算) KEY INVARIANTS ① 任何 LLM Call 之前都必须先过预检 ② 压缩后用户目标 / 约束 / 工具关键结果 / HITL 决策不得丢失 ③ 失败 / 拒绝 / 取消 / 超时 终态绝不写长期 memory ④ tenant_id 行级强制注入 · OAuth token / cookie / 临时密钥永不入长期 store
五个动作中 ①②④ 必走,③ 仅在超预算时触发,⑤ 是横切关注点同时作用于读路径和写路径。三层 store 按 Tier 区分写入策略:T1 配置只读、T2 长期受控写回、T3 短期可丢可压。

1.2 上下文装配时序图

五动作有序触发:Precheck(估 token)→ Retrieval(按 scope 召回)→(超预算才走)Compression → ctx 就绪 → LLM Call → 终态后 Writeback。左侧是 Harness s2_context_assemble,右侧是 Memory Store。点 lifeline 跳章节。

Context Assembly · 五动作有序触发时序图
Harness s2 EXT BOUNDARY ① 预检 PRECHECK ② 召回 RETRIEVAL ⑤ 隔离过滤 SCOPE GUARD ③ 压缩 COMPRESSION ④ 写回 WRITEBACK LLM API EXT Memory Store T1+T2+T3 1 assemble(input + history) 2 ↻ 估算 system + history + tools schema 3 trigger retrieve · target_budget 4 query(scope filter · tenant + user + session) → 候选条目 5 候选 memory + session 摘要返回 6 交叉验证 scope 7 已脱 PII 候选 8 候选 + 召回 token 总量 9 ⚠ 超预算 → trigger compress(条件触发) 10 ↻ 裁剪 → 摘要 → LLM 压缩 → 二次预检 11 压缩后 token 已合规 12 ctx ready ✓ PHASE · LLM CALL + TERMINAL WRITEBACK 13 LLM Call · 已预算 / 已脱敏 / 已隔离的 ctx 14 LLM 响应 → Harness 收口 15 终态 = SUCCEEDED → trigger writeback 16 写 user/agent/team_memory · PII 已剔除 ✕ FAILED / REJECTED / CANCELLED → 跳过写回(仅写 session 摘要)
实线 = 必走 · 虚线 = 条件触发(压缩仅超预算才走、写回仅成功终态才走) · ↻ = 组件内自循环 · 数字编号 = 跨组件调用顺序。关键不变量:步骤 12 的 ctx ready 是 LLM Call 的前置条件,没有它绝不能进入步骤 13;步骤 15 的终态判断是写回的硬门禁,失败路径直接跳到底部虚线。

1.3 为什么要做:缺口与解决的问题

6 类已在生产发生过的真实故障 → 对应 D1–D5 五条强约束。

缺口未做时的现象本设计的解决方式
没有预检历史拼太长直接被 LLM 拒绝(context length exceeded),用户看到一堆错误重试也没用D1 预检先行:调用 LLM 之前先估算 token,触发召回限流或压缩
召回粒度过粗把整段历史塞进 prompt,模型注意力被无关历史冲淡,回答跑题D2 相关召回:按优先级 + token cap 维度分层召回
压缩信息丢失简单截断丢掉了用户最初的目标,模型走偏;HITL 审批结果被丢,重新跑还是被拦D3 压缩可证:关键约束清单一票否决,丢了就拒绝执行
失败经验沉淀被 Policy 拒绝的危险操作经过总结后进了长期记忆,下次又被建议D4 写回受控:仅 SUCCEEDED 终态写长期,REJECTED / FAILED 一律不写
跨租户数据泄漏A 租户的项目事实被召回到 B 租户的 promptD5 多层隔离:tenant_id 行级强制注入,DAO 层一票否决
OAuth token 进长期临时凭据被压缩进对话摘要,长期存在 store 里造成横向泄漏D5 写前安全过滤:token / cookie / 临时密钥写前必剔

1.4 三层职责分工

Memory store 横跨三层、每层策略不同——T1 只读底座、T3 用完即弃、T2 是所有写回与召回的唯一持久层。

Tier承担的 store核心职责是否参与压缩 / 写回
Tier 1 · 配置(蓝)System prompt · Persona · Agent 元数据 · Team 配置提供"模型是谁、能力是什么"的稳定底座,跨 Run 不变不压缩、不写回——这一层是只读的
Tier 2 · 长期 + 共享(紫)user_memory(按用户)· agent_memory(按 Agent)· team_memory(按团队,共享)沉淀长期偏好 / 项目事实 / 团队经验,是写回的核心目标需召回、需压缩、需写回受控——所有 D3/D4/D5 约束都作用在这一层
Tier 3 · 短期(金)session_history(最近 N 轮)· tool result scratch(工具结果暂存)承载当次 session 的对话 + 工具暂存,session 结束可丢压缩首选目标——超预算时优先摘要这一层;不直接写长期 store

"是否写长期"的决策只在 T2 发生——配合终态门禁(仅 SUCCEEDED 写)+ PII 过滤(写前剔 token / cookie / 临时密钥)形成闭环。


2. 预检 Precheck

SECTION GOAL

说明在 LLM 调用之前如何估算 token 预算、何时触发后续动作、超预算的判定阈值是多少,保证超长 prompt 永远不进 LLM。

2.1 职责与触发时机

预检是 Memory 子系统的入口:每次 Harness 进入 s2_context_assemble 阶段,就会先做一次预检估算,再决定召回、压缩、装配的具体策略。它的核心输出是"当前估算 token / 目标预算 / 是否需要压缩"三个标量,下游所有动作都基于这个输出做决策。

2.2 token 预算估算口径

估算的是"如果按当前候选条目全量装配,会用掉多少 token"。组成包括 system prompt、history(按 session 配置的最近 N 轮)、工具 schema 描述、候选记忆条目(来自召回前的初步候选集)四部分。估算精度不要求字面级精确——能在 5% 误差内即可,足够支撑触发决策。

2.3 触发压缩的判定规则

把估算结果与目标预算比较,目标预算定为 model_context_window × 0.8,留 20% 给响应输出。判定规则按优先级如下。

判定情境处理动作
估算 ≤ 目标预算 × 0.6无需压缩,直接进入召回与装配
0.6 < 估算 / 预算 ≤ 1.0触发轻量裁剪:减少 history 轮数 + 限制召回 top-N
估算 > 目标预算触发完整压缩链路(详见第 4 章)
压缩二次预检仍超预算拒绝执行,写 ErrorTrace + 退到无记忆模式或终止

2.4 输出 Trace

预检每次产出一条 StepTrace,作为 s2 的子步骤记录,包含current_tokens、target_budget、decision(pass / trim / compress / reject)、candidate_count 四个关键字段。这条 trace 是后续调试"为什么 prompt 这么长"的第一手数据。


3. 召回 Retrieval

SECTION GOAL

给出召回管道的总览定位;具体打分公式、Hybrid 检索、阈值截断、Provider 抽象等实现细节单独写在 Memory Retrieval 召回算法设计 ↗。

召回是 Memory 子系统里最复杂的一个动作——它涉及向量检索、关键词检索、多维打分、阈值截断、降级路径多个环节,单独成篇详细描述。本节只给总览,所有实现细节请直接看专篇文档。

3.1 五阶段管道

阶段核心职责
① scope 过滤tenant / user / persona / team 行级硬隔离,DAO 层强制注入,scope 缺失直接终止 Run
② 候选生成Hybrid Search:向量召回 + BM25 关键词召回,RRF 融合产出 topN_candidate
③ 打分排序四维加权:语义相似度 + 时间衰减 + 频次加成 + 来源可信度
④ 截断双闸score 阈值(质量优先)+ topK 上限 + token 预算(≤ target_budget × 0.4)三者取交集
⑤ Trace每次召回写一条 memory_retrieve ToolTrace,记录 scope / 候选 / 打分分布 / 截断 / 降级标记

3.2 与本子系统的边界

Retrieval 的输入:当前 query、CallerIdentity(来自 RunContext)、token 预算余量(来自 §2 Precheck)。输出:一组 MemoryItem,由 §4 Compression 决定是否要进一步压缩、由 ctx 装配最终拼成 system message 段。所有写入 store 的元数据(来源 / 写入时间 / 频次)由 §5 Writeback 维护,召回时只读不写。

详细设计:Memory Retrieval 召回算法设计 →(scope 过滤的 DAO 注入细节、Hybrid 融合公式、四维打分的归一与权重区间、截断三闸优先级、六类故障降级动作、ToolTrace 字段集、Provider 选型边界)


4. 压缩 Compression

SECTION GOAL

说明超预算时如何分级压缩、压缩中绝不能丢失的关键信息清单、压缩失败时的降级链。

4.1 压缩策略链

压缩是分级递进的,先做轻量动作再做重量动作;每一级压缩后都做一次预检,能进入预算就停止。

  1. 裁剪冗余历史 · 去重相同事实、合并相近操作(无信息损失)
  2. 摘要工具结果 · 超长 tool_result 只保留摘要 + 引用 ID,原始结果留在 trace
  3. LLM 压缩历史对话 · 用专用压缩 LLM 把若干轮对话合并成一段摘要
  4. 二次预检 · 仍超预算 → 拒绝执行(绝不允许超长 prompt 进 LLM)

4.2 关键约束(一票否决清单)

压缩本质是"丢信息以换 token",但有些信息丢了之后整个 Run 就失去意义。这份清单是压缩链路的硬约束:压缩前后必须存在;丢失任意一项,本次压缩失败,回退到上一级压缩或拒绝执行。

必保留项原因校验方式
用户目标(最初的 user input)丢了模型不知道在做什么压缩前后都必须包含原始 input 的语义指纹
用户约束(明确说"不要做 X")丢了模型可能直接做禁止操作关键否定词 + 约束陈述必须在压缩输出中保留
工具关键结果(影响后续决策的工具输出)丢了模型基于错误事实推理tool_result 摘要必须保留 status + 关键字段值
HITL 决策(已批准 / 已拒绝)丢了重新跑还要再审批一次HITL 事件必须以原始结构形式保留,不能摘要

4.3 降级链

压缩本身可能失败(专用 LLM 不可用、超时、输出格式错误等),需要明确的降级路径。

触发情境降级动作
压缩 LLM 调用成功 + 关键约束保留使用压缩结果,正常进入装配
压缩 LLM 调用失败 / 超时降级为简单裁剪(截取最近 N 轮 + 工具结果摘要)
简单裁剪后仍超预算降级为"无记忆执行"——只保留 system + 当前 input
无记忆模式仍超预算(极少见,仅当 system 本身过长)拒绝执行 · 写 ErrorTrace · Run 进入 FAILED 终态
压缩成功但关键约束清单校验未过视为压缩失败,按上述链路降级(不接受"目标都丢了的压缩")

压缩动作每次写一条 StepTrace + summary_id,记录原始 token / 压缩后 token / 压缩模型 / 关键约束校验结果,便于事后追溯"这条 prompt 为什么变短了"。


5. 写回 Writeback

SECTION GOAL

说明哪些终态才允许写长期记忆、写前必须做的安全过滤、不同终态的差异化处理,避免"危险经验"被沉淀。

5.1 按终态决定写回

写回是 Run 收口阶段(Harness s8_trace 之后、Run finalize)的最后一个动作。是否写长期记忆完全由 RunState 终态决定,不允许业务层覆盖。

终态user_memoryagent_memorysession 摘要语义
SUCCEEDED写(稳定偏好 / 项目事实 / 长期结论)写(岗位上下文)写唯一允许写长期的终态
CLOSED_FAILED不写不写写(仅错误 trace 引用,方便复盘)不沉淀失败结论,避免错误经验复用
CLOSED_REJECTED · Policy 拒绝绝对不写不写写"被拒绝"事实禁止把危险操作经验写入长期
CLOSED_REJECTED · HITL 拒绝不写不写写不写"已完成事实"
CLOSED_CANCELLED · 用户取消不写不写写不写不完整结论
TIMEOUT不写不写写状态不确定,不沉淀

5.2 写前安全过滤

即便终态是 SUCCEEDED,写入长期 store 之前还要过一道安全过滤——这一层是 Memory 子系统对外的最后一道隐私边界。

字段类型处理动作
临时密钥 / OAuth token / cookie / 临时密码必须剔除,绝不入长期 store
一次性参数(本次调用的具体 ID / 临时文件路径)不写长期,仅留在 trace
短期 PII(手机号 / 身份证号)保持 mask 状态写入,不存原文
邮箱 / 用户名等长期 PII允许写入但需打上 PII tag,便于后续 GDPR 删除
金额 / 业务事实允许写入,配合 confidence 评分

5.3 写回元数据

每条写入的长期 memory 都要带溯源信息:source_run_id(来自哪次 Run)、confidence(0-1 置信度,未来支持基于多次确认升权)、expires_at(可选过期时间)。这些字段是后续做"记忆版本化、过期淘汰、置信度更新"的基础。

写回动作每次写一条 StepTrace + AuditLog 关键事件 memory_written,记录条目种类、大小、source_run_id,便于审计"这条记忆是怎么进来的"。


6. 隔离 + 安全过滤

SECTION GOAL

把 scope 隔离与隐私过滤合并讨论:前者管"读路径不串",后者管"写路径不漏",两者共同构成 Memory 子系统的安全边界。

6.1 三层 scope 强制隔离

scope 是 Memory 子系统最底层的安全约束,任何召回 / 写回路径都必须先过 scope。三层从强到弱依次为 tenant、user、session。

层级字段实施位置违反后果
tenanttenant_idDAO 行级强制注入 · 编译期校验一票否决,违反即视为重大事故
useruser_id + createByDAO 行级过滤 · 业务层校验视为越权访问,立即终止 Run
sessionsession_id应用层过滤 · 仅 session 私有摘要可见跨 session 串数据,写 WarnTrace + 返回空

6.2 隔离不变式(必测)

这三条不变式是上线前必须通过的验收测试,缺一项不允许放行。

  1. 用户隔离:用户 A 的 user_memory 在任何召回 / 检索 / 列表接口下,都不得被用户 B 的 Run 看到
  2. session 隔离:不同 session 的 session 私有摘要不得跨串(团队共享 memory 不在此约束内,那走 team scope)
  3. tenant 隔离:不同租户的任何 memory 在任何路径下都不得跨可见,包括误用 SQL、误用缓存键、误用向量索引

6.3 安全过滤与 scope 的协同

scope 隔离管"读路径不串",安全过滤管"写路径不漏"。两者协同覆盖三类典型故障:

故障类型scope 隔离的角色安全过滤的角色
跨租户数据看见tenant_id 行级过滤兜底不直接相关
OAuth token 落入长期 store不直接相关写前剔除 token / cookie
把别的用户的偏好误召回user_id 行级过滤 + 二次校验不直接相关
对话摘要里嵌入了临时密钥不直接相关压缩 LLM 输出过 PII 检测器
团队 memory 被非成员召回team_id + 成员关系双向校验不直接相关

6.4 PII 处理策略

PII 不是简单的"全部脱敏"——不同类型 PII 的处理策略不同。设计原则是:能 mask 就 mask,必须保留原文的字段(如长期联系邮箱)打 PII tag 便于后续 GDPR 删除。

PII 类型处理动作原因
手机号 / 身份证号mask 后写入(保留前 3 后 4)支持模糊匹配但避免完整暴露
邮箱(业务联系用)原文写入 + PII tag后续召回需要精确匹配
姓名原文写入 + PII tag对话语境需要
地址 / 金额 / 业务数据原文写入 + PII tag语义不可替代
临时密钥 / OAuth token / cookie绝不写入不是 PII 而是凭据,长期保留无意义且高风险

所有打了 PII tag 的字段在 store 层支持按 user_id 一键删除,配合公司 GDPR 流程使用。