uv tool install git+https://github.com/andreasschmidtjensen/aorta4llm.gitIn your project directory:
aorta init --template safe-agent --scope src/This creates:
.aorta/safe-agent.yaml— the org spec (governance rules), withaccessmap entries set from your--scope.claude/settings.local.json— Claude Code hook configuration.aorta/state.json— event-sourced state (project-local).aorta/events.jsonl— event log foraorta watch.claude/commands/aorta/permissions.md,status.md, andcontext.md— slash commands for agent introspection- Registers agent
agentwith scopesrc/
Multiple scopes are supported:
aorta init --template safe-agent --scope src/ tests/The safe-agent template uses no-access entries for .env, secrets/, *.key, *.pem, and *.secret, so Read/Glob/Grep tools are automatically hooked. Use --strict to hook reads even without no-access entries.
Available templates (aorta template list):
- safe-agent — single agent scoped to a directory
- test-gate — must pass tests before committing
- minimal — scope-only, no norms or bash analysis (built-in)
Edit .aorta/safe-agent.yaml. The access map is the primary way to control file access:
organization: my-project
roles:
agent:
objectives: [task_complete]
capabilities: [read_file, write_file, execute_command]
# Access map — the primary interface for file-level governance.
# read-write: agent can read and write
# read-only: agent can read but not write
# no-access: agent cannot read or write
access:
src/: read-write
tests/: read-write
.env: no-access
.env.local: no-access
secrets/: no-access
"*.key": no-access
"*.pem": no-access
"*.secret": no-access
config/: read-only
# Norms — for command-level governance and advanced rules.
norms:
# Require tests before committing
- type: required_before
role: agent
command_pattern: "git commit"
requires: tests_passing
# Soft-block git operations (agent must confirm with user)
- type: forbidden_command
role: agent
command_pattern: "git commit"
severity: soft
- type: forbidden_command
role: agent
command_pattern: "git push"
severity: soft
# Hard-block destructive operations (never allowed)
- type: forbidden_command
role: agent
command_pattern: "git reset --hard"
# Mark tests_passing when pytest succeeds; reset when files change
achievement_triggers:
- tool: Bash
command_pattern: pytest
exit_code: 0
marks: tests_passing
reset_on_file_change: true
# Analyze bash commands for hidden file writes
bash_analysis: true
# Allow agent to write to the Claude Code memory directory (.claude/memory/)
allow_memory: true
# Commands that skip bash analysis (read-only, fast path)
safe_commands: ["pytest", "git status", "git diff", "git log", "npm test"]
# Composite achievements
counts_as:
- when: [tests_passing, lint_clean]
marks: quality_verified
# How long a soft block retry window lasts (default: 60)
soft_block_window: 30Note: .aorta/ and .claude/ are always protected — the hook engine
hard-blocks writes to governance infrastructure regardless of your org spec.
The access map is the recommended way to control file access:
| Level | Effect |
|---|---|
read-write |
Agent can read and write (also sets the write scope) |
read-only |
Agent can read but writes are blocked |
no-access |
Both reads and writes are blocked |
Manage from the CLI:
aorta access src/ read-write
aorta access .env no-access
aorta protect .ssh/ # shorthand for no-access
aorta readonly config/ # shorthand for read-onlyWhen the agent reads a file marked read-only or no-access (via allow-once), a PostToolUse hook injects a governance notice into the conversation:
[GOVERNANCE NOTICE] 'config/settings.yaml' is marked as sensitive (read-only or no-access). Do NOT copy, embed, or hardcode specific values from this file in any code you write. Use environment variable lookups or placeholder values instead.
This is a prompt-level signal, not enforcement — aorta controls where the agent writes, not what it writes. But the contextual warning at the exact moment of reading significantly reduces the chance of accidental credential leakage into source code.
The warning fires automatically for any path in the access map with read-only or no-access level. No additional configuration needed.
For command-level governance and advanced rules, use explicit norms:
| Type | What it blocks | Key fields |
|---|---|---|
forbidden_command |
execute_command containing a substring |
command_pattern, optional severity |
required_before |
execute_command until an achievement exists |
command_pattern, requires |
obliged |
Creates an obligation with a deadline | objective, condition, deadline |
forbidden |
Creates a prohibition with a condition | objective, condition |
Any norm can have severity: soft (confirmation-required) or severity: hard (default, always denied).
For file-level access control, use the access map (see above) — it's the recommended interface. The norms: list is for command-level governance and conditional rules.
- Hard block: action denied. The agent sees the reason and cannot proceed.
- Soft block: action denied with a "SOFT BLOCK" message that instructs the agent to ask the user for confirmation. If the agent retries the exact same action within the configured window, the retry is approved automatically.
This guards against post-compaction hallucination — if the agent tries to commit based on stale context, it gets blocked and must ask the user.
aorta validate .aorta/safe-agent.yamlChecks for missing fields, undefined roles, invalid norm types, and broken references.
# Test a file write
aorta dry-run --tool Write --path config/secret.py \
--agent agent --role agent --scope src/
# Test a bash command
aorta dry-run --bash-command "cp src/a.py /tmp/leak.py" \
--agent agent --role agent --scope src/When Claude Code calls a tool (Write, Edit, Bash, etc.):
- Hook fires — settings.local.json routes matched tools to
aorta hook pre-tool-use - Self-protection — writes to
.aorta/and.claude/are always denied - Agent check — unregistered agents are denied (fail-closed)
- Permission check — the engine checks the action against active norms
- Bash analysis (if enabled) — for Bash commands that pass:
- Fast path: known safe commands skip analysis
- Heuristic: regex extracts write paths from
cp,mv,>,tee,rm, etc. - LLM fallback: ambiguous commands (variable expansion, complex pipes) go to Haiku (~5s)
- Each extracted write path is checked against file-write norms
- Block or approve — hard blocks deny; soft blocks prompt for confirmation
When bash_analysis is enabled, every Bash command goes through write-path extraction. Commands on the safe_commands list skip this entirely — no heuristic, no LLM call, zero latency.
The default list covers common read-only commands:
safe_commands: ["pytest", "git status", "git diff", "git log", "npm test"]If you use custom test runners or build tools, add them to avoid the ~5s LLM analysis hit:
safe_commands:
- pytest
- git status
- git diff
- git log
- npm test
- cargo test
- make check
- jestEdit safe_commands directly in your .aorta/<template>.yaml. These are prefix-matched: "pytest" covers pytest tests/ -v, "git status" covers git status --short, etc.
Detect and respond to thrashing patterns:
guardrails:
per_file_rewrites:
threshold: 5
action: hold # or "warning"
failure_rate:
threshold: 0.5
action: warning
bash_commands:
threshold: 30
action: warningWhen a hold triggers, all actions are blocked until aorta continue is run.
Escalate repeated violations:
sanctions:
- on_violation_count: 5
then:
- type: hold
message: "5 policy violations — review your approach"aorta init creates slash commands that the agent can use during a Claude Code session:
/aorta:permissions— show effective permissions/aorta:status— show governance state/aorta:context— show governance summary (access map, rules, achievement gates, sanctions, obligations)
These run read-only aorta commands. The agent can also run aorta status, aorta permissions, aorta explain, aorta validate, and aorta doctor directly via Bash — read-only aorta commands are allowed. Only mutating commands (init, reset, allow-once, etc.) are blocked.
aorta statusShows registered agents, active norms, achievements, and recent activity (approved/blocked counts, last blocked actions). Add --json for machine-readable output.
All commands auto-detect the org spec from .aorta/. Use --org-spec to override when you have multiple specs.
aorta permissionsShows the access map with actual read/write status, command restrictions, achievements, and self-protection rules.
aorta resetClears registered agents, achievements, and events. Use --keep-events to preserve the event log. Reset auto-re-registers agents from prior state or derives them from the org spec.
Monitor governance events in real-time from a separate terminal:
aorta watchShows blocks, approvals, registrations, and achievements as they happen. Useful during a Claude Code session.
Split-panel live view with policy tree and event stream:
aorta watch --dashboardShow achievement dependencies as an ASCII graph:
aorta status --graphThe agent receives governance rules at session start via the SessionStart hook. Run manually:
aorta contextShows access map, rules, achievement gates (with status), sanctions, and obligations in LLM-friendly format. The /aorta:context slash command runs this.
Validate policies against real session traces:
aorta replay --last # replay most recent session
aorta replay --trace session.jsonl # replay specific trace
aorta replay --last --format full # full output with divergence pointsShows what your policy would have blocked or approved. Distinguishes policy blocks from sanctions and held (counterfactual) events.
Measure hook latency to see how much overhead governance adds:
aorta timing # show stats from recent hook invocations
aorta timing -n 50 # last 50 invocations onlyShows count, average, p50, p95, and max latency per hook type (pre-tool-use, post-tool-use), with init vs handle breakdown. Timing data is recorded automatically in events.jsonl.
Running aorta init when aorta hooks already exist will exit with an error. Use --reinit to overwrite:
aorta init --template safe-agent --scope src/ tests/ --reinitNon-aorta hooks (e.g., your own linters) are always preserved — only aorta hooks are replaced. --reinit also clears allow-once exceptions and soft block state.
When a hook blocks an action, the block message includes an allow-once command hint. To grant a one-time exception:
aorta allow-once .envThe next access to .env will be approved; subsequent accesses are blocked again. Use --agent <name> to restrict the exception to a specific agent in multi-agent setups.
To understand why an action is allowed or blocked:
aorta explain --tool Write --path config/db.yml --agent agent --role agent --scope "src/"Shows each norm, whether it applies, and why it matches or doesn't.
Claude Code treats hook errors as non-blocking — if the aorta binary
isn't found or the hook crashes, actions proceed silently. Run:
aorta doctor
Common causes:
aortanot on PATH (install withpip install aorta4llmoruv pip install aorta4llm)- Stale hooks after reinstall (run
aorta init --reinit) - Agent not registered (check
aorta status)
- No content governance: aorta blocks writing to
.envbut cannot prevent the agent from reading a file and pasting its contents elsewhere. Useno-accessto block reads of truly sensitive files. For files the agent needs to read but shouldn't leak (e.g.config/), the sensitive content warning provides a prompt-level nudge but not hard enforcement. - Bash escape hatch: an agent can construct commands that evade heuristic detection (e.g.,
python -c "open('x','w')..."). LLM analysis catches most of these but isn't bulletproof. - No filesystem monitoring: governance only sees tool calls, not side effects.
- LLM analysis latency: ~5s per ambiguous bash command. The heuristic pre-filter handles ~80% of patterns instantly.