Idempotency and budgets
IBEX uses more than one idempotency mechanism. They do not have the same store, the same failure mode, or the same guarantee. Choose the contract that matches the surface you call.
Surface comparison
| Surface | Header / key | Store | Duplicate behavior | Store outage |
|---|---|---|---|---|
| Chat completions | Idempotency-Key | Redis | Replay stored response | Fail open; no dedupe |
| Memory write | X-Idempotency-Key | PostgreSQL ledger | Replay stored 2xx | Fail closed with 503 |
| Worker enqueue | Idempotency-Key | PostgreSQL ledger | Replay original task ID | Fail closed with 503 |
| Budget hard cap | reservation key | PostgreSQL ledger | Typed owned/in-progress/conflict | Fail closed with 503 |
The difference is deliberate: a missed memory write or admission hold is a correctness failure, while a missed chat dedupe is a cost and duplicate-response risk.
Chat idempotency
POST /v1/chat/completions accepts an opaque Idempotency-Key of at most 256 characters. The key is scoped by organization and is not forwarded upstream.
| Case | Result |
|---|---|
| Same key, same body fingerprint | Replay stored status and body; no second provider call |
| Same key, different body | 409 IDEMPOTENCY_KEY_REUSE |
| Claim still active | 409 IDEMPOTENCY_IN_PROGRESS; retry after backoff |
| Transient upstream 429/5xx | Release the claim so the key can be retried |
| Redis unavailable or timed out | Request proceeds without dedupe |
stream=true with a key | 400 VALIDATION_ERROR |
A replay does not create a second session checkpoint or trace.
Memory idempotency
POST /v1/memories uses a PostgreSQL ledger scoped by (org, agent, key).
| Case | Result |
|---|---|
| Same key, same body | Replay the stored 2xx |
| Same key, different body | 409 IDEMPOTENCY_CONFLICT |
| Claim active | 409 IDEMPOTENCY_IN_PROGRESS plus Retry-After |
| PostgreSQL unavailable | 503 IDEMPOTENCY_UNAVAILABLE |
| Row committed, claim not finalized | 503; never report success over an unfinished claim |
Worker enqueue
The extraction enqueue endpoint requires both a static bearer and an Idempotency-Key. The full normalized payload, including every turn, is hashed before dispatch. A changed payload under the same key conflicts. A dispatch failure releases the claim. If dispatch succeeds but finalization fails, the endpoint returns 503 and the 120-second lease governs the retry window.
Budget admission
For an organization with an active hard_cap period, the proxy claims a durable hold before provider work.
| Outcome | Behavior |
|---|---|
owned | Admit; finalize or release after the provider call |
exceeded | 402 BUDGET_EXCEEDED; no provider call |
in_progress | Retry after backoff; do not dispatch twice |
conflict | Reject the reused key with a different fingerprint |
no_cap | Continue without a hold |
| Ledger unavailable | 503 BUDGET_UNAVAILABLE; fail closed |
A provider response without usage must finalize as estimated, not complete.
What is not guaranteed
- No exactly-once provider call.
- No exactly-once Celery execution.
- No final reconciliation evidence for extraction usage.
- No hosted/HA replay evidence.
- No cross-language canonicalization acceptance.
Use a stable key per logical operation, retain the key with your request record, and retry only transient failures.

