Memory — write path and read path
An agent starts every attempt with no experience. The attempt record already
holds what happened; that is evidence, and evidence is not memory. Memory is the
curated layer above it: a bounded taxonomy of domain / topic / fact
that a human can read in full.
The one rule that shapes both paths
The model decides where a candidate belongs. The rules decide what is legal. The registry is closed to the model, the caps bind it, the scope rules bind it, and it never writes. Every stage that touches a model is skippable: with no provider registered, memory still files and still recalls.
Write path
Five stages, and exactly one of them calls a model. A machine capture is durable before anything is derived from it, so a crash between the task transition and the filing transaction loses nothing.
flowchart TB
subgraph L0["Layer 0 · Actors"]
agent["Coding agent
finishes a task"]
human["Operator
kanthord memory remember"]
end
subgraph L2["Layer 2 · cli/"]
cli["memory remember · resolve
supersede · vocabulary"]
end
subgraph L3["Layer 3 · http/contract/"]
contract["memory.remember · memory.resolve
vocabulary.declare · vocabulary.amend
typed topic-path tuples"]
end
subgraph L4["Layer 4 · http/server/"]
handler["handler
parse → one command → format
no domain branching"]
end
subgraph L5["Layer 5 · commands/memory/"]
capture["capture
writes memory_capture INSIDE
the task transition transaction"]
stage1["1 · nominate — zero tokens"]
stage2["2 · classify — the only model call"]
stage3["3 · admit"]
stage4["4 · file — one transaction"]
end
subgraph L6["Layer 6 · domain/ (pure, zod only)"]
admission["memory-admission
two floors · filing cap over the union
exactly one primary filing"]
pathd["memory-path
topic path tuple · one renderer"]
factd["memory-fact
kinds · required fields
canonical payload hash"]
end
subgraph L7["Layer 7 · services/ — interfaces"]
memif["MemoryStore
accepts the transaction context"]
clsif["Classifier
no tools · no workspace · no turn loop"]
stif["Storage — owns the transaction"]
end
subgraph L8["Layer 8 · services/ — implementations"]
sqlmem["SqliteMemoryStore
FTS5 MATCH + bm25()
relevance = -bm25()"]
piacls["PiAiClassifier
call 1 domains → call 2 topics
returns topic paths only"]
end
subgraph L9["Layer 9 · external resources"]
db[("kanthord.db
memory_fact · memory_filing
memory_journal · memory_index")]
provider["model provider API"]
end
agent -->|"task reaches done"| capture
human --> cli
cli --> contract
contract --> handler
handler --> stage1
capture -.->|"sweep reads the open row later"| stage1
stage1 --> stage2
stage2 --> stage3
stage3 --> stage4
stage1 -->|"relevance over descriptor + fact rows"| memif
stage2 --> clsif
stage3 -->|"pure predicate"| admission
stage4 --> pathd
stage4 --> factd
stage4 -->|"re-nominate and re-admit
inside the transaction"| memif
stage4 --> stif
memif --> sqlmem
clsif --> piacls
stif --> sqlmem
sqlmem --> db
piacls -->|"one memory_inference row per call,
opened BEFORE dispatch"| db
piacls --> provider
classDef model fill:#fde68a,stroke:#b45309,color:#7c2d12;
classDef pure fill:#dcfce7,stroke:#15803d,color:#14532d;
class stage2,clsif,piacls,provider model;
class admission,pathd,factd pure;
| Stage | Layer | Model | What it decides |
|---|---|---|---|
| capture | 5 | no | nothing. It names its evidence by blob and stops, inside the task transaction. |
| nominate | 5 → 7 → 8 | no | which topics share vocabulary with the candidate. Candidate generation, not membership. |
| classify | 5 → 7 → 8 | yes | where a paraphrase belongs. Two calls: domains, then topics inside them. |
| admit | 6 | no | what is legal: the registry, the caps, the scope rules, the single primary filing. |
| file | 5 → 7 → 8 → 9 | no | one transaction: dedupe, insert, reconcile filings, index, journal. |
Read path
Recall is two stages, because that is how a person recalls: pick the subject, then read what you know about it. A KanthorD attempt spends no model call on either stage, because its recall is pinned once and every attempt renders the same bytes.
flowchart TB
subgraph L0r["Layer 0 · Readers"]
swe["general@1 / swe@1
the implementer"]
rev["re@1
the reviewer"]
foreign["Foreign harness
Claude Code · Codex"]
opr["Operator
kanthord memory recall"]
end
subgraph L2r["Layer 2 · cli/"]
clir["memory catalog · memory recall
memory export"]
end
subgraph L3r["Layer 3 · http/contract/"]
contractr["memory.catalog · memory.recall
memory.export"]
end
subgraph L4r["Layer 4 · http/server/"]
handlerr["handler · parse → one query → format"]
compiler["instruction compiler
typed channels, precedence before rendering"]
end
subgraph L5r["Layer 5 · queries/memory/"]
catalog["stage 1 · catalog
every domain and topic, bounded in bytes"]
recall["stage 2 · recall
rank fact rows inside the chosen paths"]
pin["pin
stored once, before a task's first attempt"]
end
subgraph L6r["Layer 6 · domain/ (pure)"]
order["memory-recall
1 relevance desc · 2 scope tier · 3 fact ULID
skip a fact that does not fit · never truncate"]
disp["dispute pairing
both sides adjacent, or neither"]
end
subgraph L7r["Layer 7 · interfaces"]
memifr["MemoryStore"]
clsifr["Classifier — select: model only"]
end
subgraph L8r["Layer 8 · implementations"]
sqlr["SqliteMemoryStore
bm25() over fact rows"]
end
subgraph L9r["Layer 9 · external"]
dbr[("kanthord.db
memory_recall holds the rendered blob")]
tree[("exported tree
CATALOG.md · fact/ · index/ · MANIFEST.json")]
end
opr --> clir
clir --> contractr
contractr --> handlerr
handlerr --> catalog
catalog --> recall
recall --> order
order --> disp
recall --> memifr
memifr --> sqlr
sqlr --> dbr
catalog -.->|"select: model — optional, audited"| clsifr
recall --> pin
pin -->|"stores the ordered ids AND the rendered bytes"| dbr
pin --> compiler
compiler -->|"channel: recalled memory
below every contract channel"| swe
compiler -. "never receives the channel" .-> rev
clir -->|"memory export"| tree
tree -->|"reads files, its own model chooses"| foreign
classDef model fill:#fde68a,stroke:#b45309,color:#7c2d12;
classDef pure fill:#dcfce7,stroke:#15803d,color:#14532d;
classDef refused fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d;
class clsifr model;
class order,disp pure;
class rev refused;
Three rules the diagrams encode
- The recall channel sits below every contract channel, and it is never refused and never truncated. The resolver renders facts in rank order and skips any whose rendered block would not fit, continuing with the next. A single fact can never exceed the budget, because a body over the cap is refused at write time.
-
re@1receives no memory. A reviewer that reads what the implementer read makes correlated mistakes, and the review stops being independent. Every fact is implementation guidance by the definition of that channel; anything that governs rather than advises belongs in the repository profile. -
A task's recall is pinned before its first attempt, and the pin stores
the result rather than the query. A
bm25()relevance depends on statistics over the whole index, so a fact written later would reorder an older query. Storing the ordered ids and the rendered bytes is what makes an attempt reproducible — and it is why an agent cannot change the instruction governing its own run.
Where a model may appear, and where it may not
| Operation | Model | Why |
|---|---|---|
| Classify a candidate into topic paths | permitted | Placing a paraphrase is the one thing the lexical index cannot do. |
Select paths on read (select: model) | permitted | Opt-in, audited identically, and never the default for an attempt. |
| Create a domain or a topic | refused | A model would consume the whole registry budget before a human read the catalog. |
| Resolve a draft into a fact | refused | A concise problem and solution pair is prose and a write. Both fail the inference-service exception. |
| Declare two facts contradictory | refused | The classifier never receives a fact body, so it has no evidence to compare. |
| Rank facts on read | refused | The ordering key is total and deterministic. A model in front of it buys variance. |
The full specification is in the engine repository under
docs/proposal/memory/: the model and the scopes, the vocabulary and
its budgets, the write path, the read path, the architecture and the portable
format.