Skip to content

Conversation

@rasmi
Copy link
Contributor

@rasmi rasmi commented Dec 11, 2025

This PR adds support for routing individual participants to pre-defined cohorts based on conditions. Unlike the existing transfer system (which waits for groups and creates new cohorts), this system can route participants immediately to cohorts defined in the experiment configuration.

Key Features

1. Cohort Definitions

  • New CohortDefinition type on Experiment for pre-defining cohorts with alias, name, and description
  • Cohorts are eagerly created when the experiment is initialized
  • generatedCohortId stored on definition for O(1) lookup during routing

2. Cohort-Specific Static Variables

  • Extended StaticVariableConfig with cohortValues field for per-cohort variable values
  • Keys are cohort aliases, transformed to cohortIds at cohort creation time
  • Enables different treatment conditions per cohort (e.g., different agent personas)

3. Condition-Based Routing with Group Composition

  • TransferGroup now uses composition: GroupComposition[] for flexible grouping
  • Each GroupComposition specifies: condition, minCount, maxCount
  • Supports both single-entry groups (all high scorers) and mixed-composition groups (2 high + 2 low scorers)
  • Added targetCohortAlias to TransferGroup for routing to existing cohorts
  • When targetCohortAlias is set, participants are routed directly to the specified cohort
  • When not set, existing behavior (create new cohort with TRANSFER_PENDING)

4. Direct Transfers following existing transfer behavior

  • Direct transfers use sequential transactions (one per participant) matching TRANSFER_PENDING behavior
  • handleConditionAutoTransfer returns DirectTransferInstructions instead of executing in-transaction
  • executeDirectTransfers executes transfers after the main transaction commits
  • Shared completeParticipantTransfer helper used by both frontend-initiated and backend-initiated transfers

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                    Transfer Trigger Points                          │
├─────────────────────────────────────────────────────────────────────┤
│  TRANSFER_PENDING (user-triggered)    │  Direct (backend-triggered) │
│  ───────────────────────────────────  │  ─────────────────────────  │
│  1. handleConditionAutoTransfer       │  1. handleConditionAutoTransfer
│     sets TRANSFER_PENDING             │     returns directTransferInstructions
│  2. User clicks accept                │  2. executeDirectTransfers loops
│  3. acceptParticipantTransfer         │  3. Each participant gets own txn
│     calls completeParticipantTransfer │     calling completeParticipantTransfer
└─────────────────────────────────────────────────────────────────────┘

Files Changed

Utils Package

  • experiment.ts - Added CohortDefinition type and cohortDefinitions field
  • cohort.ts - Added alias field to CohortConfig
  • variables.ts - Added cohortValues to StaticVariableConfig
  • variables.utils.ts - Updated generateStaticVariables to use cohortValues
  • transfer_stage.ts - Added GroupComposition interface, updated TransferGroup with composition and targetCohortAlias
  • *.validation.ts - Added corresponding validation schemas

Functions Package

  • experiment.utils.ts - Eager cohort creation from definitions
  • cohort.utils.ts - Added helpers for cohort creation and variable transformation
  • variables.utils.ts - Pass context to static variable generation
  • participant.utils.ts - Added executeDirectTransfers, completeParticipantTransfer, updated handleConditionAutoTransfer
  • participant.endpoints.ts - Execute direct transfers after transaction, refactored acceptParticipantTransfer

Tests

  • utils/src/variables.utils.test.ts - Added cohortValues tests for generateStaticVariables
  • functions/src/cohort.utils.test.ts - Unit tests for transformCohortValuesKeys
  • functions/src/cohort_definitions.integration.test.ts - Integration tests for createCohortInternal, findCohortByAlias, and executeDirectTransfers

Example Usage

// Experiment with cohort definitions
const experiment = {
  cohortDefinitions: [
    { id: 'def-1', alias: 'arm-pro-ai', name: 'Pro-AI Study Arm' },
    { id: 'def-2', alias: 'arm-skeptic', name: 'AI-Skeptic Arm' },
  ],
  variableConfigs: [{
    type: VariableConfigType.STATIC,
    scope: VariableScope.COHORT,
    value: '"control"',
    cohortValues: {
      'arm-pro-ai': '"pro_ai"',
      'arm-skeptic': '"skeptic"',
    },
  }],
};

// Transfer stage with condition-based routing (single composition entry)
const transferStage = {
  autoTransferConfig: {
    type: AutoTransferType.CONDITION,
    transferGroups: [{
      name: 'High Scorers',
      composition: [{
        id: 'high-score',
        condition: createComparisonCondition(..., '>=', 7),
        minCount: 1,
        maxCount: 5,
      }],
      targetCohortAlias: 'arm-pro-ai',  // Routes to existing cohort
    }],
  },
};

// Transfer stage with mixed-composition (2 high + 2 low scorers per cohort)
const mixedTransferStage = {
  autoTransferConfig: {
    type: AutoTransferType.CONDITION,
    transferGroups: [{
      name: 'Mixed Group',
      composition: [
        {
          id: 'high-score',
          condition: createComparisonCondition(..., '>=', 7),
          minCount: 2,
          maxCount: 2,
        },
        {
          id: 'low-score',
          condition: createComparisonCondition(..., '<', 5),
          minCount: 2,
          maxCount: 2,
        },
      ],
      // No targetCohortAlias - creates new cohort with TRANSFER_PENDING
    }],
  },
};

@rasmi rasmi force-pushed the transfer-conditions branch from b60a4b8 to 7a0a27d Compare December 11, 2025 20:56
@rasmi rasmi marked this pull request as draft December 11, 2025 21:15
@rasmi rasmi force-pushed the transfer-conditions branch 2 times, most recently from 331ee09 to 648ad3e Compare December 15, 2025 18:19
@rasmi rasmi force-pushed the transfer-conditions branch from 648ad3e to ef60231 Compare December 15, 2025 18:23
@rasmi rasmi force-pushed the transfer-conditions branch from 20be313 to 25e4af5 Compare December 15, 2025 19:37
@rasmi rasmi marked this pull request as ready for review December 15, 2025 19:38
@rasmi rasmi force-pushed the transfer-conditions branch from 604a5b0 to ae2e5d2 Compare December 15, 2025 20:59
@rasmi rasmi force-pushed the transfer-conditions branch 2 times, most recently from 915b3d8 to da8c625 Compare December 16, 2025 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant