New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

loreweave

Package Overview
Dependencies
Maintainers
1
Versions
88
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

loreweave

A temporal knowledge engine for markdown vaults — indexes, links, remembers, forgets, and dreams. Local-first, agent-ready (MCP).

Source
npmnpm
Version
0.34.0
Version published
Weekly downloads
147
-61.01%
Maintainers
1
Weekly downloads
 
Created
Source

Loreweave

A temporal knowledge engine for markdown vaults.
It indexes, links, remembers, forgets, and dreams — locally, over files you own.

npm CI node >= 20 MIT

Quickstart · Different how? · CLI · Agent memory · How it works · Research

loreweave demo: temporal search, timeline, as-of facts, session resume

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.

Loreweave is the layer that fixes that. Point it at a folder of markdown (Obsidian or plain) and it builds a knowledge graph, a bitemporal fact store, and a memory model over your notes — then hands them to you through a CLI and to your AI agents through MCP.

Your files stay exactly as they are. The vault is the source of truth; the index is a cache you can delete at any time.

npx loreweave init && npx loreweave index

Quickstart

cd ~/my-vault
npx loreweave init          # creates .lore/
npx loreweave index         # incremental; ~1.2 ms per note (see Scale below)

npx loreweave search "why did we drop the queue design"
npx loreweave ask "what's the status of project atlas"
npx loreweave dream         # what's duplicated, contradicted, stale, unlinked

Zero configuration required and no network calls: out of the box it runs on BM25 + knowledge-graph spreading activation. Add embeddings when you want them:

// .lore/config.json
{ "embedding": { "provider": "ollama", "model": "nomic-embed-text" } }

Everything degrades gracefully — no embedding provider means lexical + graph retrieval, still fully functional.

Works with non-English vaults: Chinese, Japanese and Korean text is segmented per character so it is searchable at all, and other scripts index as written.

Measured, not asserted

Loreweave ships its own benchmark — npm run eval — over three purpose-built vaults, scored against a BM25 baseline and a graph-only baseline.

The second corpus exists to catch overfitting: it is deliberately unlike the first in every dimension the config could have been tuned to (markdown links instead of [[wiki links]], deep nesting, filenames unrelated to titles, real engineering note shapes). The third measures time instead of topicality: facts change across dated notes, every date lives only in frontmatter where BM25 cannot see it, and each windowed question is paired with a shifted-window twin whose correct answer is a different note — the perturbation test that exposes systems faking temporal competence through lexical overlap.

corpussystemfinds the answerin top 5MRRanswer shown
kestrel (40 q)hybrid100%75%0.54555%
BM2575%65%0.53255%
northwind (24 q)hybrid96%92%0.69083%
BM2563%58%0.52154%
meridian (18 q)hybrid100%94%0.95294%
BM2594%83%0.43783%

Multi-hop is where the graph earns its keep: BM25 finds 0% on both prose corpora — it cannot reach a note that shares no words with your query, at any depth — while hybrid finds 90% and 100%. Time is where the temporal machinery earns its keep: on meridian's windowed questions hybrid ranks the right note first 100% of the time vs BM25's 0% — and on the paired perturbation test (same question, shifted window, different correct answer, both directions must rank first) hybrid scores 100% vs BM25's 0%. The consistency number is computed and regression-gated by npm run eval, not hand-derived.

"answer shown" is the strictest measure: not just the right note, but a returned passage that literally contains the answer. Results are one per note, showing whichever of that note's sections best covers your query — ranking decides which notes matter, coverage decides which part of them you see.

The same shipped config wins on all three corpora, and by more on the ones it was never tuned against. If you prefer pure lexical behaviour, set retrieval.weights.expansion: 0.

Run it yourself: npm run eval. npm run eval:gate fails the build on any regression across all three corpora, and CI enforces it on every push.

What lexical + graph retrieval cannot do, stated precisely: the kestrel multi-hop questions use words like "hardware" and "outpost" that appear in zero notes — the vault says "instrument" and "station". No statistic derived from the vault (co-occurrence, PPMI, LSA) can bridge that, because there are no occurrences to derive one from. Those answers are still found by following links; ranking them first needs semantics from outside the vault.

Embeddings supply it — once the model is asked correctly. npm run eval -- --embed reruns everything with local dense vectors (Ollama). For three releases that measured worse than no embeddings at all, and the cause turned out to be ours: nomic-embed-text is an asymmetric model trained with task prefixes (search_query: / search_document:), and we were sending raw text for both — so queries and passages landed in the same region of the space and the dense channel stopped discriminating. With the prefixes sent:

corpusr@5MRRanswer shown
kestrelmodel-free0.7500.5450.550
+ embeddings0.8250.5720.575
northwindmodel-free0.9170.6900.833
+ embeddings0.9580.6810.875
meridianeither0.9440.9510.944

