|
| 1 | +# LevelCode Sessions — Project Memory & Continuity |
| 2 | + |
| 3 | +**Status:** proposed · **Scope:** `extensions/levelcode-ai` + the sessions store · **Third of the triad:** [`levelcode-chat-sessions-design.md`](./levelcode-chat-sessions-design.md) stores a chat · [`levelcode-sessions-experience.md`](./levelcode-sessions-experience.md) lets you browse it · **this doc lets the project *remember itself*.** |
| 4 | + |
| 5 | +> The wish, in the user's words: *"if in one session we tidy the CHANGELOG for v1.0.4, then a new session already knows something about what was achieved — the past experience keeps growing, and it feels like home."* That is the right ambition. It is also the feature most likely to become expensive, stale, creepy, or wrong. This document is mostly about **not** doing those four things while still delivering the magic. |
| 6 | +
|
| 7 | +--- |
| 8 | + |
| 9 | +## 0. Four disciplines, stated up front |
| 10 | + |
| 11 | +Every decision below serves one of these. If a choice violates one, it's the wrong choice: |
| 12 | + |
| 13 | +1. **Small when always-on.** What loads into *every* session must be a tight digest (a page, not a library). You can never pour past sessions into a new one — it costs money and drowns the task. |
| 14 | +2. **Deep when asked.** The full episodic record is retrieved *on demand* (a tool call, a search), not carried in context. |
| 15 | +3. **Transparent always.** Memory is plain, readable, editable files the user owns and can correct — never a hidden vector store. This is the BYOK/hackable ethos *and* the only way memory earns trust. |
| 16 | +4. **Never trusted blindly.** Memory is context, not gospel. It is dated, sourced, decays, and is treated as *possibly-stale, possibly-poisoned* input the agent verifies — because some of it was distilled from untrusted workspace content. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## 1. The trap: three things people call "memory" |
| 21 | + |
| 22 | +Conflating these is why most "AI memory" features feel either useless or unsettling. LevelCode keeps them **separate layers**, each with a different size, lifetime, and trust level: |
| 23 | + |
| 24 | +| Layer | What it is | Size | Loaded | Example | |
| 25 | +|---|---|---|---|---| |
| 26 | +| **Facts** | durable, curated truths about the project & your preferences | tiny | **always-on** | "Idempotency keys live in Redis." "CHANGELOG is `RELEASE-NOTES.md`." | |
| 27 | +| **Journal** | a rolling digest of *recent outcomes* — what got done lately | small | **always-on (recent slice)** | "Shipped v1.0.4 notes; fixed the MCP timeline rail; tidied the CHANGELOG." | |
| 28 | +| **Recall** | the full episodic corpus — every session, verbatim & summarized | large | **on-demand only** | *"What did we decide about refund retries last month?"* | |
| 29 | + |
| 30 | +The user's example — *a new session knows we tidied the CHANGELOG* — is the **Journal** layer (recent accomplishments), backed by **Recall** for depth and **Facts** for the stable stuff. The magic is the Journal; the safety is that it's small and the rest is retrieved. |
| 31 | + |
| 32 | +--- |
| 33 | + |
| 34 | +## 2. Prior art — what to copy, what to avoid |
| 35 | + |
| 36 | +- **Claude Code `CLAUDE.md` / this project's own `# Memory`** — a *plain-file, curated, indexed* memory folded into every session. **Copy:** plain text, human-editable, an index file, per-item provenance, typed entries (fact / preference / project / reference). It is the proven shape. **Its gap:** it's hand-maintained; we add cheap-lane *auto-extraction* on top, without losing the editability. |
| 37 | +- **ChatGPT Memory** — auto-extracts durable facts across chats, **shows them, lets you delete them.** **Copy:** transparency + per-item delete is non-negotiable. **Avoid:** opacity about *when* something was learned. |
| 38 | +- **Cursor "memories" / rules** — auto + manual project rules. **Copy:** the always-on project-rules injection. **Avoid:** memories that silently accumulate with no review surface. |
| 39 | +- **MemGPT / Letta** — the tiered *core memory (always-on) + archival memory (retrieved)* architecture. **Copy:** exactly this tiering — it is §0.1/§0.2 in one sentence. **Avoid:** its complexity; we don't need a self-editing agent loop to start, just extract-on-seal + a recall tool. |
| 40 | +- **Naïve RAG-over-everything** — embed every message, retrieve top-k into every prompt. **Avoid:** unbounded cost, retrieval of stale/irrelevant chunks, and an opaque store nobody can read. Greppable JSONL + a scoped tool beats a mystery index for a single-project developer corpus. |
| 41 | + |
| 42 | +**The synthesis:** a **tiered, plain-text, auto-extracted-but-user-editable** memory, per project, that loads a tight digest always and retrieves depth on demand. |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## 3. Architecture (grounded in what already ships) |
| 47 | + |
| 48 | +Three pieces of LevelCode already exist and this design just connects them: |
| 49 | + |
| 50 | +- **`loadProjectRules`** (`agent.js`) already folds `AGENTS.md` / `CLAUDE.md` / `.cursorrules` into the cached system block. **That is the always-on injection channel** — `MEMORY.md` rides the exact same mechanism. |
| 51 | +- **`compactAgentMemory` / `COMPACT_SYSTEM`** already summarize a transcript into a briefing. **That is the extraction engine** — pointed at a *sealed* session instead of a live one, on the **cheap/fast model lane** (the same routing titles use). |
| 52 | +- **The sessions store** (`~/.levelcode/sessions/<project-slug>/`) is already per-project, plain-file, greppable, local. **Memory lives beside the sessions it came from.** |
| 53 | + |
| 54 | +### Storage — plain files, per project |
| 55 | + |
| 56 | +``` |
| 57 | +~/.levelcode/sessions/<project-slug>/ |
| 58 | + memory/ |
| 59 | + MEMORY.md ← the always-on digest: Facts + a recent-Journal slice. Capped (~1–2k tokens). Human-editable. Injected into every session. |
| 60 | + journal.jsonl ← append-only, one line per sealed session: its outcome summary, files, decisions, links back to the session id. |
| 61 | + facts.jsonl ← durable facts with provenance {text, source_session, learned_at, confidence, confirmed:bool} |
| 62 | +``` |
| 63 | + |
| 64 | +Everything is readable, `grep`-able, `git`-able (a user could even check `memory/` into a dotfiles repo), and rides the future M9 encrypted sync unchanged. No opaque DB, ever (anti-Cursor, per the spine). |
| 65 | + |
| 66 | +### The pipeline |
| 67 | + |
| 68 | +``` |
| 69 | + session seals ──▶ cheap-lane "outcome" summary ──▶ append to journal.jsonl |
| 70 | + (1–3 sentences: what was achieved, key files, any decision) |
| 71 | +
|
| 72 | + every N seals ──▶ CONSOLIDATION pass (cheap-lane) ──▶ rewrite MEMORY.md |
| 73 | + (or on demand) • fold recent journal entries into a tight "Recently" section |
| 74 | + • promote repeated/confirmed observations into Facts |
| 75 | + • let stale entries decay OUT of the digest (they stay in journal.jsonl) |
| 76 | + • hard cap the file — evict lowest-signal first |
| 77 | +
|
| 78 | + any session ──▶ MEMORY.md injected into the system block (like project rules) |
| 79 | +
|
| 80 | + on request ──▶ recall_sessions(query) ← an agent TOOL: fuzzy/greppable search over |
| 81 | + journal.jsonl + the full session JSONL; returns cited snippets on demand |
| 82 | +``` |
| 83 | + |
| 84 | +- **Extraction is cheap and incremental:** one small model call when a session seals — not a batch job, not embeddings-of-everything. Reuses the compaction machinery. |
| 85 | +- **Consolidation is where "growing memory" happens honestly:** it is the *sleep* of the system — recent experience is folded in, durable patterns are promoted to Facts, and stale detail decays from the always-on digest while remaining fully recoverable in `journal.jsonl` and Recall. This is what makes memory *compound* without *bloating*. |
| 86 | +- **Recall is a tool, not a context tax:** `recall_sessions("refund retries")` runs only when the agent (or user) asks, and returns cited results — never loaded speculatively. |
| 87 | + |
| 88 | +--- |
| 89 | + |
| 90 | +## 4. Staying true: freshness, provenance, conflict |
| 91 | + |
| 92 | +Memory that lies is worse than no memory. Four guards: |
| 93 | + |
| 94 | +- **Provenance on everything.** Each fact/journal entry carries `source_session` + `learned_at`. The UI and the model can always answer *"says who, and when?"* A memory is never a free-floating assertion. |
| 95 | +- **"As of" honesty.** Injected memory is framed to the model as *"known as of <date>; verify against the current code before relying on it"* — the same discipline this project's own memory system uses. Memory informs; the code decides. |
| 96 | +- **Newest-wins with flagging.** When a new observation contradicts a stored fact (`uses Redis` → `moved to Postgres`), consolidation supersedes the old one and keeps a one-line history; a genuinely ambiguous conflict is surfaced for the user, not silently guessed. |
| 97 | +- **Decay.** Journal entries lose always-on weight with age and inactivity, so the digest reflects *current* project reality, not a museum. Decayed ≠ deleted — it's still in Recall. |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +## 5. How lifecycle feeds memory (the "not auto-archived" instinct, reconciled) |
| 102 | + |
| 103 | +Your instinct — *draw memory from the sessions that weren't auto-archived* — is right, once we separate two kinds of archiving (see experience doc §4.9): |
| 104 | + |
| 105 | +- **Manual "Done"** = *"I finished this real work."* These are **prime memory** — a shipped feature is exactly what a new session should know about. They feed the Journal and can be promoted to Facts. |
| 106 | +- **Auto-archive (30d inactive)** = *"this went cold."* These **fade from the always-on digest** (they're stale — that is the decay in §4) **but stay in `journal.jsonl` and Recall.** So they're never *forgotten*, just no longer *front-of-mind*. |
| 107 | +- **Pinned** sessions get **extra memory weight** — a long-running refactor thread you keep returning to should stay in the digest regardless of age. |
| 108 | + |
| 109 | +So: **the Journal keeps growing (append-only), while the always-on digest stays fresh** — recent + done + pinned in front of mind, cold stuff one recall away. That is precisely "the past experience keeps growing" without "every new chat pays for a year of history." |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +## 6. Transparency & control — the non-negotiables |
| 114 | + |
| 115 | +Auto-memory is only acceptable if the user is never surprised by it: |
| 116 | + |
| 117 | +- **A "Project memory" surface** in the Sessions panel (a tab beside History): the current `MEMORY.md` rendered, every Fact and recent-Journal line with its source session and date, and per-item **edit · pin · delete · "not true"**. It is *your* growing knowledge base, visible and yours to curate. |
| 118 | +- **It's just files.** `memory/MEMORY.md` opens in the editor. Power users edit it directly; the consolidation pass respects hand-written sections (a `<!-- pinned -->` block is never evicted). |
| 119 | +- **Consent posture:** extraction is **on by default but fully reviewable**; auto-inferred Facts are marked *inferred* (lower trust, dimmer) until used/confirmed, at which point they become *confirmed*. Nothing durable is asserted with false confidence. |
| 120 | +- **Off switches:** `sessions.memory.enabled` (master), per-project opt-out, and a "forget this session" that removes its contribution. |
| 121 | +- **Never a black box:** no hidden embeddings the user can't read. If we ever add vectors for recall speed, they are a *derived cache* over the plain files — rebuildable, never the source of truth (same rule as the sessions index). |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +## 7. Security — memory is an attack surface |
| 126 | + |
| 127 | +This is the part most designs skip, and LevelCode can't (it's the security-forward editor): |
| 128 | + |
| 129 | +- **Poisoning via untrusted content.** Sessions contain workspace text, which in a hostile repo is attacker-controlled. A naïve extractor could be steered into writing a false "memory" (*"the deploy token is safe to print"*). Mitigations: extraction summarizes **outcomes and user/agent actions, not arbitrary quoted content**; the digest is **bounded and reviewable**; and injected memory is **framed as untrusted, verify-first** (§4) — it can inform, never command. |
| 130 | +- **Memory never executes.** It is context in the system block, exactly like project rules. It cannot run a tool, approve an MCP call, or edit a file. An injected instruction inside a "memory" is treated like any other untrusted text (the project's existing prompt-injection posture). |
| 131 | +- **Provenance limits blast radius.** Because every item is sourced and dated, a poisoned entry is traceable to its session and removable in one click — and its low, *inferred* confidence keeps it from being load-bearing until a human confirms it. |
| 132 | +- **Local & private.** Memory never leaves the machine (BYOK promise); M9 sync, if enabled later, encrypts it like the sessions themselves. |
| 133 | + |
| 134 | +--- |
| 135 | + |
| 136 | +## 8. The "feels like home" experience |
| 137 | + |
| 138 | +The magic, delivered quietly (never a wall of text): |
| 139 | + |
| 140 | +- **Welcome-back digest.** A new session's empty state shows a tight, dismissible strip: *"This project, lately: shipped v1.0.4 notes · fixed the MCP timeline · tidied the CHANGELOG. 3 pinned threads. [what I remember ↗]"* — glanceable, honest, one click to the full memory surface. The agent already **has** this context, so its first reply is continuous, not amnesiac. |
| 141 | +- **Continuity in the answer.** Because `MEMORY.md` is in context, a new session's agent naturally says *"picking up from the v1.0.4 work — the CHANGELOG's already tidied; want me to…"* instead of asking what project this is. That is the "home" feeling: it remembers, so you don't re-explain. |
| 142 | +- **The growing artifact.** Over weeks, `MEMORY.md` becomes a genuine, readable project brain the user can watch grow, prune, and pin — a compounding asset, not a chat log. Opening it feels like opening a well-kept lab notebook the project wrote about itself. |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## 9. Settings |
| 147 | + |
| 148 | +| Setting | Default | Meaning | |
| 149 | +|---|---|---| |
| 150 | +| `sessions.memory.enabled` | `true` | master switch for extraction + injection | |
| 151 | +| `sessions.memory.dir` | `<sessions.dir>/<project>/memory` | hackability: relocate it | |
| 152 | +| `sessions.memory.digestTokens` | `1500` | hard cap on the always-on `MEMORY.md` | |
| 153 | +| `sessions.memory.consolidateEverySeals` | `5` | how often the consolidation pass runs (also: on demand) | |
| 154 | +| `sessions.memory.journalRecentDays` | `21` | recency window fed to the always-on digest | |
| 155 | +| `sessions.memory.confirmFacts` | `false` | require a click before an inferred Fact becomes confirmed | |
| 156 | +| `sessions.memory.recallTool` | `true` | expose `recall_sessions` to the agent | |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +## 10. Implementation phases (layered on the sessions plan) |
| 161 | + |
| 162 | +**M1 — Journal + recall tool** *(M)*. Extract-on-seal → `journal.jsonl` (reuse compaction, cheap lane); ship `recall_sessions` as an agent tool over journal + JSONL. *No always-on injection yet — deep recall works first, cheaply.* Exit: in a fresh session, *"what did we do about refunds?"* returns cited past-session answers. |
| 163 | + |
| 164 | +**M2 — the always-on digest** *(M)*. Consolidation pass → `MEMORY.md`; inject it via the `loadProjectRules` channel; the welcome-back strip. Freshness/decay/provenance. Exit: a new session opens already knowing the recent arc, in ≤1.5k tokens, with sources. |
| 165 | + |
| 166 | +**M3 — the memory surface & control** *(M)*. The "Project memory" panel tab: view/edit/pin/delete/"not true", inferred-vs-confirmed, per-project off. Exit: a user corrects a wrong memory and the agent stops repeating it. |
| 167 | + |
| 168 | +**M4 — polish & safety hardening** *(S)*. Conflict reconciliation UI, poisoning red-team pass, decayed-entry recall, export. Exit: an adversarial repo cannot plant a load-bearing memory; EXIT-TEST.md green. |
| 169 | + |
| 170 | +**Deliberately later:** cross-*project* memory ("how did I do idempotency in the *other* service?"); a vector cache over the plain files for large corpora; team-shared project memory (rides M9 sync). |
| 171 | + |
| 172 | +--- |
| 173 | + |
| 174 | +## 11. Risks, honestly |
| 175 | + |
| 176 | +- **The digest goes stale or wrong** → decay + provenance + "verify-first" framing + one-click correction; and it's capped so a bad line can't dominate. |
| 177 | +- **Extraction costs add up** → cheap/fast lane only, one small call per seal, consolidation every N (not every) seals; all tunable, all disableable. |
| 178 | +- **Poisoning from hostile repos** → outcomes-not-content extraction, bounded reviewable digest, untrusted-context framing, memory-never-executes (§7). |
| 179 | +- **Creepiness** → nothing is hidden; it's plain files with a review surface and a delete button; inferred facts are visibly low-confidence until confirmed. |
| 180 | +- **Over-remembering noise** → not every session earns a Fact; the Journal is recency-weighted and decays; one-off Q&A sessions contribute a thin line at most, and fade. |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +*Store it (spine) · browse it (experience) · **remember it (this doc).** Together: a sessions feature where the project accumulates its own experience — small in every prompt, deep on request, legible and yours, and safe against the repo that would lie to it.* |
0 commit comments