A transparent, read-only SQL query layer over Open Knowledge Format (OKF) bundles.
Project description
readb
A transparent, read-only SQL query layer over an Open Knowledge Format (OKF) bundle — a directory of markdown files with YAML frontmatter — so an agent or a human can run real SQL against the wiki with no explicit database-creation step.
readb loads a bundle into an embedded DuckDB engine and lets DuckDB execute the SQL. There is no custom SQL parser or query planner, and the source files are never modified.
Status: MVP implemented. Bundle loading, type inference, the library API, and the CLI all work; the 12 acceptance criteria in
docs/design-brief.mdare covered by tests. Not yet built (left as clean seams): persistent on-disk index, git-aware incremental rebuild, and write-back — all explicit non-goals for this MVP.
Install
uv sync
Usage
Library:
import readb
db = readb.open("./path/to/bundle") # builds an in-memory DuckDB; no files written
rows = db.sql("SELECT * FROM __DOCUMENTS WHERE type = 'Metric'")
CLI:
readb query "SELECT * FROM __DOCUMENTS" --bundle ./path # results as a table
readb query "SELECT * FROM __DOCUMENTS" --bundle ./path --format json # or csv | tsv | raw
readb schema --bundle ./path # detected types, tables, columns
readb show --bundle ./path some-concept # a concept's body, by name
Concepts are addressed wiki-style: a simple file name (some-concept) when it is unique in
the bundle, or the full path (sub/some-concept.md) — always unambiguous — when it is not.
An ambiguous bare name is always a hard error listing the clashing paths — never a silent
first match. Names are filenames: __name is immutable and filename-derived, and a producer
name: frontmatter key is just an ordinary data column — it affects neither __name nor
addressing.
readb init: skip --bundle
--bundle may be omitted once you register your bundles (the git model — a directory is a
bundle because you said so once):
readb init tasks docs/adr # in the project root: writes .readb/config.toml (commit it)
cd tasks && readb query "SELECT count(*) FROM task" # bundle resolved by walking up
Commands without --bundle walk up from the cwd to the nearest .readb/ registry, then pick
the declared bundle containing the cwd (innermost wins), else the sole declared bundle, else
the config's optional default_bundle. Genuine ambiguity — e.g. the root of a repo declaring
several bundles, no default — is a hard error listing the choices, never a guess; a directory
never silently becomes a bundle. Explicit --bundle <dir> always works on any directory,
initialized or not, and never consults the registry. Re-running init merges new dirs and
never removes. Besides the config, .readb/ is reserved for readb's future persistent index —
it is never read as bundle content.
readb only addresses files inside the bundle root: a ../ path or a symlink that resolves
outside the bundle is not supported and is refused with an explicit error (by name and by
path alike). --format raw prints values verbatim, so
readb query "SELECT __raw FROM task WHERE __name = 'x'" --format raw is exactly cat.
Tables and views
| Name | Rows |
|---|---|
__DOCUMENTS |
one per concept (the six reserved OKF fields + the virtual fields below) |
| per-type tables | one per detected type; columns are reserved fields + the union of producer keys across docs of that type |
__INDEXES |
one per reserved index.md file; columns are the union of their frontmatter fields |
__LOG |
one per reserved log.md file (created only if any exist) |
__UNKNOWNTYPE |
one per non-conformant concept (no / non-string / empty-normalized type) |
__TAGS |
normalized (concept_path, tag) view for join-style tag filtering |
Every table also carries four virtual columns: __path (bundle-relative path, with .md —
the guaranteed-unique key), __name (the simple file name, no directories or .md; wiki-style,
assumed unique but not guaranteed), __body (the markdown body, frontmatter stripped), and
__raw (the byte-exact file text as on disk, frontmatter included).
Type inference
Each column's type is inferred once at load time as the narrowest DuckDB type that losslessly
holds every observed value (int+float → DOUBLE; a scalar alongside a list → a LIST;
maps with a consistent key set → a STRUCT). Anything that doesn't reduce to a single engine
type is stored as a JSON column — nothing is dropped, and producer intent is never guessed
(strings are never split or parsed). The reserved tags field is always a LIST. Run
readb schema to see the inferred type of every column.
Development
uv sync # install deps + dev tools
uv run pytest # run tests
uv run ruff check # lint
uv run ruff format # format
License
Apache 2.0 © 2026 Stanislav Lukyanov
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file readb-0.1.0.tar.gz.
File metadata
- Download URL: readb-0.1.0.tar.gz
- Upload date:
- Size: 109.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e47b84fc6ffb6f351863ec95b7e23f41d53a97886dca79073ce50176d11ba03
|
|
| MD5 |
8c318a46b259d398ecbabb7123bf05a0
|
|
| BLAKE2b-256 |
869ba272cb1cf47655e04aeecf697eb9c69cd9c5c24750c72c191afb7a9d1e8b
|
File details
Details for the file readb-0.1.0-py3-none-any.whl.
File metadata
- Download URL: readb-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c888faac67db7369c8c8a59879434ab1e09141640b71f2442fbefcf9939d9d52
|
|
| MD5 |
a528ced97a22dd1db4629df00853815c
|
|
| BLAKE2b-256 |
3b9f2eba3ba118ee6b4dccbd6759e8f8319cb03414b3f41aebdbb7fed9895e7c
|