Getting started¶
Install¶
pip install research-tracker
During development, from a neighboring project:
uv add --editable ../research-tracker
To install this project together with its test dependencies:
uv sync --group dev
Upgrading a store written by 0.1.x additionally needs the migrate extra, which
pulls in pyarrow for reading the old Parquet tables:
pip install "research-tracker[migrate]"
Creating a store¶
ExperimentStore takes the store directory. Constructing it creates the
directory, schema.json, and store.sqlite if they do not exist, and validates
the existing store otherwise:
from pathlib import Path
from research_tracker import ExperimentStore
store = ExperimentStore(Path("artifacts"))
print(store.db_path) # artifacts/store.sqlite
print(store.schema_version) # 2
A store directory is identified by schema.json; opening a directory whose
schema.json says a different version raises SchemaMismatchError at
construction, before anything is created or modified. A directory with only one
of schema.json / store.sqlite is rejected as incomplete.
Writes are serialized through a .sync.lock file lock, so separate jobs can
safely append to the same store. Config, checkpoint, and artifact paths must
stay inside the store root; an absolute or escaping path raises ValueError.
The evaluation context manager¶
store.evaluation(run_id, name) creates an evaluation and yields an
EvaluationSession bound to it:
from research_tracker.trackers import Status
run = store.create_run(
model="inverse-operator",
model_class="my_project.models:InverseOperator",
dataset="dynamic-pet",
dataset_version="irr-v1",
config=Path("artifacts/config.yaml"),
status=Status.RUNNING,
)
with store.evaluation(run.run_id, "test") as evaluation:
evaluation.add_condition("split", "out-of-distribution")
evaluation.log_metric(sample_id="mouse-01", metric="mse", value=0.012)
evaluation.log_artifact(
kind="prediction",
path=Path("artifacts/predictions/mouse-01.npz"),
sample_id="mouse-01",
)
store.set_status(Status.COMPLETED, run.run_id)
add_condition(name, value, *, unit=None)acceptsstr,int,float, orbool; the value keeps its JSON type.log_metric(metric, value, *, sample_id=None, target=None, comparison=None, region=None, unit=None)—metricandvalueare required, the rest are stored as null when omitted.log_artifact(kind, path, *, sample_id=None)stores the path relative to the store root.
On success the context flushes buffered metrics and conditions. If the body or the flush raises, the evaluation and its metric, condition, and artifact rows are rolled back, including records already written during the block. The artifact files themselves are kept. See runs and evaluations for the details.
Lightning callback¶
ResearchTrackerCallback creates the run at fit start, records the selected
Lightning logger, embeds the research run ID in checkpoints, records the best or
last checkpoint, and marks the run completed or failed.
trainer:
callbacks:
- class_path: research_tracker.ResearchTrackerCallback
init_args:
store_root: ${oc.env:RESEARCH_TRACKER_ROOT,./artifacts}
dataset: dynamic-pet
dataset_version: irr-v1
checkpoint_policy: best_or_last
Useful defaults and options:
store_rootdefaults totrainer.default_root_dir.modeldefaults tomodel_nameor the LightningModule class name.datasetdefaults todataset_nameor the LightningDataModule class name.config_pathis optional. The callback searches the logger experiment directory, logger log/save directories, then the trainer root forconfig.yaml(config_filenamechanges the name).logger_indexselects which logger supplies external tracking metadata.checkpoint_policyisbest_or_last,best,last, ornone.resume_mode="continue"reuses a known run ID from a loaded checkpoint;"fork"creates a child run instead.parent_run_idexplicitly links weight-only fine-tuning or other derived runs.loaderselects a custom checkpoint loader.- Fast development runs are ignored unless
track_fast_dev_runs=true.
Only the global-zero Lightning process writes to the store. Separate training jobs can safely append to the same store through its file lock.
The callback writes through the same ExperimentStore API, so any buffered
metric or condition still needs a flush() before the store is copied — see
durability.