Headlessly
Experimentation

Experiment

A/B tests, multivariate experiments, ML experiments, and prompt experiments with statistical rigor.

Schema

import { Noun } from 'digital-objects'

export const Experiment = Noun('Experiment', {
  name: 'string!',
  slug: 'string##',
  description: 'string',
  hypothesis: 'string',
  type: 'ABTest | Multivariate | FeatureFlag | MLExperiment | PromptExperiment',
  status: 'Draft | Started | Running | Paused | Concluded | Completed | Archived',
  organization: '-> Organization',
  owner: '-> Contact',
  startAt: 'datetime',
  endAt: 'datetime',
  targetAudience: 'string',
  trafficAllocation: 'number',
  variants: 'json',
  metrics: 'string',
  primaryMetric: 'string',
  results: 'json',
  winner: 'string',
  confidence: 'number',
  sampleSize: 'number',
  conversions: 'number',
  tags: 'string',
  start: 'Started',
  conclude: 'Concluded',
  pause: 'Paused',
  stop: 'Stopped',
})

Fields

FieldTypeRequiredDescription
namestringYesDisplay name of the experiment
slugstring (unique, indexed)NoURL-safe identifier
descriptionstringNoDetailed description of what is being tested
hypothesisstringNoThe hypothesis being validated
typeenumNoABTest, Multivariate, FeatureFlag, MLExperiment, or PromptExperiment
statusenumNoDraft, Started, Running, Paused, Concluded, Completed, or Archived
organization-> OrganizationNoOrganization running this experiment
owner-> ContactNoPerson responsible for the experiment
startAtdatetimeNoScheduled start time
endAtdatetimeNoScheduled end time
targetAudiencestringNoDescription or segment ID of the target audience
trafficAllocationnumberNoPercentage of traffic allocated to the experiment (0-100)
variantsjsonNoArray of variant definitions with keys, names, and weights
metricsstringNoComma-separated list of metrics being tracked
primaryMetricstringNoThe primary success metric
resultsjsonNoStatistical results per variant
winnerstringNoKey of the winning variant
confidencenumberNoStatistical confidence level (0-100)
sampleSizenumberNoTotal number of participants
conversionsnumberNoTotal number of conversions
tagsstringNoComma-separated tags for categorization

Relationships

FieldDirectionTargetDescription
organization->OrganizationThe organization running this experiment
owner->ContactThe person responsible for experiment design and analysis

Verbs

VerbEventDescription
createCreatedCreate a new experiment
updateUpdatedUpdate experiment fields
deleteDeletedDelete an experiment
startStartedBegin running the experiment and collecting data
concludeConcludedDeclare results, set winner, and stop data collection
pausePausedTemporarily pause the experiment
stopStoppedPermanently stop the experiment without concluding

Verb Lifecycle

import { Experiment } from '@headlessly/experiments'

// BEFORE hook -- validate prerequisites
Experiment.starting(experiment => {
  if (!experiment.variants || experiment.variants.length < 2) {
    throw new Error('At least two variants required to start')
  }
  if (!experiment.primaryMetric) {
    throw new Error('Primary metric must be defined before starting')
  }
})

// Execute -- start the experiment
await Experiment.start('experiment_hR5nVkTw')

// AFTER hook -- trigger downstream actions
Experiment.started((experiment, $) => {
  $.Event.create({
    type: 'experiment.started',
    data: {
      experiment: experiment.$id,
      variants: experiment.variants.length,
      trafficAllocation: experiment.trafficAllocation,
    },
  })
})

Status State Machine

Draft --> Started --> Running --> Concluded --> Completed
             |           |                         |
             v           v                         v
           Paused --> Running                  Archived
             |
             v
          Stopped --> Archived
  • Draft: Initial state, experiment is being configured
  • Started: Experiment has been kicked off, awaiting first data
  • Running: Actively collecting data and assigning variants
  • Paused: Temporarily halted, can be resumed
  • Concluded: Results declared, winner chosen, data collection stopped
  • Completed: Fully finalized with documentation and learnings
  • Stopped: Permanently halted without conclusion
  • Archived: Historical record, no longer active

Cross-Domain Patterns

Experiment is the analytical core that connects to feature flags, analytics, and marketing:

  • Experimentation: Each Experiment can own one or more FeatureFlags. Concluding an experiment can trigger a flag rollout.
  • Analytics: Experiment variant assignments and conversions emit Events. Funnels can segment by experiment variant. Goals can track experiment outcomes.
  • Marketing: Campaign A/B tests use Experiment entities. Segment-based targeting determines experiment audience.
  • CRM: Contact-level variant assignment enables personalized experiment analysis. Deal conversion rates can serve as experiment metrics.
  • Platform: Agents use PromptExperiment type to optimize system prompts. Workflows trigger experiment lifecycle transitions.
import { Experiment } from '@headlessly/experiments'

// When an experiment is concluded, propagate the results
Experiment.concluded((experiment, $) => {
  // Roll out the winning variant
  if (experiment.winner && experiment.confidence >= 95) {
    const flags = await $.FeatureFlag.find({ experiment: experiment.$id })
    for (const flag of flags) {
      await $.FeatureFlag.rollout(flag.$id)
    }
  }

  // Record the outcome as a metric
  $.Metric.create({
    name: `Experiment: ${experiment.name}`,
    type: 'Conversion',
    value: experiment.confidence,
  })

  // Notify the experiment owner
  $.Activity.create({
    subject: `Experiment concluded: ${experiment.name}`,
    type: 'Task',
    contact: experiment.owner,
    status: 'Pending',
  })
})

Query Examples

SDK

import { Experiment } from '@headlessly/experiments'

// Find all running A/B tests
const running = await Experiment.find({
  status: 'Running',
  type: 'ABTest',
})

// Get a specific experiment with full details
const experiment = await Experiment.get('experiment_hR5nVkTw')

// Create and start a prompt experiment
const promptTest = await Experiment.create({
  name: 'Support Agent Tone',
  type: 'PromptExperiment',
  hypothesis: 'A warmer tone increases customer satisfaction by 10%',
  primaryMetric: 'csat_score',
  trafficAllocation: 30,
  variants: [
    { key: 'control', name: 'Professional tone', weight: 50 },
    { key: 'warm', name: 'Warm and friendly tone', weight: 50 },
  ],
})
await Experiment.start(promptTest.$id)

MCP

headless.ly/mcp#search
{
  "type": "Experiment",
  "filter": { "status": "Running", "type": "ABTest" },
  "sort": { "startAt": "desc" },
  "limit": 20
}
headless.ly/mcp#fetch
{ "type": "Experiment", "id": "experiment_hR5nVkTw" }
headless.ly/mcp#do
const experiments = await $.Experiment.find({ status: 'Running' })
await $.Experiment.conclude('experiment_hR5nVkTw')

REST

# List running experiments
curl https://experiment.headless.ly/~acme/experiments?status=Running

# Get a specific experiment
curl https://experiment.headless.ly/~acme/experiments/experiment_hR5nVkTw

# Create an experiment
curl -X POST https://experiment.headless.ly/~acme/experiments \
  -H 'Content-Type: application/json' \
  -d '{"name": "Pricing Page V2", "type": "ABTest", "hypothesis": "New layout increases conversions"}'

# Start an experiment
curl -X POST https://experiment.headless.ly/~acme/experiments/experiment_hR5nVkTw/start

# Conclude an experiment
curl -X POST https://experiment.headless.ly/~acme/experiments/experiment_hR5nVkTw/conclude

On this page