Burrowbox Blog
← All posts

Manus gives every task its own cloud computer. Build it as a durable workflow on Vercel

· The Burrowbox team · 4 min read

#What Manus does

Manus is an agent that takes a goal and works on it by itself. Its January 2026 post on the Manus Sandbox explains the computing side (Manus):

  • Manus allocates a fully isolated cloud virtual machine for each task.
  • The sandbox works like a personal computer, with networking, a file system, a browser and software tools.
  • Files survive when the sandbox sleeps and wakes. Sandboxes that sit inactive are recycled after 7 days on the free plan and 21 days on Pro. After that, only artifacts and uploads are restored, not temporary files.

#The pattern

A task gets a whole computer: the agent can install what it needs, write files and use a browser without touching anyone else's work. Tasks can run for a long time, so the code driving them has to survive retries and restarts, and it has to clean up when the task ends.

#Build it with Burrowbox and the Vercel Workflow SDK

Vercel's Workflow SDK runs durable functions. Each "use step" function is retried automatically when it throws, and the workflow resumes where it left off. Below, each task claims a pre-built Burrowbox machine, runs an agent on it, saves the result and turns the machine off. Burrowbox isn't involved with Manus.

#1. A warm pool with your tools preinstalled

Every pool machine runs this template before it counts as ready, so claimed machines don't spend time installing anything.

curl -X POST https://burrowbox.dev/api/pools \
  -H "Authorization: Bearer $BURROWBOX_KEY" -H "Content-Type: application/json" \
  -d '{"name": "tasks", "size": "small", "target": 2,
       "setup": [{"type": "tool", "tool": "apps_install", "arguments": {"kind": "apt", "packages": ["python3", "pandoc"]}}]}'

#2. The workflow

npm i workflow ai @ai-sdk/mcp, then wrap next.config.ts with withWorkflow from workflow/next.

// workflows/task.ts
import { generateText, isStepCount } from "ai";
import { createMCPClient } from "@ai-sdk/mcp";

const BB = "https://burrowbox.dev";
const bb = (path: string, init: RequestInit = {}) =>
  fetch(`${BB}${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.BURROWBOX_KEY}`, "Content-Type": "application/json" },
  }).then((r) => r.json());

export async function runTask(userId: string, goal: string) {
  "use workflow";
  const machineId = await claimMachine(userId);
  try {
    const report = await runAgent(machineId, goal);
    await saveReport(userId, report);
  } finally {
    await stopMachine(machineId);
  }
}

async function claimMachine(userId: string) {
  "use step";
  const m = await bb(`/api/pools/${process.env.POOL_ID}/claim`, {
    method: "POST",
    body: JSON.stringify({ externalId: userId, ttlMinutes: 120 }), // stops itself after 2 h at the latest
  });
  return m.id as string; // keep the token out of workflow state
}

async function runAgent(machineId: string, goal: string) {
  "use step";
  const m = await bb(`/api/machines/${machineId}`); // includes mcpUrl and mcpToken
  const mcp = await createMCPClient({
    transport: { type: "http", url: m.mcpUrl, headers: { Authorization: `Bearer ${m.mcpToken}` } },
  });
  try {
    const { text } = await generateText({
      model: "anthropic/claude-sonnet-5.5",
      tools: await mcp.tools(), // shell_run, file_write, browser_navigate, …
      stopWhen: isStepCount(50),
      system: "Work in ~/task. Write the final report to ~/task/report.md, then reply with its contents.",
      prompt: goal,
    });
    return text;
  } finally {
    await mcp.close();
  }
}

async function stopMachine(machineId: string) {
  "use step";
  await bb(`/api/machines/${machineId}/stop`, { method: "POST" }); // files are kept for follow-ups
}

async function saveReport(userId: string, report: string) {
  "use step";
  await db.reports.insert({ userId, report }); // your database
}

Start it from a route:

// app/api/tasks/route.ts
import { start } from "workflow/api";
import { runTask } from "@/workflows/task";

export async function POST(req: Request) {
  const { userId, goal } = await req.json();
  await start(runTask, [userId, goal]);
  return Response.json({ ok: true });
}

Some notes:

  • Retries. If a step throws, it's retried. A retried claimMachine claims a second machine, so for strict once-only behavior, look up the user's machine first with GET /api/machines?externalId=….
  • Follow-ups go to the same machine. POST /api/machines/{id}/start restores its files, installed apps and (with "browser": "full") browser logins. When you're finished with it, DELETE /api/machines/{id}.
  • Watching. To watch a task live, create a live-view link for its machine.

#What it costs

A small machine (1 vCPU, 6 GB) costs $0.11 an hour while running. A stopped machine costs $0.001 an hour. Idle pool machines are billed like running ones, so a pool of 2 small machines costs about $0.22 an hour. See Billing.

Create an account to try it.

#Sources