lorekeep 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. lorekeep-0.1.0/.github/workflows/release.yml +32 -0
  2. lorekeep-0.1.0/.gitignore +31 -0
  3. lorekeep-0.1.0/.lorekeep/config.yaml.example +31 -0
  4. lorekeep-0.1.0/LICENSE +21 -0
  5. lorekeep-0.1.0/PKG-INFO +246 -0
  6. lorekeep-0.1.0/README.md +224 -0
  7. lorekeep-0.1.0/SECURITY.md +76 -0
  8. lorekeep-0.1.0/docs/compile.md +69 -0
  9. lorekeep-0.1.0/docs/serve.md +65 -0
  10. lorekeep-0.1.0/docs/superpowers/plans/2026-06-14-lorekeep-plan-a-compile-pipeline.md +2393 -0
  11. lorekeep-0.1.0/docs/superpowers/plans/2026-06-14-lorekeep-plan-b-serve-mcp.md +2049 -0
  12. lorekeep-0.1.0/docs/superpowers/plans/2026-06-15-lorekeep-plan-c-data-home-dev-mode.md +618 -0
  13. lorekeep-0.1.0/docs/superpowers/specs/2026-06-14-lorekeep-temporal-kg-mcp-design.md +399 -0
  14. lorekeep-0.1.0/graph/schema.json +26 -0
  15. lorekeep-0.1.0/pyproject.toml +41 -0
  16. lorekeep-0.1.0/src/lorekeep/__init__.py +3 -0
  17. lorekeep-0.1.0/src/lorekeep/cli.py +229 -0
  18. lorekeep-0.1.0/src/lorekeep/compile/__init__.py +0 -0
  19. lorekeep-0.1.0/src/lorekeep/compile/extract.py +150 -0
  20. lorekeep-0.1.0/src/lorekeep/compile/ingest.py +55 -0
  21. lorekeep-0.1.0/src/lorekeep/compile/providers.py +49 -0
  22. lorekeep-0.1.0/src/lorekeep/compile/resolve.py +111 -0
  23. lorekeep-0.1.0/src/lorekeep/compile/writer.py +63 -0
  24. lorekeep-0.1.0/src/lorekeep/config.py +39 -0
  25. lorekeep-0.1.0/src/lorekeep/defaults.py +44 -0
  26. lorekeep-0.1.0/src/lorekeep/eval/__init__.py +0 -0
  27. lorekeep-0.1.0/src/lorekeep/eval/construction.py +97 -0
  28. lorekeep-0.1.0/src/lorekeep/eval/gold.py +31 -0
  29. lorekeep-0.1.0/src/lorekeep/eval/retrieval.py +46 -0
  30. lorekeep-0.1.0/src/lorekeep/facts_io.py +22 -0
  31. lorekeep-0.1.0/src/lorekeep/integrations/__init__.py +0 -0
  32. lorekeep-0.1.0/src/lorekeep/integrations/claude_code.py +19 -0
  33. lorekeep-0.1.0/src/lorekeep/integrations/codex.py +56 -0
  34. lorekeep-0.1.0/src/lorekeep/integrations/common.py +23 -0
  35. lorekeep-0.1.0/src/lorekeep/integrations/cursor.py +21 -0
  36. lorekeep-0.1.0/src/lorekeep/mcp_server.py +120 -0
  37. lorekeep-0.1.0/src/lorekeep/models.py +130 -0
  38. lorekeep-0.1.0/src/lorekeep/paths.py +58 -0
  39. lorekeep-0.1.0/src/lorekeep/perm/__init__.py +0 -0
  40. lorekeep-0.1.0/src/lorekeep/perm/ns.py +113 -0
  41. lorekeep-0.1.0/src/lorekeep/pipeline.py +67 -0
  42. lorekeep-0.1.0/src/lorekeep/schema_io.py +12 -0
  43. lorekeep-0.1.0/src/lorekeep/store/__init__.py +0 -0
  44. lorekeep-0.1.0/src/lorekeep/store/fts.py +54 -0
  45. lorekeep-0.1.0/src/lorekeep/store/graph.py +137 -0
  46. lorekeep-0.1.0/tests/conftest.py +7 -0
  47. lorekeep-0.1.0/tests/fixtures/gold/payments.facts.jsonl +6 -0
  48. lorekeep-0.1.0/tests/fixtures/raw/backend/payments.md +11 -0
  49. lorekeep-0.1.0/tests/fixtures/retrieval/questions.json +24 -0
  50. lorekeep-0.1.0/tests/fixtures/schema.json +13 -0
  51. lorekeep-0.1.0/tests/test_compile_cli.py +23 -0
  52. lorekeep-0.1.0/tests/test_config.py +24 -0
  53. lorekeep-0.1.0/tests/test_construction_eval.py +105 -0
  54. lorekeep-0.1.0/tests/test_defaults.py +21 -0
  55. lorekeep-0.1.0/tests/test_determinism.py +53 -0
  56. lorekeep-0.1.0/tests/test_doctor_cli.py +25 -0
  57. lorekeep-0.1.0/tests/test_eval_cli.py +35 -0
  58. lorekeep-0.1.0/tests/test_extract.py +125 -0
  59. lorekeep-0.1.0/tests/test_fts.py +31 -0
  60. lorekeep-0.1.0/tests/test_graph_store.py +103 -0
  61. lorekeep-0.1.0/tests/test_ingest.py +47 -0
  62. lorekeep-0.1.0/tests/test_init_cli.py +31 -0
  63. lorekeep-0.1.0/tests/test_integrations.py +68 -0
  64. lorekeep-0.1.0/tests/test_mcp_add_cli.py +28 -0
  65. lorekeep-0.1.0/tests/test_mcp_reload.py +32 -0
  66. lorekeep-0.1.0/tests/test_mcp_server.py +74 -0
  67. lorekeep-0.1.0/tests/test_models.py +46 -0
  68. lorekeep-0.1.0/tests/test_paths.py +57 -0
  69. lorekeep-0.1.0/tests/test_perm.py +122 -0
  70. lorekeep-0.1.0/tests/test_pipeline.py +45 -0
  71. lorekeep-0.1.0/tests/test_providers.py +22 -0
  72. lorekeep-0.1.0/tests/test_resolve.py +66 -0
  73. lorekeep-0.1.0/tests/test_retrieval_eval.py +17 -0
  74. lorekeep-0.1.0/tests/test_schema_io.py +12 -0
  75. lorekeep-0.1.0/tests/test_serve_cli.py +28 -0
  76. lorekeep-0.1.0/tests/test_smoke.py +10 -0
  77. lorekeep-0.1.0/tests/test_writer.py +54 -0
  78. lorekeep-0.1.0/uv.lock +2377 -0
