Runs and events
Two records: a run and the events inside it.
Run
One execution of an agent.
| Field | Notes |
|---|---|
id |
Client-generated, run_<region>_.... See Identifiers. |
agent |
Slug. |
started_at / received_at |
Client clock / server clock. |
status |
running, success, failed, timeout, cancelled. |
finished_at |
Set on close. |
customer_id, feature |
Optional attribution tags. |
parent_run_id |
Optional, for cross-agent traces. |
idle_timeout_seconds |
Optional. Close the run automatically once it has been quiet this long; also makes it resumable. |
Re-finishing a terminal run returns the existing record idempotently.
A finished run is normally final: a late event on a completed job is out-of-order
delivery, and it lands without changing the run's status. The exception is a run that
opted into the idle sweep with idle_timeout_seconds. That run's ending was a guess -
nobody told us it was over, we stopped waiting, so an event newer than the finish
reopens it and the turns join the ones already there. That is what lets someone come
back to yesterday's chat and continue it rather than starting a second record. See
Conversations.
Event
A discrete observation inside a run. SDKs emit events automatically; you can append your own.
| Type | Source |
|---|---|
llm_call |
Auto-captured by the provider wrappers and framework hooks; report any other provider with run.event("llm_call", {...}) |
tool_call |
Auto-captured by the framework hooks (OpenAI Agents SDK, LangChain, Vercel AI SDK); report your own for anything else |
log |
run.event("log", {"message": "..."}) |
decision |
Explicit branching points |
score |
Self-reported quality, passed as scores on run.finish(...) |
Each event has a client-generated id (evt_<region>_...), timestamp, type, and JSON data payload.
Lifecycle
run start ─► event ─► event ─► event ─► run finish
│ │
└────── server stamps received_at ───────────────┘
The SDK constructs run.id locally before any network call. Log it, pass it to child processes, include it in your own errors immediately. Events can be sent in any order.
with agentping.run("support-triage", customer_id="acme-corp") as run:
run.event("log", {"message": "classifying ticket"})
decision = classify(ticket)
run.finish(scores={"confidence": decision.confidence})
return decision
Parent runs
When agent A spawns agent B, set the parent so traces link across processes:
AGENTPING_PARENT_RUN=run_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b6(env var, read on init)parent_run_idparameter on the child run
A failure on a child surfaces the root run too: a five-step pipeline that breaks at step four shows up as one incident, not four.
Heartbeats
A heartbeat is a run with started_at == finished_at and no events. Same table, same dashboard, same alerts. See Heartbeats.