Skip to content

How TapPass works

Every action an agent takes — an LLM call, a tool call, a write to a system of record — passes through one decision point: the governance kernel. The kernel is a pure function: given the facts about the action and the agent’s compiled policy, it returns a verdict. Deterministic, replayable, fail-closed.

facts compiled policy
│ │
▼ ▼
agent action ──▶ build input ──▶ evaluate ──▶ verdict + obligations ──▶ audit record
(Behavior) (5 axes) (in-process) allow / block (signed &
hash-chained)
decision = await govern(ctx, behavior) # decide only
result = await enforce(ctx, behavior, execute=call_llm) # decide → run → re-decide
  • govern evaluates a Behavior (an intended action) against the agent’s policy and returns a Decision. Nothing executes.
  • enforce is the decide→execute→decide sandwich: govern the request, run the action only if allowed, then govern the response before it reaches the agent. Blocked actions never execute; unsafe responses are withheld.

Policy is evaluated in-process per call — no network hop in the hot path.

Validate → collect the facts the policy needs → evaluate → record.

On allow, the kernel also mints a mandate: a short-lived signed token naming exactly what was authorized (which model, which tool), so downstream executors can verify the decision offline.

The kernel is embedded in the TapPass server and reached through any of its surfaces:

Surface How actions arrive
LLM gateway POST /v1/chat/completions, /v1/messages, /v1/embeddings — your existing SDK, one env var
Govern API POST /v1/govern — pre-execution check for any action, from any language
Tool execution POST /v1/tools/govern, /v1/tools/execute — governed tool calls with mandates
MCP TapPass as MCP server bridging upstream servers; every tool call governed
IDE hooks e.g. Claude Code PreToolUse hook → /v1/govern
Python SDK Agent, tappass.govern(), framework guards — all call the same endpoints

Same kernel, same policies, same audit trail — regardless of which surface the action came through.

  • No compiled policy → the call is blocked (no_policy_compiled). There is no hidden default-allow.
  • A policy bundle that can’t be read or evaluated → blocked.
  • A fact producer that fails → blocked.
  • Every block traces to a specific rule in a specific policy version — never to a hardcoded floor you can’t inspect.