041 · Gates
Rules that gate an agent’s turn, checked before it runs and after it answers; guardrails that watch every step of it (spec_gates_041)
| Status | Done in v0.7.1 (the owner’s request, 2026-10-17): rules GT-01–GT-11, guardrails GT-20–GT-29. |
| Source | pkg/engine/gates/gates.go, pkg/engine/frontmatter/frontmatter.go, pkg/engine/rules.go, pkg/engine/turn.go, pkg/engine/memory/memory.go (splitRule), pkg/engine/tools/checkpoint.go (LatestTurnDiff), apps/service/internal/server/rules.go, apps/desktop/web/src/studio/RulesPane.tsx, apps/desktop/web/src/studio/RulesOrGuardrails.tsx, apps/desktop/web/src/studio/ruleForm.ts, pkg/engine/gates/guardrails.go, pkg/engine/guardrails.go, pkg/engine/runtime/guard.go, pkg/redact/pii.go, apps/service/internal/server/guardrails.go, apps/desktop/web/src/studio/GuardrailsPane.tsx, apps/desktop/web/src/studio/guardrailForm.ts |
| Tests | pkg/engine/gates/gates_test.go, pkg/engine/frontmatter/frontmatter_test.go, pkg/engine/rules_test.go, pkg/engine/memory/memory_more_test.go (TestGateRulesAreNotInstructions), apps/service/internal/server/rules_test.go, apps/desktop/web/src/studio/ruleForm.test.ts, pkg/engine/gates/guardrails_test.go, pkg/engine/guardrails_test.go, pkg/redact/pii_test.go, apps/service/internal/server/guardrails_test.go, apps/desktop/web/src/studio/guardrailForm.test.ts |
| Depends on | spec_memory_012 (rule files), spec_studio_040 |
A team’s expectations of its agents (“never touch production”, “say how the change was tested”) are more dependable checked than hoped for. A rule says one such thing in plain English; a fast model judges it on the prompt before the agent runs, or on the answer and the turn’s changes before the turn ends; and the rule says what a failure does.
- GT-01 A rule file (
*.mdin the rule folders,.blitz/rules,.agents/rules,.claude/rulesand the global one; MEM-07) with front matterwhen: before_agentorwhen: after_agentis a gate: not an instruction (the memory loader skips it), judged around the turn. Other fields:action(block,warnorrevise;warnwhen unset),order(gates run by order, then name),agents(the agents whose turns it gates; none means every agent),description. Its body is the rule. A rule withoutwhenis an instruction rule, unchanged (MEM-07). An unknownwhenoraction, or an empty gate, is the rule’s problem, and it’s skipped (gates.Parse). - GT-02 The judge (
gates.Judge) asks the suggestions model (else auto mode’s, else the workspace’s) whether the subject passes the one rule, with a brief to fail only when the rule is clearly broken (a rule that doesn’t apply passes), and reads its JSON verdict (passorfail, a reason saying what would make it pass). Secrets in the subject are redacted first. - GT-03 Before the agent: once the prompt is accepted (after the
prompt_submithooks), each before-agent gate offered to the active agent judges it, in order. A failing block rule refuses the prompt (*api.BlockedError, “The rule … stopped this prompt: …”), nothing recorded, the agent not run; warn says so in the conversation once the prompt is recorded; revise gives the agent the reason with the prompt (<rules-hook-context>). Side questions aren’t judged. - GT-04 After the agent: once the turn’s passes are done (an approved plan carried out,
stophooks and a goal), each after-agent gate offered to the active agent judges the answer (12 KiB) and the diff of the files the latest pass changed (Checkpoints.LatestTurnDiff, 12 KiB), in order. A failing revise rule sends the agent back with the reasons ("(rules) …" in the transcript,session.KindRule), and the rules judge again, twice at most; then “Rules still fail after 2 revisions”. block marks the answer as failing the rule (an error notice); warn a notice. A plan that wasn’t carried out isn’t judged. - GT-05 A rule the judge can’t judge (no model, an answer that isn’t a verdict) says so (“The rule … couldn’t be checked”) and lets the turn go on.
- GT-06 Every verdict goes to the audit log (
audit.KindRule: the rule, pass, warn, revise, block or unjudged, and the reason). - GT-07
WorkspaceService.ListRulesreturns the workspace’s rules: gates in the order they run, then instruction rules; each with whether it’s editable (in.agents/rules).SaveRulewrites.agents/rules/<name>.mdfrom the form, renaming when the name changes, keeping the front matter’s other fields (frontmatter.Edit); an instruction rule keeps no gate fields, a gate nopaths; problems (a bad name, when or action, an empty rule, a taken name) come back with nothing written.DeleteRule(RULE_NOT_FOUND),ReorderRules(orders 10, 20, 30… in the order given) andTryRule(a sample prompt or answer judged against the rule as written; a judge that fails is aproblem). - GT-08 Studio › Rules (
RulesPane): three lists, Before the agent and After the agent (each in the order its rules run, numbered, with the action and the agents; dragged, or moved with ↑ ↓, to reorder) and Instructions (always, or for the files named); a rule elsewhere than.agents/rulesis listed with its path and opens read-only. + in the title bar (the page’s only one) starts a rule; a new gate goes last in the list its when puts it in. A rule’s form slides in over the list as an agent’s does: name, what it’s for, when (before, after, or an instruction), the action with what it does then, the agents it applies to (or an instruction’s files), the rule with Redraft (DraftManifestkindrule), and for a gate Try it (a sample, Check, the verdict). Back, Cancel and Save; unsaved changes are asked about. - GT-09 Each list row is one button that opens the rule beside its own buttons (↑ ↓), never a button inside a button, so each control has its own name.
- GT-11 Rules or guardrails: Studio › Rules and Studio › Guardrails both have an (i) beside the page’s title (
GatesTitle) that shows the same side-by-side comparison under it, hidden at first since vertical space is dear (RulesOrGuardrails: what each is for, when it runs, what it checks, what a failure does, an example; the page shown marked “this page”) and a button to the other: rules check the work against the team’s expectations, at the turn’s edges (the prompt before, the answer and changes after), and can send the work back; guardrails are for safety and limits (instructions hidden in fetched pages or files, secrets in answers, spending, risky commands) and watch every step, built in or the user’s own, blocking, redacting or warning, never revising. A line under it says when the other is the one to use (“About safety rather than quality? It’s a guardrail.”). A rule’s form says the same in a line (a gate: “if it’s about safety rather than quality, make it a guardrail”; an instruction rule: it isn’t checked). - GT-10
pkg/engine/frontmatteredits Markdown front matter for Studio’s forms (rules, skills): fields set, or removed when empty, every other field kept in its order.
- GT-20 A guardrail is
.agents/guardrails/<name>.md(or~/.blitz/guardrails/<name>.md, for every workspace; a workspace’s file wins over a global one of the same name), with front matterstage(input: the prompt;tool_result: what a tool returns, before the agent reads it;output: the agent’s answer;action: each tool call, before it runs),action(block,warnorredact; what the stage allows),agents(none: every agent),tools(fortool_resultandaction; none: every tool, or for prompt injection the tools that bring in outside content:web_fetch,web_search,browserand MCP tools),description,enabled(falseswitches it off, its settings kept), and eitherbuiltin: <id>or a body: the user’s own check in plain English. An unknown stage, action or built-in, or a custom guardrail with no body, is its problem, and it’s skipped (gates.ParseGuardrail). Guardrails are listed built-ins first, then by stage and name. - GT-21 Three built-ins (
gates.Builtins), on by default: without a file, each runs with its defaults (gates.BuiltinDefault). Its file (named after it, so it’s versioned with the project) changes its settings, or switches it off withenabled: false; deleting the file puts it back to its defaults. The built-ins: prompt injection (prompt_injection, tool results,warnorblock; patterns for text aimed at the agent,judge: trueto have the judge confirm a match first), secrets and personal data (secrets, answers,redact,warnorblock; the redactor’s credential patterns, and withpii: trueemail addresses, phone numbers, Luhn-valid card numbers and social security numbers,redact.PII) and limits (limits, actions,block;max_cost_usd,max_turns,max_files_changed,allowed_domains,commands; without a file it refusesgates.DefaultCommands: force pushes,rm -rfof/or~). - GT-22 Limits: a turn’s model calls and spending are capped at the guardrail’s
max_turnsandmax_cost_usd(the lower of it and the turn’s own). Before a tool runs, a URL in its arguments offallowed_domains(a domain allows its subdomains), a shell command matching acommandsglob (*for anything, slashes too,?one character, against the whole command), or a write once the turn has changedmax_files_changedfiles is refused with the reason ("🛡 The guardrail limits stopped this: …"). - GT-23 Prompt injection, on what a checked tool returns: warn gives the agent the result with a caution that it’s content, not instructions (
guardrailin the result), and says so in the chat; block withholds the result (the agent is told it was withheld and not to follow it) and says so. - GT-24 Secrets in the answer, as it streams and in full: redact masks them ("🛡 … masked secrets or personal data in the answer"), block withholds the text, warn says the answer shows what looks like one. Each guardrail’s notice comes once a turn.
- GT-25 Custom guardrails are judged (
gates.JudgeGuardrail, the rules’ judge with a safety brief):inputon the prompt before the turn (after the input built-ins, before the before-agent rules: GT-03),actionon the tool and its arguments,tool_resulton the result,outputon the whole answer. A failing block refuses the prompt or the call, withholds the result, or withholds the answer, with the reason; warn says so. A custom guardrail that can’t be checked (no model, no verdict) refuses what it guards when it blocks, unlike a rule; when it warns, it says it couldn’t check. - GT-26 The engine calls the guardrails through
runtime.Guard(WithGuard):ToolCallbefore a tool runs,ToolResultafter,Outputon the model’s text; a sub-agent’s calls are guarded by its parent’s turn’s guardrails. Every decision goes to the audit log (audit.KindGuardrail). - GT-27
WorkspaceService.ListGuardrailsreturns the built-ins (each as it runs: its file, or its defaults with no path;offwhen switched off) and the workspace’s own guardrails.SaveGuardrailwrites.agents/guardrails/<name>.mdfrom the form (a built-in under its id, edited in place when it’s there,enabled: falsewhen it’s off; renaming when a custom one’s name changes; other front matter kept, GT-10), problems coming back with nothing written.DeleteGuardrail(a built-in’s file: back to its defaults;GUARDRAIL_NOT_FOUND) andTryGuardrail(a sample prompt, result, answer or action checked as the guardrail is written: the patterns for the built-ins, the judge for custom ones). - GT-28 Studio › Guardrails (
GuardrailsPane): Built in, on unless switched off, each with what it watches, its action, a switch (off writesenabled: falsein its file, keeping its settings; on removes it) and its settings; and Your own (+ in the title bar adds one). A guardrail’s form slides in over the list (GT-08’s layout): a built-in’s switch beside its title, its settings (the action; personal data too; confirm with a model; the limits) and the agents it guards, and Reset to defaults (asked first) once it has a file; a custom one’s name, what it’s for, what it checks, the action with what it does then, its tools, agents and what to catch, with Draft or Redraft (DraftManifestkindguardrail); Try it with a sample (not for limits). Back, Cancel and Save, unsaved changes asked about. - GT-29 The engine’s notices and the page’s text are in every locale (
guardrail.*,desktop.guardrails.*).
A custom output guardrail judges the answer once it’s complete, so a streamed answer is shown before it’s withheld. Limits count the files the turn changed by writes the engine sees, not by shell commands.