# Enforcing Approval Gates with Paperclip AI Governance System

> Enforce approval gates with Paperclip AI governance. Ensure human-in-the-loop approval for high-risk operations, create formal records, and block execution until authorized.

- Repository: [Paperclip/paperclip](https://github.com/paperclipai/paperclip)
- Tags: how-to-guide
- Published: 2026-08-12

---

**Paperclip AI enforces human-in-the-loop approval for high-risk operations by detecting policy decisions, creating formal board-level approval records, and blocking execution until authorized operators explicitly approve or reject the request.**

The `paperclipai/paperclip` repository implements a robust governance layer that protects every destructive or write-classified tool invocation. When the policy engine determines that an action requires human consent, the system automatically pauses execution and escalates to board-level operators through interactive approval cards.

## How Approval Gates Work in Paperclip AI

The approval gate mechanism coordinates across the **Tool Gateway**, **Policy Engine**, and **Database Layer** to ensure no high-risk operation executes without explicit authorization.

### Policy Evaluation and Decision Detection

The governance flow begins in [`server/src/services/tool-access-policy.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/tool-access-policy.ts). When evaluating tool access, policies can return a `require_approval` decision that propagates to the gateway.

```ts
// server/src/services/tool-access-policy.ts (line 1249)
if (accessDecision.decision === "require_approval") return "pending";

```

This decision signals that the requested operation exceeds automated risk thresholds and requires human review.

### Gateway Orchestration and Record Creation

The **Tool Gateway** ([`server/src/services/tool-gateway.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/tool-gateway.ts) at line 1711) detects the `policyDecision === "require_approval"` condition and initiates the approval workflow. The gateway performs three critical operations:

1. **Creates a formal approval record** in the `approvals` table with `type: "request_board_approval"`
2. **Links the approval to the current issue** via the `issue_approvals` join table
3. **Posts an interactive confirmation card** on the issue thread

```ts
// server/src/services/tool-gateway.ts (lines 15113-15134)
const [approval] = await db
  .insert(approvals)
  .values({
    companyId: input.session.companyId,
    type: "request_board_approval",
    requestedByAgentId: input.session.agentId,
    payload: {
      title: `Approve high‑risk tool action: ${input.tool.name}`,
      // Tool arguments, risk level, and preview data
    },
  })
  .returning();

// Link approval to issue (lines 15138-15144)
await db.insert(issueApprovals).values({
  companyId: input.session.companyId,
  issueId: input.session.issueId,
  approvalId: approval.id,
  linkedByAgentId: input.session.agentId,
});

```

### Interactive UI Cards and Execution Blocking

After creating the database records, the gateway posts an interactive card and sets the invocation status to `awaiting_approval` (line 2931).

```ts
// server/src/services/tool-gateway.ts (lines 15148-15186)
const interaction = await interactions.create(
  { id: input.session.issueId, companyId: input.session.companyId },
  {
    kind: "request_confirmation",
    title: "Approve tool action",
    summary: `${input.tool.name} requires approval before Paperclip will execute it.`,
    payload: {
      target: { type: "custom", key: `tool-action:${actionRequest.id}` },
      // Approve/Reject buttons rendered in the UI
    },
  }
);

// Pause execution (line 2931)
await db.update(toolInvocations).set({
  status: "awaiting_approval",
  approvalState: "pending",
});

```

The tool remains blocked until a board operator interacts with the approval card.

## Database Schema for Approval Tracking

Paperclip AI persists approval state across two primary tables that enable audit trails and cross-reference capabilities.

### The Approvals Table

