Runback / Documentation

Documentation

Everything you need to instrument an agent, replay an incident, write a policy, and ship with confidence. Covers all editions — Community (free), Starter, Growth, Scale, Pro, and Enterprise.

Tier badges mark which edition a feature requires. See pricing →
Docs

Wrapping one agent takes three lines. Here's exactly how — SDK, OpenTelemetry, policies, self-hosting, and the full API.

How a team rolls this out

Overview

Runback is an observability and governance platform for AI agents. It captures every LLM call, tool call, and agent step — then lets you re-run any decision from the exact captured context, enforce policies, run regression tests, and export a tamper-evident audit record.

runback.dev/app — Fleet overview dashboard
Fleet overview dashboard

The platform has five layers:

1 · Capture

Instrument with the Runback SDK or send traces via OpenTelemetry. Every span is stored in a Postgres database you own.

2 · Replay

Re-run any step from the exact captured context — tools, retrieval, messages[] held fixed. Root-cause in minutes.

3 · Gate

Write policies as JSON rules. They evaluate on every run in real time and can gate your CI pipeline.

4 · Audit

Any run exports as a signed, re-executable audit record. Deterministic — no model calls required.

5 · Govern

Approvals, incidents, trust chains, and regulatory mappings for teams operating in regulated environments.

Quick start (5 minutes)

Community

The fastest path: the managed hosted tier. You get a live dashboard in under 5 minutes without running any infrastructure.

Who does what. Steps 1 and 5 are for whoever owns the account — creating it, generating a key, and reading the result in the dashboard. Steps 2 to 4 need someone who can run code: they install a package and add three lines to your agent, which takes a developer roughly fifteen minutes. If that is not you, send them this page — everything they need is on it, and once it is done the dashboard, replay, policies and audit export are all usable without writing anything.
1
Create a free account — go to runback.dev/get-started and enter your work email. You'll receive a magic link. Once signed in, open Settings → API Key (SDK) and click Generate API key.
runback.dev/app — Settings → API Key (SDK)
Settings → API Key (SDK)
You are asked to choose a scope. Telemetry only is the right answer for the steps below and for anything that ships inside an application or container image — it can send run data and nothing else. Pick Full API access only where the CI release gate, step replay or deep replay actually run. The key is shown once, so copy it now.
2
Install the SDK
bash
npm install @runback/sdk ai

# "ai" is a peer dependency — the snippet in step 4 imports from it, so
# installing @runback/sdk alone fails with ERR_MODULE_NOT_FOUND.
# Not using the Vercel AI SDK? Install just @runback/sdk and see the tip below.

# Prefer zero-install? "Send a run with cURL" and "OpenTelemetry" below both
# work with nothing added to your project.
Not on the Vercel AI SDK? Import from @runback/sdk/core and use startRun() — same recording, no ai peer dependency to install. See Manual recording.
3
Set your key and point the SDK at Runback
bash
export RUNBACK_API_KEY=rb_live_your_key_here
export RUNBACK_INGEST_URL=https://runback.dev   # self-hosted? use your own origin
RUNBACK_INGEST_URL is required on the managed service. The SDK defaults to http://localhost:3000 so that it can never phone home from a self-hosted or air-gapped deployment — which means that without this line your events go to localhost and nothing is recorded.
4
Wrap your model call
typescript
import { withDebugger } from "@runback/sdk";
import { generateText, stepCountIs } from "ai";

const dbg = withDebugger(model, {
  runName: "support-agent",
  apiKey: process.env.RUNBACK_API_KEY,
  redact: "standard",
});

const result = await generateText({
  model: dbg.model,
  tools: dbg.tools(myTools),
  stopWhen: stepCountIs(10),
  prompt: userMessage,
});

// finish() resolves with whether the run actually reached Runback.
// Check it — an audit trail you did not confirm is not an audit trail.
const rec = await dbg.finish({ output: result.text, status: "success" });
if (!rec.ok) console.error("[runback] not recorded:", rec.error);
5
Run your agent once — then open your runs list. It appears within seconds. If it does not, dbg.finish() in the snippet above returns why.

Prefer a one-liner? This installs, configures and sends a synthetic test run, so you can see the whole path working before touching your own agent:

bash
curl -fsSL https://runback.dev/api/quickstart | RUNBACK_API_KEY=your_key bash

Your first run

Once a run lands in your dashboard you can:

  • Click into it — opens the time-travel debugger. Scrub the slider to any step.
  • Switch to Inspect mode — see the raw request/response for any LLM or tool call.
  • Download the audit record — a signed, re-executable bundle. Verifiable at runback.dev/verify.
runback.dev/app — Runs list — every agent decision captured
Runs list — every agent decision captured
The run detail page shows a one-time onboarding guide on first visit. Click Got it to dismiss it and access the full debugger.

Runs & spans

A run is one end-to-end execution of an agent — from the initial prompt to the final output. A span is a single step within that run: either an LLM call or a tool call.

ObjectWhat it isKey fields
RunOne agent executionrun_id, name, status, input, output, started_at
LLM spanOne model callmodel, messages, response, tokens, latency_ms
Tool spanOne tool calltool_name, input, output, error, policy_block
Audit recordSigned bundle of all spanscontent_digest, signature, re-executable
runback.dev/app — Run detail — trace view with policy block
Run detail — trace view with policy block

Span IDs are stable — the same span replays identically every time, from the recording. No model call required.

Every figure in the app is a count over these runs, and opens into them. Click a number — failed runs on the Overview, a model's error rate on Model attribution, the error count on a compliance report — and you land on exactly the rows behind it, with a line stating what was filtered to. The run list takes those filters directly:

QueryShows
/app/runs?status=errorRuns that failed. Also success and running.
/app/runs?agent=billing-agentOne agent, by its recorded name.
/app/runs?model=gpt-4oRuns attributed to a model — its first LLM call decides, the same rule cost attribution uses.
/app/runs?since=2026-09-01Runs created on or after an instant, which is what a period-scoped figure means.
Filters are applied as database predicates, not by filtering a page of results after the fact, so a filtered list is bounded the same way an unfiltered one is and the count you clicked is the count you get.

The whole loop — one incident, end to end

The sections below document each capability on its own. This one shows how they fit together, because that is the part a reference cannot tell you: what you actually do on the day an agent gets something wrong.

Everything here follows one incident — an agent issued a refund it should have escalated — from the moment it happened to the record you hand an auditor.

1
It is already recorded. You did nothing after the fact — the SDK captured every LLM call, tool call and environment read as the agent ran. Open /app/runs?status=error and the failing run is there with its spans. Your first run
runback.dev/app — Runs — the failing run is already there
Runs — the failing run is already there
2
See what the agent saw. Time-travel to the step before it went wrong and read the exact messages[] the model was given — not a summary, the context. Deterministic, offline, no model calls. Replay
runback.dev/app — Time travel — scrub to the step before it went wrong
Time travel — scrub to the step before it went wrong
3
Ask whether another model would have done better. Step replay re-runs that one call on a different model. Deep replay re-runs the whole agent, holding every recorded tool output fixed and going live only where the new model diverges — so you learn what it would actually have done, and where it forked. Deep replay
runback.dev/app — Replay diff — which decisions changed, and where it forked
Replay diff — which decisions changed, and where it forked
4
Write the rule that would have stopped it — then simulate it against real history before it goes live, so you know exactly what it would have blocked and what it would have cost you. Policies
runback.dev/app — Policies — the rule, and what it would have blocked
Policies — the rule, and what it would have blocked
5
Make it permanent. Save the failing step as an eval so the incident can never ship again, and gate your CI on it. The corpus miner turns incidents into adversarial tests automatically. Evals & CI gate · Auto-mined tests
runback.dev/app — Golden corpus — the incident, now a test that must keep passing
Golden corpus — the incident, now a test that must keep passing
6
Produce the record. The run was sealed into a hash chain the moment it happened. Export it and anyone can verify it — offline, with npx @runback/verify, without an account and without trusting us. Signed audit records · The audit ledger
runback.dev/app — Audit ledger — verify the chain, with or without us
Audit ledger — verify the chain, with or without us
Steps 1, 2, 4, 5 and 6 work on every plan, including Community. Step 3's deep replay is Enterprise; step replay covers the same question one call at a time on any plan.

