Building agentsChapter 2 of 5

Serverless tool calls

The short answer

A serverless tool call runs one of the model’s commands on fresh, isolated compute with the agent’s workspace already mounted, and leaves nothing running. It should let the harness decide per call what the command can reach and change, commit its writes before it returns, and bill for the call instead of a machine-hour. On Archil, that call is disk.exec().

Hunter LeathFounder & CEO, Archil

12 min readChecked November 2026Read as Markdown

A serverless tool call runs one of the model’s commands on compute that exists only for that command, not on a sandbox that’s up all session. What comes after sandboxes describes the alternative: “the harness itself doesn’t actually care how or where the tools are run. It just needs to fire off a function call to something that then runs deterministic code and returns a result.”From Hunter Leath’s post of October 14, 2026. Why the harness belongs outside the sandbox is the subject of Two places to run an agent.

Whatever runs that call has to provide six things:

  • Isolation for each call, with nothing left running after it
  • The workspace, mounted before the command starts
  • Access to only what the call needs
  • Writes that the loop and the next call can see
  • Limits you can design around
  • A price and an overhead that follow the work

Each section takes one and shows how Archil’s serverless execution provides it, starting from one function against the agent’s disk:

TypeScript
import * as archil from "disk";
 
const disk = await archil.getDisk("dsk-0123456789abcdef");
const { exitCode, stdout, stderr, timing } = await disk.exec("npm test");

Isolation for each call§

Every call runs code nobody reviewed, so it gets compute of its own that’s gone when the command returns. On Archil, disk.exec() sends the command to Archil, not to a machine you manage. Archil starts a Firecracker microVM, with its own kernel, next to the disk’s data, mounts the disk, and runs the command with the disk as its working directory. When the command exits, the microVM unmounts the disk, which commits the command’s writes and hands back anything it checked out. Then the result comes back to your harness, and the microVM is gone.From Serverless execution and the TypeScript SDK. That the unmount commits and checks in before the result returns is from Archil’s container runtime.

· One tool call, start to finishA microVM exists only while the command runs. The disk is there before the call and after it.

One serverless tool call. The harness calls disk.exec("npm test"). A fresh Firecracker microVM starts for the call, the workspace disk is mounted as its working directory, and npm test runs. When it exits with code 0, the disk is unmounted, which commits the writes and checks in anything the call checked out. The result, carrying stdout, the exit code, and timing, travels back to the harness, and the microVM is gone. The disk keeps every file. Under the drawing, the result’s timing fields: queueMs covers queueing, scheduling, booting the microVM, and mounting the disk; executeMs covers the command; and totalMs is the whole request, measured on the server.

Because the compute is new for every call, the workspace is the only thing that carries over. On Archil, files written to the disk persist, including installed packages and a virtual environment kept inside the workspace. Running processes, memory, environment variables, and files written anywhere but the disk don’t. Two habits follow. Install dependencies into the workspace once and reuse them, and chain related steps in one command, such as cd app && npm run build && npm test, instead of relying on an earlier call’s cd or export.From How to run agent tool calls without keeping a VM open.

The workspace, mounted§

The command has to find the repository, its packages, and the agent’s earlier changes at a path the model can rely on. With Archil, a harness that serves many tenants holds one client and mounts each tenant’s disk under its own ID, the form Many agents, one machine wires into pi:

TypeScript
import { Archil, ArchilApiError } from "disk";
 
const archil = new Archil({ apiKey: process.env.ARCHIL_API_KEY, region: "aws-us-east-1" });
 
// The bash tool. Each call gets a fresh microVM with the tenant's workspace at /mnt/archil/<diskId>.
export async function bash(diskId: string, command: string) {
  try {
    const { exitCode, stdout, stderr } = await archil.exec({
      disks: { [diskId]: { disk: diskId, subdirectory: "workspace", conditional: true } },
      command: `cd /mnt/archil/${diskId} && ${command}`,
    });
    return { exitCode, stdout, stderr };
  } catch (e) {
    if (e instanceof ArchilApiError && e.status === 504) {
      // The command ran past the per-call limit and was stopped.
      return { exitCode: 124, stdout: "", stderr: "Timed out. Split the work into smaller commands." };
    }
    throw e;
  }
}

