Checkpoints publish atomically.
Readers see the complete new checkpoint or the previous one — never a half-written file, even across a crash.
Metadata control plane for agent workspaces
NoKV turns scattered runs, logs, checkpoints, and artifacts into one live,
filesystem-shaped workspace that agents can ls, find, and
grep. Same data, same model: 45% fewer tokens, 39% lower cost
— measured in an open benchmark.
We're looking for design partners.
45% fewer tokens. 39% lower cost.
Same data, same model.
Open benchmark, 100 runs, raw telemetry committed to the repo.
The navigation tax
Every agent run leaves behind configs, metrics, logs, checkpoints. That state scatters across folders, JSON files, object-store keys, and database rows — and your agents pay a navigation tax in tokens every time they go looking.
NoKV gives that state .
Animated demo: a small robot agent works away, emitting scattered run state — a config.yaml, params.json, two object-store keys, two database rows, and an outputs folder — which then converges into one NoKV namespace under /workspace/runs. The namespace then stays live: run 0143 publishes new artifacts as typed events, a snapshot pins a frozen view, and an agent answers find and grep calls with line-numbered evidence.
state scatters across folders, JSON files, object-store keys, and database rows
NoKV gives that state one address
/workspace/runs/0142
├── config.yaml
├── metadata.json
└── artifacts/
├── stdout.txt
├── stderr.txt
└── ckpt/best_model_1.0_1.pt indexed, queryable, watchable, snapshot-able
An open benchmark ran the same ML-research tasks over identical data through two interfaces: raw SQL and a filesystem-shaped namespace. The namespace surface used 45% fewer tokens, cost 39% less, and answered more accurately.
Why nokv's metadata layer is built on a blob-framed Adaptive Radix Tree — and what that buys you on real NVMe.
What NoKV is
To your tools and agents, NoKV looks like a filesystem: paths, folders, files — mountable, listable, readable. Underneath, file bodies live as immutable blocks in your S3-compatible object store, and NoKV's built-in metadata engine keeps the namespace — what exists, where, in which version — transactional, queryable, and snapshot-able. No separate database to run.
The agent interface
Lists the direct children of a path — non-recursive, cursor-paginated.
{"path": "/yanex/runs", "limit": 50}{
"path": "/yanex/runs",
"entry_count": 2,
"entries": [{"path": "/yanex/runs/run-1", "name": "run-1", "kind": "directory", "size_bytes": null, "entry_count": 5}],
"next_cursor": null,
"truncated": false
}kind is one of file | directory | symlink.
Returns one compact card for a single path: schema, body descriptor, catalog, indexed values.
{"path": "/yanex/runs/run-1/metadata.json"}{
"card": {
"path": "...",
"name": "metadata.json",
"kind": "file",
"size_bytes": 13,
"record_count": 1,
"schema": {"record_type": "json_object", "fields": ["status"]},
"sample": "...",
"body": {"producer": "yanex", "size": 13, "content_type": "application/json"},
"catalog": {"filterable": [...], "sortable": [...], "facetable": [...], "facets": []},
"indexed_values": [{"field": "run.status", "value": "completed"}]
}
}body is a descriptor of the stored object — not the file content.
Discovers which fields are filterable, sortable, and facetable under a path.
{"path": "/yanex/runs", "include_facets": true}{
"path": "/yanex/runs",
"catalog_empty": false,
"catalog": {
"filterable": [
{
"operators": ["eq", "ne", "in", "prefix", "suffix", "contains", "gt", "gte", "lt", "lte", "exists", "not_exists"],
"fields": ["run.status", "run.script"]
}
],
"sortable": ["run.duration_seconds"],
"facetable": ["run.status"],
"facets": [
{
"field": "run.status",
"values": [{"value": "completed", "count": 12}],
"distinct_count": 1,
"truncated": false
}
]
},
"child_catalogs": []
}If a catalog is empty, the engine scans the first 20 children and returns up to 5 non-empty child_catalogs as candidate search roots.
Predicate search with projection, sort, and facets — all pushed down into the engine.
guardlimit is hard-capped at 10 per page, and the tool explicitly rejects an include parameter.
{
"path": "/yanex/runs",
"predicates": [{"field": "run.status", "op": "eq", "value": "completed"}],
"sort": [{"field": "run.duration_seconds", "direction": "desc"}],
"fields": ["run.script", "run.duration_seconds"],
"limit": 5
}{
"path": "/yanex/runs",
"match_count": 1,
"matches": [{"path": "/yanex/runs/run-1", "values": {"run.script": "train.py", "run.duration_seconds": 42}}],
"facets": [],
"next_cursor": null,
"truncated": false
}Projectable built-ins: path, name, kind, size_bytes, body.content_type, body.producer — plus any indexed field.
Computes summary rows — count, sum, avg, min, max — with optional grouping.
{
"path": "/yanex/runs",
"predicates": [{"field": "run.script", "op": "eq", "value": "train.py"}],
"group_by": ["run.status"],
"measures": [{"name": "runs", "op": "count"}, {"name": "avg_dur", "op": "avg", "field": "run.duration_seconds"}],
"sort": [{"field": "runs", "direction": "desc"}]
}{
"path": "/yanex/runs",
"input_match_count": 12,
"row_count": 12,
"group_count": 2,
"groups": [{"key": {"run.status": "completed"}, "values": {"runs": 10, "avg_dur": 41.5}}],
"truncated": false
}Every measure except count must name a field.
Reads body content as structured records or raw bytes.
guardA structured read of a file with more than 100 records fails fast and points to stat/find instead — the error message itself is an agent prompt.
{"path": "/yanex/runs/run-1/metadata.json", "format": "structured"}{
"path": "...",
"total_size_bytes": 13,
"format": "structured",
"record_type": "json_object",
"record_count": 1,
"items": [{"index": 0, "value": {"status": "completed"}}],
"bytes": null,
"cursor": null,
"next_cursor": null,
"truncated": false
}record_type is one of directory_entries | json_array | json_object | yaml_mapping | text_lines.
Case-insensitive literal substring search over body text, with line numbers.
{"path": "/yanex/runs/run-1/artifacts", "pattern": "best_model", "recursive": true}{
"tool": "grep",
"path": "...",
"pattern": "best_model",
"recursive": true,
"evidence": "nokv-native:///yanex/runs/run-1/artifacts",
"snapshot_id": 1,
"matches": [
{
"path": ".../artifacts/stdout.txt",
"line_number": 3,
"snippet": "Checkpoint: best_model_1.0_1.pt",
"evidence": "nokv-native:///yanex/runs/run-1/artifacts/stdout.txt@generation:1#L3"
}
],
"files_scanned": 1,
"bytes_read": 31,
"next_cursor": null,
"truncated": false
}The only verb that exposes evidence URIs in its JSON response (nokv-native://…@generation:N#L3) — the other six serializers omit them on purpose.
The agent discovers what exists, learns what's queryable, and pays to read only what it
needs. A "top-5 runs by val_loss" report costs two calls —
predicates, sort, and projection run inside the engine, not in the context window.
grep sweeps the whole tree and returns line-numbered evidence your agent can
cite.
Open benchmark
Same 875-run ML experiment corpus, same model, same questions — two interfaces: raw SQL vs. the NoKV namespace. The namespace answered more accurately on 45% fewer tokens and a 39% smaller bill; on compound exploration tasks the gap widens to 2.4×. Single-shot analytics? SQL won that task, and the report says so.
Explore the benchmark →| Task | The question | NoKV namespace | Raw SQL | Verdict |
|---|---|---|---|---|
| T1 train_top_configs_report | Top 5 training runs by val_loss — learning rate, batch size, stdout size, git state. | 7,914 tok · $0.0058 100% correct | 23,626 tok · $0.0132 50% correct | Win |
| T2 eval_fidelity_leaderboard | Top 5 eval runs by latest fidelity — utility, detection, privacy metrics, stderr size. | 9,314 tok · $0.0066 100% correct | 4,787 tok · $0.0062 100% correct | Loss |
| T3 tabdiff_ddxplus_dcr_checkpoint_provenance | Which checkpoint did each sampler load, and how many parameters? Evidence only in stdout logs. | 35,778 tok · $0.0178 60% correct | 84,607 tok · $0.0322 100% correct | Mixed |
| T4 best_detection_eval_method_audit | Best run by latest detection_roc_auc — method name exactly as its log states it. | 20,213 tok · $0.0081 90% correct | 19,334 tok · $0.0087 90% correct | Tie |
| T5 cancelled_train_interrupt_triage | Triage every non-completed run for a KeyboardInterrupt — with the line number of its last occurrence. | 9,608 tok · $0.0050 100% correct | 19,217 tok · $0.0104 100% correct | Win |
Mean of 10 repeats per arm; tokens are prompt tokens.
T3: NoKV answered correctly in 60% of repeats vs SQL's 100%; counting only correct answers, NoKV still costs less — $0.0297 vs $0.0322.
Write-path primitives
Readers see the complete new checkpoint or the previous one — never a half-written file, even across a crash.
Pin a frozen view of any subtree and keep reading it while jobs write. GC never deletes what a snapshot still needs.
Every create, rename, and publish lands as a typed, replayable event with a cursor.
If your team runs agents over experiment data, eval pipelines, or artifact-heavy research workflows, we'll help you stand NoKV up, instrument your token bill, and shape the roadmap. In return we ask for timely, transparent feedback in a shared Slack channel, engineer-to-engineer — and the truth about what breaks.
Quickstart
# 0. Prereqs: Rust toolchain (1.88+); RustFS (or any S3-compatible store); macFUSE on macOS for mount
# 1. Build
cargo build --release -p nokv --bin nokv
# 2. Local S3 (RustFS) + create the default bucket
mkdir -p /tmp/rustfs-data
RUSTFS_ACCESS_KEY=rustfsadmin RUSTFS_SECRET_KEY=rustfsadmin \
rustfs server --address 127.0.0.1:9000 /tmp/rustfs-data &
AWS_ACCESS_KEY_ID=rustfsadmin AWS_SECRET_ACCESS_KEY=rustfsadmin \
aws --endpoint-url http://127.0.0.1:9000 s3api create-bucket --bucket nokv
# 3. Start the metadata server (every client command talks to 127.0.0.1:7777)
cargo run --release -p nokv --bin nokv -- serve &
# 4. Use it
cargo run --release -p nokv --bin nokv -- init
cargo run --release -p nokv --bin nokv -- mkdir /runs
cargo run --release -p nokv --bin nokv -- put-artifact /runs/1/ckpt.bin ./ckpt.bin
cargo run --release -p nokv --bin nokv -- cat /runs/1/ckpt.bin > restored.bin
cargo run --release -p nokv --bin nokv -- snapshot /runs # time travel starts here Status: single-node today — no HA, POSIX surface incomplete. Distributed metadata is on the roadmap.