Nothing above requires you to have predicted the incident. That is the point of recording everything at decision grain: the evidence is already there when you need it, and the fix becomes a test rather than a memory.

Replay

Runback has two kinds of replay:

Time-travel replay

Rewind any run to any step. The full agent state — every prior tool output, every message in context — is reconstructed from the recording. Deterministic, offline, no model calls.

Community+
Step replay

Re-execute a specific LLM call with a different model or prompt. Runback freezes the recorded inputs and makes a fresh call to the model you choose. Use it to bisect which model change caused a regression.

Community
Deep replay

Re-run your whole agent against a recorded run on a different model. Every recorded tool output and environment read is served back, so everything upstream stays fixed — and the replay goes live only where the new model actually diverges. Answers “what would this agent have done?”, not just “what did this one call return?”. See Deep replay.

Enterprise
runback.dev/app — Time-travel replay — scrub any step
Time-travel replay — scrub any step

Time-travel controls:

  • Drag the slider to scrub to any step
  • / step backward and forward one span at a time
  • Click any span in the transcript to jump directly to that step
  • Switch to Inspect mode to see the raw request/response JSON for any step

Whole-run replay on another model goes further than one step. It re-runs the entire recorded run against a candidate, reusing tool outputs from the cassette by content, and continues past the first divergence instead of stopping there — so you see every decision that would change, not just the first.

The result reads as a diff. The leading run of identical decisions is stated once and stepped over; the fork opens itself; each diverging step names what actually changed — "now answers directly instead of calling issue_refund" — rather than leaving you to compare two blobs. A step whose tool has no recorded output is marked as needing a live call, because everything after it is what the candidate would do, not what it did.

runback.dev/app — Whole-run replay — the fork, step by step
Whole-run replay — the fork, step by step

Step replay is for:

  • Verifying that switching to a cheaper model doesn't change behaviour on historical incidents
  • Bisecting which model or prompt change caused a regression
  • Building a counterfactual — “what would the agent have done if I'd used GPT-4o mini here?”
Bisect compares the model's DECISION at each candidate — which tool it calls, with what arguments — against the recorded run. It does not re-run your tools, so a regression caused by a changed tool or environment response, not the model's choice, is outside what this search can find.

Deep replay — re-run the whole agent

Enterprise

Time-travel and step replay work on what was recorded. Deep replay answers a different question: what would this agent have done on a different model, given exactly the same world?

You cannot answer that by re-running the agent live — every downstream prompt would change, because the agent builds its later inputs from its earlier outputs. And you cannot answer it by replaying the recording, because a recording only ever replays itself. So replayRun() does both at once:

  • Hit — the new model asks for something the recording already has. The recorded output is served, and your tool is not called. Everything upstream is held exactly fixed, with no live side effects.
  • Miss — the new model asks for something different. That is a genuine divergence, so execution goes live from that point, and the boundary is recorded.

The result is not a pass/fail but a frontier: how far the run reproduced identically, and the exact step where behaviour forked.

ts
import { replayRun } from "@runback/sdk";

const { value, outcome } = await replayRun({
  runId: "run_abc123",
  model: "claude-sonnet-4-6",        // the candidate you are testing
  apiKey: process.env.RUNBACK_API_KEY,
  ingestUrl: "https://runback.dev",

  // Your agent, with its tools wrapped so replay can intercept them.
  // deps.model is the candidate; deps.tool wraps each tool you call.
  agent: ({ tool, model }) =>
    myAgent(model, {
      search: tool("search", liveSearch),
      refund: tool("refund", liveRefund),
    }),
});

console.log(outcome.reproducedPrefix);  // e.g. 7 — identical through step 7
console.log(outcome.frontier);          // { kind: "llm", key: "...", name: "..." } or null
console.log(outcome.digestMatch);       // true = byte-exact reproduction, zero misses
runback.dev/app — The same result in the app — reproduced prefix, and the forking step
The same result in the app — reproduced prefix, and the forking step

What the outcome tells you

FieldMeaning
reproducedPrefixConsecutive hits before the first miss — "identical through step N".
frontierThe first divergence, or null if the run never diverged.
hits / missesHow much was served from the recording versus run live.
provenanceEvery read in order, each marked recorded, live or blocked.
digestMatchTrue only if the served stream and its order match exactly, with zero misses — byte-exact reproduction.
allRecordedConsumedWhether every recorded value was used. False means the candidate skipped a step the original took.
replayEvents() takes an array of events instead of a run id, so it needs no network at all. Use it in unit tests to pin agent behaviour against a fixture.
Deep replay re-executes your agent code in your process, so it cannot run on our servers — which is why it lives in the SDK. Fetching the cassette requires the deep_replay entitlement: GET /api/runs/:id/cassette returns 403 on other plans. Time-travel and step replay are available on every plan.

A tool that runs live on a miss will have real side effects. Pass onMiss to intercept divergences — log them, block them, or fail the replay — if your tools are not safe to re-run.

Policies

A policy is a JSON array of rules evaluated against every run. A rule is either assert (a predicate that must always hold) or require (an antecedent when that, if it fires, makes a consequent then mandatory) — both block the run if violated. Predicates read tool calls, their arguments, and input/output text — never a fuzzy judge, an exact and repeatable verdict.

json
[
  {
    "id": "no-large-disputed-refund",
    "kind": "require",
    "description": "Disputed refunds over $100 must be escalated, not auto-issued",
    "when": {
      "op": "and",
      "all": [
        { "op": "tool_arg", "tool": "issue_refund", "path": "amount", "cmp": "gt", "value": 100 },
        { "op": "input_matches", "pattern": "disputed" }
      ]
    },
    "then": { "op": "tool_called", "tool": "escalate_to_human" }
  }
]
runback.dev/app — Policies — governance as code
Policies — governance as code

Rule fields:

FieldValuesDescription
kindrequire / assertrequire = block the run if triggered. assert = flag a violation but let the run continue.
ontool_call / llm_call / run_endWhich event type triggers evaluation.
whenJSON condition objectField path, operator, and value. Supports and / or / not nesting.
actionblock / flag / alertblock stops execution. flag records a violation. alert also fires your alert rules.

Operators: eq ne gt gte lt lte contains starts_with exists

Policies are versioned. Simulate a new policy against your run history before activating it — no live traffic needed.

Coverage gaps

A deterministic gate only catches what you've written a rule for. Coverage-gap analysis answers the honest follow-up question — which tools has nothing written a rule for at all — by scanning the last 90 days of real tool calls and flagging every one that no active rule's tool_called or tool_arg predicate even names. Not "a rule exists and didn't fire": there is nothing that could have blocked a bad call to it, even in principle. Ranked by call volume, so the biggest live blind spot sorts to the top.

runback.dev/app — Coverage gaps — tools nothing is watching, ranked by call volume
Coverage gaps — tools nothing is watching, ranked by call volume

Evals & CI gate

Community

Evals test your agent on a fixed dataset. The CI release gate fails a build if a new model or prompt causes a regression against your golden tests.

runback.dev/app — Evals — run your agent against a dataset
Evals — run your agent against a dataset
1
Create a dataset — a list of input/expected pairs in your dashboard.
2
Run evals via the CLI or API — Runback executes your agent against each input and scores the output.
3
Add the release gate to CI. It fails the build if the eval score drops below your threshold.
yaml
# .github/workflows/ci.yml
- name: Runback eval gate
  env:
    RUNBACK_API_KEY: ${{ secrets.RUNBACK_API_KEY }}
  run: |
    # Run the eval — this blocks until scoring finishes and returns its id.
    EVAL_ID=$(curl -fsS -X POST https://runback.dev/api/evals \
      -H "authorization: Bearer $RUNBACK_API_KEY" \
      -H "content-type: application/json" \
      -d '{"dataset_id":"YOUR_DATASET_ID"}' | jq -r .eval_id)

    # Ask for the release-gate verdict and fail the build if it did not pass.
    # verdict is "pass" | "warning" | "fail" | "no_data".
    curl -fsS "https://runback.dev/api/evals/$EVAL_ID/gate" \
      -H "authorization: Bearer $RUNBACK_API_KEY" \
      | jq -e '.verdict == "pass"'
