policy · architecture · 6 min read

Why the Policy Engine has to be a pure function

Spend authorization that an LLM made is fundamentally unauditable. We trace the same input twice and get different decisions. Pure-function policy is the only path to a decision auditor can sign off on.

By Hekate Gate Team · 2026-04-18· Markdown
Want to check a real agent workflow?

Paste a manifest into the public demo or bring a scoped workflow to the private beta.

The temptation to use LLMs for policy

It feels obvious to ask the LLM 'is this spend OK?' alongside 'what should the agent do next?'. They're both judgment calls. The LLM is right there.

And the LLM will reliably say yes or no — until two months later, an audit asks why the agent approved a $200 spend on a Tuesday. We can't replay the decision: same prompt, different output, different reasoning trace.

Pure-function policy: the rule

Mission Control's Policy Engine takes a typed PolicyDecisionRequest and a typed PolicyEvaluationContext, and returns a typed PolicyDecision. No I/O. No async. No LLM. Same inputs always return the same output.

That's testable: every decision branch has a unit test. That's hashable: the decision goes into the audit ledger as a fixed-shape event. That's auditable: a SOC 2 reviewer can read evaluatePolicy() and reason about every possible decision in finite time.

What you give up

You give up nuance. The Policy Engine can't say 'this spend looks weird, ask the human'. It can say 'this spend triggers an approval rule, ask the human' — but the rule has to be configured up front. There's no in-context judgment.

That's the right trade. Nuance lives in the approval queue. The deterministic rule decides yes/no/escalate; the human resolves the escalations. The audit trail records both layers.

Related reading

Read more