Prefixes are inferred from the model name (nomic, E5 and BGE families are known) and overridable with embedding.queryPrefix / documentPrefix. The default is still no models at all — that is the guarantee this project is built on — but the optional layer now earns its place when you turn it on.

Measured on public benchmarks

The corpora above are ours — we wrote the notes and the questions, which makes them good for regression and worthless as proof. These are third-party, with relevance labels nobody here chose. All runs are the model-free configuration (no LLM, no embeddings, no network), and all are retrieval metrics: loreweave finds the evidence, it does not write the answer, so these are not comparable to end-to-end QA accuracy quoted by systems that put a language model after retrieval.

BEIR / SciFact — 5 183 abstracts, 300 queries, nDCG@10:

systemnDCG@10Recall@10
loreweave + local embeddings0.7290.869
loreweave, lexical channel0.6820.808
loreweave, model-free pipeline0.6810.817
BM25 (BEIR paper, Anserini)0.665

LongMemEval_S (ICLR 2025) — 500 questions, ~50-session history each, session-level recall:

categorynR@1R@5R@10
knowledge-update780.4810.9420.955
temporal-reasoning1330.4140.8710.910
multi-session1330.3710.8350.925
single-session (all)1560.8330.9550.974
overall5000.5520.8990.943

LoCoMo — 10 long conversations, 1 982 evidence-labelled questions, turn-level recall: R@1 0.337, R@5 0.538, R@10 0.610, R@20 0.656. Its strongest category is temporal (R@1 0.454). Multi-hop looks worst at R@1 0.088, but those questions carry 3.13 evidence turns on average, so R@1 cannot exceed 0.390 there — measured against what is achievable, multi-hop reaches 22% of ceiling against 40-48% for single-evidence categories. Still the weakest area; the honest framing is 22%, not 0.088.

Two things worth saying plainly. None of these corpora have links, tags, or frontmatter — the structure this engine exists to exploit — so it is being measured with one hand tied; that is the honest cost of using benchmarks we didn't design. And measuring them found a real defect: the graph channel was trusted even on corpora with no graph to walk, which cost 0.024 nDCG@10 on SciFact and 6 points of R@5 on LoCoMo. Fixed in 0.32.0. It is not uniformly better — LongMemEval's R@10 moved 0.955 → 0.943 — and the trade is documented rather than hidden.

Reproduce every number: docs/benchmarks.md.

Scale

Measured, like the quality numbers — npm run scale reproduces this on your own machine (synthetic vaults, 3 blocks per note, dense interlinking):

notesblocksentitiesedgesfull indexincrementalsearch p50p95dreamheap
1 0003 0002 76617 k1.2 s32 ms3 ms4 ms0.2 s63 MB
5 00015 00013 76685 k6.1 s165 ms9 ms12 ms1.0 s145 MB
20 00060 00055 016339 k22.5 s656 ms38 ms53 ms4.8 s333 MB

Full index scales at 0.93× per note from 5 k to 20 k — linear or better, no superlinear step hiding in the middle. "Incremental" is one changed note, which is what lore watch actually does all day. Everything here is one process, one SQLite file, no daemon.

What makes it different

1. Knowledge that has a timeline. Facts are bitemporal: when they were true in the world (valid_from/valid_until) and when the system learned them (recorded_at). Contradictions supersede rather than overwrite, so history stays queryable.

$ lore assert "Ledger Format" status draft --valid-from 2026-01-01
$ lore assert "Ledger Format" status final --valid-from 2026-08-01
✓ Ledger Format :: status :: final
  superseded: "draft" (now valid until 2026-08-01)
  journal: lore/journal/2026-08-01.md

$ lore facts --subject "Ledger Format"
Ledger Format :: status :: final  (2026-08-01 → now)
    asserted · lore/journal/2026-08-01.md

$ lore facts --subject "Ledger Format" --as-of 2026-03-01
Ledger Format :: status :: draft  (2026-01-01 → 2026-08-01)  [superseded]
    asserted · lore/journal/2026-08-01.md

Both axes are queryable, which is the part that makes it bitemporal rather than merely historical. --as-of asks what was true then; --as-known-at asks what was believed then, excluding anything recorded later however far back it was backdated. They disagree exactly when you learn something after the fact — which is when you most need to reconstruct what a past decision was actually based on:

$ lore facts --subject Vendor --as-of 2024-06-01
Vendor :: reliability :: poor — outage postmortem  (2024-01-01 → now)

$ lore facts --subject Vendor --as-known-at 2024-06-01
Vendor :: reliability :: good  (2024-01-01 → 2024-01-01)  [superseded]

Which fact wins is decided deterministically (newest valid-time, provenance as tiebreak) — never by asking a language model which one looks fresher.

