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_id parameter 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.