A guardrail is a product decision with a safety vocabulary.
It defines what the product allows, blocks, explains, logs, routes, and escalates. It should answer three questions:
- what is blocked?
- what is allowed?
- what happens next?
A code assistant might block writes outside the workspace and route risky edits through diff review. A support assistant might draft a reply but require approval before sending. A content tool might log refusals and preserve the triggering input so the team can inspect false positives.
The refusal, allowed scope, and receipt should describe the same boundary. If each surface tells a different story, the policy is hidden in three places.
The state machine can stay small:
type GuardrailDecision = "allow" | "block" | "review"
function decide(request: string): GuardrailDecision {
if (request.includes("export all customer data")) return "review"
if (request.includes("ignore previous instructions")) return "block"
return "allow"
}
That example is intentionally simple. The complexity belongs in the policy and the evidence around the decision, not in pretending every state needs a new abstraction.
A guardrail is good product design when the user can understand the result and the team can explain why it happened. It is not a patch added after the product has already decided how work should flow.
Related posts

About Jeremy London
Engineering leader and builder in Denver. I write about AI platforms, agents, security, reliability, homelab infrastructure, and the parts of engineering work that have to survive production.