Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

Module 04: Autonomy

Progressive trust levels for agent independence.

The Concept

Instead of binary "ask for everything" or "do whatever you want", autonomy levels give Claude graduated independence:

A0 (Manual)     --> Confirm everything
A1 (Guided)     --> Skip confirmation for previously-approved categories
A2 (Supervised) --> Skip confirmation for approved file patterns
A3 (Trusted)    --> Skip confirmation for all medium-risk if validation passes
A4 (Autonomous) --> Even some high-risk, based on successful history

Every project starts at A0. Claude earns higher levels through successful actions.

Do You Need This?

Probably not. Most projects work fine with the default behavior:

  • Claude asks before risky operations
  • You approve or deny
  • No explicit level tracking needed

Autonomy levels are useful when:

  • You run long-running sessions where constant approvals slow things down
  • You have well-understood codebases with clear safe/unsafe zones
  • You want to formalize the "I trust Claude with X but not Y" pattern

Level Details

A0: Manual (Default)

Everything requires confirmation. Safe for first-time use on any project.

A1: Guided

After you approve 5 actions of the same type (e.g., editing Python files), Claude can repeat that category without asking.

{
  "level": 1,
  "approved_categories": ["python", "typescript"],
  "success_count": 7
}

A2: Supervised

You define file patterns where Claude can edit freely:

{
  "level": 2,
  "grants": [
    { "pattern": "src/**/*.py", "type": "glob", "reason": "All Python source files" },
    { "pattern": "tests/**", "type": "glob", "reason": "Test directory" }
  ]
}

Claude can edit src/routes/users.py without asking, but still needs approval for docker-compose.yml.

A3: Trusted

All medium-risk actions proceed if validation passes (syntax check, test run). High-risk still requires approval.

A4: Autonomous

Even some high-risk commands auto-approve if the exact same command has been approved before:

{
  "level": 4,
  "high_risk_history": [
    "git push origin feature-branch",
    "docker restart myapp"
  ]
}

Note: This uses exact string matching, not pattern matching. docker restart myapp in history does NOT approve docker restart myapp-v2. This is intentional -- it's safer to require re-approval for any variation.

Getting to A4 requires explicit user action -- it never auto-escalates to this level.

Escalation Rules

Action succeeds --> success_count += 1
5 successes (same category, from A0) --> Suggest A1
10 successes (from A1) --> Suggest A2
25 successes (from A2) --> Suggest A3
Explicit user command --> A4

Action fails/error --> Drop 1 level
2+ errors in 10 minutes --> Drop to A0

The State File

Autonomy state is stored per-project in .autonomy-state:

{
  "level": 2,
  "grants": [
    {
      "pattern": "src/**/*.py",
      "type": "glob",
      "granted_at": "2026-01-15T10:00:00Z",
      "reason": "Python source files approved after 10 successful edits"
    }
  ],
  "approved_categories": ["python", "config"],
  "high_risk_history": [],
  "success_count": 15,
  "error_count": 0,
  "last_escalation": "2026-01-15T09:30:00Z"
}

Implementing with Hooks

If you adopt this module, you need a hook that reads .autonomy-state and decides whether to prompt:

#!/bin/bash
# hooks/check-autonomy
# PreToolUse hook that checks autonomy level

set -uo pipefail

# Fail closed if jq is missing
if ! command -v jq >/dev/null 2>&1; then
    echo "ERROR: jq is required for autonomy checks." >&2
    exit 2
fi

INPUT=$(cat)
if [ -z "$INPUT" ]; then
    echo "ERROR: No input received." >&2
    exit 2
fi

TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty')
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty')

# Load autonomy state
STATE_FILE=".autonomy-state"
LEVEL=0
if [ -f "$STATE_FILE" ] && [ ! -L "$STATE_FILE" ]; then
    LEVEL=$(jq -r '.level // 0' "$STATE_FILE" 2>/dev/null || echo "0")
fi

# Determine risk (reuse from guard-risk or inline)
RISK="low"
if [ "$TOOL" = "Write" ] || [ "$TOOL" = "Edit" ]; then
    RISK="medium"
fi
CMD_UPPER="${CMD^^}"
if [[ "$CMD" =~ (^|[[:space:]])rm[[:space:]].*-[rR] ]] \
|| [[ "$CMD_UPPER" =~ DROP[[:space:]]+(DATABASE|TABLE) ]] \
|| [[ "$CMD" =~ git[[:space:]]+push[[:space:]]+--force ]] \
|| [[ "$CMD" =~ git[[:space:]]+reset[[:space:]]+--hard ]]; then
    RISK="high"
fi

# Apply autonomy rules
case "$RISK" in
    "low")
        exit 0  # Always allow
        ;;
    "medium")
        if [ "$LEVEL" -ge 3 ]; then
            exit 0  # A3+ auto-approves medium
        elif [ "$LEVEL" -ge 2 ] && [ -n "$FILE_PATH" ]; then
            # Check grants using a safe while-read loop
            MATCHED=false
            while IFS= read -r GRANT; do
                [ -z "$GRANT" ] && continue
                # Reject overly broad patterns
                case "$GRANT" in "*"|"/*"|"**") continue ;; esac
                # WARNING: bash [[ == ]] glob does NOT support ** without
                # shopt -s globstar. For deep path matching, adapt this
                # to use a case statement or dedicated tool.
                if [[ "$FILE_PATH" == $GRANT ]]; then
                    MATCHED=true
                    break
                fi
            done < <(jq -r '.grants[]?.pattern // empty' "$STATE_FILE" 2>/dev/null)
            if [ "$MATCHED" = true ]; then
                exit 0
            fi
        fi
        echo "AUTONOMY: Medium-risk action at level A${LEVEL}. Requesting approval." >&2
        exit 0  # Advisory (change to exit 2 for enforcement)
        ;;
    "high")
        if [ "$LEVEL" -ge 4 ]; then
            # Check exact command history (not patterns -- exact strings only)
            if jq -e --arg cmd "$CMD" '.high_risk_history | index($cmd)' "$STATE_FILE" >/dev/null 2>&1; then
                exit 0  # Previously approved exact command
            fi
        fi
        echo "BLOCKED: High-risk action requires explicit approval." >&2
        exit 2
        ;;
esac

CLAUDE.md Snippet

## Autonomy

This project uses autonomy levels (A0-A4) to control agent independence.
Current level is stored in `.autonomy-state`.

- Low-risk actions always proceed
- Medium-risk actions check autonomy grants
- High-risk actions always require explicit approval (unless A4 with history)
- Errors demote the autonomy level

Schema

See autonomy-state.schema.json for the full schema.