@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.
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",
}),
],
});
}
After a restart, the run carries on from where it was, without repeating calls it already made or printing its output twice.
User functionality can be provided to the agent’s code, via custom modules. The user function can now call ctx.interrupt() to pause the run until it receives an answer.
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 });
},
},
},
});
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");
}
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.
Source: Cloudflare


