Request Lifecycle
Every governed request that reaches Keel travels the same lifecycle, regardless of which execution surface originated it. This page walks the lifecycle phase by phase and shows how sync, async, and streaming requests differ at each stage.
For the trust-boundary view, see Execution Lifecycle. For the chronological replay of a single request after the fact, see Timeline Replay.
At a glance
Lifecycle keys
A governed request is identified by two stable keys that you can use across surfaces and over time:
request_id— the lifecycle lookup key. Carries through ingress, dispatch, accounting, and timeline reconstruction. Pass this key toGET /v1/requests/{request_id}/timelineto replay the full chronology.permit_id— the canonical decision-record key. Stable across the entire lifecycle even if the request is retried, and the right key to attach to long-lived audit references.
Async surfaces add a third key, job_id, that pairs with the request_id for the duration of background processing.
Phase 1 — Ingress and authentication
The route authenticates the project-scoped API key, binds request metadata, and constructs the canonical request shape needed for permit evaluation:
- a canonical permit request for permit-first flows, or
- an execution intent for execution-backed routes
Auth failures and request-freshness failures stop the request before any governance work runs. See Request Freshness for the freshness contract on execution surfaces.
Phase 2 — Permit evaluation
Keel evaluates the request against the project’s governance configuration before any provider call. The evaluation considers:
- canonicalized request fields (provider, model, operation)
- active policy rows (project-scoped, then organization-scoped)
- pricing availability and estimated cost
- budget caps, rate limits, and threshold guardrails
- billing and entitlement gates
- prompt firewall state on supported execution routes
- routing metadata when the route supplies it
The result is a persisted permit. The permit is the canonical governance record for both permit-first and execution-backed paths. See Decision Model for the full evaluation contract.
If the decision is deny or challenge, the lifecycle ends here for permit-first flows. Execution-backed routes also stop before dispatch — the permit captures the denial and is the durable record.
Phase 3 — Dispatch
If the permit decision is allow, execution-backed routes continue:
- resolve the project’s provider credential
- build a provider request shaped for the chosen route
- dispatch through the provider adapter
- attempt fallback when the route and adapter support it
Permit-first stops earlier. Keel does not make the provider call on that path — your application does, using its own provider credential. See Permits for the permit-first end-to-end flow.
Phase 4 — Usage reconciliation
After a non-stream provider response, Keel reconciles:
- the provider-served model identity against the authorized model
- input and output token usage
- estimated versus actual usage
- final cost from configured pricing
If the served model is outside the authorized model boundary, reconciliation fails with a deny-style error rather than silently accepting the drift.
For permit-first flows, this phase happens when your application calls POST /v1/permits/{permit_id}/usage to close the permit out with actual usage and verification material.
The reconciliation surface is the part of the lifecycle that produces the accounting_disposition field on permit records and the per-permit reconciliation states. See Reconciliation for the full pillar.
Phase 5 — Terminal accounting
Terminal accounting is the durable boundary for cost and usage closeout. Keel persists the terminal record even when later closeout work is interrupted:
- if a stream reaches dispatch but the normal terminal persistence path fails, Keel retries through a hardened fallback path rather than leaving an accounting gap
- on streaming interruption, observed streamed output is used as a lower bound so the terminal record does not collapse to zero
- when a permit-first reservation ages past its configured stale threshold without a closeout, Keel reconciles it to
expiredso the stale reservation does not remain pinned to budget windows. A later valid closeout can still move the permit tocompleted
The narrower honest limit: when a provider never sends final usage on a stream, Keel may persist estimated or lower-bound terminal usage rather than exact final usage. Those rows are marked as estimated final accounting, not exact billing truth.
Phase 6 — Lifecycle persistence
Lifecycle reconstruction reads from the persisted permit, request state, usage records, execution events, and any async-job or realtime-session rows that the request produced. These records overlap intentionally — execution events add chronology but do not replace the permit or the accounting rows as the source of truth.
Phase 7 — Timeline replay
GET /v1/requests/{request_id}/timeline rebuilds the chronological lifecycle from those stored rows. The timeline emits normalized events for permit decisions, firewall blocks, routing selection, fallback, execution start, provider send and receive, usage, request completion, and async-job and webhook events when applicable.
The timeline is reconstructed from current persisted evidence. It is not a separate append-only event store. See Timeline Replay for the full event vocabulary and response shape.
Sync, async, and streaming differences
Three execution shapes sit on top of the same seven phases. The differences are at the dispatch and accounting boundary, not in governance.
Sync
- one request, one response
- permit, dispatch, reconcile, and ledger typically complete inline before the response returns
Async
- job submission persists queue state first; the caller receives a job acknowledgement, not a final result
- background processing runs the shared execution stages later
- callback delivery (when configured) adds its own lifecycle events for
webhook.delivery_attempted,webhook.delivery_succeeded, andwebhook.delivery_failed
Streaming
- dispatch can succeed before final usage is known; the route delivers events to the caller as they arrive
- terminal accounting may defer until stream close. Stream closeout retries through the hardened fallback path rather than tolerating an accounting gap after successful dispatch.
- public streaming support is narrower than sync support and remains route-specific. See the per-route maturity matrix on Surface Maturity.
Webhook delivery
Keel has two outbound delivery systems. They are intentionally different and should not be treated as one feature.
| System | Trigger | Retries survive process restart | Secret |
|---|---|---|---|
Webhook subscriptions (/v1/webhooks) | Matching governance event | Yes (durable queue) | Per-subscription |
Async job callbacks (POST /v1/jobs callback_url) | Async job completion or failure | No (in-process retry loop) | Global shared secret |
Webhook subscriptions are at-least-once with bounded durable retries and a terminal dead state. Async job callbacks are best-effort with bounded in-process retries; if the worker dies mid-retry, the remaining retry budget is lost. Final delivery status is recorded on the async job record.
Callers that need durable delivery today should use webhook subscriptions.
What this lifecycle does and does not claim
- Every Keel-managed execution route runs the same governance phase (Phase 2) before any provider call. Allow decisions are durable; deny decisions stop dispatch.
- The permit is the canonical governance record for both permit-first and execution-backed paths.
- Lifecycle reconstruction reads only persisted evidence. Keel does not invent latency, usage, or timing values that were never recorded.
- Successful prompt-firewall passes do not always appear as explicit lifecycle events. Only
firewall.blockedevents are emitted. - Streaming behavior varies by route family. Public streaming coverage is narrower than total sync coverage.
- Permit-first closeout is not execution-bound — Keel does not directly observe the provider call on that path.