npm package: @reallyartificial/approval-protocol-core
Description: A protocol for AI agent human-in-the-loop approval. What MCP did for tool access, AP does for human oversight.
Author: josharsh
Latest version: 0.1.1

# @reallyartificial/approval-protocol-core

**The safety layer between AI agents and the real world.**

What MCP did for tool access, Approval Protocol does for human oversight.

---

AI agents take actions in the real world — send emails, spend money, modify databases, deploy code. Every agent framework has its own half-baked human-in-the-loop mechanism, all framework-locked, all missing notification channels and escalation.

Approval Protocol (AP) is a **protocol** — not a library — that standardizes how agents get human permission before executing actions, confirm execution results, and enable rollback when things go wrong.

## Install

```bash
npm install @reallyartificial/approval-protocol-core
```

## How It Works

```
Agent                    AP Server                   Human
  |                         |                          |
  |-- ap/negotiate -------->|                          |
  |<-- policies + session --|                          |
  |                         |                          |
  |-- ap/request ---------->|                          |
  |<-- request_id + mode ---|-- notify --------------->|
  |                         |                          |
  |-- ap/status ----------->|         ap/decide <------|
  |<-- approved ------------|                          |
  |                         |                          |
  |-- [execute action]      |                          |
  |                         |                          |
  |-- ap/confirm ---------->|  (result + undo info)    |
  |<-- confirmed -----------|                          |
  |                         |                          |
  |   ap/rollback <---------|--------------------------|
  |<-- undo instructions ---|                          |
```

## Quickstart

### Server

```typescript
import { ApprovalServer, CliChannel } from "@reallyartificial/approval-protocol-core";

const server = new ApprovalServer({
  port: 4000,
  channel: new CliChannel(),
  policies: {
    send_email:     { requires: "always" },
    search_kb:      { requires: "never" },
    issue_refund:   { requires: "conditional", when: "params.amount > 100" },
    delete_records: { requires: "always", execution: "sandbox" },
  },
});

await server.start();
```

### Client

```typescript
import { ApprovalClient } from "@reallyartificial/approval-protocol-core";

const client = new ApprovalClient("http://localhost:4000");

// 1. Negotiate — declare what actions you may take
const session = await client.negotiate("my-agent", ["send_email", "search_kb"]);

// 2. Request approval
const { request_id, status, execution_mode } = await client.request({
  action: { name: "send_email", params: { to: "user@example.com", body: "Hello" } },
  context: { agent: "my-agent", reason: "Customer requested help" },
  risk: { reversible: false, blast_radius: "external" },
});

// 3. Wait for human decision
if (status === "pending") {
  const result = await client.waitForDecision(request_id, { timeout: 60_000 });

  if (result.status === "approved") {
    // 4. Execute the action
    const emailResult = sendEmail("user@example.com", "Hello");

    // 5. Confirm execution with undo instructions
    await client.confirm({
      request_id,
      success: true,
      result: { message_id: emailResult.id },
      undo: {
        type: "api_call",
        description: "Recall the email",
        instructions: { method: "POST", url: `/recall/${emailResult.id}` },
      },
      confirmed_by: "my-agent",
    });
  }
}

// 6. Later, if something went wrong:
await client.rollback({
  request_id,
  reason: "Email contained incorrect information",
  initiated_by: "admin@company.com",
});
```

### withApproval Wrapper

For transparent lifecycle management without manual request/confirm calls:

```typescript
import { withApproval } from "@reallyartificial/approval-protocol-core/client";

const sendEmail = withApproval(
  async (to: string, body: string) => { /* actual send logic */ },
  {
    action: "send_email",
    client,
    risk: { reversible: false, blast_radius: "external" },
    undo: { type: "manual", description: "Contact recipient to disregard" },
  }
);

// Approval + execution + confirmation happens transparently
await sendEmail("user@example.com", "Your refund is ready");
```

## The Protocol

6 methods over HTTP:

| Method | Purpose |
|--------|---------|
| `ap/negotiate` | Trust handshake — agent declares capabilities, server returns policies |
| `ap/request` | Agent requests approval for an action |
| `ap/decide` | Human submits approval decision (approve / deny / edit) |
| `ap/confirm` | Agent confirms execution result with undo metadata |
| `ap/rollback` | Request rollback of a confirmed action |
| `ap/status` | Check full lifecycle status of any request |

## Policies

Per-action rules that control approval behavior:

```typescript
policies: {
  send_email:     { requires: "always" },                              // always ask a human
  search_kb:      { requires: "never" },                               // auto-approve
  issue_refund:   { requires: "conditional", when: "params.amount > 100" }, // ask if > $100
  delete_records: { requires: "always", execution: "sandbox" },        // ask + sandbox mode
}
```

- `"always"` — human approval required
- `"never"` — auto-approved, no human needed
- `"conditional"` — evaluate expression against action params and risk metadata

## Execution Modes

Policies can specify how the agent should execute:

| Mode | Behavior |
|------|----------|
| `live` | Normal execution (default) |
| `sandbox` | Run in isolated/test environment |
| `dry_run` | Simulate without side effects |

## Channels

AP routes approval requests to humans via channels:

- **CliChannel** — Terminal prompt (built-in, for development and demos)
- **WebhookChannel** — POST to any URL (Slack bot, email service, custom UI)

```typescript
import { WebhookChannel } from "@reallyartificial/approval-protocol-core";

const channel = new WebhookChannel({
  url: "https://your-slack-bot.example.com/approvals",
});
```

## Exports

```typescript
// Server
import { ApprovalServer, CliChannel, WebhookChannel } from "@reallyartificial/approval-protocol-core";
import { ApprovalServer } from "@reallyartificial/approval-protocol-core/server";

// Client
import { ApprovalClient, withApproval } from "@reallyartificial/approval-protocol-core";
import { ApprovalClient } from "@reallyartificial/approval-protocol-core/client";

// Types
import type { ApPolicy, ApRiskMetadata, ApUndoMetadata, ApChannel } from "@reallyartificial/approval-protocol-core";

// Zod Schemas (for validation in your own code)
import { ApRequestParamsSchema, ApDecideParamsSchema } from "@reallyartificial/approval-protocol-core";
```

## Full Specification

See [PROTOCOL.md](https://github.com/ReallyArtificial/ApprovalProtocol/blob/main/PROTOCOL.md) for the complete protocol specification.

## Related

- **[@reallyartificial/approval-protocol-langchain](https://www.npmjs.com/package/@reallyartificial/approval-protocol-langchain)** — LangChain integration. Wrap any LangChain tool with AP approval in one function call.

## License

MIT