The markdown is the record and the database is a replay, so a wrong fact is fixed by editing a line

Loreweave

A temporal knowledge engine over a markdown vault whose fact lines are the durable store, with both a valid-time as-of and a transaction-time as-known-at query, and a subject key that refuses to normalise to nothing.

Carries 3 of 7 rubric mechanisms. Most systems here carry none or one (41%), and a dash means the mechanism was not found at this commit — not that the system needed it. Each mark is one LLM reviewer's reading of the code at this commit rather than a run of it — known limits.

  • Tombstone
  • Trust state
  • Bi-temporal
  • Scope enforced
  • Mutation audit
  • Human review
  • Negative evals

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 — 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
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"] -.-> DB

3. 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-50keyOf, 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-168fe4c5a2… — 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.