How-to guide
DraftDefine a policy gate
Turn a written constraint into a separate, testable decision that permits or blocks a consequential agent action.
- For
- Teams adding controls to agent workflows
- You will finish with
- A policy gate with bounded inputs, explicit decisions, separate authority, and tests for denial and failure.
Before you start
- A named action with a known destination and side effect
- A written policy or operating rule for that action
- A way to stop the action before it reaches the target system
A policy gate sits between a proposed action and the system that would carry it out. It answers a bounded question such as “may this refund be issued?” or “may this change deploy to production?”
The agent can prepare the request. The gate owns the decision. The executor requires proof of that decision before it acts.
1. Name the protected action
Start with one action and one enforcement point. “Check security” is too broad.
“Authorize deployment of artifact digest D to production environment E” is
specific enough to enforce.
Write down:
- the side effect;
- the target system or resource;
- the identity that performs it;
- the last point where the workflow can still stop it;
- whether the action is reversible;
- the maximum acceptable scope.
Place the gate before the side effect. A check that runs after a message is sent or money moves is an audit, not a gate.
2. Translate prose into a decision table
Take the written policy and remove ambiguous terms. Define the input source for each condition.
Example policy: refunds above 500 require a person, the order must be settled, and the refund must not exceed the captured amount.
| Condition | Source | Decision |
|---|---|---|
| Order is not settled | Payments system | Deny |
| Requested amount exceeds captured amount | Payments system | Deny |
| Requested amount is above 500 | Request plus policy version | Require approval |
| All required facts are present and no rule blocks | Gate | Allow |
| A required fact is missing or stale | Gate | Indeterminate |
Use the units and currency defined by the source system. If the source stores minor units, keep the gate input in minor units rather than introducing a conversion inside a prompt.
3. Define the contract
The gate should receive facts, not a persuasive narrative from the agent.
{
"action": "refund.create",
"subject": "order-1842",
"amount_minor": 42500,
"currency": "ZAR",
"captured_amount_minor": 60000,
"order_state": "settled",
"facts_observed_at": "2026-08-13T09:30:00Z",
"request_digest": "sha256:...",
"policy_version": "refund-v4"
}
Return a structured decision:
{
"decision": "allow",
"reason_codes": ["ORDER_SETTLED", "WITHIN_CAPTURED_AMOUNT"],
"policy_version": "refund-v4",
"request_digest": "sha256:...",
"expires_at": "2026-08-13T09:35:00Z",
"decision_id": "gate-7f31"
}
Use a closed set of decisions such as allow, deny, require_approval, and
indeterminate. Reason codes should also come from a controlled set so callers
can handle them without parsing prose.
4. Separate the gate from the proposer
Run the gate with an identity the agent cannot modify or impersonate. The agent may call the decision interface, but it should not be able to:
- deploy a new policy version;
- edit the facts returned by systems of record;
- mint an allow decision;
- use the executor without a valid decision;
- alter gate logs.
Do not rely on the agent to remember to call the gate. Make the executor reject requests that lack an applicable decision.
execute(request, decision):
require decision.decision == "allow"
require decision.request_digest == hash(request)
require decision.policy_version in accepted_policy_versions
require now < decision.expires_at
require decision has not been consumed
perform request
Binding the decision to the request digest prevents an allow decision for one request from authorizing a different request.
5. Decide how uncertainty behaves
List every dependency the gate needs: policy store, identity provider, source facts, clock, and decision store. For each dependency, define what happens when it is unavailable or returns stale data.
Use indeterminate when the gate cannot establish the facts needed to allow or
deny. The executor should treat indeterminate as a stop. If the workflow can
route the case to a person, make that an explicit transition rather than
converting uncertainty into permission.
Also define:
- how long a decision remains valid;
- whether a decision can be used once or more than once;
- what happens when policy changes after a decision is issued;
- who can grant an exception and how the exception is recorded.
6. Make the decision explainable
Store enough information to reconstruct why the gate decided as it did:
decision_id
request_digest
action and subject
policy version
input fact references and observation times
decision
reason codes
expiry and consumption state
Keep sensitive values out of general logs. A fact reference and digest may be enough when the underlying system already holds the value under appropriate access controls.
7. Verify the gate
Test the executor and gate together. A correct policy function is not enough if the side effect can bypass it.
- Submit a normal allowed request. Confirm the executor accepts the matching unexpired decision.
- Submit each deny condition separately. Confirm the reason code identifies the failed rule.
- Remove a required fact. Confirm the decision is
indeterminateand no side effect occurs. - Reuse an allow decision with a changed amount or target. Confirm the digest mismatch blocks execution.
- Replay a consumed single-use decision. Confirm the executor rejects it.
- Stop the gate or policy store. Confirm the executor does not fall back to an unchecked path.
- Attempt the side effect with the agent’s own credentials. Confirm the target system denies it.
- Change the policy version. Confirm old decisions follow the expiry and revocation rules you defined.
Common failure modes
The policy is still natural language at runtime
Written policy is useful input to design. It is a poor runtime return type. Convert each enforceable condition into typed facts, comparisons, and reason codes. Route genuinely interpretive cases to a named reviewer.
The gate trusts facts supplied by the agent
An agent can identify a subject, but authoritative amounts, states, and roles should come from their systems of record. Include provenance and observation times in the decision input.
The allow token is not bound to the action
A reusable boolean such as approved: true can drift to another target or
amount. Bind the decision to a request digest, policy version, expiry, and use
count.
There is an emergency bypass with no separate owner
An override controlled by the same agent is part of the normal path. Give exceptions a separate identity, a narrow scope, an expiry, and a recorded reason.
Denials disappear into logs
Return stable reason codes and a next state. The caller should know whether to correct the request, seek approval, wait for a dependency, or stop.
Related reading
- agent-systems
- trust-boundary
- runtime-infra
Keep exploring