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.
- {openkos-0.2.0 → openkos-0.2.3}/PKG-INFO +53 -61
- {openkos-0.2.0 → openkos-0.2.3}/README.md +52 -60
- openkos-0.2.3/pyproject.toml +131 -0
- openkos-0.2.0/pyproject.toml → openkos-0.2.3/pyproject.toml.orig +18 -2
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/index.py +78 -0
- openkos-0.2.3/src/openkos/bundle/listing.py +199 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/merge.py +21 -8
- openkos-0.2.3/src/openkos/bundle/provenance.py +662 -0
- openkos-0.2.3/src/openkos/bundle/source_titles.py +361 -0
- openkos-0.2.3/src/openkos/cli/curate.py +1574 -0
- openkos-0.2.3/src/openkos/cli/main.py +11526 -0
- openkos-0.2.3/src/openkos/cli/next_action.py +679 -0
- openkos-0.2.3/src/openkos/cli/observability.py +283 -0
- openkos-0.2.3/src/openkos/config.py +1155 -0
- openkos-0.2.3/src/openkos/extraction/concept.py +1160 -0
- openkos-0.2.3/src/openkos/extraction/judge.py +136 -0
- openkos-0.2.3/src/openkos/fsio.py +214 -0
- openkos-0.2.3/src/openkos/graph/proximity.py +199 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/sqlite_graph.py +253 -25
- openkos-0.2.3/src/openkos/graph/summary.py +85 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/lifecycle.py +1 -1
- openkos-0.2.3/src/openkos/lint.py +1284 -0
- openkos-0.2.3/src/openkos/llm/ollama.py +768 -0
- openkos-0.2.3/src/openkos/llm/parsing.py +134 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/okf.py +468 -29
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/relations.py +43 -0
- openkos-0.2.3/src/openkos/resolution/__init__.py +49 -0
- openkos-0.2.3/src/openkos/resolution/adjudication.py +453 -0
- openkos-0.2.3/src/openkos/resolution/candidates.py +536 -0
- openkos-0.2.3/src/openkos/resolution/contradiction.py +1081 -0
- openkos-0.2.3/src/openkos/resolution/edge_typing.py +697 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/resolution/similarity.py +70 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/resolution/volatility_typing.py +145 -34
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/retrieval/answer.py +248 -152
- openkos-0.2.3/src/openkos/retrieval/fusion.py +82 -0
- openkos-0.2.3/src/openkos/retrieval/pool.py +24 -0
- openkos-0.2.3/src/openkos/sensitivity.py +277 -0
- openkos-0.2.3/src/openkos/source_title.py +351 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/derived.py +69 -4
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/fts.py +1 -1
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/reindex.py +51 -13
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/vectorstore.py +84 -0
- openkos-0.2.3/src/openkos/templates/openkos.yaml.template +33 -0
- openkos-0.2.0/src/openkos/bundle/provenance.py +0 -98
- openkos-0.2.0/src/openkos/cli/main.py +0 -6182
- openkos-0.2.0/src/openkos/cli/observability.py +0 -66
- openkos-0.2.0/src/openkos/config.py +0 -653
- openkos-0.2.0/src/openkos/extraction/concept.py +0 -262
- openkos-0.2.0/src/openkos/fsio.py +0 -97
- openkos-0.2.0/src/openkos/lint.py +0 -524
- openkos-0.2.0/src/openkos/llm/ollama.py +0 -369
- openkos-0.2.0/src/openkos/llm/parsing.py +0 -95
- openkos-0.2.0/src/openkos/resolution/__init__.py +0 -22
- openkos-0.2.0/src/openkos/resolution/adjudication.py +0 -277
- openkos-0.2.0/src/openkos/resolution/candidates.py +0 -195
- openkos-0.2.0/src/openkos/resolution/contradiction.py +0 -468
- openkos-0.2.0/src/openkos/resolution/edge_typing.py +0 -331
- openkos-0.2.0/src/openkos/retrieval/fusion.py +0 -81
- openkos-0.2.0/src/openkos/retrieval/graph_retrieve.py +0 -77
- openkos-0.2.0/src/openkos/retrieval/pool.py +0 -20
- openkos-0.2.0/src/openkos/sensitivity.py +0 -123
- openkos-0.2.0/src/openkos/templates/openkos.yaml.template +0 -15
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/bundle.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/links.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/log.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/references.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/bundle/relations.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/cli/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/extraction/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/analysis.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/graph/base.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/llm/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/llm/base.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/model/types.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/py.typed +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/resolution/normalize.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/retrieval/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/state/__init__.py +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/templates/agents.md.template +0 -0
- {openkos-0.2.0 → openkos-0.2.3}/src/openkos/vcs/__init__.py +0 -0
- {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.
|
|
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
|
+
[](https://pypi.org/project/openkos/)
|
|
37
|
+
[](https://pypi.org/project/openkos/)
|
|
38
|
+
[](https://github.com/jasonssdev/openkos/actions/workflows/ci.yml)
|
|
39
|
+
[](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.**
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
+
**2 · Pull the two default models** (one time, ~6 GB total):
|
|
53
56
|
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
## Getting started
|
|
70
|
+
To track the latest unreleased `main` instead: `uv tool install git+https://github.com/jasonssdev/openkos`.
|
|
85
71
|
|
|
86
|
-
|
|
72
|
+
**4 · Check the setup before anything can fail:**
|
|
87
73
|
|
|
88
|
-
|
|
74
|
+
```bash
|
|
75
|
+
openkos doctor
|
|
76
|
+
```
|
|
89
77
|
|
|
90
|
-
|
|
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
|
-
**
|
|
80
|
+
**5 · Create a knowledge base and run the loop:**
|
|
93
81
|
|
|
94
82
|
```bash
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
+
---
|
|
105
94
|
|
|
106
|
-
|
|
107
|
-
uv tool install git+https://github.com/jasonssdev/openkos
|
|
108
|
-
```
|
|
95
|
+
## The problem
|
|
109
96
|
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
+
## The idea
|
|
118
102
|
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
+
[](https://pypi.org/project/openkos/)
|
|
4
|
+
[](https://pypi.org/project/openkos/)
|
|
5
|
+
[](https://github.com/jasonssdev/openkos/actions/workflows/ci.yml)
|
|
6
|
+
[](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.**
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
**2 · Pull the two default models** (one time, ~6 GB total):
|
|
20
23
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
## Getting started
|
|
37
|
+
To track the latest unreleased `main` instead: `uv tool install git+https://github.com/jasonssdev/openkos`.
|
|
52
38
|
|
|
53
|
-
|
|
39
|
+
**4 · Check the setup before anything can fail:**
|
|
54
40
|
|
|
55
|
-
|
|
41
|
+
```bash
|
|
42
|
+
openkos doctor
|
|
43
|
+
```
|
|
56
44
|
|
|
57
|
-
|
|
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
|
-
**
|
|
47
|
+
**5 · Create a knowledge base and run the loop:**
|
|
60
48
|
|
|
61
49
|
```bash
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
---
|
|
72
61
|
|
|
73
|
-
|
|
74
|
-
uv tool install git+https://github.com/jasonssdev/openkos
|
|
75
|
-
```
|
|
62
|
+
## The problem
|
|
76
63
|
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
## The idea
|
|
85
69
|
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|