And the whole history of anything is one command — every value change from the fact store merged chronologically with the dated prose that mentions it:

$ lore timeline Project Atlas
2024-01-15  status: planning  (until 2024-09-01)
2024-02-10  • [[Project Atlas]] kicked off with a three-person crew.  [kickoff.md]
2024-09-01  status: planning → active
2025-06-20  • The [[Project Atlas]] midpoint review went long but well.  [review.md]

"What was X before it changed" is the query temporal-graph products market as their flagship — built there by running an LLM over every ingested document. Here the supersede chain has been maintained all along, so it is a read-side join: no LLM, no network, same answer every time.

2. Retrieval that follows connections, not just words. Queries fuse BM25, dense similarity (when configured), and Personalized PageRank over the vault's own graph — wiki-links, shared entities, tags, co-occurrence. Two-hop neighbors surface even when they share no vocabulary with your query, and every result tells you why:

• data/glacier-dataset.md#@0  (0.0327)  ⟨via amara osei⟩
  The Glacier Dataset holds meltwater sensor readings from 2019-2024.

3. Memory with dynamics. Every passage carries FSRS-style stability and retrievability — a power-law forgetting curve. Passages that actually get used (not merely retrieved) decay slower; important-but-fading knowledge gets surfaced for review instead of silently rotting. Nothing is ever deleted.

4. It dreams. lore dream is an idle-time consolidation pass that reviews the vault and reports duplicate passages, contradicted facts, stale knowledge, missing links between notes that clearly belong together, and orphans. With --apply it writes a digest and a review queue — append-only, under lore/. It never rewrites your prose: LLM-driven whole-file rewriting is a documented failure mode (context collapse), so the architecture forbids it.

5. Questions retrieval can't answer. Counting, grouping, and date-range queries run as deterministic SQL over the fact store, not as vibes over embeddings:

$ lore count --predicate trip_to --since 2025-01-01 --until 2025-12-31
    2  Japan
    1  Kenya

6. Facts come from your notes, not from a form. The fact store used to be empty on any real vault — nobody hand-writes - [fact] X :: y :: z. It now mines the conventions vaults already use:

status: shipped              # frontmatter
- owner:: Priya              # Dataview inline field
- [location] Hyderabad       # Basic Memory observation

Only unambiguous field syntax is accepted automatically. Prose formatting like - **Owner:** Priya is precise on entity notes and noisy on report notes, so it is opt-in (facts.extract: "all") — or an agent can review candidates via lore_propose_facts and assert the real ones. Judgement stays out of the index.

7. Time means when it happened, not when you saved the file. --since and --until filter on content time — taken from frontmatter dates, dated filenames (2025-03-14-standup.md), or dates in the text — falling back to file mtime only when a note carries no date of its own:

$ lore search ledger --since 2025-01-01 --until 2025-12-31
• 2025-03-14-standup.md › Standup  [all terms]
  Discussed the ledger migration.

Both files were written seconds ago, so an mtime filter could not tell them apart. lore watch keeps the index current so you never have to remember to reindex.

8. Built for agents. An MCP server exposes 15 typed tools so Claude Code, Cursor, or any MCP client can use your vault as durable memory — with a session context pack, fact assertion, point-in-time queries, and a reinforcement signal. Session continuity is a query, not a paraphrase: lore resume returns exactly what changed since the agent last connected, computed from record time —

$ lore resume
since 2026-08-11 15:55
~ lore/journal/2026-08-11.md
+ Project Atlas :: status :: shipped (since 2026-08-11)
± Project Atlas :: status: active → shipped

$ lore resume
since 2026-08-11 15:56
nothing changed

— where the popular alternatives run an LLM over the previous session and inject the summary: a paraphrase, unreproducible, wrong exactly when it matters.

The CLI

CommandWhat it does
lore initcreate .lore/ with a default config
lore index [--full] [--no-nlp] [--rebuild-similar]incremental sync of vault → index
lore search <q> [-k] [--since] [--until] [--tag] [--folder] [--json]hybrid retrieval with provenance
lore ask <q>extractive answer: current facts + top passages (no LLM needed)
lore facts [--subject] [--predicate] [--as-of] [--as-known-at] [--history]query the fact store
lore timeline <entity> [--since] [--until]chronological history: fact changes merged with dated mentions
lore resume [--since]what changed since the last resume: notes, facts, supersessions
lore review [--threshold] [--limit]important-but-fading knowledge to revisit or archive
lore assert <s> <p> <o…> [--valid-from]record a fact (journalled, supersedes)
lore invalidate <s> <p>close the current fact in a slot
lore count [--predicate] [--group-by] [--since]aggregate over fact history
lore capture <text…>append a timestamped line to lore/inbox.md
lore dream [--apply]consolidation pass + optional digest/review queue
lore watchreindex automatically as the vault changes
lore mark-used <note> [anchor]reinforce a passage that proved useful
lore graph export --format json|graphml|dotexport the graph
lore doctorhealth check: broken links, integrity, coverage
lore statsvault statistics and top entities
lore serve --mcpstart the MCP server on stdio

