Concept
Outcomes
A single agent run is rarely a single event. It plans, calls a tool, reasons, calls another tool, and finishes. StillRunning groups all of those pings, everything sharing one traceId, into one outcome: the logical execution, with its status, duration, and total cost rolled up. You watch executions, not just pings.
The model
Every ping can carry a traceId. Pings with the same traceId belong to the same outcome. The SDKs set it for you; raw callers send it in the body, a ?traceId= query param, or an X-StillRunning-Trace-Id header.
Outcome tr_9f3a success 48.2s $0.142 2 models
│
├─ 1 start — — plan: research + draft + review
├─ 2 log 8.2s $0.031 gpt-4o gathered 12 sources
├─ 3 log 22.4s $0.082 claude-opus-4 drafted 1,800 words
└─ 4 success 17.6s $0.029 claude-opus-4 review passed, publishedThe outcome's status is failed if any step failed, else success once a step succeeds, else running. Its duration is the wall-clock span of the chain, and its cost, tokens, and tool calls are the sum across every step.
How grouping happens
Grouping is on the write path, not a nightly batch. Each ping recomputes its outcome from the steps of its trace, so the Outcomes view is always current and there is no aggregation lag. Workflows that send no traceId (a plain cron heartbeat) have no outcomes, their pings are their runs, and they show up in the run history unchanged.
Getting traces into your pings
The easiest path is an SDK, they generate a traceId per run and let you group several calls under one with withTrace / with_trace:
import { stillrunning, withTrace } from 'stillrunning-vercel-ai-sdk'
const sr = stillrunning()
await withTrace(async () => {
await sr.generateText({ model, prompt: 'plan' })
await sr.generateText({ model, prompt: 'execute' })
}) // both steps -> one outcomeSee the Vercel AI SDK, Python, and Node SDK docs for setup.