outmem 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 (156) hide show
  1. outmem-0.1.0/.github/workflows/publish.yml +69 -0
  2. outmem-0.1.0/.gitignore +67 -0
  3. outmem-0.1.0/LICENSE +21 -0
  4. outmem-0.1.0/PKG-INFO +458 -0
  5. outmem-0.1.0/README.md +402 -0
  6. outmem-0.1.0/config.yaml +30 -0
  7. outmem-0.1.0/docs/cli.md +188 -0
  8. outmem-0.1.0/docs/configuration.md +153 -0
  9. outmem-0.1.0/docs/development.md +72 -0
  10. outmem-0.1.0/docs/features.md +197 -0
  11. outmem-0.1.0/docs/growing-the-wiki.md +129 -0
  12. outmem-0.1.0/docs/python-api.md +360 -0
  13. outmem-0.1.0/evals/README.md +202 -0
  14. outmem-0.1.0/evals/__init__.py +34 -0
  15. outmem-0.1.0/evals/cases/__init__.py +31 -0
  16. outmem-0.1.0/evals/cases/approval.py +87 -0
  17. outmem-0.1.0/evals/cases/contradictions.py +39 -0
  18. outmem-0.1.0/evals/cases/convergence.py +34 -0
  19. outmem-0.1.0/evals/cases/duplicates.py +36 -0
  20. outmem-0.1.0/evals/cases/expansion.py +26 -0
  21. outmem-0.1.0/evals/cases/ingestion.py +30 -0
  22. outmem-0.1.0/evals/cases/multi_author.py +44 -0
  23. outmem-0.1.0/evals/cases/writeback.py +29 -0
  24. outmem-0.1.0/evals/config.py +83 -0
  25. outmem-0.1.0/evals/fixtures/wikis/approval-fallback/config.yaml +31 -0
  26. outmem-0.1.0/evals/fixtures/wikis/approval-fallback/wiki/pricing-formula.md +9 -0
  27. outmem-0.1.0/evals/fixtures/wikis/approval-fallback/wiki/q1-product-roadmap.md +14 -0
  28. outmem-0.1.0/evals/fixtures/wikis/approval-fallback/wiki/sources/.sources.db +0 -0
  29. outmem-0.1.0/evals/fixtures/wikis/approval-fallback/wiki/sources/q1-product-roadmap.md +16 -0
  30. outmem-0.1.0/evals/fixtures/wikis/duplicate-trap/config.yaml +31 -0
  31. outmem-0.1.0/evals/fixtures/wikis/duplicate-trap/wiki/cat-drug-dosages.md +19 -0
  32. outmem-0.1.0/evals/fixtures/wikis/duplicate-trap/wiki/sources/.sources.db +0 -0
  33. outmem-0.1.0/evals/fixtures/wikis/duplicate-trap/wiki/sources/veterinary/drugs.md +11 -0
  34. outmem-0.1.0/evals/fixtures/wikis/multi-author-divergence/CONTRIBUTORS.md +5 -0
  35. outmem-0.1.0/evals/fixtures/wikis/multi-author-divergence/SEED.md +8 -0
  36. outmem-0.1.0/evals/fixtures/wikis/multi-author-divergence/config.yaml +31 -0
  37. outmem-0.1.0/evals/fixtures/wikis/multi-author-divergence/log/2026-05-04.md +5 -0
  38. outmem-0.1.0/evals/fixtures/wikis/multi-author-divergence/wiki/pricing-formula.md +13 -0
  39. outmem-0.1.0/evals/fixtures/wikis/no-match-query/config.yaml +31 -0
  40. outmem-0.1.0/evals/fixtures/wikis/no-match-query/wiki/onboarding.md +10 -0
  41. outmem-0.1.0/evals/fixtures/wikis/no-match-query/wiki/team-handbook.md +11 -0
  42. outmem-0.1.0/evals/fixtures/wikis/pricing-cost-plus/CONTRIBUTORS.md +4 -0
  43. outmem-0.1.0/evals/fixtures/wikis/pricing-cost-plus/config.yaml +31 -0
  44. outmem-0.1.0/evals/fixtures/wikis/pricing-cost-plus/raw/pricing-deck-2026-Q1.md +13 -0
  45. outmem-0.1.0/evals/fixtures/wikis/pricing-cost-plus/wiki/acme-msa.md +14 -0
  46. outmem-0.1.0/evals/fixtures/wikis/pricing-cost-plus/wiki/pricing-formula.md +18 -0
  47. outmem-0.1.0/evals/fixtures/wikis/raw-contradicts-wiki/config.yaml +31 -0
  48. outmem-0.1.0/evals/fixtures/wikis/raw-contradicts-wiki/raw/sales-policy-2025.md +11 -0
  49. outmem-0.1.0/evals/fixtures/wikis/raw-contradicts-wiki/wiki/discounts.md +17 -0
  50. outmem-0.1.0/evals/fixtures/wikis/stale-wikilink/config.yaml +31 -0
  51. outmem-0.1.0/evals/fixtures/wikis/stale-wikilink/wiki/acme-msa.md +11 -0
  52. outmem-0.1.0/evals/fixtures/wikis/stale-wikilink/wiki/pricing-formula.md +12 -0
  53. outmem-0.1.0/evals/fixtures/wikis/temporal-evolution/SEED.md +11 -0
  54. outmem-0.1.0/evals/fixtures/wikis/temporal-evolution/config.yaml +31 -0
  55. outmem-0.1.0/evals/fixtures/wikis/temporal-evolution/log/2025-06-01.md +3 -0
  56. outmem-0.1.0/evals/fixtures/wikis/temporal-evolution/log/2026-02-10.md +4 -0
  57. outmem-0.1.0/evals/fixtures/wikis/temporal-evolution/wiki/pricing-formula.md +12 -0
  58. outmem-0.1.0/evals/harness.py +715 -0
  59. outmem-0.1.0/evals/judges/__init__.py +10 -0
  60. outmem-0.1.0/evals/judges/llm_judge.py +95 -0
  61. outmem-0.1.0/evals/judges/trace.py +8 -0
  62. outmem-0.1.0/evals/run.py +225 -0
  63. outmem-0.1.0/evals/subagent_e2e.py +266 -0
  64. outmem-0.1.0/examples/starter-wiki/CONTRIBUTORS.md +8 -0
  65. outmem-0.1.0/examples/starter-wiki/README.md +72 -0
  66. outmem-0.1.0/examples/starter-wiki/config.yaml +43 -0
  67. outmem-0.1.0/examples/starter-wiki/log/2026-05-01.md +9 -0
  68. outmem-0.1.0/examples/starter-wiki/raw/acme-msa-text.md +30 -0
  69. outmem-0.1.0/examples/starter-wiki/raw/pricing-deck-2026-Q1.md +43 -0
  70. outmem-0.1.0/examples/starter-wiki/wiki/acme-msa.md +22 -0
  71. outmem-0.1.0/examples/starter-wiki/wiki/pricing-formula.md +27 -0
  72. outmem-0.1.0/pyproject.toml +108 -0
  73. outmem-0.1.0/specs/concept.md +63 -0
  74. outmem-0.1.0/specs/planning_prompt.md +306 -0
  75. outmem-0.1.0/specs/spec.md +351 -0
  76. outmem-0.1.0/src/outmem/__init__.py +51 -0
  77. outmem-0.1.0/src/outmem/_logfire.py +54 -0
  78. outmem-0.1.0/src/outmem/_sqlite.py +23 -0
  79. outmem-0.1.0/src/outmem/_store/__init__.py +0 -0
  80. outmem-0.1.0/src/outmem/_store/import_vault.py +308 -0
  81. outmem-0.1.0/src/outmem/_store/semantic.py +275 -0
  82. outmem-0.1.0/src/outmem/_store/sources.py +105 -0
  83. outmem-0.1.0/src/outmem/_time.py +45 -0
  84. outmem-0.1.0/src/outmem/adapters/__init__.py +10 -0
  85. outmem-0.1.0/src/outmem/adapters/pydantic_ai.py +803 -0
  86. outmem-0.1.0/src/outmem/agent/__init__.py +45 -0
  87. outmem-0.1.0/src/outmem/agent/approval.py +271 -0
  88. outmem-0.1.0/src/outmem/agent/prompts/system.j2 +183 -0
  89. outmem-0.1.0/src/outmem/agent/runtime.py +269 -0
  90. outmem-0.1.0/src/outmem/agent/service.py +342 -0
  91. outmem-0.1.0/src/outmem/backlinks.py +176 -0
  92. outmem-0.1.0/src/outmem/cli/__init__.py +12 -0
  93. outmem-0.1.0/src/outmem/cli/__main__.py +1046 -0
  94. outmem-0.1.0/src/outmem/config.py +506 -0
  95. outmem-0.1.0/src/outmem/dashboard/__init__.py +24 -0
  96. outmem-0.1.0/src/outmem/dashboard/app.py +33 -0
  97. outmem-0.1.0/src/outmem/dashboard/router.py +167 -0
  98. outmem-0.1.0/src/outmem/dashboard/service.py +60 -0
  99. outmem-0.1.0/src/outmem/dashboard/templates/base.html.j2 +94 -0
  100. outmem-0.1.0/src/outmem/dashboard/templates/wiki_history.html.j2 +19 -0
  101. outmem-0.1.0/src/outmem/dashboard/templates/wiki_index.html.j2 +16 -0
  102. outmem-0.1.0/src/outmem/dashboard/templates/wiki_page.html.j2 +38 -0
  103. outmem-0.1.0/src/outmem/exceptions.py +81 -0
  104. outmem-0.1.0/src/outmem/frontmatter.py +230 -0
  105. outmem-0.1.0/src/outmem/git_ops.py +494 -0
  106. outmem-0.1.0/src/outmem/history.py +69 -0
  107. outmem-0.1.0/src/outmem/identity.py +109 -0
  108. outmem-0.1.0/src/outmem/index.py +97 -0
  109. outmem-0.1.0/src/outmem/lint.py +399 -0
  110. outmem-0.1.0/src/outmem/observability.py +53 -0
  111. outmem-0.1.0/src/outmem/search.py +223 -0
  112. outmem-0.1.0/src/outmem/semantic/__init__.py +46 -0
  113. outmem-0.1.0/src/outmem/semantic/chunker.py +154 -0
  114. outmem-0.1.0/src/outmem/semantic/embeddings.py +97 -0
  115. outmem-0.1.0/src/outmem/semantic/store.py +452 -0
  116. outmem-0.1.0/src/outmem/semantic/testing.py +139 -0
  117. outmem-0.1.0/src/outmem/skills/notes/evolution/SKILL.md +96 -0
  118. outmem-0.1.0/src/outmem/skills/notes/evolution/references/git-log.md +50 -0
  119. outmem-0.1.0/src/outmem/skills/notes/search/SKILL.md +106 -0
  120. outmem-0.1.0/src/outmem/skills/notes/search/references/patterns.md +50 -0
  121. outmem-0.1.0/src/outmem/skills/notes/write/SKILL.md +156 -0
  122. outmem-0.1.0/src/outmem/skills/notes/write/references/commit-grammar.md +43 -0
  123. outmem-0.1.0/src/outmem/skills/notes/write/references/frontmatter.md +65 -0
  124. outmem-0.1.0/src/outmem/skills/notes/write/references/rebase-loop.md +50 -0
  125. outmem-0.1.0/src/outmem/skills.py +40 -0
  126. outmem-0.1.0/src/outmem/slug.py +73 -0
  127. outmem-0.1.0/src/outmem/sources.py +392 -0
  128. outmem-0.1.0/src/outmem/state.py +173 -0
  129. outmem-0.1.0/src/outmem/store.py +922 -0
  130. outmem-0.1.0/tests/__init__.py +0 -0
  131. outmem-0.1.0/tests/conftest.py +192 -0
  132. outmem-0.1.0/tests/test_adapters_pydantic_ai.py +358 -0
  133. outmem-0.1.0/tests/test_agent.py +720 -0
  134. outmem-0.1.0/tests/test_approval.py +474 -0
  135. outmem-0.1.0/tests/test_backlinks.py +139 -0
  136. outmem-0.1.0/tests/test_cli.py +222 -0
  137. outmem-0.1.0/tests/test_config.py +299 -0
  138. outmem-0.1.0/tests/test_dashboard.py +224 -0
  139. outmem-0.1.0/tests/test_evals_fixtures.py +230 -0
  140. outmem-0.1.0/tests/test_frontmatter.py +142 -0
  141. outmem-0.1.0/tests/test_git_lock.py +171 -0
  142. outmem-0.1.0/tests/test_git_ops.py +273 -0
  143. outmem-0.1.0/tests/test_history.py +54 -0
  144. outmem-0.1.0/tests/test_identity.py +113 -0
  145. outmem-0.1.0/tests/test_import_vault.py +224 -0
  146. outmem-0.1.0/tests/test_index.py +249 -0
  147. outmem-0.1.0/tests/test_ingest.py +361 -0
  148. outmem-0.1.0/tests/test_lint.py +219 -0
  149. outmem-0.1.0/tests/test_logfire_setup.py +132 -0
  150. outmem-0.1.0/tests/test_search.py +108 -0
  151. outmem-0.1.0/tests/test_semantic.py +659 -0
  152. outmem-0.1.0/tests/test_skills_bundle.py +150 -0
  153. outmem-0.1.0/tests/test_slug.py +57 -0
  154. outmem-0.1.0/tests/test_sources.py +337 -0
  155. outmem-0.1.0/tests/test_state.py +105 -0
  156. outmem-0.1.0/tests/test_store.py +711 -0
