You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Clients poll GET /v1/responses/{id} while status is queued or in_progress.
POST /v1/responses/{id}/cancel cancels an in-flight response; repeated cancellation is idempotent.
background=true is valid with store=false. Temporary operational state must still be retained long enough for polling and cancellation, without turning store=false into durable application storage.
background=true is valid with stream=true. A client can resume the stream with GET /v1/responses/{id}?stream=true&starting_after={sequence_number} when the response was originally created with streaming enabled.
The existing stream + background and background + store=false rejection rules are obsolete and must be removed as part of this work or a prerequisite conformance change.
Architecture boundary
The filter schedules and exposes the OpenAI lifecycle; Praxis core owns background task lifecycle and execution through the configured proxy machinery.
A worker must run the request through the applicable routing, load balancing, upstream exchange, response filters, persistence, guardrails, and observability path. Do not implement this as:
a hidden direct backend call from the filter;
a loopback HTTP request into the public listener; or
a second proxy implementation embedded in background_jobs.
The required Praxis jobs/task lifecycle work is tracked by praxis-proxy/praxis#807. The AI implementation must be based on the resulting core contract rather than inventing an incompatible local runtime.
Lifecycle
Enqueue
For a locally handled POST /v1/responses request with background=true:
Generate the stable response ID.
Reserve bounded queue capacity before persisting queued.
Persist the initial response object with status=queued.
Return the queued response immediately for non-streaming requests.
The worker atomically claims queued -> in_progress.
The worker executes the configured Responses path.
Completion atomically transitions in_progress to completed, incomplete, or failed.
The locally generated response ID must remain the public and stored ID even if the inference backend generates a different provider response ID.
Queue exhaustion returns a structured 429 response, preferably with Retry-After, and must not leave an orphaned stored response.
State transitions
Storage updates must use compare-and-set semantics:
A worker may write a terminal result only while the stored status is still in_progress. Cancellation must not be overwritten by a concurrently completing worker.
A dequeued job that can no longer claim queued -> in_progress must be skipped.
Timeout and failure
Per-request execution timeout is configurable; default 5 minutes.
Timeout, worker failure, or task panic must leave a terminal stored response rather than queued or in_progress.
Worker failure must not terminate the worker pool.
Shutdown and dynamic configuration reload behavior must be explicit: jobs are either drained, transferred, or moved to a documented terminal state.
Polling
GET /v1/responses/{id} returns the latest stored response object, including queued, in_progress, and terminal states.
Tenant isolation and the existing not-found/access-denied behavior must apply to polling, cancellation, and resumed streams.
Cancellation
POST /v1/responses/{id}/cancel:
queued: atomically update to cancelled; the worker skips it if later dequeued.
in_progress: request task cancellation and atomically update to cancelled.
cancelled: return the stored response unchanged (idempotent).
other terminal states: return the documented structured error without changing the response.
The cancel route must bypass/defer request-body format validation so an empty POST body is not rejected before the handler runs.
Cancellation and task ownership must remain correct when polling/cancellation traffic reaches another worker or process. If the first implementation is intentionally single-process, document and enforce that limitation rather than silently exposing unsafe multi-process behavior.
Streaming and resumption
For background=true, stream=true:
Begin returning Responses SSE events while processing continues independently of the client connection.
Persist or otherwise retain events with their sequence_number.
Client disconnect must not cancel the job.
Resume requests use starting_after and return only later events.
A new stream can only be started for a response originally created with stream=true.
Event retention must be bounded by bytes and time and must preserve terminal events.
This is a separate implementation phase from the non-streaming queue/polling foundation, but it remains part of the umbrella compatibility contract.
Resource bounds
Queue capacity: configurable, default 100 jobs.
Workers: configurable, default 10.
Timeout: configurable, default 300 seconds.
Retained queued request bodies and resumable event logs must be bounded by bytes as well as item count.
Do not clone request bodies, provider payloads, or streaming chunks merely to pass them between the request and worker paths. Move owned data at the background-job boundary or store only the data needed to resume processing.
Purpose
Implement OpenAI Responses background mode for locally orchestrated Responses requests: asynchronous execution, bounded scheduling, polling, cancellation, timeout handling, and resumable streaming.
This filter is opt-in. When it is not configured, backends that natively support background mode continue to receive the request unchanged.
Current OpenAI contract
The implementation must follow the current OpenAI background mode documentation:
background=truestarts asynchronous processing.GET /v1/responses/{id}while status isqueuedorin_progress.POST /v1/responses/{id}/cancelcancels an in-flight response; repeated cancellation is idempotent.background=trueis valid withstore=false. Temporary operational state must still be retained long enough for polling and cancellation, without turningstore=falseinto durable application storage.background=trueis valid withstream=true. A client can resume the stream withGET /v1/responses/{id}?stream=true&starting_after={sequence_number}when the response was originally created with streaming enabled.The existing
stream + backgroundandbackground + store=falserejection rules are obsolete and must be removed as part of this work or a prerequisite conformance change.Architecture boundary
The filter schedules and exposes the OpenAI lifecycle; Praxis core owns background task lifecycle and execution through the configured proxy machinery.
A worker must run the request through the applicable routing, load balancing, upstream exchange, response filters, persistence, guardrails, and observability path. Do not implement this as:
background_jobs.The required Praxis jobs/task lifecycle work is tracked by praxis-proxy/praxis#807. The AI implementation must be based on the resulting core contract rather than inventing an incompatible local runtime.
Lifecycle
Enqueue
For a locally handled
POST /v1/responsesrequest withbackground=true:queued.status=queued.queued -> in_progress.in_progresstocompleted,incomplete, orfailed.The locally generated response ID must remain the public and stored ID even if the inference backend generates a different provider response ID.
Queue exhaustion returns a structured 429 response, preferably with
Retry-After, and must not leave an orphaned stored response.State transitions
Storage updates must use compare-and-set semantics:
A worker may write a terminal result only while the stored status is still
in_progress. Cancellation must not be overwritten by a concurrently completing worker.A dequeued job that can no longer claim
queued -> in_progressmust be skipped.Timeout and failure
queuedorin_progress.Polling
GET /v1/responses/{id}returns the latest stored response object, includingqueued,in_progress, and terminal states.Tenant isolation and the existing not-found/access-denied behavior must apply to polling, cancellation, and resumed streams.
Cancellation
POST /v1/responses/{id}/cancel:queued: atomically update tocancelled; the worker skips it if later dequeued.in_progress: request task cancellation and atomically update tocancelled.cancelled: return the stored response unchanged (idempotent).The cancel route must bypass/defer request-body format validation so an empty POST body is not rejected before the handler runs.
Cancellation and task ownership must remain correct when polling/cancellation traffic reaches another worker or process. If the first implementation is intentionally single-process, document and enforce that limitation rather than silently exposing unsafe multi-process behavior.
Streaming and resumption
For
background=true, stream=true:sequence_number.starting_afterand return only later events.stream=true.This is a separate implementation phase from the non-streaming queue/polling foundation, but it remains part of the umbrella compatibility contract.
Resource bounds
Config
Additional byte and retention limits for queued payloads and resumable events must be defined before implementation is considered complete.
Dependencies
Sub-issues
Resumable background streaming should be tracked as a separately reviewable change under this umbrella issue.
Required tests
starting_after, retention limits, and terminal events.examples/configs/.Reference