The verdict is pass, warning, fail, or no_data, measured against your org's thresholds over trusted items only — production-captured runs and human-approved scenarios. The gate is most powerful when seeded by production incidents — see Golden corpus below.

Golden corpus

Growth

The golden corpus automatically mines your production incident runs into regression tests. Every run that triggered a policy violation or was marked as a failure is surfaced for review and approval as a golden test.

runback.dev/app — Golden corpus — incidents auto-mined into tests
Golden corpus — incidents auto-mined into tests
1
Runback identifies runs that triggered a policy violation, had a non-zero error count, or were manually flagged.
2
They appear in Golden in your dashboard. You inspect, label, and approve (or reject) each candidate.
3
Approved candidates are added to your golden dataset. The CI gate runs against them on every build.
4
Each new incident adds more tests. Coverage compounds without manual effort.
Golden tests compound. After 30 days of production usage, most teams have 50–200 real edge-case tests they never had to write.

Auto-suggested rules

An uncovered incident shouldn't depend on someone remembering to go write a rule for it. When a still-open golden entry traces back to a specific tool call that threw, Runback drafts a candidate policy rule right there — a starting point for review, never something applied automatically. It's stated plainly when the draft is blunt: the rule language has no predicate for "only when it errors like this one did," so the draft blocks every future call to that tool unconditionally until a human narrows it.

runback.dev/app — Suggested rule — drafted from an uncovered incident, not yet active
Suggested rule — drafted from an uncovered incident, not yet active

Prompts

Growth

A versioned registry for the prompts your agents run — never a string hardcoded in your codebase. Every save is a new immutable version; a label (production, staging, or anything you name) points at the version your agents actually fetch, so you can edit and test without touching what's live.

runback.dev/app — Prompts — versioned templates, movable labels
Prompts — versioned templates, movable labels
1
Write a template — a JSON array of { role, content } messages with {{variables}} — and save it under a name.
2
Test it in the playground against real variable values, and against more than one model side by side, before it ships.
3
Move the production label to the version you approved. Fully audited.
Moving the production label requires the admin role, enforced server-side — every other role can save a new version but can't promote it live.

Approvals

Starter

A human-in-the-loop review queue for high-stakes agent decisions. There's no policy-rule field that routes a decision here automatically — it's explicit: your own agent code calls the API at the point you want a human to sign off, typically right before a policy-flagged action would otherwise run.

How it works

Your call to POST /api/approvals creates a pending approval. Approvers (team members with the approve permission) see it in Approvals and can approve or reject with a note. The decision is sealed into the run record.

The audit trail

Every approval decision (who decided, when, with what note) is appended to the run's tamper-evident record. The chain covers both the agent's decision and the human decision.

typescript
// Request approval from your own agent code, at the point you want a human decision
POST /api/approvals
{
  "run_id": "run_abc123",
  "policy_name": "no-large-disputed-refund",
  "rule_desc": "Disputed $250 refund — requires senior review"
}

// Get pending approvals
GET /api/approvals?status=pending
This is a manual integration point, not an automatic one — nothing in Runback watches for a policy violation and calls this for you. Wrap the call around the specific tool calls you want gated.

Anomalous — flagged for review

Deliberately separate from the deterministic gate above: a statistical-outlier score is a judgment call, and a judgment call should never be the thing that blocks a run. This section scores each tool's recent calls against that same tool's own history — a numeric argument more than 3 standard deviations from baseline gets surfaced here, nothing more. No rule matched, nothing was blocked; it's a review signal, not a verdict. Tools with no policy coverage at all sort first, since an anomaly on a tool nothing else is watching is the bigger gap.

runback.dev/app — Anomalous — a statistical outlier flagged for human review, not blocked
Anomalous — a statistical outlier flagged for human review, not blocked

Incidents

Growth

An incident is a structured record of an agent failure or policy breach — separate from the run itself. Incidents track status (open → investigating → resolved), timeline, and linked runs.

runback.dev/app — Incidents — auto-RCA from a policy block, no log digging
Incidents — auto-RCA from a policy block, no log digging
StatusMeaning
openIncident created, not yet assigned or being investigated
investigatingA team member is actively looking into the root cause
resolvedRoot cause identified, fix deployed, golden test added
typescript
// Create an incident from a run
POST /api/incidents
{
  "run_id": "run_abc123",
  "title": "Refund agent bypassed $100 limit",
  "severity": "high"
}

// Update status
PATCH /api/incidents/:id
{ "status": "resolved", "resolution": "Policy updated, golden test enrolled" }

From any run detail page, use the Open incident button to create a linked incident in one click. The incident page shows the full run, the policy violation, the approval history, and the timeline.

Alerts

Growth

An alert rule watches the fleet and fires the moment something crosses a line, so the first you hear of a bad deploy is not a customer. Rules are evaluated against captured runs, not sampled metrics.

TriggerFires when
Run failureAny run ends in error.
Error rateThe failure rate over a rolling window crosses your threshold.
Cost spikeSpend over a rolling window crosses a dollar threshold.

Each rule routes to email, Slack, or any webhook — PagerDuty and Opsgenie both accept one. Deliveries are deduped, so one incident does not become a hundred pings, and the Alerts page shows the last delivery per rule including failures, so a webhook that has quietly stopped accepting posts is visible rather than assumed healthy.

An alert tells you something happened; an incident is where the root cause and timeline live. A rule firing on a policy block is usually worth opening as an incident, which carries the run with it.

Inter-agent trust chain

Pro

In multi-agent systems, a subagent receiving instructions from an orchestrator has no cryptographic proof the orchestrator is who it claims to be, or that the scope of the delegation hasn't been widened in transit. Runback's trust fabric seals every delegation edge.

Delegation tokens

Every orchestrator→subagent call produces a signed attestation — Ed25519 wherever this deployment has a keypair configured, the same signature the per-run audit record uses, HMAC-SHA256 fallback otherwise. The signed payload carries the calling agent, called agent, depth, permitted scope, and a hash of the parent attestation.

Chain verification

The full chain from root to leaf exports as a signed artifact. POST it to /api/trust/verify — or re-derive the signature yourself against our published key. No Runback account needed.

typescript
// Issue a delegation token (orchestrator → subagent)
POST /api/trust/chain
{
  "parent_run_id": "run_orchestrator_abc",
  "child_agent": "kyc-subagent",
  "scope": "read:customer",
  "depth": 1
}

// Verify the full chain
POST /api/trust/verify
{
  "chain": [ /* array of attestation tokens */ ]
}

// Response: { valid: true, depth: 2, root: "loan-orchestrator", ... }
Trust chain signing uses the same key as the audit record: Ed25519 via AUDIT_ED25519_PRIVATE_KEY where configured — asymmetric, independently verifiable offline against the published public key — HMAC-SHA256 via AUDIT_SIGNING_KEY as a fallback. Self-hosted deployments need at least one of the two set.

Vercel AI SDK

Community

The deepest integration — full context capture, in-process PII redaction, step replay, and typed tool wrappers. About 3 lines of change to an existing agent.

typescript
import { withDebugger } from "@runback/sdk";
import { generateText, stepCountIs } from "ai";

// 1. Wrap your model and tools
const dbg = withDebugger(model, {
  runName: "customer-support",
  apiKey: process.env.RUNBACK_API_KEY,
  redact: "standard",      // false | "standard" | "strict"
  tags: { env: "prod" },
});

// 2. Use dbg.model and dbg.tools — drop-in replacements
const result = await generateText({
  model: dbg.model,
  tools: dbg.tools(myTools),
  stopWhen: stepCountIs(10),
  prompt: task,
});

// 3. Finish the run
await dbg.finish({ output: result.text, status: "success" });

// On error:
// await dbg.finish({ status: "error", error: err });

Redaction levels:

LevelWhat it strips
noneNothing — full fidelity capture
standardEmail, SSN, credit card numbers, and API keys/secrets (Anthropic, OpenAI, Groq, Stripe, GitHub, Slack, Google, AWS, JWTs) — high-confidence patterns only
strictAll of standard + phone numbers and IPv4 addresses — noisier patterns, more false positives
This is pattern-based redaction, not a names/addresses NER model — it matches structured formats (an email shape, a card-number shape), not free text. It will not catch a name or street address written in a sentence. For that, pass customPatterns or a full redactor callback in the SDK config.

