# Approvals

Set a session's immutable `permissionPolicy` when calling `openSession`:

- `reject_all` prefers a native `reject_once` option, then `reject_always`, and fails with `permission_policy_unsatisfied` when neither exists.
- `allow_all` (the default) prefers `allow_once`, then `allow_always`.
- `ask` durably records the exact ACP `RequestPermissionRequest` in the ordinary sequenced session-event stream.

This controls how AgentOS answers an adapter's native ACP permission request. It does not grant VM filesystem or network permissions, change which tools the adapter exposes, or become ACP adapter configuration.

Subscribing to session events does not enable interactive approval. You must set `permissionPolicy: "ask"` in `openSession`; if it is omitted, the default `allow_all` policy resolves requests automatically and no `permission_request` event is emitted or persisted.

## Human in the loop

With `ask`, respond using the AgentOS `requestId` plus one of the exact `optionId` values supplied by the adapter. Do not translate options into AgentOS-specific `once`/`always` strings.

examples/approvals/client.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({
	endpoint: "http://localhost:6420",
});
const agent = client.vm.getOrCreate("my-agent");

// Permission requests use the ordinary durable session-event stream.
const conn = agent.connect();
conn.on("sessionEvent", (event) => {
	if (event.type !== "permission_request") return;
	const option = event.options.find(
		(candidate) => candidate.kind === "allow_once",
	);
	if (option) {
		agent.sessions
			.respondPermission({
				sessionId: event.sessionId,
				requestId: event.requestId,
				optionId: option.optionId,
			})
			.catch((error) => console.error("Permission response failed:", error));
	}
});

await agent.sessions.open({
	agent: "pi",
	// Required for permission_request events; the default is allow_all.
	permissionPolicy: "ask",
	env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.sessions.prompt({
	content: [{ type: "text", text: "Create /workspace/output.txt" }],
});
```

examples/approvals/human-in-the-loop.ts:

```ts
import pi from "@agentos-software/pi";
import { agentOS, setup } from "@rivet-dev/agentos";

const vm = agentOS({
	software: [pi],
	// This generic hook observes the same durable event union as clients. A
	// connected client answers permission_request variants with respondPermission.
	onSessionEvent: async (_c, sessionId, event) => {
		if (event.type === "permission_request") {
			console.log("permission requested:", sessionId, event.requestId);
		}
	},
});

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

The `permission_request` session-event variant contains:

- `sessionId`: stable public AgentOS session ID from the durable event envelope.
- `requestId`: globally unique AgentOS correlation ID; the adapter JSON-RPC ID is private.
- `toolCall`, `options`, and optional `_meta`: exact native ACP request fields exposed directly, without a nested `request` object.

`respondPermission` requires an explicit `sessionId` and returns `accepted` or `not_pending` with a specific terminal reason. The first valid response wins atomically. Invalid options fail with `invalid_permission_option` and list the offered IDs. `accepted` means the decision reached the active ACP waiter; it does not mean the tool operation succeeded.

Permission requests have no sidecar expiry. They remain pending until answered or terminated by prompt cancellation, adapter exit, session deletion, or VM shutdown. RivetKit's actor-wide safety bound defaults to about 24.8 days rather than the old 15-minute action timeout. Both the request and its accepted response are durable history entries, so reconnecting consumers subscribe, read history after their last sequence, and deduplicate by `(sessionId, sequence)`.

## Automatic policy

For a fully automated session, omit `permissionPolicy` or choose `allow_all` explicitly. No permission event or client round-trip is required.

examples/approvals/auto-approve-client.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({
	endpoint: "http://localhost:6420",
});
const agent = client.vm.getOrCreate("my-agent");

// allow_all selects an adapter-supplied allow option without a client round-trip.
await agent.sessions.open({
	agent: "pi",
	permissionPolicy: "allow_all",
	env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.sessions.prompt({
	content: [{ type: "text", text: "Write files as needed" }],
});
```

For unattended fail-closed work, choose `reject_all` explicitly. ACP approval is advisory; VM filesystem, network, and process permissions remain the security boundary. Automatically handled requests are neither emitted nor persisted.