@@ -0,0 +1,32 @@
1
+ name: release
2
+
3
+ # Publish to PyPI via OIDC trusted publishing (no API token).
4
+ # Triggers on a published GitHub Release. Requires a "pending trusted publisher"
5
+ # configured on PyPI (project = lorekeep, repo = manhhailua/lorekeep,
6
+ # workflow = release.yml, environment = blank) before the first release.
7
+ on:
8
+ release:
9
+ types: [published]
10
+ workflow_dispatch: {}
11
+
12
+ permissions:
13
+ contents: read
14
+ id-token: write # required for PyPI trusted publishing
15
+
16
+ jobs:
17
+ build-and-publish:
18
+ runs-on: ubuntu-latest
19
+ environment: pypi
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - name: Set up uv
24
+ uses: astral-sh/setup-uv@v5
25
+ with:
26
+ enable-cache: true
27
+
28
+ - name: Build sdist + wheel
29
+ run: uv build
30
+
31
+ - name: Publish to PyPI
32
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,31 @@
1
+ # Lorekeep local-only artifacts (never committed) — except the config template
2
+ .lorekeep/*
3
+ !.lorekeep/config.yaml.example
4
+ *.sqlite
5
+ *.sqlite-*
6
+
7
+ # Lorekeep compiled graph (reproducible via `lorekeep compile`; keep schema.json)
8
+ graph/facts.jsonl
9
+ graph/manifest.json
10
+
11
+ # Personal trial data (local-only; do not commit — contains personal/work memory)
12
+ raw/*-memory/
13
+ raw/personal/
14
+ .mcp.json
15
+
16
+ # Python
17
+ __pycache__/
18
+ *.py[cod]
19
+ .venv/
20
+ venv/
21
+ dist/
22
+ build/
23
+ *.egg-info/
24
+ .ruff_cache/
25
+ .mypy_cache/
26
+ .pytest_cache/
27
+
28
+ # OS / editors
29
+ .DS_Store
30
+ .idea/
31
+ .vscode/
@@ -0,0 +1,31 @@
1
+ # Copy to .lorekeep/config.yaml (gitignored). Provider used at compile time only.
2
+ # API keys NEVER go in this file - set them as env vars (api_key_env names which).
3
+ #
4
+ # Option A: Alibaba ModelStudio / DashScope (international, OpenAI-compatible)
5
+ provider:
6
+ backend: openai
7
+ model: openai/qwen-plus # qwen-max | qwen-plus | qwen-turbo
8
+ api_base: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
9
+ api_key_env: DASHSCOPE_API_KEY # export DASHSCOPE_API_KEY=...
10
+ temperature: 0.0
11
+ #
12
+ # Option B: OpenAI
13
+ # provider:
14
+ # backend: openai
15
+ # model: openai/gpt-4o-mini
16
+ # api_base: null
17
+ # api_key_env: OPENAI_API_KEY
18
+ # temperature: 0.0
19
+ #
20
+ # Option C: Ollama (local, strict privacy)
21
+ # provider:
22
+ # backend: ollama
23
+ # model: ollama/llama3
24
+ # api_base: http://localhost:11434
25
+ # api_key_env: null
26
+ # temperature: 0.0
27
+ compile:
28
+ chunk_lines: 60
29
+ ns:
30
+ default: [public]
31
+ install_source: pypi # pypi (portable .mcp.json) | local | git+URL | path
lorekeep-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manh Pham
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: lorekeep
3
+ Version: 0.1.0
4
+ Summary: Temporal knowledge graph for AI agents via MCP
5
+ Project-URL: Homepage, https://github.com/manhhailua/lorekeep
6
+ Project-URL: Repository, https://github.com/manhhailua/lorekeep
7
+ Project-URL: Security, https://github.com/manhhailua/lorekeep/security/policy
8
+ Author: Manh Pham
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: litellm>=1.40
13
+ Requires-Dist: mcp>=1.0
14
+ Requires-Dist: mistune>=3.0
15
+ Requires-Dist: networkx>=3.2
16
+ Requires-Dist: platformdirs>=4.0
17
+ Requires-Dist: pydantic>=2.6
18
+ Requires-Dist: pyyaml>=6.0
19
+ Requires-Dist: rich>=13.7
20
+ Requires-Dist: typer>=0.12
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Lorekeep
24
+
25
+ **A temporal knowledge graph for AI agents, served read-only over MCP.**
26
+
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
28
+
29
+ Lorekeep compiles a team's raw documentation into a versioned, time-aware
30
+ knowledge graph (`facts.jsonl`) and exposes it to coding agents (Claude Code,
31
+ Cursor, Codex) through the Model Context Protocol — with per-namespace
32
+ permission and zero servers to run.
33
+
34
+ It applies Andrej Karpathy's "LLM Knowledge Base" idea: raw docs are the
35
+ **source code**, the compiled graph is the **executable**. Knowledge is
36
+ processed once at compile time, not re-RAG'd on every query.
37
+
38
+ ---
39
+
40
+ ## Why
41
+
42
+ Existing tools each miss part of what a team needs:
43
+
44
+ | | file-based | temporal KG | compile step | team permission | MCP |
45
+ |---|---|---|---|---|---|
46
+ | Obsidian + MCP | ✅ | ❌ | ❌ | ❌ | ✅ |
47
+ | mcp-knowledge-graph | ✅ | ❌ | ❌ | ❌ (local) | ✅ |
48
+ | mem0 / cognee | ❌ (DB) | partial | ❌ | partial (DB) | ✅ |
49
+
50
+ Lorekeep targets the gap: **strictly file-based + temporal graph + compile-once +
51
+ namespace-scoped permission + MCP** — for team-level (not just single-user)
52
+ knowledge.
53
+
54
+ ## Features
55
+
56
+ - **Compile-only** — a curator (human + LLM) builds the graph; agents only read.
57
+ No write path, no concurrency hell, deterministic output.
58
+ - **File-sovereign** — `facts.jsonl` (one fact per line, sorted) is the single
59
+ source of truth and the sync unit (git or S3). No binary store committed.
60
+ - **Temporal** — every fact carries `valid_from`/`valid_to` (half-open
61
+ `[from, to)`); query "what was true at *T*", history, diffs.
62
+ - **Namespace permission** — facts are tagged `ns` from the directory tree
63
+ (`raw/<ns>/`); agents scoped to namespaces; cross-namespace edges
64
+ hidden unless both endpoints are visible. Deny-by-default.
65
+ - **MCP, stdio-first** — `lorekeep serve` exposes 8 read-only tools; `lorekeep mcp add`
66
+ wires Claude Code / Cursor / Codex. No server process to babysit.
67
+ - **Lazy-reload** — `lorekeep compile` updates the graph; the MCP server
68
+ auto-refreshes on the next query. Connect once, use forever.
69
+ - **Provider-pluggable extraction** — litellm (OpenAI / Anthropic /
70
+ DashScope/Qwen / Ollama). Strict-privacy → Ollama, fully local.
71
+ - **Tier-1 eval** — extraction P/R/F1 vs a gold corpus, entity-resolution F1,
72
+ graph-structure metrics, determinism property tests.
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ # from PyPI:
78
+ uvx lorekeep init # try it without installing
79
+
80
+ # or from a clone:
81
+ git clone https://github.com/manhhailua/lorekeep && cd lorekeep
82
+ uv tool install . # installs the `lorekeep` command
83
+ ```
84
+
85
+ ## Quickstart
86
+
87
+ ```bash
88
+ # 1. bootstrap a data home (~/.config/lorekeep + ~/.local/share/lorekeep)
89
+ uvx lorekeep init
90
+
91
+ # 2. add docs under the data home's raw/<namespace>/
92
+ mkdir -p ~/.local/share/lorekeep/raw/backend
93
+ cp your-docs.md ~/.local/share/lorekeep/raw/backend/
94
+
95
+ # 3. set a provider (edit ~/.config/lorekeep/config.yaml), then compile
96
+ uvx lorekeep compile # raw/*.md -> graph/facts.jsonl
97
+
98
+ # 4. wire a coding agent (writes a portable .mcp.json)
99
+ uvx lorekeep mcp add --agent claude --ns backend
100
+
101
+ # 5. verify
102
+ uvx lorekeep doctor
103
+ ```
104
+
105
+ Restart Claude Code → the 8 Lorekeep tools are available, scoped to your namespace.
106
+
107
+ ## How it works
108
+
109
+ ```
110
+ COMPILE (offline, curator) SYNC
111
+ raw/<ns>/*.md ──► ingest ──► extract(LLM) ──► resolve ──► writer ──► facts.jsonl
112
+
113
+ ┌─────────────────────────────────┘
114
+ ▼ (git pull / aws s3 sync)
115
+ SERVE + QUERY (runtime, per device)
116
+ facts.jsonl ──load──► GraphStore (networkx, temporal) ──► ScopedGraph (ns) ──► MCP ──► agent
117
+ ▲ │
118
+ └── lazy-reload on mtime change ◄──┘
119
+ ```
120
+
121
+ **Pipeline** (`ingest → extract → resolve → writer`): markdown is chunked with
122
+ provenance; an LLM extracts schema-constrained nodes/edges with temporal +
123
+ namespace tags; aliases collapse to canonical entities; a deterministic writer
124
+ emits sorted, byte-stable `facts.jsonl` + a `manifest.json` (provenance +
125
+ errors + quarantine). Re-compiling unchanged input is byte-identical (per-chunk
126
+ hash cache), so git diffs stay clean.
127
+
128
+ **Serve**: `GraphStore` loads `facts.jsonl` into a networkx graph with temporal
129
+ queries. `ScopedGraph` is the single permission chokepoint — every query is
130
+ filtered through strict visibility rules. The FastMCP server is a thin layer of
131
+ read-only tools over `ScopedGraph`. It lazy-reloads when `facts.jsonl` changes,
132
+ so `compile` is instantly visible without reconnecting.
133
+
134
+ ## Concepts
135
+
136
+ **fact** — one line of `facts.jsonl`, a `node` or `edge`:
137
+ ```jsonl
138
+ {"kind":"node","id":"svc:payments","type":"service","ns":["backend"],"valid_from":"2024-01-15","valid_to":null,"props":{"lang":"go"},"src":["raw/backend/payments.md:12"]}
139
+ {"kind":"edge","id":"e_depends_on_0001","type":"depends_on","from":"svc:payments","to":"svc:auth","ns":["backend"],"valid_from":"2024-01-15","valid_to":"2025-03-01","props":{},"src":["...:20"]}
140
+ ```
141
+ - `ns` — namespace set; `["public"]` is globally visible.
142
+ - `valid_to: null` ⇒ current. History = multiple edges, same endpoints, different windows.
143
+ - `src` — provenance to raw doc line (audit, incremental re-compile, agent citations).
144
+
145
+ **Permission** — effective_ns = allowed ∪ {public}. Node visible iff
146
+ `ns ∩ effective_ns ≠ ∅`. Edge visible iff **both** endpoints visible **and**
147
+ `edge.ns ∩ effective_ns ≠ ∅`. Deny-by-default; an edge never reveals a
148
+ neighbor the caller can't see.
149
+
150
+ **Temporal queries** — `at_time(T)` (snapshot of facts valid at T, half-open
151
+ `[from,to)`), `history(id)` (versions of an entity), `changes(t1,t2)` (edges
152
+ that began/ended in the window).
153
+
154
+ ## MCP tools (read-only, scoped)
155
+
156
+ `search` · `get_node` · `neighbors` · `at_time` · `history` · `changes` ·
157
+ `list_namespaces` · `schema`. Every result is filtered to the caller's
158
+ namespace.
159
+
160
+ ## Configuration
161
+
162
+ `config.yaml` (resolved by precedence: explicit `LOREKEEP_*` env > `LOREKEEP_HOME` >
163
+ dev marker > XDG):
164
+ ```yaml
165
+ provider:
166
+ model: openai/qwen-plus # litellm model string
167
+ api_base: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
168
+ api_key_env: DASHSCOPE_API_KEY # env var name (preferred)
169
+ api_key: null # or inline (gitignored config only)
170
+ ns:
171
+ default: [public]
172
+ install_source: pypi # pypi = portable .mcp.json
173
+ ```
174
+ API keys never live in committed files — use `api_key_env` (env) or inline
175
+ `api_key` in the gitignored config only. Examples (DashScope / OpenAI / Ollama)
176
+ in [`.lorekeep/config.yaml.example`](.lorekeep/config.yaml.example).
177
+
178
+ ## Data home & dev mode
179
+
180
+ Path resolution (high → low): explicit `LOREKEEP_*` env → `LOREKEEP_HOME` →
181
+ **dev mode** (`.lorekeep/` or `raw/` in CWD; auto-detected in a source checkout)
182
+ → XDG (`~/.config/lorekeep`, `~/.local/share/lorekeep`). So:
183
+
184
+ - **Installed**: `uvx lorekeep init` bootstraps the XDG home.
185
+ - **Local dev**: from the repo, `uv run lorekeep compile` uses the repo's
186
+ `raw/` + `graph/` (zero migration).
187
+ - **Custom KB**: `LOREKEEP_HOME=~/kb-work uvx lorekeep …`.
188
+
189
+ See [`docs/compile.md`](docs/compile.md) and [`docs/serve.md`](docs/serve.md).
190
+
191
+ ## Evaluation
192
+
193
+ Tier-1 (CI): extraction P/R/F1 vs a gold corpus, entity-resolution pairwise F1,
194
+ graph-structure metrics, determinism. Run: `uvx lorekeep eval`. The north star is
195
+ *systematic thinking with complete information* — memory-recall benchmarks
196
+ (LoCoMo, LongMemEval) are parity checks, not the optimization target. See the
197
+ [design spec](docs/superpowers/specs/2026-06-14-lorekeep-temporal-kg-mcp-design.md) §16.
198
+
199
+ ## Project layout
200
+
201
+ ```
202
+ src/lorekeep/
203
+ models.py shared contract (Node/Edge/Schema/Manifest)
204
+ facts_io.py facts.jsonl loader (store + eval)
205
+ paths.py 4-tier path resolution (env/home/dev/XDG)
206
+ defaults.py default schema + config (for `init`)
207
+ config.py, schema_io.py
208
+ compile/{ingest,extract,resolve,writer}.py the compile pipeline
209
+ compile/providers.py LLMProvider (Fake/LiteLLM)
210
+ store/{graph,fts}.py GraphStore + optional FTS cache
211
+ perm/ns.py ScopedGraph permission chokepoint
212
+ mcp_server.py FastMCP + 8 read tools (lazy-reload)
213
+ integrations/{claude_code,cursor,codex,common}.py
214
+ pipeline.py, cli.py
215
+ eval/{gold,construction,retrieval}.py
216
+ tests/ ~106 tests
217
+ docs/ compile.md, serve.md, specs/, plans/
218
+ ```
219
+
220
+ ## Status
221
+
222
+ **v1** — compile pipeline + serve (store/permission/MCP/integrations) + data-home
223
+ + dev mode + lazy-reload, all merged to `main`, 114 tests green. Published to
224
+ PyPI as `lorekeep`.
225
+
226
+ Roadmap (phase 2+): streamable-HTTP team server, OIDC/SSO,
227
+ embeddings/hybrid search, `wiki.md` views, full Tier-2 benchmark datasets
228
+ (HotpotQA/CronQuestions) and the bespoke Tier-3 Lorekeep-Reason eval.
229
+
230
+ ## Documentation
231
+
232
+ - [Compile quickstart](docs/compile.md)
233
+ - [Serve to coding agents](docs/serve.md)
234
+ - [Design spec (architecture, permission, temporal, eval)](docs/superpowers/specs/2026-06-14-lorekeep-temporal-kg-mcp-design.md)
235
+ - Implementation plans: [A compile](docs/superpowers/plans/2026-06-14-lorekeep-plan-a-compile-pipeline.md),
236
+ [B serve](docs/superpowers/plans/2026-06-14-lorekeep-plan-b-serve-mcp.md),
237
+ [C data-home](docs/superpowers/plans/2026-06-15-lorekeep-plan-c-data-home-dev-mode.md)
238
+
239
+ ## License
240
+
241
+ Lorekeep is released under the **MIT License** — see [`LICENSE`](LICENSE).
242
+
243
+ Copyright © 2026 Manh Pham. You're free to use, copy, modify, merge, publish,
244
+ distribute, sublicense, and/or sell copies of the software, provided the
245
+ copyright and permission notice are included in all copies. The software is
246
+ provided "as is", without warranty of any kind.
@@ -0,0 +1,224 @@
1
+ # Lorekeep
2
+
3
+ **A temporal knowledge graph for AI agents, served read-only over MCP.**
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ Lorekeep compiles a team's raw documentation into a versioned, time-aware
8
+ knowledge graph (`facts.jsonl`) and exposes it to coding agents (Claude Code,
9
+ Cursor, Codex) through the Model Context Protocol — with per-namespace
10
+ permission and zero servers to run.
11
+
12
+ It applies Andrej Karpathy's "LLM Knowledge Base" idea: raw docs are the
13
+ **source code**, the compiled graph is the **executable**. Knowledge is
14
+ processed once at compile time, not re-RAG'd on every query.
15
+
16
+ ---
17
+
18
+ ## Why
19
+
20
+ Existing tools each miss part of what a team needs:
21
+
22
+ | | file-based | temporal KG | compile step | team permission | MCP |
23
+ |---|---|---|---|---|---|
24
+ | Obsidian + MCP | ✅ | ❌ | ❌ | ❌ | ✅ |
25
+ | mcp-knowledge-graph | ✅ | ❌ | ❌ | ❌ (local) | ✅ |
26
+ | mem0 / cognee | ❌ (DB) | partial | ❌ | partial (DB) | ✅ |
27
+
28
+ Lorekeep targets the gap: **strictly file-based + temporal graph + compile-once +
29
+ namespace-scoped permission + MCP** — for team-level (not just single-user)
30
+ knowledge.
31
+
32
+ ## Features
33
+
34
+ - **Compile-only** — a curator (human + LLM) builds the graph; agents only read.
35
+ No write path, no concurrency hell, deterministic output.
36
+ - **File-sovereign** — `facts.jsonl` (one fact per line, sorted) is the single
37
+ source of truth and the sync unit (git or S3). No binary store committed.
38
+ - **Temporal** — every fact carries `valid_from`/`valid_to` (half-open
39
+ `[from, to)`); query "what was true at *T*", history, diffs.
40
+ - **Namespace permission** — facts are tagged `ns` from the directory tree
41
+ (`raw/<ns>/`); agents scoped to namespaces; cross-namespace edges
42
+ hidden unless both endpoints are visible. Deny-by-default.
43
+ - **MCP, stdio-first** — `lorekeep serve` exposes 8 read-only tools; `lorekeep mcp add`
44
+ wires Claude Code / Cursor / Codex. No server process to babysit.
45
+ - **Lazy-reload** — `lorekeep compile` updates the graph; the MCP server
46
+ auto-refreshes on the next query. Connect once, use forever.
47
+ - **Provider-pluggable extraction** — litellm (OpenAI / Anthropic /
48
+ DashScope/Qwen / Ollama). Strict-privacy → Ollama, fully local.
49
+ - **Tier-1 eval** — extraction P/R/F1 vs a gold corpus, entity-resolution F1,
50
+ graph-structure metrics, determinism property tests.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ # from PyPI:
56
+ uvx lorekeep init # try it without installing
57
+
58
+ # or from a clone:
59
+ git clone https://github.com/manhhailua/lorekeep && cd lorekeep
60
+ uv tool install . # installs the `lorekeep` command
61
+ ```
62
+
63
+ ## Quickstart
64
+
65
+ ```bash
66
+ # 1. bootstrap a data home (~/.config/lorekeep + ~/.local/share/lorekeep)
67
+ uvx lorekeep init
68
+
69
+ # 2. add docs under the data home's raw/<namespace>/
70
+ mkdir -p ~/.local/share/lorekeep/raw/backend
71
+ cp your-docs.md ~/.local/share/lorekeep/raw/backend/
72
+
73
+ # 3. set a provider (edit ~/.config/lorekeep/config.yaml), then compile
74
+ uvx lorekeep compile # raw/*.md -> graph/facts.jsonl
75
+
76
+ # 4. wire a coding agent (writes a portable .mcp.json)
77
+ uvx lorekeep mcp add --agent claude --ns backend
78
+
79
+ # 5. verify
80
+ uvx lorekeep doctor
81
+ ```
82
+
83
+ Restart Claude Code → the 8 Lorekeep tools are available, scoped to your namespace.
84
+
85
+ ## How it works
86
+
87
+ ```
88
+ COMPILE (offline, curator) SYNC
89
+ raw/<ns>/*.md ──► ingest ──► extract(LLM) ──► resolve ──► writer ──► facts.jsonl
90
+
91
+ ┌─────────────────────────────────┘
92
+ ▼ (git pull / aws s3 sync)
93
+ SERVE + QUERY (runtime, per device)
94
+ facts.jsonl ──load──► GraphStore (networkx, temporal) ──► ScopedGraph (ns) ──► MCP ──► agent
95
+ ▲ │
96
+ └── lazy-reload on mtime change ◄──┘
97
+ ```
98
+
99
+ **Pipeline** (`ingest → extract → resolve → writer`): markdown is chunked with
100
+ provenance; an LLM extracts schema-constrained nodes/edges with temporal +
101
+ namespace tags; aliases collapse to canonical entities; a deterministic writer
102
+ emits sorted, byte-stable `facts.jsonl` + a `manifest.json` (provenance +
103
+ errors + quarantine). Re-compiling unchanged input is byte-identical (per-chunk
104
+ hash cache), so git diffs stay clean.
105
+
106
+ **Serve**: `GraphStore` loads `facts.jsonl` into a networkx graph with temporal
107
+ queries. `ScopedGraph` is the single permission chokepoint — every query is
108
+ filtered through strict visibility rules. The FastMCP server is a thin layer of
109
+ read-only tools over `ScopedGraph`. It lazy-reloads when `facts.jsonl` changes,
110
+ so `compile` is instantly visible without reconnecting.
111
+
112
+ ## Concepts
113
+
114
+ **fact** — one line of `facts.jsonl`, a `node` or `edge`:
115
+ ```jsonl
116
+ {"kind":"node","id":"svc:payments","type":"service","ns":["backend"],"valid_from":"2024-01-15","valid_to":null,"props":{"lang":"go"},"src":["raw/backend/payments.md:12"]}
117
+ {"kind":"edge","id":"e_depends_on_0001","type":"depends_on","from":"svc:payments","to":"svc:auth","ns":["backend"],"valid_from":"2024-01-15","valid_to":"2025-03-01","props":{},"src":["...:20"]}
118
+ ```
119
+ - `ns` — namespace set; `["public"]` is globally visible.
120
+ - `valid_to: null` ⇒ current. History = multiple edges, same endpoints, different windows.
121
+ - `src` — provenance to raw doc line (audit, incremental re-compile, agent citations).
122
+
123
+ **Permission** — effective_ns = allowed ∪ {public}. Node visible iff
124
+ `ns ∩ effective_ns ≠ ∅`. Edge visible iff **both** endpoints visible **and**
125
+ `edge.ns ∩ effective_ns ≠ ∅`. Deny-by-default; an edge never reveals a
126
+ neighbor the caller can't see.
127
+
128
+ **Temporal queries** — `at_time(T)` (snapshot of facts valid at T, half-open
129
+ `[from,to)`), `history(id)` (versions of an entity), `changes(t1,t2)` (edges
130
+ that began/ended in the window).
131
+
132
+ ## MCP tools (read-only, scoped)
133
+
134
+ `search` · `get_node` · `neighbors` · `at_time` · `history` · `changes` ·
135
+ `list_namespaces` · `schema`. Every result is filtered to the caller's
136
+ namespace.
137
+
138
+ ## Configuration
139
+
140
+ `config.yaml` (resolved by precedence: explicit `LOREKEEP_*` env > `LOREKEEP_HOME` >
141
+ dev marker > XDG):
142
+ ```yaml
143
+ provider:
144
+ model: openai/qwen-plus # litellm model string
145
+ api_base: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
146
+ api_key_env: DASHSCOPE_API_KEY # env var name (preferred)
147
+ api_key: null # or inline (gitignored config only)
148
+ ns:
149
+ default: [public]
150
+ install_source: pypi # pypi = portable .mcp.json
151
+ ```
152
+ API keys never live in committed files — use `api_key_env` (env) or inline
153
+ `api_key` in the gitignored config only. Examples (DashScope / OpenAI / Ollama)
154
+ in [`.lorekeep/config.yaml.example`](.lorekeep/config.yaml.example).
155
+
156
+ ## Data home & dev mode
157
+
158
+ Path resolution (high → low): explicit `LOREKEEP_*` env → `LOREKEEP_HOME` →
159
+ **dev mode** (`.lorekeep/` or `raw/` in CWD; auto-detected in a source checkout)
160
+ → XDG (`~/.config/lorekeep`, `~/.local/share/lorekeep`). So:
161
+
162
+ - **Installed**: `uvx lorekeep init` bootstraps the XDG home.
163
+ - **Local dev**: from the repo, `uv run lorekeep compile` uses the repo's
164
+ `raw/` + `graph/` (zero migration).
165
+ - **Custom KB**: `LOREKEEP_HOME=~/kb-work uvx lorekeep …`.
166
+
167
+ See [`docs/compile.md`](docs/compile.md) and [`docs/serve.md`](docs/serve.md).
168
+
169
+ ## Evaluation
170
+
171
+ Tier-1 (CI): extraction P/R/F1 vs a gold corpus, entity-resolution pairwise F1,
172
+ graph-structure metrics, determinism. Run: `uvx lorekeep eval`. The north star is
173
+ *systematic thinking with complete information* — memory-recall benchmarks
174
+ (LoCoMo, LongMemEval) are parity checks, not the optimization target. See the
175
+ [design spec](docs/superpowers/specs/2026-06-14-lorekeep-temporal-kg-mcp-design.md) §16.
176
+
177
+ ## Project layout
178
+
179
+ ```
180
+ src/lorekeep/
181
+ models.py shared contract (Node/Edge/Schema/Manifest)
182
+ facts_io.py facts.jsonl loader (store + eval)
183
+ paths.py 4-tier path resolution (env/home/dev/XDG)
184
+ defaults.py default schema + config (for `init`)
185
+ config.py, schema_io.py
186
+ compile/{ingest,extract,resolve,writer}.py the compile pipeline
187
+ compile/providers.py LLMProvider (Fake/LiteLLM)
188
+ store/{graph,fts}.py GraphStore + optional FTS cache
189
+ perm/ns.py ScopedGraph permission chokepoint
190
+ mcp_server.py FastMCP + 8 read tools (lazy-reload)
191
+ integrations/{claude_code,cursor,codex,common}.py
192
+ pipeline.py, cli.py
193
+ eval/{gold,construction,retrieval}.py
194
+ tests/ ~106 tests
195
+ docs/ compile.md, serve.md, specs/, plans/
196
+ ```
197
+
198
+ ## Status
199
+
200
+ **v1** — compile pipeline + serve (store/permission/MCP/integrations) + data-home
201
+ + dev mode + lazy-reload, all merged to `main`, 114 tests green. Published to
202
+ PyPI as `lorekeep`.
203
+
204
+ Roadmap (phase 2+): streamable-HTTP team server, OIDC/SSO,
205
+ embeddings/hybrid search, `wiki.md` views, full Tier-2 benchmark datasets
206
+ (HotpotQA/CronQuestions) and the bespoke Tier-3 Lorekeep-Reason eval.
207
+
208
+ ## Documentation
209
+
210
+ - [Compile quickstart](docs/compile.md)
211
+ - [Serve to coding agents](docs/serve.md)
212
+ - [Design spec (architecture, permission, temporal, eval)](docs/superpowers/specs/2026-06-14-lorekeep-temporal-kg-mcp-design.md)
213
+ - Implementation plans: [A compile](docs/superpowers/plans/2026-06-14-lorekeep-plan-a-compile-pipeline.md),
214
+ [B serve](docs/superpowers/plans/2026-06-14-lorekeep-plan-b-serve-mcp.md),
215
+ [C data-home](docs/superpowers/plans/2026-06-15-lorekeep-plan-c-data-home-dev-mode.md)
216
+
217
+ ## License
218
+
219
+ Lorekeep is released under the **MIT License** — see [`LICENSE`](LICENSE).
220
+
221
+ Copyright © 2026 Manh Pham. You're free to use, copy, modify, merge, publish,
222
+ distribute, sublicense, and/or sell copies of the software, provided the
223
+ copyright and permission notice are included in all copies. The software is
224
+ provided "as is", without warranty of any kind.
@@ -0,0 +1,76 @@
1
+ # Security policy — Lorekeep
2
+
3
+ Lorekeep compiles team documents into a temporal knowledge graph and serves it
4
+ read-only to AI coding agents over MCP. This document describes the threat
5
+ model and the configuration decisions that keep a deployment safe.
6
+
7
+ ## Trust model
8
+
9
+ - **Compile-only, single writer.** The graph (`graph/facts.jsonl`) is produced by
10
+ `lorekeep compile` and never mutated by the server. Agents read via MCP; there is
11
+ no write API. No concurrency control is needed because readers are read-only.
12
+ - **Per-process namespace scope.** An MCP server's visible data is fixed at startup
13
+ by `LOREKEEP_NS` (comma-separated namespaces). Visibility is enforced by a single
14
+ chokepoint, `ScopedGraph` (`src/lorekeep/perm/ns.py`), applied to **every** query.
15
+ - **Deny-by-default.** `effective_ns = allowed ∪ {public}`. A node is visible iff
16
+ `node.ns ∩ effective_ns ≠ ∅`; an edge is visible iff **both** endpoints are
17
+ visible **and** `edge.ns ∩ effective_ns ≠ ∅`.
18
+ - **No information oracle.** `get_node` returns the same `"not found or out of
19
+ scope"` whether a node is absent or merely outside scope. `list_namespaces`
20
+ returns only the caller's own `effective_ns` — it does **not** enumerate
21
+ namespace names that exist but are hidden.
22
+
23
+ ## Compile-time data egress
24
+
25
+ At compile, **every file under `raw/` is sent to the configured LLM provider**
26
+ for extraction. This is by design (the documents *are* the knowledge graph's
27
+ source), but it has two consequences:
28
+
29
+ 1. Treat `raw/` as trusted content. Do not point `LOREKEEP_RAW` at a directory
30
+ that holds secrets.
31
+ 2. **Symlink guard.** `compile/ingest.py` skips any file whose resolved target
32
+ escapes `raw_root` and warns on stderr. This prevents a planted symlink
33
+ (`raw/x/leak.md -> ~/.ssh/id_rsa`) from exfiltrating files outside `raw/` to
34
+ the provider. Keep this guard; do not disable it.
35
+
36
+ For team/shared `raw/` directories (a stated target), the trust boundary is
37
+ "anyone who can write to `raw/`". Isolate raw/ per team and compile per team.
38
+
39
+ ## API keys
40
+
41
+ - Prefer `api_key_env` (the name of an environment variable) over an inline
42
+ `api_key`. The provider resolves the env var first and only falls back to the
43
+ inline value if the env var is unset.
44
+ - `config.yaml` is gitignored by default (`.lorekeep/*` except the `.example`
45
+ template). **Never commit a real `config.yaml`.** If an inline key is used,
46
+ `lorekeep compile` prints a warning recommending `api_key_env`.
47
+ - Keys are passed only to `litellm.completion` at compile; the server never reads
48
+ or transmits a key.
49
+
50
+ ## No remote-code surface
51
+
52
+ - No `subprocess`, shell, `eval`, `exec`, `pickle`, or `marshal`. Config uses
53
+ `yaml.safe_load`; all user-supplied data is parsed as JSON. SQLite FTS uses
54
+ parameterized queries.
55
+ - Filesystem writes are confined to the data home (`raw/`, `graph/`, `.lorekeep/`)
56
+ and, for `lorekeep mcp add`, the agent config file (`.mcp.json` /
57
+ `.cursor/mcp.json` / `config.toml`), which is merged, not clobbered.
58
+ - `facts.jsonl` and `manifest.json` are written atomically (temp file +
59
+ `os.replace`), so a concurrent read during compile never sees a partial file.
60
+
61
+ ## Reporting a vulnerability
62
+
63
+ Please open a private security advisory on
64
+ [github.com/manhhailua/lorekeep](https://github.com/manhhailua/lorekeep/security/advisories/new)
65
+ rather than a public issue. Include the affected version, a reproduction, and
66
+ impact. Reports are acknowledged within 7 days. A fix and disclosure are
67
+ coordinated with the reporter.
68
+
69
+ ## Residual accepted risks
70
+
71
+ - An inline `api_key` may live in a gitignored `config.yaml`. Owners are
72
+ responsible for keeping that file local.
73
+ - Shared `raw/` directories trust everyone with write access (see egress above).
74
+ - The MCP client (the coding agent) is trusted within its `LOREKEEP_NS` scope: it
75
+ can read everything in scope, which is the intended behavior. Scope assignment
76
+ is an operational responsibility, not enforced by the server.