Mounting only workspace/ keeps the rest of the tenant’s disk, such as the conversation history, out of the command’s reach. conditional: true lets the command write without a checkout, for reasons later in this chapter. Tell the model the rules in its instructions: each command starts fresh in the workspace, and anything worth keeping belongs on the disk. The SDK retries an exec only when it couldn’t connect at all, so a retry never runs a command twice.From the disk package’s retry policy and its changelog for version 1.6.0: creations, execs, and deletes retry only when no connection could be established.

Access to only what the call needs§

The harness, not the model, should decide what each call can reach and what it can change. On Archil, disk.exec() mounts one disk at its root. archil.exec() takes a map instead, and mounts each disk at /mnt/archil/<key>. A common shape is an agent that reads a customer’s documents and writes to its own session workspace:

TypeScript
const result = await archil.exec({
  disks: {
    workspace: { disk: sessionDiskId, conditional: true },
    docs: { disk: customerDocsDiskId, readOnly: true },
    team: { disk: teamDiskId, subdirectory: "users/ana", readOnly: true },
  },
  command: "grep -ril refund /mnt/archil/docs /mnt/archil/team > /mnt/archil/workspace/refunds.txt",
});

Each entry is either a disk ID, which mounts the disk’s root, or an object with these options:

  • readOnly: true mounts the disk read-only. Every write from inside the microVM fails with EROFS, so a command can’t damage files that other threads or tenants read.
  • subdirectory exposes only part of the disk. The path is relative, with no . or .. segments, and the rest of the disk isn’t in the microVM at all.
  • conditional: true mounts in conditional mode, which the next section explains.
  • checkoutPaths and queueMs check paths out before the command starts, also explained in the next section.The options are the ExecMountSpec type in the disk package and the ExecMount schema in our API. Serverless execution documents readOnly and subdirectory; Sharing disks documents conditional.

A disk that isn’t in the map doesn’t exist inside the microVM. The request carries only the command and its mounts, so your API key stays in the harness.

Session workspace
this thread’s repo and notes
Customer documents
the tenant’s files, shared by every thread
Team drive
one folder per user
App logs
every service’s logs

/mnt/archil/workspace: writes anywhere not checked out. /mnt/archil/docs: read-only.

· Compose a callPick how each disk is mounted, and see what the command sees, what it can change, and the call that asks for it.

An explorable with four disks: a session workspace (this thread’s repository and notes), customer documents (the tenant’s files, shared by every thread), a team drive with one folder per user, and app logs from every service. Each can be off, read-only, shared, or conditional, and the team drive and the logs can expose only users/ana or app/logs. It starts with the workspace conditional and the documents read-only. The figure lists what the command sees under /mnt/archil/. A read-only mount can change nothing, and every write fails with EROFS. A shared mount can create files and directories and edit files it opens, but deleting or renaming needs archil checkout on the parent. A conditional mount can change anything nobody else has checked out, and the last write to reach the disk wins. A subdirectory mount hides the rest of the disk. Disks that are off are out of reach. The figure also writes the matching archil.exec call, and says that a call needs at least one disk.

Writes the loop can see§

When a call returns, the harness and the next call have to see everything it wrote. When several writers share a workspace, you need to know whose write wins. Archil’s serverless execution has the same read-after-write consistency as any other Archil client. A file one command writes is visible to the next command that reads it, and a call’s writes are committed before its result returns. If committing fails, the exit code is non-zero and stderr says archil: command output may not have been durably committed, so a harness that checks exitCode already catches it.From Consistency with other clients and Limits in our docs.

How a call may write depends on the mount mode. By default, serverless execution mounts each disk in shared mode, which lets many clients mount one disk at once. In shared mode, the disk reads as read-only except where this client holds a checkout. Creating a file or a directory, or opening a file to write, checks it out automatically. Deleting or renaming is a change to the parent directory, so it needs a checkout of the parent, and without one it fails with Read-only file system. That surprises agents, which create a file in one call and can’t remove it in the next.From Automatic delegations in shared mode. Our docs call a checkout a delegation.

