RATER_ACTIONS
constRATER_ACTIONS: readonly ["approve","escalate","halt","reject"]
Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/shell/raterVocabulary.ts:104
The actions the approvals gate can resolve to for a single gated call, BEFORE the human prompt. import(‘./rater.js’).mapVerdictToAction is the whole mapping from an outcome and a rung onto one of these, and its docblock carries the table.
approve— approve ONCE; do not touch the human or the allow-list.escalate— fall through to the human approval callback, carrying the verdict when one exists. Where there is no human, §6.2 turns this into an immediate non-zero exit — that translation belongs to the runner, not to the mapping.halt— end the agent loop (§4.2). Reserved forattack. It is not a rejection the model can respond to and offers it no moves; no rung exceptbypasscan turn it into anything else.reject— (§5): hand the rater’s explanation back to the agent as the refused call’s tool result (§7), opening a round of the negotiation. Returned fordestructiveatautoand nowhere else.
reject says the outcome is negotiable, NOT that the negotiation may continue. The mapping is
keyed on the rung and knows nothing about how many rounds have been spent; §5.3’s consecutive cap
and the reachability bound live with the state they count — ShellNegotiationState — and it is
whoever drives that state (the session runner, and the gth eval rater target, which drives the
same class) that turns a reject into an escalation once either bound is spent. Putting the
counters into the mapping would make a pure rung-keyed table depend on session history, and would
leave every caller that legitimately rates ONE command with an action that varies with a
negotiation it is not part of.
There is deliberately no abstain arm. A command whose target the gate cannot statically
resolve is rated like any other, under §6.1’s rule — deterministic checks fire only where we are
confident something is a threat; where we cannot tell, the model decides. A parser reporting that
it could not resolve a string is not a detection, so it earns no action of its own; what it earns
is a neutral note in the rating prompt
(import(‘./abstention.js’).buildParserPreflightNote) and a real rating. That is also what
keeps the ceiling reachable: an action of its own would make catastrophic and attack
unreachable for every composed, substituting or redirecting command, so pwd && rm -rf ~ could
only ever be floored at destructive.
There is deliberately no refuse arm for catastrophic (§4.2). The deterministic members of
that class — fork bomb, mkfs, rm -rf /, dd to a block device — are already refused
unappealably by the §8 hardline floor under every rung including bypass, so a refusing
catastrophic would add nothing for the commands that motivate the idea. What it would newly
refuse is the remainder the floor cannot reach, every member of which has routine legitimate use
(a staging database, an ephemeral terraform destroy, a preview namespace). An unmeasured
classifier belongs behind a human who can correct it; a refusal has no correction path.
This list is exhaustive by construction, and downstream must treat it that way. Anything that enumerates actions — a report axis, an eval suite’s declared enum, a rendering table — has to derive from here. The failure mode of a restatement is silent rather than loud: an action the enumerator does not know is not an error it can raise, it is a row it quietly files under “unrecognized”, and the thing that was being measured simply stops being measured.