The kernel decides whether an action is allowed β bands, caps, kill switch. Guard adapters decide whether an allowed action is sane: they catch what a model does wrong inside its authority. Pluggable pre/post hooks on every governed action, hash-pinned so reviewed code is the code that runs. Everything is an adapter β enable exactly the risk surface you want, or ship your own.
An adapter runs pre_action before execution and post_action after, and returns one of four verdicts. A DENY short-circuits the pipeline; TRANSFORMs chain, each adapter seeing the last one's edits. A crashing adapter becomes a DENY if it declared fail_closed, else a WARN β the pipeline never dies mid-run.
Clean. The action proceeds unchanged.
Logged and flagged, but not blocked.
Edits the action β redact, rewrite, clamp β then continues.
Short-circuits. The action never runs.
Each is a hash-pinned adapter you turn on with one command: custodian adapters enable secret-leak-guard. Third parties ship their own via a pip entry point.
~/.ssh, *.env, keys) β reads and writes, including paths inside shell commands.paladin:// secret sent to a destination not on its allowlist gets denied.Local adapters install with their SHA-256 pinned in the manifest. If the file changes after install, it refuses to load β the same stance as the kernel's own source-tamper check. No silent swap between the review and the run.
Every guardrail that matters holds its state outside the model and enforces at pre_action β independent of whether the model still remembers the rule. A forgetful local agent gets reminded, but enforcement never depends on the reminder landing.
Ship it as a pip package exposing the custodian.adapters entry point, or install a local file that's SHA-256 pinned.
from custodian.adapters.base import Adapter, Verdict class BusinessHoursGuard(Adapter): name = "business-hours" category = "guardrail" fail_closed = True def pre_action(self, ctx): import datetime if ctx.skill.startswith("stripe-") and \ datetime.datetime.now().hour < 6: return Verdict.deny(self.name, "no payments before 06:00") return Verdict.allow(self.name)
# list available + enabled, by category custodian adapters list # enable one, with config custodian adapters enable spend-sentinel \ --config '{"max_per_minute": 4}' # install a local file β SHA-256 pinned custodian adapters install ./business_hours.py # dry-run a check custodian adapters check stripe-spend \ --args '{"amount": 5}' --band L2
Bands and caps stop an agent from exceeding its authority. Adapters catch what it does wrong within it β and you enable exactly the surface you want.