Catalog / Cloudflare Developer Platform

Agents, Workers - Cloudflare Computer now helps agents recover from interruptions and wait for approval

todayaddedOriginal notes

@cloudflare/computer ↗︎ gives your agent a workspace to run code in. From v0.7.0, that code can resume after a deploy or crash, and pause on an external action, for example to approve a tool call.

Resume interrupted runs

Set onInterrupt: "replay" on WorkerJavaScriptBackend, or on a single run.

import { DurableObject } from "cloudflare:workers";
import { Workspace } from "@cloudflare/computer";
import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript";

export class MyAgent extends DurableObject {
	workspace = new Workspace({
		storage: this.ctx.storage,
		backends: [
			new WorkerJavaScriptBackend({
				loader: this.env.LOADER,
				onInterrupt: "replay",
			}),
		],
	});
}
import { DurableObject } from "cloudflare:workers";
import { Workspace } from "@cloudflare/computer";
import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript";

export class MyAgent extends DurableObject<Env> {
	workspace = new Workspace({
		storage: this.ctx.storage,
		backends: [
			new WorkerJavaScriptBackend({
				loader: this.env.LOADER,
				onInterrupt: "replay",
			}),
		],
	});
}

When recovery is triggered, the JavaScript runs again from the beginning, reusing recorded results for completed calls without printing previously recorded output again. If a call's result was not recorded before the interruption, recovery may repeat the call — even if its action already succeeded. For retry handling, including unsafeToRepeat() for operations that cannot safely repeat, refer to Calls that must not run twice.

Recovery starts on the next call to workspace.runtime. To recover without waiting for traffic, call workspace.runtime.recover() from a Durable Object alarm.

Wait for approvals

Expose your application functions through custom modules. A function can call ctx.interrupt() to pause the run until your application supplies an approval decision.

Require approval before posting a comment

For example, to provide a GitHub module that allows an agent to post a comment on an issue with approval, an implementation of postComment could look like this:

new WorkerJavaScriptBackend({
	loader: this.env.LOADER,
	onInterrupt: "replay",
	modules: {
		// The agent's code imports this with `import { postComment } from "ws:github"`.
		"ws:github": {
			postComment: async ([issue, body], ctx) => {
				const { approved } = await ctx.interrupt({
					issue,
					body,
				});
				if (!approved) throw new Error("The comment was rejected.");
				return github.comment(issue, body, { idempotencyKey: ctx.callId });
			},
		},
	},
});
new WorkerJavaScriptBackend({
	loader: this.env.LOADER,
	onInterrupt: "replay",
	modules: {
		// The agent's code imports this with `import { postComment } from "ws:github"`.
		"ws:github": {
			postComment: async ([issue, body], ctx) => {
				const { approved } = await ctx.interrupt<{ approved: boolean }>({
					issue,
					body,
				});
				if (!approved) throw new Error("The comment was rejected.");
				return github.comment(issue, body, { idempotencyKey: ctx.callId });
			},
		},
	},
});

Call it from agent code

When the agent's code calls postComment(), the run pauses at ctx.interrupt() until your app responds. The agent's code calls it like any other function:

import { postComment } from "ws:github";

export default async function ({ issue }) {
	return postComment(issue, "Hello world");
}

Respond to the approval request

The time spent waiting does not count toward its timeout. The wait also survives deploys. Fetch the pending calls with workspace.runtime.pending() and resolve them with respond():

for (const call of await workspace.runtime.pending()) {
	await workspace.runtime.respond(call.id, call.callId, { approved: true });
}
for (const call of await workspace.runtime.pending()) {
	await workspace.runtime.respond(call.id, call.callId, { approved: true });
}

The run continues once all pending calls are resolved.

For setup, current limitations, and how replay works, refer to Resume runs after a restart.