rements, including the EU AI Act Article 19 (6-month retention for high-risk systems) and Article 99 (penalties up to €15M or 3% global turnover). The governance layer doesn't restrict what agents can do; it ensures every action is evaluated, authorized, logged, and reversible.
Core Solution
Building a production-ready agent control plane requires three distinct components: a deterministic policy evaluator, a risk-tiered approval queue, and an immutable audit ledger. The architecture routes every tool invocation through this gate before execution.
Step 1: Define Action Taxonomy & Risk Tiers
Not all tool calls carry equal weight. Classify actions by impact rather than tool type. A database SELECT is low risk. A DELETE or UPDATE without a WHERE clause is high risk. An external API call modifying customer data is critical.
export enum RiskTier {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
CRITICAL = 'critical'
}
export interface ActionRequest {
agentId: string;
toolName: string;
parameters: Record<string, unknown>;
timestamp: number;
sessionId: string;
}
Step 2: Implement the Deterministic Policy Engine
The policy engine must be stateless and rule-based. It evaluates the ActionRequest against a configuration matrix. Never delegate policy evaluation to the LLM.
export interface PolicyRule {
toolPattern: string;
allowedTiers: RiskTier[];
parameterConstraints: Record<string, (value: unknown) => boolean>;
requiresApproval: boolean;
}
export class PolicyEvaluator {
private rules: PolicyRule[];
constructor(rules: PolicyRule[]) {
this.rules = rules;
}
evaluate(request: ActionRequest): { allowed: boolean; tier: RiskTier; requiresApproval: boolean } {
const matchingRule = this.rules.find(r =>
new RegExp(r.toolPattern).test(request.toolName)
);
if (!matchingRule) {
return { allowed: false, tier: RiskTier.HIGH, requiresApproval: true };
}
const paramViolations = Object.entries(matchingRule.parameterConstraints)
.filter(([key, validator]) => !validator(request.parameters[key]));
if (paramViolations.length > 0) {
return { allowed: false, tier: RiskTier.HIGH, requiresApproval: true };
}
return {
allowed: true,
tier: matchingRule.allowedTiers[0],
requiresApproval: matchingRule.requiresApproval
};
}
}
Step 3: Build the Approval Queue
High and critical risk actions enter an async queue. The agent pauses, the request is routed to a human reviewer or automated compliance check, and execution resumes only upon explicit approval.
import { EventEmitter } from 'events';
export class ApprovalQueue extends EventEmitter {
private pending: Map<string, ActionRequest> = new Map();
async submit(request: ActionRequest): Promise<string> {
const ticketId = `${request.sessionId}-${Date.now()}`;
this.pending.set(ticketId, request);
this.emit('pending_approval', { ticketId, request });
return ticketId;
}
async resolve(ticketId: string, approved: boolean): Promise<boolean> {
const request = this.pending.get(ticketId);
if (!request) throw new Error('Ticket not found');
this.pending.delete(ticketId);
this.emit('approval_resolved', { ticketId, approved, request });
return approved;
}
}
Step 4: Attach Cryptographic Audit Logging
Every evaluation, approval, and execution outcome must be recorded in an append-only structure. Hash chaining ensures tamper evidence.
import { createHash } from 'crypto';
export interface AuditEntry {
ticketId: string;
action: ActionRequest;
policyResult: { allowed: boolean; tier: RiskTier };
approvalStatus: 'pending' | 'approved' | 'rejected';
executionResult: unknown;
previousHash: string;
currentHash: string;
timestamp: number;
}
export class ImmutableLedger {
private chain: AuditEntry[] = [];
append(entry: Omit<AuditEntry, 'currentHash'>): AuditEntry {
const payload = JSON.stringify(entry);
const currentHash = createHash('sha256').update(payload + entry.previousHash).digest('hex');
const fullEntry = { ...entry, currentHash };
this.chain.push(fullEntry);
return fullEntry;
}
verifyIntegrity(): boolean {
for (let i = 1; i < this.chain.length; i++) {
const prev = this.chain[i - 1].currentHash;
if (this.chain[i].previousHash !== prev) return false;
}
return true;
}
}
Architecture Rationale
- Deterministic over Probabilistic: LLMs optimize for token prediction, not constraint satisfaction. A rule engine guarantees consistent enforcement regardless of prompt context or model version.
- Separation of Concerns: Orchestration handles state and routing. Governance handles safety. This allows policy updates without redeploying agent logic.
- Async Approval: Synchronous blocking kills agent throughput. An event-driven queue preserves velocity while enforcing oversight for high-impact actions.
- Hash-Chained Audits: Regulatory frameworks require tamper-evident logs. SHA-256 chaining with append-only storage satisfies compliance without heavy infrastructure.
Pitfall Guide
1. LLM-Based Policy Evaluation
Explanation: Asking the model to self-audit or evaluate its own tool calls introduces probabilistic drift. The model may approve destructive actions when context windows shift or when adversarial inputs manipulate reasoning chains.
Fix: Route all policy checks through a deterministic engine. Use explicit allowlists, regex patterns, and parameter validators. Treat the LLM as a decision generator, not a decision validator.
2. Hardcoded Approval Gates
Explanation: Embedding if (tool === 'delete') await humanApprove() directly in agent code couples safety logic to business logic. Updating risk thresholds requires code changes, deployments, and regression testing.
Fix: Externalize policies to a configuration store or database. Load rules at runtime. This enables security teams to adjust thresholds without engineering involvement.
3. Ignoring Parameter Context
Explanation: Validating only the tool name (DELETE) while ignoring the payload (WHERE id = 1 vs. no WHERE clause) creates blind spots. Agents can bypass restrictions by altering arguments.
Fix: Implement deep parameter inspection. Validate data types, enforce range limits, require explicit identifiers for destructive operations, and reject wildcard patterns on critical endpoints.
4. Mutable Audit Logs
Explanation: Storing audit records in standard databases or flat files allows post-execution modification. This breaks compliance requirements and eliminates forensic reliability during incident response.
Fix: Use append-only storage with cryptographic hash chaining. Consider immutable ledger services or write-once cloud storage buckets. Regularly verify chain integrity.
5. Over-Blocking Low-Risk Actions
Explanation: Applying critical-tier approval gates to read-only or internal operations stalls agent velocity. Teams abandon governance layers because they perceive them as bottlenecks.
Fix: Implement risk-tiered routing. Low-risk actions execute synchronously. Medium-risk actions log and proceed. High/critical actions enter the approval queue. Tune thresholds based on actual incident data, not theoretical risk.
6. Assuming Framework Sandboxing is Sufficient
Explanation: Container isolation (Docker, Wasm) prevents network escape but does not stop logical misuse. An agent inside a sandbox can still call DROP TABLE if the database credentials are mounted.
Fix: Combine network isolation with logical policy gates. Sandboxing contains blast radius; governance prevents the blast from occurring.
7. Skipping Idempotency & Rollback Planning
Explanation: Agents retry failed actions or execute duplicates when timeouts occur. Without idempotency keys or automated rollback strategies, partial failures compound into data corruption.
Fix: Enforce idempotency tokens on all write operations. Maintain automated rollback scripts mapped to risk tiers. Test rollback paths during staging, not during incidents.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Internal Dev/Debug Tools | Sync execution + lightweight logging | Low blast radius, high iteration speed | Minimal infrastructure overhead |
| Customer-Facing Support | Async approval for writes, strict parameter validation | Prevents data leakage and unauthorized modifications | Moderate queue infrastructure, high trust ROI |
| Financial/Compliance Workflows | Deterministic policy + mandatory human gate + cryptographic audit | Regulatory requirements demand tamper-evident trails and explicit authorization | Higher latency, but avoids €15M+ compliance penalties |
| Research/Exploration Agents | Sandboxed execution + rate limiting + post-hoc audit review | Enables discovery while containing emergent behavior | Compute isolation costs, reduced incident response overhead |
Configuration Template
# agent-governance-policy.yaml
policy_version: "1.0"
evaluation_mode: "deterministic"
rules:
- tool_pattern: "^db\\.(select|read)$"
allowed_tiers: ["low"]
requires_approval: false
parameter_constraints:
table_name: "^(users|products|logs)$"
limit: "number <= 1000"
- tool_pattern: "^db\\.(update|delete)$"
allowed_tiers: ["high"]
requires_approval: true
parameter_constraints:
where_clause: "regex ^id = '[a-f0-9-]+$'"
dry_run: "boolean == true"
- tool_pattern: "^api\\.(external|payment)$"
allowed_tiers: ["critical"]
requires_approval: true
parameter_constraints:
amount: "number <= 5000"
currency: "^(USD|EUR|GBP)$"
audit:
storage: "append_only_s3"
hash_algorithm: "sha256"
retention_days: 180
integrity_check_interval: "1h"
approval:
queue_type: "async_event_driven"
timeout_seconds: 3600
escalation_policy: "on_timeout_reject"
Quick Start Guide
- Install Dependencies: Initialize a TypeScript project and install
events, crypto, and your preferred queue backend (Redis, SQS, or in-memory for testing).
- Define Policies: Create a
policy.yaml or JSON config mapping tool patterns to risk tiers and parameter constraints. Load it into the PolicyEvaluator at startup.
- Wire the Gateway: Intercept all tool calls in your orchestration layer. Pass each
ActionRequest through PolicyEvaluator.evaluate(). Route results to ApprovalQueue if requiresApproval is true, or execute directly if allowed.
- Attach the Ledger: On every evaluation and execution outcome, append a record to
ImmutableLedger. Configure automated integrity checks and export logs to your compliance storage.
- Validate & Deploy: Run adversarial test cases (wildcard deletes, parameter injection, goal redirection). Verify that blocked actions never reach external systems, approvals pause execution correctly, and audit chains remain intact. Deploy to staging, then production.