Manual recording (any agent loop)

Community

Not on the Vercel AI SDK? startRun() records the same spans from any TypeScript or JavaScript agent loop — a raw provider SDK, your own orchestration, anything. Nothing is wrapped; you call it where the work happens.

typescript
import { startRun } from "@runback/sdk/core";

const run = startRun({ runName: "support-agent", input: task, redact: "standard" });

// Record each model call
run.llm({
  model: { provider: "openai", model_id: "gpt-4o" },
  request: { system: systemPrompt, messages },
  response: { text: completion, finish_reason: "stop" },
  usage: { input_tokens: 412, output_tokens: 88, total_tokens: 500 },
  latencyMs: 940,
});

// …and each tool call
run.tool({ toolName: "lookup_customer", toolCallId: "t1", input, output, latencyMs: 44 });

// Optional: capture intermediate reasoning
run.reasoning("Disputed charge over the limit — escalating");

await run.finish({ output: answer, status: "success" });
Records produced this way are identical to the wrapped path — same hash chain, same signed audit export, same replay. The only difference is that you choose the call sites.

Go (preview)

Community

The Go SDK ships in the Community repository at packages/sdk-go. It is not yet published as a Go modulego get github.com/letsRunback/runback-go will not resolve — so vendor it or add a replace github.com/letsRunback/runback-go => ./packages/sdk-go to your go.mod for now. It covers event capture, redaction, and ingest — the same manual-recording shape as above, called from a Go agent loop. Redaction is on by default (standard tier): emails, SSNs, credit cards, private key blocks, and common provider API keys/tokens are scrubbed from event content before it's sent. Every run also gets a real cassette_digest, computed the same way as the TS/Python SDKs — checked byte-for-byte against the TS implementation in CI.

go
import "github.com/letsRunback/runback-go/runback"

run := runback.NewRun(runback.Options{RunName: "support-agent", Input: task})

run.LLM(runback.LlmInput{
    Model:     runback.Model{Provider: "openai", ModelID: "gpt-4o"},
    Request:   runback.LlmRequest{Messages: []runback.ModelMessage{{Role: "user", Content: task}}},
    Response:  runback.LlmResponse{Text: runback.Ptr(completion), FinishReason: runback.Ptr("stop")},
    Usage:     &runback.TokenUsage{InputTokens: 412, OutputTokens: 88, TotalTokens: 500},
    LatencyMs: runback.Ptr(940),
})

run.Tool(runback.ToolInput{ToolName: "lookup_customer", ToolCallID: "t1", Input: input, Output: output})

run.Finish(runback.FinishInput{Output: answer, Status: "success"})
Preview, not full parity with the TS/Python SDKs: the digest proves tamper-evidence, but step-by-step replay/bisection of a Go-recorded run isn't available yet. Redaction covers the built-in standard/strict tiers only — custom patterns, key allow/deny lists, and a custom redactor callback (all available in the TS/Python SDKs) aren't ported yet.

OpenTelemetry (any framework)

Community

Any framework that emits OpenTelemetry GenAI spans works with Runback. Set three environment variables and your traces flow in automatically.

bash
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://runback.dev/api/otel/v1/traces
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_TRACES_HEADERS=authorization=Bearer <RUNBACK_API_KEY>
Replace RUNBACK_API_KEY with the key from your dashboard. Self-hosted deployments use your own base URL instead of runback.dev.
Redaction on this path runs server-side, not in-process. The Vercel AI SDK and Python/LangGraph collectors redact before anything leaves your process — the strongest guarantee. Every OTel-fed integration (this page, Python OpenAI/Anthropic, LangChain/LangGraph/CrewAI/LlamaIndex, OpenAI Agents SDK, Google ADK) instead sends raw OTLP spans over the network to Runback first, which then applies the same standard-tier redaction before storing or displaying anything. If sending unredacted content over the wire to us is not acceptable for your data, use a first-party collector instead.

Gateway proxy — any language, no SDK

Community

Point your agent's API base URL at a local gateway process instead of the model provider. Calls flow through to the provider and are captured on the way — your agent's source does not change, and it needs no Runback library, so this is the path for languages the SDKs do not cover.

This runs from source today — it is not on npm. @runback/gateway is not a published package, so there is nonpm install for it: you clone runback-community and run it from the repo. It also needs a terminal and a long-running server process. "No SDK" means no library inside your agent — it does not mean no engineer.
bash
# record: calls flow to the provider and are captured on the way through
npx tsx node_modules/@runback/gateway/bin/gateway.ts \
  --mode record --port 8888 \
  --upstream https://api.openai.com \
  --cassette baseline.cassette.json

# then point your agent at it — e.g. OPENAI_BASE_URL=http://localhost:8888

Replay mode serves the recorded responses with no upstream at all, and exits non-zero if the agent diverges from the recording — which is what makes it usable as a CI gate for an agent written in any language.

bash
npx tsx node_modules/@runback/gateway/bin/gateway.ts \
  --mode replay --port 8888 --cassette baseline.cassette.json
By default the gateway forwards whatever credential your agent sends, so the same key also works against the provider directly. Set RUNBACK_GATEWAY_UPSTREAM_KEY and RUNBACK_GATEWAY_TOKEN to keep the real credential inside the gateway process — see Security, "Gateway credential isolation". Secrets are read from the environment, never from argv, because a process's command line is readable by anything else on the host.

Python — OpenAI / Anthropic

Community

Use OpenLLMetry (Traceloop) to auto-instrument the official Python SDKs.

bash
pip install traceloop-sdk
python
from traceloop.sdk import Traceloop

Traceloop.init(
    api_endpoint="https://runback.dev/api/otel",
    headers={"authorization": "Bearer " + os.environ["RUNBACK_API_KEY"]},
)

# All openai.chat.completions.create() and
# anthropic.messages.create() calls are now traced.

LangChain, LangGraph, CrewAI, LlamaIndex

Community

Use OpenInference or OpenLLMetry instrumentors, then point the OTLP exporter at Runback via the env vars above.

bash
# LangChain / LangGraph
pip install openinference-instrumentation-langchain opentelemetry-exporter-otlp-proto-http
# LangChainInstrumentor().instrument()

# CrewAI
pip install traceloop-sdk
# Traceloop.init(...) — CrewAI spans flow straight in

# LlamaIndex
pip install openinference-instrumentation-llama-index
# LlamaIndexInstrumentor().instrument()
Set the three OTEL_* environment variables in the same shell before running.

OpenAI Agents SDK

Community

The Agents SDK has an official OpenTelemetry instrumentation package that converts its native trace data — agents, tools, generations, guardrails, handoffs — into GenAI semantic-convention spans. Point it at Runback with the same env vars as every other OTel source.

bash
pip install openai-agents opentelemetry-instrumentation-openai-agents-v2
python
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.openai_agents_v2 import OpenAIAgentsInstrumentor

provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))  # reads OTEL_EXPORTER_OTLP_TRACES_* from env
OpenAIAgentsInstrumentor().instrument(tracer_provider=provider)

# Every Runner.run(...) call is now traced — including tool calls and handoffs.
Set the three OTEL_* environment variables above first. The exporter reads them automatically — no endpoint/protocol arguments needed in code.

Google Agent Development Kit (ADK)

Community

ADK emits standard OTLP spans natively — agent runs, tool calls, and model requests already follow the GenAI semantic conventions. No third-party instrumentor needed, just point the exporter at Runback.

bash
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://runback.dev/api/otel/v1/traces
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_TRACES_HEADERS=authorization=Bearer <RUNBACK_API_KEY>
Same three env vars as the generic OpenTelemetry setup above — ADK picks them up through the standard OTel SDK. See OpenTelemetry (any framework) for the full variable reference.

Environment variables

