---
title: "Governance SDK"
description: "Policy enforcement, human-in-the-loop approval gates, hash-chained audit trails, and credential isolation for AI agent workspaces"
canonical: "https://docs.ainative.studio/docs/agent-cloud/governance"
last-updated: "2026-09-23T01:51:08.820Z"
---

# Governance SDK

Source: https://docs.ainative.studio/docs/agent-cloud/governance

> Policy enforcement, human-in-the-loop approval gates, hash-chained audit trails, and credential isolation for AI agent workspaces

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:

1. **PDP/PEP** — Policy Decision Point + Policy Enforcement Point (rule engine + inline interceptor)
2. **HITL Approvals** — Human-in-the-loop durable suspend/resume gates with TTL
3. **Audit Trail** — SHA-256 hash-chained compliance records with evidence export
4. **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

```json
{
  "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:

```json
{
  "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 to `allow` (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:**

```json
{
  "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:**

```json
{
  "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:

```json
{
  "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 calls
- `POST /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.

```python
# Middleware is added to the FastAPI app automatically
app.add_middleware(GovernancePEPMiddleware)
```

For endpoint-level enforcement, use the decorator:

```python
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):
    ...
```

## Internal Dogfooding

AINative uses the governance layer on its own agent swarm. The `swarm-agent-protection` policy enforces rules on the 16-agent local swarm:

| Rule | Effect | What it protects |
|------|--------|-----------------|
| `crontab_disable` | `require_approval` | Prevents agents from disabling other agents' cron entries without human approval |
| `agent_retire` | `require_approval` | Prevents agents from deprecating scripts without approval |
| `schema_drop` | `require_approval` | Prevents dropping production tables without approval |
| `credential_expose` | `deny` | Blocks hardcoding credentials in source code |
| `ir_email_reply` | `require_approval` | Requires pass detection before auto-replying to investors |
| `*` (default) | `allow` | Everything else proceeds normally |

### Using the governance check in agents

```python
from governance_check import check_crontab_disable

decision = check_crontab_disable(
    agent_id="worktree-agent",
    target_agent="startup_camp",
    reason="event ended",
)

if not decision.allowed:
    log.warning(f"BLOCKED: {decision.reason}")
    return  # do not proceed

# proceed with the action
```

Available convenience wrappers:

```python
from governance_check import (
    check_crontab_disable,    # Before disabling cron entries
    check_agent_retire,       # Before deprecating agent scripts
    check_credential_write,   # Before writing credentials to files
    check_ir_auto_reply,      # Before auto-replying to investors
    check_schema_drop,        # Before dropping production tables
    check_governance,         # Generic check for any action
)
```

All checks **fail open** — if the PDP is unreachable, actions are allowed with a logged warning. This preserves the enable-first design principle.