You have three ways to give a call the writes it needs:

  • Check out inside the command. Start the command with archil checkout . to hold the whole disk for the call, as exclusive mode would. Writes then run at local-disk speed.
  • Check out before the command starts. Pass checkoutPaths: ["src"] in the mount, and add queueMs to wait that many milliseconds for each checkout if another client holds it. queueMs alone checks out the mount’s root.
  • Mount in conditional mode. With conditional: true, the call writes anywhere nobody else has checked out, with no checkout at all. Changes run on the server, at the speed of a remote file system, and if two clients write the same part of a file, the last write to reach the disk wins.

Checkouts last only as long as the call. We recommend conditional mode for an agent’s general-purpose bash tool. For one call that writes heavily to paths no other call touches, such as a dependency install, a checkout trades sharing for local speed. Can many machines use it at once? compares the modes in depth.

· Three commands, three mountsThe same commands in three calls against one disk. Only the delete in the default mode is refused.

The same three commands run in three calls against one disk: echo ok > new.txt, echo '- ship' >> plan.md, and rm old.log. In shared mode, the default, the create and the append are committed, and rm old.log fails with Read-only file system, because a delete needs a checkout of the parent directory. In shared mode with archil checkout . at the start of the command, all three are committed. With conditional: true, all three are committed without a checkout.

Many calls at once§

Each call runs in its own microVM, so Promise.all over a list of calls is a fan-out with no pool to manage. A harness can lint, type-check, and run test shards at once against the agent’s latest changes:

TypeScript
const shards = [1, 2, 3, 4];
 
// The bash tool's mount, so every call writes without a checkout.
// Each call writes its own output file, so no two calls write the same path.
const runs = await Promise.all(
  shards.map((n) =>
    archil.exec({
      disks: { [diskId]: { disk: diskId, subdirectory: "workspace", conditional: true } },
      command: `cd /mnt/archil/${diskId} && mkdir -p results && npx vitest run --shard=${n}/4 > results/${n}.txt 2>&1`,
    }),
  ),
);
const failed = runs.filter((r) => r.exitCode !== 0).length;

The next call reads results/, and your harness reads workspace/results/ from the same disk. Branches that only read should mount readOnly: true, so no branch can damage the files the others are reading. Branches that must not see each other’s writes at all need separate copies, which is what checkpoints and branches are for.From How to fan out agent tool calls in parallel against one workspace.

· A fan-out against one workspaceEvery call mounts the same disk, writes its own result file, and exits when its command does.

The harness starts its tool calls at once with Promise.all: lint, a type check, and four test shards. Each call gets its own microVM with the same workspace disk mounted, runs its command, writes its output to its own file under results/, and exits as soon as its command finishes. The microVMs finish at different times. All the results return to the harness, and the output files stay on the disk for the next call. Nothing is left running.

Parallel calls cost little extra time. In our measurement, calls in flight together took no longer than calls made one at a time, until the largest burst, which added about 160 ms at the median. Each account can run hundreds of commands in parallel, and we raise that to thousands on request.Measured from a laptop to AWS us-west-2; see How to fan out agent tool calls in parallel. The parallelism limit is in Serverless execution.

To search many files, don’t write the fan-out yourself. disk.grep() splits the listing and the matching across many microVMs and returns structured matches, and the whole search counts as one call however many workers it uses.From Search files and the TypeScript SDK.

Limits§

Per-call compute trades a machine’s open-endedness for caps on each call, and the caps decide which steps still need a sandbox. Archil’s caps are in the following table, with what to do at each one.