VariableRequiredDescription
RUNBACK_API_KEYYesYour API key. All keys are prefixed rb_live_.
RUNBACK_INGEST_URLYes*Where the SDK sends events — e.g. https://runback.dev (hosted) or your own origin (self-hosted). Defaults to http://localhost:3000, so *effectively required outside local dev — without it, events silently go nowhere.
RUNBACK_DEMO_MODENoSet to 1 to disable real model calls in replay/evals — used by this site's own public demo, and available on any deployment.

Governance coverage

Community

Every other number here is measured over the agents that already report. Coverage is the one that measures what does not — because an agent nobody instrumented is the one most likely to be a problem, and it cannot show up in a dashboard built from its own telemetry.

You declare the estate: the agents that exist, who owns each, and how critical it is. Coverage then measures what is actually reporting against that register rather than against itself.

StateMeaning
Under governanceDeclared and reporting. The only state that counts as covered.
Not instrumentedDeclared, never sent a run. The gap the register exists to surface.
Stopped reportingWas reporting, has gone quiet. Silence is not health.
UndeclaredSending runs but not on the register — nobody claimed ownership.
runback.dev/app — Coverage — tools nothing is watching, ranked by call volume
Coverage — tools nothing is watching, ranked by call volume

Coverage feeds the compliance report directly: "we govern our AI estate" is a claim an auditor can check against a number, and the number is derived from your own declarations rather than from ours.

Model attribution & the upgrade gate

Scale

Model attribution mines your own run history for per-model error rate, latency and token cost, so an upgrade decision is made against your workload rather than a public benchmark. A run is attributed to the model of its first LLM call — the same rule cost attribution uses, so the two screens never disagree about which runs a model owns.

Every model name opens the runs behind its numbers.

Upgrade gate

Run your golden suite against a candidate model before it reaches production. A regression fails the gate, with the failing cases named.

Model diff

Which agent classes behave differently across two models, with a severity and a plain-language reason for each divergence.

Drift

A model changing underneath you with no deploy on your side. Compares a current window against a historical baseline per agent.

Bisect

Binary-search an ordered candidate list for the exact change that flipped a run — log₂ probes, each one recorded as proof of the search path.

Bisect compares the model's decision at each step — which tool, with what arguments — against the recording. It does not re-run your tools, so a regression caused by a changed tool or environment response will not be found by this search.

Team & roles

Pro

One governed workspace rather than a shared login. Roles decide who can change a policy, approve a decision, or seal a checkpoint.

RoleCan
OwnerEverything, including billing and removing other owners.
AdminManage members, policies, alerts and checkpoints. Cannot remove an owner.
MemberUse the product: runs, replays, evals, approvals.
ViewerRead-only.

Every administrative act — a role change, a sealed checkpoint, an issued grant, a revoked session — is appended to a hash-chained admin audit log, visible under Activity. Who sealed a checkpoint is itself audit-relevant, so it is recorded with the same discipline as the decisions the product captures.

Sessions are revocable per member. Removing someone from the workspace does not by itself end an active session — revoke explicitly when offboarding.

Fleet topology & benchmarks

Scale

The Topology view renders your entire agent fleet as a live DAG — orchestrators at the top, subagents and tools at the leaves. Each node shows its call volume, error rate, and token spend. Click a node to drill into its runs.

