Skip to content

docs(adr): 0003 — Memory contract (v1)#271

Merged
EmersonBraun merged 2 commits intomainfrom
foundation/adr-0003-memory
Apr 15, 2026
Merged

docs(adr): 0003 — Memory contract (v1)#271
EmersonBraun merged 2 commits intomainfrom
foundation/adr-0003-memory

Conversation

@EmersonBraun
Copy link
Copy Markdown
Owner

Summary

Third ADR of Phase 0 — formalizes Memory as two independent contracts (ChatMemory + VectorMemory) plus a standalone EmbedFn primitive.

Builds on ADR 0001 (Adapter) and ADR 0002 (Tool).

Merge note: this PR's docs/architecture/adrs/README.md will conflict with #267's. Recommended order: merge #267#270#271 (rebasing each onto main as merged).

What's included

  • docs/architecture/adrs/0003-memory-contract.md — 17 invariants total (6 chat + 8 vector + 3 embed), stability tier stable, rationale, trade-offs.
  • ADR index updated.

Key decision: split, don't unify

LangChain's unified Memory interface produces implementations that half-fulfill both chat and vector concerns. Real backends are asymmetric — Redis great for chat, mediocre for vectors; pgvector the opposite. We split them so a backend implements only what it does well.

Highlight invariants

ChatMemory

  • CM2 Save is replace-all — append-with-dedup leaks complexity into every consumer; replace-all = "flush current state"
  • CM4 Atomic from consumer view — partial save is a contract violation
  • CM5 Empty returns [] — never null, never throw

VectorMemory

  • VM1 Store is upsert by id — no duplicate-id errors, makes re-indexing cheap
  • VM2 Dimensionality is a constructor concern — not in contract; trying to standardize it is bikeshedding
  • VM3 Descending sort + VM5 threshold exclusive — reproducibility tiebreakers
  • VM7 Delete is opt-in — compliance scenarios may legitimately refuse

EmbedFn

  • E1 Stable output per input+model — randomness is a violation
  • Separate from VectorMemory so adapters can ship their own embedders

Trade-offs accepted

  • No native streaming/pagination of chat history (custom solutions for the rare giant sessions)
  • No standardized metadata filtering — every backend has one but specs differ; v2 ADR if needed
  • Embedding-model lock-in within a vector store is userland responsibility (good adapters log model name in metadata)

Unlocks

Test plan

  • Doc renders on GitHub
  • ADR index updated
  • No code changes — pure documentation
  • Reviewer: 17 invariants match current backends (file-vector, sqlite, redis-chat, redis-vector)?
  • Reviewer: split convincing, or argue for unification?

Refs #214 #211

Formalizes Memory as two independent contracts:

ChatMemory (6 invariants, CM1-CM6):
- load() is a snapshot, no streaming
- save() is replace-all, not append
- ordering preserved, atomic from consumer view
- empty memory returns [], clear is opt-in

VectorMemory (8 invariants, VM1-VM8):
- store is upsert by id (no duplicate errors)
- dimensionality is a constructor concern, not contract
- search returns descending-scored, threshold exclusive
- topK is upper bound, no padding
- metadata opaque, JSON-serializable docs
- delete is opt-in (compliance scenarios)

EmbedFn (3 invariants, E1-E3):
- stable output per input+model
- no side effects beyond caching
- error-as-rejection

Splitting ChatMemory and VectorMemory rather than unifying (as LangChain
does) matches real backends: Redis is a great chat store, mediocre vector
store; pgvector is the opposite. Forcing one interface produces
implementations that half-fulfill both sides.

Unblocks 8 new memory backends in Fase 3 (#178) and stable RAG (#175).
Memory graph (#180) will be a third contract, not a replacement.

Refs #214
@EmersonBraun EmersonBraun merged commit ddb724f into main Apr 15, 2026
1 check failed
@EmersonBraun EmersonBraun deleted the foundation/adr-0003-memory branch April 15, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant