Skip to main content
An LLM span tells you what a model call cost and how long it took. It doesn’t tell you whether the call actually mattered — whether the ticket it answered stayed closed, whether the refund it prevented was real. Outcomes close that gap: your own backend reports a business result and attributes it to the trace that produced it, and Zespan joins the two so the Value dashboard can show cost-per-success and value-per-dollar broken down by agent and model, not just cost-per-call.
Currently TypeScript-only. The Python SDK does not yet expose an outcome() method. Python customers (and anyone reporting from a non-SDK backend) call POST /v1/ingest/outcomes directly — see Reporting without the TypeScript SDK below. Field names on the wire are the same camelCase (valueUsd, traceId) either way.

Why out-of-band

Most outcomes aren’t known when the trace runs. An agent answers a support message; whether that ticket actually stayed resolved is only known when it doesn’t reopen a few hours later, or a refund is only “avoided” once the customer’s session ends without one. outcome() is built for that gap: it’s a direct, one-shot call your backend makes whenever the result becomes known — a webhook handler, a nightly reconciliation job, a support-desk close event — not something you call inside the same request that produced the trace. Because of that, an explicit traceId is the common case, not the exception. outcome() also works from inside an active trace (it falls back to the current trace context when traceId is omitted), but most real call sites are in a different process, minutes or hours after the trace that they’re describing has already finished.

Reporting an outcome

outcome() lives on the client, the same place checkGuardrails() and datasets do — get it from zespan.getClient() (or construct/import a ZespanClient directly) rather than calling it on the zespan object itself.

OutcomeInput fields

string
required
A short label for what this outcome measures — "ticket_deflected", "refund_avoided", "sla_met", or any other string your team uses consistently. There’s no fixed enum; kind is whatever your business tracks, and it’s what the Value dashboard groups and filters by. Max 100 characters.
boolean
required
Whether this outcome was actually achieved. A deflected-ticket check that failed (the ticket reopened) is still worth reporting — send success: false rather than skipping the call — since a kind’s success rate is only meaningful if failures are reported too.
number
The dollar value of this outcome, if you can put a number on it (an avoided refund’s amount, an estimated support-cost saving). Omit it entirely rather than passing 0 for “unknown” — omitted, it’s left out of the request body rather than sent as null, and the dashboard’s value/cost ratios treat “no value reported” differently from “reported as zero.”
Record<string, string | number | boolean>
Free-form key-value metadata for this outcome. Values are coerced to strings before the request is sent — attributes: { ticketId: 4821, tier: "gold" } is transmitted as { "ticketId": "4821", "tier": "gold" } — because every other Zespan SDK surface treats span/event attributes as strings, and this keeps outcomes consistent with that rather than introducing a second, wider attribute type.
string
The trace this outcome is attributed to. Explicit traceId always wins over an active trace, even if outcome() happens to be called from inside one — see Resolving traceId below. Required in practice: outcome() throws synchronously if it can’t resolve one.
string
The session this outcome belongs to, if your traces are grouped into multi-turn sessions. Purely descriptive — not used to resolve traceId.
string
The agent that produced the trace, if known. Populating this is what lets the Value dashboard’s By agent breakdown attribute the outcome correctly; an outcome reported with no agentName still counts toward totals but groups under an empty agent name.

Resolving traceId

An explicit input.traceId is always used as-is, never overridden by an active trace even if one exists. When traceId is omitted, outcome() falls back to the trace currently active in this process (the same context withAgent and the LLM wrappers read). If neither is available, outcome() throws synchronously — it never silently no-ops or sends a request with a missing traceId.
A trace doesn’t need to exist in Zespan yet for its outcome to be accepted. Outcomes and traces are joined at query time, not at write time, so an outcome that arrives before its trace has finished ingesting is stored and joined correctly once the trace shows up. This is the normal shape for the out-of-band case, not an edge case to work around.

Response and errors

On success, outcome() resolves to { accepted: number }1 for the single outcome this call sent. On a non-2xx response from the API, it throws an Error whose message includes the HTTP status and, when the server returned one, a truncated response body — for example, a kind over the 100-character limit fails with a 400 and the server’s validation message.
A rejected outcome() call throws rather than resolving to a failure object, so wrap it in try/catch (or let it propagate) the same way you would any other awaited network call — there’s no { ok: false } shape to check instead.

Reporting without the TypeScript SDK

Python customers, and anyone reporting from a backend that isn’t a Zespan SDK at all, call the ingest endpoint directly. It’s the same endpoint outcome() wraps — see the full request/response contract in the Outcomes group of the API Reference.
Python
Field names on the wire are camelCase (valueUsd, traceId, sessionId, agentName) even from Python — this is the raw ingest API’s shape, not a Python-idiomatic wrapper, since no Python SDK method exists yet.

Next steps

  • Value — the dashboard page this data powers, with per-agent and per-model breakdowns
  • API Reference — the Outcomes group covers the full request/response contract for POST /v1/ingest/outcomes and the two read endpoints
  • Agent tracingwithAgent, for producing the traces you’ll later attribute outcomes to