1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Approval-reviewer policy.
#
# The reviewer decides whether a refused action should be granted. Everything
# that a person would want to change without recompiling lives here: which model
# reviews, how hard it thinks, when the circuit breaker trips, what it may never
# grant, and which categories start from a presumption of denial.
#
# Read once at the resolver seam and handed to the reviewer session as typed
# input. Nothing here is read from the environment, so a run's receipt can say
# which policy it enforced.
#
# The reviewer session itself -- prompt assembly, verdict schema, decision
# record -- is Harn (`std/agent/approval_review`). Rust holds only the seam.
= 1
[]
# Cheap by default: the reviewer runs on a would-be denial, not on every tool
# call, but a busy run can still produce dozens of verdicts. Evals override this
# with a stronger model; see `docs/how-to/approval-review.md`.
= "claude-haiku-4-5-20251001"
= "low"
# Fail closed. A reviewer that times out has not approved anything, and treating
# silence as assent would make the timeout the widest grant in the system.
= 30000
= "deny"
[]
# A reviewer that has denied three times in a row is not exercising judgment
# about three different requests -- it is stuck and the agent is looping against
# it. Consecutive, not total: a run that legitimately denies several unrelated
# requests across a long session is working correctly.
= 3
# Codex's window, and the reason for it: without a total cap an auto-denying
# reviewer is a livelock rather than a failure.
= 10
# Cell-level regression signal. Flags the cell; never aborts the batch.
= 0.5
# Actions no reviewer may grant, whatever the goal claims to justify.
#
# This list is a floor, not a heuristic: it is checked before the reviewer is
# consulted, so a persuasive session goal never reaches a model that could be
# talked out of it. `universal_catastrophic_reason` in the process layer is the
# independent enforcement of the same rule for commands; this list is what the
# resolver refuses to even ask about.
[]
= [
"credential_exfiltration",
"destructive_root_delete",
"disable_security_control",
"history_rewrite_on_shared_ref",
]
# Categories that start from a presumption of denial. The reviewer may still
# approve one when the user's stated goal plainly requires it -- reading
# `~/.ssh/config` to fix a remote is a real task -- but the burden is on the
# request, and an approval here must name the part of the goal that grants it.
[]
= [
"secret_material_read",
"outbound_network_to_unstated_host",
"package_install_from_unstated_source",
"force_push",
"recursive_delete_outside_workspace",
]
# What the reviewer is told about the trust of each input. Tool output and file
# contents are untrusted: they are where an injection would arrive, and they can
# never widen what the user's goal authorized.
[]
= ["user_goal", "session_task", "agent_instructions"]
= ["tool_output", "file_contents", "command_stderr", "network_response"]
[]
# The reviewer returns a decomposed verdict rather than a bare allow/deny, so a
# rollup can separate "the action was low risk" from "the goal authorized it".
# Codex's risk x authorization split; the outcome is derived, not asserted.
= ["low", "medium", "high", "critical"]
= ["unknown", "low", "medium", "high"]
# The derivation, stated as data so a policy change does not need a code change.
# Read as: at this risk, approve only if authorization is at least this.
[]
= "unknown"
= "low"
= "medium"
= "never"