DEV Community

Richard Okonicha
Richard Okonicha

Posted on • Originally published at github.com

Deterministic Agentic Governance: Bringing Order to Autonomous Software Engineering

Deterministic Agentic Governance: Bringing Order to Autonomous Software Engineering

How we built a framework that makes AI agents reliable enough for production


The Problem: Agents Don't Play Nice at Scale

You've seen the demo. An AI agent writes a feature, runs tests, commits to git. Magic.

Then you try it on a real codebase. Ten tasks in, the agent hallucinates an API. Fifteen tasks in, it forgets the architectural constraints you gave it in the first prompt. Twenty tasks in, you're manually reverting broken commits and wondering why you didn't just write the code yourself.

The math is brutal. If each step has a 95% success rate (optimistic), a 20-step task chain succeeds only 36% of the time:

P(success) = 0.95^n

n=5   → 77%
n=10  → 60%
n=20  → 36%
n=50  → 8%
Enter fullscreen mode Exit fullscreen mode

This is compositional decay—the exponential failure rate of chaining probabilistic operations.


Why Current Approaches Fail

1. Context Rot

Every conversation turn adds to the LLM's context window. After 20 turns:

  • Initial instructions get diluted (attention thinning)
  • Error logs pollute the context (cascade pollution)
  • The agent "forgets" constraints it was given initially

2. No Objective Verification

Asking an LLM to review its own code is like asking a student to grade their own exam. Hallucinated "looks good to me" responses are the norm.

3. No Safety Boundaries

Agents with shell access can rm -rf /, commit secrets, or open PRs to production branches.

4. Human-in-the-Loop Doesn't Scale

Manual approval for every step kills velocity. But no approval means broken code in main.


The Insight: Treat the LLM as a CPU, Not a Brain

What if we stop treating the LLM as an autonomous decision-maker and start treating it as a stateless processing unit—like a CPU?

The CPU doesn't manage memory, enforce permissions, or decide which program runs next. The operating system kernel does that.

So we built a governance kernel around the LLM.


The Three Pillars of Deterministic Governance

Pillar 1: Persistent State Ledger (PROGRESS.json)

Instead of stuffing task state into the LLM's context, we externalize it:

{
  "version": "1.0.0",
  "projectName": "electron-to-tauri-migration",
  "tasks": [
    {
      "id": "TASK-003",
      "category": "DATA_STORAGE",
      "referencePath": "electron/store/index.ts",
      "targetPath": "src-tauri/src/storage/mod.rs",
      "status": "PENDING",
      "retryCount": 0,
      "maxRetries": 5,
      "verificationLogs": []
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Every agent turn gets a fresh, clean context containing only:

  • The single task specification
  • The reference code (read-only)
  • Acceptance criteria

No history. No error logs from previous tasks. No instruction dilution.

Pillar 2: Declarative Permission Sandboxing

Before any code runs, the governance kernel enforces boundaries via policy-as-code:

{
  "readOnlyPaths": ["src/**", "package.json", "Cargo.toml"],
  "writeOnlyPaths": ["src/**", "tests/**", "dist/**"],
  "forbiddenPaths": [".git/**", ".env*", "*.key", "secrets/**"],
  "allowedCommands": ["npm", "cargo", "tsc", "eslint", "git"],
  "forbiddenCommands": ["rm -rf", "sudo", "curl", "wget", "ssh"],
  "networkAccess": false,
  "maxExecutionTimeMs": 300000,
  "maxMemoryMb": 1024
}
Enter fullscreen mode Exit fullscreen mode

This isn't a suggestion. It's enforced at the OS level via environment variables and command validation.

Pillar 3: Compiler Verification Gates

No LLM self-evaluation. Only binary, deterministic gates:

Gate Tools Purpose
TYPE_CHECK tsc --noEmit, cargo check Type safety & compilation
SYNTAX_CHECK prettier --check, cargo fmt --check Formatting & syntax
AST_LINT eslint, cargo clippy Static analysis
ANTI_STUB Regex/AST patterns Detect TODO, unimplemented!(), empty blocks
TEST_SUITE npm test, cargo test Functional correctness

If any gate fails, the task is retried with the compiler error fed back to a fresh agent context. After maxRetries, the workspace is git-reset to the last clean commit.


The Governance Loop

while pending_tasks_exist():
    task = ledger.get_next_pending()

    base_commit = git.current_commit()

    for attempt in range(task.maxRetries + 1):
        if attempt > 0:
            git.reset_hard(base_commit)  # Clean slate
            ledger.increment_retry(task.id)

        # Fresh context, single task
        context = build_clean_context(task, reference_code)
        result = agent.execute(context)

        if not result.success:
            continue

        # Objective verification
        verification = run_all_gates(task.category)

        if verification.all_passed():
            git.commit(task.id, task.description)
            ledger.mark_completed(task.id)
            break
        else:
            ledger.log_verification_failures(task.id, verification)

    if not verification.all_passed():
        ledger.mark_failed(task.id)
        git.reset_hard(base_commit)
Enter fullscreen mode Exit fullscreen mode

Result: Zero context rot. Deterministic verification. Atomic commits. Full audit trail.


Real-World Example: Electron → Tauri 2.0 Migration

We migrated a production desktop app (100k+ lines) from Electron/Node to Tauri 2.0 (Rust + React + SQLite).

10 coordinated tasks:

Task Category Verification Gates
Tauri main.rs entry point INFRASTRUCTURE TYPE, SYNTAX, LINT, ANTI_STUB
IPC layer replacement INFRASTRUCTURE TYPE, SYNTAX, LINT, ANTI_STUB
Hybrid SQLite + Markdown storage DATA_STORAGE TYPE, SYNTAX, LINT, ANTI_STUB, TEST
Non-blocking token streaming (10Hz) LLM_INFERENCE TYPE, SYNTAX, LINT, ANTI_STUB
React virtualization (@tanstack/react-virtual) UI_COMPONENT TYPE, SYNTAX, LINT, ANTI_STUB
Integration tests TESTING TYPE, SYNTAX, LINT, TEST
Build config & documentation DOCUMENTATION SYNTAX

Results:

  • 3 tasks failed TYPE_CHECK on first attempt → auto-retried with compiler errors → passed
  • 1 task failed ANTI_STUB (empty function body) → auto-retried → passed
  • All 10 committed atomically with structured messages
  • Full audit trail in PROGRESS.json + git history

Enterprise Governance: Three-Tier Authorization

Not all actions are equal. The framework enforces a tiered model:

Tier Actions Approval
Autonomous Read code, create branches, run type checks, format files Pre-approved
Semi-Autonomous Open PRs, modify schemas, update dependencies Human sign-off
Forbidden Commit to protected branches, export secrets, unsandboxed network Hard-blocked

Compliance alignment:

  • ISO/IEC 42001 (AI management systems)
  • NIST AI RMF (Risk management)
  • IEEE 7000 (Ethical system design)
  • SFIA 9 (Competency framework)

Supported Agents

The framework is agent-agnostic. Built-in adapters:

// Claude Code (Anthropic)
{ type: 'claude-code', command: 'claude', args: ['-p', '--bare', '--dangerously-skip-permissions'] }

// Aider
{ type: 'aider', command: 'aider', args: ['--no-git', '--yes', '--message'] }

// OpenCode
{ type: 'opencode', command: 'opencode', args: ['run', '--headless'] }

// Kilo Code
{ type: 'kilo', command: 'kilo', args: ['run', '--headless'] }

// Custom - implement AgentAdapter interface
Enter fullscreen mode Exit fullscreen mode

Getting Started

# Install
npm install deterministic-agentic-governance

# Initialize in your workspace
npx govern init --workspace ./my-project --project my-migration

# Define tasks (JSON)
cat > tasks.json << 'EOF'
[{
  "id": "TASK-001",
  "category": "UI_COMPONENT",
  "referencePath": "src/components/OldButton.tsx",
  "targetPath": "src/components/NewButton.tsx",
  "description": "Migrate button to new design system",
  "acceptanceCriteria": ["TypeScript compiles", "No stubs", "Passes ESLint"],
  "dependencies": [],
  "maxRetries": 3,
  "timeoutMs": 180000,
  "metadata": {}
}]
EOF

# Add and run
npx govern add --file tasks.json
npx govern run
npx govern status
Enter fullscreen mode Exit fullscreen mode

When to Use This (And When Not To)

✅ Ideal For

  • Large-scale migrations (framework ports, language rewrites)
  • Automated refactoring (design systems, strict TypeScript, dead code)
  • Cross-platform code generation (OpenAPI → clients, schema → ORM)
  • Compliance-required environments (fintech, healthcare, defense)
  • CI/CD automation (dependency updates, security patches)

❌ Not Ideal For

  • Quick prototyping → Use Cursor/Claude Code directly
  • Single-file edits → IDE copilot
  • Exploratory coding → Unconstrained agent
  • Learning → Notebook/REPL

The Key Differentiator

Every task either passes all compiler/linter gates and commits atomically, or rolls back cleanly with full diagnostic capture. No context rot. No hallucinated verifications. No partial broken states.


Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│                     GOVERNANCE ORCHESTRATOR                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │
│  │   LEDGER    │  │  SANDBOX    │  │ VERIFICATION│              │
│  │  MANAGER    │  │  ENFORCER   │  │ ORCHESTRATOR│              │
│  └─────────────┘  └─────────────┘  └─────────────┘              │
│         │               │                │                       │
│         ▼               ▼                ▼                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    AGENT ADAPTERS                        │    │
│  │  ┌──────────┐ ┌────────┐ ┌──────────┐ ┌──────────────┐  │    │
│  │  │Claude Code│ │ Aider  │ │ OpenCode │ │   Kilo Code  │  │    │
│  │  └──────────┘ └────────┘ └──────────┘ └──────────────┘  │    │
│  └─────────────────────────────────────────────────────────┘    │
│         │                                                       │
│         ▼                                                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                     GIT MANAGER                          │    │
│  │  Atomic commits • Auto-rollback • Branch management      │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Resources


Conclusion

The industry is shifting from writing code to governing agents. But governance without determinism is just chaos with better marketing.

This framework proves that you can have autonomous execution with deterministic guarantees. The LLM does what it's good at (code generation from context). The governance kernel does what it's good at (state, permissions, verification, rollback).

The result: AI agents you can actually trust in production.


Published as part of the Deterministic Agentic Governance Framework v1.0.0. MIT License.

Top comments (0)