Operating manual for AI coding agents (and humans) working on the OpenKOS codebase. This is the canon: read it first and keep every change consistent with it. (It is distinct from the AGENTS.md inside a knowledge bundle, which tells an agent how to operate that bundle.)
OpenKOS is an open-source, local-first engine that compiles a person's text into a living, portable knowledge base, built on the Open Knowledge Format (OKF). It is alpha; MVP 1 (The Compiler) and MVP 2 (The Graph and Memory) are complete, and MVP 3 (The Runtime and Interoperability) is next.
docs/vision.md,docs/philosophy.md— what and why.docs/architecture.md— repository + bundle structure, conventions, delivery.docs/knowledge-object-model.md— the data model (the Knowledge Object).docs/roadmap.md— MVP scoping;docs/cli.md— the command surface.docs/tech_stack.md— technology choices and rationale.examples/good-life-demo/— a real workspace; the concrete target output of MVP 1'singest, and the fixture for the conformance tests.
Every change must respect these. A technically good change that violates one is wrong.
- Local-first & private. Runs on the user's machine, offline; local models (Ollama). No mandatory cloud, no accounts, no API keys.
- Adopt OKF, don't invent a format. Output is always a conformant OKF bundle (v0.1 spec). OpenKOS is the engine / reference implementation, never its own "standard." This extends to definitions: where OKF has decided something, do not decide it again. Identity is the Concept ID — the file path minus
.md(§2); there is noidfield. Links are bundle-relative and untyped — the kind of relationship lives in the prose (§5.1, §5.3). Citations use the# Citationsheading (§8). Conformance is exactly the three rules of §9 (parseable frontmatter on every non-reserved.md; non-emptytype; reserved files follow §6/§7) — and the lint is not a conformance checker: it is our opinion about knowledge health, never OKF's verdict about validity, since OKF explicitly tolerates broken links and missing indexes. Anything we add is an extension carried in frontmatter (legal under §4.1) that degrades gracefully. - Immutable sources, living objects.
raw/is read-only; concept documents are rewritten over time; history via git +log.md. raw/sits outside the bundle;bundle/is pure OKF. A workspace israw/(input material, any extension, untouched) +bundle/(the OKF bundle root) + the engine's files. Sources are not concepts, so they do not live in the concept tree — which is what makes §9 conformance hold by construction, even for files a user drops in by hand. Never put non-concept files insidebundle/.- Reconstructible. Every index / embedding / graph rebuilds from the canonical files (markdown + SQLite + git). Derived stores are caches, never the source of truth.
- Provenance & freshness are first-class. Every derived object cites its sources; volatile facts carry an
as ofstamp (timeless / snapshot / pointer). - Sensitivity across boundaries.
public | private | confidential(defaultprivate); confidential never leaves the device; high-water-mark propagation. - Representation, not truth. OpenKOS preserves representations; it does not validate them and is not an epistemic authority.
- Human curates, engine maintains. Consequential changes stay reviewable, not silently automatic.
- Specs are the contract. Behavior is agreed in
openspec/before it is built:openspec/specs/{domain}/spec.mdis the living per-domain contract;openspec/changes/{change-name}/carries a change in flight (proposal, delta specs, design, tasks) until archive merges its deltas into the main spec;openspec/config.yamlholds the per-phase rules. It is tracked and reviewed like any other file. Required for anything touching the knowledge model, the OKF conformance surface, the ingestion pipeline, or public interfaces (CLI, API, MCP); below that bar, seeCONTRIBUTING.md. - Docs describe the shape, not the diff.
docs/changes only when the general shape changes — a layer, a workflow, a principle. Behavioral detail lives inopenspec/specs/, decisions in ADRs, particulars in issues and the CHANGELOG. Do not syncdocs/on every merged change; do not write counts that rot ("26 verbs" —--helpis the authority); do not mark behavior with "since #NNN" — state it timelessly, history carries the when. (Full policy:CONTRIBUTING.md, "Where knowledge about the project lives".) - Python 3.12+,
src/layout, packageopenkos,uvfor envs/deps. pyproject.tomlis the single config source — deps, the console entry point (openkos = "openkos.cli.main:app"), and Ruff / MyPy / Pytest settings.- Ship types: keep
src/openkos/py.typed. - Start lean, grow by MVP. Create a package when its code arrives — do not scaffold empty folders. MVP 1 needs:
model,bundle,state,llm,producers,compiler,retrieval(lexical + context),lint,lifecycle,config,cli. - Extension interfaces are
typing.Protocol(Producer,Consumer,VectorStore,GraphStore,LLMBackend); plugins via entry points. engine.pystays thin (wiring / composition only); behavior lives in subpackages.- The core is synchronous. Async only at the MVP 3 API/MCP edge (which calls the sync engine via a thread pool). Do not make the core async.
- Layering: the canonical layer (
model,bundle,state) never depends on the derived layer (retrieval,graph,memory). - The OKF adapter is one seam. All knowledge of the format's on-disk shape — frontmatter parsing/emission, reserved files, §9 conformance — lives in
model/okf.pyand nowhere else; the rest of the engine handles Knowledge Objects. OKF is a v0.1 draft whose §11 permits breaking major bumps, so this containment is what lets us adopt it safely. Do not spread format knowledge across the codebase. - LLM calls go behind
LLMBackendand talk to Ollama's OpenAI-compatible endpoint; use Pydantic-validated structured output (e.g.instructor) with retry. The compiler is a deterministic pipeline with LLM steps — no agent framework in the core.
- Tests with pytest (unit / integration / e2e). Test the deterministic parts thoroughly; spike-then-test the fuzzy extraction parts.
- Ruff (lint + format) and MyPy (types) must pass. CI runs all three plus a 90% branch-coverage gate (
pytest --cov), the eval harness self-tests (uv run python evals/run_self_tests.py— every--self-testunderevals/, discovered rather than listed, run against a poisonedOLLAMA_HOSTso "model-free" stays a check), and a packaging build (wheel smoke test); nothing merges without green CI + review. Reproduce the lint/format/type gate locally in one drift-free command —uv run pre-commit run --all-files(the hooks are version-pinned touv.lock, so they match CI exactly) — thenuv run pytest --cov. Note thatruff checkalone is not the gate:ruff format --checkis enforced separately, so verifying with the linter but skipping the formatter passes locally and fails CI. - Conventional Commits with project scopes:
okf, model, bundle, config, ingest, extract, graph, retrieval, memory, lint, cli, api, mcp, sdd, docs, ci. The scope is the subsystem or domain touched, not the command — aninit/ingest/querychange is scopedcli. Like packages, the list grows as code lands (line 36); the not-yet-built pipeline scopes are kept as known roadmap. - The PR and issue flow is exactly what
CONTRIBUTING.mdand.github/define — nothing stricter. Branchfeat/…orfix/…offmain; a PR references its issue and anyopenspec/change in prose (Closes #N/Refs #N); issue-first applies only above the small-obvious-fix bar. This repo has nostatus:*ortype:*label gates and no issue-linkage CI check — the only things that block a merge are green CI and review. A general PR workflow that assumes stricter label or issue machinery does not apply here; this repo's convention wins.
When you make a significant, hard-to-reverse decision (a technology, a pattern, an interface, a trade-off), record it as an ADR:
- Copy
docs/adr/template.mdtodocs/adr/NNNN-short-title.mdusing the next number (0001,0002, …). - Fill in context, decision, consequences, and alternatives; set status
Proposed, adddescription, date, and timestamp. - Add a row to the index in
docs/adr/README.md.
Write it while the forces are still fresh — when the change's design settles the decision, or during implementation if it only emerges there — never afterwards. It is accepted when the change merges, and from then on it is append-only: a later ADR supersedes it; the old one is never edited. Only significant decisions get an ADR — not every change. The log starts with the first code-time decision.
Spec = what, ADR = why. They are not duplicates but opposite mechanisms. A spec is a living document — archive merges each change's deltas (ADDED / MODIFIED / REMOVED / RENAMED) into openspec/specs/{domain}/spec.md, so it is rewritten over time and always describes the present. An ADR is immutable once accepted and project-wide, so the log preserves the past. "Ingest MUST copy the source into raw/ preserving the original" is a spec; "we adopt SQLite + FTS5 over a vector store because local-first and reconstructible" is an ADR. A decision belonging to no single change — "the core does not use LangChain" — has no home in openspec/ at all: it is an ADR, or a principle above.
MVP 1 and MVP 2 have shipped, so this is no longer a greenfield build order. The vertical slice MVP 1 was scoped as — openkos init → create the workspace (raw/, bundle/, openkos.yaml, AGENTS.md) → openkos ingest <path> → copy the source into raw/ → compile it with the local model into one or more OKF concept documents (with provenance + freshness) → update index.md and log.md — exists, along with hybrid retrieval, the typed graph, entity resolution and merge, contradiction detection, and the forget/purge lifecycle. examples/good-life-demo/ is still the reference shape for what ingest produces, and the conformance fixture.
New work therefore starts from the shipped surface, not from a scaffold: read docs/cli.md for what each verb already does, docs/roadmap.md for what MVP 3 (api, mcp, memory, full OKF import/export) still owes, and the open issues for what is actually queued. A change that re-describes existing behavior as new is the failure mode to avoid.
- Invent a competing format, or add a cloud / multi-tenant dependency.
- Make the core async, or pull a heavy agent framework (LangChain, ADK) into the core.
- Add server databases (Postgres, Qdrant, Milvus) or abandoned deps; prefer boring, durable, permissively-licensed tools (SQLite, NetworkX).
- Document personal or single-vendor dev tooling in the repo (a specific AI agent, a memory tool, a local skill cache) — keep it tool-agnostic so anyone can build with just Python +
uv+ these docs. The line is not "no tooling", it is personal: an open, interoperable format the project has adopted — OKF for the product, OpenSpec for the process — is tool-agnostic by construction and belongs here; the assistant you happen to drive it with does not. Test a new one by asking whether a contributor on a completely different setup could read and use it. If it only works with yours, it stays out — and stays gitignored.