Use it as agent memory (MCP)

// Claude Code: .mcp.json  (or claude_desktop_config.json)
{
  "mcpServers": {
    "loreweave": {
      "command": "npx",
      "args": ["-y", "loreweave", "--vault", "/path/to/vault", "serve", "--mcp"]
    }
  }
}

Tools: lore_search, lore_context_pack, lore_read_note, lore_assert_fact, lore_invalidate_fact, lore_query_facts, lore_timeline, lore_resume, lore_review, lore_aggregate_facts, lore_capture, lore_mark_used, lore_propose_facts, lore_dream_report, lore_index.

Facts asserted through MCP are written back to lore/journal/YYYY-MM-DD.md as readable markdown lines, so an agent's memory is something you can open, read, edit, and git diff:

- [fact] Ledger Format :: status :: final {valid_from=2026-08-01, confidence=0.9, source=stated}

Delete .lore/ and reindex — every fact and edge is reconstructed from those files.

How it works

vault/*.md ──parse──▶ notes · blocks · wiki-links · tags · entities
                            │  (incremental: mtime + content hash)
                            ▼
                  SQLite .lore/index.db  ── disposable cache, rebuildable
                            │
        ┌───────────────────┼────────────────────┐
        ▼                   ▼                    ▼
   graph (CSR)         retrieval             facts
   blocks ∪ entities   BM25 + dense + PPR    bitemporal, supersession,
   2-iteration PPR     → weighted RRF        deterministic freshness,
   α = 0.5             → FSRS boosts         aggregates
        └─────────┬─────────┴──────────┬─────────┘
                  ▼                    ▼
             dream (idle-time)     CLI · MCP

Design rules the code enforces:

  • Files win. User markdown is never mutated. The engine only appends, and only under lore/.
  • Invariants in code, not prompts. Schema, migrations, graph construction, and supersession are typed, versioned, and tested — no LLM re-specifies them at runtime.
  • No LLM required anywhere in the core. Indexing and retrieval use zero tokens. Language models are consumers of this engine, not dependencies of it.
  • Everything is re-derivable. A full rebuild reproduces byte-identical derived state (there's a test for that).

Research lineage

Every significant choice traces to 2024-2026 literature; the full 87-finding survey lives in docs/research/ and the reasoning in docs/superpowers/specs/.

ChoiceSource
Dense-sparse fusion + PPR with dense reset probabilitiesHippoRAG 2 (ICML 2025), 2502.14802
Shallow 2-iteration PPR, heterogeneous nodesNodeRAG (2025), 2504.11544
Relation-free graph — no LLM triple extractionLinearRAG (ICLR 2026), 2510.10114; AtomicRAG (2026)
No index-time community summarizationLazyGraphRAG (Microsoft, 2024) — same quality at 0.1% index cost
Route/fuse instead of graph-everythingGraphRAG-Bench (ICLR 2026), 2506.05690
Bitemporal facts, invalidate-never-deleteZep/Graphiti (2025), 2501.13956
Typed version links (updates/extends/derives)Supermemory, SOTA on LongMemEval
Deterministic freshness, not LLM-judged"Don't Ask the LLM to Track Freshness" (2026)
Power-law forgetting, use-gated reinforcementFSRS; RMM (ACL 2025), 2503.08026
Consolidation as idle-time workSleep-time compute (Letta, 2025), 2504.13171
Never let an LLM rewrite whole memory filesACE (2025), 2510.04618
Computable facts for aggregationUser as Code (2026), 2606.16707
Fine-grained indexing + fact-augmented keysLongMemEval (ICLR 2025), 2410.10813

Library use

import { openContext, indexVault, search, assertFact, queryFacts, dream } from 'loreweave';

const ctx = openContext('/path/to/vault');
await indexVault(ctx.store, ctx.root);
const hits = await search(ctx, 'streaming compaction', { k: 5 });
assertFact(ctx, { subject: 'Atlas', predicate: 'status', object: 'shipped', validFrom: '2026-08-01' });
const asOfMarch = queryFacts(ctx.store, { subject: 'Atlas', asOf: '2026-03-01' });
const report = dream(ctx);
ctx.close();

Development

npm install
npm test          # 429 tests
npm run eval      # retrieval benchmark vs BM25 baseline
npm run typecheck
npm run build

Requires Node ≥ 20. Single native dependency (better-sqlite3). Tested in CI on Linux, macOS and Windows across Node 20 and 22.

License

MIT © Ambuj Upadhyay

Keywords

pkm

FAQs

Package last updated on 15 Aug 2026

Related posts