- - #45888
Conversation
|
✅ Verified — Approval callback wiring in Responses API path Reviewed the diff for
The implementation correctly separates the streaming and non-streaming approval paths. No issues found. |
|
Nice — registering 1. The queued 2. There's no way to resolve the approval. Once the callback is registered the guard blocks the agent thread in 3. Minor — the non-streaming Happy to help if any of this is useful. |
|
Addressed the two functional gaps from the review on the current branch head.
Validation rerun:
|
48a3218 to
a5af237
Compare
eb1c16c to
ce372b1
Compare
|
Thanks for completing the missing Responses approval transport. The current Problems
Suggested changes
Automated hermes-sweeper review. |
ce372b1 to
942e9e3
Compare
|
Thanks for the detailed review. Addressed both gaps.
Additionally: |
Summary
The Responses API path was only half-wired for guarded approvals:
_write_sse_responses()silently dropped the queued__approval_request__tuple, no resolution endpoint existed, and the non-streaming path registered a no-op approval callback that could block indefinitely.This update completes the Responses approval transport with proper scope isolation. Streaming responses emit
response.approval.requestedSSE events keyed by the generatedresponse_id. The approval callback, event-loop queue, resolution endpoint,resolve_all, profile ownership, and worker cleanup all useresponse_idas the sole key, so concurrent requests sharing a session or memory key cannot overwrite each other's notifier or resolve each other's pending actions. The non-streaming path remains callback-free.Changes
gateway/platforms/api_server.py: scoped all Responses approval state to the generatedresponse_id; added event-loop queue handoff withcall_soon_threadsafe; redactedevent["command"]through_redact_approval_commandbefore SSE delivery; added_response_approval_sessionsprofile ownership map; addedPOST /v1/responses/{response_id}/approvalwith profile-scoped authentication and sibling-profile rejection; passedapproval_session_key=response_idseparately from the conversation key; registered approval state through the shared callback seam and cleared it in worker completionfinally; removed the non-streaming no-op callback.tools/approval.py: added_register_gateway_approvalto check callback identity atomically before queue insertion so a captured worker that loses its callback during disconnect teardown fails closed without entering the timeout loop; extendedclear_sessionto evict the session key from_denial_tally.tests/gateway/test_api_server.py: added one-worker approval progression, same-session concurrency andresolve_allisolation, profile rejection, redaction contract, late-callback guard, lifecycle cleanup, and Runs preservation coverage.tests/tools/test_approval_interrupt.py: added callback teardown race regression.tests/tools/test_denial_circuit_breaker.py: added named-key denial-tally eviction regression.Validation
pytest tests/gateway/test_api_server.py tests/gateway/test_api_server_runs.py tests/gateway/test_approval_prompt_redaction.py tests/tools/test_approval_interrupt.py tests/tools/test_denial_circuit_breaker.py -v --timeout=0— 137 passedNot in scope
This PR does not try to invent a second approval mechanism for normal chat replies in OpenAI-compatible clients. It only completes the existing gateway approval contract for the Responses API: structured approval events plus a matching resolution endpoint keyed by
response_id.Upstream
Closes #45505.
Reported by @arnoulddw.
Thanks to @AIalliAI for the Responses approval wiring analysis.