How it works

The policy chokepoint, in 3 steps.

LLM proposes. Policy gate decides. Audit log records — with verbatim policy clause text on every event.

closegate policy gate flow General ledger, subledger, and bank feed enter the policy gate from the left. The gate branches every state-changing call to an append-only audit log and a human-in-the-loop approval envelope before settling to downstream systems. General ledger GL entries Subledger AR · AP · payroll Bank feed Plaid · Mercury Policy gate T0 · T1 · T2 · T3 SoD · materiality verbatim clauses Append-only audit log SQLite triggers HITL approval envelope approver ≠ proposer NetSuite posted journals Slack · Teams approval bots Bank ACH dual-HITL · T3

1. The LLM proposes

Your AI client (Claude Desktop, Cursor, OpenAI Apps SDK, or any MCP-compliant agent) calls one of closegate's 19 finance tools. Tools are tier-routed:

T0 Read-only · auto T1 Reversible · auto + audit T2 HITL required T3 Dual HITL · irreversible

The LLM never sets its own actor identity. The MCP transport binds X-Actor-Id from the authentication backend (OIDC token or reverse-proxy header). The LLM's chat history can't override the transport.

2. The policy gate decides

Every state-changing call passes through closegate_policy.gate.evaluate() — a pure Python function that returns one of three decisions:

  • Allow: tier is T0/T1 below materiality, no sensitive accounts, no SoD violation. The call proceeds; an audit row is written.
  • RequireHumanApproval(clause): tier is T2, or the action touches a sensitive account, or it's above materiality. The call is queued in the HITL inbox with the verbatim policy clause text. A human with a different actor identity confirms it.
  • Deny(clause): SoD violation, missing rationale on a required field, or an explicit policy-block. The call fails; a POLICY_VIOLATION event is written.

The gate has no I/O. No global state. No external calls. It's deterministic given its inputs, which makes it unit-testable and replay-able.

3. The audit log records — at the database layer

Every decision lands in an append-only SQLite table. The append-only invariant is enforced by SQLite triggers, not by application code:

CREATE TRIGGER audit_no_update
BEFORE UPDATE ON audit_events
BEGIN
  SELECT RAISE(ABORT, 'audit_events is append-only');
END;

CREATE TRIGGER audit_no_delete
BEFORE DELETE ON audit_events
BEGIN
  SELECT RAISE(ABORT, 'audit_events is append-only');
END;

The result: an insider with code-write access to the application layer cannot mutate prior events. The trigger fires at the DB level. The chain is also hash-evidenced; closegate-engine audit verify exits non-zero on any inconsistency.

What this gives you

  • Replay-able decisions. Anyone with the audit log + the git history of policy.yaml can reconstruct any decision made in any prior period.
  • Verbatim clause attribution. Every blocked event carries the exact text of the rule that fired, plus a JSON-pointer source. Auditors quote it; you don't paraphrase.
  • Tier-routed automation. The agent runs full-speed on T0/T1; T2 routes to a human; T3 routes to dual-HITL with full SoD chain. No "should we ask" logic anywhere else.
  • LLM-agnostic. Bring Claude, GPT-4, Gemini, or open-weight models. The policy gate doesn't care.
  • Reproducible. The eval harness produces a JSON artifact suitable for SOC 2 CC4.2 evidence on every nightly CI run.

What's underneath

Three Python packages: closegate-policy (the gate + money + calendar + FSM primitives), closegate-engine (the MCP server + reconciliation + AP workflows + audit store), closegate-agent (the FastAPI loop + HITL inbox + OIDC SSO). Plus a Vue 3 workspace UI for the human side of the HITL queue.

All three packages are on PyPI. All four Docker images are on GHCR. The MCP server is in the official registry. Install paths →

The full technical reference

The architecture deep-dives — ADRs, FSM reference, MCP tool surface, threat model, concurrency model — live in the technical docs. Start with the architecture overview, then ADR-0002 (policy chokepoint), then ADR-0004 (two-database boundary).

Inbound

Talk to the maintainer

Two design-partner slots open this quarter. One real workflow, your real policy.yaml, monthly 30-min call, direct line. Apache-2.0, self-hosted, no seat licensing — forever.