Skip to content

Miasyster/claude-code-governance

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

verdikt

Governance is runtime enforcement, not prompt suggestion.

sys.exit(1) > "please don't."


The Problem

AI agents can read any file, run any command, modify any code. When you write "don't touch the auth module" in a system prompt, you're making a suggestion. The model can — and will — ignore it.

No amount of prompt engineering creates a hard boundary. Prompt-level instructions are suggestions that fail silently.

The Architecture

verdikt provides a governance architecture for AI agents, built on one core thesis:

Every agent action must pass through a policy evaluation graph that returns machine-enforceable verdicts. A blocked verdict stops the action. No negotiation, no override, no prompt injection.

One Primitive

Everything in verdikt is built from a single primitive:

Guard: check(Action) → Verdict

A Guard inspects an Action (what the agent wants to do) and returns a Verdict (pass, block, or warn). That's it.

Composable Policy Graph

Guards are leaf nodes. Combinators compose them into a decision tree:

All(                                    # AND — all must pass
    All(                                #   Layer 1: BOUNDARY (fail fast)
        FileBoundaryGuard(),
        BashSafetyGuard(),
        short_circuit=True,
    ),
    BudgetGuard(max_tasks=100),         #   Layer 2: RESOURCE
    When(                               #   Layer 3: INTENT (conditional)
        condition=lambda a, ctx: a.task_type == "deploy",
        then=Escalate(                  #     strict check → fallback
            DeployChecklist(),
            AutoApprove(max_risk=0.3),
        ),
        otherwise=DefaultIntent(),
    ),
)

Six combinators cover all governance composition patterns:

Combinator Semantics Use case
All AND — all children must pass Layer grouping, multi-check
Any OR — one child pass suffices Alternative approval paths
Not Invert verdict Deny-list from allow-list
When Conditional branch Route by action type, risk, env
Threshold N-of-M must pass Quorum / multi-reviewer
Escalate Primary → fallback on block Strict check with override path

GovernanceContext — State Flows Through the Graph

Unlike a flat middleware stack, verdikt's policy graph carries state:

GovernanceContext:
    action          # the current action
    verdicts[]      # accumulated leaf verdicts
    annotations{}   # inter-guard communication blackboard
    risk_score      # dynamic [0,1] — guards can increment
    session         # cross-action state (action count, block count, history)
    trace[]         # auto-recorded evaluation path

Early guards can write annotations that later guards read. Risk scores accumulate. Session state persists across evaluations — enabling progressive policies like "block after 3 consecutive sensitive-file accesses."

Two Cut Points

Governance intersects with agent execution at exactly two points:

Agent Runtime                         Governance
─────────────                         ──────────

  Agent wants to act ──────────────→  engine.evaluate(action)
                                        │
                     ←── pass ──────────┤  → proceed
                     ←── block ─────────┤  → stop + fix suggestion

  Agent acted ─────────────────────→  engine.post_action(action, outcome)
                                        │
                                        └── record to session + trace

Any system that can call two functions can integrate governance.

Quick Start

from verdikt import Action, ActionKind, PolicyEngine, All
from verdikt.guards import BashSafetyGuard, FileBoundaryGuard

engine = PolicyEngine(
    All(FileBoundaryGuard(), BashSafetyGuard(), short_circuit=True)
)

result = engine.evaluate(Action(kind=ActionKind.BASH, payload={"command": "rm -rf /"}))
print(result.passed)   # False
print(result.reason)   # "Command matches blocked pattern: rm\s+-rf\s+/"

With Conditional Policies

from verdikt import Action, ActionKind, PolicyEngine, All, When, Escalate

engine = PolicyEngine(
    All(
        BashSafetyGuard(),
        When(
            condition=lambda a, ctx: a.kind == ActionKind.BASH,
            then=All(FileBoundaryGuard(), BashSafetyGuard(), name="strict"),
            otherwise=FileBoundaryGuard(),  # read/write only need boundary
        ),
    )
)

With Session Tracking

from verdikt import PolicyEngine, All, InMemorySessionStore
from verdikt.guards import BashSafetyGuard

engine = PolicyEngine(
    All(BashSafetyGuard()),
    session_store=InMemorySessionStore(),  # or SQLiteSessionStore()
)

# Session state accumulates across evaluations
result = engine.evaluate(Action(kind=ActionKind.BASH, session_id="s1", payload={"command": "ls"}))
print(result.context.session.action_count)  # 1

With Tracing

from verdikt import PolicyEngine, All, StdoutTracer, CollectingTracer

# Print trace to stderr
engine = PolicyEngine(All(BashSafetyGuard()), tracer=StdoutTracer())

# Or collect traces programmatically
tracer = CollectingTracer()
engine = PolicyEngine(All(BashSafetyGuard()), tracer=tracer)
engine.evaluate(action)
print(tracer.traces)  # [(action, [TraceEntry, ...])]

Core Concepts

Concept What it is
Action What an agent wants to do (read, write, bash, execute_code, submit_task)
Guard Leaf node — check an action, return a verdict
Verdict Pass, block, or warn — with reason and fix suggestion
PolicyNode Any node in the policy graph (Guard or combinator)
All / Any / Not / When / Threshold / Escalate Combinators — compose guards into a decision tree
GovernanceContext State that flows through the graph during evaluation
PolicyEngine Evaluates the policy graph, manages sessions and tracing

Built-in Guards