Use topology to:

  • Identify which subagent is the source of a systemic failure
  • Spot unexpected delegation chains (orchestrator calling an agent it shouldn't)
  • See token spend distribution across the fleet at a glance
Topology updates in real time during a run — watch the flame graph as each agent executes.

Fleet benchmarks (Pro+)

The Benchmarks page compares your fleet's error rate, latency, and token spend against anonymised aggregate data from peer organisations in the same vertical. Percentile bands (p25, p50, p75, p95) let you see where your fleet stands relative to comparable deployments.

runback.dev/app — Fleet benchmarks — your agents vs. vertical peers
Fleet benchmarks — your agents vs. vertical peers
MetricWhat it shows
Error rate% of runs ending in error vs. peer p50/p75
Latency (p95)Your 95th-percentile run duration vs. peer distribution
Token spend / runAverage tokens consumed per run vs. vertical peers
Policy block rate% of runs blocked by governance rules vs. peer fleet
Peer data is anonymised and aggregated — no individual organisation's data is attributable. Opt-out is available in Settings → Data sharing.

Policy causes

Scale

The Policy causes page shows a heat map of which policy × agent pairs produce the most blocks. It answers: “which agent is firing this rule most, and is it getting better or worse?”

ColumnWhat it shows
Block rateFraction of runs blocked by this policy/agent pair
Trend↑ worse / ↓ better vs. prior 30-day window
Total blocksAbsolute count over the selected window

The heat map intensifies as block rate increases — zero is transparent, >20% is rose. Use it to identify policies that need rule refinement or agents that need prompt changes.

PII redaction

Community

On the Vercel AI SDK and Python/LangGraph collectors, redaction runs in-process — inside your application, before any data leaves to Runback; the raw values are never stored anywhere outside your process. Integrations that ship via OpenTelemetry redact server-side instead, after the raw spans reach us — see the note in OpenTelemetry for which integrations that applies to.

Standard redaction strips: email addresses, SSNs, credit card numbers, API keys, bearer tokens, and common secret patterns (Anthropic, OpenAI, Groq, Stripe, GitHub, Slack, Google, AWS, JWTs) — high-confidence, format-based matches. Strict adds phone numbers and IPv4 addresses, at the cost of more false positives.

Redacted values are replaced with a deterministic placeholder like [redacted:email] so the structure of the trace is preserved for replay.

Every string field is also capped at 50,000 characters (configurable via maxStringLength) before it's sent — a single oversized tool output or pasted document can't bloat an ingest payload. A capped field gets a …[truncated: N more chars] marker, and the run's metadata reports how many fields were capped (truncated_fields) — visible, not silent.

This is pattern matching, not an NER model. It catches values that look like an email, a card number, or a known secret shape — it does not detect names, street addresses, or other free-text PII, and redaction applies to string values only. If your agent embeds PII inside structured JSON objects, or needs coverage beyond format-based patterns, pass customPatterns or a full redactor callback via the SDK config.

Cost attribution & chargeback

Scale

The Cost page breaks down your AI spend by model and agent over any time window. The Cost → Teams view shows spend per team with budget tracking and alert thresholds.

runback.dev/app — Cost attribution — spend by model and agent
Cost attribution — spend by model and agent

Cost data is derived from captured token counts in every LLM span. No billing integration required.

Team chargeback (Enterprise)

Per-team budget caps and automated alerts when a team approaches or exceeds its monthly cap.

FeatureDescription
Per-team spendToken cost attributed to each team over a rolling 30-day window
Budget capsSet a monthly spend limit per team; alerts fire at 80% and 100%
Overage enforcementOptional: block ingest for a team that exceeds its cap until the next month
CSV exportExport the period report for internal billing or departmental chargebacks

Configure team budgets under Settings → Teams → Budget.

Signed audit records

Community

Every run can be exported as a signed audit record — a JSON bundle containing all spans, a SHA-256 hash chain over them (each event hashed over the previous, terminating in one content_digest), and a cryptographic signature over that digest. Signed with Ed25519 by default, so it's verifiable offline by anyone against Runback's published public key — no shared secret, no account, no trusting Runback's servers. Self-hosted instances that haven't set an Ed25519 key fall back to an HMAC-SHA256 signature, verifiable only with your own shared key. (The org-wide ledger those runs seal into separately carries a Merkle root over its checkpoints — see Security — a different construction from a single run's hash chain.) Verifiable independently at runback.dev/verify.

bash
# Download via API
curl -H "Authorization: Bearer $RUNBACK_API_KEY" \
  https://runback.dev/api/runs/{run_id}/audit > audit.json

# Verify locally
npx @runback/verify audit.json

The audit ledger

Community

The section above describes one run's signed record. The ledger is the org-wide version: every sealed run appended to a single append-only hash chain, where each entry carries the hash of the entry before it. Change, delete or insert any past decision and the chain stops reproducing — at the exact entry that changed, not merely somewhere.

Leaves are re-derived, not trusted

Verification does not compare a stored hash against itself. It rebuilds each leaf from the run as it exists now and checks it still matches what was sealed. A run edited after the fact fails at its own position even though its stored hash is untouched.

Checkpoints anchor the head

Sealing a checkpoint signs the current head plus a Merkle root over every leaf up to it. The signature covers the tombstone set too, so a retention deletion cannot be used to quietly excuse an alteration until a fresh checkpoint re-anchors it.

Verify integrity on the Ledger page runs that check live and shows it happening: the chain re-derives entry by entry, then the head and Merkle root are matched against the last signed checkpoint. Nothing is pre-computed — if your ledger verifies in 80ms you see 80ms, and if it takes three seconds you see three seconds.

runback.dev/app — Audit ledger — verifying the chain live
Audit ledger — verifying the chain live

A break is the case the screen is designed for. The failing entry is marked at its exact sequence number with the run behind it, and everything after it greys out rather than turning red: past a broken link nothing has been established, and only the break itself is a finding.

VerdictMeaning
Chain intactEvery run re-derives to its sealed leaf, the links hold, and the head + Merkle root match the signed checkpoint.
Tamper detectedA specific entry no longer reproduces. The seq and run id are named; the ledger is the evidence, not the alarm.
Could not verifyThe ledger or run store could not be read in full. Explicitly NOT a tamper finding — retry before drawing any conclusion.
"Could not verify" and "tamper detected" are deliberately different answers. Verification is fail-closed, but closed means unconfirmed, never confirmed broken — a false tamper alarm is the most expensive wrong answer this product can give.

Self-attested is not the same as witnessed. A checkpoint signed only by this deployment proves nobody other than the operator altered the chain. Where an external time-stamp authority has attested a checkpoint, verification names it, and that head cannot have been re-sealed after the fact.

bash
# The same check, over the API
curl -X POST -H "Authorization: Bearer $RUNBACK_API_KEY" \
  https://runback.dev/api/app/ledger

# Streamed, one JSON object per line, as the walk happens
curl -N -X POST -H "Authorization: Bearer $RUNBACK_API_KEY" \
  https://runback.dev/api/app/ledger/stream

Regulatory dashboard & compliance export

Enterprise

The Regulatory page maps your live run data to framework controls — EU AI Act Annex III, APRA CPS 230, and NIST AI RMF. Each control shows whether it's satisfied, partially covered, or missing evidence, based on your actual captures.

FrameworkControls mapped
EU AI Act Art. 12Logging and traceability — tamper-evident decision record
APRA CPS 230Operational risk, continuous monitoring, incident management
NIST AI RMFGovern, Map, Measure, Manage — live status per function
ISO/IEC 42001AI management system — evidence export for auditors

Compliance export

The Compliance export generates a structured evidence package for auditor and regulator submissions — a signed, time-stamped summary of your governance posture, not a self-assessment. It includes:

  • Policy inventory: all active rules, their versions, and block/flag counts over the audit period
  • Incident log: every opened incident, its resolution, and the run record it links to
  • Model change history: every model upgrade event and the CI gate result that preceded it
  • Merkle-rooted ledger checkpoint: proof that the audit period record is complete and unaltered
Both the dashboard and the export are evidence support — neither is a compliance certificate. Your legal or compliance team must assess whether the evidence satisfies your specific obligations.

Sealed AI narratives

Enterprise

An AI-generated explanation — either a root-cause narrative for a model-diff divergence, or a compliance control's evidence summary — sealed the moment it's generated: hash-chained to your org's prior narratives and signed, with the exact evidence it was generated from pinned by digest. If that evidence changes afterward, re-verification catches it — the explanation is disposable, the proof it wasn't rewritten is not.

runback.dev/app — Regulatory — sealed compliance narrative, verified against current evidence
Regulatory — sealed compliance narrative, verified against current evidence

Two entry points, one sealing mechanism:

  • “Explain this” on a model-diff divergence (Models → Diff) — a root-cause narrative for why behaviour changed between two models.
  • “Explain this control” on any regulatory control (Regulatory page) — a plain-English summary of that control's live evidence.
Every narrative shows its signature algorithm and a live re-verification badge — “verified against current evidence” or “evidence changed since sealed,” recomputed on every page load, not cached at generation time.

Pairwise comparison & judge calibration

Growth

Pairwise comparison judges two eval runs head-to-head, item by item — not two independent pass/fail scores. For each shared item, an LLM judge picks which output is better (or calls it a tie), with position-bias mitigation (the side order is randomized and un-swapped for storage) so the judge can't learn to favor “A.”

runback.dev/app — Pairwise comparison — judged head-to-head, item by item
Pairwise comparison — judged head-to-head, item by item
1
Run two evals against the same dataset — different models, different prompt versions, whatever you're deciding between.
2
Compare them. Runback judges every item both runs share, and shows win rate, loss rate, and ties.
3
Disagree with a verdict? Override it. Human overrides are audited and take precedence over the judge's call.
Re-comparing the same pair is safe and cheap — already-judged items are skipped, so you only pay for the ones a new dataset item added.

Judge calibration

Spot-check real (non-demo) LLM-judge verdicts against human review. Agree with the judge, or correct it — a correction becomes a few-shot example fed back into that rubric's future judging, so the judge gets better at exactly the cases it's been wrong about.

runback.dev/app — Judge calibration — spot-check real judge verdicts as they arrive
Judge calibration — spot-check real judge verdicts as they arrive
Calibration is scoped to the rubric (a hash of its criteria), not the dataset — one correction improves every eval that shares that rubric, not just the one it came from.

Auto-mined adversarial tests

Scale

A daily job goes further than mining the literal failure: it takes each real policy block or low-scoring eval and asks a model to probe the same weak spot from a different angle — a new phrasing, not the identical input — so your test suite covers the failure mode your production traffic actually found, not just the one exact case.

runback.dev/app — Datasets — Auto-mined failures, proposed from real production failures
Datasets — Auto-mined failures, proposed from real production failures

Candidates land in an “Auto-mined failures” dataset as pending items — never auto-approved. Review and approve each one in Datasets before it affects your release gate, same as any golden test.

Capped at 5 highest-severity signals per org per run, so an incident spike can't flood your review queue. Each signal is mined once — a re-run of the job never proposes the same failure twice.

External security findings

Enterprise

A narrowly-scoped, write-only key lets a guardrail vendor — Lakera, Cisco AI Defense, or similar — post findings about your runs into your own sealed record instead of staying siloed in their dashboard. Each finding is hash-chained and signed on arrival, independently of your run's own oracle chain, so a vendor's webhook can never retroactively alter a run's replay identity.

runback.dev/app — Run detail — a sealed external security finding, surfaced in the header
Run detail — a sealed external security finding, surfaced in the header
1
Generate a security-findings key under Settings → Security Findings Key.
2
Paste it into your guardrail vendor's outbound-webhook config, pointed at POST /api/security-findings.
3
Findings tied to a run_id show up as a pill in that run's header — everything else lives in the run's sealed record.
This key can only ever POST a finding. It cannot ingest runs, read run content, or open a dashboard session — pasting it into a vendor config leaks nothing beyond that one write.

External auditor & regulator grants

Enterprise

Issue a read-only, time-limited key scoped to specific runs — or all of them — and hand it to an auditor or regulator instead of a login. It downloads the exact same signed audit record your own team would see: byte-identical, not a summary.

runback.dev/app — Settings — issuing an external grant, scoped to specific runs
Settings — issuing an external grant, scoped to specific runs
1
Under Settings → External Grants, label the grant, choose specific runs or all of them, and set an expiry. Enterprise only — on other plans the section appears locked, with no grant controls.
2
Hand the raw key to the auditor — it authenticates as a Bearer token on the run's audit endpoint, nowhere else.
3
Revoke it any time from the same screen. Every grant is checked for expiry and revocation on every read, not just at issuance.

Requirements

Community

Runback is a standard Next.js app backed by a Postgres database. No exotic infrastructure.

ComponentMinimumRecommended
Node.js20 LTS22 LTS
Postgres1416 (Supabase, RDS, Cloud SQL)
RAM512 MB2 GB
CPU1 vCPU2 vCPU
Disk10 GB50 GB (depends on run volume)
For production self-hosting, use a managed Postgres service. SQLite is not supported.

Docker Compose

Community

The fastest self-host path. Spins up the app and a local Postgres in two commands.

bash
# 1. Clone the Community edition. Public, source-available, no request needed.
git clone https://github.com/letsRunback/runback-community.git
cd runback-community

# 2. Copy and fill in the env file (set AUDIT_SIGNING_KEY and JWT_SECRET at minimum)
cp .env.example .env && $EDITOR .env

# 3. Start
docker compose up -d

# App is now at http://localhost:3000

Postgres is bundled — no external database required. For production, override POSTGRES_PASSWORD in .env and point NEXT_PUBLIC_APP_URL at your domain.

A scheduler service comes up alongside web and runs the same 18 background jobs the hosted deployment runs on Vercel Cron (ledger sealing, retention, drift alerts, the guard kill-switch, …) — nothing extra to configure. docker compose logs -f scheduler shows each run.

Environment variables (self-hosted)

Community
VariableRequiredDescription
AUDIT_SIGNING_KEYYesHMAC key for tamper-evident audit records. Generate: openssl rand -hex 32
AUDIT_ED25519_PRIVATE_KEYRecommendedEd25519 key so audit records are verifiable offline by anyone, with no shared secret. Falls back to HMAC (not independently verifiable) if unset. Generate: openssl genpkey -algorithm ed25519
JWT_SECRETYesSigns PostgREST session tokens. Generate: openssl rand -hex 32. Also activates database-enforced tenant isolation (Postgres RLS) as a defense-in-depth layer under the app's own org filtering — docker-compose.yml passes it through as SUPABASE_JWT_SECRET automatically, nothing extra to set.
POSTGRES_PASSWORDYesPostgres password. No default — must be set in .env before docker compose up.
AUTHENTICATOR_PASSWORDYesPostgREST's own DB role password, separate from POSTGRES_PASSWORD. docker compose up hard-fails without it. Generate: openssl rand -hex 32
SUPABASE_SERVICE_ROLE_KEYYesPostgREST service-role JWT, signed with JWT_SECRET. docker compose up hard-fails without it — see .env.example for the one-line generation command.
CRON_SECRETYesBearer token the bundled scheduler service presents to the 18 scheduled jobs (retention, ledger sealing, drift, billing reconcile, newsletter, …) — see docs/SELF_HOSTING.md's "Scheduled jobs" section. docker compose up hard-fails without it. Generate: openssl rand -hex 32
NEXT_PUBLIC_APP_URLYesYour public base URL, used for magic-link emails, webhooks, and redirects. docker compose up hard-fails without it — e.g. http://localhost:3000 or https://runback.yourco.com
RUNBACK_LICENSEEnterpriseLicense key for Enterprise features (SSO, fleet dashboard, etc.).
RUNBACK_SMTP_URLAir-gappedSend email through your own SMTP relay instead of Resend's REST API, which a network with no egress cannot reach. Standard connection URL — smtp://relay.internal:25 (no auth), smtp://user:pass@relay.internal:587 (STARTTLS), smtps://…:465 (implicit TLS). Takes precedence over RESEND_API_KEY when both are set. Without either, a self-hosted deployment prints sign-in links to the server log.
RUNBACK_OPENAI_BASE_URLAir-gappedPoint the OpenAI provider at your own OpenAI-compatible endpoint (vLLM, Ollama, LiteLLM, Azure OpenAI) instead of api.openai.com — e.g. http://vllm.internal:8000/v1. Without it, replay and everything built on it (evals, golden suites, model diff, playground, judge calibration) cannot reach a model on a network with no egress. OPENAI_API_KEY becomes optional once this is set, because self-hosted inference commonly takes no auth.
RUNBACK_ANTHROPIC_BASE_URLAir-gappedSame, for the Anthropic provider.
RUNBACK_GROQ_BASE_URLAir-gappedSame, for the Groq provider.
RUNBACK_REPLAY_MODELSAir-gappedComma-separated. Replaces the built-in replay allowlist entirely — the right choice on an air-gap, where every built-in is an unreachable option and the first is the default selection. Read from the environment only, never from a request: the allowlist is a security control precisely because a caller cannot extend it.
RUNBACK_EXTRA_REPLAY_MODELSNoComma-separated. Appends to the built-in allowlist instead of replacing it, for a deployment that can reach both its own models and the public APIs.
RUNBACK_ALLOW_PRIVATE_TARGETSNoReaching a private/RFC1918 SSO issuer, alert webhook, or SIEM collector is allowed by default self-hosted — nothing to set. Set to false only to opt into the stricter, hosted-style SSRF guard anyway. Always ignored on the hosted service.
RUNBACK_TSA_URLSNoInternal RFC 3161 timestamp authority for a genuinely air-gapped deployment. Unset, ledger checkpoints are witnessed by two public authorities (freetsa.org, digicert) over the open internet — see Privacy posture in the self-hosting guide.
RUNBACK_DEMO_MODENoSet to 1 to disable real model calls in replay/evals.
RESEND_API_KEYNoResend API key for magic-link email auth.
OPENAI_API_KEYNoOnly needed for live step-replay & LLM eval judges.

Upgrading

Community

SQL migrations run automatically on startup — no manual steps required.

bash
# Docker Compose
docker compose pull
docker compose up -d

# Migrations run automatically on boot.
# Downtime: typically under 5 seconds for minor releases.
Before upgrading across a major version, read the release notes — major versions may include breaking schema changes that require a one-time migration step.

Authentication

All API requests require a Bearer token in the Authorization header.

bash
curl -H "Authorization: Bearer rb_live_your_key" \
  https://runback.dev/api/runs

API keys are created in your dashboard under Settings → API Key (SDK). Keys are prefixed rb_live_.

Rotating a key

Keys are stored as SHA-256 hashes — we cannot show you an existing key again, only replace it. To rotate:

  1. Go to Settings → API Key (SDK) and issue a new key. The raw value is shown once.
  2. Deploy it to your agents as RUNBACK_API_KEY. Both keys work during the changeover, so there is no ingest gap.
  3. Revoke the old key from the same screen once no agent is using it.
Issuance and revocation are both written to the administrative audit log with the actor, source IP and timestamp — see Security. If a key is leaked, revoke first and rotate second: revocation takes effect on the next request, and an ingest gap is cheaper than an open credential.

Two scopes, chosen when you create the key. A telemetry-onlykey (the recommended option) can post runs and nothing else — it cannot read run content, so a leak cannot exfiltrate your traces. A full-accesskey drives the Bearer flows documented below — the CI gate, replay, and the read endpoints — and therefore CAN read run content, so treat it like a password and prefer telemetry-only wherever those flows are not needed. Compliance-read (rb_comp_) and SCIM (rb_scim_) keys are separate scopes and rotate the same way.

Roles

Every member of a workspace holds one of four roles. They are cumulative — each one can do everything the role below it can, plus more — and they are enforced server-side on every request, not in the interface. A session cookie and a full-access API key are both subject to the same check.

RoleCan do
viewerRead runs, traces, evals, policies, dashboards and audit records. Cannot change anything, and cannot trigger any action that spends money.
memberEverything a viewer can, plus the day-to-day work: replay a step, re-execute or bisect a run, write policies and prompts, create datasets, run evals, enrol golden cases, open and update incidents.
adminEverything a member can, plus workspace administration: issue and revoke API keys, decide approvals, configure SSO, SIEM and model keys, place legal holds, issue external auditor grants, calibrate judges, and manage the subscription.
ownerEverything an admin can. The owner cannot be removed from the workspace and is the billing contact.
Replay, re-execution and eval runs call a model provider and are therefore billed to whoever's key is configured. That is why they require memberrather than viewer: entitlement answers whether the workspace may use a feature, which is a different question from whether this person may spend on it.

Roles are set when you invite someone (Settings → Team) and can be changed there afterwards. Invitations, role changes and removals are all written to the administrative audit log with the actor and source IP.

Runs API

MethodPathDescription
GET/api/runsList runs. Query params: limit, offset, status, agent_name, from, to.
GET/api/runs/:idGet a single run with all spans.
POST/api/runs/:id/replayTrigger a step replay. Body: { span_id, model }.
GET/api/runs/:id/auditDownload the signed audit record as JSON.
GET/api/runs/:id/cassetteDownload the re-executable cassette bundle. Enterprise — this is what replayRun() fetches.
bash
# List the last 10 failed runs
curl -H "Authorization: Bearer $KEY" \
  "https://runback.dev/api/runs?status=error&limit=10"

# Download audit record
curl -H "Authorization: Bearer $KEY" \
  "https://runback.dev/api/runs/run_abc123/audit" > audit.json

Prompts API

Growth

The one your agent's backend or CI actually calls: fetch whatever version a label currently points at, with plain HTTP caching so it works identically behind Docker Compose with no extra infra.

MethodPathDescription
GET/api/prompts/:name?label=productionFetch the version a label points at. ETag + Cache-Control: max-age=60, stale-while-revalidate=300.
GET/api/promptsList every prompt's latest version in the org.
POST/api/promptsSave a new immutable version. Body: { name, template, model, variables?, commit_message? }.
GET/api/prompts/:name/versionsList every version of one named prompt, newest first.
GET/api/prompts/:name/labelsList every label currently set and the version it points at.
POST/api/prompts/:name/labelsMove a label. Body: { label, version }. Moving "production" requires admin.
POST/api/prompts/playgroundRender a template with real values and run it against one or more models. Body: { template, variables, values, model, compare_model_ids? }.
bash
# What your agent's backend calls on every invocation
curl -H "Authorization: Bearer $KEY" \
  "https://runback.dev/api/prompts/support-agent?label=production"

# Save a new version
curl -X POST -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  https://runback.dev/api/prompts \
  -d '{"name":"support-agent","template":[{"role":"user","content":"Hi {{name}}"}],"model":{"provider":"anthropic","model_id":"claude-sonnet-4-6"}}'

Approvals & Incidents API

Starter
MethodPathDescription
GET/api/approvalsList approvals. Query: status=pending|approved|rejected
POST/api/approvalsCreate an approval request for a run.
PATCH/api/approvals/:idApprove or reject. Body: { decision, note }
GET/api/incidentsList incidents. Query: status=open|investigating|resolved
POST/api/incidentsCreate an incident linked to a run.
PATCH/api/incidents/:idUpdate incident status or resolution.

Pairwise & calibration API

Growth
MethodPathDescription
POST/api/evals/pairwiseJudge every shared item between two finished eval runs. Body: { eval_run_a_id, eval_run_b_id, randomize_order? }. Safe to re-run — already-judged items are skipped.
POST/api/evals/pairwise/verdictA human overrides one item's verdict. Body: { eval_run_a_id, eval_run_b_id, item_id, winner }. winner is a, b, or tie.
POST/api/evals/calibrateA human agrees with or corrects one judge verdict. Body: { review_id, human_passed, human_note? }.

Audit API

Audit records are self-contained signed JSON bundles. To verify a record without trusting Runback:

bash
# Verify with the CLI
npx @runback/verify ./audit.json

# Output: OK  root=abc123...  signature=valid  spans=14

The CLI recomputes the Merkle tree from the raw span data and checks it against the root in the record. On the hosted service, and any self-host with AUDIT_ED25519_PRIVATE_KEY set, the signature is Ed25519 — verifiable against Runback's published public key, with no shared secret and no account. A self-host that hasn't set that key falls back to an HMAC-SHA256 signature, verifiable only with your own key.

Narratives API

Enterprise
MethodPathDescription
POST/api/runs/:run_id/narrativeGenerate and seal a root-cause narrative for a model-diff divergence. Body: { model_a, model_b, window_days? }. Session-authenticated.
GET/api/runs/:run_id/narrativeList sealed narratives for this run, each with a live re-verification verdict. Session-authenticated.
POST/api/regulatory/:framework_id/:control_id/narrativeGenerate and seal a compliance narrative for one control's live evidence. Session-authenticated.
GET/api/regulatory/:framework_id/:control_id/narrativeList sealed narratives for this control, each with a live re-verification verdict. Session-authenticated.
Every narrative is chained into the same per-org sequence regardless of subject — a model-diff narrative and a compliance narrative for the same org share one chain.

Security findings API

Enterprise
MethodPathDescription
POST/api/security-findingsIngest one finding. Body: { run_id?, span_id?, vendor, rule, severity, verdict, detail, raw_finding }. Authenticated with a security-findings-scoped key, not your ingest key.
GET/api/runs/:run_id/security-findingsList a run's sealed findings, each with a live re-verification verdict. Session-authenticated.
bash
curl -X POST https://runback.dev/api/security-findings \
  -H "Authorization: Bearer rb_secfind_your_key" \
  -H "content-type: application/json" \
  -d '{
    "run_id": "run_abc123",
    "vendor": "lakera",
    "rule": "prompt-injection-detected",
    "severity": "high",
    "verdict": "flagged",
    "detail": "Detected an embedded instruction overriding the system prompt.",
    "raw_finding": { "score": 0.94 }
  }'
severity must be one of info/low/medium/high/critical; verdict must be one of flagged/blocked/allowed. A run_id, if provided, must belong to the key's own org — a forged or cross-org run_id is rejected.

External grants API

Enterprise
MethodPathDescription
POST/api/app/external-grantsIssue a grant. Body: { label, scope_type: 'run_ids'|'org_wide', run_ids?, expires_in_days }. Session-authenticated, admin+.
GET/api/app/external-grantsList issued grants for the org — metadata only, never the raw key.
DELETE/api/app/external-grants/:idRevoke a grant immediately.

The raw key returned from issuance authenticates directly on GET /api/runs/:run_id/audit as a Bearer token — no other endpoint accepts it.

Trust chain API

Pro
MethodPathDescription
POST/api/trust/chainIssue a delegation token from orchestrator to subagent.
POST/api/trust/verifyVerify a trust chain. Returns: valid, depth, root, chain.

Analytics & reporting API

Pro

Read-only, org-scoped reports — the same data the dashboard pages render, for pulling into your own BI, FinOps, or governance tooling. All accept a Bearer key.

MethodPathDescription
GET/api/cost/attributionCost by model and agent. Query: days (7–90, default 30).
GET/api/chargebackPer-team cost rollup with budget utilisation. Enterprise.
GET/api/models/attributionPer-model run counts, error rate, latency, tokens, trend.
GET/api/models/diffCompare two models over your runs. Query: modelA, modelB, days. Omit both to list models.
GET/api/benchmark/fleetYour metrics against anonymised fleet and vertical percentiles.
GET/api/policy-causesWhich policies block which agents, and how often.
GET/api/corpus/signalsPer-agent anomaly and error-rate signals.
GET/api/golden/reportGolden-corpus coverage: cases enrolled, approved, blocking.
GET/api/drift/reportBehavioural drift per agent vs the prior window.
bash
# 30-day cost by model, as JSON
curl -H "Authorization: Bearer $RUNBACK_API_KEY" \
  "https://runback.dev/api/cost/attribution?days=30"
Each endpoint is gated by the plan its dashboard page requires, and scoped to the key's org — a key can never read another tenant's data.

Webhooks

Starter

Runback can POST a JSON payload to any URL when a run ends or a policy is triggered.

EventWhen it fires
run.completedAny run finishes (success or error)
run.errorA run ends with status = error
policy.blockA policy rule fires with action = block
policy.flagA policy rule fires with action = flag
approval.createdAn approval request is created
incident.openedA new incident is opened
json
// Example payload — policy.block
{
  "event": "policy.block",
  "run_id": "run_abc123",
  "policy_id": "no-large-disputed-refund",
  "tool": "issue_refund",
  "blocked_at": "2026-07-01T14:22:01Z"
}

Configure webhooks in your dashboard under Alerts → Webhooks. HMAC-SHA256 request signing is enabled by default.

Rate limits

LimitCommunityStarterGrowthScaleProEnterprise
Ingest — spans/sec502005001,0002,000Custom
API reads — req/min603006009001,200Custom
Replay — per hour1050100200500Custom
Eval runs — per day520100200500Custom
Audit exports — per day10502005001,000Custom
Requests over the limit return 429 Too Many Requests with a retry-after header. Per-request quota headers (X-RateLimit-*) are not currently sent — this previously said they were on every response, and no code emits them.