Docker Sandboxes launched this week — purpose-built microVMs for coding agents running Claude Code, Codex, and Gemini. Isolated daemon, filesystem, network. Metered compute.
Right architecture for agent isolation. Wrong assumption for most cost guards.
The model most guards assume — and when it breaks
Local agent execution has a simple cost model:
Session cost = tokens × model rate
The sandbox is your machine.
Compute is free.
Move to Docker Sandboxes, E2B, Daytona, or Modal — and you now have two meters running simultaneously:
Token cost: tokens × model rate ← your guard probably tracks this
Compute cost: uptime × compute rate ← your guard probably doesn't
A retry loop that runs 45 minutes doesn't just burn token budget.
It keeps a metered microVM alive for 45 minutes.
At E2B's standard rate of ~$0.083/hour, that loop adds $0.062 in compute on top of whatever tokens it consumed.
Small in isolation.
Fatal to a session budget calibrated on token costs alone.
What a real overrun looks like
A production coding agent, $0.50 session budget, GPT-5.6 Terra pricing ($2/$12 per million):
Expected:
5 agent turns × ~3,000 tokens
Token cost: ~$0.050
Sandbox uptime: ~8 minutes
Compute cost: ~$0.011
Projected: ~$0.061 — well within budget
A tool call fails.
Agent retries.
Gets confused.
Loops.
Actual:
80+ tool calls, ~50,000 tokens
Token cost: ~$0.710
Compute cost: ~$0.065 (47 min × $0.083/hr)
Actual total: ~$0.775
The token guard would have eventually blocked on token spend.
The compute cost was accruing from the moment the sandbox spawned , no token-level guard can touch it.
Only a compute budget check or a lifetime limit can stop it.
The unified session model
interface SandboxSession {
sandboxId: string;
startedAt: Date;
computeRatePerHour: number;
spentTokenCents: number;
reservedTokenCents: number;
totalLimitCents: number;
}
function currentComputeCost(session: SandboxSession): number {
const uptimeHours = (Date.now() - session.startedAt.getTime()) / 3_600_000;
return uptimeHours * session.computeRatePerHour * 100; // cents
}
function totalCurrentCost(session: SandboxSession): number {
return session.spentTokenCents + currentComputeCost(session);
}
Pre-call guard checks the combined total before every model call:
function guardCall(
model: string,
estimatedInputTokens: number,
estimatedOutputTokens: number,
estimatedCallMinutes: number,
session: SandboxSession
): void {
const price = MODEL_PRICES[model];
if (!price) throw new Error(`Unregistered model: "${model}"`);
const tokenCost =
(estimatedInputTokens / 1_000_000) * price.inputPerM * 100 +
(estimatedOutputTokens / 1_000_000) * price.outputPerM * 100;
const additionalCompute =
(estimatedCallMinutes / 60) * session.computeRatePerHour * 100;
const projectedTotal =
totalCurrentCost(session) +
session.reservedTokenCents +
tokenCost +
additionalCompute;
if (projectedTotal > session.totalLimitCents) {
throw new BudgetExceededError({
sandboxId: session.sandboxId,
projectedTotal,
limitCents: session.totalLimitCents,
breakdown: {
currentTokenSpend: session.spentTokenCents,
currentComputeCost: currentComputeCost(session),
thisCallTokens: tokenCost,
thisCallCompute: additionalCompute,
},
});
}
session.spentTokenCents += tokenCost;
}
projectedTotal includes already-accrued compute.
If the session has been running 20 minutes, currentComputeCost reflects that before the guard evaluates the next call.
The guard sees the real cost trajectory , not just forward token spend.
The simpler version: just cap sandbox lifetime
Per-call compute modeling too granular for your workload? A lifetime limit gets you most of the protection with a fraction of the complexity:
function guardSandboxLifetime(
session: SandboxSession,
maxLifetimeMinutes: number
): void {
const uptimeMinutes = (Date.now() - session.startedAt.getTime()) / 60_000;
if (uptimeMinutes >= maxLifetimeMinutes) {
throw new SandboxLifetimeExceededError({
sandboxId: session.sandboxId,
uptimeMinutes,
limitMinutes: maxLifetimeMinutes,
});
}
}
// Call at the start of every agent turn — before any tool calls
guardSandboxLifetime(session, 30);
A 30-minute hard cap is a compute ceiling without exact rate modeling.
It also directly kills the loop scenario , the agent can't run for 47 minutes if the session terminates at 30.
One important detail: call this at the start of every agent turn, not just on LLM calls.
Compute billing accrues during tool execution, file I/O, and idle waiting , not just during inference.
Before you move to metered sandboxes
If your agents run locally today and you're planning the move to Docker Sandboxes or any metered provider, expand the budget model before you flip the switch.
Token-only cost modeling that was accurate locally becomes structurally incomplete on metered infrastructure.
A session you budgeted at $0.06 in token costs becomes $0.12 once compute time is included , before any overruns.
That gap isn't an edge case.
It's the baseline.
The two invoices are real. The guard needs to see both.
Top comments (0)