Skip to content
TuffTasks, docs and chat for teams and AI agents

Observability

Deploy Timeline

Prompt and model versions on a timeline over the eval score they shipped with — regressions, rollbacks and the live canary.

1 variantMIT

Preview

Deployments

triage-l · prompt & model versions · 14 days

livev14 · o4-minicanary 1%v15eval0.931deploys6 · 1 regr
Deployments: 6 deploys; live v14 at 0.931; v13 regressed −4.4 pts and was rolled back after 38 min; v15 in canary at 1%.
VersionKindDeployedEval scoreChangeStatus
v11: few-shot refreshpromptOct 270.904FirstReplaced
v12: shorter system promptpromptOct 290.912+0.8 ptsReplaced
o4-mini: model swapmodelOct 310.921+0.9 ptsReplaced
v13: stricter escalation rulespromptNov 20.877−4.4 ptsRolled back after 38 min
v14: escalation rules, take twopromptNov 40.931+1.0 ptsLive
v15: tool-use hintspromptNov 70.918−1.3 ptsCanary, 1% of traffic

Installation

Copies the component into components/ui and installs its dependencies and theme tokens.

pnpm dlx shadcn@latest add https://tuffui.com/r/deploy-timeline.json

Usage

Pass your own data and wire the callbacks to your agent runtime.

import { DeployTimeline } from "@/components/ui/deploy-timeline";

export function AgentDeploys({ agent }: { agent: Agent }) {
  return (
    <DeployTimeline
      title="Deployments"
      subtitle={`${agent.name} · prompt & model versions · 14 days`}
      threshold={0.88} // minimum eval score
      range={[daysAgo(14), new Date()]}
      deploys={agent.releases.map((r) => ({
        id: r.id,
        version: r.kind === "model" ? r.model : `v${r.promptVersion}`,
        kind: r.kind, // "prompt" | "model"
        label: r.note,
        at: r.deployedAt,
        score: r.evalScore, // 0–1
        rolledBackAfter: r.rolledBackAt
          ? (r.rolledBackAt - r.deployedAt) / 60_000 // minutes
          : undefined,
        canary: r.trafficShare < 1 ? r.trafficShare : undefined,
      }))}
      onCompare={(version, live) => openDiff(version.id, live.id)}
      onRollback={(version) => confirmRollback(version.id)}
    />
  );
}

Props

PropTypeDefaultDescription
deploys*Deploy[]—Every prompt and model deploy in the window, in any order. The score steps at each one; a deploy with `rolledBackAfter` dips and comes back, the latest one with `canary` runs as a dashed line beside live.
thresholdnumber—Minimum eval score, drawn as a dashed line. Deploys under it count as regressions even without a rollback.
range[Date | string | number, Date | string | number]around the deploysTime window of the axis. Day labels thin out to fit the width.
nowDate | string | numberend of rangeWhere the score line stops. Move it forward to grow the live and canary lines.
livebooleantruePulses the canary while traffic is still being scored.
titlestring"Deployments"Header title.
subtitlestring—Secondary line under the title.
statsDeployStat[]live, canary, eval, deploysChips under the chart. By default: the live version and its model, canary share, live eval score and the deploy count with regressions. Pass `[]` to hide them.
selectedIdstring | null—Controlled selection. Left out, the component keeps its own.
onSelect(deploy: Deploy | null) => void—Called when a marker (or its segment of the line) is clicked; null when the selection is cleared again or with Escape.
onCompare(deploy: Deploy, live: Deploy) => void—Shows a Compare button for the selected deploy against live. With nothing selected it compares the last good version before live.
onRollback(deploy: Deploy) => void—Shows a Roll back button to the selected deploy, or the last good version before live. Disabled for live, the canary and regressions.
asideReactNode—Extra content in the header, before the buttons.
introbooleantrueDraws the line in from the left on mount, then markers and the regression callout. Off renders the finished chart. Skipped with reduced motion.
formatScore(score: number) => stringscore.toFixed(3)Formats scores in the callout, chips and the minimum label.
formatDate(date: Date) => string"Nov 2" in UTCFormats the day axis and dates in the callout.
classNamestring—Extra classes for the root element.
type Deploy = {
  id: string;
  version: string;          // "v13", or a model id like "o4-mini"
  kind: "prompt" | "model"; // dot or diamond on the rail
  label?: string;           // what changed, shown in the callout
  at: Date | string | number;
  score: number;            // eval score it shipped with, 0–1
  rolledBackAfter?: number; // minutes until rollback: a regression
  canary?: number;          // traffic share 0–1 while in canary
};

type DeployStat = {
  label: string;
  value: string;
  tone?: "neutral" | "brand" | "negative";
};