@@ -0,0 +1,69 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ name: Build sdist and wheel
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install ripgrep
23
+ run: sudo apt-get update && sudo apt-get install -y ripgrep
24
+
25
+ - name: Verify release tag matches package version
26
+ run: |
27
+ tag="${GITHUB_REF_NAME#v}"
28
+ pkg=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
29
+ if [ "$tag" != "$pkg" ]; then
30
+ echo "Release tag v$tag does not match pyproject.toml version $pkg" >&2
31
+ exit 1
32
+ fi
33
+
34
+ - name: Install build
35
+ run: python -m pip install --upgrade build
36
+
37
+ - name: Install package with dev extras (for tests)
38
+ run: python -m pip install -e ".[dev,agent,dashboard,semantic]"
39
+
40
+ - name: Run tests
41
+ run: python -m pytest -q
42
+
43
+ - name: Build distributions
44
+ run: python -m build
45
+
46
+ - name: Upload distributions
47
+ uses: actions/upload-artifact@v4
48
+ with:
49
+ name: dist
50
+ path: dist/
51
+
52
+ publish:
53
+ name: Publish to PyPI
54
+ needs: build
55
+ runs-on: ubuntu-latest
56
+ environment:
57
+ name: pypi
58
+ url: https://pypi.org/p/outmem
59
+ permissions:
60
+ id-token: write
61
+ steps:
62
+ - name: Download distributions
63
+ uses: actions/download-artifact@v4
64
+ with:
65
+ name: dist
66
+ path: dist/
67
+
68
+ - name: Publish to PyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,67 @@
1
+ # Virtualenvs
2
+ .venv/
3
+ venv/
4
+ ENV/
5
+
6
+ # Python build artefacts
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Python bytecode + extensions
13
+ __pycache__/
14
+ *.py[cod]
15
+ *$py.class
16
+ *.so
17
+
18
+ # Tooling caches — each tool drops its own self-ignore inside the cache
19
+ # dir, but only AFTER first run. Listing here is defensive.
20
+ .mypy_cache/
21
+ .pytest_cache/
22
+ .ruff_cache/
23
+ .pyright/
24
+ .pytype/
25
+ .coverage
26
+ .coverage.*
27
+ htmlcov/
28
+ .tox/
29
+ .hypothesis/
30
+
31
+ # Notebooks
32
+ .ipynb_checkpoints/
33
+
34
+ # Lockfiles — outmem is a library; consumers pick their own resolver.
35
+ uv.lock
36
+ poetry.lock
37
+ Pipfile.lock
38
+
39
+ # Pyenv pin — let contributors use any 3.12+ patch.
40
+ .python-version
41
+
42
+ # Secrets — never commit.
43
+ .env
44
+ .env.*
45
+ .secrets.*
46
+
47
+ # Outmem wiki state — only created if a contributor points
48
+ # OUTMEM_PATH at this repo during dev.
49
+ .outmem/
50
+ .vectors.db
51
+
52
+ # IDE / editor
53
+ .idea/
54
+ .vscode/
55
+ .cursor/
56
+ .zed/
57
+ *.swp
58
+ *.swo
59
+ *~
60
+
61
+ # Scratch
62
+ tmp/
63
+
64
+ # OS junk
65
+ .DS_Store
66
+ Thumbs.db
67
+ Desktop.ini
outmem-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 phiweger
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.
outmem-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,458 @@
1
+ Metadata-Version: 2.4
2
+ Name: outmem
3
+ Version: 0.1.0
4
+ Summary: Agentic RAG memory: a git-versioned wiki an agent maintains for itself and a team.
5
+ Project-URL: Repository, https://github.com/phiweger/outmem
6
+ Author: phiweger
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: agent,git,memory,rag,wiki
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: python-dotenv>=1.0
19
+ Requires-Dist: pyyaml>=6.0
20
+ Requires-Dist: skillfull>=0.2.0
21
+ Provides-Extra: agent
22
+ Requires-Dist: jinja2>=3.1; extra == 'agent'
23
+ Requires-Dist: pydantic-ai>=0.1.0; extra == 'agent'
24
+ Provides-Extra: all
25
+ Requires-Dist: fastapi>=0.115; extra == 'all'
26
+ Requires-Dist: jinja2>=3.1; extra == 'all'
27
+ Requires-Dist: logfire>=3.0; extra == 'all'
28
+ Requires-Dist: markdown-it-py>=3.0; extra == 'all'
29
+ Requires-Dist: pydantic-ai>=0.1.0; extra == 'all'
30
+ Requires-Dist: sqlite-vec>=0.1.7; extra == 'all'
31
+ Requires-Dist: uvicorn[standard]>=0.30; extra == 'all'
32
+ Provides-Extra: dashboard
33
+ Requires-Dist: fastapi>=0.115; extra == 'dashboard'
34
+ Requires-Dist: jinja2>=3.1; extra == 'dashboard'
35
+ Requires-Dist: markdown-it-py>=3.0; extra == 'dashboard'
36
+ Requires-Dist: uvicorn[standard]>=0.30; extra == 'dashboard'
37
+ Provides-Extra: dev
38
+ Requires-Dist: mypy>=1.11; extra == 'dev'
39
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
40
+ Requires-Dist: pytest>=8.3; extra == 'dev'
41
+ Requires-Dist: ruff>=0.6; extra == 'dev'
42
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
43
+ Provides-Extra: evals
44
+ Requires-Dist: jinja2>=3.1; extra == 'evals'
45
+ Requires-Dist: pydantic-ai>=0.1.0; extra == 'evals'
46
+ Requires-Dist: pytest>=8.3; extra == 'evals'
47
+ Requires-Dist: sqlite-vec>=0.1.7; extra == 'evals'
48
+ Provides-Extra: logfire
49
+ Requires-Dist: logfire>=3.0; extra == 'logfire'
50
+ Provides-Extra: pydantic-ai
51
+ Requires-Dist: pydantic-ai>=0.1.0; extra == 'pydantic-ai'
52
+ Provides-Extra: semantic
53
+ Requires-Dist: pydantic-ai>=0.1.0; extra == 'semantic'
54
+ Requires-Dist: sqlite-vec>=0.1.7; extra == 'semantic'
55
+ Description-Content-Type: text/markdown
56
+
57
+ # outmem
58
+
59
+ **Agentic RAG memory over a git-versioned wiki.**
60
+
61
+ A standalone Python library + CLI for maintaining a directory of
62
+ plain-markdown notes that an LLM agent compiles, retrieves from, and
63
+ writes back to. Retrieval is shell-tool based (`ripgrep`, `git log`,
64
+ `cat`) — no vector index by default — and every agent turn is required
65
+ to produce a git commit so identical future queries don't re-pay
66
+ retrieval cost.
67
+
68
+ This implements the pattern in [`specs/concept.md`](specs/concept.md):
69
+ the LLM owns the wiki, the human curates sources and asks questions,
70
+ the wiki is a compounding artifact that gets richer with every source
71
+ ingested.
72
+
73
+ ---
74
+
75
+ ## Main idea
76
+
77
+ outmem is a deliberate inversion of conventional RAG. Instead of
78
+ pre-indexing raw sources into a vector store and reaching into them
79
+ on every query, the agent **compiles** raw material into a wiki of
80
+ small markdown pages, **retrieves** over the compiled material with
81
+ shell tools (`ripgrep`, `git log`, `cat`), and is **required to
82
+ commit** at the end of every turn — so identical future queries
83
+ don't re-pay retrieval cost. The wiki compounds; the vector index
84
+ that would otherwise grow stale doesn't exist.
85
+
86
+ Three design choices anchor the rest of the system:
87
+
88
+ 1. **Compaction first.** The cheapest retrieval is reading a compiled
89
+ wiki page; raw sources are the fall-through, not the default. This
90
+ directly attacks the *relevance trap* — the gap between embedding
91
+ similarity and actual usefulness ([Raudaschl, "The Relevance
92
+ Trap"][rt]; [Fleck, "Divergence Engines"][de]).
93
+ 2. **Git as the substrate.** Every write produces a commit; `git log`
94
+ is both the audit trail and the agent's steering signal (recent
95
+ human commits become phase-1 planning context), and `git blame`
96
+ tracks line-level authorship. The same move Claude Code makes for
97
+ code search — agentic shell tools, no index — works here for
98
+ prose ([Nicolai, "Claude Code Doesn't Index Your
99
+ Codebase"][cc]; [SmartScope, "Settling the RAG Debate"][rd]).
100
+ 3. **Mandatory writeback.** Every agent turn ends with at least one
101
+ commit (`compact:` / `extend:` / `log:`), so the system records
102
+ not just what was retrieved but what it was retrieved *for*. The
103
+ pattern is closest to Karpathy's "LLM Wiki" sketch
104
+ ([gist][kw]). Adoption is measured via the **TARS** product
105
+ metric — Target / Adopted / Retained / Satisfied — rather than
106
+ recall@k or nDCG ([Raudaschl, "TARS"][tars]).
107
+
108
+ A single divergence primitive ships in v0.1 — `topic_evolution`, a
109
+ chronological `git log -p` over a topic — for the class of question
110
+ convergent retrieval can't answer ("how has our thinking on X
111
+ changed?"). The four other divergence primitives sketched in
112
+ `concept.md` (contradiction surfacer, negative-space query,
113
+ associative drift, cross-domain bridges) are queued for when the
114
+ first one has earned its place.
115
+
116
+ The full conceptual rationale and v0.1 implementation spec live in
117
+ [`specs/concept.md`](specs/concept.md) and
118
+ [`specs/spec.md`](specs/spec.md).
119
+
120
+ [rt]: https://breakingproduct.substack.com/p/the-relevance-trap
121
+ [tars]: https://uxdesign.cc/tars-a-product-metric-game-changer-c523f260306a
122
+ [de]: https://medium.com/@j0lian/divergence-engines-escaping-the-relevance-trap-1bbdbee55ea6
123
+ [cc]: https://vadim.blog/claude-code-no-indexing
124
+ [rd]: https://smartscope.blog/en/ai-development/practices/rag-debate-agentic-search-code-exploration/
125
+ [kw]: https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f
126
+
127
+ ---
128
+
129
+ ## Install
130
+
131
+ ```bash
132
+ pip install outmem # core: WikiStore + CLI
133
+ pip install outmem[all] # everything: agent runtime, semantic, dashboard, logfire
134
+ ```
135
+
136
+ Or pick a subset:
137
+
138
+ ```bash
139
+ pip install outmem[agent] # + standalone PydanticAI agent runtime
140
+ pip install outmem[semantic] # + sqlite-vec index for find_similar
141
+ pip install outmem[dashboard] # + read-only FastAPI dashboard
142
+ pip install outmem[logfire] # + Pydantic Logfire instrumentation
143
+ ```
144
+
145
+ System: Python 3.12+, `git`, and `ripgrep` (`rg`) on PATH. `outmem init`
146
+ checks for these and refuses to proceed if either is missing.
147
+
148
+ ---
149
+
150
+ ## The 60-second mental model
151
+
152
+ outmem maintains four directories under one wiki root:
153
+
154
+ | Directory | Tracked in git | Who writes | What lives there |
155
+ |-------------|----------------|------------|------------------|
156
+ | `raw/` | no | upstream ingestion pipeline | plain-text/markdown source material |
157
+ | `wiki/` | **yes** | agent + humans (via Obsidian) | compiled knowledge, one concept per file, YAML frontmatter + `[[wikilinks]]` |
158
+ | `log/` | **yes** | agent + humans | dated decision / observation trail |
159
+ | `.outmem/` | no (auto-gitignored) | outmem | non-git state (backlinks cache, last-run marker) |
160
+
161
+ Plus two special wiki-root files:
162
+
163
+ - **`wiki/AGENTS.md`** — user-editable conventions doc loaded into the
164
+ agent's system prompt every turn. Your customization layer for
165
+ domain, page structure, source-handling preferences.
166
+ - **`wiki/index.md`** — auto-maintained slug list, regenerated on
167
+ every write.
168
+
169
+ The agent's loop per turn:
170
+
171
+ 1. **Orient** — read recent human commits (steering signal); choose
172
+ *convergence* (look up a fact) or *expansion* (walk history).
173
+ 2. **Retrieve** — cheapest tool first: `rg wiki/`, then `rg raw/`,
174
+ then `git log -p --follow` for the expansion path.
175
+ 3. **Compact** — produce at least one commit before responding
176
+ (`compact:` for new pages, `extend:` for edits, `log:` for
177
+ observations). **Mandatory** — runs that skip it raise `WritebackError`.
178
+
179
+ ---
180
+
181
+ ## Quickstart
182
+
183
+ ```bash
184
+ # Scaffold a fresh wiki.
185
+ outmem init /srv/my-wiki
186
+ export OUTMEM_PATH=/srv/my-wiki
187
+
188
+ # Optional: tell the agent what this wiki is for.
189
+ ${EDITOR:-vi} /srv/my-wiki/wiki/AGENTS.md
190
+
191
+ # Optional: ask the agent something (requires outmem[agent] + an API key).
192
+ export OUTMEM_MODEL="anthropic:claude-sonnet-4-6"
193
+ echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
194
+ outmem ask "what's our pricing policy?"
195
+ ```
196
+
197
+ There's also a pre-populated example wiki at
198
+ [`examples/starter-wiki/`](examples/starter-wiki/) if you want to
199
+ poke at outmem before scaffolding your own.
200
+
201
+ ---
202
+
203
+ ## Common workflows
204
+
205
+ ### Ask the agent
206
+
207
+ ```bash
208
+ outmem ask "what is our pricing formula and where does it come from?"
209
+ ```
210
+
211
+ Searches the wiki first, falls back to `raw/` if needed, and produces
212
+ at least one commit before responding — either extending a wiki page
213
+ (`extend: <slug>`) or logging the observation (`log: <topic>`).
214
+
215
+ ### Ingest a source document
216
+
217
+ ```bash
218
+ outmem ingest /path/to/some-paper.md \
219
+ --into research \
220
+ --prompt "extract methodology and headline results"
221
+ ```
222
+
223
+ Copies the file under `wiki/sources/[<into>/]<sha256[:12]>/`,
224
+ registers it in `wiki/sources/.sources.db`, then runs the agent to
225
+ write/extend pages with `provenance:` pointing at the registered
226
+ source. Parallel `outmem ingest` runs are safe (SQLite serialises
227
+ writers). See [docs/cli.md](docs/cli.md#ingestion-requires-outmemagent)
228
+ for `--register-only`, re-ingest semantics, and file-type rules.
229
+
230
+ ### Import an existing markdown vault (Obsidian, plain notes folder)
231
+
232
+ ```bash
233
+ outmem import /path/to/obsidian-vault
234
+ outmem lint # surface anything not auto-resolved
235
+ ```
236
+
237
+ One-shot bulk import: walks the source for `*.md`, generates
238
+ frontmatter, normalises slugs to outmem's flat namespace, rewrites
239
+ wikilinks, and commits everything as `import: <vault-name>`. Hidden
240
+ dirs (`.obsidian/`, `.git/`, …) are skipped. See
241
+ [docs/cli.md](docs/cli.md#import-existing-markdown-vault) for
242
+ collision handling and `--force` semantics.
243
+
244
+ ### Edit wiki files manually (Obsidian, vim, VS Code)
245
+
246
+ Edit `wiki/*.md` however you like — outmem keeps the agent happy
247
+ as long as you commit through git. Install the pre-commit hook once
248
+ so the auto-maintained `wiki/index.md` and the semantic vector DB
249
+ stay in lockstep with your edits:
250
+
251
+ ```bash
252
+ outmem hook install
253
+ ```
254
+
255
+ Without the hook, the explicit commands are `outmem index rebuild`
256
+ and `outmem reindex`. See [docs/cli.md](docs/cli.md#sync-derived-artefacts-after-manual-edits).
257
+
258
+ ### Sync across machines
259
+
260
+ ```bash
261
+ outmem pull
262
+ outmem push
263
+ ```
264
+
265
+ The agent does pull-rebase-push around every `outmem ask` by default
266
+ (disable with `--no-pull` / `--no-push`). outmem's git operations
267
+ are vanilla; any other tool (Obsidian Git plugin, GitHub Desktop,
268
+ plain `git`) interoperates.
269
+
270
+ ---
271
+
272
+ ## Wiki page format
273
+
274
+ Every `wiki/<slug>.md` opens with YAML frontmatter:
275
+
276
+ ```yaml
277
+ ---
278
+ title: Pricing formula
279
+ slug: pricing-formula
280
+ provenance:
281
+ - path: raw/pricing-deck-2026-Q1.md
282
+ drive_path: /shared/Sales/2026-Q1-pricing-deck.pdf
283
+ sha256: 9e2c1f00aa
284
+ created: 2026-01-15T10:30:00Z
285
+ updated: 2026-05-04T11:32:00Z
286
+ tags: [pricing, contracts, finance]
287
+ ---
288
+
289
+ The 2026 pricing formula is **cost-plus 35%**…
290
+
291
+ See also [[acme-msa]] for the Acme exception.
292
+ ```
293
+
294
+ `provenance:` accepts plain path strings or dicts with richer
295
+ ingestion metadata. There's no `authority` field — anyone (human or
296
+ agent) may edit any page; "who wrote what" is reconstructed from
297
+ `git log` / `git blame`. Wikilinks (`[[slug]]`) resolve to
298
+ `wiki/<slug>.md`.
299
+
300
+ Full schema: [docs/python-api.md](docs/python-api.md#writing--three-paths).
301
+
302
+ ---
303
+
304
+ ## Embed in your own PydanticAI agent
305
+
306
+ If you want outmem as a *component* of a larger agent (virtual
307
+ assistant, RAG pipeline, etc.) rather than as its own runtime,
308
+ attach the tools + the same system prompt `outmem ask` uses to your
309
+ own `pydantic_ai.Agent`:
310
+
311
+ ```python
312
+ from pydantic_ai import Agent
313
+
314
+ from outmem import WikiStore
315
+ from outmem.adapters.pydantic_ai import wiki_tools
316
+ from outmem.agent import render_system_prompt
317
+
318
+ store = WikiStore.open("/path/to/wiki")
319
+
320
+ agent = Agent(
321
+ "anthropic:claude-sonnet-4-6",
322
+ tools=wiki_tools(store), # 9 typed functions
323
+ system_prompt=render_system_prompt(store), # identical to outmem ask's
324
+ )
325
+ ```
326
+
327
+ `render_system_prompt(store)` returns the **exact same prompt string**
328
+ the bundled `outmem ask` runtime sends — the three-phase framing
329
+ (orient / retrieve / compact), recent human commits as steering
330
+ signal, the bundled skill bodies (`search` / `evolution` / `write`),
331
+ and your wiki's `AGENTS.md`. Tools + prompt → your agent has prompt-
332
+ level parity with `outmem ask`. Pass `include_steering=False` if you
333
+ don't want the phase-1 steering signal injected (handy for stateless
334
+ assistant turns).
335
+
336
+ **What you don't get in embed mode** (intentional — these are runtime
337
+ concerns):
338
+
339
+ - Mandatory writeback enforcement
340
+ - Pull-before / push-after / record-run lifecycle
341
+ - HITL approval gate around `write_page` / `extend_page`
342
+ - Default `max_tokens=16384` and Anthropic prompt caching
343
+
344
+ If you want those too, the all-in-one is `outmem.agent.ask_sync(store, query=…)`.
345
+
346
+ **Fine-grained control** — if you want to compose the prompt yourself
347
+ (e.g. prepend your own preamble, swap skill selection, omit AGENTS.md),
348
+ the building blocks are all public:
349
+
350
+ ```python
351
+ from outmem.adapters.pydantic_ai import skill_text, wiki_tools
352
+
353
+ agents_md = store.read_agents_md() or ""
354
+ agent = Agent(
355
+ "anthropic:claude-sonnet-4-6",
356
+ tools=wiki_tools(store),
357
+ system_prompt=(
358
+ "You are a helpful assistant.\n\n"
359
+ + skill_text("search")
360
+ + skill_text("write")
361
+ + (f"\n# Wiki conventions\n\n{agents_md}" if agents_md else "")
362
+ ),
363
+ )
364
+ ```
365
+
366
+ ---
367
+
368
+ ## Read-only consult — wiki as a tool in someone else's agent
369
+
370
+ When you've curated a wiki and want an *external* agent to consult it
371
+ without ever modifying it, there's a one-call factory:
372
+
373
+ ```python
374
+ from pydantic_ai import Agent
375
+ from outmem.adapters.pydantic_ai import build_consult_wiki
376
+
377
+ consult_wiki = build_consult_wiki("/srv/curated-wiki")
378
+
379
+ my_assistant = Agent(
380
+ "anthropic:claude-sonnet-4-6",
381
+ tools=[consult_wiki],
382
+ system_prompt=(
383
+ "You're a helpful assistant. For questions about internal "
384
+ "policies, decisions, or customer history, call `consult_wiki`."
385
+ ),
386
+ )
387
+ result = my_assistant.run_sync("What's our pricing policy?")
388
+ ```
389
+
390
+ `build_consult_wiki` opens the wiki via
391
+ `WikiStore.open(path, read_only=True)` and builds an inner PydanticAI
392
+ agent with the read-only tool palette (search / read / list / backlinks
393
+ / history / evolution / sources) plus a tight "cite by `[[slug]]`,
394
+ explicitly say so if the wiki has nothing on the topic" system prompt
395
+ and the same `max_tokens=16384` + Anthropic prompt-caching settings as
396
+ the full `outmem ask` runtime. The outer agent gets a black-box
397
+ `consult_wiki(question) -> str` tool — outmem-internal vocabulary
398
+ never leaks across the boundary.
399
+
400
+ What `read_only=True` guarantees:
401
+
402
+ - Every commit-producing entry point on `WikiStore` (`write_page`,
403
+ `extend_page`, `append_log`, `add_source`, `record_ingestion`,
404
+ `rebuild_index`, `import_vault`) raises `OutmemError` via a single
405
+ guard in `_commit_paths`. `pull()` is also refused (rebase mutates
406
+ the working tree). `push()` stays unguarded — nothing local to push.
407
+ - The read-tool palette doesn't even expose write tools (defense in
408
+ depth — the model never sees the write API).
409
+ - `WikiStore.open(read_only=True)` skips the directory-creating layout
410
+ step, skips the stale `.git/index.lock` cleanup, and the backlinks
411
+ cache runs memo-only (no writes to `.outmem/`). The wiki's
412
+ filesystem state is left exactly as the caller found it, which makes
413
+ the mode safe to use on a literally read-only mount.
414
+
415
+ For finer-grained control (custom system prompt, your own retry logic):
416
+
417
+ ```python
418
+ from pydantic_ai import Agent
419
+ from outmem import WikiStore
420
+ from outmem.adapters.pydantic_ai import wiki_read_tools
421
+
422
+ store = WikiStore.open("/srv/curated-wiki", read_only=True)
423
+ agent = Agent(
424
+ "anthropic:claude-sonnet-4-6",
425
+ tools=wiki_read_tools(store),
426
+ system_prompt="You answer from the wiki only. Cite [[slugs]].",
427
+ )
428
+ ```
429
+
430
+ ---
431
+
432
+ ## Where to go next
433
+
434
+ - [`docs/cli.md`](docs/cli.md) — every subcommand with examples.
435
+ - [`docs/python-api.md`](docs/python-api.md) — `WikiStore` + the
436
+ PydanticAI adapter + the standalone agent runtime.
437
+ - [`docs/growing-the-wiki.md`](docs/growing-the-wiki.md) — reading
438
+ the log + lint signals to figure out what to ingest next.
439
+ - [`docs/configuration.md`](docs/configuration.md) — `wiki/AGENTS.md`,
440
+ `config.yaml`, `.env`, environment variables, system requirements.
441
+ - [`docs/features.md`](docs/features.md) — semantic index, write
442
+ approval, Logfire, dashboard (all opt-in).
443
+ - [`docs/development.md`](docs/development.md) — dev install,
444
+ repository layout.
445
+ - [`specs/concept.md`](specs/concept.md) — the original pattern this
446
+ implements.
447
+ - [`specs/spec.md`](specs/spec.md) — v0.1 implementation spec.
448
+
449
+ ---
450
+
451
+ ## Status
452
+
453
+ v0.1 + a v0.10-tagged batch of refinements (SQLite source registry,
454
+ parallel-safe ingest, shared `_sqlite` / `_time` / `_logfire` helpers,
455
+ `wiki/AGENTS.md` schema doc, `store.py` split into facets, README
456
+ restructure). Tests + ruff + mypy strict clean.
457
+
458
+ To give feedback, report at <https://github.com/phiweger/outmem/issues>.