抽象记忆 (Virtual Memory)#
1. 概念#
抽象记忆是从实体记忆提炼出的紧凑索引。它不替代实体层,而是给实体层建一张带坐标的导航地图。
核心属性:
- Provenance-linked:每条记忆都带指针回指实体层出处
- Bi-temporal:每条记忆记两个时间——
event_time(事情发生时)和ingestion_time(记下来时) - 不可变追加:旧记录不删除,冲突时标
superseded_by
抽象记忆有三种类型:
| 类型 | 回答的问题 | 存储 |
|---|---|---|
| Timeline | 何时发生了什么 | memory/timeline/YYYY-MM.jsonl |
| Knowledge Graph | 什么和什么是什么关系 | memory/graph/{entities,edges}.jsonl |
| Core.md | LLM 每次必看的最小快照 | memory/core.md |
2. Timeline(时间轴)#
按时间组织的事件流。回答"何时发生了什么"。
2.1 创建#
触发:
- Session 空闲(idle ≥30 min)→ watcher 触发提炼
- Daily sleep(03:00)→ 批量整理
过程:
- 从实体层读新增的 DAG 节点(
iter_nodes_since()) - LLM 提取事件(Stage 2: extract)
- 每个事件挂
Provenance指针 - Append 到对应月份的 JSONL 文件
2.2 存储#
~/.openprogram/memory/timeline/
├── 2026-05.jsonl
├── 2026-06.jsonl
└── ...
单条记录:
{
"id": "ev_abc",
"summary": "在 OpenProgram 修了 Windows cp1252 编码 bug,涉及 38 个文件",
"kind": "work",
"provenance": {
"project_id": "proj_openprogram",
"session_id": "local_13d5",
"commit": "73bfc05",
"event_time": 1779900000,
"ingestion_time": 1779986400
},
"entities": ["project.openprogram", "issue.cp1252"]
}
kind 枚举:work | decision | learning | event
2.3 读取#
- 按时间范围:
memory_timeline(since, until) - 按关联实体:
memory_timeline(entity="project.openprogram") - 召回时注入最近高信号事件到 Core.md
2.4 更新#
Append-only。同一事实被重新提炼时,旧记录标 superseded_by 指向新记录 ID,不删除。
2.5 删除#
不删除。理由:事件是历史事实的记录,删除会破坏时间线完整性。标记 superseded 即可。
3. Knowledge Graph(知识图谱)#
实体 + 关系的图结构。回答"什么和什么是什么关系"。
3.1 创建#
触发:同 Timeline(session-end / daily sleep)。
过程:
- 提炼管道 Stage 2 从 DAG 抽取实体和关系
- Stage 3 做 alias resolution("worker"/"后端"/"daemon" → 同一节点)
- Stage 4 做矛盾检测(新边与旧边冲突 → 标 superseded)
- Append 到 JSONL
3.2 存储#
~/.openprogram/memory/graph/
├── entities.jsonl 点
├── edges.jsonl 边(带 bi-temporal + provenance)
└── views/
└── entity/<slug>.md 每个实体一个可读页面
实体:
{
"id": "project.openprogram",
"type": "project",
"name": "OpenProgram",
"attrs": {"path": "/Users/fzkuji/OpenProgram", "lang": "python"},
"scope": "global"
}
边:
{
"from": "issue.cp1252",
"to": "commit.73bfc05",
"relation": "fixed-by",
"event_time": 1779900000,
"ingestion_time": 1779986400,
"provenance": {"project_id": "proj_openprogram", "session_id": "local_13d5"},
"confidence": 0.95,
"superseded_by": null
}
3.3 读取#
- 邻居遍历:
memory_graph_neighbors(entity, hops=2) - Hybrid search:
memory_search(query)— FTS + 可选向量 - Scope 过滤:查询时按当前上下文过滤(
global+project:<current>)
3.4 更新#
- Alias resolution:相同实体的不同名称合并为一个节点
- 矛盾处理:新边与旧边冲突时,旧边标
superseded_by指向新边,不删除旧边 - Views 重建:Stage 5 重新投影
views/entity/*.md
3.5 删除#
不删除。旧边标 superseded_by,保留完整历史。
3.6 Scope 标签#
每个 entity/edge 带 scope,查询时按上下文过滤:
scope: "global" 跨所有项目(如用户语言偏好)
scope: "project:openprogram" 仅此项目
scope: "agent:research" 仅此 agent
4. Core.md(注入快照)#
LLM 每次调用时注入 system prompt 的最小记忆快照。≤2KB。
4.1 创建/更新#
触发:sleep::deep(daily 03:00)重新生成。
来源:
- Timeline 中最近的高信号事件(top-N by recency × importance)
- Graph 中高频/高置信的实体和关系
每一行都带 provenance 指针(↪ session:<id>),LLM 看到后可用导航工具深入。
4.2 读取#
每次 LLM 调用时,Core.md 内容被注入 system prompt:
═══════════════════════════════════════════════
OpenProgram 记忆 — 项目: OpenProgram, 最后整理 2026-06-18
═══════════════════════════════════════════════
[时间轴 · 最近]
· 2026-06-15 重构 Functions 页面为三 tab 布局 ↪ session:local_fc03
· 2026-06-17 修复 CLI attended mode 问题 ↪ session:local_d125
[图谱 · 当前项目相关]
· OpenProgram 在 /Users/fzkuji/OpenProgram (python, next.js)
· worker ──listens-on──► :18109
需要细节: memory_open_session(<id>) / memory_git_log(<project>)
═══════════════════════════════════════════════
4.3 删除#
覆盖式更新。每次 sleep::deep 重新生成整个文件,旧内容被完全替换。
5. 跟线性总结链的关系#
overview.md 记录的线性链用更粗的结构覆盖同一片范围:
short-term/YYYY-MM-DD.md → (sleep::light) → wiki/<kind>/<slug>.md → (sleep::deep) → core.md
- short-term:session-end 时追加 0–10 条事实到当日文件
- wiki:sleep::deep 把 short-term 事实提升为知识页面
- core.md:sleep::deep 从 wiki 投影最小快照
这条链从 get_branch() 渲染的对话文本读取,不读 DAG 节点,因此丢失工具调用链(agent 跑了什么、参数、结果)、reads 边(什么影响了决策)和 project-git commit 历史。Timeline 取代 short-term/,Graph 取代 wiki/,core.md 从两者重新投影。
6. 提炼管道(实体 → 抽象)#
6.1 触发时机#
| 触发 | 频率 | 目的 |
|---|---|---|
| Session-end | 会话空闲 ≥30 min | 增量提炼新 turn |
| Sleep (03:00) | 每天一次 | 批量整理、消歧、矛盾检测、重建 core |
| Pre-compress | context 接近上限时 | flush 对话中的关键信息到实体层 |
6.2 五阶段 (Five Stages)#
Stage 1: Collect 从 session-git + project-git 拉自上次提炼以来的新 commit
读 DAG 节点全量(user/llm/code + reads 边)
Stage 2: Extract LLM 一遍:抽取时间轴事件 + 图实体/关系,每条挂 provenance
Stage 3: Link 新实体跟现有图做 alias resolution
Stage 4: Reconcile 矛盾检测,旧边标 superseded,不删
Stage 5: Project 重新投影 core.md / entity views / timeline 分片
Stage 2 是最贵的(需要 LLM),可先用规则版(pattern match)起步,prompt 版逐步替换。
6.3 关键:直接读 DAG#
管道直接读 session-git 里的 Call DAG,包括 code 节点(工具调用)和 reads 边(上下文引用)。这些是图谱投影的关键数据来源,改读渲染出的对话文本就会丢掉它们。
读层是 store/session/provenance.py,提供 iter_nodes_since() / node_provenance() / session_commits() / project_commits()。
7. 召回机制#
7.1 注入(只给抽象)#
LLM 每次调用时注入:
- Core.md(≤2KB,始终注入)
- 可选:按当前 query 召回的 timeline/graph 结果
不注入任何 raw chat。
7.2 导航(LLM 自取)#
LLM 需要实体层细节时,调导航工具:
| 工具 | 作用 | 落到实体层哪 |
|---|---|---|
memory_open_session(session_id, turn) |
读某会话原始消息 | Session-Git history/ |
memory_git_log(project_id, since) |
看某项目提交历史 | Project-Git |
memory_git_show(project_id, commit) |
看某次改了什么 | git show |
memory_timeline(entity|since|until) |
时间轴切片 | Virtual timeline |
memory_graph_neighbors(entity, hops) |
图的邻居 | Virtual graph |
memory_search(query) |
跨虚拟层 hybrid 搜索 | Virtual (FTS + 向量) |
附录:实现状态#
提炼所需的读取层(store/session/provenance.py)已就位。§2、§3 描述的 Timeline 和 Graph 存储,以及 §7.2 的导航工具尚未建成;当前在跑的是 §5 描述、overview.md 记录的线性总结链,它读的仍是渲染出的对话文本而不是 DAG。