Governance SDK
The Governance SDK provides a policy enforcement layer that controls what AI agents can do within a workspace. Every tool call and agent-to-agent message passes through the Policy Enforcement Point (PEP), which evaluates it against the workspace's active policy before execution.
The four subsystems are:
- PDP/PEP — Policy Decision Point + Policy Enforcement Point (rule engine + inline interceptor)
- HITL Approvals — Human-in-the-loop durable suspend/resume gates with TTL
- Audit Trail — SHA-256 hash-chained compliance records with evidence export
- Credential Isolation — Ephemeral credential refs that prevent agents from seeing raw secrets
Refs #4343, #4344, #4345, #4346, #4347
Architecture
Agent Tool Call
│
▼
┌─────────────┐ ┌─────────────┐
│ PEP │────▶│ PDP │
│ (intercept) │ │ (evaluate) │
└──────┬──────┘ └─────────────┘
│
┌───┼───┐
│ │ │
ALLOW DENY REQUIRE_APPROVAL
│ │ │
▼ ▼ ▼
exec 403 Approval Gate
(HITL)
│
▼
Audit Trail
(hash chain)
The PDP evaluates tool calls against a JSON policy using first-match-wins priority ordering. The PEP intercepts requests at the middleware level and acts on the PDP decision. Every decision is logged to the audit trail.
Policy DSL Reference
Policies are JSON documents containing an ordered list of rules. Each rule matches against the tool call context using glob patterns and arg predicates.
Policy Structure
{
"policy_id": "pol_abc123",
"workspace_id": "ws_xyz",
"default_effect": "allow",
"enforcement_mode": "enforce",
"rules": [
{
"priority": 0,
"effect": "deny",
"tool": "deploy",
"capability": "*",
"target": "*.production",
"arg_predicates": {},
"description": "Block production deploys"
}
]
}
Rule Fields
| Field | Type | Description |
|---|---|---|
priority | int | Lower number = higher precedence. First match wins. |
effect | string | allow, deny, or require_approval |
tool | string | Glob pattern matching the tool name |
capability | string | Glob pattern matching the capability (e.g., tool_execute, memory_write) |
target | string | Glob pattern matching the target resource |
arg_predicates | object | Conditional checks on tool arguments |
description | string | Human-readable explanation for audit logs |
Arg Predicates
Arg predicates let you write conditional rules based on tool arguments:
{
"effect": "deny",
"tool": "transfer",
"target": "*",
"arg_predicates": {
"amount": {"op": "gt", "value": 1000}
},
"description": "Block transfers over $1,000"
}
Supported operators:
| Operator | Description | Example |
|---|---|---|
eq | Equal | {"op": "eq", "value": "prod"} |
ne | Not equal | {"op": "ne", "value": "test"} |
gt | Greater than | {"op": "gt", "value": 1000} |
gte | Greater than or equal | {"op": "gte", "value": 100} |
lt | Less than | {"op": "lt", "value": 50} |
lte | Less than or equal | {"op": "lte", "value": 10} |
contains | String contains | {"op": "contains", "value": "secret"} |
Effects
| Effect | HTTP Status | Behavior |
|---|---|---|
allow | 200 | Tool call proceeds |
deny | 403 | Tool call blocked, reason returned |
require_approval | 202 | Tool call suspended, approval_id returned |
Default Effect and Fail Modes
default_effect: Applied when no rule matches. Defaults toallow(fail-open).- Fail-open (default): If the PDP encounters an internal error, the call is allowed.
- Fail-closed: Workspaces can opt into fail-closed mode. On PDP error, the call is denied.
Example Policies
Block production deploys except from CI:
{
"rules": [
{
"priority": 0,
"effect": "allow",
"tool": "deploy",
"target": "*.production",
"arg_predicates": {
"source": {"op": "eq", "value": "ci-pipeline"}
},
"description": "CI can deploy to production"
},
{
"priority": 1,
"effect": "deny",
"tool": "deploy",
"target": "*.production",
"description": "Block manual production deploys"
}
]
}
Require approval for destructive operations:
{
"rules": [
{
"priority": 0,
"effect": "require_approval",
"tool": "delete_*",
"target": "*",
"description": "Destructive ops need human approval"
}
]
}
Approval Gates (HITL)
When a rule has effect: require_approval, the PEP returns HTTP 202 with an approval_id. The agent run is suspended until a human decides.
Approval Lifecycle
1. PEP evaluates → require_approval
2. Approval record created (status: pending, TTL: 30 min)
3. Notification sent to approver_ref (e.g., team:platform-ops)
4. Human reviews context and decides:
- approved → signal: resume → agent continues
- denied → signal: abort → agent stops
5. If TTL expires → status: expired → signal: abort
API
Create approval (automatic — triggered by PEP):
POST /api/v1/governance/approvals
Decide on approval:
POST /api/v1/governance/approvals/{approval_id}/decide
Content-Type: application/json
{
"decision": "approved",
"note": "Reviewed and approved for staging cleanup"
}
Escalate to different approver:
POST /api/v1/governance/approvals/{approval_id}/escalate
Content-Type: application/json
{
"new_approver": "user:uuid-of-senior-engineer",
"extend_ttl_minutes": 60
}
List pending approvals:
GET /api/v1/governance/approvals?status=pending&approver_ref=team:platform-ops
Approver Refs
| Format | Behavior |
|---|---|
team:<name> | Any authenticated user can decide |
user:<uuid> | Only that specific user can decide |
Audit Trail + Evidence Export
Every governance decision is recorded in a SHA-256 hash-chained audit trail. Each record links to the previous via prev_hash, creating a tamper-evident log suitable for SOC 2, ISO 27001, GDPR, and HIPAA compliance.
Hash Chain Structure
Genesis (000...000)
│
▼
Record 1: SHA-256(prev_hash + content) = hash_1
│
▼
Record 2: SHA-256(hash_1 + content) = hash_2
│
▼
Record N: SHA-256(hash_{n-1} + content) = hash_n
Record Fields
Each audit record contains:
| Field | Description |
|---|---|
agent_id | Agent that triggered the decision |
identity | User or agent identity |
capability | Capability being exercised |
tool | Tool being called |
decision | allow, deny, or require_approval |
input_hash | SHA-256 of the tool call input |
output_hash | SHA-256 of the tool call output |
controls | Matched compliance control IDs |
record_hash | Hash of this record (chain link) |
prev_hash | Hash of the previous record |
latency_ms | PDP evaluation latency |
Chain Verification
GET /api/v1/governance/audit/verify?start_id=1&end_id=1000
Returns:
{
"valid": true,
"broken_at": null,
"records_checked": 1000
}
Evidence Export
Export compliance evidence for a specific framework and time range:
POST /api/v1/governance/audit/export
Content-Type: application/json
{
"framework": "SOC2",
"start_time": "2026-01-01T00:00:00Z",
"end_time": "2026-06-30T23:59:59Z"
}
Supported frameworks: SOC2, ISO27001, GDPR, HIPAA
Control mappings use prefix matching:
| Framework | Control ID Prefix |
|---|---|
| SOC 2 | CC (e.g., CC6.1, CC7.2) |
| ISO 27001 | A. (e.g., A.9.4.1) |
| GDPR | GDPR- (e.g., GDPR-Art25) |
| HIPAA | HIPAA- (e.g., HIPAA-164.312) |
Credential Isolation
Agents never see raw API keys. Instead, they receive opaque credential references (cref_...) that the egress proxy resolves at request time.
How It Works
1. Admin stores credential in Credential Vault (AES-256-GCM encrypted)
2. Admin creates a credential ref scoped to a specific agent
3. Agent receives opaque ref_id (e.g., cref_abc123...)
4. Agent passes ref_id to egress proxy
5. Egress proxy resolves ref → decrypts credential → injects into outbound request
6. Every resolution is audit-logged
Security Properties
- Agent isolation: Agents only see opaque ref IDs, never raw secrets
- TTL expiry: Refs expire after a configurable TTL (default 1 hour)
- Max uses: Refs can be limited to N resolutions
- Revocation: Refs can be revoked instantly without affecting the underlying credential
- Transparent rotation: Rotating the underlying credential does not invalidate active refs
API
Create a credential ref:
POST /api/v1/governance/credential-refs
Content-Type: application/json
{
"credential_id": "cred-uuid",
"agent_id": "agent-uuid",
"ttl_seconds": 3600,
"max_uses": 100
}
Resolve a ref (called by egress proxy, not agents):
POST /api/v1/governance/credential-refs/resolve
Content-Type: application/json
{
"ref_id": "cref_abc123...",
"agent_id": "agent-uuid"
}
Revoke a ref:
DELETE /api/v1/governance/credential-refs/{ref_id}
Performance
The governance layer is designed for sub-50ms overhead on every tool call:
| Operation | p95 Target | Measured |
|---|---|---|
| PDP evaluate (50 rules) | < 50ms | ~0.5ms |
| Ungoverned workspace | < 5ms | ~0.01ms |
| Audit hash chain append | < 20ms | ~0.05ms |
Ungoverned workspaces (no active policy) have effectively zero overhead — the PDP returns ALLOW immediately without scanning any rules.
Middleware Integration
The GovernancePEPMiddleware automatically intercepts:
POST /api/v1/mcp/tools/*/call— MCP tool callsPOST /api/v1/cloud/a2a/*— Agent-to-agent messages
No application code changes are needed. The middleware reads X-Workspace-ID and X-Agent-ID headers to determine the policy context.
# Middleware is added to the FastAPI app automatically
app.add_middleware(GovernancePEPMiddleware)
For endpoint-level enforcement, use the decorator:
from app.services.governance_pep_service import enforce_governance
@router.post("/tools/{tool_id}/call")
@enforce_governance(tool="mcp_tool_call", capability="tool_execute")
async def call_tool(request: Request, tool_id: str):
...