Backfill and recomputation
Safely ingest delayed history and rebuild derived customer state.
Backfill adds immutable historical events. Recompute rebuilds derived state from retained events under one pinned configuration snapshot. They solve different parts of a correction workflow.
Understand late events
Track events accept an ISO 8601 timestamp with timezone. There is no timestamp-age validation cutoff at ingestion, but plan retention can later remove old raw events. Recompute replays only events that are still retained.
Event windows use occurrence time. last_event_at remains the latest known timestamp, so an old
event does not make an inactive customer look newly active.
Late ingestion loads the customer's full retained event history but starts from current derived state. Terminal lifecycle states and completed journeys can therefore differ from a clean replay from zero. Use recompute after authoritative out-of-order corrections.
Run a restartable backfill
There is no separate import endpoint or kite backfill command. Send historical tracks through
the SDK or POST /v1/events/batch, with 1 to 1,000 operations per request.
{
"events": [
{
"type": "track",
"customerId": "account_42",
"event": "report.exported",
"properties": { "format": "csv" },
"timestamp": "2026-04-02T15:04:05Z",
"idempotencyKey": "warehouse:exports:98765"
}
]
}Use deterministic idempotency keys derived from immutable source record IDs. Repeating the same
key and payload is safe; reusing a key with a changed customer, event, properties, or timestamp
returns 409 IDEMPOTENCY_CONFLICT.
Recommended procedure:
- Deploy the schema and definitions to
test. - Normalize timestamps to ISO 8601 with an explicit timezone.
- Sort records chronologically per customer when practical.
- Process bounded batches and checkpoint only after HTTP
202. - Retry rejected batches after correcting the failing operation.
- Start with a small cohort and inspect state and timelines.
- Ingest the complete data set, then run dry-run and persisted recompute.
Batch validation is all-or-nothing. One invalid operation or idempotency conflict rejects the request rather than partially accepting earlier operations.
Treat traits as current state
Identify operations are current-state mutations, not replayable historical facts. The SDK does not accept an identify timestamp. During recompute, current customer traits apply throughout the entire replay.
If historical plan or entitlement affects a rule, encode the value that was true at the time in immutable event properties instead of relying only on current traits.
Run recompute
Choose exactly one scope:
kite recompute --customer account_42 --dry-run --env test
kite recompute --customer account_42 --env test
kite recompute --all --dry-run --env test
kite recompute --all --env test
kite recompute --all --env test --no-waitA persisted global run asks you to type the target environment unless --yes is supplied. A
global dry-run does not require confirmation. There is no segment-scoped recompute.
At queue time, the job pins:
- the active config version;
- the evaluation timestamp;
- the customer population boundary.
Customers created after queueing are excluded. A customer ID that does not exist creates a valid zero-customer job rather than returning not found.
Interpret dry-run results
A dry-run reports both aggregate impact and a per-customer diff. The aggregate counters include:
- customers whose final lifecycle changes;
- customers whose final health changes;
- segment memberships entered;
- segment memberships exited.
Each affected customer includes:
| Field | Meaning |
|---|---|
lifecycle | Current and recomputed final lifecycle. |
health | Current and recomputed overall score and component values. |
segments | Current and recomputed complete membership lists. |
journeys | Current and recomputed journey progress, including status, step data, and timestamps. |
rules | Rule executions that the replay would persist, including result, trigger, conditions, and action. |
emissions | Corrective events that the recompute would append, with source, metadata, and timestamp. |
Customers with no state difference, rule execution, or emission are omitted from customers.
Rules and emissions are predicted effects rather than before/after state, so they are returned
as lists. The result does not count or separately diff every intermediate historical lifecycle
transition.
Use --json to retain the complete machine-readable result:
kite recompute --customer account_42 --dry-run --env test --json \
> recompute-preview.jsonThe response keeps the aggregate counters and adds result.customers:
{
"status": "completed",
"dry_run": true,
"result": {
"lifecycleChanged": 1,
"healthChanged": 1,
"segmentsEntered": 1,
"segmentsExited": 0,
"customers": [
{
"customerId": "account_42",
"lifecycle": { "before": "new", "after": "activated" },
"health": {
"before": { "overall": 42, "components": { "usage": 30 } },
"after": { "overall": 78, "components": { "usage": 85 } }
},
"segments": { "before": [], "after": ["power_users"] },
"journeys": {
"before": [],
"after": [
{
"journeyId": "activation",
"status": "complete",
"currentStep": "first_value",
"stepsData": {},
"startedAt": "2026-04-02T15:04:05.000Z",
"completedAt": "2026-04-03T09:10:00.000Z",
"expiredAt": null
}
]
},
"rules": [
{
"ruleId": "mark_power_user",
"result": "pass",
"triggeredBy": "event:report.exported",
"conditions": { "lifecycle": "activated" },
"action": { "type": "add_to_segment", "segment": "power_users" },
"executedAt": "2026-04-03T09:10:00.000Z"
}
],
"emissions": [
{
"eventType": "customer.activated",
"source": "lifecycle",
"metadata": {
"previous_lifecycle": "new",
"new_lifecycle": "activated"
},
"timestamp": "2026-04-03T09:10:00.000Z"
}
]
}
]
}
}The human-readable CLI output prints the same customer sections after the summary. The dashboard shows each customer in an expandable row.
Understand persisted changes
A persisted run leaves raw events and current traits unchanged. It rebuilds:
| State | Persisted result |
|---|---|
| Lifecycle | Replayed lifecycle transition rows and final state |
| Journeys | Current journey progress |
| Segments | Current memberships |
| Rules | Replayed rule logs |
| Health | Current score and one score record for the replay result, not full historical scores |
Recompute may append expiration-time and other synthetic emissions without deleting old timeline rows. Matching webhooks can therefore receive new delivery IDs for corrective outcomes.
Plan for partial progress
Global recompute is not one environment-wide transaction. Each customer commits independently, and ingestion can continue between customer transactions. If a job fails:
- previously committed customers remain changed;
- retries resume after committed progress;
- after the configured attempts,
dead_lettercan contain partial persisted work.
Only one job runs at a time per project and environment, but duplicate jobs can queue and execute serially. Do not immediately enqueue another global run after failure. Inspect status, progress, attempts, and error first.
When to recompute
- After historical backfill or corrected timestamps.
- After changing lifecycle, journey, segment, rule, or health semantics for existing customers.
- After repairing traits that materially affect current calculations, accepting current-trait replay semantics.
- After rollback when existing state must match the restored version.
Schedule large persisted runs outside peak ingestion periods. Configuration deployment alone does not rebuild existing customers.