Experiment Tracker
A local-first experiment tracker. Metrics land in a JSONL file you own, stay
queryable while the run is live, and can trigger alerts from an expression language.
wandb and trackio are optional mirrors, not requirements.
📖 Documentation: https://hspk.github.io/expr_tracker/
import expr_tracker as et
et.init(project="demo", name="run-1", alert_rules=["zscore(loss[50]) > 3 => error: spike"])
for step in range(1000):
et.log({"loss": loss, "lr": lr})
et.finish()
et.history(50) # the last 50 steps, as dicts
et.history(-1, output_type="pd") # everything, as a DataFrame
Why
- The file is the source of truth. One JSON object per step, appended to
metrics.jsonl. No server, no database, no vendor. - History is queryable during the run.
et.history(n)answers from an in-memory cache and touches the file only for what it evicted — 227 µs forhistory(50)whether the run has 1,000 steps or 100,000. - Alerts are expressions, not callbacks.
zscore(loss[50]) > 3 or isnan(loss)is parsed, validated, and evaluated against a rolling window. Rules can be replayed over a finished run to tune thresholds before you trust them. - It stays out of the way.
log()costs ~26 µs. A failed disk, a dead webhook or an unserialisable value degrades with a warning; none can stop training.
Install
uv add expr_tracker # local-first: click, loguru, pydantic only
uv add "expr_tracker[wandb]" # mirror to Weights & Biases
uv add "expr_tracker[trackio]" # mirror to trackio
uv add "expr_tracker[lark]" # Feishu/Lark alert channel
uv add "expr_tracker[pandas]" # history(output_type="pandas")
uv add "expr_tracker[all]" # everything
Only the JSONL history is built in. A missing extra is reported with the exact install command; it never crashes a run.
Features
| Logging | One line per step. Several log() calls for one step merge into one row, wandb-compatible step/commit semantics, numpy and pydantic values handled. |
| History | et.history(n) during or after the run, offline reads of any run directory, dict/pandas/polars output, bounded in-memory cache with observable hit rate. |
| Alerts | An expression DSL with rolling windows, three-valued logic (no false alarms during warm-up), a rule state machine, and a watchdog that catches a hung run. |
| Channels | Lark, Slack, DingTalk, WeCom, generic webhook, email — with rate limiting, dedup, retries and per-channel routing. |
| Artifacts | Versioned file sets, deduplicated by content, shared across a project's runs, with lineage. |
| Distributed | Per-rank shards so concurrent appends cannot corrupt step order; only rank 0 alerts by default. |
| CLI | et history, et rules explain, et rules test, et alert. |
wandb compatibility
Migrating an existing script is usually one line:
# import wandb as et
import expr_tracker as et
init, log, finish, alert, log_artifact, use_artifact, Artifact,
define_metric, run.summary, run.step, run.dir and run.url keep their wandb
names and signatures. See the
compatibility table.
Development
uv sync --all-extras
uv run pytest # everything
uv run pytest -m "not slow and not benchmark" # the fast suite
uv run pytest -m benchmark -s # timing and memory report
uv run pytest --cov=expr_tracker # coverage
uv run ruff check src tests
uv run ruff format src tests
Test layout
| File | Covers |
|---|---|
test_history, test_expr_*, test_alert_*, test_writer_durability, … |
per-module unit tests |
test_correctness.py |
value and type round trips, randomised commit sequences, ordering invariants |
test_cache.py |
that the cache really serves reads: zero-IO assertions, eviction boundaries, warm/cold parity |
test_failure_modes.py |
degradation: write failures, read-only dirs, encoder blow-ups, dead alert backends |
test_e2e.py |
full runs, resume, crash recovery, offline reads, CLI |
test_scenarios.py |
live cross-process reads, alerts during eviction, out-of-order resume |
test_hot_paths.py |
contracts and defaults of et.log / et.history / summary / alerts |
test_value_encoding.py |
numpy, pydantic, datetime, Path, Enum round trips; output types; query bounds |
test_expr_properties.py |
DSL properties: render round-trip stability, precedence, the whole M builder |
test_distributed.py |
rank shards, alert_on_rank, real multi-process runs |
test_wandb.py |
real wandb in offline mode: parameter mapping, step alignment, artifacts |
test_trackio.py |
trackio contract, resume mapping, real end-to-end |
test_lark_live.py |
Lark channel; real delivery when ET_LARK_TEST_WEBHOOK is set |
test_stress.py (slow) |
100k-row writes, concurrency, cache thrash, write-failure recovery |
test_benchmark.py (benchmark) |
throughput, tail latency, query cost, memory stability |
Docs
uv run --group docs mkdocs serve # preview at localhost:8000
uv run --group docs mkdocs build # build into site/
Published to GitHub Pages by .github/workflows/docs.yaml on every push to main.
Internals: docs/design.md (data model and key invariants) and
docs/architecture.md (module map, read/write paths,
concurrency model).
License
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 expr_tracker-0.2.2.tar.gz.
File metadata
- Download URL: expr_tracker-0.2.2.tar.gz
- Upload date:
- Size: 344.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55788802cc19a2b2f47415b5aa8f3edc7b07a249e22ae7b8ffa948f22f51aaba
|
|
| MD5 |
5db8068f013f9691736794ae83c15b25
|
|
| BLAKE2b-256 |
bf190586695d039f2e82681c020653e370bcc636f8b63878fa6a08ea1f76d977
|
Provenance
The following attestation bundles were made for expr_tracker-0.2.2.tar.gz:
Publisher:
release.yaml on HSPK/expr_tracker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
expr_tracker-0.2.2.tar.gz -
Subject digest:
55788802cc19a2b2f47415b5aa8f3edc7b07a249e22ae7b8ffa948f22f51aaba - Sigstore transparency entry: 2364223690
- Sigstore integration time:
-
Permalink:
HSPK/expr_tracker@21260a42216f4c4730e53a62208180c9bb8dd8f0 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/HSPK
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@21260a42216f4c4730e53a62208180c9bb8dd8f0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file expr_tracker-0.2.2-py3-none-any.whl.
File metadata
- Download URL: expr_tracker-0.2.2-py3-none-any.whl
- Upload date:
- Size: 79.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58f7b1bec03425b844761224194a61245845bc11acfe29e84752a066776e99b8
|
|
| MD5 |
44a2d56f5f475d5075f567d42aa0c711
|
|
| BLAKE2b-256 |
05a6fbe158ee6f8267e7d738f52b3708cccebbb071b76f4b8d502d78ad068fd7
|
Provenance
The following attestation bundles were made for expr_tracker-0.2.2-py3-none-any.whl:
Publisher:
release.yaml on HSPK/expr_tracker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
expr_tracker-0.2.2-py3-none-any.whl -
Subject digest:
58f7b1bec03425b844761224194a61245845bc11acfe29e84752a066776e99b8 - Sigstore transparency entry: 2364223741
- Sigstore integration time:
-
Permalink:
HSPK/expr_tracker@21260a42216f4c4730e53a62208180c9bb8dd8f0 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/HSPK
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@21260a42216f4c4730e53a62208180c9bb8dd8f0 -
Trigger Event:
push
-
Statement type: