A policy gate is the single transactional chokepoint every state-changing action by an AI agent passes through. It returns one of three decisions: Allow, RequireHumanApproval, or Deny. That’s the entire interface. Everything that makes the pattern defensible — segregation of duties enforcement, materiality routing, sensitive-account flagging, verbatim audit-clause attribution — is implementation detail underneath those three return types.

This article walks the pattern, why it matters specifically for finance AI agents, and why open-sourcing the chokepoint is the unlock for getting AI agents past your audit committee.

The problem the policy gate solves

Your AI agent can recognize 95% of GL/SL matches in seconds. It can propose invoice codings. It can flag duplicate vendors. None of that is the hard part.

The hard part is what happens when the agent goes to actually do something. Confirm a $40K match. Approve an invoice for payment. Submit a payment run to the bank. The audit committee question lands instantly: who authorized this, against what rule, with what materiality threshold, with which actor identity?

Without a policy gate, the answer is buried in your application code. “We have a confirmMatch() function that calls policyCheck() which calls auditService.log().” Three indirections; each is a place to argue what happened; each is a place the next refactor could quietly weaken. Your auditor doesn’t trust this; your CISO doesn’t trust this; you can’t reason about it without reading the entire codebase.

With a policy gate, the answer is one function. Every state-changing call routes through evaluate(). The function is pure (no I/O, no global state), deterministic, ~200 lines, and unit-tested in isolation. There is no second confirmation API. Adding a new MCP tool requires a tier annotation; the annotation is the routing rule.

That property — one chokepoint, no escape hatch — is what makes the pattern audit-defensible.

The three properties of a defensible policy gate

1. It’s a pure function

evaluate(action, match, actor, accounts, rationale, config)Allow | RequireHumanApproval(clause) | Deny(clause).

No I/O. No global state. No async. Deterministic given inputs. The function lives in closegate_policy.gate and is ~200 lines. It’s unit-testable in isolation; you can run all 21 deterministic policy-enforcement scenarios in under a second without an LLM or a database.

Pure functions are easier to audit because the audit reduces to “what does this function compute given these inputs?” There’s no question of which other code paths might be reached, what global state might mutate, what side effects might fire. The function is the entire decision.

2. It’s the only mutation API

Every workflow transition — match confirm, AP approve, payment submit, period close — routes through the same gate. Not “a gate per workflow.” One gate, one set of decision rules, one place to read the policy.yaml that drives them.

This is the property that makes the audit committee comfortable. They don’t have to verify N different chokepoints; they verify one, and trust that the engineering convention prevents anyone from adding a second.

The convention is enforced in code: adding a new MCP tool requires a @register_tool(tier=Tier.T2, name="my_tool") decorator. The tier annotation is enforced at registration time — the decorator fails if you don’t specify one. The runtime checks the tier on every invocation. There’s no path to ship a tool that bypasses the gate.

3. It carries verbatim policy clause text

When the gate denies or routes to HITL, the resulting audit event carries the exact text of the rule from your policy.yaml, plus a JSON-pointer to the rule. The external auditor reads the audit log and quotes the text verbatim in the PBC bundle. No vendor-mediated translation layer between what the rule actually says and what the auditor sees.

This sounds minor; in practice it’s load-bearing. Vendor audit logs typically record something like event_type: BLOCKED, reason: MATERIALITY_EXCEEDED, materiality: 10000. That’s information but it’s not evidence — the auditor still has to ask the vendor “what was the actual rule wording?” and trust the vendor’s answer.

With verbatim clauses, the audit row is the evidence. The text is the rule. The rule is in git. Anyone can replay any decision.

The four-tier reversibility model

Inside the gate, every action gets routed by a tier — closegate uses the NIST AI RMF Agentic Profile classification:

  • T0 Read-only · auto — read-only or propose-only actions. Always automatic. No HITL, no audit row beyond a normal access log.
  • T1 Reversible · auto + audit — reversible changes below the materiality threshold. Automatic with an audit row + verbatim clause.
  • T2 HITL required — reversible changes above materiality or against a sensitive account. HITL required. SoD enforced server-side.
  • T3 Dual HITL · irreversible — irreversible (payment submission, period close, journal post). Dual HITL required: requestor ≠ approver ≠ payer.

The tier model is the answer to “how much do we trust the agent?” — and the answer is it depends on what action it wants to take. T0 actions run full speed; T3 actions require three humans. That gradient is what lets the agent move fast on the 95% and your team focus on the 5% that has real judgment cost.

Why the gate has to be open-source

Three reasons:

Audit defensibility. Your external auditor reads the gate code as part of the SOX 404 walkthrough. If the gate is in a vendor binary, the auditor takes the vendor’s word for it — which means your audit committee takes the vendor’s word for it, which means a single vendor incident can shake your audit confidence.

Threat-model legibility. Your CISO needs to reason about prompt injection, LLM-impersonating-human, and audit-log tampering. None of that is reasonable if the chokepoint code is closed; the security review board will ask, and “the vendor says it’s secure” will fail the review.

Architectural reusability. Different finance teams have different policy shapes. A multi-entity holdco with USD/JPY/INR materiality needs per-entity overrides; a SOX-public-company needs strict period-close locks; a fintech needs different sensitive-account categories. With an open gate, each team forks the starter policy.yaml and adapts. With a closed gate, each team negotiates with the vendor.

What this means in practice

If you’re scoping an AI agent for close, recon, or AP work:

  1. Look at any candidate framework or vendor and ask: where’s the chokepoint?
  2. If it’s in the prompt — “we tell the LLM not to do X” — that’s not a chokepoint. That’s prompt engineering.
  3. If it’s in application code spread across three modules — “we have a confirmMatch() that calls policyCheck() which…” — that’s also not a chokepoint. That’s a refactor waiting to break.
  4. If it’s a single function with verbatim clause attribution, server-side actor identity binding, and explicit tier annotations on every tool — that’s a chokepoint. Now check whether you can read its source.

The policy gate as a pattern is older than closegate. The novelty is shipping it open, in a form that’s load-bearing in production, with the verbatim-clause-text attribution that auditors specifically need. That’s the bet.