SAGEA LogoDocs & API
Cursus

Cursus overview

Cursus is SAGEA's experiment tracking platform — log training runs, track metrics, version artifacts, and orchestrate hyperparameter sweeps with a lightweight Python SDK.

GitHub: github.com/sagea-ai/cursus

Cursus is open source and self-hosted. Your base URL is the address of your own Cursus instance — everyone's is different.

Install and configure

pip install sagea-cursus
export CURSUS_API_KEY="cursus_..."          # from <server>/<org>/settings/keys
export CURSUS_BASE_URL="https://cursus.example.com"  # omit for localhost:3000

requests is the only hard dependency (Python 3.9+). Numpy and Pillow are optional extras — never install requirements.

Full reference: SDK Setup

Basic training loop

Credentials come from the environment above, or explicitly per call (handy for multi-server scripts — never commit real keys to git):

import sagea_cursus as cursus
 
run = cursus.init(
    project="demo",
    config={"lr": 1e-4, "arch": "mlp"},
    api_key="cursus_...",  # or CURSUS_API_KEY
    base_url="https://cursus.example.com",  # or CURSUS_BASE_URL
)
try:
    for step in range(100):
        loss = 1.0 / (step + 1)
        cursus.log({"train/loss": loss}, step=step)
        if step % 10 == 0:
            cursus.log({"train/loss": loss, "eval/acc": step / 100}, step=step)
    cursus.finish()
except Exception:
    cursus.finish("crashed")
    raise

Images, artifacts, and mid-run config

import sagea_cursus as cursus
 
run = cursus.init(project="vision", config={"backbone": "resnet50"})
for epoch in range(10):
    train_one_epoch(...)
    cursus.log({"train/loss": 0.4 / (epoch + 1)}, step=epoch)
    cursus.log_image("val/samples", f"preds_epoch{epoch}.png", step=epoch)
    if epoch == 5:
        cursus.config.update({"lr": 1e-5})  # synced back, debounced
cursus.log_artifact("resnet50", "runs/best.pt", type="model", description="best val acc")
cursus.finish()

Group projects and named runs

import sagea_cursus as cursus
 
# Log inside a group project (you must belong to the group).
run = cursus.init(project="detection", group="vision-team", name="baseline-a", tags=["v2-data"])
print("view at:", run.url)
cursus.log({"map50": 0.61}, step=1)
cursus.finish()

Sweep worker (grid/random)

import sagea_cursus as cursus
 
sweep = cursus.create_sweep(
    "demo",
    {"lr": {"min": 1e-5, "max": 1e-1, "scale": "log"}, "batch": {"values": [16, 32]}},
    name="lr-search",
)
while (trial := cursus.next_trial(sweep["id"])) is not None:
    run = cursus.init(
        project="demo",
        config=trial["config"],
        name=f"sweep-{trial['trial']}",
        sweep_id=trial["sweep_id"],
    )
    try:
        train(trial["config"])
        cursus.finish()
    except Exception:
        cursus.finish("crashed")
        raise

Reliability contract

init() may raise (missing key, version skew). Everything else degrades to warnings: dead servers cost you points, never a crashed training job. Always finish() in a finally — unflushed points and heartbeats die with the process, and the run stays RUNNING until staleness flips it.

See Troubleshooting for the full error model.

Cookbooks

What's next

On this page