LimitValueWhat to do
Command duration5 minutes, then 504 with Exec timed outSplit long work, or run it in a persistent sandbox. Writes flushed with fsync before the timeout are durable; the rest are flushed on a best-effort basis.
Compute per command2 vCPU and 4 GiB of memory, with no per-call sizingMove heavier steps to a persistent sandbox with the same disk mounted.
Options per callNo timeout, abort signal, or environment variablesPut variables in the command. A call the user cancels runs until it finishes or is stopped at the duration cap.
Command length100 KiBWrite long scripts to the disk and run the file.
Output in the responseThe trailing 128 KiB of stdout and of stderrRedirect large output to a file, and read the file back.
State between callsOnly the diskKeep dev servers, watchers, and loaded interpreters in a persistent sandbox.
Regionsaws-us-east-1, aws-us-west-2, aws-eu-west-1, and BYOCCreate disks that run commands in an AWS region. A disk in gcp-us-central1 stores data, but exec returns Exec is not enabled.

Every microVM also starts with an empty client cache, so a command that walks thousands of files reads them fresh in every call. In our test, a second rg over the repository in the same microVM took about 0.12 s, under a third of its time in a fresh one. That gap is the reason to use disk.grep() for wide searches.The limits are from Limits, Serverless execution, and Region availability. BYOC deployments run serverless execution on other clouds and on bare metal; see BYOC. The cache measurement is from How fast is a serverless tool call for an AI agent? The steps that don’t fit, and how to move an agent that depends on them, are in Migrating from an in-sandbox agent.

Price and speed§

Per-call compute pays off only when the price follows the calls and the overhead is small next to a model turn. On Archil, each disk.exec() or archil.exec() invocation is one serverless tool call, even when it chains several commands, at $0.05 per 1,000 calls on the Developer and Team plans. The call’s CPU and memory are included, and the Free plan includes 1,000 calls a month. For scale, a persistent sandbox with the same CPU and memory as a call costs $0.19 an hour at our sandbox rates, or about $0.00005 a second. One call costs about what one second of that sandbox does, however long the command runs within its limit.From Pricing & Billing: $0.055 per vCPU-hour and $0.020 per GiB-hour for sandboxes. Storage and data transfer are metered separately, and model inference isn’t included. What that means across a fleet of agents, idle time included, is the subject of What it saves.

Speed is the other half. We timed calls against one disk holding the Express repository, from a laptop about 40 ms from the region, with 50 calls per row.

· How long a tool call takesMedians end to end, including the laptop’s round trip. The first part of each bar is queueMs: starting the microVM with the disk mounted.

Median end-to-end latency of serverless tool calls, measured from a laptop on the public internet to one disk in AWS us-west-2, 50 calls per row. The first part of each bar is the median queueMs. Quick commands, on a 0 to 300 ms scale: true, one at a time, 82 ms, with 37 ms of queueMs; cat package.json 92 ms (36); ls -la lib 84 ms (36); true ten at a time 81 ms (42); true fifty at once 245 ms (138). Real work, on a 0 to 5 s scale: rg -n params . over the repository 422 ms (35); npm test, with 1,288 tests, 4.7 s (36); npm test ten at a time 4.7 s (36); npm test fifty at once 4.8 s.

Read each bar as overhead plus work. Starting a microVM with the disk mounted took 35 to 40 ms when calls arrived at a normal pace, and a harness in the same region skips the laptop’s round trip. Our engineering team’s coding agents spent a median of 2.7 s on each model turn, so tens of milliseconds per tool call barely move how long a task takes.

Two cases are slower. The first call against a newly created disk took 331 ms at the median across five new disks. A call after the disk sat untouched for 11 to 20 minutes took between 0.25 s and 1.1 s, with queueMs of 74 to 219 ms, and most of the rest was the client opening a new connection.All timings are from How fast is a serverless tool call for an AI agent?, which includes a script to reproduce them. The model-turn median is from 30 days of our team’s agent transcripts, described in Do AI agents need a sandbox running for the whole session?

Your numbers depend on your region and your harness’s distance from it. Every result carries timing.queueMs and timing.executeMs, so log both with each tool call and measure the overhead and the work where your agents run. Many agents, one machine puts this call behind a harness that serves every tenant from one process.