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
| Procedure | Type | Access | Description |
|---|---|---|---|
getHoldoutConfig | query | admin | Get holdout test configuration |
updateHoldoutConfig | mutation | admin | Update holdout percentage and rotation |
rotateHoldoutSeed | mutation | admin | Rotate the holdout seed |
getLoopStatus | query | admin | Get comprehensive feedback loop status |
getHoldoutEvaluation | query | admin | Measure holdout lift |
processConversionEvent | mutation | tenant | Record 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
}
| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether holdout testing is active |
percentage | number (1--20) | Percentage of entities in holdout group |
seed | number | Random seed for consistent group assignment |
rotationDays | number (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
| Field | Type | Required | Description |
|---|---|---|---|
percentage | number (1--20) | No | Holdout group percentage |
rotationDays | number (30--365) | No | Days 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();
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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
days | number (1--365) | No | 30 | Evaluation window in days |
Response
{
"treatmentConversionRate": 0.12,
"holdoutConversionRate": 0.08,
"lift": 0.50,
"treatmentSize": 900,
"holdoutSize": 100,
"days": 30,
"statisticallySignificant": true
}
| Field | Description |
|---|---|
lift | Relative lift: (treatment - holdout) / holdout |
statisticallySignificant | Whether 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
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | Yes | Entity identifier (person, account, or opportunity) |
converted | boolean | Yes | Whether 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 });
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.
Related Pages
- Drift API -- Drift detection powered by feedback data
- Scoring API -- Scores improved by the feedback loop
- Audit API -- Holdout config changes in audit trail