openkos 0.2.0__tar.gz → 0.2.3__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 (85) hide show
  1. {openkos-0.2.0 → openkos-0.2.3}/PKG-INFO +53 -61
  2. {openkos-0.2.0 → openkos-0.2.3}/README.md +52 -60
  3. openkos-0.2.3/pyproject.toml +131 -0
  4. openkos-0.2.0/pyproject.toml → openkos-0.2.3/pyproject.toml.orig +18 -2
  5. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/index.py +78 -0
  6. openkos-0.2.3/src/openkos/bundle/listing.py +199 -0
  7. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/merge.py +21 -8
  8. openkos-0.2.3/src/openkos/bundle/provenance.py +662 -0
  9. openkos-0.2.3/src/openkos/bundle/source_titles.py +361 -0
  10. openkos-0.2.3/src/openkos/cli/curate.py +1574 -0
  11. openkos-0.2.3/src/openkos/cli/main.py +11526 -0
  12. openkos-0.2.3/src/openkos/cli/next_action.py +679 -0
  13. openkos-0.2.3/src/openkos/cli/observability.py +283 -0
  14. openkos-0.2.3/src/openkos/config.py +1155 -0
  15. openkos-0.2.3/src/openkos/extraction/concept.py +1160 -0
  16. openkos-0.2.3/src/openkos/extraction/judge.py +136 -0
  17. openkos-0.2.3/src/openkos/fsio.py +214 -0
  18. openkos-0.2.3/src/openkos/graph/proximity.py +199 -0
  19. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/sqlite_graph.py +253 -25
  20. openkos-0.2.3/src/openkos/graph/summary.py +85 -0
  21. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/lifecycle.py +1 -1
  22. openkos-0.2.3/src/openkos/lint.py +1284 -0
  23. openkos-0.2.3/src/openkos/llm/ollama.py +768 -0
  24. openkos-0.2.3/src/openkos/llm/parsing.py +134 -0
  25. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/okf.py +468 -29
  26. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/relations.py +43 -0
  27. openkos-0.2.3/src/openkos/resolution/__init__.py +49 -0
  28. openkos-0.2.3/src/openkos/resolution/adjudication.py +453 -0
  29. openkos-0.2.3/src/openkos/resolution/candidates.py +536 -0
  30. openkos-0.2.3/src/openkos/resolution/contradiction.py +1081 -0
  31. openkos-0.2.3/src/openkos/resolution/edge_typing.py +697 -0
  32. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/resolution/similarity.py +70 -0
  33. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/resolution/volatility_typing.py +145 -34
  34. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/retrieval/answer.py +248 -152
  35. openkos-0.2.3/src/openkos/retrieval/fusion.py +82 -0
  36. openkos-0.2.3/src/openkos/retrieval/pool.py +24 -0
  37. openkos-0.2.3/src/openkos/sensitivity.py +277 -0
  38. openkos-0.2.3/src/openkos/source_title.py +351 -0
  39. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/derived.py +69 -4
  40. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/fts.py +1 -1
  41. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/reindex.py +51 -13
  42. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/vectorstore.py +84 -0
  43. openkos-0.2.3/src/openkos/templates/openkos.yaml.template +33 -0
  44. openkos-0.2.0/src/openkos/bundle/provenance.py +0 -98
  45. openkos-0.2.0/src/openkos/cli/main.py +0 -6182
  46. openkos-0.2.0/src/openkos/cli/observability.py +0 -66
  47. openkos-0.2.0/src/openkos/config.py +0 -653
  48. openkos-0.2.0/src/openkos/extraction/concept.py +0 -262
  49. openkos-0.2.0/src/openkos/fsio.py +0 -97
  50. openkos-0.2.0/src/openkos/lint.py +0 -524
  51. openkos-0.2.0/src/openkos/llm/ollama.py +0 -369
  52. openkos-0.2.0/src/openkos/llm/parsing.py +0 -95
  53. openkos-0.2.0/src/openkos/resolution/__init__.py +0 -22
  54. openkos-0.2.0/src/openkos/resolution/adjudication.py +0 -277
  55. openkos-0.2.0/src/openkos/resolution/candidates.py +0 -195
  56. openkos-0.2.0/src/openkos/resolution/contradiction.py +0 -468
  57. openkos-0.2.0/src/openkos/resolution/edge_typing.py +0 -331
  58. openkos-0.2.0/src/openkos/retrieval/fusion.py +0 -81
  59. openkos-0.2.0/src/openkos/retrieval/graph_retrieve.py +0 -77
  60. openkos-0.2.0/src/openkos/retrieval/pool.py +0 -20
  61. openkos-0.2.0/src/openkos/sensitivity.py +0 -123
  62. openkos-0.2.0/src/openkos/templates/openkos.yaml.template +0 -15
  63. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/__init__.py +0 -0
  64. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/__init__.py +0 -0
  65. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/bundle.py +0 -0
  66. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/links.py +0 -0
  67. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/log.py +0 -0
  68. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/references.py +0 -0
  69. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/relations.py +0 -0
  70. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/cli/__init__.py +0 -0
  71. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/extraction/__init__.py +0 -0
  72. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/__init__.py +0 -0
  73. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/analysis.py +0 -0
  74. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/base.py +0 -0
  75. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/llm/__init__.py +0 -0
  76. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/llm/base.py +0 -0
  77. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/__init__.py +0 -0
  78. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/types.py +0 -0
  79. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/py.typed +0 -0
  80. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/resolution/normalize.py +0 -0
  81. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/retrieval/__init__.py +0 -0
  82. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/__init__.py +0 -0
  83. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/templates/agents.md.template +0 -0
  84. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/vcs/__init__.py +0 -0
  85. {openkos-0.2.0 → openkos-0.2.3}/src/openkos/vcs/git.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openkos
