| name | xetrack |
| description | API reference and usage patterns for xetrack — lightweight experiment tracking with SQLite/DuckDB. Use when writing code that uses xetrack, when unsure about Tracker or Reader API, when using the xt CLI, when working with caching/diskcache, or when tracking ML experiments. Triggers on "xetrack", "Tracker", "Reader", "tracker.track", "tracker.log", "xt cli", "xetrack cache", "experiment tracking", "track function", "track metrics". |
xetrack API Reference
xetrack is a lightweight experiment tracking tool. It stores metrics, parameters, and assets in SQLite or DuckDB. This skill is the canonical API reference — use it to write correct xetrack code.
DO NOT hallucinate APIs. If a method isn't listed here, check the source before using it.
Install
pip install xetrack
pip install xetrack[duckdb]
pip install xetrack[cache]
pip install xetrack[assets]
pip install xetrack[duckdb,cache,assets]
Imports
from xetrack import Tracker, Reader, copy
Tracker
The main interface for tracking experiments.
Constructor
Tracker(
db: str = "track.db",
params: Dict[str, Any] | None = None,
reset: bool = False,
log_system_params: bool = False,
log_network_params: bool = False,
raise_on_error: bool = True,
measurement_interval: float = 1,
track_id: Optional[str] = None,
logs_path: Optional[str] = None,
logs_file_format: Optional[str] = None,
logs_stdout: bool = False,
jsonl: Optional[str] = None,
cache: Optional[str] = None,
compress: bool = False,
warnings: bool = True,
git_root: Optional[str] = None,
engine: Literal["duckdb", "sqlite"] = "sqlite",
table: str = "default",
)
Properties
tracker.db
tracker.conn
tracker.table_name
tracker.columns
tracker.dtypes
tracker.assets
tracker.track_id
tracker.params
tracker.latest
Core Methods
tracker.log(data: dict) -> dict
Log arbitrary key-value data as an event. Adds track_id and timestamp automatically. Creates columns dynamically.
tracker.log({"accuracy": 0.95, "loss": 0.05, "epoch": 10})
tracker.log({"model": "bert", "dataset": "squad"})
Returns: The validated data dict (with track_id and timestamp added).
tracker.track(func, params=None, args=None, kwargs=None, cache_force=False)
Execute a function, log its execution metadata, and return the result. Automatically tracks function_name, function_time, args, kwargs, error, and the function's return value.
def train(lr: float, epochs: int) -> dict:
return {"accuracy": 0.95, "loss": 0.05}
result = tracker.track(train, args=[0.01, 10])
result = tracker.track(train, args=[0.01, 10], params={"model": "bert"})
result = tracker.track(train, kwargs={"lr": 0.01, "epochs": 10})
result = tracker.track(train, args=[0.01, 10], cache_force=True)
Return value handling:
- If the function returns a
dict, its keys are merged into the logged event as columns.
- If the function returns a primitive (int, float, str, etc.), it's stored in
function_result.
- If the function returns a non-primitive, it's converted to string and stored in
function_result.
Dataclass argument unpacking:
When arguments are dataclass or Pydantic BaseModel instances, their fields are automatically extracted as columns with {param_name}_{field_name} naming:
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
lr: float
batch_size: int
def train(config: Config) -> dict:
return {"accuracy": 0.95}
tracker.track(train, args=[Config(lr=0.01, batch_size=32)])
tracker.wrap(params=None)
Decorator factory for tracking functions.
@tracker.wrap(params={"experiment": "baseline"})
def predict(x):
return x * 2
result = predict(5)
tracker.log_batch(data: List[dict]) -> dict
Log multiple events at once (batch insert).
tracker.log_batch([
{"step": 1, "loss": 0.5},
{"step": 2, "loss": 0.3},
{"step": 3, "loss": 0.1},
])
Parameter Management
tracker.set_param("model", "bert")
tracker.set_params({"model": "bert", "version": "v2"})
tracker.set_value("accuracy", 0.95)
tracker.set_where("label", "good", "accuracy", 0.95)
Query Methods
tracker.to_df(all=False)
tracker.head(n=5)
tracker.tail(n=5)
tracker.count_all()
tracker.count_run()
len(tracker)
tracker["accuracy"]
tracker[1]
Export
tracker.to_csv("output.csv", all=False)
tracker.to_parquet("output.parquet", all=False)
Assets
Requires pip install xetrack[assets]. Stores complex Python objects with hash-based deduplication.
import sklearn.linear_model
model = sklearn.linear_model.LinearRegression()
tracker.log({"model_obj": model})
tracker.get("model_obj_hash")
Static/Utility
Tracker.generate_track_id()
Tracker.IN_MEMORY
Tracker.SKIP_INSERT
Caching (diskcache)
Requires pip install xetrack[cache] or pip install diskcache.
How It Works
When cache path is provided to Tracker, tracker.track() automatically caches function results:
- Cache key =
(module.func_name, args, kwargs, params) — all must be hashable
- Cache hit = skip function execution, log cached result with lineage reference
- Cache miss = execute function, store result, log with empty cache field
Cache Lineage in DB
The cache column in the DB tracks lineage:
"" (empty string) = this row was computed fresh
"cool-name-1234" (a track_id) = this row was served from cache, originally computed by that track_id
cache_force
Skip cache read and re-execute the function, overwriting the existing cache entry:
result = tracker.track(func, args=[1, 2])
result = tracker.track(func, args=[1, 2], cache_force=True)
What Gets Cached
- Only successful executions (exceptions are NOT cached)
- Only calls with fully hashable arguments (primitives, frozen dataclasses, objects with
__hash__)
- Unhashable args (lists, dicts, non-frozen dataclasses) silently skip caching
Frozen Dataclasses for Caching
@dataclass(frozen=True)
class Config:
model: str
lr: float
tracker.track(train, args=[Config("bert", 0.01)])
tracker.track(train, args=[Config("gpt", 0.01)])
tracker.track(train, args=[Config("bert", 0.01)])
Reader
Read-only interface for accessing tracked data.
Constructor
Reader(
db: str,
engine: Literal["duckdb", "sqlite"] = "sqlite",
table: str = "default",
)
Properties
reader.db
reader.conn
reader.columns
reader.dtypes
reader.assets
Query Methods
reader.to_df(track_id=None, head=None, tail=None)
reader.latest()
len(reader)
Data Modification
reader.delete_run("cool-name-1234")
reader.set_value("key", "value", track_id="cool-name-1234")
reader.set_where("key", "value", "where_key", "where_val", track_id=None)
reader.remove_asset("hash", column=None, remove_keys=True)
Class Methods — Database
Reader.read_db(db, engine="sqlite", table="default", track_id=None, head=None, tail=None)
Class Methods — Logs
Reader.read_logs(path, limit=None)
Reader.read_jsonl(path)
Class Methods — Cache
Reader.read_cache(cache_path, key)
for key, cached_data in Reader.scan_cache(cache_path):
print(cached_data["result"], cached_data["cache"])
deleted_count = Reader.delete_cache_by_track_id(cache_path, "cool-name-1234")
CLI (xt)
Database Queries
xt head db.db --n=10
xt tail db.db --n=10
xt columns db.db
xt ls db.db
xt ls db.db track_id --unique
xt sql db.db "SELECT * FROM \"default\" LIMIT 5"
Data Operations
xt set db.db key value --track-id=cool-name-1234
xt delete db.db cool-name-1234
xt copy source.db target.db --assets/--no-assets --table=default --table=other
Cache Management
xt cache ls ./cache_dir
xt cache delete ./cache_dir cool-name-1234
Asset Management
xt assets ls db.db
xt assets export db.db <hash> output.pkl
xt assets delete db.db <hash>
Statistics
xt stats describe db.db --columns=accuracy,loss
xt stats top db.db accuracy
xt stats bottom db.db loss
Plotting (requires pip install xetrack[bashplotlib])
xt plot hist db.db accuracy --bins=20
xt plot scatter db.db epoch loss
Common Options
All DB commands support:
--engine sqlite|duckdb (default: sqlite)
--table <name> (default: "default")
--json (head/tail only — output as JSON)
copy()
Copy data between databases (DuckDB only).
from xetrack import copy
copy("source.db", "target.db")
copy("source.db", "target.db", tables=["predictions", "metrics"])
copy("source.db", "target.db", assets=False)
Engine Selection Guide
| Feature | SQLite | DuckDB |
|---|
| Default | Yes | No (pip install xetrack[duckdb]) |
| Concurrent writes | multiprocessing.Pool | ThreadPoolExecutor |
| Table name in SQL | "default" (quoted) | db.default |
| Best for | Local dev, single process | Analytics, parallel I/O |
| copy() support | No | Yes |
Common Patterns
Multi-Table Tracking
pred_tracker = Tracker(db="exp.db", table="predictions", cache="cache")
metrics_tracker = Tracker(db="exp.db", table="metrics")
for item in dataset:
pred_tracker.track(predict, args=[item, params])
metrics_tracker.log({"accuracy": 0.95, "model": "bert"})
JSONL Logging (for data synthesis pipelines)
tracker = Tracker(db=Tracker.SKIP_INSERT, jsonl="output.jsonl", logs_stdout=True)
tracker.log({"prompt": "...", "response": "...", "label": 1})
df = Reader.read_jsonl("output.jsonl")
Git Commit Tracking
tracker = Tracker(db="exp.db", git_root=".")
System Resource Monitoring
tracker = Tracker(db="exp.db", log_system_params=True, log_network_params=True)
result = tracker.track(expensive_func, args=[data])
Gotchas
- Table name quoting: SQLite table
default is a reserved keyword. In raw SQL use "default" (quoted). The engine handles this automatically.
- Unhashable args skip caching silently. Lists, dicts, non-frozen dataclasses won't be cached. Use frozen dataclasses.
- Multiple Trackers on same DB: Each Tracker instance has its own
_columns set. Creating a second Tracker on the same DB may trigger "duplicate column" errors if both try to log. Use a single Tracker or separate track_ids on the same instance.
- Dynamic schema: Columns are added on first use. If you rename a param, xetrack creates a NEW column — the old one stays with NULLs for new rows.
- Assets require sqlitedict:
tracker.assets is None unless pip install xetrack[assets].
- DuckDB table names: DuckDB uses
db.tablename syntax. The engine handles this, but raw SQL queries need to use the right format.