# Agent Code Tool

Letting a model write code instead of chaining one tool call per step costs
fewer tokens and handles real data processing. The catch is that you must run
code nobody reviewed. Secure Exec makes that a function call.

## A run_code tool

The tool handler type-checks the model's code, then evaluates it in a fresh VM
with no network and a hard timeout. Either way, the model gets back something it
can act on.

secure-exec/examples/ai-agent/src/index.ts:

```ts
interface RunCodeResult {
	ok: boolean;
	value?: unknown;
	/** Type errors or a runtime failure for the model to fix and retry. */
	feedback?: string;
}

// The handler for a `run_code` tool. The source comes from a model, so it is
// never trusted: it runs in a fresh VM with no network and a hard timeout.
async function runCode(
	source: string,
	inputs: Record<string, number[]>,
): Promise<RunCodeResult> {
	// Type-check first. Diagnostics are cheap feedback that saves a run.
	const checked = await check(
		`declare const inputs: { prices: number[] };\n${source}`,
	);
	if (checked.outcome !== "succeeded" || checked.hasErrors) {
		const feedback = checked.diagnostics
			.map((diagnostic) => `TS${diagnostic.code}: ${diagnostic.message}`)
			.join("\n");
		return { ok: false, feedback };
	}

	const result = await evaluate(source, {
		inputs,
		timeoutMs: 5_000,
		output: { capture: "stderr" },
	});
	if (result.outcome !== "succeeded") {
		return { ok: false, feedback: result.stderr ?? result.error.message };
	}
	return { ok: true, value: result.value };
}
```

- **Type-check first.** Diagnostics come back in milliseconds and never run the
  code. Most generated-code bugs stop here.
- **Pass data with `inputs`.** Never interpolate values into source.
- **Return failures as feedback.** Guest errors are results, not exceptions, so
  the handler stays a straight line.

## The retry loop

secure-exec/examples/ai-agent/src/index.ts:

```ts
// The model's first attempt misnames a field, so it gets the diagnostics back
// without the code ever running.
const first = await runCode(
	`inputs.price.reduce((sum, price) => sum + price, 0)`,
	{ prices: [5, 10, 27] },
);
console.log(first); // { ok: false, feedback: "TS2551: Property 'price' does not exist ..." }

// The corrected attempt runs.
const second = await runCode(
	`inputs.prices.reduce((sum, price) => sum + price, 0)`,
	{ prices: [5, 10, 27] },
);
console.log(second); // { ok: true, value: 42 }
```

## Give the code your tools

Expose your own functions with [host functions](/secure-exec/docs/host-functions)
so generated code can call your APIs without ever holding a credential.

## Multi-step agents

When the agent builds on its earlier steps, create one
[VM](/secure-exec/docs/vms) per conversation. Files and
[installed packages](/secure-exec/docs/npm) carry over between calls. Add a
[context](/secure-exec/docs/contexts) if variables should carry over too. Dispose
the VM when the conversation ends.

## Locking it down

The defaults are already safe for generated code: no network, a virtual
filesystem, and bounded resources. Add a `timeoutMs` to every call, and grant
only what a task needs with [permissions](/secure-exec/docs/permissions).
