Skip to content

How-to guide

Draft

Define 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.

  1. Submit a normal allowed request. Confirm the executor accepts the matching unexpired decision.
  2. Submit each deny condition separately. Confirm the reason code identifies the failed rule.
  3. Remove a required fact. Confirm the decision is indeterminate and no side effect occurs.
  4. Reuse an allow decision with a changed amount or target. Confirm the digest mismatch blocks execution.
  5. Replay a consumed single-use decision. Confirm the executor rejects it.
  6. Stop the gate or policy store. Confirm the executor does not fall back to an unchecked path.
  7. Attempt the side effect with the agent’s own credentials. Confirm the target system denies it.
  8. 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.

Updated 2026-08-13by Tim
  • agent-systems
  • trust-boundary
  • runtime-infra

Keep exploring

Keep building the workflow