Approvals
Handle native ACP permission options with durable AgentOS correlation.
Set a session’s immutable permissionPolicy when calling openSession:
reject_allprefers a nativereject_onceoption, thenreject_always, and fails withpermission_policy_unsatisfiedwhen neither exists.allow_all(the default) prefersallow_once, thenallow_always.askdurably records the exact ACPRequestPermissionRequestin 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.
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
.respondPermission({
sessionId: event.sessionId,
requestId: event.requestId,
optionId: option.optionId,
})
.catch((error) => console.error("Permission response failed:", error));
}
});
await agent.openSession({
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.prompt({
content: [{ type: "text", text: "Create /workspace/output.txt" }],
});
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 nestedrequestobject.
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.
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.openSession({
agent: "pi",
permissionPolicy: "allow_all",
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.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.