Koobi
File-native model and experiment registry for local workflows.
Koobi stores experiment metadata as plain Markdown cards, builds a disposable DuckDB index, and serves an interactive Marimo UI for browsing, filtering, visualizing, and editing cards. The source of truth is always your files.
Why Koobi
Most experiment trackers assume a hosted service and database-backed runs. Koobi is designed for local-first users who want:
- readable, versionable records in Git
- zero-server operation
- domain-agnostic metrics and parameters
- workflows friendly to both humans and coding agents
Current MVP Capabilities
- Markdown card parsing and serialization with preserved body/key ordering
- additive card updates (only targeted fields are rewritten)
- DuckDB index build with:
cardstable (metadata + body)measurestable (long-format metrics)series_registrytable (series pointers)
- Marimo app with:
- project/status/search filters
- model table view
- card detail view (metrics/params/tags/body)
- edit form (name/status/tags/body) with write-back
- one measure bar chart
- Execution model (Mode B) — optionally let Koobi run an experiment instead of just recording it:
- one function registry with
component/series/measure/itemskinds; functions live in your package and self-register on import (never embedded in cards) - components declared as cards (a model, a framework) backed by registered functions, composed into a typed-port dataflow
koobi run <experiment>resolves the components, runs them in dependency order, computes the declared measures, persists a result series, and stamps provenance — writing the same card a human would have authored by hand- opt-in input-hash caching (off by default, so research runs always re-execute)
- a finance pack providing Sharpe / Calmar / max-drawdown measures
- one function registry with
- CLI commands:
koobi initkoobi newkoobi indexkoobi runkoobi ui
Architecture Overview
cards/*.md --> index.duckdb --> Marimo app
^ |
|---------- edit/write-back -------|
- Cards are authoritative.
- Index is derived and disposable.
- UI reads from index and writes edits back to cards.
Koobi works two ways over one data model: you can record results yourself (Mode A), or declare runnable components and let koobi run produce the card (Mode B). Either way the card is the contract; execution is just an optional producer of it. The engine stays domain-free — it wires named ports and runs functions; finance knowledge (the Sharpe formula, date slicing) lives only in packs and your own project code.
experiment card --> resolve components --> run in dependency order --> measures + series --> write card back --> reindex
(model + framework, by typed ports) (provenance stamped)
Installation
Install into any project or environment directly from Git:
pip install git+https://github.com/blu3c0ral/koobi.git
This installs the koobi command and the importable koobi package.
For local development on Koobi itself, install editable with dev extras:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
Quickstart
Koobi discovers its configuration automatically: every command searches the
current directory and its parents for a koobi.toml. Work from inside your
workspace and no --config flag is needed.
1) Create (or enter) a workspace
koobi init --path my-registry
cd my-registry
Or use the bundled demo workspace:
python examples/local_example_to_cards.py --overwrite
cd examples/local
2) Build the index
koobi index
3) Launch the UI
koobi ui
The UI opens at http://localhost:2718.
Running from outside the workspace
If you prefer not to cd, point Koobi at the config explicitly or via an
environment variable (precedence: --config > KOOBI_CONFIG > current dir):
koobi ui --config examples/local/koobi.toml
# or
export KOOBI_CONFIG=examples/local/koobi.toml
koobi ui
CLI Reference
Initialize a new workspace:
koobi init --path /path/to/workspace
Create a new card (run from inside the workspace):
koobi new demo_project.my_model
Rebuild index:
koobi index
Run an experiment (Mode B):
koobi run my_project.exp_001 # --force to ignore the cache
koobi run my_project.exp_001 --dry-run # validate wiring only — runs nothing, pulls no data
run exits non-zero on a degraded run (a component raised, a port produced no output, or a
declared measure didn't compute), printing the reasons to stderr — so a scheduled run can't
mistake a silent partial failure for success. -v/-vv add per-node progress and (at -vv)
tracebacks, and may go before or after the subcommand.
Run UI:
koobi ui
Running Experiments (Mode B)
An experiment is a composition of components — e.g. a model and an entry/exit
framework. You declare each as a card backed by a registered function, then declare
an experiment card that wires them together; koobi run executes the graph and fills
in the results.
Point config at your function module and any datasets (a name → loader binding — the engine only sees the name; your loader gives it meaning):
# koobi.toml
[koobi]
function_modules = ["my_project.koobi_functions"] # imported to register runners/measures
cache = false # opt-in; default off
[datasets.prices]
loader = "my_project.koobi_functions:load_prices"
params = { universe = "16etf" }
[display]
packs = ["finance"] # Sharpe / Calmar / max-drawdown
Register the functions in your own package (never code in cards):
# my_project/koobi_functions.py
from koobi import registry
@registry.component("my_project.model", produces="predictions")
def run_model(ctx):
return predict(ctx.artifact("model"), ctx.data("prices")) # resources pulled in
@registry.component("my_project.framework", needs=["predictions"], produces="equity_curve")
def run_framework(ctx):
return backtest(ctx.get("predictions"), ctx.data("prices"), **ctx.params)
Declare the cards. needs/produces list ports only (the node-to-node dataflow);
external inputs like prices are pulled via ctx.data, not wired as ports:
# model card — produces a "predictions" port
id: my_project.model
type: model
runner: my_project.model
produces: predictions
# framework card — consumes "predictions", produces "equity_curve"
id: my_project.framework
type: framework
runner: my_project.framework
needs: [predictions]
produces: equity_curve
# experiment card — composes the two by role and pins params
id: my_project.exp_001
type: experiment
components:
model: my_project.model
framework: my_project.framework
params: { entry_threshold: 0.025 }
measures: [sharpe, calmar, max_drawdown]
Then koobi run my_project.exp_001 writes metrics, a result series, and a
provenance block back into the experiment card. See
docs/backend_technical_design.md §16 for the full model.
Use as a Library
Koobi's core is importable, so other projects and coding agents can read, query, and write cards programmatically:
from koobi import load_config, build, connect, query_cards
from koobi import Card, load_card, save_card
# Resolve config by searching upward from a path (file or directory)
cfg = load_config("path/to/workspace")
# Build the disposable DuckDB index from the Markdown cards
build(cfg)
# Query cards joined with wide-format measures
con = connect(cfg)
df = query_cards(con, project="demo_project", status="candidate")
# Read, mutate, and write back a single card (only targeted fields change)
card = load_card("path/to/workspace/cards/demo_project.my_model.md")
card.status = "promoted"
save_card(card, fields=["status"])
The execution engine is importable too:
from koobi import load_config, bootstrap, resolve_card, run_experiment
cfg = load_config("path/to/workspace")
bootstrap(cfg) # load packs + import function_modules
exp = resolve_card(cfg, "my_project.exp_001")
run_experiment(exp, cfg, ran_at="2026-06-26T00:00:00") # writes results back to the card
The full public API is exported from the top-level koobi package; see
koobi.__all__.
Repository Layout
src/koobi/ # core package (schema, cards, config, index, charts, app, cli)
src/koobi/engine.py # execution engine (run_experiment, Context, toposort)
src/koobi/registry.py # component/series/measure function registry + bootstrap
src/koobi/packs/ # domain packs (finance: Sharpe/Calmar/max-drawdown)
tests/ # unit tests
examples/local/ # self-contained demo workspace
examples/local_example_to_cards.py
docs/ # product, technical, and future feature docs
Development
Run tests:
pytest
Recommended pre-PR checklist:
- run
pytest - regenerate local example cards if example inputs changed
- verify
koobi indexandkoobi uiagainstexamples/local/koobi.toml
Documentation
docs/product_design.md- product framing and roadmapdocs/backend_technical_design.md- architecture and data contractsdocs/status.md- living snapshot of what is built vs. planneddocs/future_features.md- planned automation and extensions
Status
Phase 0 (the file-native registry) is built, reviewed, and stable. The §16 B1
execution slice — koobi run composing a model + framework experiment — is now
implemented and tested. Next: card types (framework/strategy) as first-class
declared types, then parameter sweeps and the measure-vs-parameter dashboards. See
docs/status.md.
Metadata
Release files for koobi 0.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| koobi-0.0.6.tar.gz | 493.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| koobi-0.0.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 976.1 kB
Release files / koobi-0.0.6.tar.gz
| Download URL | koobi-0.0.6.tar.gz |
|---|---|
| Size | 493.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
912a9574122627d7f67871e5f10c27b20620aa20d41fae9a32172efd516a8311
|
|
BLAKE2b-256 checksum How to use checksums |
2919ef369cd8d9aab2b61d3c4066eb741dcafbc5cee75816469e645f0bdc9edf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / koobi-0.0.6-py3-none-any.whl
| Download URL | koobi-0.0.6-py3-none-any.whl |
|---|---|
| Size | 483.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
46dee8f5f2b9f4564b385725a09387b6640c8c51a66fbc2694057f5391a68933
|
|
BLAKE2b-256 checksum How to use checksums |
8faff48bdd80366c1e41562cd036a8cbbfc15ba99c9c616289dbafdc87d315f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|