Guides
AI Agents
Build AI agents with safe code execution
Why Iris for AI Agents?
AI agents that execute code face a fundamental problem: code can fail, corrupt state, or cause unintended side effects.
Iris solves this with instant forks:
- Fork before risky operations
- Execute AI-generated code in the branch
- Discard the branch on failure — original is untouched
Basic Pattern
import { Sandbox } from '@iris/sdk'
import { generateCode } from './your-llm'
async function safeExecute(sandbox: Sandbox, task: string) {
const branch = await sandbox.fork()
try {
const code = await generateCode(task)
const result = await branch.exec.run(code)
if (!result.ok) {
throw new Error(result.stderr)
}
return { success: true, result }
} catch (error) {
await branch.delete()
return { success: false, error }
}
}# from your_llm import generate_code
def safe_execute(sandbox, task: str):
branch = sandbox.fork()
try:
cmd = generate_code(task)
result = branch.exec.run(["bash", "-c", cmd])
if not result.ok:
raise RuntimeError(result.stderr)
return {"success": True, "result": result}
except Exception as e:
branch.delete()
return {"success": False, "error": e}ReAct Agent Example
import { Sandbox } from '@iris/sdk'
import Anthropic from '@anthropic-ai/sdk'
const anthropic = new Anthropic()
async function reactAgent(task: string) {
const sandbox = await Sandbox.create()
const messages = []
while (true) {
const response = await anthropic.messages.create({
model: 'claude-opus-4-7',
max_tokens: 4096,
messages: [{ role: 'user', content: task }, ...messages],
tools: [{
name: 'execute_code',
description: 'Execute a shell command in the sandbox',
input_schema: {
type: 'object',
properties: { code: { type: 'string' } },
required: ['code'],
},
}],
})
if (response.stop_reason === 'end_turn') break
const toolResults = []
for (const block of response.content) {
if (block.type === 'tool_use') {
const branch = await sandbox.fork()
const result = await branch.exec.run(['bash', '-c', block.input.code])
if (result.ok) {
toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: result.stdout })
} else {
await branch.delete()
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: `Error (exit ${result.exit_code}): ${result.stderr}`,
is_error: true,
})
}
}
}
messages.push({ role: 'assistant', content: response.content })
if (toolResults.length > 0) messages.push({ role: 'user', content: toolResults })
}
await sandbox.delete()
}import anthropic
from iris import IrisClient
iris = IrisClient()
claude = anthropic.Anthropic()
def react_agent(task: str):
sandbox = iris.sandboxes.create()
messages = []
while True:
response = claude.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
messages=[{"role": "user", "content": task}, *messages],
tools=[{
"name": "execute_code",
"description": "Execute a shell command in the sandbox",
"input_schema": {
"type": "object",
"properties": {"code": {"type": "string"}},
"required": ["code"],
},
}],
)
if response.stop_reason == "end_turn":
break
tool_results = []
for block in response.content:
if block.type == "tool_use":
branch = sandbox.fork()
result = branch.exec.run(["bash", "-c", block.input["code"]])
if result.ok:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result.stdout,
})
else:
branch.delete()
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": f"Error (exit {result.exit_code}): {result.stderr}",
"is_error": True,
})
messages.append({"role": "assistant", "content": response.content})
if tool_results:
messages.append({"role": "user", "content": tool_results})
sandbox.delete()Multi-Step Workflows
Checkpoint at each successful step so you can fork back to the last good state:
const checkpoints: string[] = []
for (const step of workflow) {
const cp = await sandbox.checkpoint.create({ name: step.name })
checkpoints.push(cp.checkpoint_id)
const result = await sandbox.exec.run(step.code)
if (!result.ok) {
// Roll back to the last good checkpoint and retry or bail
const lastGood = checkpoints[checkpoints.length - 2]
if (lastGood) await sandbox.checkpoint.restore(lastGood)
console.error(`Step ${step.name} failed:`, result.stderr)
break
}
}checkpoints = []
for step in workflow:
cp = sandbox.checkpoint.create(name=step.name)
checkpoints.append(cp.checkpoint_id)
result = sandbox.exec.run(step.cmd)
if not result.ok:
# Roll back to the last good checkpoint and retry or bail
if len(checkpoints) >= 2:
sandbox.checkpoint.restore(checkpoints[-2])
print(f"Step {step.name} failed:", result.stderr)
breakParallel Exploration
Fork from a decision point to explore multiple approaches simultaneously:
await sandbox.exec.run('python3 setup.py')
const approaches = ['approach_a.py', 'approach_b.py', 'approach_c.py']
const results = await Promise.all(
approaches.map(async (approach) => {
const branch = await sandbox.fork()
const result = await branch.exec.run(`python3 ${approach}`)
await branch.delete()
return { approach, stdout: result.stdout, ok: result.ok }
}),
)
const best = results.find((r) => r.ok)import asyncio
from iris import AsyncIrisClient
async def main():
client = AsyncIrisClient()
sandbox = await client.sandboxes.create()
await sandbox.exec.run(["python3", "setup.py"])
approaches = ["approach_a.py", "approach_b.py", "approach_c.py"]
async def run_approach(approach):
branch = await sandbox.fork()
result = await branch.exec.run(["python3", approach])
await branch.delete()
return {"approach": approach, "stdout": result.stdout, "ok": result.ok}
results = await asyncio.gather(*[run_approach(a) for a in approaches])
best = next((r for r in results if r["ok"]), None)
asyncio.run(main())Best Practices
Fork, don't mutate
Fork before any action the agent might want to roll back. Keep the base sandbox clean.
Use timeouts
Set timeout_ms on exec.run() to prevent runaway processes from blocking your agent.
Check result.ok
result.ok is true only when exit_code === 0. Always check before treating output as valid.
Clean up branches
Call branch.delete() on completed forks. Suspended sandboxes still consume quota.