Skip to main content

Feedback Loop API

The feedbackLoop router manages the online learning feedback loop that continuously improves scoring accuracy. It provides holdout testing configuration, loop health monitoring, lift measurement, and the core conversion event ingestion endpoint.

Router namespace: feedbackLoop

Source: src/server/trpc/routers/feedback-loop.ts

Procedures

ProcedureTypeAccessDescription
getHoldoutConfigqueryadminGet holdout test configuration
updateHoldoutConfigmutationadminUpdate holdout percentage and rotation
rotateHoldoutSeedmutationadminRotate the holdout seed
getLoopStatusqueryadminGet comprehensive feedback loop status
getHoldoutEvaluationqueryadminMeasure holdout lift
processConversionEventmutationtenantRecord a conversion signal

getHoldoutConfig

Returns the current holdout test configuration. The holdout group is a random subset of entities excluded from ML-boosted scoring, used to measure the model's incremental lift.

feedbackLoop.getHoldoutConfig.queryOptions()

Access: Admin only

Input

None.

Response

{
"enabled": true,
"percentage": 10,
"seed": 42,
"rotationDays": 90
}
FieldTypeDescription
enabledbooleanWhether holdout testing is active
percentagenumber (1--20)Percentage of entities in holdout group
seednumberRandom seed for consistent group assignment
rotationDaysnumber (30--365)Days between automatic seed rotations

Example

const trpc = useTRPC();
const { data: config } = useQuery(
trpc.feedbackLoop.getHoldoutConfig.queryOptions()
);

updateHoldoutConfig

Updates the holdout test configuration. Changes take effect on the next scoring run.

feedbackLoop.updateHoldoutConfig.mutationOptions(options)

Access: Admin only

Input

FieldTypeRequiredDescription
percentagenumber (1--20)NoHoldout group percentage
rotationDaysnumber (30--365)NoDays between seed rotations

Response

{
"success": true
}

Example

const trpc = useTRPC();

const mutation = useMutation(
trpc.feedbackLoop.updateHoldoutConfig.mutationOptions({})
);

mutation.mutate({ percentage: 15, rotationDays: 60 });

rotateHoldoutSeed

Generates a new random seed for holdout group assignment. This clears all current holdout assignments, causing entities to be randomly reassigned on the next scoring run.

feedbackLoop.rotateHoldoutSeed.mutationOptions(options)

Access: Admin only

Input

None.

Response

{
"success": true,
"newSeed": 8472
}

Example

const trpc = useTRPC();
const queryClient = useQueryClient();

const mutation = useMutation(
trpc.feedbackLoop.rotateHoldoutSeed.mutationOptions({
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: trpc.feedbackLoop.getHoldoutConfig.queryKey(),
});
},
})
);

mutation.mutate();
warning

Rotating the seed reassigns all entities to new holdout/treatment groups. This resets any in-progress lift measurement. Only rotate when you have sufficient data from the current period.


getLoopStatus

Returns a comprehensive status overview of the entire feedback loop, including online learning state, holdout configuration, drift detection, and push status.

feedbackLoop.getLoopStatus.queryOptions()

Access: Admin only

Input

None.

Response

{
"onlineLearning": {
"enabled": true,
"eventsProcessed": 1247,
"lastEventAt": "2026-03-20T15:45:00.000Z"
},
"holdout": {
"enabled": true,
"percentage": 10,
"rotationDays": 90,
"nextRotation": "2026-06-01T00:00:00.000Z"
},
"drift": {
"isDrifted": false,
"lastChecked": "2026-03-20T14:30:00.000Z"
},
"push": {
"lastPushAt": "2026-03-20T12:00:00.000Z",
"modelVersion": "v3.2"
}
}

Example

const trpc = useTRPC();
const { data: status } = useQuery(
trpc.feedbackLoop.getLoopStatus.queryOptions()
);

getHoldoutEvaluation

Measures the incremental lift of the ML model by comparing conversion rates between the holdout group (no ML scoring) and the treatment group (ML-boosted scoring).

feedbackLoop.getHoldoutEvaluation.queryOptions(input)

Access: Admin only

Input

FieldTypeRequiredDefaultDescription
daysnumber (1--365)No30Evaluation window in days

Response

{
"treatmentConversionRate": 0.12,
"holdoutConversionRate": 0.08,
"lift": 0.50,
"treatmentSize": 900,
"holdoutSize": 100,
"days": 30,
"statisticallySignificant": true
}
FieldDescription
liftRelative lift: (treatment - holdout) / holdout
statisticallySignificantWhether the sample size is sufficient for confidence

Example

const trpc = useTRPC();
const { data: evaluation } = useQuery(
trpc.feedbackLoop.getHoldoutEvaluation.queryOptions({ days: 90 })
);

if (evaluation) {
console.log(`ML model lift: ${(evaluation.lift * 100).toFixed(1)}%`);
}

processConversionEvent

Records a conversion signal for an entity. This is the core feedback mechanism that drives online learning. Events are forwarded to the ML sidecar for incremental model updates.

feedbackLoop.processConversionEvent.mutationOptions(options)

Input

FieldTypeRequiredDescription
entityIdstringYesEntity identifier (person, account, or opportunity)
convertedbooleanYesWhether the entity converted

Response

{
"success": true
}

Example

const trpc = useTRPC();

const mutation = useMutation(
trpc.feedbackLoop.processConversionEvent.mutationOptions({})
);

// Record a conversion
mutation.mutate({ entityId: "opp_abc123", converted: true });

// Record a non-conversion (lost deal)
mutation.mutate({ entityId: "opp_def456", converted: false });
Automated Ingestion

In most setups, conversion events are ingested automatically via Salesforce sync (closed-won/closed-lost). Use this procedure for manual feedback or custom conversion definitions.

  • Drift API -- Drift detection powered by feedback data
  • Scoring API -- Scores improved by the feedback loop
  • Audit API -- Holdout config changes in audit trail