Agentic Covenants

Detect (DE) · Approval gating

Approval gating at the client side layer

deterministic · Outside the model's reasoning

If this concern is breached, how do we know?

What this cell does

Approval-timing analysis surfaces alert-fatigue (response under 2s across more than 50 approvals); typed-confirmation mismatch events; out-of-band channel decisions joined to session.

Artifacts (3)

approval-timing-emit.shview on GitHub
#!/usr/bin/env bash
# ABOUTME: Snippet that wraps the typed-confirmation read with timing measurement and emits a structured timing event.
# ABOUTME: Source from pre_tool_use_tiered.sh in place of the bare `read -r confirmation < /dev/tty`.

emit_approval_timing() {
  # Args:
  #   $1 = tier (numeric: 2, 3, 4)
  #   $2 = expected confirmation (the literal command, for typed-verbatim tiers)
  # Reads response from /dev/tty into the global confirmation variable.
  local tier="$1"
  local expected="$2"

  local start_ns end_ns response_ms matched
  start_ns="$(date +%s%N)"
  IFS= read -r confirmation < /dev/tty
  end_ns="$(date +%s%N)"
  response_ms=$(( (end_ns - start_ns) / 1000000 ))

  if [[ "$confirmation" == "$expected" ]]; then
    matched=true
  else
    matched=false
  fi

  local event
  event="$(jq -n \
    --arg session "${SESSION_ID:-default}" \
    --argjson tier "$tier" \
    --argjson response_ms "$response_ms" \
    --argjson matched "$matched" \
    --arg ts "$(date -Iseconds)" \
    '{event: "approval_timing", session: $session, tier: $tier, response_ms: $response_ms, matched: $matched, ts: $ts}')"
  logger -t agent-sentinel -p user.info "$event"
}

# Usage in pre_tool_use_tiered.sh:
#
#   . /etc/agents/hooks/approval-timing-emit.sh
#
#   echo "TIER-3 ACTION: $TOOL_INPUT" >&2
#   echo "Type the command verbatim to confirm:" >&2
#   emit_approval_timing 3 "$TOOL_INPUT"
#   if [[ "$confirmation" != "$TOOL_INPUT" ]]; then
#     ...
#   fi
oob-decision-log.shview on GitHub
#!/usr/bin/env bash
# ABOUTME: Logs out-of-band approval decisions with the original request ID and session, so SIEM can reconstruct the chain.
# ABOUTME: Called by the OOB approval workflow (Slack approver action, FIDO2 prompt result, etc.) when a decision is made.

set -euo pipefail

REQ_ID="${1:?Missing request ID}"
DECISION="${2:?Missing decision (approved|denied|timeout)}"
APPROVER="${3:-unknown}"
SESSION_ID="${4:-unknown}"

case "$DECISION" in
  approved|denied|timeout) ;;
  *)
    echo "oob-decision-log: invalid decision '$DECISION' (must be approved|denied|timeout)" >&2
    exit 64
    ;;
esac

event="$(jq -n \
  --arg req "$REQ_ID" \
  --arg decision "$DECISION" \
  --arg approver "$APPROVER" \
  --arg session "$SESSION_ID" \
  --arg ts "$(date -Iseconds)" \
  '{event: "oob_approval", req_id: $req, decision: $decision, approver: $approver, session: $session, ts: $ts}')"

logger -t agent-sentinel -p user.info "$event"
sigma-approval-fatigue.yamlview on GitHub
# ABOUTME: Sigma rule firing when a user's mean tier-≥2 response time is <2s across >50 approvals.
# ABOUTME: Calibrated against AHRQ PSNet alarm-fatigue research and Anthropic Auto Mode telemetry. Tune for your team.
title: Operator approval fatigue pattern
id: 5a7b8c9d-0e1f-2a3b-4c5d-6e7f8a9b0c1d
status: experimental
description: >
  Detects an operator whose typed-confirmation response time has dropped to
  reflexive levels: mean response under 2 seconds across 50 or more
  tier-≥2 approvals. Reflexive approval is the failure mode tiered approval
  exists to fight; when this rule fires, the tiering is no longer adding
  meaningful friction to the approval flow.
references:
  - https://github.com/peopleforrester/agentic-covenants/blob/main/SENTINELS_MATRIX.md
  - https://psnet.ahrq.gov/perspective/alert-fatigue
  - Anthropic Auto Mode (March 2026 paper)
author: agentic-covenants
date: 2026/05/08
logsource:
  category: agent_hook
detection:
  selection:
    event: approval_timing
    tier|gte: 2
  timeframe: 1d
  condition: |
    selection
    | aggregate avg(response_ms) as avg_ms, count() as n by user
    | where avg_ms < 2000 and n > 50
falsepositives:
  - Operator running a batch script that pre-types confirmations (legitimate but indicates a scope mismatch — those operations probably don't need tiered approval at all).
  - Single very-long approval inflating one user's average without other indicators (re-check; the rule uses mean, not median).
level: medium
tags:
  - agent
  - approval_gating
  - sentinels
  - human_factors

Cell notes

Sentinels, Approval gating / Client-side

Control. Approval-timing logger captures local think-time on every confirmation. OOB approval channel logger captures decisions joined to session. SIEM rule fires on response under 2s across more than 50 approvals (the alert-fatigue pattern, calibrated against AHRQ PSNet research and Anthropic Auto Mode telemetry).

Strength. Deterministic when timings are measured locally and shipped reliably. Failure modes: timing measurement that includes network latency to a remote approval service (measure local think-time only); threshold too aggressive (anything under 5 seconds is "fatigue") yielding false positives; threshold too lenient (anything over 100ms is "real consideration") so the rule never fires; OOB channel log not joined to the original request session, cannot reconstruct the chain.

Tooling

Files in this directory

  • - approval-timing-emit.sh, appendable snippet that wraps the typed-confirmation read with timing measurement (start/end nanoseconds, response_ms, matched).
  • - oob-decision-log.sh, script that the out-of-band approval workflow calls when an approver decides; emits a structured event keyed to the original request ID and session.
  • - sigma-approval-fatigue.yaml, SIEM rule firing when a user's mean tier-≥2 response time is under 2s across more than 50 approvals.

Verification


# 1. Approval timing captured
# Run a tier-3 command, type the confirmation, check log
journalctl -t agent-sentinel --since "5 minutes ago" | grep approval_timing
# expected: response_ms field present

# 2. Fatigue pattern alert fires (test environment)
# Generate 60 approvals at <1s each
for i in {1..60}; do
  echo '{"session_id":"fatigue-test","tool_name":"Bash","tool_input":{"command":"echo test"}}' \
    | /etc/agents/hooks/pre_tool_use_tiered.sh
done
# Query SIEM for the fatigue rule output

Common mistakes

  • - Timing measurement that includes network latency to a remote approval service. Measure local think-time only.
  • - Threshold too aggressive (anything under 5s): false positives on routine approvals.
  • - Threshold too lenient (anything over 100ms is "real consideration"): never fires.
  • - OOB channel log not joined to the original session. Cannot reconstruct full chain.

Citation

NIST CSF 2.0 DE.CM-03, DE.AE-02. AHRQ PSNet alarm-fatigue research. Anthropic Auto Mode (March 2026).

Primary failure modes

Documented, not hypothetical. A control whose bypass is undocumented is worse than no control, because somebody trusted it.

  • timing measurement includes network latency
  • threshold too aggressive (5s false-positives) or too lenient (100ms never fires)

Crosswalk

NIST CSF 2 0DE.CM-03, DE.AE-02
OTHERAHRQ PSNet alarm fatigue, Anthropic Auto Mode (March 2026)