Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/quitohooded/keel-skills/llms.txt

Use this file to discover all available pages before exploring further.

The Green-Light Brake example shows the soft brake — the agent reasoning its way to a stop. This example shows the hard brake: the PreToolUse hook (enforce-policy.cjs) intercepting a tool call and stopping it regardless of what the model decided. Two layers, defense in depth. The difference matters in one sentence: the soft layer depends on the model choosing to comply; the hard layer doesn’t.

The Setup

The project’s AGENT_POLICY.md contains a machine-readable block that the hook parses at runtime:
hot_paths:
  - "src/**"
hot_commands:
  - "git push"
standing_allow_commands:
  - "npm run build"
These entries refine the SPEC §4 defaults — they don’t replace them. The hook always enforces the defaults (git push, deploy, rm -rf, schema changes, outward MCP calls) even with no block at all. A project that omits the block entirely still gets full default coverage.

Call-by-Call Decisions

Every tool call is inspected before it runs. The hook emits a decision — allow, ask, or deny. When the verdict is ask, the agent surfaces an explicit human-approval prompt: that’s the request for a green light.
// Input
{"tool_name": "Read", "tool_input": {"file_path": "src/x.ts"}}

// Output
{"permissionDecision": "allow", "reason": "keel: read-only tool"}

Headless Mode — No Human, No Pass

Run the same push in a non-interactive context — CI, a headless agent, a scheduled job — triggered by CI=true or KEEL_NONINTERACTIVE=1. There is no one to give a green light, so ask cannot be answered. The hook denies outright:
{"tool_name": "Bash", "tool_input": {"command": "git push --force origin main"}}
{
  "permissionDecision": "deny",
  "reason": "keel: BLOCKED (no human present to give a green light) — command matches hot pattern `git push`"
}
In a non-interactive context (CI=true or KEEL_NONINTERACTIVE=1), there is no one to give a green light, so ask automatically becomes deny. This is the case the reasoning layer fundamentally cannot cover: when the agent runs unattended, “stop and ask” has to mean stop, because asking goes nowhere.

The Audit Trail

Every decision lands in .keel/audit.jsonl — so after the fact you can answer “what did the agent try, and what did we stop?”
{"ts":"2026-06-30T18:56:08.520Z","tool":"Write","input":"src/app/page.tsx","verdict":"ask","rule":"hot_path:src/**"}
{"ts":"2026-06-30T18:56:08.795Z","tool":"Bash","input":"npm run build","verdict":"allow","rule":"standing_allow:npm run build"}
{"ts":"2026-06-30T18:56:09.110Z","tool":"Bash","input":"git push --force origin main","verdict":"deny","rule":"hot_command:git push"}
The log records the timestamp, the tool, the matched rule, and the verdict. One line per decision — straightforward to grep, parse, or pipe into an alerting system.

Honest Scope — Backstop, Not Sandbox

The hook catches accidents, drift, and hallucinated actions — a large lift in assurance. It does not contain an adversarial or jailbroken agent: shell command matching can be routed around (g=push; git $g), and an agent with real credentials can still do real damage.
Pair enforcement with scoped credentials and a sandbox for actual isolation. Keel raises the floor; it is not the wall.

Why Both Layers

Both layers are necessary. Neither is sufficient on its own.
Soft layer (skills)Hard layer (hook)
What it isThe agent applies the four-step check itselfDeterministic code intercepts the call
StrengthContext-aware, smart, explains itselfFires regardless of the model’s choice
WeaknessDepends on the model complyingOnly catches concrete, pattern-matchable cases
Covers headless?No (assumes a human is present to be asked)Yes — denies when no human present
The skill reasons; the hook backstops. See SPEC.md §8.1 for the enforcing-implementation conformance requirements.

Build docs developers (and LLMs) love