CClawMetryDocs

Guard & governance

Policies and escalation#

A policy turns a detector incident into at most one enforcement decision per session per tick. The evaluator is pure: it does no I/O, opens no store and sends no signals. It takes incidents, policies and facts, and returns decisions. The daemon does the reading, the dispatching and the auditing.

That split means the whole matching surface is testable without a daemon, a database or a live agent.

A policy#

json
{
  "policy_id":      "stop-codex-loops",
  "enabled":        true,
  "scope_runtime":  "codex",
  "scope_agent_id": "",
  "trigger_kind":   "stuck_loop",
  "min_severity":   "warning",
  "min_repeat":     2,
  "min_duration_s": 120,
  "min_spend_usd":  0,
  "min_spend_at_risk_usd": 1.50,
  "action":         "pause"
}
FieldMeaning
enabledOff policies are not evaluated
scope_runtimeEmpty string means every runtime
scope_agent_idEmpty string means every agent
trigger_kindEmpty string means any detector kind
min_severityinfo or warning
min_repeatThe incident must have been seen at least this many times
min_duration_sThe session must have been bad for at least this long
min_spend_usdThe session's total cost must be at least this
min_spend_at_risk_usdThe flagged stretch must be worth at least this. 0 or missing means unused.
actionmonitor, alert, pause, stop or kill
stepsOptional escalation ladder

min_spend_at_risk_usd is usually the right filter. It is the difference between "this session has cost $80 in total" and "the part that is currently going nowhere is worth $6".

The five actions#

ActionEffect
monitorRecord what it would have done. Changes nothing. The default for a new policy.
alertFire an alert. Does not touch the process.
pauseSuspend the process. Resumable.
stopAsk it to stop cleanly.
killTerminate the process tree.

Escalation ladders#

A single action fired once is not how anyone actually responds to a stuck agent. The real shape is pause it, tell me, give it five minutes, then kill it if it is still stuck.

json
"steps": [
  {"action": "pause", "after_secs": 0},
  {"action": "alert", "after_secs": 0},
  {"action": "kill",  "after_secs": 300}
]

The semantics are chosen so a ladder can never act faster than a plain policy:

  • Step 0 fires when the policy first matches. Its after_secs is ignored —

use min_duration_s for a delay before the first action.

  • Step n becomes due after_secs after step n-1 actually fired, not

after the incident started. A ladder measures the time the agent was given to recover.

  • A due step only fires if the session is still matching this tick. That is

what makes "kill if still stuck" mean still stuck. If the detector stops reporting, the ladder simply stops.

  • Every step passes the same three locks. A kill step on a node with

enforcement off is a recorded dry run, exactly like a kill policy.

Ladders are capped at a maximum number of steps, so a policy cannot become an open-ended scheduler.

The three locks#

Every autonomous action requires all three:

  1. The policy's own action must be pause, stop or kill. A new policy

defaults to monitor.

  1. CLAWMETRY_POLICY_ENFORCE=1 on the node. Default 0. One environment

variable disables every policy on that machine.

  1. An entitlement check that fails closed. If the check cannot be answered,

the answer is no.

bash
# The node-wide switch. Nothing autonomous happens without this.
export CLAWMETRY_POLICY_ENFORCE=1

# Skip the evaluation pass entirely.
export CLAWMETRY_GUARD_POLICIES=0

The durable latch#

Each rung fires at most once per session, latched on (session_id, policy_id, step_index) as a primary key in the store.

That is durable across a daemon restart. A daemon that dies mid-ladder resumes at the right rung instead of replaying it — which, for a ladder ending in kill, is the difference between a considered escalation and a restart loop that kills every session it sees.

Managing policies#

bash
# list
curl -s localhost:8900/api/guard/policies | jq

# create
curl -s localhost:8900/api/guard/policies -X POST \
  -H 'content-type: application/json' \
  -d '{"scope_runtime":"codex","trigger_kind":"stuck_loop",
       "min_severity":"warning","min_repeat":2,
       "min_spend_at_risk_usd":1.5,"action":"monitor"}'

# delete
curl -s -X DELETE localhost:8900/api/guard/policies/<policy_id>

# what has fired, including dry runs
curl -s localhost:8900/api/guard/actions | jq '.actions[:20]'

Rolling one out#

The order that avoids surprises:

  1. Create it as monitor. That is the default; do not change it yet.
  2. Leave enforcement off. Also the default.
  3. Read the decision log for a week. GET /api/guard/actions shows exactly

what would have happened.

  1. Check the false positives. Would you have been happy for it to pause

those sessions? If not, tune the policy, not the detector.

  1. Change the action to pause. Still nothing happens — enforcement is off.

Verify the log now shows the intended action.

  1. Turn on CLAWMETRY_POLICY_ENFORCE=1.
  2. Only then consider a ladder ending in kill.

Skipping to step 6 is how you find out that a policy you thought matched three sessions a week actually matches thirty.

Naming#

/api/guard/policies is deliberately distinct from /api/tool-policy. This one is mid-run enforcement; the other is the pre-tool permission surface. Different axis, different table, no shared state. → Tool permissions

Cookie preferences