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:
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.yamlcan 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).