Full Flow Agentic.Visualizer
Full Flow Agentic.Visualizer configuration for openai/responses.
Versions marked “overview” do not contain this page. Selecting one opens that version’s documentation overview.
Category: Setup-dependent integration
Task: Full Flow Agentic.Visualizer configuration for openai/responses.
Prerequisites: The external service, credentials, or certificates referenced by this configuration.
Run it: Use ghcr.io/praxis-proxy/ai:0.8.0 and follow the container quickstart to mount and start the configuration.
This configuration comes from the selected release. The example has not been run here; external services are not bundled.
Download the source file.
Companion resources from the same snapshot:
- agentic-loop.yaml
- full-flow-agentic.yaml
- web_search_nonstreaming.json
- native-basic-nonstream.json
- native-basic-stream.json
- chat-basic-stream.json
- chat-web-search.json
- websocket-codex-0.144.1.json
pipeline:
filters:
- id: trace_context
name: trace_context
group: trace
filter_type: trace_context
phase: request-phase; establishes typed correlation BEFORE the IRR snapshots request extensions; no response phase
config: {}
promotes: establishes a typed TraceContext extension carried across every IRR iteration; child callouts retain this request/trace identity while Praxis core assigns a fresh span ID to each outbound hop
scenarios:
S1:
state: active
note: establishes TraceContext for the single inference round
S2:
state: active
note: establishes TraceContext for the streaming round
S3:
state: active
note: establishes TraceContext (independent of the store flag)
S4:
state: active
note: establishes TraceContext for the multi-turn request
S5:
state: active
note: TraceContext propagates into the file_search vector-store callout (fresh span per hop)
S6:
state: active
note: TraceContext propagates into every MCP resolve/dispatch hop (fresh span each)
S7:
state: active
note: TraceContext propagates into the Brave web_search callout (fresh span per hop)
S8:
state: active
note: establishes TraceContext before the store answers the GET retrieve
S9:
state: active
note: establishes TraceContext on the WS handshake before the direct-services branch
S10:
state: active
note: 'establishes TraceContext for the single inference round'
S11:
state: active
note: establishes TraceContext before the dedicated-service direct-services branch
S12:
state: active
note: establishes TraceContext before the direct-openai passthrough forward
S13:
state: active
note: establishes TraceContext for the managed chat-completions round
S14:
state: active
note: establishes TraceContext for the native Chat Completions ingress
- id: state_owner
name: state_owner
group: identity
filter_type: state_owner
phase: request-phase header mapping only; no response phase
config:
mode: trusted_headers
tenant.header: x-auth-tenant
issuer.static: urn:example:gateway
subject.header: x-auth-user
promotes: normalized StateOwner extension (tenant/issuer/subject) carried across IRR iterations
scenarios:
S1:
state: active
note: maps x-auth-* ingress into owner context (no auth verification)
S2:
state: active
note: same owner mapping for streaming create
S3:
state: active
note: owner mapped even though store:false
S4:
state: active
note: owner mapped for multi-turn create
S5:
state: active
note: owner mapped; forwarded to file_search callout
S6:
state: active
note: owner mapped; forwarded to MCP connector
S7:
state: active
note: owner underpins per-user brave_search slot selection
S8:
state: active
note: runs before store answers the GET retrieve
S9:
state: active
note: owner mapped on WS handshake before the direct-services branch
S10:
state: active
note: 'maps x-auth-* ingress into owner context (no auth verification)'
S11:
state: active
note: owner mapped before the dedicated-service direct-services branch
S12:
state: active
note: owner mapped before the direct-openai forward
S13:
state: active
note: owner mapped for the managed chat-completions round
S14:
state: active
note: owner mapped for the native Chat Completions ingress
- id: callout_credentials
name: callout_credentials
group: identity
filter_type: callout_credentials
phase: 'request-phase security filter; captures typed credential slots AND the trusted-boundary MCP authorization assertion (unified in #1341); does not authenticate provenance and never parses the assertion'
config:
credentials:
- slot: brave_search
source_header: x-user-brave-key
- slot: ogx_files
source_header: x-user-ogx-key
- slot: mcp_gateway
source_header: x-user-mcp-key
assertions:
- slot: mcp_gateway
source_header: x-mcp-authorized
promotes: 'the single establishing filter for scoped secrets: captures per-user brave_search / ogx_files / mcp_gateway credential slots (from x-user-brave-key / x-user-ogx-key / x-user-mcp-key) and STRIPS those raw x-user-* ingress headers; unified in #1341 it ALSO captures the mcp_gateway authorization assertion — unconditionally deleting any client-supplied x-mcp-authorized then setting the verified, short-lived assertion. Each dispatcher selects only its slot; the assertion is forwarded unchanged ONLY to configured connector_id targets (the MCP Gateway makes the authorization decision). Formerly a separate callout_authorization filter.'
scenarios:
S1:
state: active
note: strips all x-user-* provider keys defensively (slots empty if absent); also strips client x-mcp-authorized and sets the verified assertion, consumed only by MCP filters
S2:
state: active
note: strips all provider-key headers and strips + sets the MCP assertion; slots/assertion unused this scenario
S3:
state: active
note: strips all provider-key headers and strips + sets the assertion; unused this scenario
S4:
state: active
note: strips all provider-key headers and strips + sets the assertion; unused this scenario
S5:
state: active
note: captures the ogx_files key later injected on the file_search callout; strips + sets the assertion (file_search does not consume it)
S6:
state: active
note: captures the mcp_gateway key AND the mcp_gateway authorization assertion (x-mcp-authorized); both consumed by openai_mcp_tool_resolve + openai_mcp_dispatch
S7:
state: active
note: captures the brave_search key required by openai_web_search_dispatch; strips + sets the assertion (web_search does not consume it)
S8:
state: active
note: strips all provider-key headers and client x-mcp-authorized on GET before the store answers
S9:
state: active
note: strips all provider-key headers and sets the assertion on the WS handshake
S10:
state: active
note: 'strips all x-user-* provider keys defensively (slots empty if absent); also strips client x-mcp-authorized and sets the verified assertion, consumed only by MCP filters'
S11:
state: active
note: strips all provider-key headers and sets the assertion before the dedicated-service direct-services branch
S12:
state: active
note: strips all provider-key headers and sets the assertion before the direct-openai forward (unused this scenario)
S13:
state: active
note: strips all provider-key headers and sets the assertion for the managed round (unused this scenario)
S14:
state: active
note: strips all provider-key headers and sets the assertion; unused on the native chat passthrough
- id: project_state_owner_headers_preirr
name: project_state_owner_headers
group: identity
filter_type: project_state_owner_headers
phase: request-phase header projection; no response phase
config:
tenant_header: x-tenant-id
subject_header: x-user-id
promotes: projects owner as x-tenant-id/x-user-id for OGX and allowlisted callouts; strips raw x-auth-* ingress
scenarios:
S1:
state: active
note: projects x-tenant-id/x-user-id, strips raw ingress
S2:
state: active
note: same projection for streaming
S3:
state: active
note: projection regardless of store flag
S4:
state: active
note: projection for multi-turn
S5:
state: active
note: projected ids forwarded to file_search callout
S6:
state: active
note: projected ids forwarded to MCP connector
S7:
state: active
note: projection precedes web_search
S8:
state: active
note: projects before store answers GET
S9:
state: active
note: projects before the WS direct-services branch
S10:
state: active
note: 'projects x-tenant-id/x-user-id, strips raw ingress'
S11:
state: active
note: projects before the dedicated-service direct-services branch
S12:
state: active
note: projects before the direct-openai forward
S13:
state: active
note: projects before the managed chat-completions round
S14:
state: active
note: projects identity before the native chat passthrough
- id: ai_operation
name: ai_operation
group: classification
filter_type: ai_operation
phase: request-phase classification; no response phase
config: {}
promotes: classifies the request head (method + path + transport) against the registered OpenAI operation registries (Conversations, Chat Completions, Responses); on a match publishes the typed operation into request extensions, sets x-praxis-ai-application-protocol + x-praxis-ai-operation, and records application_protocol/operation_id filter results consumed by on_result branches and openai_conversations; on no match strips those headers and continues. Must precede openai_conversations.
scenarios:
S1:
state: active
note: POST /v1/responses -> application_protocol openai_responses (create)
S2:
state: active
note: streaming create, same head -> openai_responses
S3:
state: active
note: store:false create, same head -> openai_responses
S4:
state: active
note: previous_response_id create (POST /v1/responses) -> openai_responses
S5:
state: active
note: file_search create -> openai_responses
S6:
state: active
note: MCP create -> openai_responses
S7:
state: active
note: web_search create -> openai_responses
S8:
state: active
note: GET /v1/responses/{id} -> openai_responses (retrieve)
S9:
state: active
note: 'GET /v1/responses + Upgrade: websocket -> openai_responses (WebSocket handshake)'
S10:
state: active
note: 'head-based match -> openai_responses create; operation identity is authoritative, so the Chat/Anthropic/unclassified body shape does not change the operation (#1602)'
S11:
state: passthrough
note: /v1/prompts|/v1/embeddings|/v1/files|/v1/vector_stores are not in any operation registry; strips routing headers
S12:
state: active
note: POST /v1/responses -> openai_responses (create); gpt-5 head is classified here, provider chosen at the router
S13:
state: active
note: POST /v1/responses -> openai_responses (create); vllm-chat head is classified here, provider chosen at the router
S14:
state: active
note: POST /v1/chat/completions -> application_protocol openai_chat_completions (create); classified against the Chat Completions registry
- id: openai_responses_request_prerouting
name: openai_responses_request (pre-routing)
group: classification
filter_type: openai_responses_request
phase: request-phase classifier (initialize_state false); promotes internal x-praxis-* routing headers without minting state
config:
initialize_state: false
on_invalid: continue
headers.format: x-praxis-ai-format
headers.model: x-praxis-ai-model
headers.stream: x-praxis-ai-stream
headers.mode: x-praxis-responses-mode
promotes: x-praxis-ai-format, x-praxis-ai-model, x-praxis-ai-stream, x-praxis-responses-mode; the model header is overwritten from the parsed body so clients cannot spoof the router's model policy
scenarios:
S1:
state: active
note: classifies openai_responses; sets format/model/mode, stream=false
S2:
state: active
note: classifies openai_responses; promotes x-praxis-ai-stream=true
S3:
state: active
note: classifies openai_responses (store flag not classification input)
S4:
state: active
note: classifies openai_responses create
S5:
state: active
note: classifies openai_responses (tools present)
S6:
state: active
note: classifies openai_responses (mcp tools)
S7:
state: active
note: classifies openai_responses
S8:
state: active
note: classifies GET as non-create; header drives later gating
S9:
state: active
note: classifies GET/WS as non-create (no openai_responses create marker)
S10:
state: active
note: 'classify_matched_operation forces format=openai_responses for the matched create body regardless of its Chat/Anthropic shape; promotes format + model=gpt-4, stream=false'
S11:
state: passthrough
note: path not /v1/responses; no Responses classification
S12:
state: active
note: classifies openai_responses; model gpt-5 drives the direct-openai (application_provider=openai) route
S13:
state: active
note: classifies openai_responses; model vllm-chat drives the chat-completions (managed) route
S14:
state: active
note: classifies the Chat Completions body -> not openai_responses (on_invalid:continue); the native chat route binds by PATH, not format
- id: openai_responses_model_rewrite_managed
name: openai_responses_model_rewrite (managed /v1/responses)
group: routing
filter_type: openai_responses_model_rewrite
phase: request-phase model policy for the managed Responses route; path-gated to /v1/responses; runs after the classifier froze x-praxis-ai-model and before the binding router, so it never changes the router key
config:
conditions:
- 'when path_prefix: /v1/responses'
model_aliases:
vllm-chat: vllm-chat
promotes: rewrites the outbound Responses body `model` via the alias table without touching the classifier-owned x-praxis-ai-model router key; the shipped alias is identity (vllm-chat -> vllm-chat), so a matching model still marks the body mutated and re-serializes it but the VALUE is preserved (semantically unchanged); openai_responses_validate below the router reads the body model into ResponsesState; a deployment retargets vllm-chat to the model its Chat backend actually serves
scenarios:
S1:
state: passthrough
note: /v1/responses create but the model is not vllm-chat; no alias match and no default_model, so the body is unchanged
S2:
state: passthrough
note: streaming create; model not aliased; body unchanged
S3:
state: passthrough
note: stateless create; model not aliased; body unchanged
S4:
state: passthrough
note: multi-turn create; model not aliased; body unchanged
S5:
state: passthrough
note: file_search create; model not aliased; body unchanged
S6:
state: passthrough
note: MCP create; model not aliased; body unchanged
S7:
state: passthrough
note: web_search create; model not aliased; body unchanged
S8:
state: passthrough
note: GET retrieve is not a create; the filter is a no-op
S9:
state: passthrough
note: WebSocket handshake is not a create; the filter is a no-op
S10:
state: passthrough
note: a non-Responses body is not a Responses create; the filter is a no-op
S11:
state: skipped
note: path is not /v1/responses; the condition gates the filter out
S12:
state: passthrough
note: gpt-5 create; model not aliased; body unchanged (the direct-openai route is chosen at the router)
S13:
state: active
note: vllm-chat create matches the identity alias; the body is marked mutated and re-serialized but the model value is preserved - semantically unchanged
S14:
state: skipped
note: path is /v1/chat/completions; the /v1/responses condition gates this instance out
- id: openai_responses_model_rewrite_chat
name: openai_responses_model_rewrite (native /v1/chat/completions)
group: routing
filter_type: openai_responses_model_rewrite
phase: request-phase model policy for the native Chat Completions ingress; path-gated to /v1/chat/completions; runs before the binding router so the rewritten body model rides the raw chat body to the direct Chat backend
config:
conditions:
- 'when path_prefix: /v1/chat/completions'
default_model: llama-3.3-70b
model_aliases:
codex-*: llama-3.3-70b
headers.effective_model: x-praxis-ai-effective-model
headers.original_model: x-praxis-ai-original-model
promotes: on the native Chat path only, rewrites the body `model` (codex-* -> llama-3.3-70b, default llama-3.3-70b when the model is absent) and promotes x-praxis-ai-effective-model / x-praxis-ai-original-model; the chat route binds by PATH so no model header is needed; lets a gateway advertise one client-facing name and forward the provider-specific target on the native Chat API
scenarios:
S1:
state: skipped
note: path is /v1/responses; the /v1/chat/completions condition gates this instance out
S2:
state: skipped
note: path is /v1/responses; condition gates this instance out
S3:
state: skipped
note: path is /v1/responses; condition gates this instance out
S4:
state: skipped
note: path is /v1/responses; condition gates this instance out
S5:
state: skipped
note: path is /v1/responses; condition gates this instance out
S6:
state: skipped
note: path is /v1/responses; condition gates this instance out
S7:
state: skipped
note: path is /v1/responses; condition gates this instance out
S8:
state: skipped
note: path is /v1/responses; condition gates this instance out
S9:
state: skipped
note: path is /v1/responses; condition gates this instance out
S10:
state: skipped
note: path is /v1/responses; condition gates this instance out
S11:
state: skipped
note: path is a dedicated-service API, not /v1/chat/completions; condition gates this instance out
S12:
state: skipped
note: path is /v1/responses; condition gates this instance out
S13:
state: skipped
note: path is /v1/responses; condition gates this instance out
S14:
state: active
note: POST /v1/chat/completions {model:codex-mini} matches codex-* -> llama-3.3-70b; the rewritten body rides the raw chat body to chat-ingress-backend
- id: router
name: router
group: routing
filter_type: router
phase: request-phase binding router; freezes ONE logical cluster plus its application_provider/application_protocol as bound_upstream metadata; exactly one top-level router per binding pipeline
config:
routes:
- 'path_prefix /v1/prompts -> prompts-api'
- 'path_prefix /v1/embeddings -> embeddings-api'
- 'path_prefix /v1/files -> files-api'
- 'path_prefix /v1/conversations -> inference-backend'
- 'path /v1/chat/completions -> chat-ingress-backend'
- 'path /v1/responses + upgrade:websocket -> responses-websocket-backend'
- 'path /v1/responses + x-praxis-ai-format:openai_responses + x-praxis-ai-model:gpt-5 -> openai-responses-backend'
- 'path /v1/responses + x-praxis-ai-format:openai_responses + x-praxis-ai-model:vllm-chat -> chat-completions-backend'
- 'path_prefix /v1/responses + x-praxis-ai-format:openai_responses -> inference-backend'
- 'path_prefix /v1/vector_stores -> vector-stores-backend'
promotes: binds exactly one logical cluster per request and freezes its application_provider/application_protocol as bound_upstream metadata read by the two terminal branches and the IRR-step load balancer; the classifier-owned x-praxis-ai-model header cannot be spoofed; model gpt-5 -> openai-responses-backend (provider openai), vllm-chat -> chat-completions-backend (managed), any other Responses model -> inference-backend (managed native); the exact path /v1/chat/completions -> chat-ingress-backend (provider direct) for native Chat Completions ingress; a request matching no route is a route-miss (bare 404)
scenarios:
S1:
state: active
note: binds inference-backend (managed native Responses) via the /v1/responses format route
S2:
state: active
note: binds inference-backend via the /v1/responses format route
S3:
state: active
note: binds inference-backend via the /v1/responses format route
S4:
state: active
note: binds inference-backend via the /v1/responses format route
S5:
state: active
note: binds inference-backend via the /v1/responses format route
S6:
state: active
note: binds inference-backend via the /v1/responses format route
S7:
state: active
note: binds inference-backend via the /v1/responses format route
S8:
state: active
note: binds inference-backend via the /v1/responses prefix route so the GET retrieve reaches the local store
S9:
state: active
note: 'binds responses-websocket-backend (application_provider=direct) via the /v1/responses upgrade:websocket route'
S10:
state: active
note: 'model gpt-4 misses the gpt-5/vllm-chat model routes and falls to the Responses catch-all (path_prefix /v1/responses + x-praxis-ai-format openai_responses) -> inference-backend (managed native)'
S11:
state: active
note: binds a dedicated-service cluster by path_prefix (application_provider=direct)
S12:
state: active
note: binds openai-responses-backend (application_provider=openai) via the gpt-5 route
S13:
state: active
note: binds chat-completions-backend (managed vllm) via the vllm-chat route
S14:
state: active
note: binds chat-ingress-backend (application_provider=direct) via the exact /v1/chat/completions path route
- id: direct_openai_branch
name: headers (direct-openai branch)
group: routing
filter_type: headers
phase: request-phase branch carrier; when bound_upstream.application_provider=openai it takes the direct-openai terminal branch and rejoins at terminal; header set is a no-op anchor only
config:
conditions:
- 'when bound_upstream.application_provider: openai'
branch_chains:
- name: direct-openai
rejoin: terminal
chain: direct-openai-dispatch (load_balancer cluster_source:bound_upstream -> openai-responses-backend 127.0.0.1:3001)
promotes: terminal branch for the direct-OpenAI path; when the router bound a cluster whose application_provider is openai, forwards the ORIGINAL Responses request unchanged to the OpenAI-compatible provider and stops the parent chain, so the managed body filters and the IRR never run
scenarios:
S1:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S2:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S3:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S4:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S5:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S6:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S7:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S8:
state: inert
note: GET bound inference-backend (managed); branch not taken
S9:
state: inert
note: WS bound responses-websocket-backend (direct), not openai; branch not taken
S10:
state: inert
note: 'bound provider is managed native; branch condition unmet, chain continues'
S11:
state: inert
note: dedicated-service bound provider=direct, not openai; branch not taken
S12:
state: active
note: bound provider=openai -> takes the direct-openai terminal branch; forwards the original request to openai-responses-backend 3001 and stops the chain (no managed body work, no IRR)
S13:
state: inert
note: bound provider=vllm (managed), not openai; branch not taken, chain continues to the managed body filters
S14:
state: inert
note: bound provider=direct (chat-ingress), not openai; branch not taken
- id: direct_services_branch
name: headers (direct-services branch)
group: routing
filter_type: headers
phase: request-phase branch carrier; when bound_upstream.application_provider=direct it takes the direct-services terminal branch and rejoins at terminal
config:
conditions:
- 'when bound_upstream.application_provider: direct'
branch_chains:
- name: direct-services
rejoin: terminal
chain: direct-services-dispatch (load_balancer cluster_source:bound_upstream -> prompts-api 9998 / embeddings-api 9997 / files-api 9999 / responses-websocket-backend 3001 / vector-stores-backend 3002 / chat-ingress-backend 3001)
promotes: terminal branch for dedicated services; when the router bound a cluster whose application_provider is direct (/v1/prompts, /v1/embeddings, /v1/files, /v1/vector_stores, the /v1/responses WebSocket upgrade, and the native /v1/chat/completions ingress), forwards straight upstream and rejoins terminal so the IRR never runs
scenarios:
S1:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S2:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S3:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S4:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S5:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S6:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S7:
state: inert
note: bound provider is managed native; branch condition unmet, chain continues
S8:
state: inert
note: GET bound inference-backend (managed); branch not taken (store answers later)
S9:
state: active
note: bound responses-websocket-backend (direct) -> takes the direct-services terminal branch; forwards the WS upgrade to 3001
S10:
state: inert
note: 'bound provider is managed native; branch condition unmet, chain continues'
S11:
state: active
note: bound dedicated-service cluster (direct) -> takes the direct-services terminal branch; forwards to the path_prefix backend
S12:
state: skipped
note: direct-openai branch already rejoined terminal at the prior filter
S13:
state: inert
note: bound provider=vllm (managed), not direct; branch not taken
S14:
state: active
note: bound provider=direct; takes the terminal branch and forwards the raw chat body to chat-ingress-backend (3001); the managed body filters and the IRR never run
- id: openai_responses_request
name: openai_responses_request
group: classification
filter_type: openai_responses_request
phase: request-phase; JSON syntax + ID generation + managed-provider background/prompt policy; gated unless bound_upstream.application_provider=openai
config:
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: validates JSON syntax; generates response and conversation IDs; writes responses.* metadata (provider-owned param combos pass through); suppressed on the direct-openai path
scenarios:
S1:
state: active
note: validates syntax, generates response id
S2:
state: active
note: validates + generates ids for streaming create
S3:
state: active
note: validates; ids generated even if not persisted
S4:
state: active
note: validates multi-turn create
S5:
state: active
note: validates create with file_search tools
S6:
state: active
note: validates create with mcp tools
S7:
state: active
note: validates create
S8:
state: passthrough
note: GET has no body to validate
S9:
state: skipped
note: WS took the direct-services terminal branch before the body filters
S10:
state: active
note: 'managed owner builds ResponsesState and generates the response id from the one parse; the Chat-shaped body is carried as the Responses create body'
S11:
state: skipped
note: dedicated services took the direct-services terminal branch before the body filters
S12:
state: skipped
note: direct-openai terminal branch ran before the body filters
S13:
state: active
note: validates the managed vllm-chat create; generates ids
- id: openai_tool_parse
name: openai_tool_parse
group: classification
filter_type: openai_tool_parse
phase: request-phase; parses tools/tool_choice, does NOT mutate body; gated unless bound_upstream.application_provider=openai
config:
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: has_tools, has_web_search, tool_choice, function_count (summary facts to metadata/results)
scenarios:
S1:
state: passthrough
note: no tools; has_tools=false
S2:
state: passthrough
note: no tools
S3:
state: passthrough
note: no tools
S4:
state: passthrough
note: no tools
S5:
state: active
note: parses file_search tool; has_tools=true
S6:
state: active
note: parses mcp tool; has_tools=true
S7:
state: active
note: parses tools; may set has_web_search
S8:
state: passthrough
note: GET retrieve
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: passthrough
note: 'no tools; has_tools=false'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: passthrough
note: no tools; has_tools=false
- id: openai_conversations
name: openai_conversations
group: conversations
filter_type: openai_conversations
phase: request-phase (CRUD + has_conversation); response-phase appends items on success (non-streaming); gated unless bound_upstream.application_provider=openai
config:
backend: sqlite
database_url: sqlite://responses.db?mode=rwc
conversations_table: openai_conversations
items_table: openai_conversation_items
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: sets has_conversation when Responses request carries a conversation field; on response path appends input+output items back to the conversation
scenarios:
S1:
state: passthrough
note: no conversation field; would activate on /v1/conversations CRUD or conversation-bearing create
S2:
state: passthrough
note: no conversation field; note append-back is non-streaming only
S3:
state: passthrough
note: no conversation field
S4:
state: passthrough
note: prev_response_id path, not a conversation field
S5:
state: passthrough
note: no conversation field
S6:
state: passthrough
note: no conversation field
S7:
state: passthrough
note: no conversation field
S8:
state: passthrough
note: GET retrieve, not a conversations op
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: passthrough
note: 'no conversation field; would activate on /v1/conversations CRUD or conversation-bearing create'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: passthrough
note: no conversation field
- id: openai_responses_store
name: openai_responses_store
group: store
filter_type: openai_responses_store
phase: request-phase registers backend + serves GET retrieve (terminal); response-phase persists AFTER IRR composes terminal (reverse order); gated unless bound_upstream.application_provider=openai
config:
backend: sqlite
database_url: sqlite://responses.db?mode=rwc
responses_table: openai_responses
conversations_table: openai_conversations
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: registers store backend for rehydrate/compact; persists ResponsesState.response_object on response path; answers GET /v1/responses/{id} pre-IRR
scenarios:
S1:
state: active
note: persists buffered response on reverse path (store default true)
S2:
state: active
note: persists response_object populated by openai_stream_events inside IRR
S3:
state: inert
note: backend registered but store:false -> no persistence
S4:
state: active
note: persists; rehydrate also reads prior response from this store
S5:
state: active
note: persists final composed response after file_search loop
S6:
state: active
note: persists final response after MCP loop
S7:
state: active
note: persists final response after web_search loop
S8:
state: active
note: SERVES GET as terminal response pre-IRR and short-circuits pipeline
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: active
note: 'persists buffered response on reverse path (store default true)'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: inert
note: backend registered but store:false -> no persistence
- id: openai_responses_rehydrate
name: openai_responses_rehydrate
group: context
filter_type: openai_responses_rehydrate
phase: request-phase; loads and prepends prior history; gated unless bound_upstream.application_provider=openai
config:
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: loads context from previous_response_id or conversation, prepends message history; previous_response_id takes precedence when both present
scenarios:
S1:
state: passthrough
note: no previous_response_id/conversation
S2:
state: passthrough
note: no prior context markers
S3:
state: passthrough
note: no prior context markers
S4:
state: active
note: loads stored prior response via previous_response_id and prepends it
S5:
state: passthrough
note: no prior context markers
S6:
state: passthrough
note: no prior context markers
S7:
state: passthrough
note: no prior context markers
S8:
state: skipped
note: store short-circuited GET before this filter
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: passthrough
note: 'no previous_response_id/conversation'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: passthrough
note: no previous_response_id/conversation
- id: openai_file_resolve
name: openai_file_resolve
group: context
filter_type: openai_file_resolve
phase: request-phase pre-security callout; fires only when file_id refs present; gated unless bound_upstream.application_provider=openai
config:
files_api_url: http://127.0.0.1:9999
user_credential: ogx_files
allow_pre_security_callout: true
outbound_chain: files-api-outbound (project_state_owner_headers x-tenant-id/x-user-id)
on_missing: reject
timeout_ms: 10000
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: 'resolves file_id refs in current input + rehydrated history via Files API; rebuilds body with file_data/image_url (on_missing: reject); injects the caller-scoped ogx_files credential on file_id callouts (file_url downloads use the separate credential-free resolver)'
scenarios:
S1:
state: inert
note: no file_id refs; callout does not fire
S2:
state: inert
note: no file_id refs
S3:
state: inert
note: no file_id refs
S4:
state: inert
note: no file_id refs in prepended history
S5:
state: inert
note: file_search uses vector stores, not file_id input refs
S6:
state: inert
note: no file_id refs
S7:
state: inert
note: no file_id refs
S8:
state: skipped
note: store short-circuited GET
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: inert
note: 'no file_id refs; callout does not fire'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: inert
note: no file_id refs; callout does not fire
- id: openai_doc_extract
name: openai_doc_extract
group: context
filter_type: openai_doc_extract
phase: request-phase; converts input_file parts, on_unsupported continue; gated unless bound_upstream.application_provider=openai
config:
allow_pre_security_callout: true
on_unsupported: continue
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: converts input_file content parts to input_text (base64 decode + UTF-8 validate) for backends lacking native input_file; unsupported formats left unchanged
scenarios:
S1:
state: inert
note: no input_file parts to convert
S2:
state: inert
note: no input_file parts
S3:
state: inert
note: no input_file parts
S4:
state: inert
note: no input_file parts
S5:
state: inert
note: no input_file parts
S6:
state: inert
note: no input_file parts
S7:
state: inert
note: no input_file parts
S8:
state: skipped
note: store short-circuited GET
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: inert
note: 'no input_file parts to convert'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: inert
note: no input_file parts to convert
- id: openai_responses_compact
name: openai_responses_compact
group: context
filter_type: openai_responses_compact
phase: request-phase pre-security summarization callout in the bound-body managed-responses chain, after rehydrate and file_resolve/doc_extract; fires only when a request carries context_management compaction and its token count exceeds the threshold; declares the experimental bound-upstream request-body phase (bound_upstream_request_body_access) so it can carry managed-responses' inherited bound_upstream condition; OpenAI-bound requests skip it with the rest of the chain
config:
inference_url: http://127.0.0.1:3001/v1/chat/completions
allow_private_inference_url: true
allow_pre_security_callout: true
default_model: gpt-4o-mini
timeout_ms: 60000
promotes: summarizes prior history via an anonymous Chat callout to inference_url when context_management compaction is present and the token budget is exceeded, preserving the rewritten current-turn tail; transparent passthrough on requests without context_management
scenarios:
S1:
state: passthrough
note: managed create without context_management; the summarizer is inert
S2:
state: passthrough
note: streaming create without context_management; inert
S3:
state: passthrough
note: stateless create without context_management; inert
S4:
state: passthrough
note: multi-turn create without context_management; inert (history is rehydrated but not summarized)
S5:
state: passthrough
note: file_search create without context_management; inert
S6:
state: passthrough
note: MCP create without context_management; inert
S7:
state: passthrough
note: web_search create without context_management; inert
S8:
state: skipped
note: GET retrieve short-circuits at the store before this filter
S9:
state: skipped
note: direct-services terminal branch; never reached
S10:
state: passthrough
note: "managed create (chat/anthropic body routed by #1602); no context_management, summarizer inert"
S11:
state: skipped
note: direct-services terminal branch; never reached
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: passthrough
note: managed chat create without context_management; the summarizer is inert
S14:
state: skipped
note: native chat direct-services terminal branch; never reached
- id: openai_mcp_tool_resolve
name: openai_mcp_tool_resolve
group: context
filter_type: openai_mcp_tool_resolve
phase: request-phase; resolves MCP tools/list from operator connectors; gated unless bound_upstream.application_provider=openai
config:
user_credential: mcp_gateway
authorization_assertion: mcp_gateway
forward_headers:
- x-tenant-id
- x-user-id
connectors:
- id: trusted-mcp
server_url: http://127.0.0.1:8001/mcp
conditions:
- 'unless bound_upstream.application_provider: openai'
promotes: calls tools/list on each upstream MCP server; writes mcp_tool_map to ResponsesState; forwards the caller-scoped mcp_gateway credential + opaque authorization assertion and identity headers ONLY to configured connector_id targets, never an arbitrary client server_url
scenarios:
S1:
state: passthrough
note: no MCP tool entries
S2:
state: passthrough
note: no MCP tool entries
S3:
state: passthrough
note: no MCP tool entries
S4:
state: passthrough
note: no MCP tool entries
S5:
state: passthrough
note: file_search tool, not MCP
S6:
state: active
note: resolves tools/list from connector trusted-mcp with mcp_gateway credential + authorization assertion; writes mcp_tool_map
S7:
state: passthrough
note: no MCP tool entries
S8:
state: skipped
note: store short-circuited GET
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: passthrough
note: 'no MCP tool entries'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: passthrough
note: no MCP tool entries
- id: static_response
name: static_response (unsupported subpath 404)
group: routing
filter_type: static_response
phase: request-phase terminal guard; returns 404 for a managed Responses request on an unsupported /v1/responses subpath
config:
status: 404
body: '{"error":{"message":"unsupported managed Responses operation","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}'
conditions:
- 'when path_prefix: /v1/responses'
- 'unless path: /v1/responses'
promotes: fail-closed 404 for a managed Responses request on an unsupported /v1/responses subpath (e.g. POST /v1/responses/{id}/cancel); legitimate GET/DELETE resource ops were already served by openai_responses_store, and exact /v1/responses create/retrieve is excluded by the unless gate; provider-owned OpenAI traffic already left via direct-openai
scenarios:
S1:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
S2:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
S3:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
S4:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
S5:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
S6:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
S7:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
S8:
state: skipped
note: openai_responses_store already served the GET retrieve
S9:
state: skipped
note: direct-services terminal branch ran first
S10:
state: inert
note: 'path is exactly /v1/responses (create); the unless gate excludes it'
S11:
state: skipped
note: direct-services terminal branch ran first
S12:
state: skipped
note: direct-openai terminal branch ran first
S13:
state: inert
note: path is exactly /v1/responses (create); the unless gate excludes it
- id: iterative_request_router
name: iterative_request_router
group: irr
filter_type: iterative_request_router
phase: terminal filter; owns agentic model-tool-model loop; transitions on openai_agentic_loop action
config:
initial_step: inference
max_iterations: 8
timeout_ms: 360000
step_timeout_ms: 300000
max_response_bytes: 67108864
max_stream_response_bytes: 67108864
max_state_bytes: 136314880
on_result:
- filter: openai_agentic_loop
key: action
value: loop
next: inference
- default: true
done: true
promotes: runs gateway-owned openai_responses create requests through the inference step loop; OpenAI-bound traffic already left via the direct-openai terminal branch, so IRR is reached only by managed providers; the inference step consumes the frozen logical binding directly (no second routing decision)
scenarios:
S1:
state: active
note: enters IRR; one inference round; agentic_loop action=done
S2:
state: active
note: enters IRR; one round; streaming lifecycle composed
S3:
state: active
note: enters IRR; routed but not persisted
S4:
state: active
note: enters IRR with rehydrated history
S5:
state: active
note: enters IRR; loops for file_search rounds then done
S6:
state: active
note: enters IRR; loops for MCP rounds then done
S7:
state: active
note: enters IRR; loops for web_search rounds then done
S8:
state: skipped
note: store short-circuited GET before IRR
S9:
state: skipped
note: WS handshake took the direct-services terminal branch
S10:
state: active
note: 'enters IRR; one inference round; agentic_loop action=done'
S11:
state: skipped
note: dedicated services took the direct-services terminal branch
S12:
state: skipped
note: direct-openai terminal branch bypassed the IRR
S13:
state: active
note: enters IRR; one round; the bound chat-completions upstream drives protocol translation
- id: irr_project_state_owner_headers
name: project_state_owner_headers (IRR step)
group: irr-step
filter_type: project_state_owner_headers
phase: IRR inference step, request-phase; re-projection inside destination-owned subrequest
config:
tenant_header: x-tenant-id
subject_header: x-user-id
promotes: re-projects x-tenant-id/x-user-id inside subrequest; strips still-uncommitted raw ingress identity headers
scenarios:
S1:
state: active
note: re-projects owner headers each round
S2:
state: active
note: re-projects for streaming subrequest
S3:
state: active
note: re-projects owner headers
S4:
state: active
note: re-projects owner headers
S5:
state: active
note: re-projects; ids forwarded to file_search callout
S6:
state: active
note: re-projects; ids forwarded to MCP dispatch
S7:
state: active
note: re-projects; owner underpins web_search slot
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: active
note: 're-projects owner headers each round'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: active
note: re-projects owner headers for the managed chat-completions round
- id: openai_stream_events
name: openai_stream_events
group: irr-step
filter_type: openai_stream_events
phase: IRR step; request-phase pass + response-phase composes one logical Responses lifecycle across rounds
config:
max_buffer_bytes: 67108864
max_events: 200000
timeout_secs: 600
promotes: composes every inference SSE stream into ONE lifecycle; withholds per-round terminal events until IRR transition known; populates ResponsesState.response_object (required for streaming persistence + loop-terminal error delivery); limits stay at or above the openai_responses_to_chat_completions converter bounds so no client-visible frame is dropped from the store
scenarios:
S1:
state: active
note: one-round logical stream; still composes and populates response_object
S2:
state: active
note: composes streaming lifecycle; populates response_object for store persistence
S3:
state: active
note: composes lifecycle (object not persisted due to store:false)
S4:
state: active
note: composes lifecycle for multi-turn
S5:
state: active
note: composes across file_search rounds; withholds terminal until done
S6:
state: active
note: composes across MCP rounds
S7:
state: active
note: composes across web_search rounds
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: active
note: 'one-round logical stream; still composes and populates response_object'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: active
note: composes the chat-translated lifecycle; populates response_object
- id: openai_web_search_dispatch
name: openai_web_search_dispatch
group: irr-step
filter_type: openai_web_search_dispatch
phase: IRR step request-phase dispatcher; no response phase; inert until model emits web_search_call
config:
provider: brave
api_key:
env_var: WEB_SEARCH_API_KEY
user_credential: brave_search
max_calls_per_round: 32
promotes: executes web_search_call assignments prepared by the loop owner; fails closed if per-user brave_search slot missing
scenarios:
S1:
state: inert
note: no web_search_call emitted
S2:
state: inert
note: no web_search_call emitted
S3:
state: inert
note: no web_search_call emitted
S4:
state: inert
note: no web_search_call emitted
S5:
state: inert
note: file_search, not web_search
S6:
state: inert
note: MCP, not web_search
S7:
state: active
note: executes web_search_call via Brave using per-user brave_search slot; fails closed if slot missing
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: inert
note: 'no web_search_call emitted'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: inert
note: no web_search_call emitted
- id: openai_mcp_dispatch
name: openai_mcp_dispatch
group: irr-step
filter_type: openai_mcp_dispatch
phase: IRR step request-phase dispatcher; no response phase; inert until model emits MCP tool call
config:
user_credential: mcp_gateway
authorization_assertion: mcp_gateway
forward_headers:
- x-tenant-id
- x-user-id
max_calls_per_round: 32
max_parallel_calls: 8
max_result_bytes: 1048576
max_total_result_bytes: 8388608
promotes: executes MCP calls prepared/classified by the loop owner; forwards the caller-scoped mcp_gateway credential + authorization assertion on each connector call; reconciles results into ResponsesState
scenarios:
S1:
state: inert
note: no MCP call emitted
S2:
state: inert
note: no MCP call emitted
S3:
state: inert
note: no MCP call emitted
S4:
state: inert
note: no MCP call emitted
S5:
state: inert
note: file_search, not MCP
S6:
state: active
note: executes MCP calls via 127.0.0.1:8001/mcp with mcp_gateway credential + assertion (max 8 parallel, byte-capped)
S7:
state: inert
note: web_search, not MCP
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: inert
note: 'no MCP call emitted'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: inert
note: no MCP call emitted
- id: openai_file_search_dispatch
name: openai_file_search_dispatch
group: irr-step
filter_type: openai_file_search_dispatch
phase: IRR step request-phase dispatcher; executes at request-body EOS on re-entry; no response phase; inert until model emits file_search_call
config:
vector_store_url: http://127.0.0.1:3002
user_credential: ogx_files
outbound_chain: vector-store-outbound (project_state_owner_headers x-tenant-id/x-user-id)
timeout_ms: 5000
max_response_bytes: 10485760
max_total_response_bytes: 67108864
max_state_bytes: 136314880
on_failure: closed
promotes: executes assigned file_search_call items against vector store 3002; injects the caller-scoped ogx_files credential on the vector-store callout; reconciles results in place in ResponsesState.accumulated_output
scenarios:
S1:
state: inert
note: no file_search_call emitted
S2:
state: inert
note: no file_search_call emitted
S3:
state: inert
note: no file_search_call emitted
S4:
state: inert
note: no file_search_call emitted
S5:
state: active
note: executes file_search_call via vector store 3002 with the ogx_files credential; on_failure closed
S6:
state: inert
note: MCP, not file_search
S7:
state: inert
note: web_search, not file_search
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: inert
note: 'no file_search_call emitted'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: inert
note: no file_search_call emitted
- id: ai_guardrails
name: ai_guardrails
group: irr-step
filter_type: ai_guardrails
phase: IRR step request-body EOS; checks only newly produced local Responses tool results; records blocked/provider-failure verdicts for openai_agentic_loop; no response phase
config:
provider.type: nemo
provider.endpoint: http://127.0.0.1:3003/v1/checks
provider.guardrails.config_ids:
- your-config
provider.timeout_ms: 5000
provider.max_message_checks: 32
phase.request: false
phase.response: false
phase.tool_results: true
promotes: checks the new MCP, web-search, or file-search result suffix before it can be sent to the model on the next IRR round; a block or check failure is finalized by openai_agentic_loop
scenarios:
S1:
state: inert
note: no local tool result produced
S2:
state: inert
note: no local tool result produced
S3:
state: inert
note: no local tool result produced
S4:
state: inert
note: no local tool result produced
S5:
state: active
note: checks the file-search result before model re-entry
S6:
state: active
note: checks the MCP result before model re-entry
S7:
state: active
note: checks the web-search result before model re-entry
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: inert
note: 'no local tool result produced'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: inert
note: no local tool result produced
- id: openai_agentic_loop
name: openai_agentic_loop
group: irr-step
filter_type: openai_agentic_loop
phase: IRR step; request-phase records assignments; response-phase parses model output and publishes single loop|done transition (sole authority)
config:
max_infer_iters: 7
promotes: 'extracts completed function_call/web_search_call/file_search_call/MCP items; records dispatcher assignments; publishes action=loop|done (issue #1046 sole transition authority)'
scenarios:
S1:
state: active
note: parses single round; action=done (no tools)
S2:
state: active
note: parses streaming round; action=done
S3:
state: active
note: parses single round; action=done
S4:
state: active
note: parses round over rehydrated context; action=done
S5:
state: active
note: assigns file_search_call then action=loop; finalizes with done
S6:
state: active
note: assigns MCP call then action=loop; finalizes with done
S7:
state: active
note: assigns web_search_call then action=loop; finalizes with done
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: active
note: 'parses single round; action=done (no tools)'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: active
note: parses the translated round; action=done (no tools)
- id: openai_client_tool_compat
name: openai_client_tool_compat
group: irr-step
filter_type: openai_client_tool_compat
phase: IRR step; request-phase lowers rich client-owned tools to private function tools in the request body; response-phase restores the returned function_call items; runs after openai_agentic_loop and before responses_to_chat_completions
config:
max_rewritten_body_bytes: 67108864
max_client_tools: 512
promotes: lowers custom / namespace / local-shell / client-executed tool_search tools to private function tools the backend understands (request body only) and restores them to canonical typed items on the response path; transparent passthrough when no rich client tools are declared
scenarios:
S1:
state: passthrough
note: in the IRR but no rich client tools declared; body unchanged
S2:
state: passthrough
note: streaming round; no rich client tools; body unchanged
S3:
state: passthrough
note: no rich client tools; body unchanged
S4:
state: passthrough
note: no rich client tools; body unchanged
S5:
state: passthrough
note: file_search is a hosted tool, not a client function tool; body unchanged
S6:
state: passthrough
note: MCP is a hosted tool, not a client function tool; body unchanged
S7:
state: passthrough
note: web_search is a hosted tool, not a client function tool; body unchanged
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: passthrough
note: managed inference round entered; no rich client tools declared; body unchanged
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: passthrough
note: the translated chat round carries no rich client tools; body unchanged
S14:
state: skipped
note: native chat bypasses the IRR
- id: irr_headers
name: headers (IRR step)
group: irr-step
filter_type: headers
phase: IRR step request-phase; sets outbound header
config:
request_set:
- name: Content-Type
value: application/json
promotes: 'forces Content-Type: application/json on the outbound subrequest'
scenarios:
S1:
state: active
note: sets Content-Type
S2:
state: active
note: sets Content-Type
S3:
state: active
note: sets Content-Type
S4:
state: active
note: sets Content-Type
S5:
state: active
note: sets Content-Type each round
S6:
state: active
note: sets Content-Type each round
S7:
state: active
note: sets Content-Type each round
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: active
note: 'sets Content-Type'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: active
note: sets Content-Type on the outbound chat-completions subrequest
- id: irr_load_balancer
name: load_balancer (IRR step)
group: irr-step
filter_type: load_balancer
phase: IRR step request-phase; selects the endpoint for the router-bound cluster (cluster_source:bound_upstream) and publishes its typed application_protocol BEFORE the protocol-adaptive body builders
config:
cluster_source: bound_upstream
clusters:
- name: inference-backend
endpoints:
- 127.0.0.1:3001
http.application_protocol: openai_responses
http.application_provider: vllm
- name: chat-completions-backend
endpoints:
- 127.0.0.1:3001
http.application_protocol: openai_chat_completions
http.application_provider: vllm
promotes: resolves the request-level binding to a concrete endpoint (no second routing decision) and publishes the selected cluster's application_protocol; every selected_upstream gate below reads this fresh selection so exactly one protocol-adaptive body builder fires each round
scenarios:
S1:
state: active
note: selects inference-backend 127.0.0.1:3001 (openai_responses); publishes protocol
S2:
state: active
note: selects inference-backend 127.0.0.1:3001 (openai_responses); publishes protocol
S3:
state: active
note: selects inference-backend 127.0.0.1:3001 (openai_responses); publishes protocol
S4:
state: active
note: selects inference-backend 127.0.0.1:3001 (openai_responses); publishes protocol
S5:
state: active
note: selects inference-backend 127.0.0.1:3001 (openai_responses) each round
S6:
state: active
note: selects inference-backend 127.0.0.1:3001 (openai_responses) each round
S7:
state: active
note: selects inference-backend 127.0.0.1:3001 (openai_responses) each round
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: active
note: 'selects inference-backend 127.0.0.1:3001 (openai_responses); publishes protocol'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: active
note: selects chat-completions-backend 127.0.0.1:3001 (openai_chat_completions); publishes protocol
- id: openai_responses_proxy
name: openai_responses_proxy
group: irr-step
filter_type: openai_responses_proxy
phase: IRR step protocol-adaptive body builder; fires only when the selected upstream speaks Responses (selected_upstream.application_protocol=openai_responses); response-phase handles buffered/streamed
config:
conditions:
- 'when selected_upstream.application_protocol: openai_responses'
promotes: on a native-Responses upstream rebuilds the outbound Responses body from ResponsesState; auto-selects typed streaming when the effective body has stream:true; inert when the selected upstream speaks Chat Completions
scenarios:
S1:
state: active
note: rebuilds body; buffered (stream:false)
S2:
state: active
note: rebuilds body; auto-selects typed streaming (stream:true)
S3:
state: active
note: rebuilds body; buffered
S4:
state: active
note: rebuilds body with rehydrated history
S5:
state: active
note: rebuilds body each round incorporating file_search results
S6:
state: active
note: rebuilds body each round incorporating MCP results
S7:
state: active
note: rebuilds body each round incorporating web_search results
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: active
note: 'rebuilds body; buffered (stream:false)'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: inert
note: selected upstream is openai_chat_completions, not openai_responses -> gate fails; the body builder does not fire
- id: openai_responses_to_chat_completions
name: openai_responses_to_chat_completions
group: irr-step
filter_type: openai_responses_to_chat_completions
phase: IRR step protocol-adaptive body builder; fires only when the selected upstream speaks Chat Completions; request-phase translates Responses->Chat, response-phase (reverse) translates Chat->Responses
config:
max_rewritten_body_bytes: 33554432
max_emitted_sse_frame_bytes: 50331648
max_stream_events: 100000
stream_timeout_secs: 300
reasoning:
dialect: vllm
max_reasoning_bytes: 65536
conditions:
- 'when selected_upstream.application_protocol: openai_chat_completions'
promotes: when the selected upstream speaks Chat Completions, translates the canonical Responses request into a Chat Completions request and (because response filters run in reverse) converts the Chat response - finite JSON or streamed SSE - back into a Responses resource / Responses SSE before openai_agentic_loop and openai_stream_events observe it; inert when the selected upstream speaks Responses natively
scenarios:
S1:
state: inert
note: selected upstream openai_responses; no translation
S2:
state: inert
note: selected upstream openai_responses; no translation
S3:
state: inert
note: selected upstream openai_responses; no translation
S4:
state: inert
note: selected upstream openai_responses; no translation
S5:
state: inert
note: selected upstream openai_responses; no translation
S6:
state: inert
note: selected upstream openai_responses; no translation
S7:
state: inert
note: selected upstream openai_responses; no translation
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: inert
note: 'selected upstream openai_responses; no translation'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: active
note: translates the Responses request to Chat Completions and the Chat response back to Responses for chat-completions-backend
- id: path_rewrite
name: path_rewrite
group: irr-step
filter_type: path_rewrite
phase: IRR step request-phase; rewrites the outbound path to /v1/chat/completions when the selected upstream speaks Chat Completions
config:
replace.pattern: ^/v1/responses/?$
replace.replacement: /v1/chat/completions
conditions:
- 'when selected_upstream.application_protocol: openai_chat_completions + path_prefix /v1/responses + methods [POST]'
promotes: the translator leaves the request URI unchanged, so this rewrites POST /v1/responses -> /v1/chat/completions for the Chat backend; the identical protocol gate plus the create verb/path guards ensure only translated create requests are rewritten
scenarios:
S1:
state: inert
note: selected upstream openai_responses; no path rewrite
S2:
state: inert
note: selected upstream openai_responses; no path rewrite
S3:
state: inert
note: selected upstream openai_responses; no path rewrite
S4:
state: inert
note: selected upstream openai_responses; no path rewrite
S5:
state: inert
note: selected upstream openai_responses; no path rewrite
S6:
state: inert
note: selected upstream openai_responses; no path rewrite
S7:
state: inert
note: selected upstream openai_responses; no path rewrite
S8:
state: skipped
note: IRR not entered
S9:
state: skipped
note: IRR not entered
S10:
state: inert
note: 'selected upstream openai_responses; no path rewrite'
S11:
state: skipped
note: IRR not entered
S12:
state: skipped
note: IRR not entered
S13:
state: active
note: rewrites POST /v1/responses -> /v1/chat/completions
scenarios:
- id: S1
label: Stateful create
request: POST /v1/responses {model,input} (store defaults true)
entersIRR: true
loopRounds: 1
- id: S2
label: Streaming create
request: POST /v1/responses {model,input,stream:true}
entersIRR: true
loopRounds: 1
- id: S3
label: Stateless create
request: POST /v1/responses {model,input,store:false}
entersIRR: true
loopRounds: 1
- id: S4
label: Multi-turn
request: POST /v1/responses {input,previous_response_id}
entersIRR: true
loopRounds: 1
- id: S5
label: File search
request: POST /v1/responses tools:[{type:file_search,vector_store_ids}]
entersIRR: true
loopRounds: 2+ (model emits file_search_call, callout via vector store 3002, re-enter, finalize)
- id: S6
label: MCP loop
request: POST /v1/responses tools:[{type:mcp,server_label,server_url,allowed_tools,require_approval:never}]
entersIRR: true
loopRounds: 2+ (mcp_tool_resolve then model emits MCP call, dispatch via 8001, finalize)
- id: S7
label: Web search
request: POST /v1/responses; model emits web_search_call (per-user brave_search key required)
entersIRR: true
loopRounds: 2+ (web_search via Brave, re-enter, finalize)
- id: S8
label: GET retrieve
request: GET /v1/responses/{id}
entersIRR: false
loopRounds: 0
- id: S9
label: WebSocket
request: 'GET /v1/responses with Upgrade: websocket'
entersIRR: false
loopRounds: 0
- id: S10
label: Non-Responses body on /v1/responses
request: POST /v1/responses with Chat Completions / Anthropic / unclassified body
entersIRR: true
loopRounds: 1
- id: S11
label: Dedicated services
request: POST /v1/prompts | /v1/embeddings | /v1/files | /v1/vector_stores
entersIRR: false
loopRounds: 0
- id: S12
label: Direct OpenAI passthrough
request: 'POST /v1/responses {model:gpt-5,...} (application_provider=openai)'
entersIRR: false
loopRounds: 0
- id: S13
label: Chat Completions backend
request: 'POST /v1/responses {model:vllm-chat,store:false} (managed vllm, openai_chat_completions)'
entersIRR: true
loopRounds: 1
- id: S14
label: Native Chat ingress
request: 'POST /v1/chat/completions {model:codex-mini,messages} (direct passthrough)'
entersIRR: false
loopRounds: 0
clusters:
- name: inference-backend
endpoints:
- 127.0.0.1:3001
definedIn: router route (path_prefix /v1/responses) + IRR-step load_balancer (cluster_source:bound_upstream)
routedBy: router binds managed native Responses for gpt-4.1/default models; the IRR inference step resolves it on 3001
note: application_provider vllm; the IRR-step load_balancer publishes application_protocol openai_responses so openai_responses_proxy fires
- name: openai-responses-backend
endpoints:
- 127.0.0.1:3001
definedIn: router route (model gpt-5) + direct-openai-dispatch load_balancer (cluster_source:bound_upstream)
routedBy: router binds this when x-praxis-ai-model is gpt-5; the direct-openai terminal branch forwards the original request unchanged
note: application_provider openai -> the managed body filters and the IRR are skipped; the provider owns the response
- name: chat-completions-backend
endpoints:
- 127.0.0.1:3001
definedIn: router route (model vllm-chat) + IRR-step load_balancer (cluster_source:bound_upstream)
routedBy: router binds this when x-praxis-ai-model is vllm-chat; the IRR inference step resolves it on 3001
note: application_provider vllm, application_protocol openai_chat_completions -> openai_responses_to_chat_completions + path_rewrite fire instead of openai_responses_proxy
- name: prompts-api
endpoints:
- 127.0.0.1:9998
definedIn: direct-services-dispatch load_balancer
routedBy: router path_prefix /v1/prompts (application_provider direct)
- name: embeddings-api
endpoints:
- 127.0.0.1:9997
definedIn: direct-services-dispatch load_balancer
routedBy: router path_prefix /v1/embeddings (application_provider direct)
- name: files-api
endpoints:
- 127.0.0.1:9999
definedIn: direct-services-dispatch load_balancer
routedBy: router path_prefix /v1/files (application_provider direct)
- name: vector-stores-backend
endpoints:
- 127.0.0.1:3002
definedIn: direct-services-dispatch load_balancer
routedBy: router path_prefix /v1/vector_stores (application_provider direct)
- name: responses-websocket-backend
endpoints:
- 127.0.0.1:3001
definedIn: direct-services-dispatch load_balancer
routedBy: 'router path /v1/responses + upgrade:websocket (application_provider direct)'
- name: chat-ingress-backend
endpoints:
- 127.0.0.1:3001
definedIn: direct-services-dispatch load_balancer
routedBy: router path /v1/chat/completions (application_provider direct)
note: native Chat Completions passthrough; the request is already Chat-shaped so it is dispatched raw with no Responses translation
externalServices:
- name: MCP server
endpoint: http://127.0.0.1:8001/mcp
connectorId: trusted-mcp
usedBy:
- openai_mcp_tool_resolve (tools/list)
- openai_mcp_dispatch (tool calls)
credential: user_credential mcp_gateway (x-user-mcp-key) + opaque authorization_assertion mcp_gateway (x-mcp-authorized); both forwarded ONLY to connector_id trusted-mcp, never a client-supplied server_url
- name: Files API
endpoint: http://127.0.0.1:9999
usedBy:
- openai_file_resolve callout (files_api_url)
- files-api cluster (/v1/files direct-services branch)
credential: user_credential ogx_files (per-user slot from x-user-ogx-key) injected on file_id callouts; file_url downloads use the separate credential-free resolver
- name: Vector store
endpoint: http://127.0.0.1:3002
usedBy:
- openai_file_search_dispatch (vector_store_url)
- vector-stores-backend cluster (/v1/vector_stores direct-services branch)
credential: user_credential ogx_files (per-user slot from x-user-ogx-key) injected on file_search callouts
- name: NeMo Guardrails
endpoint: http://127.0.0.1:3003/v1/checks
usedBy:
- ai_guardrails (local tool-result checks inside the IRR)
- name: Brave Search
endpoint: provider:brave (external Brave API)
usedBy:
- openai_web_search_dispatch
credential: user_credential brave_search (per-user slot from x-user-brave-key); WEB_SEARCH_API_KEY still required by provider config; missing per-user slot fails closed (no shared-key fallback)
knobs:
max_infer_iters: 7
max_iterations: 8
max_iterations_constraint: 'must be >= max_infer_iters + 1 (7+1=8): initial inference plus allowed tool-backed continuations'
timeout_ms: 360000
step_timeout_ms: 300000
max_response_bytes: 67108864
max_stream_response_bytes: 67108864
max_state_bytes: 136314880
file_search.timeout_ms: 5000
file_search.max_response_bytes: 10485760
file_search.max_total_response_bytes: 67108864
file_search.max_state_bytes: 136314880
file_search.on_failure: closed
mcp_dispatch.max_calls_per_round: 32
mcp_dispatch.max_parallel_calls: 8
mcp_dispatch.max_result_bytes: 1048576
mcp_dispatch.max_total_result_bytes: 8388608
web_search.max_calls_per_round: 32
file_resolve.timeout_ms: 10000
file_resolve.on_missing: reject
insecure_options.allow_private_endpoints: true
insecure_options.allow_private_upstreams: true
scen_meta:
overview:
narr: '<b>Overview.</b> All filters shown at rest. trace_context opens the spine and callout_credentials anchors the identity band — since #1341 it is the single establishing filter for scoped secrets AND the MCP authorization assertion. Select a scenario chip to trace the exact path — active filters glow, and skipped / inert / passthrough filters are dimmed and labeled with their state.'
clusters: []
services: []
S1:
narr: <b>Stateful create</b> (store defaults true) runs the full pre-IRR spine, enters the IRR for one inference round, then persists the composed response on the reverse path.
clusters:
- inference-backend
services: []
S2:
narr: <b>Streaming create.</b> openai_stream_events composes one logical SSE lifecycle inside the IRR and populates the response_object that openai_responses_store persists on the reverse path.
clusters:
- inference-backend
services: []
S3:
narr: <b>Stateless create</b> (store:false) runs identically, but the store is inert on the reverse path — nothing is persisted.
clusters:
- inference-backend
services: []
S4:
narr: <b>Multi-turn.</b> openai_responses_rehydrate loads the prior response via previous_response_id and prepends its history, then one inference round runs.
clusters:
- inference-backend
services: []
S5:
narr: <b>File search loop.</b> The model emits file_search_call; openai_file_search_dispatch queries vector store 3002 at re-entry carrying the caller-scoped ogx_files credential and the request's trace identity; ai_guardrails checks the result before model re-entry; the loop owner finalizes with done.
clusters:
- inference-backend
- vector-stores-backend
services:
- Vector store
- NeMo Guardrails
S6:
narr: <b>MCP loop.</b> openai_mcp_tool_resolve lists tools from connector trusted-mcp; the model emits an MCP call; openai_mcp_dispatch executes it against 127.0.0.1:8001. Both forward the mcp_gateway credential plus the opaque authorization assertion, and each hop gets a fresh trace span. ai_guardrails checks the result before model re-entry.
clusters:
- inference-backend
services:
- MCP server
- NeMo Guardrails
S7:
narr: <b>Web search loop.</b> The model emits web_search_call; openai_web_search_dispatch executes it via Brave using the per-user brave_search slot (fails closed if the slot is missing), inheriting the request's trace identity; ai_guardrails checks the result before model re-entry.
clusters:
- inference-backend
services:
- Brave Search
- NeMo Guardrails
S8:
narr: <b>GET retrieve.</b> openai_responses_store answers PRE-IRR as a terminal response and short-circuits the pipeline — rehydrate, the callouts, static_response, and the IRR never run.
clusters: []
services: []
S9:
narr: <b>WebSocket.</b> A GET /v1/responses handshake with Upgrade:websocket is bound by the router to responses-websocket-backend; the direct-services terminal branch forwards it to 3001, so the managed body filters and the IRR never run.
clusters:
- responses-websocket-backend
services: []
S10:
narr: '<b>Chat/Anthropic body on /v1/responses.</b> Operation identity is authoritative over body shape (#1602): ai_operation matches the POST /v1/responses head and openai_responses_request forces format=openai_responses, so a Chat/Anthropic/unclassified body routes by model through the Responses catch-all to the managed inference-backend and runs one IRR round, returning 200 - superseding the pre-#1602 route-miss 404.'
clusters:
- inference-backend
services: []
S11:
narr: <b>Dedicated services.</b> /v1/prompts | /v1/embeddings | /v1/files | /v1/vector_stores are bound by the router to their path_prefix cluster (application_provider direct); the direct-services terminal branch forwards straight upstream and the IRR never runs.
clusters:
- prompts-api
- embeddings-api
- files-api
- vector-stores-backend
services:
- Files API
- Vector store
S12:
narr: <b>Direct OpenAI passthrough.</b> A create with model gpt-5 is bound by the router to openai-responses-backend (application_provider openai); the direct-openai terminal branch forwards the ORIGINAL Responses request unchanged to 3001. The managed body filters and the IRR are skipped — the provider owns the response.
clusters:
- openai-responses-backend
services: []
S13:
narr: <b>Chat Completions backend.</b> A create with model vllm-chat is bound by the router to chat-completions-backend (managed). The IRR runs one round; the IRR-step load_balancer selects openai_chat_completions, so openai_responses_to_chat_completions translates the request to Chat Completions (path_rewrite retargets /v1/chat/completions) and translates the Chat response back into a Responses resource.
clusters:
- chat-completions-backend
services: []
S14:
narr: <b>Native Chat ingress.</b> A raw POST /v1/chat/completions is model-rewritten by the native instance (codex-* -> llama-3.3-70b) and bound by the exact-path route to chat-ingress-backend (application_provider direct); the direct-services terminal branch forwards the Chat-shaped body raw, so no Responses translation and no IRR run. The backend response is returned verbatim.
clusters:
- chat-ingress-backend
services: []
wire:
S1:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
reqBody: "{\n \"model\": \"gpt-4.1\",\n \"input\": \"Hello, world!\"\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'content-type: application/json'
- 'x-request-id: req_d1561f573ba741d7adf74a0ad57445f0'
resBody: "{\n \"id\": \"resp_abc123\",\n \"object\": \"response\",\n \"created_at\": 1000,\n \"status\": \"completed\",\n \"model\": \"gpt-4.1\",\n \"previous_response_id\": null,\n \"store\": true,\n \"error\": null,\n \"incomplete_details\": null,\n \"instructions\": null,\n \"max_output_tokens\": null,\n \"output\": [\n {\n \"id\": \"msg_001\",\n \"type\": \"message\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"Hello! How can I help you today?\",\n \"annotations\": []\n }\n ]\n }\n ],\n \"parallel_tool_calls\": true,\n \"temperature\": 1.0,\n \"tool_choice\": \"auto\",\n \"tools\": [],\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"metadata\": {},\n \"usage\": {\n \"input_tokens\": 17,\n \"input_tokens_details\": {\n \"cache_write_tokens\": 0,\n \"cached_tokens\": 0\n },\n \"output_tokens\": 9,\n \"output_tokens_details\": {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 26\n }\n}"
notes: 'Bare stateful create: store defaults to true (an implicit stateful marker per the config header comment), so the response echoes store:true, is looped through the IRR, and is persisted by openai_responses_store to SQLite for later GET /v1/responses/{id}. Only Content-Type is needed on ingress. previous_response_id is null on a fresh turn.'
provenance:
- examples/configs/openai/responses/full-flow-agentic.yaml:202-205
- examples/configs/openai/responses/full-flow-agentic.yaml:192-198
- tests/integration/fixtures/inference/recordings/openai/responses/native-basic-nonstream.json:51-144
- tests/integration/tests/suite/examples/openai_responses_store.rs:20,48-78
S2:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'Accept: text/event-stream'
reqBody: "{\n \"model\": \"gpt-4.1\",\n \"input\": \"Hello\",\n \"stream\": true\n}"
resStatus: 200 OK
resKind: sse
resHeaders:
- 'Content-Type: text/event-stream'
- 'Cache-Control: no-cache'
resBody: 'event: response.created
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_a1b2c3d4e5f60718","object":"response","created_at":1730902147,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-4.1","output":[],"parallel_tool_calls":true,"previous_response_id":null,"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","metadata":{},"usage":null}}
event: response.in_progress
data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_a1b2c3d4e5f60718","object":"response","created_at":1730902147,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-4.1","output":[],"parallel_tool_calls":true,"previous_response_id":null,"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","metadata":{},"usage":null}}
event: response.output_item.added
data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_a1b2c3d4e5f60718","type":"message","role":"assistant","status":"in_progress","content":[]}}
event: response.content_part.added
data: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_a1b2c3d4e5f60718","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_a1b2c3d4e5f60718","output_index":0,"content_index":0,"delta":"Hi"}
event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_a1b2c3d4e5f60718","output_index":0,"content_index":0,"delta":" there"}
event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":6,"item_id":"msg_a1b2c3d4e5f60718","output_index":0,"content_index":0,"delta":"!"}
event: response.output_text.done
data: {"type":"response.output_text.done","sequence_number":7,"item_id":"msg_a1b2c3d4e5f60718","output_index":0,"content_index":0,"text":"Hi there!"}
event: response.content_part.done
data: {"type":"response.content_part.done","sequence_number":8,"item_id":"msg_a1b2c3d4e5f60718","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Hi there!","annotations":[]}}
event: response.output_item.done
data: {"type":"response.output_item.done","sequence_number":9,"output_index":0,"item":{"id":"msg_a1b2c3d4e5f60718","type":"message","role":"assistant","status":"completed","content":[{"type":"output_text","text":"Hi there!","annotations":[]}]}}
event: response.completed
data: {"type":"response.completed","sequence_number":10,"response":{"id":"resp_a1b2c3d4e5f60718","object":"response","created_at":1730902147,"completed_at":1730902148,"status":"completed","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-4.1","output":[{"id":"msg_a1b2c3d4e5f60718","type":"message","role":"assistant","status":"completed","content":[{"type":"output_text","text":"Hi there!","annotations":[]}]}],"parallel_tool_calls":true,"previous_response_id":null,"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1.0,"truncation":"disabled","metadata":{},"usage":{"input_tokens":9,"input_tokens_details":{"cached_tokens":0},"output_tokens":4,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":13}}}
'
notes: 'Full native Responses SSE lifecycle composed by openai_stream_events inside the IRR: response.created/in_progress carry an in_progress response with empty output[], the message item streams through output_item.added -> content_part.added -> several output_text.delta -> output_text.done -> content_part.done -> output_item.done, and the terminal response.completed carries the fully assembled response object (status completed, output[], usage). sequence_number is monotonic and integer on every frame; the client stream ends on response.completed with no trailing data: [DONE] (client fixtures set done:false). store defaults to true so this streamed resource is accumulated into response_object and persisted pre-IRR, retrievable via GET /v1/responses/{id}. Bare create: only Content-Type (plus Accept for SSE) is relevant; no credential/trace headers apply.'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:372-455 (exact request {"model":"gpt-4.1","input":"Hello","stream":true} against THIS config; native SSE lifecycle, resp id + created_at + model in terminal, store-default persistence + retrieval)
- tests/integration/fixtures/inference/recordings/synthetic/responses/chat-basic-stream.json:50-113 (full client lifecycle with sequence_number on every frame, two output_text.delta, output_text.done/content_part.done/output_item.done ordering, usage detail shape, done:false = no [DONE])
- 'tests/integration/fixtures/inference/recordings/openai/responses/native-basic-stream.json:54-107 (native OpenAI lifecycle frame shapes: message item content[], content_part output_text part, terminal response object with usage)'
- tests/integration/tests/suite/examples/openai_stream_events.rs:63-157 (openai_stream_events composes ONE lifecycle + populates response_object for SQLite persistence; asserts content-type text/event-stream and response.completed present)
- 'tests/integration/sdk/openai/test_openai_responses_vllm.py:255-293,2540-2556 (SDK stream contract: response.created first, monotonic integer sequence_number, content_part.added/done + output_item.added/done + output_text.done present, deltas concatenate to terminal output_text, terminal completed carries usage)'
- examples/configs/openai/responses/full-flow-agentic.yaml:207-210,512-659 (streaming create curl example; IRR step with openai_stream_events + openai_responses_proxy typed streaming; store PRE-IRR persists accumulated response_object)
S3:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
reqBody: "{\n \"model\": \"gpt-4.1\",\n \"input\": \"Hello, world!\",\n \"store\": false\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'content-type: application/json'
- 'x-request-id: req_d1561f573ba741d7adf74a0ad57445f0'
resBody: "{\n \"id\": \"resp_def456\",\n \"object\": \"response\",\n \"created_at\": 1000,\n \"status\": \"completed\",\n \"model\": \"gpt-4.1\",\n \"previous_response_id\": null,\n \"store\": false,\n \"error\": null,\n \"incomplete_details\": null,\n \"instructions\": null,\n \"max_output_tokens\": null,\n \"output\": [\n {\n \"id\": \"msg_002\",\n \"type\": \"message\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"Hello! How can I help you today?\",\n \"annotations\": []\n }\n ]\n }\n ],\n \"parallel_tool_calls\": true,\n \"temperature\": 1.0,\n \"tool_choice\": \"auto\",\n \"tools\": [],\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"metadata\": {},\n \"usage\": {\n \"input_tokens\": 17,\n \"input_tokens_details\": {\n \"cache_write_tokens\": 0,\n \"cached_tokens\": 0\n },\n \"output_tokens\": 9,\n \"output_tokens_details\": {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 26\n }\n}"
notes: Same shape as S1 but the body sets store:false and carries no other stateful marker, so the object echoes store:false and is NOT persisted. A subsequent GET /v1/responses/resp_def456 returns 404 (test_stateless_request asserts NotFound). The native recording is itself a store:false turn, grounding the echoed field.
provenance:
- examples/configs/openai/responses/full-flow-agentic.yaml:212-215
- tests/integration/fixtures/inference/recordings/openai/responses/native-basic-nonstream.json:30-39,100
- tests/integration/sdk/openai/test_openai_responses_vllm.py:1658-1677
S4:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: acme'
- 'x-auth-user: user_42'
reqBody: "{\n \"model\": \"gpt-4.1\",\n \"input\": \"What did I just ask?\",\n \"previous_response_id\": \"resp_abc123\"\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'content-type: application/json'
- 'x-request-id: req_a72c0f9e4b8d4f1e9c2a1b3d5e6f7a80'
resBody: "{\n \"id\": \"resp_ghi789\",\n \"object\": \"response\",\n \"created_at\": 1010,\n \"status\": \"completed\",\n \"model\": \"gpt-4.1\",\n \"previous_response_id\": \"resp_abc123\",\n \"store\": true,\n \"error\": null,\n \"incomplete_details\": null,\n \"instructions\": null,\n \"max_output_tokens\": null,\n \"output\": [\n {\n \"id\": \"msg_003\",\n \"type\": \"message\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"You just asked: \\\"Hello, world!\\\"\",\n \"annotations\": []\n }\n ]\n }\n ],\n \"parallel_tool_calls\": true,\n \"temperature\": 1.0,\n \"tool_choice\": \"auto\",\n \"tools\": [],\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"metadata\": {},\n \"usage\": {\n \"input_tokens\": 24,\n \"input_tokens_details\": {\n \"cache_write_tokens\": 0,\n \"cached_tokens\": 0\n },\n \"output_tokens\": 8,\n \"output_tokens_details\": {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 32\n }\n}"
notes: 'Multi-turn continuation. previous_response_id is a stateful marker: openai_responses_rehydrate loads resp_abc123''s stored history (owner-scoped by x-auth-tenant/x-auth-user) and prepends it into the upstream input array, stripping previous_response_id from the outbound body so the backend echoes null. The rehydrate filter then restores the caller''s previous_response_id (resp_abc123) into the returned object (issue #932), so the client always sees the id it sent.'
provenance:
- examples/configs/openai/responses/full-flow-agentic.yaml:217-220
- examples/configs/openai/responses/full-flow-agentic.yaml:60-72
- tests/integration/tests/suite/examples/rehydrate.rs:66-88
- tests/integration/sdk/openai/test_openai_responses_vllm.py:1913-1960
- tests/integration/sdk/openai/test_openai_responses_vllm.py:1962-2021
S5:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'x-user-ogx-key: Bearer integration-ogx-key'
- 'x-auth-tenant: integration-tenant'
- 'x-auth-user: integration-user'
- 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
reqBody: "{\n \"model\": \"gpt-4.1\",\n \"input\": \"What does the README say about deployment?\",\n \"include\": [\n \"file_search_call.results\"\n ],\n \"tools\": [\n {\n \"type\": \"file_search\",\n \"vector_store_ids\": [\n \"vs_abc123\"\n ]\n }\n ]\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"resp_final\",\n \"object\": \"response\",\n \"status\": \"completed\",\n \"model\": \"gpt-4.1\",\n \"output\": [\n {\n \"id\": \"fs_1\",\n \"type\": \"file_search_call\",\n \"status\": \"completed\",\n \"queries\": [\n \"deployment instructions\"\n ],\n \"results\": [\n {\n \"file_id\": \"file-readme\",\n \"filename\": \"README.md\",\n \"score\": 0.92,\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Deploy the gateway by building the container image with `make container`, then run it behind an auth boundary.\"\n }\n ],\n \"attributes\": null\n }\n ]\n },\n {\n \"id\": \"msg_final\",\n \"type\": \"message\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"Deploy by building the container image and running it.\",\n \"annotations\": [\n {\n \"type\": \"file_citation\",\n \"file_id\": \"file-readme\",\n \"filename\": \"README.md\",\n \"index\": 54\n }\n ]\n }\n ]\n }\n ],\n \"usage\": {\n \"input_tokens\": 30,\n \"output_tokens\": 12,\n \"total_tokens\": 42\n }\n}"
notes: 'Buffered final composed object after a 2-round model->tool->model loop. Round 1: model emits a pending file_search_call (id fs_1, status "searching", queries). openai_file_search_dispatch POSTs a vector-store query to 127.0.0.1:3002 with body {"query":"deployment instructions","rewrite_query":false}, authenticating with the ogx_files slot (Authorization: Bearer integration-ogx-key captured from x-user-ogx-key) and projected x-tenant-id/x-user-id headers; it reconciles results in place. Round 2: model composes the answer with a <|file-readme|> marker; the citation rewriter strips the marker (popping the preceding space) and appends a file_citation annotation at cleaned-text index 54. openai_agentic_loop publishes action=done. results[] appears only because include:["file_search_call.results"] was requested (otherwise stripped). usage aggregates both rounds (10+20 / 5+7 / 15+27).'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1190-1305
- tests/integration/tests/suite/examples/file_search_dispatch.rs:37-146
- apis/src/openai/responses/file_search_dispatch/citations.rs:158-213
- apis/src/openai/include.rs:206-224
- examples/configs/openai/responses/full-flow-agentic.yaml:585-607
S6:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: acme'
- 'x-auth-user: user_42'
- 'x-user-mcp-key: mcp-user-secret-7f3a2c'
- 'x-mcp-authorized: mcpa_eyJ0ZW5hbnQiOiJhY21lIiwic3ViIjoidXNlcl80MiJ9.opaque-short-lived-assertion'
reqBody: "{\n \"model\": \"gpt-4.1\",\n \"input\": \"What is the weather in SF?\",\n \"tools\": [\n {\n \"type\": \"mcp\",\n \"server_label\": \"weather\",\n \"connector_id\": \"trusted-mcp\",\n \"allowed_tools\": [\n \"get_weather\"\n ],\n \"require_approval\": \"never\"\n }\n ]\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"resp_2\",\n \"object\": \"response\",\n \"status\": \"completed\",\n \"model\": \"gpt-4.1\",\n \"output\": [\n {\n \"id\": \"mcpl_9f2c1a7b3d4e5f60\",\n \"type\": \"mcp_list_tools\",\n \"server_label\": \"weather\",\n \"tools\": [\n {\n \"name\": \"get_weather\",\n \"description\": \"Get the weather for a location\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"location\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"location\"\n ],\n \"additionalProperties\": false\n }\n }\n ]\n },\n {\n \"type\": \"function_call\",\n \"id\": \"fc_1\",\n \"call_id\": \"call_abc\",\n \"name\": \"weather__get_weather\",\n \"arguments\": \"{\\\"location\\\":\\\"SF\\\"}\",\n \"status\": \"completed\"\n },\n {\n \"type\": \"mcp_call\",\n \"id\": \"call_abc\",\n \"approval_request_id\": null,\n \"server_label\": \"weather\",\n \"name\": \"get_weather\",\n \"arguments\": \"{\\\"location\\\":\\\"SF\\\"}\",\n \"output\": \"mock result for get_weather\"\n },\n {\n \"type\": \"message\",\n \"id\": \"msg_2\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"The weather in SF is 72F and sunny.\"\n }\n ]\n }\n ],\n \"usage\": {\n \"input_tokens\": 30,\n \"output_tokens\": 11,\n \"total_tokens\": 41\n }\n}"
notes: 'Distinctive shape: a two-phase agentic loop composed into one buffered Responses object. openai_mcp_tool_resolve first resolves connector_id ''trusted-mcp'' to server_url http://127.0.0.1:8001/mcp, calls tools/list, and seeds one mcp_list_tools item (server_label is the client label ''weather'', not the connector id; tools carry input_schema not inputSchema). The model then emits a function_call (name prefixed ''weather__get_weather''); openai_mcp_dispatch executes it against the same connector and appends the mcp_call record (name is the bare ''get_weather'', server_label ''weather'', id = the call_id, approval_request_id null since require_approval:never, output = the tool result). openai_agentic_loop signals loop, a second inference round returns the assistant message, then done. The mcp_gateway credential (x-user-mcp-key) and authorization assertion (x-mcp-authorized) are forwarded ONLY to the configured connector_id target, never to an arbitrary client server_url; the buffered terminal keeps the last round''s backend response id (resp_2) and sums usage across both rounds (30/11/41).'
provenance:
- examples/configs/openai/responses/full-flow-agentic.yaml:88-93
- examples/configs/openai/responses/full-flow-agentic.yaml:414-425
- examples/configs/openai/responses/full-flow-agentic.yaml:326-345
- examples/configs/openai/responses/full-flow-agentic.yaml:569-578
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:438-536
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:1270-1318
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:2106-2264
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:6813-6819
- tests/integration/tests/suite/examples/openai_mcp_streaming.rs:56-137
- apis/src/openai/responses/mcp_tool_resolve/mod.rs:2606-2635
- apis/src/openai/responses/mcp_tool_resolve/mod.rs:2705-2715
- apis/src/openai/responses/mcp_dispatch/mod.rs:1774-1816
- apis/src/openai/responses/mcp_dispatch/mod.rs:1630-1716
S7:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: acme'
- 'x-auth-user: alice'
- 'x-user-brave-key: alice-brave-secret'
reqBody: "{\n \"model\": \"gpt-4.1\",\n \"input\": \"What are the latest headlines about the Toronto Raptors?\",\n \"tools\": [\n {\n \"type\": \"web_search\"\n }\n ]\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"resp_0a1b2c3d4e5f60718293a4b5c6\",\n \"object\": \"response\",\n \"created_at\": 1783307880,\n \"status\": \"completed\",\n \"error\": null,\n \"incomplete_details\": null,\n \"model\": \"gpt-4.1\",\n \"previous_response_id\": null,\n \"output\": [\n {\n \"type\": \"web_search_call\",\n \"id\": \"ws_68a1f0c2d1348190a7c3e5f102\",\n \"status\": \"completed\",\n \"action\": {\n \"type\": \"search\",\n \"query\": \"latest Toronto Raptors headlines\"\n }\n },\n {\n \"type\": \"message\",\n \"id\": \"msg_68a1f0c3a2b48190b4d6f70213\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"The Raptors are finalizing their rotation ahead of the season opener, with Scottie Barnes expected to take on a larger playmaking role, and the team added frontcourt depth in a roster move this week.\",\n \"annotations\": [\n {\n \"type\": \"url_citation\",\n \"url\": \"https://www.nba.com/raptors/news\",\n \"title\": \"Toronto Raptors News | NBA.com\",\n \"start_index\": 0,\n \"end_index\": 132\n },\n {\n \"type\": \"url_citation\",\n \"url\": \"https://www.sportsnet.ca/nba/toronto-raptors/\",\n \"title\": \"Toronto Raptors - Sportsnet\",\n \"start_index\": 134,\n \"end_index\": 195\n }\n ]\n }\n ]\n }\n ],\n \"usage\": {\n \"input_tokens\": 318,\n \"input_tokens_details\": {\n \"cached_tokens\": 0\n },\n \"output_tokens\": 96,\n \"output_tokens_details\": {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 414\n }\n}"
notes: 'Final composed Responses object after the agentic loop: output[] holds a completed web_search_call (action.type=search + query) followed by the assistant message whose output_text carries url_citation annotations. openai_web_search_dispatch executes the model-emitted web_search_call via Brave using the per-user brave_search slot captured from x-user-brave-key by callout_credentials; the slot is required (user_credential: brave_search) so a missing key fails closed with 401 missing_callout_context before any provider callout. action.sources is omitted because the request did not set include:["web_search_call.action.sources"]. openai_agentic_loop is the sole transition authority and finalizes with action=done once no further tool calls remain.'
provenance:
- examples/configs/openai/responses/full-flow-agentic.yaml:551-565
- examples/configs/openai/responses/full-flow-agentic.yaml:326-337
- apis/src/openai/responses/web_search_dispatch/mod.rs:819-856
- apis/src/openai/responses/file_search_dispatch/citations.rs:507-512
- apis/src/openai/responses/file_search_dispatch/citations.rs:563
- tests/integration/fixtures/agentic_api/web_search_nonstreaming.json:25-83
- tests/integration/fixtures/inference/recordings/synthetic/responses/chat-web-search.json:63-92
- tests/integration/tests/suite/examples/web_search_scoped_credentials.rs:139-175
- tests/integration/tests/suite/examples/web_search_scoped_credentials.rs:225-245
S8:
reqMethod: GET
reqPath: /v1/responses/resp_abc123
reqHeaders:
- 'Host: localhost:8080'
- 'Accept: application/json'
- 'x-auth-tenant: acme'
- 'x-auth-user: alice'
reqBody: ''
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"resp_abc123\",\n \"object\": \"response\",\n \"created_at\": 1000,\n \"status\": \"completed\",\n \"model\": \"gpt-4.1\",\n \"output\": [\n {\n \"type\": \"message\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"Hi there\",\n \"annotations\": []\n }\n ]\n }\n ],\n \"usage\": {\n \"input_tokens\": 9,\n \"input_tokens_details\": {\n \"cached_tokens\": 0\n },\n \"output_tokens\": 4,\n \"output_tokens_details\": {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 13\n }\n}"
notes: openai_responses_store runs PRE-IRR and short-circuits GET /v1/responses/{id} with the persisted response_object served verbatim as application/json; rehydrate, static_response, all callouts, and the IRR agentic loop never execute. Owner-scoped lookup keys on the state_owner context (x-auth-tenant/x-auth-user), so the object is returned identically whether it was originally created buffered or accumulated from a stream:true lifecycle.
provenance:
- apis/src/openai/responses/store/filter.rs:838-844 (GET handled in on_request before IRR)
- apis/src/openai/responses/store/filter.rs:1040-1047 (returns record.response_object verbatim, content-type application/json, status 200)
- apis/src/openai/responses/store/filter.rs:1034-1037 (owner-scoped via require_state_owner)
- tests/integration/tests/suite/examples/openai_responses_store.rs:20 (RESPONSE_JSON stored shape), 164-168 (GET returns stored id/model at 200)
- tests/integration/tests/suite/examples/full_flow_agentic.rs:33 (FIRST_RESPONSE_JSON completed shape), 441-452 (GET stored streamed resource, status/output asserted)
- 'tests/integration/tests/suite/codex_http.rs:1105-1114 (OpenResponses usage: input_tokens_details.cached_tokens + output_tokens_details.reasoning_tokens required)'
- examples/configs/openai/responses/full-flow-agentic.yaml:59-66,184-185 (openai_responses_store persists PRE-IRR; GET served pre-IRR), 244-245 (retrieve curl example)
S9:
reqMethod: GET
reqPath: /v1/responses
reqHeaders:
- 'Host: localhost:8080'
- 'x-auth-tenant: acme'
- 'x-auth-user: alice'
- 'Connection: Upgrade'
- 'Upgrade: websocket'
- 'Sec-WebSocket-Version: 13'
- 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=='
reqBody: ''
resStatus: 101 Switching Protocols
resKind: handshake
resHeaders:
- 'Upgrade: websocket'
- 'Connection: Upgrade'
- 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo='
resBody: "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"
notes: 'A GET /v1/responses carrying Upgrade: websocket is bound by the top-level router to responses-websocket-backend (application_provider=direct) via the dedicated upgrade:websocket route. The direct-services terminal branch then forwards the handshake straight to 3001 and rejoins at terminal, so the managed body filters and the IRR never run (the IRR buffers responses and cannot handle 101 upgrades). The Sec-WebSocket-Accept is the RFC 6455 SHA-1 of the client key; after 101 the tunnel is bidirectional and the backend streams response.create-style Responses events.'
provenance:
- 'tests/integration/tests/suite/examples/full_flow_agentic.rs:1113-1196 (full_flow_websocket_upgrade_preserves_handshake_and_ordered_text: WS request head + ordered text relay)'
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1374-1397 (full_flow_websocket_handshake_reaches_inference_backend: handshake returns SWITCHING_PROTOCOLS and reaches the 3001 backend)
- tests/integration/fixtures/openai/responses/websocket-codex-0.144.1.json:15-19 (WS response.create client kind and Responses event schema)
- 'examples/configs/openai/responses/full-flow-agentic.yaml (router route: path /v1/responses + upgrade:websocket -> responses-websocket-backend; direct-services branch when bound_upstream.application_provider=direct forwards to 3001 and rejoins terminal)'
S10:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: integration-tenant'
- 'x-auth-user: integration-user'
reqBody: "{\n \"model\": \"gpt-4\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Hello\"\n }\n ]\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"resp_chat_body\",\n \"object\": \"response\",\n \"created_at\": 1000,\n \"model\": \"gpt-4\",\n \"status\": \"completed\",\n \"output\": []\n}"
notes: 'Operation identity is authoritative over body shape (#1602). ai_operation matches the request head POST /v1/responses -> createResponse (application_protocol=openai_responses), and openai_responses_request''s classify_matched_operation then forces format=openai_responses for the matched create body regardless of its {model,messages} Chat shape, promoting x-praxis-ai-format:openai_responses and x-praxis-ai-model:gpt-4. The model-specific /v1/responses routes do not match (gpt-5 and vllm-chat require their x-praxis-ai-model), so the body falls to the Responses catch-all (path_prefix /v1/responses + x-praxis-ai-format:openai_responses -> inference-backend). That cluster is not application_provider=openai, so the direct-OpenAI branch is skipped and the managed path handles the request, reaching the inference backend which returns a Response object -> 200. This supersedes the pre-#1602 behavior, where a Chat/Anthropic-shaped body demoted the format fact and missed every format-constrained route, yielding a bare 404. The resBody mirrors the minimal completed Response the integration test''s capturing backend returns (output shape is produced by the real inference backend).'
provenance:
- 'apis/src/openai/responses/request/mod.rs:550-552 (classify_matched_operation forces classified.format = AiRequestFormat::Responses for a matched Responses create body, so body shape never demotes the format fact)'
- 'examples/configs/openai/responses/full-flow-agentic.yaml:432-435 (router Responses catch-all: path_prefix /v1/responses + x-praxis-ai-format:openai_responses -> inference-backend, reached once the gpt-5/vllm-chat model routes miss)'
- tests/integration/tests/suite/examples/full_flow_agentic.rs:489-532 (full_flow_chat_completions_body_on_responses_path_is_treated_as_responses asserts 200 and the backend receives the request once)
- tests/integration/tests/suite/examples/full_flow_agentic.rs:534-577 (full_flow_anthropic_messages_body_on_responses_path_is_treated_as_responses: same Responses routing for an Anthropic-shaped body)
S11:
reqMethod: POST
reqPath: /v1/embeddings
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: integration-tenant'
- 'x-auth-user: integration-user'
reqBody: "{\n \"model\": \"text-embedding-3-small\",\n \"input\": \"The quick brown fox\"\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": \"embedding\",\n \"index\": 0,\n \"embedding\": [\n 0.0023064255,\n -0.009327292,\n 0.015797347,\n -0.0079056\n ]\n }\n ],\n \"model\": \"text-embedding-3-small\",\n \"usage\": {\n \"prompt_tokens\": 5,\n \"total_tokens\": 5\n }\n}"
notes: 'POST /v1/embeddings matches the top-level router''s path_prefix `/v1/embeddings` -> cluster embeddings-api (application_provider=direct, 127.0.0.1:9997). Because the binding is direct, the direct-services terminal branch forwards it straight upstream and rejoins at `terminal`, so it NEVER enters the IRR and the managed body filters never run. The gateway forwards the embeddings backend''s response verbatim, so the exact embedding object shape is produced by the upstream embeddings-api, not the gateway. The `embedding` vector is truncated to four floats for brevity (real vectors are 1536-dim for text-embedding-3-small). The other dedicated services take the same direct-services branch by path_prefix and likewise skip the IRR: /v1/prompts -> prompts-api (127.0.0.1:9998), /v1/files -> files-api (127.0.0.1:9999), /v1/vector_stores -> vector-stores-backend (127.0.0.1:3002). NOTE: no repo fixture emits a real embeddings JSON object (the embeddings-routing functional test returns the marker string "embeddings-api"); this is the minimal truthful OpenAI embeddings shape.'
provenance:
- examples/configs/openai/responses/full-flow-agentic.yaml (router path_prefix /v1/embeddings -> embeddings-api; direct-services-dispatch load_balancer endpoint 127.0.0.1:9997; also /v1/prompts:9998, /v1/files:9999, /v1/vector_stores:3002)
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1559-1588 (full_flow_agentic_non_responses_path_bypasses_irr proves a dedicated-service path bypasses the IRR and the direct-services branch forwards the backend response verbatim)
- tests/integration/tests/suite/examples/openai_embeddings_routing.rs:14-60 (embeddings routing functional test; backend returns marker string, no JSON embeddings fixture exists)
S12:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: integration-tenant'
- 'x-auth-user: integration-user'
- 'x-user-ogx-key: Bearer integration-ogx-key'
reqBody: "{\n \"model\": \"gpt-5\",\n \"input\": \"continue at the provider\",\n \"background\": true,\n \"previous_response_id\": \"resp_provider_owned\",\n \"conversation\": \"conv_provider_owned\"\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"resp_openai_direct\",\n \"object\": \"response\",\n \"status\": \"queued\",\n \"background\": true,\n \"output\": []\n}"
notes: 'model gpt-5 makes the pre-routing openai_responses_request publish x-praxis-ai-model:gpt-5, which the router matches to openai-responses-backend with application_provider=openai. The direct-openai terminal branch then forwards the ORIGINAL Responses request UNCHANGED to the provider (3001) and stops the chain, so the managed openai_responses_request owner, the store/rehydrate/callout body filters, and the IRR never run. This is why background:true, previous_response_id:resp_provider_owned, and conversation:conv_provider_owned all pass straight through: on the managed path background would be rejected 400 (see full_flow_managed_provider_rejects_background_before_forwarding) and the provider-owned continuation ids would be gateway-validated, but here the provider owns the request/response. The backend receives exactly one request at /v1/responses whose body is byte-identical to the client request, and the provider''s response (status:queued) is returned verbatim without gateway composition.'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:365-410 (full_flow_openai_provider_is_direct_passthrough: 200, provider response returned verbatim, backend gets one /v1/responses request with body === client request)
- tests/integration/tests/suite/examples/full_flow_agentic.rs:413-439 (full_flow_managed_provider_rejects_background_before_forwarding: the managed path rejects background:true with 400, contrasting the direct passthrough)
- 'examples/configs/openai/responses/full-flow-agentic.yaml (router route: path /v1/responses + x-praxis-ai-format:openai_responses + x-praxis-ai-model:gpt-5 -> openai-responses-backend; direct-openai branch when bound_upstream.application_provider=openai forwards to 3001 and rejoins terminal; body filters + IRR gated unless application_provider=openai)'
S13:
reqMethod: POST
reqPath: /v1/responses
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: integration-tenant'
- 'x-auth-user: integration-user'
- 'x-user-ogx-key: Bearer integration-ogx-key'
reqBody: "{\n \"model\": \"vllm-chat\",\n \"input\": \"hello\",\n \"store\": false\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"resp_bound\",\n \"object\": \"response\",\n \"status\": \"completed\",\n \"model\": \"vllm-chat\",\n \"output\": [\n {\n \"type\": \"message\",\n \"role\": \"assistant\",\n \"status\": \"completed\",\n \"content\": [\n {\n \"type\": \"output_text\",\n \"text\": \"translated\",\n \"annotations\": []\n }\n ]\n }\n ],\n \"usage\": {\n \"input_tokens\": 3,\n \"output_tokens\": 1,\n \"total_tokens\": 4\n }\n}"
notes: 'model vllm-chat makes the pre-routing openai_responses_request publish x-praxis-ai-model:vllm-chat, which the router matches to chat-completions-backend (managed vllm, application_provider=direct is NOT set — the provider is vllm), so the direct branches stay inert and the request flows through the managed body filters into the IRR. Inside the single IRR round the IRR-step load_balancer selects chat-completions-backend and publishes application_protocol:openai_chat_completions; that gate makes openai_responses_proxy inert and instead fires openai_responses_to_chat_completions (which translates the Responses request into a Chat Completions request carrying messages, not input) and path_rewrite (which retargets the outbound path to /v1/chat/completions). The backend therefore receives exactly one request at /v1/chat/completions. On the reverse path openai_responses_to_chat_completions translates the Chat completion back into a Responses resource before openai_agentic_loop and openai_stream_events observe it. store:false so nothing is persisted. The test asserts only response.object=="response" and output[0].content[0].text=="translated"; the id shown is gateway-generated (openai_responses_request ran on the managed path) and the usage (3/1/4) is the Chat usage mapped by the translator — both illustrative here.'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:442-486 (full_flow_managed_chat_backend_translates_after_binding: 200, response.object==response + output text "translated"; backend gets one /v1/chat/completions request whose body has messages and no input)
- 'examples/configs/openai/responses/full-flow-agentic.yaml (router route: path /v1/responses + x-praxis-ai-format:openai_responses + x-praxis-ai-model:vllm-chat -> chat-completions-backend; IRR-step load_balancer cluster_source:bound_upstream selects openai_chat_completions; openai_responses_to_chat_completions + path_rewrite gated when selected_upstream.application_protocol:openai_chat_completions; openai_responses_proxy gated when openai_responses)'
S14:
reqMethod: POST
reqPath: /v1/chat/completions
reqHeaders:
- 'Content-Type: application/json'
- 'x-auth-tenant: integration-tenant'
- 'x-auth-user: integration-user'
- 'x-user-ogx-key: Bearer integration-ogx-key'
reqBody: "{\n \"model\": \"codex-mini\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"ping\"\n }\n ]\n}"
resStatus: 200 OK
resKind: json
resHeaders:
- 'Content-Type: application/json'
resBody: "{\n \"id\": \"chatcmpl_native\",\n \"object\": \"chat.completion\",\n \"created\": 1000,\n \"model\": \"llama-3.3-70b\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"pong\"\n },\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 1,\n \"completion_tokens\": 1,\n \"total_tokens\": 2\n }\n}"
notes: 'The native Chat model policy (instance B) rewrites the body model codex-mini -> llama-3.3-70b via the codex-* alias and promotes x-praxis-ai-effective-model; the router binds the exact path /v1/chat/completions to chat-ingress-backend (application_provider=direct), so the direct-services terminal branch forwards the Chat-shaped body RAW and stops the chain. The managed body filters (validate/tool_parse/store/rehydrate/file_resolve/doc_extract/compact) and the IRR never run, and no Responses translation happens: the backend receives exactly one request at /v1/chat/completions whose body still has messages (not input) and model llama-3.3-70b, and the backend chat.completion is returned to the client verbatim.'
provenance:
- 'tests/integration/tests/suite/examples/full_flow_agentic.rs (full_flow_native_chat_ingress_reaches_chat_backend_raw: 200, backend response returned verbatim, backend gets one /v1/chat/completions request whose body has messages, no input, and model llama-3.3-70b)'
- 'examples/configs/openai/responses/full-flow-agentic.yaml (openai_responses_model_rewrite instance B: when path_prefix /v1/chat/completions, codex-* -> llama-3.3-70b; router route path /v1/chat/completions -> chat-ingress-backend; direct-services branch when bound_upstream.application_provider=direct forwards to 3001 and rejoins terminal)'
timeline:
S1:
summary: 'A no-tools create crosses the wire in exactly one inference round: client POST /v1/responses {model,input} -> IRR forwards the create to inference-backend 127.0.0.1:3001 -> the model returns a final assistant message with NO tool-call items -> the gateway ships that Responses object back byte-for-byte. The IRR transition is owned solely by openai_agentic_loop (full-flow-agentic.yaml:608-613): it parses each model response and publishes a single continuation signal action=loop|done, routed at :646-658 (`key: action, value: loop, next: inference` re-enters inference; the `default: true / done: true` branch finalizes). A plain assistant message has no hosted-tool items, so the loop does NOT publish action=loop and the default done branch finalizes after the first pass (mechanic mirrored by the injected on_result at openai_agentic_loop.rs:6919-6923, whose comment states ''IRR terminates after the first pass via the default `done: true` branch''). This is why tool scenarios need extra rounds and this one does not: hosted-tool items make the loop publish action=loop -> next: inference, adding a re-entry round after the callout (file_search two-round trip full_flow_agentic.rs:1189-1305 with one vector-store callout at :1283; MCP two-round trip openai_agentic_loop.rs:250-428 with model_reqs.len()==2 at :366), whereas a plain message — or even a client-owned function_call — returns after a single model request (openai_agentic_loop.rs:60-61 and :139-143). NOTE: the S1 passthrough test itself only asserts status + byte-equal body; forwarded-body and header-projection details are grounded in sibling tests, cited per step.'
rounds: single round · no tools
steps:
- n: 1
round: round 1
from: client
to: gateway
kind: request
title: Client create POST (no tools)
channel: POST http://{proxy}/v1/responses
note: 'Exact request body at full_flow_agentic.rs:1317. json_post() wraps it with the trusted ingress identity headers x-auth-tenant: integration-tenant, x-auth-user: integration-user, x-user-ogx-key: Bearer integration-ogx-key via the authenticated_request helper (constants TEST_TENANT/TEST_SUBJECT/TEST_OGX_CREDENTIAL at :58-60). No `store` field is sent and the test asserts nothing about persistence — this is a plain create, not verified as stateful.'
bodyKind: json
body: '{"model":"gpt-4.1","input":"Hello"}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1317
- tests/integration/tests/suite/examples/full_flow_agentic.rs:66-80
- tests/integration/tests/suite/examples/full_flow_agentic.rs:58-60
- n: 2
round: round 1
from: gateway
to: inference-backend
kind: request
title: IRR forwards the classified create to inference
channel: POST http://127.0.0.1:3001/v1/responses
note: 'Router maps /v1/responses -> cluster inference-backend at 127.0.0.1:3001 (yaml:624-639). Exactly one model request is made for a no-server-tool create and the body is forwarded byte-exact — grounded in the sibling tests single_pass_completes_through_irr (model_reqs.len()==1, openai_agentic_loop.rs:60-67) and explicit_false_preserves_original_request_bytes (byte-exact passthrough, :85-88); the S1 passthrough test uses a non-capturing backend so it does not itself assert the forwarded body. Identity projection (inference receives x-tenant-id: integration-tenant and x-user-id: integration-user via project_state_owner_headers at yaml:532-534, and raw x-auth-tenant/x-auth-user are stripped at the IRR boundary) is asserted in the sibling file_search round-trip at full_flow_agentic.rs:1261-1279, not in the S1 test.'
bodyKind: json
body: '{"model":"gpt-4.1","input":"Hello"}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1317
- examples/configs/openai/responses/full-flow-agentic.yaml:624-639
- examples/configs/openai/responses/full-flow-agentic.yaml:532-534
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:60-67
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:85-88
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1261-1279
- n: 3
round: round 1
from: inference-backend
to: gateway
kind: response
title: Final assistant message — no tool-call items
channel: 200 POST /v1/responses
note: 'Exact canned backend response at full_flow_agentic.rs:1309. openai_agentic_loop (sole loop owner, max_infer_iters: 7, yaml:608-613) parses this response, finds no hosted-tool items, so it does NOT publish action=loop. The IRR on_result then takes the `default: true / done: true` branch (yaml:646-658) and finalizes after the first pass — no re-entry. The same terminate-on-default mechanic is documented at openai_agentic_loop.rs:6919-6923.'
bodyKind: json
body: '{"id":"resp_456","object":"response","output":[{"id":"msg_456","type":"message","role":"assistant","status":"completed","content":[{"type":"output_text","text":"Hello","annotations":[]}]}]}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1309
- examples/configs/openai/responses/full-flow-agentic.yaml:608-613
- examples/configs/openai/responses/full-flow-agentic.yaml:646-658
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:6919-6923
- n: 4
round: final
from: gateway
to: client
kind: response
title: Final Responses object returned verbatim
channel: 200 POST /v1/responses
note: Status 200 (assert at :1320) and the client body equals the backend response byte-for-byte (assert_eq!(parse_body(&raw), response) at :1321), confirming a single-round pass-through with no loop re-entry.
bodyKind: json
body: '{"id":"resp_456","object":"response","output":[{"id":"msg_456","type":"message","role":"assistant","status":"completed","content":[{"type":"output_text","text":"Hello","annotations":[]}]}]}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1320-1321
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1309
S5:
summary: 'Adversarial re-verification confirms every step: a hosted file_search request enters the full-flow-agentic IRR, round-1 inference emits a file_search_call, openai_file_search_dispatch POSTs the query to the vector store at 127.0.0.1:3002 with the scoped ogx_files credential and X-Vector-Store-Client header, the result is reconciled into accumulated_output and bridged as a [function_call, function_call_output] pair into round-2 inference, whose <|file-q4|> marker becomes a file_citation annotation in the final /v1/responses object; all payloads are faithful quotes/trims and all cited line numbers are correct.'
rounds: 2 rounds · hosted file_search
steps:
- n: 1
round: round 1
from: client
to: gateway
kind: request
title: Client posts a hosted file_search request
channel: POST http://127.0.0.1:8080/v1/responses
note: 'VERIFIED. Body matches full_flow_agentic.rs:1242-1246. Authorization: Bearer search-key prepended via replacen at :1247-1251. json_post -> authenticated_request (:78-79, :67-75) injects x-auth-tenant: integration-tenant / x-auth-user: integration-user / x-user-ogx-key: Bearer integration-ogx-key (consts at :58-60); these ingress identity headers are consumed pre-IRR by state_owner + callout_credentials. Listener is 127.0.0.1:8080 (config :302-305).'
bodyKind: json
body: '{"model": "gpt-4.1", "input": "What do the documents say about Q4?", "tools": [{"type": "file_search", "vector_store_ids": ["vs_q4"]}]}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1242-1251
- tests/integration/tests/suite/examples/full_flow_agentic.rs:57-75
- examples/configs/openai/responses/full-flow-agentic.yaml:302-305
- n: 2
round: round 1
from: gateway
to: inference-backend
kind: request
title: IRR round-1 inference request carries the file_search tool
channel: POST http://127.0.0.1:3001/v1/responses
note: 'VERIFIED. openai_responses_proxy (config :645) rebuilds the outbound body from ResponsesState. S5 asserts per-round inference headers at :1261-1279: authorization: bearer search-key (client key preserved), projected x-tenant-id: integration-tenant + x-user-id: integration-user (project_state_owner_headers, config :532-534), and NO x-auth-tenant:/x-auth-user: (raw ingress identity stripped at the IRR boundary). Router path /v1/responses -> cluster inference-backend endpoint 127.0.0.1:3001 (config :624-639). The exact wire body is NOT asserted by S5; shown faithful to the client input+tools it is rebuilt from.'
bodyKind: json
body: '{"model": "gpt-4.1", "input": "What do the documents say about Q4?", "tools": [{"type": "file_search", "vector_store_ids": ["vs_q4"]}]}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1242-1246
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1261-1279
- examples/configs/openai/responses/full-flow-agentic.yaml:526-534
- examples/configs/openai/responses/full-flow-agentic.yaml:624-639
- n: 3
round: round 1
from: inference-backend
to: gateway
kind: response
title: Model emits a file_search_call (status searching)
channel: 200 http://127.0.0.1:3001/v1/responses
note: VERIFIED. Exact quote of first_model_response (full_flow_agentic.rs:1191-1202), served first by StatefulCapturingBackend (:1220-1224). openai_agentic_loop parses this, records the file_search_call assignment for the dispatcher, and publishes action=loop.
bodyKind: json
body: '{"id": "resp_search", "object": "response", "status": "completed", "output": [{"id": "fs_1", "type": "file_search_call", "status": "searching", "queries": ["What were the Q4 results?"]}], "usage": {"input_tokens": 10, "output_tokens": 5, "total_tokens": 15}}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1191-1202
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1220-1224
- n: 4
round: callout
from: gateway
to: vector-store
kind: request
title: openai_file_search_dispatch searches the vector store
channel: POST http://127.0.0.1:3002/v1/vector_stores/vs_q4/search
note: 'VERIFIED. Exactly one POST callout (S5 asserts search_callouts.len()==1 at :1281-1283). Body shape confirmed by the sibling assertion query=="What were the Q4 results?" + rewrite_query==false (file_search_dispatch.rs:131-133) and by VectorStoreSearchRequest (client.rs:120-143, rewrite_query always false at :138/:159). URL path v1/vector_stores/{store_id}/search built by search_url/resource_url (client.rs:887-903); destination is vector_store_url http://127.0.0.1:3002 (config :586). Headers asserted at :1284-1304: authorization: bearer integration-ogx-key (scoped ogx_files slot, NOT client search-key), x-tenant-id, x-user-id, no x-auth-*. Outbound chain sets X-Vector-Store-Client: praxis-ai-gateway (config :596-599). Request also carries optional filters/max_num_results/ranking_options/search_mode (omitted, skip_serializing_if none, not asserted).'
bodyKind: json
body: '{"query": "What were the Q4 results?", "rewrite_query": false}'
provenance:
- tests/integration/tests/suite/examples/file_search_dispatch.rs:131-133
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1281-1304
- apis/src/openai/responses/file_search_dispatch/client.rs:120-143
- apis/src/openai/responses/file_search_dispatch/client.rs:786-902
- examples/configs/openai/responses/full-flow-agentic.yaml:585-607
- n: 5
round: callout
from: vector-store
to: gateway
kind: response
title: Vector store returns the matching chunk
channel: 200 http://127.0.0.1:3002/v1/vector_stores/vs_q4/search
note: 'VERIFIED. Exact quote of search_response (full_flow_agentic.rs:1225-1234). The dispatcher reconciles in place: accumulated_output[fs_1].status becomes completed (status->completed proven by sibling unit test tests.rs:512; results are NOT added because the S5 request sets no include:[file_search_call.results], no-results-when-unset proven by tests.rs:611-612), citation_files gains file-q4 -> q4-results.txt, and the model-context bridge is appended to state.messages via model_context_messages (mod.rs:1339-1366).'
bodyKind: json
body: '{"data": [{"file_id": "file-q4", "filename": "q4-results.txt", "score": 0.99, "content": [{"type": "text", "text": "Q4 revenue was $42 million."}], "attributes": null}]}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1225-1234
- apis/src/openai/responses/file_search_dispatch/tests.rs:611-612
- apis/src/openai/responses/file_search_dispatch/mod.rs:1339-1366
- n: 6
round: round 2
from: gateway
to: inference-backend
kind: request
title: IRR round-2 inference request with results bridged in
channel: POST http://127.0.0.1:3001/v1/responses
note: 'VERIFIED. The [function_call, function_call_output] bridge shape is quoted from model_context_messages (mod.rs:1339-1366): call_id = file_search_{output_index}_{call_hash:016x} (:1350), arguments = json!({"query": query}).to_string() (:1351). Output string built from CONTEXT_TEMPLATE + ANNOTATION_TEMPLATE (model_context.rs:26-29), score via to_string (model_context.rs:382). Unit test tests.rs:520-536 confirms messages == [message, function_call, function_call_output] and the output starts_with ''file_search found 1 chunks for ...'', contains ''<|file-...|>'', ends_with content+''\n''. Same per-round headers as round 1 (:1261-1279). The round-2 wire body is NOT directly asserted by S5; reconstructed faithfully from the bridge unit test + templates + mod.rs applied to S5 data.'
bodyKind: json
body: '{"model": "gpt-4.1", …, "input": [ …prior items…, {"type": "function_call", "call_id": "file_search_0_<hash16>", "name": "file_search", "arguments": "{\"query\":\"What were the Q4 results?\"}", "status": "completed"}, {"type": "function_call_output", "call_id": "file_search_0_<hash16>", "output": "file_search found 1 chunks for \"What were the Q4 results?\":\n[0] q4-results.txt (score: 0.99) cite as <|file-q4|>\nQ4 revenue was $42 million.\n"}]}'
provenance:
- apis/src/openai/responses/file_search_dispatch/mod.rs:1339-1366
- apis/src/openai/responses/file_search_dispatch/tests.rs:520-536
- apis/src/openai/responses/file_search_dispatch/model_context.rs:26-29
- apis/src/openai/responses/file_search_dispatch/model_context.rs:382
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1261-1279
- n: 7
round: round 2
from: inference-backend
to: gateway
kind: response
title: Model returns the answer with a citation marker
channel: 200 http://127.0.0.1:3001/v1/responses
note: VERIFIED. Exact quote of final_model_response (full_flow_agentic.rs:1203-1219), served second by StatefulCapturingBackend (:1220-1224). openai_agentic_loop sees no further hosted-tool calls and publishes done (exit to client).
bodyKind: json
body: '{"id": "resp_final", "object": "response", "status": "completed", "output": [{"id": "msg_final", "type": "message", "role": "assistant", "status": "completed", "content": [{"type": "output_text", "text": "Q4 revenue was $42 million <|file-q4|>", "annotations": []}]}], "usage": {"input_tokens": 20, "output_tokens": 7, "total_tokens": 27}}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1203-1219
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1220-1224
- n: 8
round: final
from: gateway
to: client
kind: response
title: Final /v1/responses object returned to client
channel: 200 http://127.0.0.1:8080/v1/responses
note: 'VERIFIED. S5 asserts the structural subset directly (:1256-1259): id==resp_final, output[0].type==file_search_call, output[0].status==completed, output[1].type==message. output[0] has no results because the S5 request set no include. The <|file-q4|> marker stripping + file_citation conversion is NOT asserted by S5 itself but is proven by the sibling file-search-callout example test (file_search_dispatch.rs:117-125: text=="Q4 revenue was $42 million", annotations[0].file_id=="file-q4") and by citations.rs, which builds {type:file_citation, file_id, filename, index} annotations (citations.rs:209-212) after removing markers from output_text.'
bodyKind: json
body: '{"id": "resp_final", "object": "response", "status": "completed", "output": [{"id": "fs_1", "type": "file_search_call", "status": "completed", "queries": ["What were the Q4 results?"]}, {"id": "msg_final", "type": "message", "role": "assistant", "status": "completed", "content": [{"type": "output_text", "text": "Q4 revenue was $42 million", "annotations": [{"type": "file_citation", "file_id": "file-q4", "filename": "q4-results.txt", "index": …}]}]}]}'
provenance:
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1254-1259
- tests/integration/tests/suite/examples/file_search_dispatch.rs:117-125
- apis/src/openai/responses/file_search_dispatch/citations.rs:209-212
S6:
summary: 'A model-tool-model MCP loop: the gateway resolves MCP tools out of band (initialize/tools/list), lowers them to encoded function tools for round-1 inference, executes the model-selected MCP call via JSON-RPC tools/call, appends the tool result as a function_call_output for round-2 inference, and returns a single composed Responses object. Config STRUCTURE is grounded in full-flow-agentic.yaml (statically validated by full_flow_agentic.rs); WIRE PAYLOADS are faithful trims of the round-trip integration test (openai_agentic_loop.rs), the SSE test (openai_mcp_streaming.rs), the MCP mock-server fixtures (tests/utils agentic/mcp.rs), the mcp_client credential unit tests, and the mcp_dispatch result-construction tests. JSON-RPC id scalars are illustrative/rmcp-generated; only the envelope shapes are grounded.'
rounds: 2 rounds · MCP tool loop
steps:
- n: 1
round: pre-IRR
from: client
to: gateway
kind: request
title: Client POST /v1/responses with an MCP tool
channel: POST http://localhost:8080/v1/responses
note: 'Faithful trim of the integration-test request bodies (openai_agentic_loop.rs:299-311 and openai_mcp_streaming.rs:109-119), which pass server_url directly as a dynamic 127.0.0.1:<mcp-port>/mcp; normalized here to the documented connector port :8001. NOTE: the config curl example (yaml:232-242) actually shows server_url http://mcp-weather:8001/mcp, and per yaml:422 real clients reference the trusted-mcp connector via connector_id, not server_url. The round-trip test additionally sends conversation.id=conv_native and parallel_tool_calls=true (trimmed here). In full-flow-agentic the trusted boundary supplies x-user-mcp-key (->mcp_gateway slot) and x-mcp-authorized (->mcp_gateway assertion); callout_credentials strips both ingress headers before the IRR.'
bodyKind: json
body: '{"model":"gpt-4.1","input":"What is the weather in SF?","tools":[{"type":"mcp","server_label":"weather","server_url":"http://127.0.0.1:8001/mcp","allowed_tools":["get_weather"],"require_approval":"never"}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:299-311
- tests/integration/tests/suite/examples/openai_mcp_streaming.rs:109-119
- examples/configs/openai/responses/full-flow-agentic.yaml:232-242
- examples/configs/openai/responses/full-flow-agentic.yaml:326-345
- n: 2
round: pre-IRR (mcp_tool_resolve)
from: gateway
to: mcp-server
kind: request
title: 'MCP handshake: JSON-RPC initialize'
channel: POST http://127.0.0.1:8001/mcp
note: 'openai_mcp_tool_resolve opens a fresh Streamable HTTP transport; rmcp .serve() performs the handshake (mod.rs:403). For the connector target the exchange carries forwarded credentials: Authorization: Bearer <mcp_gateway>, x-mcp-authorized: <assertion>, x-tenant-id/x-user-id (asserted in mcp_client tests). JSON-RPC envelope is standard; the id scalar (0) and params (protocolVersion/capabilities/clientInfo) are rmcp-generated and trimmed — only protocolVersion 2025-03-26 is grounded (mcp.rs:35).'
bodyKind: json
body: '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{…},"clientInfo":{…}}}'
provenance:
- apis/src/mcp_client/mod.rs:359-403
- apis/src/mcp_client/mod.rs:716-750
- tests/utils/src/agentic/mcp.rs:35
- tests/utils/src/agentic/mcp.rs:409-410
- apis/src/mcp_client/tests.rs:1280-1290
- apis/src/mcp_client/tests.rs:1312-1345
- apis/src/callout_credentials.rs:45
- n: 3
round: pre-IRR (mcp_tool_resolve)
from: mcp-server
to: gateway
kind: response
title: initialize result
channel: 200 http://127.0.0.1:8001/mcp
note: 'Faithful to handle_initialize (mcp.rs:435-457): protocolVersion=DEFAULT_PROTOCOL_VERSION ''2025-03-26'', capabilities {tools:{}}, serverInfo name ''praxis-test-mcp'' / version ''0.0.0-test''. Stateful mock also returns header MCP-Session-Id: mock-mcp-session-1 (mcp.rs:452-453, constant at :44). Followed by a notifications/initialized (HTTP 202, no body — mcp.rs:461-463).'
bodyKind: json
body: '{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{}},"serverInfo":{"name":"praxis-test-mcp","version":"0.0.0-test"}}}'
provenance:
- tests/utils/src/agentic/mcp.rs:435-457
- tests/utils/src/agentic/mcp.rs:44
- tests/utils/src/agentic/mcp.rs:452-453
- tests/utils/src/agentic/mcp.rs:461-463
- n: 4
round: pre-IRR (mcp_tool_resolve)
from: gateway
to: mcp-server
kind: request
title: JSON-RPC tools/list
channel: POST http://127.0.0.1:8001/mcp
note: 'Envelope grounded in the transport test fixture (subrequest_transport.rs:1767 shows tools/list with id:2, params {}); the id scalar is rmcp-generated. Same forwarded credentials as initialize (mcp_client tests assert Authorization: Bearer per-user-token + x-mcp-authorized: signed-assertion + x-tenant-id/x-user-id on every connector exchange). Response body is bounded/paginated by the client (mod.rs:359-409 / paginate_tools).'
bodyKind: json
body: '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
provenance:
- apis/src/mcp_client/subrequest_transport.rs:1767
- apis/src/mcp_client/mod.rs:359-409
- apis/src/mcp_client/tests.rs:1280-1290
- apis/src/mcp_client/tests.rs:1424-1430
- n: 5
round: pre-IRR (mcp_tool_resolve)
from: mcp-server
to: gateway
kind: response
title: tools/list result (get_weather)
channel: 200 http://127.0.0.1:8001/mcp
note: 'Envelope/key-order grounded in handle_tools_list (mcp.rs:467-497 emits name, inputSchema, then description). The get_weather fixture (name/inputSchema location schema/description) is grounded in the test McpToolFixture at openai_mcp_streaming.rs:88-98 (and identically openai_agentic_loop.rs:282-290). openai_mcp_tool_resolve writes mcp_tool_map to ResponsesState keyed by (server_label, tool_name); discovery also surfaces an mcp_list_tools output item (issue #1022). mcp_dispatch/tests.rs:2112-2116 grounds the resolver-side inputSchema shape (uses a ''city'' schema there).'
bodyKind: json
body: '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"get_weather","inputSchema":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"],"additionalProperties":false},"description":"Get the weather for a location"}]}}'
provenance:
- tests/utils/src/agentic/mcp.rs:467-497
- tests/integration/tests/suite/examples/openai_mcp_streaming.rs:88-98
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:282-290
- apis/src/openai/responses/mcp_dispatch/tests.rs:2112-2116
- n: 6
round: round 1
from: gateway
to: inference-backend
kind: request
title: Round-1 inference carrying the resolved MCP tools
channel: POST http://127.0.0.1:3001/v1/responses
note: The resolver lowers each MCP tool to an encoded function tool named <server_label>__<tool_name> (encode_function_name -> weather__get_weather, mcp_dispatch/tests.rs:2124-2126). Test asserts first_model_body.tools contains {type:function, name:weather__get_weather} and preserves parallel_tool_calls=true (openai_agentic_loop.rs:365-382). Resolved to inference-backend 127.0.0.1:3001 by the IRR-step load_balancer (cluster_source:bound_upstream).
bodyKind: json
body: '{"model":"gpt-4.1","input":"What is the weather in SF?","parallel_tool_calls":true,"tools":[{"type":"function","name":"weather__get_weather",…}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:365-382
- apis/src/openai/responses/mcp_dispatch/tests.rs:2124-2126
- examples/configs/openai/responses/full-flow-agentic.yaml:624-639
- n: 7
round: round 1
from: inference-backend
to: gateway
kind: response
title: Round-1 response emits the MCP (encoded) tool call
channel: 200 http://127.0.0.1:3001/v1/responses
note: Exact quote of first_response (openai_agentic_loop.rs:250-262, identical at openai_mcp_streaming.rs:58-70). openai_agentic_loop parses this, recognizes weather__get_weather as an MCP call via mcp_tool_map (is_mcp_tool_call, mcp_dispatch/tests.rs:349-353), assigns it to openai_mcp_dispatch, and signals action=loop.
bodyKind: json
body: '{"id":"resp_1","object":"response","status":"completed","output":[{"type":"function_call","id":"fc_1","call_id":"call_abc","name":"weather__get_weather","arguments":"{\"location\":\"SF\"}","status":"completed"}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:250-262
- tests/integration/tests/suite/examples/openai_mcp_streaming.rs:58-70
- apis/src/openai/responses/mcp_dispatch/tests.rs:349-353
- n: 8
round: callout (mcp_dispatch)
from: gateway
to: mcp-server
kind: request
title: JSON-RPC tools/call get_weather
channel: POST http://127.0.0.1:8001/mcp
note: 'Envelope grounded in the transport test fixture (subrequest_transport.rs:1761 shows tools/call with id:1 and params {name, arguments}); the id scalar is rmcp-generated. openai_mcp_dispatch decodes the encoded name back to the original tool_name get_weather (mcp_dispatch/tests.rs:2158-2189) and forwards the model''s arguments (round-trip test asserts call_body.params.arguments.location == ''SF'', openai_agentic_loop.rs:359-360). The dispatch outbound_chain stamps X-MCP-Client: praxis-ai-gateway (asserted openai_agentic_loop.rs:352-358); for a connector target it also carries Bearer <mcp_gateway> + x-mcp-authorized + x-tenant-id/x-user-id, whereas for a request-supplied server_url the round-trip test asserts x-tenant-id must NOT cross (openai_agentic_loop.rs:342-347).'
bodyKind: json
body: '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"SF"}}}'
provenance:
- apis/src/mcp_client/subrequest_transport.rs:1761
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:334-360
- apis/src/openai/responses/mcp_dispatch/tests.rs:2158-2189
- apis/src/mcp_client/mod.rs:716-750
- n: 9
round: callout (mcp_dispatch)
from: mcp-server
to: gateway
kind: response
title: tools/call result
channel: 200 http://127.0.0.1:8001/mcp
note: 'Faithful to write_known_tool_result (mcp.rs:653-674): text = format!(''mock result for {name}''). openai_mcp_dispatch converts the content blocks into (a) an mcp_call output item {type:mcp_call, id:call_abc, server_label:weather, name:get_weather, output:''mock result for get_weather''} in accumulated_output and (b) a function_call_output message {type:function_call_output, call_id:call_abc, output:''mock result for get_weather''} for re-entry — both shapes grounded in build_success_result tests (mcp_dispatch/tests.rs:508-556).'
bodyKind: json
body: '{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"mock result for get_weather"}],"isError":false}}'
provenance:
- tests/utils/src/agentic/mcp.rs:653-674
- apis/src/openai/responses/mcp_dispatch/tests.rs:508-556
- n: 10
round: round 2
from: gateway
to: inference-backend
kind: request
title: Round-2 inference with the tool result appended
channel: POST http://127.0.0.1:3001/v1/responses
note: 'openai_agentic_loop rebuilds the outbound body: input is a single-item array carrying the new function_call_output delta whose output contains ''mock result for get_weather'' (asserted len==1 + contains, openai_agentic_loop.rs:392-415); it resets tool_choice to ''auto'' (424-427) and preserves parallel_tool_calls=true (420-423). conversation.id=conv_native preserved for the native-continuation variant (388-391). call_id call_abc carried from the round-1 function_call.'
bodyKind: json
body: '{"model":"gpt-4.1","conversation":{"id":"conv_native"},"parallel_tool_calls":true,"tool_choice":"auto","input":[{"type":"function_call_output","call_id":"call_abc","output":"mock result for get_weather"}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:384-427
- n: 11
round: round 2
from: inference-backend
to: gateway
kind: response
title: Final inference response (assistant message)
channel: 200 http://127.0.0.1:3001/v1/responses
note: Exact quote of second_response (openai_agentic_loop.rs:263-272, identical at openai_mcp_streaming.rs:71-80). No further tool calls, so openai_agentic_loop signals done and the IRR exits to the client.
bodyKind: json
body: '{"id":"resp_2","object":"response","status":"completed","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"The weather in SF is 72F and sunny."}]}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:263-272
- tests/integration/tests/suite/examples/openai_mcp_streaming.rs:71-80
- n: 12
round: final
from: gateway
to: client
kind: response
title: Composed /v1/responses object returned to client
channel: 200 http://localhost:8080/v1/responses
note: Round-trip test asserts response.id == resp_2 (openai_agentic_loop.rs:319-325); the SSE test asserts the returned body contains 'mock result for get_weather' (openai_mcp_streaming.rs:132-136). The single composed object carries the accumulated hosted-tool items (mcp_list_tools + mcp_call, shape grounded mcp_dispatch/tests.rs:546-556) ahead of the final assistant message; pre-IRR openai_responses_store (yaml:372-379) persists it for GET /v1/responses/{id}.
bodyKind: json
body: '{"id":"resp_2","object":"response","status":"completed","output":[…,{"type":"mcp_call","id":"call_abc","server_label":"weather","name":"get_weather","output":"mock result for get_weather"},{"type":"message","role":"assistant","content":[{"type":"output_text","text":"The weather in SF is 72F and sunny."}]}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:319-325
- tests/integration/tests/suite/examples/openai_mcp_streaming.rs:132-136
- apis/src/openai/responses/mcp_dispatch/tests.rs:546-556
- examples/configs/openai/responses/full-flow-agentic.yaml:372-379
S7:
summary: 'Honest composite: every payload below is quoted/faithful to real repo fixtures/tests, but no single functional test drives full-flow-agentic.yaml''s web_search loop end-to-end with native /v1/responses payloads — the native round-by-round chronology is proven by the sibling agentic-loop.yaml round-trip test (web_search_round_trip_executes_and_re_enters_inference, identical openai_web_search_dispatch Brave dispatcher + same 3001 native backend, config loaded via load_web_search_config which patches the provider base_url to a local mock), while the per-user brave_search slot / x-user-brave-key / fail-closed-401 behavior is proven by web_search_scoped_credentials.rs (against a Chat Completions backend) plus full-flow-agentic.yaml''s own structural guards; hence grounded:false.'
rounds: 2 rounds · web_search (+ fail-closed variant)
steps:
- n: 1
round: round 1
from: client
to: gateway
kind: request
title: Client POST /v1/responses with web_search tool
channel: POST http://127.0.0.1:8080/v1/responses
note: Classified openai_responses create request; the router binds inference-backend (managed native Responses, application_provider vllm), so the direct branches stay inert and it flows through the body filters into the IRR. In full-flow-agentic.yaml the trusted boundary would also carry x-user-brave-key (see step 4 note).
bodyKind: json
body: '{"model":"gpt-4.1","input":"Search for Rust 2025 edition features","tools":[{"type":"web_search_preview"}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:3271-3279
- 'examples/configs/openai/responses/full-flow-agentic.yaml (router route path_prefix /v1/responses + x-praxis-ai-format:openai_responses -> inference-backend)'
- n: 2
round: round 1
from: gateway
to: inference-backend
kind: request
title: IRR inference round 1 — proxy rebuilds outbound Responses body
channel: POST http://127.0.0.1:3001/v1/responses
note: openai_responses_proxy rebuilds the outbound body from ResponsesState and routes to inference-backend (127.0.0.1:3001). The test asserts the backend received exactly 2 requests (this is req[0]); round-1 body is not asserted verbatim, so this is the rebuilt body faithful to the client input.
bodyKind: json
body: '{"model":"gpt-4.1","input":"Search for Rust 2025 edition features","tools":[{"type":"web_search_preview"}], …}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:3302-3307
- examples/configs/openai/responses/full-flow-agentic.yaml:624-645
- n: 3
round: round 1
from: inference-backend
to: gateway
kind: response
title: Round-1 inference emits a web_search_call item
channel: 200 POST http://127.0.0.1:3001/v1/responses
note: openai_agentic_loop parses this, records the web_search_call assignment for the openai_web_search_dispatch dispatcher, and publishes action=loop.
bodyKind: json
body: '{"id":"resp_ws_1","object":"response","status":"completed","output":[{"type":"web_search_call","id":"ws_1","status":"completed","action":{"type":"search","query":"Rust 2025 edition"}}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:3235-3245
- n: 4
round: callout
from: gateway
to: brave
kind: request
title: openai_web_search_dispatch Brave callout (deferred per-user x-subscription-token)
channel: GET https://api.search.brave.com/res/v1/web/search?q=Rust%202025%20edition&count=5
note: 'URL modeled by build_brave_request: {base}/res/v1/web/search?q={percent-encoded query}&count={count}; count=5 from default_context_size Medium (result_count). Only Accept: application/json rides in-chain; the API key is staged as a deferred x-subscription-token injected only at the resolved authority. With user_credential: brave_search the per-user secret from x-user-brave-key is used, not the shared api_key — the slot wiring (brave_search slot, source_header x-user-brave-key, web_search user_credential brave_search) is proven structurally by full_flow_agentic.rs:1409-1457, and the runtime behavior by scoped-credentials: callout carries ''x-subscription-token: alice-brave-secret'', shared ''test-key'' absent, ''x-user-brave-key'' stripped. In the round-trip functional test the provider base_url is patched to a local mock via load_web_search_config (openai_agentic_loop.rs:5506-5509).'
bodyKind: none
provenance:
- apis/src/web_search/provider.rs:585-607
- apis/src/web_search/provider.rs:189-195
- apis/src/web_search/config.rs:90-96
- examples/configs/openai/responses/full-flow-agentic.yaml:551-565
- tests/integration/tests/suite/examples/web_search_scoped_credentials.rs:139-168
- tests/integration/tests/suite/examples/full_flow_agentic.rs:1409-1457
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:5506-5509
- n: 5
round: callout
from: brave
to: gateway
kind: response
title: Brave results payload as the code models it
channel: 200 GET /res/v1/web/search
note: parse_brave_results reads web.results[].{title,url,description→snippet}. spawn_search_mock serves exactly this body. Scoped-credentials uses the same shape ({title:'Praxis Proxy releases', url:'https://github.com/praxis-proxy/praxis/releases', description:'Current Praxis Proxy releases.'}).
bodyKind: json
body: '{"web":{"results":[{"title":"Rust 2025 Edition","url":"https://blog.rust-lang.org/2025","description":"The Rust 2025 edition is here."}]}}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:4739-4748
- apis/src/web_search/provider.rs:709-730
- tests/integration/tests/suite/examples/web_search_scoped_credentials.rs:116-122
- n: 6
round: round 2
from: gateway
to: inference-backend
kind: request
title: IRR re-entry — results appended as a synthetic function_call/function_call_output bridge
channel: POST http://127.0.0.1:3001/v1/responses
note: 'Issue #808: a hosted web_search_call is NOT a valid Responses input item, so the continuation bridges results as function_call + function_call_output (output = format_search_results). Test asserts input has NO web_search_call, has a function_call name==web_search, and a function_call_output whose output contains ''blog.rust-lang.org''.'
bodyKind: json
body: '{…, "input":[ …, {"type":"function_call","call_id":"<bridge>","name":"web_search","arguments":"{\"query\":\"Rust 2025 edition\"}","status":"completed"}, {"type":"function_call_output","call_id":"<bridge>","output":"[1] Rust 2025 Edition\nhttps://blog.rust-lang.org/2025\nThe Rust 2025 edition is here."} ]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:3309-3341
- apis/src/openai/responses/web_search_dispatch/mod.rs:876-905
- apis/src/web_search/mod.rs:66-82
- n: 7
round: round 2
from: inference-backend
to: gateway
kind: response
title: Round-2 inference returns the final assistant message
channel: 200 POST http://127.0.0.1:3001/v1/responses
note: openai_agentic_loop parses this terminal message and publishes action=done, exiting the IRR to the client.
bodyKind: json
body: '{"id":"resp_ws_2","object":"response","status":"completed","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Rust 2025 brings great features."}]}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:3246-3255
- n: 8
round: final
from: gateway
to: client
kind: response
title: Final /v1/responses object (web_search_call reconciled in place + message)
channel: 200 POST http://127.0.0.1:8080/v1/responses
note: Final id is resp_ws_2; the successful search updated the model's placeholder in place, so output carries exactly one completed web_search_call (id=ws_1, status=completed) merged with the round-2 message. Test asserts response['id']=='resp_ws_2' and exactly one web_search_call with id ws_1 / status completed.
bodyKind: json
body: '{"id":"resp_ws_2", …, "output":[{"type":"web_search_call","id":"ws_1","status":"completed","action":{"type":"search","query":"Rust 2025 edition"}}, {"type":"message","role":"assistant","content":[{"type":"output_text","text":"Rust 2025 brings great features."}]}]}'
provenance:
- tests/integration/tests/suite/examples/openai_agentic_loop.rs:3283-3300
- n: 9
round: fail-closed variant
from: gateway
to: client
kind: response
title: Missing x-user-brave-key → 401 before any provider callout or inference round
channel: 401 POST http://127.0.0.1:8080/v1/responses
note: 'Alternate path (not part of the success chronology). When user_credential: brave_search is configured and the per-user slot is empty (no x-user-brave-key), the round-0 credential preflight fails closed with a JSON 401 carrying missing_callout_context BEFORE any provider callout AND before any inference round (search.requests() empty, model.requests() empty). Also holds for stream:true (252-331). Full body envelope is not asserted verbatim — only that it contains ''missing_callout_context''.'
bodyKind: json
body: '{ … "code":"missing_callout_context" … }'
provenance:
- tests/integration/tests/suite/examples/web_search_scoped_credentials.rs:225-250
- tests/integration/tests/suite/examples/web_search_scoped_credentials.rs:252-331
- examples/configs/openai/responses/full-flow-agentic.yaml:252-257
groups:
trace:
c: var(--c-trace)
label: trace
identity:
c: var(--c-identity)
label: identity
conversations:
c: var(--c-conversations)
label: conversations
classification:
c: var(--c-classification)
label: classification
context:
c: var(--c-context)
label: context
store:
c: var(--c-store)
label: store / persist
routing:
c: var(--c-routing)
label: routing
irr:
c: var(--c-irr)
label: IRR (loop owner)
irr-step:
c: var(--c-dispatch)
label: IRR step
backend:
c: var(--c-backend)
label: backend
state_label:
active: active
passthrough: passthrough
inert: inert
skipped: skipped