Skip to content

RATER_ACTIONS

const RATER_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.
  • haltend the agent loop (§4.2). Reserved for attack. It is not a rejection the model can respond to and offers it no moves; no rung except bypass can 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 for destructive at auto and 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.