3
- Version: 0.2.0
3
+ Version: 0.2.3
4
4
  Summary: Local-first engine that turns your scattered text into a living, portable knowledge base in the Open Knowledge Format (OKF)
5
5
  Keywords: okf,open-knowledge-format,knowledge-base,knowledge-graph,local-first,rag,llm,second-brain,cli
6
6
  Author: Jason
@@ -33,97 +33,89 @@ Description-Content-Type: text/markdown
33
33
 
34
34
  # OpenKOS
35
35
 
36
+ [![PyPI](https://img.shields.io/pypi/v/openkos)](https://pypi.org/project/openkos/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/openkos)](https://pypi.org/project/openkos/)
38
+ [![CI](https://github.com/jasonssdev/openkos/actions/workflows/ci.yml/badge.svg)](https://github.com/jasonssdev/openkos/actions/workflows/ci.yml)
39
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/jasonssdev/openkos/blob/main/LICENSE)
40
+
36
41
  **Open Knowledge Orchestration System** — a local-first engine for the Open Knowledge Format.
37
42
 
38
43
  OpenKOS turns your scattered text into a living, portable knowledge base your AI agents can actually use — compiled once, kept current, and stored as plain [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) files so it is never locked to any app, model, or vendor.
39
44
 
40
- > **Project status: alpha.** OpenKOS runs: the Compiler and the Graph-and-Memory arcs (MVP 1 and MVP 2) are complete — 19 CLI verbs, hybrid retrieval, and the full forget/purge lifecycle all ship today. The API may still change between releases, but OpenKOS is published and installable now — `pip install openkos`, see [Getting started](#getting-started). Early contributors and feedback are welcome — see [Contributing](#contributing).
45
+ > **Project status: alpha.** The Compiler and the Graph-and-Memory arcs (MVP 1 and MVP 2) are complete and shipped. The API may still change between releases, but OpenKOS is published and installable now. Early contributors and feedback are welcome — see [Contributing](#contributing).
41
46
 
42
47
  ---
43
48
 
44
- ## The problem
45
-
46
- Your AI assistant forgets everything between sessions, so you re-explain the same context every time and the insights you build together disappear into chat history. Meanwhile your notes pile up in folders nobody keeps current. Two powerful things — your knowledge and your models — sit side by side, disconnected.
49
+ ## Quickstart
47
50
 
48
- Retrieval (RAG) doesn't fix this: it re-reads your raw documents on every question and rediscovers everything from scratch. Nothing accumulates. The cross-references are never drawn, the contradictions never reconciled.
51
+ Five steps from a fresh machine to your first cited answer. Everything runs on your computer: no accounts, no API keys, nothing leaves your machine.
49
52
 
50
- ## The idea
53
+ **1 · Install [Ollama](https://ollama.com)** — the local AI runtime OpenKOS talks to. Download it, open it once, and it stays running in the background.
51
54
 
52
- Instead of retrieving from raw sources every time, an LLM can *incrementally build and maintain* a persistent, interlinked knowledge base that sits between you and your sources — Andrej Karpathy's [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern. Knowledge is compiled once and then kept current. It compounds.
55
+ **2 · Pull the two default models** (one time, ~6 GB total):
53
56
 
54
- In June 2026 Google Cloud published a vendor-neutral specification for that pattern: the **Open Knowledge Format (OKF)** — a directory of markdown files with YAML frontmatter, portable across any tool. It is young (v0.1, still a draft) but open, minimal, and gaining adoption. Google's framing was: *"What's missing is a format, not another service,"* and they invited the community to build producers and consumers.
57
+ ```bash
58
+ ollama pull qwen3:8b # chat model — extraction and answers (~5 GB)
59
+ ollama pull bge-m3 # embedding model — semantic search (~1.2 GB)
60
+ ```
55
61
 
56
- **OpenKOS is that producer and consumer, built for individuals and running entirely on your machine.**
62
+ > There is a third, **optional** model for naming relations during curation (`ollama pull gemma2:27b`, **15.6 GB**). Skip it for now — if a command wants it, the error message tells you exactly what to pull.
57
63
 
58
- ## Before / after
59
-
60
- | | Without OpenKOS | With OpenKOS |
61
- | --- | --- | --- |
62
- | Asking your AI about your own notes | It re-reads raw files every time | It answers from a compiled, cited knowledge base |
63
- | New source | Piles up unread | Compiled into a Source plus typed knowledge objects, each linked to its source |
64
- | Provenance | "Where did this come from?" is a guess | Every object links back to its immutable source |
65
- | Facts that change | Old claims quietly rot | Freshness stamps keep the base honest over time |
66
- | Portability | Trapped in one app | Plain OKF files — open in Obsidian, VS Code, GitHub, anything |
67
- | Privacy | Your knowledge leaves your machine | Local-first, offline-capable, local models |
68
-
69
- ## What it feels like
64
+ **3 · Install the engine** (needs Python 3.12+ and [git](https://git-scm.com)):
70
65
 
71
66
  ```bash
72
- # point OpenKOS at a folder of text and compile it into an OKF bundle
73
- openkos ingest ./sources
74
-
75
- # ask a question and get a cited answer from the compiled knowledge
76
- openkos query "what did I conclude about how to live well?"
77
-
78
- # check the base stays honest — stale `as of` stamps and orphan pages
79
- openkos lint
67
+ uv tool install openkos # or: pipx install openkos — or: pip install openkos
80
68
  ```
81
69
 
82
- *(The full command set — 19 verbs spanning ingest, cited query, the typed graph, entity resolution, volatility tiers (`suggest-volatility`/`set-volatility`), and the forget/purge lifecycle — is in [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md).)*
83
-
84
- ## Getting started
70
+ To track the latest unreleased `main` instead: `uv tool install git+https://github.com/jasonssdev/openkos`.
85
71
 
86
- > Alpha: the commands below run today. OpenKOS is on **PyPI** — install the released version below, or install from the GitHub repository to track the latest `main`. The API may still change between releases.
72
+ **4 · Check the setup before anything can fail:**
87
73
 
88
- OpenKOS is a local-first command-line tool. You install the engine once, then create a knowledge bundle per knowledge base — much like installing git once and running `git init` in many repositories.
74
+ ```bash
75
+ openkos doctor
76
+ ```
89
77
 
90
- **Prerequisites:** Python 3.12+; [git](https://git-scm.com); [git-filter-repo](https://github.com/newren/git-filter-repo) (only needed for `purge`); and a local model runtime — [Ollama](https://ollama.com) with the chat model (`ollama pull qwen3:8b`) and the embedding model (`ollama pull bge-m3`) pulled. No accounts, no API keys: nothing leaves your machine.
78
+ Every line should read `[PASS]` except *Workspace initialized* — you haven't created one yet. `doctor` is also the first thing to run whenever something misbehaves later.
91
79
 
92
- **Install the engine** (once):
80
+ **5 · Create a knowledge base and run the loop:**
93
81
 
94
82
  ```bash
95
- uv tool install openkos # or: pipx install openkos — or: pip install openkos
83
+ mkdir ~/knowledge && cd ~/knowledge
84
+ openkos init # picks your models, scaffolds the workspace
85
+ openkos ingest ./meeting-notes.txt # compile a source — a file, a folder, or a glob
86
+ openkos query "what did we decide?" # an answer with citations, from your own knowledge
96
87
  ```
97
88
 
98
- This installs the `openkos` command on your PATH. To try it once without a persistent install, run it ephemerally:
89
+ From here, `openkos next` always tells you the one thing worth doing, and `openkos --help` lists every command, grouped. The full reference is [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md); the end-to-end experience is [`docs/user-journey.md`](https://github.com/jasonssdev/openkos/blob/main/docs/user-journey.md).
99
90
 
100
- ```bash
101
- uvx openkos --help
102
- ```
91
+ **What `init` set up for you.** `raw/` holds your immutable sources; `bundle/` is the pure-OKF knowledge base — plain markdown you can open in Obsidian, VS Code, or GitHub, and take anywhere; `openkos.yaml` is the engine config. The folder is also a git repository **you never have to operate**: every command commits its own changes, and `git log` / `git diff` / `git revert` are always there for inspection and undo.
103
92
 
104
- Prefer the latest unreleased `main`? Install from the repository instead — the same command with a git URL:
93
+ ---
105
94
 
106
- ```bash
107
- uv tool install git+https://github.com/jasonssdev/openkos
108
- ```
95
+ ## The problem
109
96
 
110
- **Create a bundle** (per knowledge base):
97
+ Your AI assistant forgets everything between sessions, so you re-explain the same context every time and the insights you build together disappear into chat history. Meanwhile your notes pile up in folders nobody keeps current. Two powerful things — your knowledge and your models — sit side by side, disconnected.
111
98
 
112
- ```bash
113
- mkdir ~/knowledge && cd ~/knowledge
114
- openkos init
115
- ```
99
+ Retrieval (RAG) doesn't fix this: it re-reads your raw documents on every question and rediscovers everything from scratch. Nothing accumulates. The cross-references are never drawn, the contradictions never reconciled.
116
100
 
117
- `init` scaffolds the workspace — `raw/` for your immutable sources and `bundle/` for the compiled OKF bundle (starting with just `index.md` and `log.md`; concept-type folders are never pre-created, only added as you ingest) — then writes `AGENTS.md` and, last, the `openkos.yaml` marker. It refuses without writing anything if the directory already looks like a workspace. Sources sit beside the bundle rather than inside it, so `bundle/` stays pure OKF: portable, conformant, and shareable on its own.
101
+ ## The idea
118
102
 
119
- **Then the loop:**
103
+ Instead of retrieving from raw sources every time, an LLM can *incrementally build and maintain* a persistent, interlinked knowledge base that sits between you and your sources — Andrej Karpathy's [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern. Knowledge is compiled once and then kept current. It compounds.
120
104
 
121
- ```bash
122
- openkos ingest ./meeting-notes.txt # compile a source into the bundle
123
- openkos query "what did we decide?" # get a cited answer
124
- ```
105
+ In June 2026 Google Cloud published a vendor-neutral specification for that pattern: the **Open Knowledge Format (OKF)** — a directory of markdown files with YAML frontmatter, portable across any tool. It is young (v0.1, still a draft) but open, minimal, and gaining adoption. Google's framing was: *"What's missing is a format, not another service,"* and they invited the community to build producers and consumers.
106
+
107
+ **OpenKOS is that producer and consumer, built for individuals and running entirely on your machine.**
125
108
 
126
- See [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md) for the full command reference and [`docs/user-journey.md`](https://github.com/jasonssdev/openkos/blob/main/docs/user-journey.md) for the end-to-end experience.
109
+ ## Before / after
110
+
111
+ | | Without OpenKOS | With OpenKOS |
112
+ | --- | --- | --- |
113
+ | Asking your AI about your own notes | It re-reads raw files every time | It answers from a compiled, cited knowledge base |
114
+ | New source | Piles up unread | Compiled into a Source plus typed knowledge objects, each linked to its source |
115
+ | Provenance | "Where did this come from?" is a guess | Every object links back to its immutable source |
116
+ | Facts that change | Old claims quietly rot | Freshness stamps keep the base honest over time |
117
+ | Portability | Trapped in one app | Plain OKF files — open in Obsidian, VS Code, GitHub, anything |
118
+ | Privacy | Your knowledge leaves your machine | Local-first, offline-capable, local models |
127
119
 
128
120
  ## Philosophy
129
121
 
@@ -148,7 +140,7 @@ The wedge, in one line: **the local-first, personal producer-consumer-runtime fo
148
140
  OpenKOS ships in three MVP arcs, each usable on its own. Full detail in [`docs/roadmap.md`](https://github.com/jasonssdev/openkos/blob/main/docs/roadmap.md).
149
141
 
150
142
  - **MVP 1 — The Compiler. (Complete.)** The Karpathy loop, locally, over text: ingest → OKF concepts with provenance → cited query → freshness lint. Useful in an afternoon.
151
- - **MVP 2 — The Graph and Memory. (Complete.)** Entity/relationship extraction and reversible merge, a typed knowledge graph (an OpenKOS layer over OKF's untyped links — other tools still read the bundle fine), hybrid retrieval (lexical FTS5 + local vectors + graph, fused via RRF with PageRank), a fail-closed sensitivity filter (confidential concepts never reach the LLM), reference-aware `forget` plus an irreversible `purge` (right-to-be-forgotten), and answers that file back into the base (`query --save`, the two-output rule).
143
+ - **MVP 2 — The Graph and Memory. (Complete.)** Entity/relationship extraction and reversible merge, a typed knowledge graph (an OpenKOS layer over OKF's untyped links — other tools still read the bundle fine), hybrid retrieval (lexical and semantic, rank-fused), a fail-closed sensitivity filter (confidential concepts never leave the machine — held back from any backend that is not verifiably local), a guided curation loop, reference-aware `forget` plus an irreversible `purge` (right-to-be-forgotten), and answers that file back into the base (the two-output rule).
152
144
  - **MVP 3 — The Runtime and Interoperability.** An MCP server and APIs so agents use OpenKOS as durable memory; full OKF import/export with the wider ecosystem.
153
145
 
154
146
  Beyond that: a desktop app, graph visualization, richer memory, and federation — explored only after the MVPs prove out with real users.
@@ -165,10 +157,10 @@ Beyond that: a desktop app, graph visualization, richer memory, and federation
165
157
  - [`docs/glossary.md`](https://github.com/jasonssdev/openkos/blob/main/docs/glossary.md) — definitions of the core vocabulary
166
158
  - [`docs/faq.md`](https://github.com/jasonssdev/openkos/blob/main/docs/faq.md) — frequently asked questions
167
159
  - [`docs/user-journey.md`](https://github.com/jasonssdev/openkos/blob/main/docs/user-journey.md) — the end-to-end user experience
168
- - [`docs/testing.md`](https://github.com/jasonssdev/openkos/blob/main/docs/testing.md) — manual end-to-end testing walkthrough (all 19 commands)
160
+ - [`docs/testing.md`](https://github.com/jasonssdev/openkos/blob/main/docs/testing.md) — manual end-to-end testing walkthrough
169
161
  - [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md) — the command-line reference
170
162
  - [`docs/brand.md`](https://github.com/jasonssdev/openkos/blob/main/docs/brand.md) — visual identity: isotype, wordmark, palette, typography
171
- - [`docs/adr/`](https://github.com/jasonssdev/openkos/blob/main/docs/adr/) — architecture decision records (the log begins with the first code-time decision)
163
+ - [`docs/adr/`](https://github.com/jasonssdev/openkos/blob/main/docs/adr/) — architecture decision records
172
164
 
173
165
  ## Contributing
174
166
 
@@ -1,96 +1,88 @@
1
1
  # OpenKOS
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/openkos)](https://pypi.org/project/openkos/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/openkos)](https://pypi.org/project/openkos/)
5
+ [![CI](https://github.com/jasonssdev/openkos/actions/workflows/ci.yml/badge.svg)](https://github.com/jasonssdev/openkos/actions/workflows/ci.yml)
6
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/jasonssdev/openkos/blob/main/LICENSE)
7
+
3
8
  **Open Knowledge Orchestration System** — a local-first engine for the Open Knowledge Format.
4
9
 
5
10
  OpenKOS turns your scattered text into a living, portable knowledge base your AI agents can actually use — compiled once, kept current, and stored as plain [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) files so it is never locked to any app, model, or vendor.
6
11
 
7
- > **Project status: alpha.** OpenKOS runs: the Compiler and the Graph-and-Memory arcs (MVP 1 and MVP 2) are complete — 19 CLI verbs, hybrid retrieval, and the full forget/purge lifecycle all ship today. The API may still change between releases, but OpenKOS is published and installable now — `pip install openkos`, see [Getting started](#getting-started). Early contributors and feedback are welcome — see [Contributing](#contributing).
12
+ > **Project status: alpha.** The Compiler and the Graph-and-Memory arcs (MVP 1 and MVP 2) are complete and shipped. The API may still change between releases, but OpenKOS is published and installable now. Early contributors and feedback are welcome — see [Contributing](#contributing).
8
13
 
9
14
  ---
10
15
 
11
- ## The problem
12
-
13
- Your AI assistant forgets everything between sessions, so you re-explain the same context every time and the insights you build together disappear into chat history. Meanwhile your notes pile up in folders nobody keeps current. Two powerful things — your knowledge and your models — sit side by side, disconnected.
16
+ ## Quickstart
14
17
 
15
- Retrieval (RAG) doesn't fix this: it re-reads your raw documents on every question and rediscovers everything from scratch. Nothing accumulates. The cross-references are never drawn, the contradictions never reconciled.
18
+ Five steps from a fresh machine to your first cited answer. Everything runs on your computer: no accounts, no API keys, nothing leaves your machine.
16
19
 
17
- ## The idea
20
+ **1 · Install [Ollama](https://ollama.com)** — the local AI runtime OpenKOS talks to. Download it, open it once, and it stays running in the background.
18
21
 
19
- Instead of retrieving from raw sources every time, an LLM can *incrementally build and maintain* a persistent, interlinked knowledge base that sits between you and your sources — Andrej Karpathy's [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern. Knowledge is compiled once and then kept current. It compounds.
22
+ **2 · Pull the two default models** (one time, ~6 GB total):
20
23
 
21
- In June 2026 Google Cloud published a vendor-neutral specification for that pattern: the **Open Knowledge Format (OKF)** — a directory of markdown files with YAML frontmatter, portable across any tool. It is young (v0.1, still a draft) but open, minimal, and gaining adoption. Google's framing was: *"What's missing is a format, not another service,"* and they invited the community to build producers and consumers.
24
+ ```bash
25
+ ollama pull qwen3:8b # chat model — extraction and answers (~5 GB)
26
+ ollama pull bge-m3 # embedding model — semantic search (~1.2 GB)
27
+ ```
22
28
 
23
- **OpenKOS is that producer and consumer, built for individuals and running entirely on your machine.**
29
+ > There is a third, **optional** model for naming relations during curation (`ollama pull gemma2:27b`, **15.6 GB**). Skip it for now — if a command wants it, the error message tells you exactly what to pull.
24
30
 
25
- ## Before / after
26
-
27
- | | Without OpenKOS | With OpenKOS |
28
- | --- | --- | --- |
29
- | Asking your AI about your own notes | It re-reads raw files every time | It answers from a compiled, cited knowledge base |
30
- | New source | Piles up unread | Compiled into a Source plus typed knowledge objects, each linked to its source |
31
- | Provenance | "Where did this come from?" is a guess | Every object links back to its immutable source |
32
- | Facts that change | Old claims quietly rot | Freshness stamps keep the base honest over time |
33
- | Portability | Trapped in one app | Plain OKF files — open in Obsidian, VS Code, GitHub, anything |
34
- | Privacy | Your knowledge leaves your machine | Local-first, offline-capable, local models |
35
-
36
- ## What it feels like
31
+ **3 · Install the engine** (needs Python 3.12+ and [git](https://git-scm.com)):
37
32
 
38
33
  ```bash
39
- # point OpenKOS at a folder of text and compile it into an OKF bundle
40
- openkos ingest ./sources
41
-
42
- # ask a question and get a cited answer from the compiled knowledge
43
- openkos query "what did I conclude about how to live well?"
44
-
45
- # check the base stays honest — stale `as of` stamps and orphan pages
46
- openkos lint
34
+ uv tool install openkos # or: pipx install openkos — or: pip install openkos
47
35
  ```
48
36
 
49
- *(The full command set — 19 verbs spanning ingest, cited query, the typed graph, entity resolution, volatility tiers (`suggest-volatility`/`set-volatility`), and the forget/purge lifecycle — is in [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md).)*
50
-
51
- ## Getting started
37
+ To track the latest unreleased `main` instead: `uv tool install git+https://github.com/jasonssdev/openkos`.
52
38
 
53
- > Alpha: the commands below run today. OpenKOS is on **PyPI** — install the released version below, or install from the GitHub repository to track the latest `main`. The API may still change between releases.
39
+ **4 · Check the setup before anything can fail:**
54
40
 
55
- OpenKOS is a local-first command-line tool. You install the engine once, then create a knowledge bundle per knowledge base — much like installing git once and running `git init` in many repositories.
41
+ ```bash
42
+ openkos doctor
43
+ ```
56
44
 
57
- **Prerequisites:** Python 3.12+; [git](https://git-scm.com); [git-filter-repo](https://github.com/newren/git-filter-repo) (only needed for `purge`); and a local model runtime — [Ollama](https://ollama.com) with the chat model (`ollama pull qwen3:8b`) and the embedding model (`ollama pull bge-m3`) pulled. No accounts, no API keys: nothing leaves your machine.
45
+ Every line should read `[PASS]` except *Workspace initialized* — you haven't created one yet. `doctor` is also the first thing to run whenever something misbehaves later.
58
46
 
59
- **Install the engine** (once):
47
+ **5 · Create a knowledge base and run the loop:**
60
48
 
61
49
  ```bash
62
- uv tool install openkos # or: pipx install openkos — or: pip install openkos
50
+ mkdir ~/knowledge && cd ~/knowledge
51
+ openkos init # picks your models, scaffolds the workspace
52
+ openkos ingest ./meeting-notes.txt # compile a source — a file, a folder, or a glob
53
+ openkos query "what did we decide?" # an answer with citations, from your own knowledge
63
54
  ```
64
55
 
65
- This installs the `openkos` command on your PATH. To try it once without a persistent install, run it ephemerally:
56
+ From here, `openkos next` always tells you the one thing worth doing, and `openkos --help` lists every command, grouped. The full reference is [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md); the end-to-end experience is [`docs/user-journey.md`](https://github.com/jasonssdev/openkos/blob/main/docs/user-journey.md).
66
57
 
67
- ```bash
68
- uvx openkos --help
69
- ```
58
+ **What `init` set up for you.** `raw/` holds your immutable sources; `bundle/` is the pure-OKF knowledge base — plain markdown you can open in Obsidian, VS Code, or GitHub, and take anywhere; `openkos.yaml` is the engine config. The folder is also a git repository **you never have to operate**: every command commits its own changes, and `git log` / `git diff` / `git revert` are always there for inspection and undo.
70
59
 
71
- Prefer the latest unreleased `main`? Install from the repository instead — the same command with a git URL:
60
+ ---
72
61
 
73
- ```bash
74
- uv tool install git+https://github.com/jasonssdev/openkos
75
- ```
62
+ ## The problem
76
63
 
77
- **Create a bundle** (per knowledge base):
64
+ Your AI assistant forgets everything between sessions, so you re-explain the same context every time and the insights you build together disappear into chat history. Meanwhile your notes pile up in folders nobody keeps current. Two powerful things — your knowledge and your models — sit side by side, disconnected.
78
65
 
79
- ```bash
80
- mkdir ~/knowledge && cd ~/knowledge
81
- openkos init
82
- ```
66
+ Retrieval (RAG) doesn't fix this: it re-reads your raw documents on every question and rediscovers everything from scratch. Nothing accumulates. The cross-references are never drawn, the contradictions never reconciled.
83
67
 
84
- `init` scaffolds the workspace — `raw/` for your immutable sources and `bundle/` for the compiled OKF bundle (starting with just `index.md` and `log.md`; concept-type folders are never pre-created, only added as you ingest) — then writes `AGENTS.md` and, last, the `openkos.yaml` marker. It refuses without writing anything if the directory already looks like a workspace. Sources sit beside the bundle rather than inside it, so `bundle/` stays pure OKF: portable, conformant, and shareable on its own.
68
+ ## The idea
85
69
 
86
- **Then the loop:**
70
+ Instead of retrieving from raw sources every time, an LLM can *incrementally build and maintain* a persistent, interlinked knowledge base that sits between you and your sources — Andrej Karpathy's [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern. Knowledge is compiled once and then kept current. It compounds.
87
71
 
88
- ```bash
89
- openkos ingest ./meeting-notes.txt # compile a source into the bundle
90
- openkos query "what did we decide?" # get a cited answer
91
- ```
72
+ In June 2026 Google Cloud published a vendor-neutral specification for that pattern: the **Open Knowledge Format (OKF)** — a directory of markdown files with YAML frontmatter, portable across any tool. It is young (v0.1, still a draft) but open, minimal, and gaining adoption. Google's framing was: *"What's missing is a format, not another service,"* and they invited the community to build producers and consumers.
73
+
74
+ **OpenKOS is that producer and consumer, built for individuals and running entirely on your machine.**
92
75
 
93
- See [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md) for the full command reference and [`docs/user-journey.md`](https://github.com/jasonssdev/openkos/blob/main/docs/user-journey.md) for the end-to-end experience.
76
+ ## Before / after
77
+
78
+ | | Without OpenKOS | With OpenKOS |
79
+ | --- | --- | --- |
80
+ | Asking your AI about your own notes | It re-reads raw files every time | It answers from a compiled, cited knowledge base |
81
+ | New source | Piles up unread | Compiled into a Source plus typed knowledge objects, each linked to its source |
82
+ | Provenance | "Where did this come from?" is a guess | Every object links back to its immutable source |
83
+ | Facts that change | Old claims quietly rot | Freshness stamps keep the base honest over time |
84
+ | Portability | Trapped in one app | Plain OKF files — open in Obsidian, VS Code, GitHub, anything |
85
+ | Privacy | Your knowledge leaves your machine | Local-first, offline-capable, local models |
94
86
 
95
87
  ## Philosophy
96
88
 
@@ -115,7 +107,7 @@ The wedge, in one line: **the local-first, personal producer-consumer-runtime fo
115
107
  OpenKOS ships in three MVP arcs, each usable on its own. Full detail in [`docs/roadmap.md`](https://github.com/jasonssdev/openkos/blob/main/docs/roadmap.md).
116
108
 
117
109
  - **MVP 1 — The Compiler. (Complete.)** The Karpathy loop, locally, over text: ingest → OKF concepts with provenance → cited query → freshness lint. Useful in an afternoon.
118
- - **MVP 2 — The Graph and Memory. (Complete.)** Entity/relationship extraction and reversible merge, a typed knowledge graph (an OpenKOS layer over OKF's untyped links — other tools still read the bundle fine), hybrid retrieval (lexical FTS5 + local vectors + graph, fused via RRF with PageRank), a fail-closed sensitivity filter (confidential concepts never reach the LLM), reference-aware `forget` plus an irreversible `purge` (right-to-be-forgotten), and answers that file back into the base (`query --save`, the two-output rule).
110
+ - **MVP 2 — The Graph and Memory. (Complete.)** Entity/relationship extraction and reversible merge, a typed knowledge graph (an OpenKOS layer over OKF's untyped links — other tools still read the bundle fine), hybrid retrieval (lexical and semantic, rank-fused), a fail-closed sensitivity filter (confidential concepts never leave the machine — held back from any backend that is not verifiably local), a guided curation loop, reference-aware `forget` plus an irreversible `purge` (right-to-be-forgotten), and answers that file back into the base (the two-output rule).
119
111
  - **MVP 3 — The Runtime and Interoperability.** An MCP server and APIs so agents use OpenKOS as durable memory; full OKF import/export with the wider ecosystem.
120
112
 
121
113
  Beyond that: a desktop app, graph visualization, richer memory, and federation — explored only after the MVPs prove out with real users.
@@ -132,10 +124,10 @@ Beyond that: a desktop app, graph visualization, richer memory, and federation
132
124
  - [`docs/glossary.md`](https://github.com/jasonssdev/openkos/blob/main/docs/glossary.md) — definitions of the core vocabulary
133
125
  - [`docs/faq.md`](https://github.com/jasonssdev/openkos/blob/main/docs/faq.md) — frequently asked questions
134
126
  - [`docs/user-journey.md`](https://github.com/jasonssdev/openkos/blob/main/docs/user-journey.md) — the end-to-end user experience
135
- - [`docs/testing.md`](https://github.com/jasonssdev/openkos/blob/main/docs/testing.md) — manual end-to-end testing walkthrough (all 19 commands)
127
+ - [`docs/testing.md`](https://github.com/jasonssdev/openkos/blob/main/docs/testing.md) — manual end-to-end testing walkthrough
136
128
  - [`docs/cli.md`](https://github.com/jasonssdev/openkos/blob/main/docs/cli.md) — the command-line reference
137
129
  - [`docs/brand.md`](https://github.com/jasonssdev/openkos/blob/main/docs/brand.md) — visual identity: isotype, wordmark, palette, typography
138
- - [`docs/adr/`](https://github.com/jasonssdev/openkos/blob/main/docs/adr/) — architecture decision records (the log begins with the first code-time decision)
130
+ - [`docs/adr/`](https://github.com/jasonssdev/openkos/blob/main/docs/adr/) — architecture decision records
139
131
 
140
132
  ## Contributing
141
133
 
@@ -0,0 +1,131 @@
1
+ [project]
2
+ name = "openkos"
3
+ version = "0.2.3"
4
+ description = "Local-first engine that turns your scattered text into a living, portable knowledge base in the Open Knowledge Format (OKF)"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ requires-python = ">=3.12"
8
+ keywords = [
9
+ "okf",
10
+ "open-knowledge-format",
11
+ "knowledge-base",
12
+ "knowledge-graph",
13
+ "local-first",
14
+ "rag",
15
+ "llm",
16
+ "second-brain",
17
+ "cli",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Environment :: Console",
22
+ "Intended Audience :: Developers",
23
+ "Intended Audience :: End Users/Desktop",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
30
+ "Topic :: Text Processing :: Indexing",
31
+ "Typing :: Typed",
32
+ ]
33
+ dependencies = [
34
+ "networkx>=3.4",
35
+ "python-frontmatter>=1.3.0",
36
+ "pyyaml>=6.0.3",
37
+ "scipy>=1.14",
38
+ "sqlite-vec>=0.1.6",
39
+ "typer>=0.27.0",
40
+ ]
41
+
42
+ [[project.authors]]
43
+ name = "Jason"
44
+ email = "jasonssdev@gmail.com"
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/jasonssdev/openkos"
48
+ Repository = "https://github.com/jasonssdev/openkos"
49
+ Issues = "https://github.com/jasonssdev/openkos/issues"
50
+ Changelog = "https://github.com/jasonssdev/openkos/blob/main/CHANGELOG.md"
51
+
52
+ [project.scripts]
53
+ openkos = "openkos.cli.main:app"
54
+
55
+ [build-system]
56
+ requires = ["uv_build>=0.11.29,<0.12.0"]
57
+ build-backend = "uv_build"
58
+
59
+ [dependency-groups]
60
+ dev = [
61
+ "git-filter-repo>=2.47.0",
62
+ "mypy>=2.3.0",
63
+ "pre-commit>=4.6.0",
64
+ "pydantic>=2.13.4",
65
+ "pytest>=9.1.1",
66
+ "pytest-cov>=7.1.0",
67
+ "ruamel-yaml>=0.19.1",
68
+ "ruff>=0.15.21",
69
+ "types-networkx>=3.6.1.20260624",
70
+ "types-pyyaml>=6.0.12.20260518",
71
+ ]
72
+
73
+ [tool.pytest.ini_options]
74
+ testpaths = ["tests"]
75
+ addopts = [
76
+ "--import-mode=importlib",
77
+ "--strict-markers",
78
+ ]
79
+ markers = ["live_backend: test deliberately reaches a live Ollama backend"]
80
+
81
+ [tool.coverage.run]
82
+ source = ["openkos"]
83
+ branch = true
84
+
85
+ [tool.coverage.report]
86
+ fail_under = 90
87
+ show_missing = true
88
+
89
+ [tool.ruff.lint]
90
+ select = [
91
+ "E",
92
+ "F",
93
+ "I",
94
+ "UP",
95
+ "B",
96
+ "SIM",
97
+ "S",
98
+ "DTZ",
99
+ "PT",
100
+ "PTH",
101
+ "RUF",
102
+ ]
103
+ ignore = ["E501"]
104
+
105
+ [tool.ruff.lint.per-file-ignores]
106
+ "tests/**/*.py" = ["S101"]
107
+
108
+ [tool.ruff.lint.flake8-bugbear]
109
+ extend-immutable-calls = [
110
+ "typer.Argument",
111
+ "typer.Option",
112
+ ]
113
+
114
+ [tool.mypy]
115
+ python_version = "3.12"
116
+ files = [
117
+ "src",
118
+ "tests",
119
+ ]
120
+ strict = true
121
+ warn_unreachable = true
122
+ enable_error_code = [
123
+ "ignore-without-code",
124
+ "redundant-expr",
125
+ "truthy-bool",
126
+ "possibly-undefined",
127
+ ]
128
+
129
+ [[tool.mypy.overrides]]
130
+ module = "sqlite_vec.*"
131
+ ignore_missing_imports = true
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openkos"
3
- version = "0.2.0"
3
+ version = "0.2.3"
4
4
  description = "Local-first engine that turns your scattered text into a living, portable knowledge base in the Open Knowledge Format (OKF)"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -74,7 +74,23 @@ dev = [
74
74
 
75
75
  [tool.pytest.ini_options]
76
76
  testpaths = ["tests"]
77
- addopts = ["--import-mode=importlib"]
77
+ # `--strict-markers` turns an unregistered marker into a collection error
78
+ # instead of a `PytestUnknownMarkWarning` nobody reads (#264). The entire
79
+ # opt-out from the unit suite's fail-closed network guard is a marker NAME
80
+ # matched as a string, so a misspelled `live_backend` would otherwise leave
81
+ # BOTH autouse fixtures installed while reading as a deliberate opt-out --
82
+ # and, combined with the absorbability the guard's own docstring concedes,
83
+ # a mis-marked live-backend pin would degrade through a broad handler and
84
+ # read green while never contacting the backend it claims to measure. That
85
+ # is #217's silence, re-entered through the escape hatch.
86
+ addopts = ["--import-mode=importlib", "--strict-markers"]
87
+ markers = [
88
+ # Opts a test out of the unit suite's fail-closed network guard (#217).
89
+ # Reserved for assumption pins that measure the real backend and already
90
+ # gate themselves on a reachability probe -- not an escape hatch for a
91
+ # unit test that is merely inconvenient to stub.
92
+ "live_backend: test deliberately reaches a live Ollama backend",
93
+ ]
78
94
 
79
95
  # Coverage is opt-in: `--cov` is deliberately not in pytest's addopts, so plain
80
96
  # `uv run pytest` stays fast for the local loop. CI passes `--cov`, and because
@@ -227,3 +227,81 @@ def remove_index_entry(index_text: str, concept_id: str) -> tuple[str, int]:
227
227
  if removed == 0:
228
228
  return index_text, 0
229
229
  return frontmatter_block + "".join(kept_lines), removed
230
+
231
+
232
+ _LABELLED_LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)]+)\)")
233
+ _LABEL_UNSAFE_CHARS_RE = re.compile(r"[\[\]()`]")
234
+
235
+
236
+ def _reject_markdown_link_delimiters(field: str, value: str) -> None:
237
+ """Raise `ValueError` if `value` contains `[`, `]`, `(`, `)`, or a
238
+ backtick (Slice 2 review finding, corroborated by execution).
239
+
240
+ The LABEL span written by `relabel_index_entry` is the one place a
241
+ title is interpolated without becoming the link target or description,
242
+ so it is the one place a slipped-through delimiter lets the REST of the
243
+ bullet be rewritten: `[`/`]` forges a second link, `(`/`)` breaks the
244
+ existing target. Either way `_LINK_RE`/`_LABELLED_LINK_RE` then match
245
+ the FORGED link, not the real one -- verified by execution: the bullet
246
+ becomes invisible to both `relabel_index_entry` and `remove_index_entry`.
247
+ """
248
+ if _LABEL_UNSAFE_CHARS_RE.search(value):
249
+ raise ValueError(
250
+ f"index.md: {field!r} must not contain a markdown link delimiter "
251
+ "('[', ']', '(', ')') or a backtick"
252
+ )
253
+
254
+
255
+ def relabel_index_entry(
256
+ index_text: str, concept_id: str, new_title: str
257
+ ) -> tuple[str, int]:
258
+ """Rewrite the LABEL of every bullet whose FIRST markdown link resolves to
259
+ `concept_id`, leaving slug, link target, and description untouched.
260
+
261
+ `remove_index_entry`'s twin: same frontmatter-splitting discipline, same
262
+ line-walk, same "first link only" candidate rule, same zero/duplicate
263
+ count semantics -- but this REWRITES a span instead of dropping a line.
264
+ Identity is `_link_identity(target) == concept_id`, never a label-text
265
+ match, because the whole point of this function is to correct a label
266
+ that has drifted from the document's actual `title` -- matching on the
267
+ (stale) label itself would never find the bullet that needs fixing.
268
+
269
+ Uses a SEPARATE regex, `_LABELLED_LINK_RE`, rather than adding a capture
270
+ group to the module-level `_LINK_RE`: `remove_index_entry` reads
271
+ `_LINK_RE.search(...).group(1)` as the link TARGET and depends on
272
+ `_LINK_RE` having exactly one group, so widening it here would silently
273
+ break that call site.
274
+
275
+ Only the label span between `[` and `]` of the bullet's first link is
276
+ replaced; the link target, the ` - ` separator, the description, the
277
+ bullet marker, leading indentation, and the line ending all round-trip
278
+ verbatim. `_reject_newline` and `_reject_markdown_link_delimiters` both
279
+ guard `new_title` (see the latter's own docstring for why the label
280
+ span is the one place that must be escaped). Count semantics mirror
281
+ `remove_index_entry`: zero matches returns `(index_text, 0)` unchanged --
282
+ not an error, since catalog drift is not a reason to refuse an otherwise
283
+ safe relabel; more than one match (a duplicate catalog entry) relabels
284
+ ALL of them, reporting the total, since leaving one stale label behind
285
+ would be worse than the duplicate itself.
286
+ """
287
+ _reject_newline("new_title", new_title)
288
+ _reject_markdown_link_delimiters("new_title", new_title)
289
+ frontmatter_block, body = _split_frontmatter_verbatim(index_text)
290
+ lines = body.splitlines(keepends=True)
291
+ result_lines: list[str] = []
292
+ relabeled = 0
293
+ for line in lines:
294
+ stripped = line.lstrip()
295
+ if stripped.startswith(_BULLET_MARKERS):
296
+ match = _LABELLED_LINK_RE.search(stripped)
297
+ if match is not None and _link_identity(match.group(2)) == concept_id:
298
+ start, end = match.span(1)
299
+ stripped = stripped[:start] + new_title + stripped[end:]
300
+ indent = line[: len(line) - len(line.lstrip())]
301
+ line = indent + stripped
302
+ relabeled += 1
303
+ result_lines.append(line)
304
+
305
+ if relabeled == 0:
306
+ return index_text, 0
307
+ return frontmatter_block + "".join(result_lines), relabeled