RGreview-game-corethe engine behind concept-first review games

Feature detail

Per-question experiment gating

Deterministic cohort resolution, exposure records, and served-question metadata for experimental or LLM-variant questions — so gated content stays visible, auditable, and WF-testable before it reaches learners.

workflow/interventions

Worked example

Gated experiment exposure

Same turn + same source question produce the same resolution key and arm; served questions carry experiment metadata that WF can gate on.

Input

{
  "resolution": {
    "sessionId": "sess-42",
    "currentTurn": 7,
    "sourceQuestionId": "factoring-q1",
    "conceptId": "factoring",
    "targetLayer": "support"
  },
  "experimentKey": "llm-support-v1"
}

Output

{
  "resolutionKey": "sess-42:7:factoring-q1:factoring:support",
  "arm": "control",
  "exposure": {
    "exposureId": "exp-0001",
    "resolutionKey": "sess-42:7:factoring-q1:factoring:support",
    "learnerId": "learner@example.edu",
    "sessionId": "sess-42",
    "experimentKey": "llm-support-v1",
    "cohortMode": "randomized",
    "resolvedArm": "control",
    "conceptId": "factoring",
    "sourceQuestionId": "factoring-q1",
    "servedQuestionId": "factoring-q1",
    "unitId": "unit-3",
    "sectionId": "section-3a",
    "targetLayer": "support",
    "interventionKind": "llm-support",
    "createdAt": "2026-04-18T12:30:00Z"
  },
  "servedQuestion": {
    "id": "factoring-q1",
    "sourceQuestionId": "factoring-q1",
    "experimentKey": "llm-support-v1",
    "experimentArm": "control",
    "exposureId": "exp-0001",
    "questionOrigin": "llm-generated",
    "interventionKind": "llm-support"
  }
}

Real source excerpt

Intervention cohort resolution and exposure contracts

src/workflow/interventions.ts
export type WorkflowInterventionCohortMode = 'off' | 'randomized' | 'control' | 'treatment';

export type ActiveWorkflowInterventionCohortMode = Exclude<WorkflowInterventionCohortMode, 'off'>;

export type ResolvedWorkflowInterventionArm = 'control' | 'treatment';

export interface WorkflowInterventionResolutionInput {
  sessionId: string;
  currentTurn: number;
  sourceQuestionId: string;
  conceptId: string;
  targetLayer?: string | null;
}

export interface WorkflowInterventionExposureMetadata<
  TCohortMode extends string = ActiveWorkflowInterventionCohortMode,
> {
  exposureId: string;
  resolutionKey: string;
  cohortMode: TCohortMode;
  resolvedArm: ResolvedWorkflowInterventionArm;
}

export interface WorkflowInterventionExposureRecord<
  TExperimentKey extends string = string,
  TInterventionKind extends string = string,
  TCohortMode extends string = ActiveWorkflowInterventionCohortMode,
> extends WorkflowInterventionExposureMetadata<TCohortMode> {
  learnerId: string;
  sessionId: string;
  experimentKey: TExperimentKey;
  conceptId: string;
  sourceQuestionId: string;
  servedQuestionId: string;
  unitId: string;
  sectionId: string;
  targetLayer: string;
  interventionKind: TInterventionKind;
  createdAt: string;
}

export interface WorkflowInterventionExposureStoreInput<
  TExperimentKey extends string = string,
  TInterventionKind extends string = string,
  TCohortMode extends string = ActiveWorkflowInterventionCohortMode,
> {
  learnerId: string;
  sessionId: string;
  experimentKey: TExperimentKey;
  cohortMode: TCohortMode;
  resolutionKey: string;
  conceptId: string;
  sourceQuestionId: string;
  servedQuestionIdByArm: Record<ResolvedWorkflowInterventionArm, string>;
  unitId: string;
  sectionId: string;
  targetLayer: string;
  interventionKind: TInterventionKind;
}

export interface WorkflowInterventionServedQuestionMetadata<
  TExperimentKey extends string = string,
  TQuestionOrigin extends string = string,
  TInterventionKind extends string = string,
> {
  experimentKey: TExperimentKey;
  experimentArm: ResolvedWorkflowInterventionArm;
  exposureId: string;
  sourceQuestionId: string;
  questionOrigin: TQuestionOrigin;
  interventionKind?: TInterventionKind;
}

export interface WorkflowInterventionQuestionLike {
  id: string;
  sourceQuestionId?: string | null;
}

export interface WorkflowInterventionQuestionOverride<
  TQuestion,
  TCohortMode extends string = ActiveWorkflowInterventionCohortMode,
> {
  question: TQuestion;
  served: boolean;
  rejected: boolean;
  reason: string;
  cohortMode?: TCohortMode;
  resolvedArm?: ResolvedWorkflowInterventionArm;
  exposureId?: string | null;
  resolutionKey?: string | null;
}