Virtual Memory#
1. Concept#
Virtual memory is a compact index distilled from the concrete-memory layer. It does not replace the concrete layer; it gives that layer a navigation map with coordinates.
Core properties:
- Provenance-linked: every memory carries a pointer back to its source in the concrete layer
- Bi-temporal: every memory records two timestamps —
event_time(when it happened) andingestion_time(when it was written down) - Immutable append: old records are never deleted; on conflict they are tagged
superseded_by
Virtual memory has three types:
| Type | Question it answers | Storage |
|---|---|---|
| Timeline | What happened, and when | memory/timeline/YYYY-MM.jsonl |
| Knowledge Graph | How things relate to one another | memory/graph/{entities,edges}.jsonl |
| Core.md | The minimal snapshot the LLM reads on every call | memory/core.md |
2. Timeline#
A stream of events organized by time. Answers "what happened, and when".
2.1 Creation#
Triggers:
- Session goes idle (idle ≥30 min) → watcher triggers distillation
- Daily sleep (03:00) → batch consolidation
Process:
- Read the newly added DAG nodes from the concrete layer (
iter_nodes_since()) - LLM extracts events (Stage 2: extract)
- Attach a
Provenancepointer to each event - Append to the JSONL file for the corresponding month
2.2 Storage#
~/.openprogram/memory/timeline/
├── 2026-05.jsonl
├── 2026-06.jsonl
└── ...
A single record:
{
"id": "ev_abc",
"summary": "Fixed the Windows cp1252 encoding bug in OpenProgram, touching 38 files",
"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 enumeration: work | decision | learning | event
2.3 Reading#
- By time range:
memory_timeline(since, until) - By associated entity:
memory_timeline(entity="project.openprogram") - On recall, inject the most recent high-signal events into Core.md
2.4 Updating#
Append-only. When the same fact is re-distilled, the old record is tagged superseded_by pointing at the new record's ID; it is not deleted.
2.5 Deletion#
No deletion. Rationale: an event is a record of a historical fact, and deleting it would break the integrity of the timeline. Marking it superseded is enough.
3. Knowledge Graph#
A graph structure of entities and relations. Answers "how things relate to one another".
3.1 Creation#
Triggers: same as Timeline (session-end / daily sleep).
Process:
- Stage 2 of the distillation pipeline extracts entities and relations from the DAG
- Stage 3 performs alias resolution ("worker"/"backend"/"daemon" → the same node)
- Stage 4 performs contradiction detection (a new edge conflicting with an old one → tag superseded)
- Append to JSONL
3.2 Storage#
~/.openprogram/memory/graph/
├── entities.jsonl nodes
├── edges.jsonl edges (with bi-temporal + provenance)
└── views/
└── entity/<slug>.md one readable page per entity
Entity:
{
"id": "project.openprogram",
"type": "project",
"name": "OpenProgram",
"attrs": {"path": "/Users/fzkuji/OpenProgram", "lang": "python"},
"scope": "global"
}
Edge:
{
"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 Reading#
- Neighbor traversal:
memory_graph_neighbors(entity, hops=2) - Hybrid search:
memory_search(query)— FTS + optional vectors - Scope filtering: filter by the current context at query time (
global+project:<current>)
3.4 Updating#
- Alias resolution: different names for the same entity are merged into a single node
- Contradiction handling: when a new edge conflicts with an old one, the old edge is tagged
superseded_bypointing at the new edge; the old edge is not deleted - View rebuild: Stage 5 re-projects
views/entity/*.md
3.5 Deletion#
No deletion. The old edge is tagged superseded_by, preserving the full history.
3.6 Scope tags#
Every entity/edge carries a scope, filtered by context at query time:
scope: "global" across all projects (e.g. the user's language preference)
scope: "project:openprogram" this project only
scope: "agent:research" this agent only
4. Core.md (injected snapshot)#
The minimal memory snapshot injected into the system prompt on every LLM call. ≤2KB.
4.1 Creation/update#
Trigger: regenerated on sleep::deep (daily 03:00).
Sources:
- The most recent high-signal events from the Timeline (top-N by recency × importance)
- High-frequency / high-confidence entities and relations from the Graph
Every line carries a provenance pointer (↪ session:<id>), so once the LLM sees it, it can use the navigation tools to dig deeper.
4.2 Reading#
On every LLM call, the contents of Core.md are injected into the system prompt:
═══════════════════════════════════════════════
OpenProgram Memory — project: OpenProgram, last consolidated 2026-06-18
═══════════════════════════════════════════════
[Timeline · recent]
· 2026-06-15 Refactored the Functions page into a three-tab layout ↪ session:local_fc03
· 2026-06-17 Fixed the CLI attended-mode issue ↪ session:local_d125
[Graph · current project]
· OpenProgram at /Users/fzkuji/OpenProgram (python, next.js)
· worker ──listens-on──► :18109
For details: memory_open_session(<id>) / memory_git_log(<project>)
═══════════════════════════════════════════════
4.3 Deletion#
Overwrite-style update. Each sleep::deep regenerates the entire file, and the old contents are fully replaced.
5. Relationship to the linear summarization chain#
The linear chain documented in overview.md covers the same ground
with a coarser structure:
short-term/YYYY-MM-DD.md → (sleep::light) → wiki/<kind>/<slug>.md → (sleep::deep) → core.md
- short-term: at session-end, append 0–10 facts to the current day's file
- wiki: sleep::deep promotes short-term facts into knowledge pages
- core.md: sleep::deep projects a minimal snapshot from the wiki
That chain reads the conversation text rendered by get_branch(), not DAG nodes,
so it loses tool-call chains (what the agent ran, with what arguments and
results), reads edges (what influenced a decision), and project-git commit
history. Timeline supersedes short-term/, Graph supersedes wiki/, and
core.md is re-projected from both.
6. Distillation pipeline (concrete → virtual)#
6.1 Trigger timing#
| Trigger | Frequency | Purpose |
|---|---|---|
| Session-end | Session idle ≥30 min | Incrementally distill new turns |
| Sleep (03:00) | Once a day | Batch consolidation, disambiguation, contradiction detection, core rebuild |
| Pre-compress | When context approaches its limit | Flush the key information from the conversation to the concrete layer |
6.2 Five Stages#
Stage 1: Collect Pull commits added since the last distillation from session-git + project-git
Read the full set of DAG nodes (user/llm/code + reads edges)
Stage 2: Extract A single LLM pass: extract timeline events + graph entities/relations, each tagged with provenance
Stage 3: Link Run alias resolution between new entities and the existing graph
Stage 4: Reconcile Contradiction detection; old edges tagged superseded, not deleted
Stage 5: Project Re-project core.md / entity views / timeline shards
Stage 2 is the most expensive (it needs an LLM); you can start with a rules-based version (pattern match) and gradually replace it with a prompt-based version.
6.3 Key point: read the DAG directly#
The pipeline reads the Call DAG in session-git directly, including code nodes (tool calls) and reads edges (context references). These are the key data sources for projecting the graph, and they are lost when reading rendered conversation text instead.
The read layer is store/session/provenance.py, which provides iter_nodes_since() / node_provenance() / session_commits() / project_commits().
7. Recall mechanism#
7.1 Injection (virtual only)#
Injected on every LLM call:
- Core.md (≤2KB, always injected)
- Optional: timeline/graph results recalled for the current query
No raw chat is injected.
7.2 Navigation (LLM fetches on its own)#
When the LLM needs detail from the concrete layer, it calls a navigation tool:
| Tool | Purpose | Where it lands in the concrete layer |
|---|---|---|
memory_open_session(session_id, turn) |
Read the raw messages of a session | Session-Git history/ |
memory_git_log(project_id, since) |
View a project's commit history | Project-Git |
memory_git_show(project_id, commit) |
See what a given commit changed | git show |
memory_timeline(entity|since|until) |
A timeline slice | Virtual timeline |
memory_graph_neighbors(entity, hops) |
A node's neighbors in the graph | Virtual graph |
memory_search(query) |
Hybrid search across the virtual layer | Virtual (FTS + vectors) |
Appendix: Implementation status#
The read layer for distillation (store/session/provenance.py) is in place. The
Timeline and Graph stores described in §2 and §3, and the navigation tools in
§7.2, are not yet built; what runs today is the linear summarization chain
described in §5 and documented in overview.md, which still reads
rendered conversation text rather than the DAG.