Metadata-Version: 2.4
Name: kymo
Version: 2026.10.13
Summary: Experiment tracker for machine learning
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/NovelAI/kymo
Project-URL: Issues, https://github.com/NovelAI/kymo/issues
Project-URL: Releases, https://github.com/NovelAI/kymo/tags
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: grpcio<2,>=1.81.0
Requires-Dist: protobuf>=6.33.5
Requires-Dist: httpx>=0.27.0
Requires-Dist: pillow>=10.0.0
Requires-Dist: numpy>=1.24.0
Provides-Extra: dev
Requires-Dist: grpcio-tools==1.81.0; extra == "dev"
Requires-Dist: ruff==0.15.2; extra == "dev"
Provides-Extra: local
Requires-Dist: kymo-local-runtime==2026.10.13; extra == "local"
Dynamic: license-file

Install with `pip install "kymo[local]"`, or `pip install kymo` if there will be a separate server. For an intro, see the [kymo README](https://github.com/NovelAI/kymo#readme).

## Commands

```text
kymo open [project [run]]   start the stack if needed and open the dashboard, a project, or a run
kymo status                 show whether the stack is running
kymo stop                   stop the stack
kymo doctor                 check the installation and show where its files live
kymo ports                  show the dashboard ports, or change them while stopped
```

## On a remote machine

Local mode listens only on the machine itself. To view a stack running on a server you reach over SSH, forward both of its ports at the same numbers:

```sh
kymo ports    # e.g. dashboard: http://127.0.0.1:24819 and CDN: http://127.0.0.1:24820
ssh -L 24819:127.0.0.1:24819 -L 24820:127.0.0.1:24820 you@server
```

Then open the URL that `kymo open --no-browser` prints on the server in your local browser.

## If the backend is not available

Metrics that cannot be delivered are written to a spool (`~/.cache/kymo/spool`, or `$KYMO_SPOOL_DIR`) instead of being dropped. A running script delivers them once the backend is back. Run `kymo-sync` to deliver anything left behind by a script that has already exited.

## Remove or back up local data

Stop any scripts that are still logging, run `kymo stop`, and use `kymo doctor --json` to find the data, state, and cache directories. Deleting those three directories uninstalls local mode. The spool directory is separate; delete it only if you also want to discard undelivered data.

For a backup, keep the stack stopped with `kymo stop --hold` while you copy the data directory and `state/runtime.json`. Put both back the same way to restore.

## Logging to a server

Leave out `mode="local"` and pass the server's gRPC address as `server_address="host:port"`, or set `KYMO_SERVER`. Pass the dashboard's origin as `url_base` (or set `KYMO_URL_BASE`) for `kymo.run_url()`. To run the server yourself, see [self-hosting](https://github.com/NovelAI/kymo/blob/main/docs/self-hosting.md).

## Reading runs back

`kymo.Api` reads what runs logged. It takes the same `server_address`, `mode` and `cdn_address` as `kymo.init` and falls back to the same environment variables (`cdn_address` has none).

```python
import kymo

api = kymo.Api("kymo.example:50051")  # or kymo.Api(mode="local")
run = api.runs("my-project")[0]  # newest first
loss = api.history("my-project", run.run_id, "train/loss")[""]  # [(step, value), ...]
config = (api.run_info("my-project", run.run_id) or {}).get("config")
errors = api.logs("my-project", run.run_id, search="Traceback", limit=100).lines
```

`history` returns every point it can read, with NaN and inf as logged. A metric logged as a list has one series per index tag (`"0"`, `"1"`, ...), and an untagged metric has the tag `""`. Its docstring lists the server's limits. `media` lists a media metric's stored keys and `fetch` downloads one.

An `Api` can be shared between threads (close it only after their calls return), but works only in the process that created it: create one in each worker process. Importing `kymo` tees stdout and stderr into a bounded capture buffer, even in a script that only reads.

Under the `fork` start method (Linux's default before Python 3.14), start worker processes and call every `kymo.init()` before the first read: gRPC is unsafe in a process forked after it started, so `init()`, which forks its upload worker, refuses once an Api has opened its gRPC channel. Selecting `spawn` or `forkserver` with `multiprocessing.set_start_method()` before the first `kymo.init()` lifts this.

## wandb importing

Two scripts in [tools/wandb-import](https://github.com/NovelAI/kymo/tree/main/tools/wandb-import) move wandb history into kymo: `export_wandb.py` downloads a wandb entity into a local archive, and `import_to_kymo.py` replays that archive into a kymo server with each run's original times. They are not part of the package: run them from a clone after `pip install ./python_client pyarrow wandb wandb-workspaces requests`, since the importer uses the client's internals. The importer dry-runs by default. To write, pass `--execute --server host:port`, with `KYMO_IMPORT_ENABLED=1` on that server (see [self-hosting](https://github.com/NovelAI/kymo/blob/main/docs/self-hosting.md)), or `--execute --local`, which also needs the runtime of the same release (`pip install kymo-local-runtime==X` for a clone at tag vX). You'll likely need an AI agent to use them, since wandb's APIs change over time.
