Experiment Registry
SkillCommerce & financeContent-addressed experiment tracking for ML trading models. Use when versioning models, features, or experiment artifacts for reproducibility.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Experiment Registry skill
What this skill tells your AI
The instructions your AI receives, as published by ml4t/skills in infrastructure/registry-system/SKILL.md and read by ahel’s review.
Without a registry, you overwrite the best model every time you retrain. Content-addressed storage - where hash(config) determines the storage path - makes every experiment reproducible and comparable without manual bookkeeping.
The Problem
A quant runs 50 model configurations. Results go into model_v2_final_FINAL.pkl. Next week, a new run overwrites it. The team cannot answer: which hyperparameters produced the best IC? Was that before or after the feature change? Did we already try alpha=0.01? Without structured tracking, experiments are lost, repeated, and unverifiable.
The Pattern
WRONG
import pickle
# Overwrite on every run - no history, no comparison, no provenance
model.fit(X_train, y_train)
with open("best_model.pkl", "wb") as f:
pickle.dump(model, f)
# Three weeks later: "Which config was this? What data did it use?"
CORRECT
import hashlib
import json
import sqlite3
from datetime import datetime
def config_hash(config: dict) -> str:
"""Deterministic hash of experiment config."""
blob = json.dumps(config, sort_keys=True).encode()
return hashlib.sha256(blob).hexdigest()[:12]
def register_run(db_path: str, config: dict, metrics: dict, predictions_path: str):
"""Register a training run with full provenance."""
run_hash = config_hash(config)
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS training_runs (
run_hash TEXT PRIMARY KEY,
config JSON NOT NULL,
metrics JSON NOT NULL,
predictions_path TEXT,
created_at TEXT NOT NULL
)
""")
conn.execute(
"INSERT OR REPLACE INTO training_runs VALUES (?, ?, ?, ?, ?)",
(run_hash, json.dumps(config), json.dumps(metrics),
predictions_path, datetime.now().isoformat()),
)
conn.commit()
return run_hash
# Usage: every config gets a unique, reproducible slot
config = {"model": "ridge", "alpha": 1.0, "features": "momentum_v2"}
run_hash = register_run("registry.db", config, {"ic": 0.04}, f"runs/{config_hash(config)}/predictions.parquet")
# Re-running same config overwrites same slot - idempotent
Registry Schema
Three linked tables capture the full experiment lifecycle:
training_runs prediction_sets backtest_runs
+------------+ +----------------+ +--------------+
| run_hash |<------>| pred_hash |<----->| bt_hash |
| config | 1:N | run_hash (FK) | 1:N | pred_hash(FK)|
| metrics | | fold | | config |
| created_at | | path | | metrics |
+------------+ +----------------+ +--------------+
- training_runs: one row per unique model config (hash of hyperparams)
- prediction_sets: one row per fold or time split within a training run
- backtest_runs: one row per strategy config applied to a prediction set
Content-Addressed Storage
run_log/
registry.db # SQLite: all metadata
models/{config_hash}/ # hash(config) -> directory
config.json
metrics.json
predictions.parquet
The hash is the directory name. Same config always maps to the same directory. No manual naming, no collisions, no "v2_final" suffixes. Query the registry with standard SQL against registry.db.
Guardrails
- Hash must be deterministic:
json.dumps(config, sort_keys=True)- withoutsort_keys, same config produces different hashes - Register per-config as they complete, not in bulk after all finish - a crash at config 49 of 50 loses everything otherwise
- Never store model weights in the SQLite database - store paths to artifacts on disk
- Config must capture everything needed to reproduce: model type, hyperparameters, feature version, data version, random seed
- Old runs are never deleted - mark as superseded, keep for audit trail
Checklist
- Every experiment has a deterministic config hash
- Registry stores config, metrics, and artifact paths (not weights in DB)
- Runs registered incrementally (per-config, not bulk)
- Same config re-run maps to same hash (idempotent)
- Top-N query by any metric works against the registry
- Full provenance: model type, hyperparams, feature version, data version, seed
Signals
- GitHub stars
- 20
- Forks
- 11
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
ml4t-registry-system- Source
- github.com/ml4t/skills