Pair Every Prompt Rule with a Code Backstop
Prompts guide, code enforces: inventory your agent's soft rules, back each critical one with an independent deterministic check, and define the allow/ask/deny escalation.
The route
4 steps to Done
- 01
Inventory soft rules and classify enforcement needs
Know which promises are currently backed by nothing.
Preview prompt + verify gate ▾ Hide ▴
Extract every behavioral rule from your system and tool prompts into a list. For each, record: what it protects (data loss, money, credentials, repo history, user trust), the worst case if the model ignores it, and how often transcripts show it violated. Classify: GUIDANCE (style, tool preference - prompt-only is fine, though code may add suggestions), CONFIRM (destructive-but-legitimate operations - needs an ask gate), and BLOCK (never legitimate without explicit authorization - needs a deny). The output is an enforcement matrix: rule, class, existing enforcement (usually none), required check. Prioritize by worst case, not frequency.
- ✓ Every prompt rule captured with worst case
- ✓ Each rule classed guidance/confirm/block
- ✓ Gaps between class and existing enforcement listed
- 02
Implement checks independent of the prompt
The code layer must stand alone.
Preview prompt + verify gate ▾ Hide ▴
For each confirm/block rule, implement a deterministic check in the tool-execution path that inspects the actual action (parsed command, target path, flags, destination) - never the conversation text. Examples: destructive git detection (force-push, hard reset, branch deletion) by parsing the command; protected-path validation that refuses removal of system or credential paths regardless of any permission rule; parameter validation that catches dangerous sub-features of allowed tools (a command runner's in-tool execute flags, write-to-file directives inside an editor command). Each check returns allow, ask with a human-readable reason, or deny. Prove independence: run the test suite with the safety prompt entirely removed - every check must still fire.
- ✓ Checks parse the real action, not conversation text
- ✓ Each returns allow/ask/deny with a reason
- ✓ Full suite passes with safety prompts removed
- 03
Wire the escalation path
Ask preserves the user's authority; deny is reserved for certainty.
Preview prompt + verify gate ▾ Hide ▴
Route check outcomes through a consistent escalation: ALLOW proceeds silently; ASK pauses the action and presents the user a specific confirmation ('This will force-push to main and may overwrite remote history. Proceed?') with the parsed action visible, not the raw model output; DENY blocks with an explanation and suggests the safe alternative. Design the ask UX so confirmations are informed: show the target, the effect, and why it was flagged. Log every ask/deny with the triggering rule for later tuning. Add warnings-without-blocking for the gray zone: known-risky patterns (recursive force delete, infra teardown) append a caution note to the confirmation rather than a hard gate.
- ✓ Ask shows parsed action, effect, and reason
- ✓ Deny explains and offers the alternative
- ✓ All escalations logged with the triggering rule
- 04
Analyze the four failure combinations
Know exactly where the architecture's edges are.
Preview prompt + verify gate ▾ Hide ▴
Document and test the four combinations. 1) Prompt fails, code catches: model ignores guidance, check fires - verify with transcripts that the ask/deny actually triggered. 2) Prompt works, code idle: the common good case - confirm no needless friction was added. 3) Both fail: construct a red-team case the code misses (novel obfuscation, unicode homoglyphs, a tool interaction you did not model) - this defines your residual risk; mitigate with sandbox/blast-radius limits and user confirmation as the last gate, and feed each miss back into step 2 as a new check. 4) Layers conflict: prompt permits, code refuses - assert code wins, then fix the prompt to stop promising what code forbids. Ship a one-page architecture note so future contributors preserve layer independence.
- ✓ All four combinations have concrete test cases
- ✓ A both-fail case exists and its mitigation is stated
- ✓ Code-wins-on-conflict is asserted in tests
Research-backed
Sources behind this flow
Tier 1 · claude-code-internals
Deep Dive: Prompt-Layer Security in BashTool
Dissects how BashTool's prompt forms the first line of defense: a three-layer structure (tool preference chains, usage constraints, git-safety and sandbox protocols) assembled dynamically per environment. Shows how wording strategy works - capitalized NOT, positive alternatives ('use Edit' rather than 'avoid sed'), and 'better user experience' framing instead of 'security' - and how token budget (config dedup, conditional blocks) is treated as a first-class engineering constraint rather than an afterthought.
Tier 1 · claude-code-internals
Deep Dive: Code-Layer Security - 20+ Validators Behind BashTool
Walks the layered validation pipeline that backs up the prompt when the model ignores its 'suggestions': control-character checks, multi-view quote extraction, obfuscated-flag detection (ANSI-C quoting, empty-quote concatenation, quote chains, triple quotes), brace-expansion and backslash-escaped-operator traps, hard path constraints, a whitelist-plus-denylist sed validator, and per-tool exit-code semantics. Every validator returns allow/ask/deny/passthrough, and misparsing-class findings outrank informational ones.
Tier 1 · claude-code-internals
Deep Dive: Dual-Defense Architecture - Prompt + Code
Argues that because LLMs are probabilistic, prompts alone are gambling and code alone wastes the model's flexibility. Maps how Claude Code pairs every soft prompt rule ('use Edit instead of sed', 'NEVER force-push') with an independent deterministic check (sed validators, destructive-git detection, sandbox gates), analyzes the four failure combinations of the two layers, and distills five reusable design principles for any tool-calling agent system.