Output Controls
Human Oversight
human-oversight holds a model response for a person to review. The caller receives a queued escalation instead of the answer, and a reviewer records a decision in Inbox.
The control escalates every response that reaches it. Approval in the Inbox does not resume or replay the original request. Set up the review workflow before production traffic uses this control. With gateway 0.1.26 or later, a streamed request also escalates. The escalation occurs at the first output text, and the client gets no output text.
Configuration
Because the control escalates everything it receives, decide which requests reach it with routes. A when condition on the chain entry has no effect on this control.
This example sends a request for review only when it has the header x-review-required: true. Apply it to a test gateway with the config-first workflow.
pack:
name: manual-review-route
version: 1.0.0
enabled: true
policies:
chain:
- audit-logger
- human-oversight
policy:
audit-logger: {}
human-oversight:
action: escalate
routes:
- name: review-required
path: /v1
headers:
x-review-required: "true"
priority: 10
chain:
- audit-logger
- human-oversight
- name: no-review
path: /v1
priority: 0
chain:
- audit-loggerThe global chain must list human-oversight, or lint fails. The catch-all no-review route then leaves it out for requests without the header.
The caller controls this header, which is fine for a demonstration. For review that must always happen, use a route condition that the caller cannot remove, such as a path or a consumer group.
Send a request for review
Use the base URL and application token from the quickstart, and a model that your gateway serves:
curl --fail-with-body "$VERDICTAN_BASE_URL/chat/completions" \
-H "Authorization: Bearer $VERDICTAN_CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-H "x-review-required: true" \
--data '{"model":"gpt-4o-mini","stream":false,"messages":[{"role":"user","content":"Draft a short greeting."}]}'The response is HTTP 200 with choices[0].message.content: null and verdictan.escalation.status: queued. Open Inbox and find the escalation with the same ID.
Send the request again without the header. This time, you get the model answer.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
action | string | Set it to escalate to turn the control on. Without it, the control does nothing. The loader rejects block. |
The loader also rejects the removed fields require_human_for, confidence_threshold, default_assignee, and timeout_seconds. Use only action. The policy schema of gateway 0.1.26 or later accepts only action: escalate and has no default for it.
How it works
- The gateway receives the provider response and runs the output controls of the active chain.
- If an earlier output control returned
blockorescalate, the stage stops before this control. - With
action: escalate, the control sets the verdict toescalatewith reason codeoversight.required. - The gateway emits a decision event with
policy_kind: human-oversight, verdictESCALATE, andoversight.required. - If the requested model has escalation routing, the gateway adds its
team_idoruser_idto the event. - The caller gets HTTP
200withchoices[0].message.content: null,finish_reason: content_filter, and averdictan.escalationobject with the escalation ID and statusqueued. With gateway 0.1.26 or later, a buffered escalation of a Responses request gets a response object withstatus: incomplete, thecontent_filterreason, and no output. A Messages request gets a message with no content and therefusalstop reason.
Phase and verdicts
| Item | Value |
|---|---|
| Phase | output |
| Enforced path | Chat Completions and Responses with stream: false. With gateway 0.1.26 or later, also streamed Chat Completions, Responses, and Messages. |
| Verdicts | escalate |
| Reason codes | oversight.required |
The control never returns allow. Without action: escalate, it adds no result.
Limits and failure behavior
- The control has no threshold, category, or pattern. It escalates every response that reaches it.
- Only the active chain decides whether it runs. Routes, consumer groups, and team or gateway targeting change the active chain. A
whencondition or astageon the chain entry does not. - With
stream: trueand gateway 0.1.26 or later, the control escalates at the first output text. The stream stops with the error event of the route, withoversight.required, the verdictESCALATE, and averdictan.escalationobject with statusqueued. See Output escalations in a stream. - A streamed escalation occurs at the first output text. Thus a later output control in the chain does not examine that output. With gateway 0.1.26 or later, the output result of this control has
chain_stopped: true, and itsnot_evaluatedlist names the later output controls. - On escalation, the caller does not receive the model text. Only the decision event and the Inbox have it.
- Reviewer assignment, categories, and timeouts are not part of this control. Configure them with escalation routing and your review workflow.
- The control stores no evidence. Add
audit-loggerif the decision must show audit logging.
Next steps
- Routes and Consumer Groups: decide which requests reach the control.
- Escalation Routing Configuration: send escalations to a team or user.
- Resolve an Escalation: claim and resolve an escalation in the Inbox.