Guard Layer What it checks
FileBoundaryGuard BOUNDARY Path allowlist/blocklist, red-line file protection
BashSafetyGuard BOUNDARY Regex pattern blocking, prefix blocking, allowlist exceptions
LayerIsolationGuard BOUNDARY Cross-layer file access in multi-layer projects
SandboxGuard BOUNDARY AST scan for dangerous code patterns
BudgetGuard RESOURCE Session-scoped resource limits (thread-safe)
IntentGuard INTENT Business rule validation for task submissions
AuditGuard OBSERVE Records every action (always passes, never blocks)

Writing Custom Guards

Implement the protocol — name + check():

from verdikt import Action, Verdict, BaseGuard, GovernanceContext
from verdikt.layers import GovernanceLayer

class RateLimitGuard(BaseGuard):
    governance_layer = GovernanceLayer.RESOURCE

    def __init__(self, max_per_minute: int):
        self._calls = []
        self._max = max_per_minute

    @property
    def name(self) -> str:
        return "rate_limit"

    def check(self, action: Action, ctx: GovernanceContext) -> Verdict:
        import time
        now = time.time()
        self._calls = [t for t in self._calls if now - t < 60]
        self._calls.append(now)

        if len(self._calls) > self._max:
            ctx.add_risk(0.3)  # signal risk to downstream guards
            return Verdict.block(guard=self.name, reason="Rate limited",
                                fix=f"Max {self._max} calls/min. Wait and retry.")
        return Verdict.pass_(guard=self.name)

Or without inheritance — just satisfy the protocol:

class MyGuard:
    @property
    def name(self) -> str:
        return "my_guard"

    def check(self, action):
        return Verdict.pass_(guard=self.name)

Both old (check(action)) and new (check(action, ctx)) signatures work — the engine auto-detects.

Integrating with Agent Runtimes

from verdikt import PolicyEngine, All
from verdikt.guards import BashSafetyGuard

engine = PolicyEngine(All(BashSafetyGuard()))

# Before the agent acts:
result = engine.evaluate(action)
if not result.passed:
    return result.reason  # block

# After the agent acts:
from verdikt import Outcome
engine.post_action(action, Outcome(success=True))

Claude Code Integration

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Glob|Grep",
        "command": "python -m verdikt.adapters.claude_hooks read \"$TOOL_INPUT\""
      },
      {
        "matcher": "Edit|Write",
        "command": "python -m verdikt.adapters.claude_hooks write \"$TOOL_INPUT\""
      },
      {
        "matcher": "Bash",
        "command": "python -m verdikt.adapters.claude_hooks bash \"$TOOL_INPUT\""
      }
    ]
  }
}

Configuration

Guards accept config via constructor or governance.yaml:

boundaries:
  allowed_dirs: [src, tests, docs]
  red_line_files: [src/auth/handler.py]

bash_safety:
  blocked_patterns:
    - 'rm\s+-rf\s+/'
    - 'DROP\s+TABLE'

Design Principles

  1. Exit codes are enforcement. sys.exit(1) blocks a tool call. No prompt engineering overrides it.
  2. One primitive. Guard: check(Action) → Verdict. Everything else is composition.
  3. Policy graphs, not middleware stacks. Combinators (All, Any, When, Escalate, ...) compose guards into decision trees with branching, fallback, and quorum logic.
  4. State flows through the graph. GovernanceContext carries verdicts, annotations, risk scores, and session history — enabling inter-guard communication and progressive policies.
  5. Two cut points. Pre-action check + post-action record. That's the entire integration surface.
  6. Fail with fix instructions. Every block tells the agent exactly what to do instead.
  7. Protocol, not inheritance. Implement name + check() and you're a Guard.
  8. Runtime-agnostic. The core framework has zero coupling to any specific agent runtime.

Architecture

verdikt/
├── primitives.py        # Action, Verdict, Guard protocol
├── context.py           # GovernanceContext, SessionState, TraceEntry
├── nodes.py             # PolicyNode, All, Any, Not, When, Threshold, Escalate
├── engine.py            # PolicyEngine, EvaluationResult, Tracers
├── session.py           # SessionStore protocol + InMemory/SQLite
├── layers.py            # Four-layer governance model (BOUNDARY→RESOURCE→INTENT→OBSERVE)
├── config.py            # governance.yaml loading
├── guards/              # Built-in Guard implementations
│   ├── file_boundary.py #   Layer 1: BOUNDARY
│   ├── bash_safety.py   #   Layer 1: BOUNDARY
│   ├── layer_isolation.py#  Layer 1: BOUNDARY
│   ├── sandbox.py       #   Layer 1: BOUNDARY
│   ├── budget.py        #   Layer 2: RESOURCE
│   ├── intent.py        #   Layer 3: INTENT
│   └── audit.py         #   Layer 4: OBSERVE
├── storage.py           # SQLite backends (audit + budget)
├── pipeline.py          # Legacy linear pipeline (still works)
├── enforcement.py       # Legacy Enforcer (still works)
└── adapters/            # Runtime adapters
    └── claude_hooks.py  #   Claude Code PreToolUse bridge

Requirements

  • Python 3.10+
  • pydantic >= 2.0
  • pyyaml (optional, for governance.yaml)

License

Apache 2.0

About

Runtime governance framework for Claude Code AI agents. Enforce boundaries, validate intent, and structure autonomous workflows — through code, not prompts.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages