1. Executive Summary
Loreweave is "[a] temporal knowledge engine for markdown vaults" that "indexes, links, remembers, forgets, and dreams — locally, over files you own." MIT, TypeScript, version 0.37.1, 19,552 lines across 100 files, a SQLite index over a directory of markdown.
Its diagnosis is that most knowledge tools are write-only: "You capture diligently, the vault grows, and six months later you can't find the thing you know you wrote — because retrieval is keyword search over prose, nothing ever resurfaces on its own, and nothing notices when what you wrote last year stopped being true."
The architectural decision worth taking is which artifact is authoritative.
"Fact lines in markdown are the durable record; DB fact rows are a replay."
Every assertion appends
- [fact] Subject :: predicate :: Object {valid_from=…} to a
dated file under lore/journal/, every closure appends
- [invalidate] Subject :: predicate {valid_until=…}, and
"[r]ebuild wipes and replays ALL fact rows deterministically, so the
index stays a pure cache of the vault." A fact an agent got wrong is
corrected by editing a line in a file the user already owns, and the
index follows.
The temporal model is the most complete of the several this
atlas has read with these four columns. queryFacts
takes asOf and asKnownAt as separate
parameters and builds separate predicates — the first over
valid_from and valid_until, the second over
recorded_at and superseded_at — and the
comment on the second says exactly why it is a different question:
"Known at T: recorded by then, and not yet superseded by then. A fact asserted afterwards was not available to anyone reasoning at T, however early its validity was backdated to start."
Backdating is the case that separates a real transaction axis from a
decorated one, and it is handled in the clause rather than left to the
caller. Valid time comes from somewhere other than the clock: a note's
frontmatter date, event_date,
created or valid_from, a trailing
{valid_from=…} annotation, or an explicit argument.
2. Mental Model
A fact line in markdown is the record; the row is a copy.
A valid window is when it was true; recorded_at is when the vault learned it.
An invalidate line closes a fact at a date rather than deleting it.
A rebuild is allowed to wipe the database, because nothing lives only there.
Diagram source
%% caption: markdown fact lines are the durable record and the SQLite rows are a deterministic replay of them, with valid time read from the note and transaction time stamped on the write, queried through two independent parameters
flowchart TB
NOTE[("any note: frontmatter date,<br/>key:: value, - [key] value")] --> EX["extract: source_type 'extracted'"]
CLI["lore assert / lore invalidate<br/>lore_assert_fact / lore_invalidate_fact"] --> APPEND["appendJournalLine"]
APPEND --> J[("lore/journal/YYYY-MM-DD.md<br/>- [fact] S :: p :: O {valid_from=…}<br/>- [invalidate] S :: p {valid_until=…}")]
J -.->|"control characters escaped losslessly,<br/>'or a fact containing a newline would be<br/>silently truncated on replay (data loss)'"| SAFE["the line survives the round trip"]
EX --> J
J --> REPLAY["rebuild wipes and replays ALL fact rows"]
REPLAY --> DB[("facts: valid_from · valid_until<br/>recorded_at · superseded_at · superseded_by<br/>source_type · note_path · block_anchor")]
REPLAY -.->|"'the index stays a pure cache of the vault' —<br/>so a mutation that never reached the journal<br/>does not survive"| AUTH["the markdown is authoritative"]
KEY{"keyOf: does the subject normalise<br/>to a non-empty key?"} --> APPEND
KEY -.->|"'Three unrelated subjects contradicting one another<br/>through a key none of them had' — refuse the key,<br/>name the value, and say why"| REFUSE["the write is refused with the value quoted"]
DB --> Q["queryFacts(store, q)"]
Q -->|"q.asOf — VALID time"| VT["COALESCE(valid_from, recorded_at) at or before T,<br/>and valid_until either NULL or after T"]
Q -->|"q.asKnownAt — TRANSACTION time"| TT["recorded_at at or before T,<br/>and superseded_at either NULL or after T"]
TT -.->|"'a fact asserted afterwards was not available<br/>to anyone reasoning at T, however early its<br/>validity was backdated to start'"| RIGHT["the backdating case, handled in the clause"]
Q -->|"neither, and not includeHistory"| NOW["valid_until IS NULL AND superseded_by IS NULL"]
VT & TT & NOW --> OUT["facts returned"]
SRC["source_type: stated, extracted or inferred"] -.->|"decided at write time, printed in the CLI,<br/>used in one timeline heuristic —<br/>it withholds nothing"| NOTS["no trust-state mark"]
NOSCOPE["no tenant, project or agent key<br/>on a fact, and no scope predicate<br/>on any read"] -.-> DB3. Architecture
| Area | Role |
|---|---|
src/facts/journal.ts |
The markdown record, its grammar, and the escaping that keeps it lossless |
src/facts/model.ts |
Assert, invalidate, supersession, and the bitemporal query |
src/temporal/ |
Date extraction from notes, and the timeline |
src/store/schema.ts |
Eleven tables, all of them derivable from the vault |
src/dream/ |
Consolidation, link proposals, and the fitted forgetting curve |
src/mcp/server.ts |
Fifteen lore_* tools |
eval/ |
LoCoMo, LongMemEval, BEIR and scale harnesses against a committed baseline |
4. Essential Implementation Paths
src/facts/journal.ts:1-11 — the sentence the
architecture rests on, and the fact-line grammar under it.
src/facts/model.ts:296-328 — two time axes as two query
parameters, and the backdating comment.
src/facts/model.ts:41-50 — keyOf, refusing
a subject that normalises to nothing.
src/facts/model.ts:340-360 — an aggregate that returns
its total because the bug it replaces was a caller not asking.
src/temporal/dates.ts:205-206 — where valid time comes
from when it is not supplied.
5. Memory Data Model
A fact is subject, predicate and object with a display form preserved beside the normalised key, a validity window, a recorded time, supersession columns, a source type, the note path and block anchor it came from, and a confidence. Around it sit notes, blocks, links, entities, mentions, edges, embeddings and an access log — every one of them derivable from the vault, which is what licenses the rebuild.
6. Retrieval Mechanics
Lexical search over blocks, embeddings, and a graph walk over entity
mentions and edges, with a rerank stage. A retrievability decay is
fitted from the vault's own retrieved-then-used history rather than
assumed, and lore review surfaces what has faded below the
threshold. The default fact query returns only facts that are neither
expired nor superseded; includeHistory returns the
chain.
7. Write Mechanics
A write is refused rather than accepted quietly when it cannot be
made meaningful: a subject that normalises to an empty key, or a
validUntil before its validFrom, which
"[a]ccepted silently … produced intervals like (2025-01-01 →
2024-06-01), which no query can answer and nothing flags." A successful
write appends its journal line first and indexes the journal file, so
the durable record and the cache are written in that order.
8. Agent Integration
Fifteen MCP tools spanning search, fact assertion and invalidation,
timeline, aggregation, context packing, session resume and the review
list, plus a lore CLI with the same operations and a file
watcher.
9. Reliability, Safety, and Trust
Provenance is concrete — every extracted fact keeps the note path and block anchor it came from — and the rebuild-from-vault property means the failure mode of a corrupted index is a re-index rather than data loss. The gaps are scope, which does not exist, and the absence of any epistemic status separate from supersession.
10. Tests, Evals, and Benchmarks
A vitest suite whose test names read as regressions with the fault
named — one edit erasing a note's whole retrieval history, one use
relabelling a week of ignored retrievals as successes — alongside an
eval/ directory running LoCoMo, LongMemEval, BEIR,
generated questions and a scale harness against a committed baseline.
There are no committed must-not-retrieve cases.
11. For Your Own Build
Decide which artifact is authoritative and make the other one disposable. Being able to wipe and replay the index is what makes every other correction cheap.
Take the empty-key refusal. Any normalisation that can produce an
empty string will eventually collapse unrelated records into one slot,
and the failure is silent — the reproduction in that comment is worth
reading before you write your own normalizeKey.
If you offer an aggregate with a cap, return the total. An opt-in total is the same design as no total.
12. Open Questions
Whether a vault is really the intended boundary once several agents share one. Nothing carries a scope key, so a fact asserted for one project is visible to every query.
Whether source_type was meant to influence retrieval. It
has three values, one of which is inferred, and a reader
would reasonably expect an inferred fact to be treated differently from
a stated one; nothing does.
Appendix: File Index
| Path | What to read it for |
|---|---|
src/facts/journal.ts:1-11 |
Markdown as the record and the database as a replay |
src/facts/model.ts:296-328 |
Two time axes as two parameters, backdating included |
src/facts/model.ts:41-50 |
A normalisation that refuses to produce an empty key |
src/facts/model.ts:340-360 |
Why the total is returned rather than requested |
src/temporal/dates.ts:205-206 |
Where valid time comes from when nobody supplies it |
History
2026-09-16 — 8fe4c5a2…
— first reading, at a commit dated 15 September 2026. Screened before
opening, from a shallow clone: four files scanned, one auto-run surface,
one build-time execution point, one unpinned dependency surface and two
dependency files inside the seven-day cooldown, with
package-lock.json present. Nothing was installed, built or
run.