Docs
Guides

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:

  1. Deploy the schema and definitions to test.
  2. Normalize timestamps to ISO 8601 with an explicit timezone.
  3. Sort records chronologically per customer when practical.
  4. Process bounded batches and checkpoint only after HTTP 202.
  5. Retry rejected batches after correcting the failing operation.
  6. Start with a small cohort and inspect state and timelines.
  7. 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-wait

A 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:

FieldMeaning
lifecycleCurrent and recomputed final lifecycle.
healthCurrent and recomputed overall score and component values.
segmentsCurrent and recomputed complete membership lists.
journeysCurrent and recomputed journey progress, including status, step data, and timestamps.
rulesRule executions that the replay would persist, including result, trigger, conditions, and action.
emissionsCorrective 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.json

The 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:

StatePersisted result
LifecycleReplayed lifecycle transition rows and final state
JourneysCurrent journey progress
SegmentsCurrent memberships
RulesReplayed rule logs
HealthCurrent 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_letter can 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.

On this page