The Zwicky Transient Facility broadcasts ~1 million alerts per night — every new dot in the sky that wasn't there last image. This demo replays a recorded ZTF-style alert stream into ARCP, hosts a transient.triage agent that crossmatches each detection against catalogs and asks an LLM to classify it, and delegates a follow-up agent (transient.followup) for high-confidence candidates that produces an observer's note plus thumbnails.
Showcases: per-alert ARCP jobs with idempotency_key, agent.delegate with strict-subset leases, structured tool_call / tool_result events, metric events tied to budget accounting, artifact_ref for derived data products, and progress for live ETA — all driven by a tiny in-process tool registry plus a Textual TUI subscriber.
cp .env.example .env # optional — defaults work out of the box
make up # ollama → runtime → alert-source → client (foreground)
make watch # in another terminal: tail ./night_reports/
make replay # re-run the alert-source against the live runtime
make down # tear downFirst run downloads the Ollama model (~1GB) into a Docker volume.
Within ~5 seconds of make up, the dashboard's sky map populates with classified alerts coloured by class. The bundled kilonova candidate (alerts/kn_001.json, positioned at NGC 1365) is flagged with score >= ALERT_FOLLOWUP_THRESHOLD, the runtime delegates transient.followup, and within ~30s a markdown observer's note + difference-image thumbnails appear under ./night_reports/. The budget panel ticks downward toward the nightly cap.
Crank ALERT_REPLAY_RATE to 50 and ALERT_INFLIGHT to 64 and the runtime keeps up — ARCP's protocol ACK pacing is visible as light queuing on the dashboard.
Kill and restart the runtime; the alert-source's idempotency_key = ztf:cand:<candid> derivation guarantees no alert is double-classified on resume.
Every knob lives in .env. Example-specific ones:
| Variable | Default | Effect |
|---|---|---|
ALERT_CORPUS_DIR |
/alerts |
Directory of bundled alert files (JSON, optionally Avro). |
ALERT_REPLAY_RATE |
2.0 |
Alerts/sec from the replayer. 50+ stresses backpressure. |
ALERT_INFLIGHT |
16 |
Concurrent in-flight triage jobs cap (semaphore). |
ALERT_FOLLOWUP_THRESHOLD |
0.85 |
Verdict score above which transient.followup is delegated. |
TRIAGE_BUDGET_USD |
0.001 |
Per-alert lease budget for the cheap triage stage. |
FOLLOWUP_BUDGET_USD |
0.02 |
Budget granted to a delegated followup job. |
NIGHT_TOTAL_BUDGET_USD |
10.00 |
Cumulative ceiling for the night; alert-source halts on exhaustion. |
USE_LOCAL_CATALOG_CACHE |
true |
Use the bundled SQLite catalog (else hit astroquery). |
LOCAL_CATALOG_DIR |
/catalogs |
Path to the SQLite cache. |
src/arcp_example/runtime/agents/triage.py— the cheap per-alert agent.src/arcp_example/runtime/agents/followup.py— delegated rich-output agent.src/arcp_example/runtime/tools/— registered tool callables.src/arcp_example/alert_source/main.py— replay producer.src/arcp_example/client/main.py— Textual dashboard.
pip install -e ".[dev]"
pytest tests/ -qThe suite exercises the crossmatch tool, both agents (with stubbed inference), idempotency-key derivation, the alert-source semaphore, and the verdict schema. Runs in well under 30 seconds.
make verifyBuilds the image without starting any container, proving agentruntimecontrolprotocol@${ARCP_SDK_VERSION} and the science stack install cleanly on python:3.12-slim.