The `approvals` table in [`packages/db/src/schema/approvals.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/db/src/schema/approvals.ts) stores every request with JSON payloads and decision metadata:

```ts
// packages/db/src/schema/approvals.ts
export const approvals = pgTable("approvals", {
  id: uuid("id").primaryKey().defaultRandom(),
  companyId: uuid("company_id").notNull(),
  type: text("type").notNull(), // "request_board_approval"
  requestedByAgentId: uuid("requested_by_agent_id"),
  requestedByUserId: text("requested_by_user_id"),
  status: text("status").notNull().default("pending"),
  payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
  decisionNote: text("decision_note"),
  decidedByUserId: text("decided_by_user_id"),
  decidedAt: timestamp("decided_at", { withTimezone: true }),
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
});

```

### Issue-Approval Linking

The `issue_approvals` join table connects approvals to their triggering issues:

```ts
// packages/db/src/schema/issue_approvals.ts
export const issueApprovals = pgTable("issue_approvals", {
  companyId: uuid("company_id").notNull(),
  issueId: uuid("issue_id").notNull(),
  approvalId: uuid("approval_id").notNull().references(() => approvals.id, { onDelete: "cascade" }),
  linkedByAgentId: uuid("linked_by_agent_id"),
});

```

## Implementing Approval Flows in Code

Developers can interact with the approval system through the REST API and SDK methods.

### Triggering Approval-Gated Tool Calls

When invoking high-risk tools, the gateway automatically handles approval requirements. Client code can detect pending approvals via HTTP 409 responses:

```ts
import { api } from "@paperclipai/mcp-server";

async function runRiskyTool(session, toolName, args) {
  const response = await api.requestJson("POST", "/tool-gateway/run", {
    sessionId: session.id,
    tool: { name: toolName, risk: "destructive" },
    arguments: args,
  });

  // Detect approval requirement (tool-gateway.ts lines 15126-15134)
  if (response.status === 409 && response.body?.errorCode === "approval_path_missing") {
    console.log("Approval required – board will be notified.");
    return;
  }

  return await response.json();
}

```

### Processing Approval Decisions

Board operators approve or reject requests through the SDK, which routes to [`packages/plugins/sdk/src/worker-rpc-host.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/plugins/sdk/src/worker-rpc-host.ts):

```ts
import { worker } from "@paperclipai/plugins-sdk";

async function handleApprovalDecision(approvalId, decision) {
  await worker.approvals.decide({
    approvalId,
    decision: decision ? "approved" : "rejected",
    decisionNote: "Reviewed and authorized by security team",
  });
}

```

Upon approval, the gateway resumes execution (lines 16171-16180 in [`tool-gateway.ts`](https://github.com/paperclipai/paperclip/blob/main/tool-gateway.ts)) and replays the original signed arguments.

### Querying Pending Approvals

Applications can fetch pending approvals via the REST API defined in [`packages/mcp-server/src/tools.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/mcp-server/src/tools.ts):

```ts
import { client } from "@paperclipai/mcp-server";

async function fetchPendingApprovals(companyId) {
  return await client.requestJson(
    "GET",
    `/companies/${companyId}/approvals?status=pending`
  );
}

```

## Summary

- **Policy-driven enforcement**: The [`tool-access-policy.ts`](https://github.com/paperclipai/paperclip/blob/main/tool-access-policy.ts) engine returns `require_approval` decisions that trigger governance workflows automatically.
- **Immutable audit trails**: Every approval creates records in the `approvals` and `issue_approvals` tables with full payload history and decision metadata.
- **Execution blocking**: The Tool Gateway sets `status: "awaiting_approval"` and pauses tool invocation until explicit authorization occurs.
- **Interactive resolution**: Approval cards surface directly in issue threads via the `interactions.create` API, enabling one-click approve/reject actions.
- **SDK integration**: The `worker.approvals.decide` method in [`packages/plugins/sdk/src/worker-rpc-host.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/plugins/sdk/src/worker-rpc-host.ts) provides programmatic access to decision handling.

## Frequently Asked Questions

### How does Paperclip AI determine when to require approval?

The **Tool Access Policy** evaluates each tool invocation against company-specific policies. When a tool is classified as destructive or write-capable, and the policy configuration mandates human oversight, the policy engine returns `require_approval` at line 1249 of [`server/src/services/tool-access-policy.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/tool-access-policy.ts). This decision propagates to the Tool Gateway, which initiates the approval flow.

### What happens to tool execution while waiting for approval?

The gateway explicitly blocks execution by updating the `toolInvocations` table with `status: "awaiting_approval"` (line 2931 in [`server/src/services/tool-gateway.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/tool-gateway.ts)). The original signed arguments are preserved in the approval payload, and execution only resumes after the `approvals.decide` endpoint processes an "approved" decision and updates the status to "executing".

### Can approvals be programmatically queried and managed?

Yes. The REST API in [`packages/mcp-server/src/tools.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/mcp-server/src/tools.ts) exposes endpoints for listing approvals by company and status. The SDK provides `worker.approvals.list`, `worker.approvals.get`, and `worker.approvals.decide` methods implemented in [`packages/plugins/sdk/src/worker-rpc-host.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/plugins/sdk/src/worker-rpc-host.ts), enabling full programmatic control over the approval lifecycle.

### Where are approval records stored and how are they linked to issues?

Approval records reside in the `approvals` table defined in [`packages/db/src/schema/approvals.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/db/src/schema/approvals.ts). The system creates a corresponding entry in the `issue_approvals` join table ([`packages/db/src/schema/issue_approvals.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/db/src/schema/issue_approvals.ts)) that links the approval UUID to the specific issue ID, enabling audit trails that trace every high-risk operation back to its original context.