---
title: "Permissions"
description: "Approve or deny agent tool use with human-in-the-loop or auto-approve patterns."
skill: true
---

- **Human-in-the-loop** approval for agent tool use (file writes, command execution, etc.)
- **Auto-approve** patterns for trusted workloads
- **Server-side hooks** for programmatic permission decisions
- **Client-side subscriptions** for building approval UIs

## Permission request flow

When an agent wants to use a tool (e.g. write a file, run a command), it emits a `permissionRequest` event. Your code responds with `respondPermission` to approve or deny.

<CodeGroup>
```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

// Listen for permission requests
agent.on("permissionRequest", async (data) => {
  console.log("Permission requested:", data.request);

  // Approve this single request
  await agent.respondPermission(
    data.sessionId,
    data.request.permissionId,
    "once",
  );
});

const session = await agent.createSession("pi", {
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.sendPrompt(session.sessionId, "Create a new file at /home/user/output.txt");
```

```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```
</CodeGroup>

## Permission reply options

| Reply | Behavior |
|-------|----------|
| `"once"` | Approve this single request |
| `"always"` | Approve this and all future requests of the same type |
| `"reject"` | Deny the request |

## Server-side auto-approve

Use the `onPermissionRequest` hook in the actor config to approve permissions server-side without client involvement. This is useful for fully automated pipelines.

<CodeGroup stacked>
```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  onPermissionRequest: async (c, sessionId, request) => {
    // Auto-approve all file operations
    await c.respondPermission(sessionId, request.permissionId, "always");
  },
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```

```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

// No need to handle permissions on the client. The server auto-approves.
const session = await agent.createSession("pi", {
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.sendPrompt(session.sessionId, "Write files as needed");
```
</CodeGroup>

## Selective approval

Inspect the permission request to make approval decisions based on the tool or path.

<CodeGroup stacked>
```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  onPermissionRequest: async (c, sessionId, request) => {
    // Auto-approve reads, require manual approval for writes
    const toolName = (request as any).toolName ?? "";
    if (toolName.includes("read")) {
      await c.respondPermission(sessionId, request.permissionId, "always");
    }
    // Writes are forwarded to the client via the permissionRequest event
  },
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
```

```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

// Only write permissions reach the client
agent.on("permissionRequest", async (data) => {
  const approved = confirm(`Allow write: ${JSON.stringify(data.request)}?`);
  await agent.respondPermission(
    data.sessionId,
    data.request.permissionId,
    approved ? "once" : "reject",
  );
});

const session = await agent.createSession("pi", {
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.sendPrompt(session.sessionId, "Read config.json and update it");
```
</CodeGroup>

## Recommendations

- Use `"always"` sparingly. It approves all future requests of that type for the session lifetime.
- For automated CI/CD pipelines, use the server-side `onPermissionRequest` hook to auto-approve without client round-trips.
- For interactive applications, subscribe to `permissionRequest` on the client and build an approval UI.
- If neither the server hook nor the client responds, the agent blocks until a response is given or the action times out.
