Metadata-Version: 2.4
Name: koobi
Version: 0.0.6
Summary: File-native model and experiment registry
Author-email: blu3c0ral <blu3c0ral@protonmail.ch>
License-Expression: MIT
Project-URL: Homepage, https://github.com/blu3c0ral/koobi
Project-URL: Repository, https://github.com/blu3c0ral/koobi
Project-URL: Issues, https://github.com/blu3c0ral/koobi/issues
Keywords: experiment-tracking,model-registry,marimo,duckdb,markdown
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: altair>=5
Requires-Dist: duckdb>=0.10
Requires-Dist: fastapi>=0.111
Requires-Dist: marimo>=0.23
Requires-Dist: pandas>=2
Requires-Dist: pyarrow>=14
Requires-Dist: pyyaml>=6
Requires-Dist: tomli; python_version < "3.11"
Requires-Dist: uvicorn[standard]>=0.30
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

<p align="center">
  <img src=".github/assets/koobi_icon.png" alt="Koobi project icon" width="180" />
</p>

# 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:
  - `cards` table (metadata + body)
  - `measures` table (long-format metrics)
  - `series_registry` table (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` / `items` kinds; 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
- CLI commands:
  - `koobi init`
  - `koobi new`
  - `koobi index`
  - `koobi run`
  - `koobi ui`

## Architecture Overview

```text
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.

```text
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:

```bash
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:

```bash
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

```bash
koobi init --path my-registry
cd my-registry
```

Or use the bundled demo workspace:

```bash
python examples/local_example_to_cards.py --overwrite
cd examples/local
```

### 2) Build the index

```bash
koobi index
```

### 3) Launch the UI

```bash
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):

```bash
koobi ui --config examples/local/koobi.toml
# or
export KOOBI_CONFIG=examples/local/koobi.toml
koobi ui
```

## CLI Reference

Initialize a new workspace:

```bash
koobi init --path /path/to/workspace
```

Create a new card (run from inside the workspace):

```bash
koobi new demo_project.my_model
```

Rebuild index:

```bash
koobi index
```

Run an experiment (Mode B):

```bash
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:

```bash
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):

```toml
# 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):

```python
# 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:

```yaml
# model card — produces a "predictions" port
id: my_project.model
type: model
runner: my_project.model
produces: predictions
```

```yaml
# framework card — consumes "predictions", produces "equity_curve"
id: my_project.framework
type: framework
runner: my_project.framework
needs: [predictions]
produces: equity_curve
```

```yaml
# 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`](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:

```python
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:

```python
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

```text
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:

```bash
pytest
```

Recommended pre-PR checklist:

- run `pytest`
- regenerate local example cards if example inputs changed
- verify `koobi index` and `koobi ui` against `examples/local/koobi.toml`

## Documentation

- [`docs/product_design.md`](docs/product_design.md) - product framing and roadmap
- [`docs/backend_technical_design.md`](docs/backend_technical_design.md) - architecture and data contracts
- [`docs/status.md`](docs/status.md) - living snapshot of what is built vs. planned
- [`docs/future_features.md`](docs/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`](docs/status.md).
