Changes and delivery

A Change is the concrete intervention that implements a Hypothesis — the thing you actually ship. This page covers what a Change is, the exposure decision that determines whether it can ever support a causal claim, and how delivery behaves when networks and configs misbehave.

The Change entity

Every Change has a kind:

Kind What it changes
page_variant A page or component variant served to a share of traffic
ad_creative A single ad creative
ad_campaign A campaign scaffold handled by Ads
product_change A change shipped in your product’s own codebase
content An article or other content artifact, usually from a brief

A Change binds a Property and environment, links its Loop and Hypothesis, and carries variants or artifacts, a stable exposure tag, rollout state, readiness, health, and an audit trail. The lifecycle runs draft → ready → pending approval → live → complete or killed, with a path back to draft. The linked Loop reflects the real lifecycle — a Loop is only live when its Change actually is.

The exposure tag is a stable slug with a strict grammar, deliberately independent of the CHG-* display ID. Code references the tag, humans reference the ID, and neither breaks the other.

The exposure decision

Every Change must answer one question before it ships: how will Torno know who saw it?

A measurable Change declares its contract up front — the exposure tag and event, the variants, the goal events that count, the assignment kind, and who is eligible. A Change without that contract is still allowed, but it is explicitly badged as unmeasurable and excluded from causal claims and results. Torno will not backfill a causal story for a Change that never declared exposure.

This is the honest version of ship-and-watch: you can still ship it, and your Metrics will still move — Torno just won’t pretend the Change caused the movement. When you want a real answer, declare exposure and measure it as an Experiment.

Delivery: signed bundles, local assignment

The backend publishes signed torno-bundle/v1 configs per Property and environment: active Changes, variant weights, consent and capture rules, an optional holdout, a version and ETag, and the signing key ID. SDKs and middleware verify the signature, cache the bundle, and evaluate assignment locally — deterministic bucketing that stays sticky per Subject.

Bundles sync in the background with a target of 60 seconds or less; after that, every assignment decision is local. There is no Torno network call in your customer’s request path.

Failure follows one rule — when in doubt, serve control:

Condition Behavior
No bundle yet (first load, blocked fetch) Control / pass-through
Bundle stale beyond tolerance Control / pass-through
Signature invalid or bundle tampered Control / pass-through
Torno unreachable, valid cached bundle Local assignment continues from cache

Client-only content swapping exists but is second-class; server and edge assignment is the first-class path.

First exposure starts measurement

Going live does not start the clock. The first accepted exposure does: it confirms the Change is actually reaching Subjects, writes the assignment through Cohorts, starts measurement, and notifies observers.

The inverse is watched too. A Change that is live but producing no exposures raises an instrumentation-drift alert instead of quietly measuring nothing. Exposure events with unknown tags are retained as unmatched evidence rather than silently attached to the nearest Change.

Rollout, ramp, kill

Operators control a sticky percentage rollout, schedule ramps, cancel ramps, and kill a Change immediately. Going live passes readiness checks and policy; kill is always available as a safety action. While an Experiment is running, arbitrary rollout edits are interlocked so the test’s allocation is not silently invalidated. Environment overrides stay isolated — staging games do not leak into production.

Working with Changes

The web app covers create, status, exposure health, rollout, and readiness. Agents drive the same lifecycle through torno change on the CLI, the API, and MCP, and Changes travel with their Loop in Loops as code.