RoboTrace — observability and evals for AI robots.
Project description
robotrace-dev (Python SDK)
The official Python SDK for RoboTrace — observability and evals for AI-powered robots.
pip install robotrace-dev==0.1.0a3
Why the pin? Pinning is the most reliable install during alpha. The pin goes away once we cut
1.0—pip install robotrace-devwill be enough then.
Distribution name vs. import name. PyPI distributes us as
robotrace-dev(matching ourrobotrace.devdomain). The un-hyphenatedrobotracePyPI namespace is held by an unrelated robotics project, and PyPI's typo-squat protector blocks any single-edit-distance variant (sorobo-tracewas rejected too). The import name staysimport robotrace— same pattern aspip install python-dateutil→import dateutil.
Status: alpha (
0.1.0a3). The public API in this README is the shape we're iterating against; once we cut1.0.0, thelog_episodesignature is locked and breakages require a major bump (perAGENTS.mdin the RoboTrace monorepo).
Quickstart
Mint an API key in your RoboTrace admin console (Admin → Clients → <client> → API access), then:
import robotrace as rt
rt.init(
api_key="rt_…",
base_url="https://app.robotrace.dev", # or http://localhost:3000 in dev
)
rt.log_episode(
name="pick_and_place v3 morning warmup",
source="real",
robot="halcyon-bimanual-01",
policy_version="pap-v3.2.1",
env_version="halcyon-cell-rev4",
git_sha="abc1234",
seed=8124,
video="/tmp/run.mp4",
sensors="/tmp/sensors.bin",
actions="/tmp/actions.parquet",
duration_s=47.2,
fps=30,
metadata={"task": "pick_and_place", "scene": "tabletop"},
)
The episode appears in /admin/episodes immediately, with the four
reproducibility fields (policy / env / git / seed) front-and-center
on the detail page.
From environment variables
Same call without hardcoding the key:
export ROBOTRACE_API_KEY=rt_…
export ROBOTRACE_BASE_URL=https://app.robotrace.dev
import robotrace as rt
# init() is optional when both env vars are set — the default
# client is constructed lazily on first use.
rt.log_episode(
name="…",
policy_version="…",
video="/tmp/run.mp4",
)
API
log_episode — the sacred call
The one-shot entrypoint. Equivalent to start_episode → upload all
artifacts → finalize. Use this for the 95% case of "I have files
on disk, log them and move on."
rt.log_episode(
*,
# Identification
name: str | None = None,
source: Literal["real", "sim", "replay"] = "real",
robot: str | None = None,
# Reproducibility — load-bearing per AGENTS.md
policy_version: str | None = None,
env_version: str | None = None,
git_sha: str | None = None,
seed: int | None = None,
# Artifact paths (uploaded to object storage via signed PUT URLs)
video: str | Path | None = None,
sensors: str | Path | None = None,
actions: str | Path | None = None,
# Run details
duration_s: float | None = None,
fps: float | None = None,
metadata: Mapping[str, Any] | None = None,
# Final state
status: Literal["ready", "failed"] = "ready",
) -> Episode
Returns the finalized Episode. On failure during upload the SDK
flips the run to status="failed" and re-raises so your program
sees what went wrong.
start_episode — explicit lifecycle
When you want fine-grained control (stream uploads, defer finalize,
react to upload errors per-artifact), use start_episode and the
returned Episode handle:
with rt.start_episode(
name="pick_and_place v3 morning warmup",
policy_version="pap-v3.2.1",
artifacts=["video", "sensors"], # only request the slots you'll fill
) as ep:
ep.upload_video("/tmp/run.mp4")
ep.upload_sensors("/tmp/sensors.bin")
# No explicit finalize — context manager handles it:
# • clean exit → status="ready"
# • exception → status="failed", with metadata.failure_reason set
Or explicit:
ep = rt.start_episode(name="…", policy_version="…", artifacts=["video"])
ep.upload_video("/tmp/run.mp4")
ep.finalize(status="ready", duration_s=47.2, fps=30)
Client — explicit instance
Skip the module-level default when you need multiple deployments at once (e.g. shipping the same run to staging + production), or for clean dependency injection in tests:
with rt.Client(api_key="rt_…", base_url="https://…") as client:
client.log_episode(name="…", policy_version="…", video="…")
Client holds a connection pool — construct it once at process
startup, reuse across many episodes, and close() (or use as a
context manager) on shutdown.
Errors
Every SDK error inherits from robotrace.RobotraceError. Catch by
type rather than parsing message strings:
| Exception | When |
|---|---|
ConfigurationError |
Missing api_key / base_url, file path doesn't exist |
TransportError |
Network / DNS / TLS / timeout |
AuthError |
401 — bad / missing / revoked key |
NotFoundError |
404 — episode id doesn't exist (or cross-tenant) |
ConflictError |
409 — episode is archived, etc. |
ValidationError |
400 — payload didn't pass server-side validation |
ServerError |
5xx — flag for retries |
from robotrace import RobotraceError, AuthError
try:
rt.log_episode(...)
except AuthError:
# mint a fresh key and reload
raise
except RobotraceError:
# generic recovery / alert
raise
Storage
Artifact uploads go to Cloudflare R2 via short-lived signed PUT URLs the server mints for each call. The SDK streams from disk so memory stays flat regardless of file size.
When the deployment hasn't wired R2 yet (R2_ACCOUNT_ID etc. are
blank), the create response has storage="unconfigured" and any
upload_* call raises ConfigurationError with a pointer to the
production setup checklist. Metadata-only runs still work — useful
for testing the SDK contract end-to-end before R2 is provisioned.
Layout (current)
src/robotrace/
├── __init__.py # public API + module-level default client
├── _version.py
├── client.py # Client class
├── episode.py # Episode handle + UploadUrl + ArtifactKind
├── errors.py # RobotraceError + typed subclasses
└── _http.py # internal httpx wrapper
ROS 2 / LeRobot adapters land later under src/robotrace/adapters/.
Contributing
The public source lives at github.com/Artl13/robotrace-dev — a read-only mirror auto-synced from our internal monorepo. File issues and PRs against the mirror; we'll cherry-pick approved changes back into the private repo and they'll flow out on the next sync.
The web app at apps/web (private) exposes the ingest API the SDK
talks to — coordinate breaking changes by emailing the
/api/ingest/episode
contract owner before opening a SDK PR that depends on a server
change.
License
MIT.
Project details
Release history Release notifications | RSS feed
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 robotrace_dev-0.1.0a3.tar.gz.
File metadata
- Download URL: robotrace_dev-0.1.0a3.tar.gz
- Upload date:
- Size: 64.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7eb72ac37504f3087730448a0ffd60a9c4d71aab1a91838ee64f73d72a31a0c4
|
|
| MD5 |
1a28a38d535466a877024c22aa1df0d3
|
|
| BLAKE2b-256 |
8cf7c1df1871690b4040e3dbf3ddb1c707c3776e3cbe6399ba6e1b4713df4cb8
|
File details
Details for the file robotrace_dev-0.1.0a3-py3-none-any.whl.
File metadata
- Download URL: robotrace_dev-0.1.0a3-py3-none-any.whl
- Upload date:
- Size: 71.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0bfea619ce7ca1fa78b77a22df576e9ff03ea28d81fea119af434fb182af906
|
|
| MD5 |
6b6650f5e6a1e06123d751522877ae26
|
|
| BLAKE2b-256 |
5dfab9f286ce2973a7c604316abac859d546089d780e82c44c92beb127f4c289
|