machinemode.io
Guide · Updated 2026-10-23

Machine Mode and code mode

Code mode has agents write code against tools. Machine Mode makes the tools easy to write code against.

Code mode is a pattern in which an AI agent writes a short program that calls tools, instead of spending one model turn on each call. Machine Mode is a standard for what a command‑line tool returns when that program calls it. They're two ends of one idea: agents operate software better through code than through a chat of one‑off tool calls.

Cloudflare named the pattern in September 2025, and Anthropic described the same move for MCP two months later. This page shows how to script a Machine Mode tool from a code‑mode runtime, and from bash, which Michael Livshits fairly called the first code mode.

What a script needs from a tool

A shape it can rely on, a way to tell the call finished, and errors it can branch on.

Code‑mode runtimes turn each tool into a function the model can call, and they build those functions from the tool's schemas. When a tool declares no output schema, the generated signature has nothing to say about the result. Pydantic AI's Code Mode documentation puts it plainly: the signature shows -> Any, “so the model has to guess the result's shape.” Cloudflare's codemode package types such results as unknown, and in October 2026 started forwarding MCP's outputSchema so the real type shows instead.

A Machine Mode tool answers tool schema --output json and tool capabilities --output json with no credentials and no network, so a binding generator can read exact event types before the agent runs anything. Every run then ends with an aoi:summary whose ok field says whether it worked, and every error carries a retryable flag the generated code can test.

A typed binding in one file

Node and TypeScript. It spawns the command, checks seq for gaps, and refuses to report success without a summary.
run-aoi.tsts
import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline';

type AoiEvent = { type: string; aoi?: { seq?: number }; [k: string]: unknown };
type AoiErrorEvent = AoiEvent & { category: string; code: string; message: string; retryable: boolean; retry_after_ms?: number };

export class AoiError extends Error {
  constructor(readonly event: AoiErrorEvent) { super(event.message); }
}
export class AoiIncomplete extends Error {}

// Runs one machine-mode command and yields its domain events.
// Throws AoiError on a process-level aoi:error, AoiIncomplete if lines go
// missing or the run ends without a successful aoi:summary.
export async function* runAoi<E extends AoiEvent>(cmd: string, args: string[]): AsyncGenerator<E> {
  const child = spawn(cmd, [...args, '--output', 'jsonl'], { stdio: ['ignore', 'pipe', 'inherit'] });
  const exit = new Promise<number | null>((resolve) => child.on('close', resolve));
  let next = 0;
  let summary: AoiEvent | undefined;

  for await (const line of createInterface({ input: child.stdout })) {
    const ev = JSON.parse(line) as AoiEvent;
    if (ev.aoi?.seq !== undefined && ev.aoi.seq !== next++) throw new AoiIncomplete('seq gap: lines were lost');
    if (ev.type === 'aoi:error' && (ev.scope ?? 'process') === 'process') throw new AoiError(ev as AoiErrorEvent);
    if (ev.type === 'aoi:summary') summary = ev;
    else if (!ev.type.startsWith('aoi:')) yield ev as E;
  }

  const code = await exit;
  if (!summary) throw new AoiIncomplete('stream ended without aoi:summary');
  if (summary.ok !== true || code !== 0) throw new AoiIncomplete(`run failed: ${String(summary.reason ?? code)}`);
}

One simplification to know about: the spec says a final line with no trailing newline is truncated and must be discarded, and Node's readline hands that line over anyway. A production binding should read raw chunks and only parse complete lines.

agent-script.tsts
type Hit = { type: 'hit'; id: string; title: string; rank: number };

const ids: string[] = [];
try {
  for await (const hit of runAoi<Hit>('outline', ['search', 'agent operable', '--limit', '50'])) {
    ids.push(hit.id);
  }
} catch (e) {
  if (e instanceof AoiError && e.event.retryable) {
    await sleep(e.event.retry_after_ms ?? 1000); // then retry, with your own attempt limit
  } else {
    throw e; // AoiIncomplete or a non-retryable error: report it, don't claim success
  }
}

The same idea extends to generated code: read the capability manifest, emit one function per command with its event types from the schema, and wrap each in runAoi. No such generator ships yet.

From bash

The runtime every agent already has.

Agents that work in a shell write pipelines, and the CLI side of that argument has always been jq. Machine Mode keeps that habit and adds one check at the end.

search.shsh
outline search "agent operable" --output jsonl --limit 50 > run.jsonl

# The results
jq -r 'select(.type == "hit") | .id' run.jsonl

# Did the run finish and succeed? Only the last line can say.
tail -n 1 run.jsonl | jq -e '.type == "aoi:summary" and .ok == true' > /dev/null \
  || echo "incomplete or failed run" >&2

Paging and retries

Bounds belong to the tool as well as the runtime.

Code‑mode runtimes cap memory and output size to protect the sandbox, and a call that hits a cap fails or gets cut short. A Machine Mode read takes --limit, sets truncated when it stops early, and returns next_cursor, so a script can page through results on purpose.

page-through.tsts
let cursor: string | undefined;
do {
  const args = ['list', '--limit', '100', ...(cursor ? ['--cursor', cursor] : [])];
  // runAoi variant that also returns the summary, so the script can read next_cursor
  const { events, summary } = await collectAoi('tickets', args);
  handle(events);
  cursor = summary.truncated ? summary.next_cursor : undefined;
} while (cursor);
  • Retry only when retryable is true, wait at least retry_after_ms with jitter, and keep your own attempt limit.
  • A tool sets retryable:falsewhen it can't tell whether a write landed, so a script never repeats a payment because of a timeout.
  • For writes you mean to retry, pass --idempotency-key where the capability manifest lists supports_idempotency_key.

Where the tool runs

A process, so the sandbox decides.

An AOI‑CLI tool is a process. Container‑style sandboxes can run it directly. V8‑isolate sandboxes such as Cloudflare Workers can't spawn processes, so there the tool runs elsewhere and the script reaches it through a binding, the same way those runtimes reach any outside service.

The safety flags in the capability manifest (read_only, destructive, requires_confirm) tell a runtime which calls to gate. A script can pass --confirmas easily as a person can, so decisions that need a human belong in the runtime's approval step.

Sources

  1. Kenton Varda and Sunil Pai, “Code Mode: the better way to use MCP,” Cloudflare blog, 2025‑09‑26.
  2. Adam Jones and Conor Kelly, “Code execution with MCP,” Anthropic Engineering, 2025‑11‑04.
  3. Pydantic AI, “Code Mode” documentation (pydantic-ai-harness), accessed 2026‑10‑23.
  4. cloudflare/agents pull request #2472, forwarding outputSchema in codemode, merged 2026‑10‑12.
  5. Michael Livshits, “Ergonomic coding surfaces for agents,” 2026‑05‑15.
  6. Xingyao Wang et al., “Executable Code Actions Elicit Better LLM Agents,” ICML 2024 (arXiv 2402.01030).