Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

agent-guardrails

test

Guardrails for coding agents you actually leave unattended: secrets, destructive git, config weakening, context bloat, verify-before-done, token metering. Measured friction (destructive-git guard): ~0.4 asks per day, zero false positives on a month of real multi-agent history.

Coding agents with shell access will eventually type git reset --hard at the wrong moment, paste an API key into a source file, or declare "done!" while the tests are red. Permission popups for everything train you to click yes; no guardrails at all trains you to fear unattended runs. This suite sits in the middle, built on three rules:

  1. Ask, don't wall. Destructive commands are sometimes exactly right. A guard's job is a deliberate pause with a reason, not a lockout. Hard deny is reserved for one thing only: high-confidence secrets.
  2. Fail-open, always. A broken guard must never block your agent. Every handler and the dispatcher swallow their own failures.
  3. Friction is a budget. Every pattern here earned its place by being quiet on real history. If a guard nags, it is a bug.

The guards

Guard Event Decision What it catches
secrets-guard before Write/Edit/Bash deny (high-confidence) / ask (generic) AWS/GitHub/Anthropic/OpenAI/Slack keys, private key blocks, JWTs, password = "..." assignments; softened to ask in docs/tests/examples paths
git-guard before Bash ask reset --hard, force-push (--force-with-lease stays frictionless), clean -f, checkout -- ., restore, branch -D, stash drop, filter-branch
config-protection before Write/Edit ask edits to linter/formatter configs - the agent should fix the code, not weaken the rules
claude-md-guard before Write/Edit ask a CLAUDE.md growing past its line budget (default 200) - long instruction files eat context and lower rule adherence; shrinking is always silent
verify-guard on Stop block (capped) "done" with failing checks: runs your project's verify commands and hands the failing output back so the agent self-corrects; attempt cap prevents infinite loops
cost-guard on Stop silent observer sums real token usage from the session transcript into per-session snapshots; optional warning threshold

One spawn, not six

All guards run through a single dispatcher (hooks/run.js) that chains comma-separated guard ids in ONE node process - on Windows, per-hook spawns cost ~75 ms each, so chaining three guards saves ~150 ms on every single tool call. Semantics: run in order, first non-null decision wins, deny-capable guards go first.

{
  "type": "command",
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run.js\" secrets-guard,git-guard",
  "timeout": 10
}

Install

As a Claude Code plugin:

/plugin marketplace add NiceLeader/agent-guardrails
/plugin install agent-guardrails@agent-guardrails

hooks/hooks.json wires all six guards with sane chains.

Manually / other runtimes: any agent that speaks the hooks stdin-JSON contract (tool_name, tool_input, cwd, session_id - Codex CLI does) can call the dispatcher directly. Wire the PreToolUse chains and the Stop chain from hooks/hooks.json into your runtime's hook config.

verify-guard: opt in per project

// .agent-guardrails/verify.json in your project root
{ "commands": ["npm run typecheck", "npm test"], "timeout": 120, "maxAttempts": 3 }

With a dirty working tree, every Stop runs the commands in order; the first failure blocks the turn and pastes the failing output back to the agent. After maxAttempts blocked turns it lets go with a notice - a fundamentally broken approach must not loop forever. Pick hermetic commands: an interactive first-run prompt or an env-dependent test will block every turn for reasons that have nothing to do with the agent's work.

Tuning

  • GUARD_HOOK_PROFILE = minimal (secrets only) / standard (default, everything)
  • GUARD_DISABLED_HOOKS = comma-separated ids to switch off
  • GUARD_CLAUDE_MD_MAX = line budget for claude-md-guard (default 200)
  • GUARD_VERIFY_CMD = one-off verify command override
  • GUARD_VERIFY_STATE_DIR = where verify-guard keeps per-session attempt state
  • GUARD_COST_DIR, GUARD_COST_WARN_USD = cost snapshots dir / opt-in warning

Timeout math for verify-guard: the hook-level timeout in hooks.json is 600s total; verify.json's timeout is per command (default 120s). Keep commands.length x timeout under the hook budget or a slow suite gets killed mid-run - which fails open, i.e. no verification at all.

Scope honestly stated

These guards protect against accidents, not adversaries. A hostile agent could edit .agent-guardrails/verify.json, split a secret across two edits, or otherwise route around a fail-open system - by design: a guard suite that can hard-lock your agent is worse than one that can be circumvented. For the after-the-fact layer that catches what slipped through, pair with the flight recorder below.

Measured, not vibes

The ask-rate claim comes from replaying these patterns against a full month of real audit trails (23k tool calls across two agents): destructive-git asks averaged 0.4/day and every single one was a genuinely destructive command. The --force-with-lease and restore --staged exclusions exist because the first draft flagged them and that was noise. The test suite (48 cases) pins both sides: everything dangerous fires, every named safe variant stays silent.

Sister projects

  • agent-flight-recorder - the observe half: zero-token JSONL trail of every tool call across your agents. Guards pause the dangerous moment; the recorder proves what actually happened.
  • llm-wiki-ops - operations engine for agent-maintained Obsidian vaults.

License

Apache-2.0 © Maciej Lewandowski - see LICENSE and NOTICE.

About

Guardrails for unattended coding agents: secrets deny, destructive-git ask, verify-before-done, token metering - fail-open, one spawn, measured friction (Claude Code + Codex)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages