# Coding agents

> Run coding agents like Claude Code and Codex in isolated Flax Cloud sandboxes with full terminal, filesystem, and git access. Clone a repo, let the agent work, and pull back the diff.

Coding agents like **Claude Code** and **Codex** write, debug, and refactor code on their own. They
need a real machine with a terminal, a filesystem, and git, and you do not want that machine to be
your laptop or your CI runner. A Flax Cloud sandbox gives each agent a disposable Linux environment,
isolated from your infrastructure, that you create in seconds and throw away when the task is done.

The loop is always the same:

1. **Create** a sandbox with the agent CLI installed.
2. **Clone** the repo (or upload files) into `/workspace`.
3. **Run the agent headless** with your prompt and API key.
4. **Extract the result**: a `git diff`, structured JSON, or the modified files.
5. **Tear down**, or snapshot the prepared environment to reuse it.

Every primitive below is part of the [Python SDK](./sdk.md); the
[JavaScript/TypeScript SDK](./sdk-js.md) mirrors the same surface with camelCase names.

## Prepare an agent environment once

The agents ship as Node CLIs, so build a [template](./concepts.md#templates) that has them
pre-installed. You build it once and then create sandboxes from it by name.

```python
import os
from flaxcloud import FlaxClient, FlaxImage

flax = FlaxClient()  # reads FLAX_API_KEY

image = (
    FlaxImage.node("22")  # Codex needs Node 22+; Claude Code is happy here too
    .apt_install(["git", "ripgrep", "ca-certificates"])
    .npm_install(["@anthropic-ai/claude-code", "@openai/codex"])
    .env({"IS_SANDBOX": "1"})  # let Claude Code run unattended as root (see below)
    .workdir("/workspace")
)

tpl = flax.create_template_from_image_definition("agent-cli", image, wait=True)
print(tpl.status)  # "ready"
```

`npm_install` installs globally, so `claude` and `codex` are on `PATH` in every sandbox created from
this template. Watch the build with `flax.build_logs(tpl.id)` if you want to see it happen.

**Why `IS_SANDBOX=1`?** Commands in a Flax sandbox run as `root`, and Claude Code refuses
`--dangerously-skip-permissions` as root unless it can tell it is inside a sandbox. Setting
`IS_SANDBOX=1` is that signal, and it is honest here: the gVisor sandbox is the real security
boundary. Baking it into the template applies it to every sandbox you create. It is not a secret, so
it is fine in the image (unlike API keys).

> Don't want to manage a template yet? Skip this step and install the CLI at runtime on a `node`
> sandbox instead: `sb.run("npm install -g @anthropic-ai/claude-code")`. It works immediately but
> adds a cold install to every run, so a template is worth it once you run agents regularly.

**Never bake API keys into the template.** Pass them per sandbox with `env=` (below) so the secret
lives only for the life of that sandbox and never lands in a reusable image or snapshot.

## Claude Code

[Claude Code](https://docs.claude.com/en/docs/claude-code) is Anthropic's agentic coding tool. Create
a sandbox from the template, pass `ANTHROPIC_API_KEY` through `env`, clone a repo, and run it in
headless mode with `-p`.

`--dangerously-skip-permissions` auto-approves the agent's tool calls. That flag is reckless on a
laptop but appropriate here: the sandbox itself is the security boundary, with dropped Linux
capabilities, no privilege escalation, and CPU/memory/pid limits (see [Security](./security.md)).

```python
import os
from flaxcloud import FlaxClient

flax = FlaxClient()

sb = flax.create_sandbox(
    template="agent-cli",
    memory_mb=1024,  # agents are memory-hungry; bump above the 512 MB default
    env={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
)
try:
    sb.git.clone("https://github.com/your-org/your-repo.git", "/workspace/repo")

    result = sb.run(
        'claude -p "Add error handling to all API endpoints" --dangerously-skip-permissions',
        working_directory="/workspace/repo",
        timeout=600,
    )
    print(result.stdout)

    diff = sb.run("git diff", working_directory="/workspace/repo")
    print(diff.stdout)  # the change the agent made
finally:
    sb.destroy()
```

### Structured output

Use `--output-format json` to get a machine-readable result you can drive a pipeline with. The JSON
includes the final text and a `session_id` you can resume from.

```python
import json

result = sb.run(
    'claude -p "Review this codebase and list security issues as JSON" '
    "--output-format json --dangerously-skip-permissions",
    working_directory="/workspace/repo",
    timeout=600,
)
response = json.loads(result.stdout)
print(response["result"])
```

For long tasks, stream the output live instead of waiting for the command to finish:

```python
stream = sb.run_stream(
    'claude -p "Find and fix all TODO comments" --dangerously-skip-permissions',
)
for chunk in stream:
    print(chunk, end="", flush=True)
print("exit:", stream.exit_code)
```

### Project context

Write a `CLAUDE.md` into the repo to give the agent standing instructions before it starts.

```python
sb.upload(
    "/workspace/repo/CLAUDE.md",
    "You are working on a Go microservice.\n"
    "Use structured logging with slog.\n"
    "Follow the error-handling conventions in pkg/errors.\n",
)
sb.run(
    'claude -p "Add a /healthz endpoint" --dangerously-skip-permissions',
    working_directory="/workspace/repo",
    timeout=600,
)
```

## Codex

[Codex](https://developers.openai.com/codex/cli) is OpenAI's coding agent. The pattern is identical;
only the CLI and the API key change. Run it non-interactively with `codex exec`. Use
`--dangerously-bypass-approvals-and-sandbox` to skip both approvals and Codex's own OS-level sandbox:
that flag is the one OpenAI recommends when you are already inside an isolated sandbox VM, and it
avoids Codex trying to nest its Landlock/seccomp sandbox inside gVisor. Add `--skip-git-repo-check`
to bypass the git-ownership check and `-C` to point it at the cloned repo.

```python
import os
from flaxcloud import FlaxClient

flax = FlaxClient()

sb = flax.create_sandbox(
    template="agent-cli",
    memory_mb=1024,
    env={"OPENAI_API_KEY": os.environ["OPENAI_API_KEY"]},
)
try:
    sb.git.clone("https://github.com/your-org/your-repo.git", "/workspace/repo")

    result = sb.run(
        "codex exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check "
        '-C /workspace/repo "Add tests for the utils module"',
        timeout=600,
    )
    print(result.stdout)

    print(sb.run("git diff", working_directory="/workspace/repo").stdout)
finally:
    sb.destroy()
```

Add `--json` for a JSONL event stream, or `--output-schema schema.json` to constrain the final
response to a JSON Schema.

## Best-of-N with forks

The strongest reason to run agents on Flax is running *many* attempts cheaply. Prepare a repo once,
snapshot it, then start several sandboxes from that snapshot and let each attempt the task
independently. Score the diffs and keep the winner. See [Forks](./forks.md) and
[Snapshots](./snapshots.md).

```python
import os
from flaxcloud import FlaxClient

flax = FlaxClient()
task = "Make the failing test in tests/test_orders.py pass without weakening it"

# 1. Prepare a clean base and snapshot it (no secret baked in).
base = flax.create_sandbox(template="agent-cli", memory_mb=1024)
base.git.clone("https://github.com/your-org/your-repo.git", "/workspace/repo")
snap = base.create_snapshot("orders-base")
base.destroy()

# 2. Run N attempts from the same starting point, scoring each and tearing it down.
attempts = []
for i in range(4):
    sb = flax.create_sandbox(
        snapshot_id=snap.id,
        memory_mb=1024,
        env={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
    )
    try:
        sb.run(
            f'claude -p "{task}" --dangerously-skip-permissions',
            working_directory="/workspace/repo",
            timeout=900,
        )
        passed = sb.run("pytest -q", working_directory="/workspace/repo").exit_code == 0
        diff = sb.run("git diff", working_directory="/workspace/repo").stdout
        attempts.append({"passed": passed, "diff": diff})
    finally:
        sb.destroy()  # free the concurrency slot before the next attempt

# 3. Keep the best result.
winner = next((a for a in attempts if a["passed"]), attempts[0])
print(winner["diff"])
flax.delete_snapshot(snap.id)
```

Because each sandbox is fully isolated, the attempts cannot interfere with each other, and a bad
diff in one never touches the others or your real repo. This runs the attempts one at a time, so it
works on any plan. To run them in parallel, launch up to your plan's concurrent-sandbox limit at
once (Free 1, Pro 2, Builder 5, Team 10) with threads, keeping each sandbox alive only until you
have captured its diff.

## Shipping the result

The agent's work lives in the sandbox's git checkout, so you push it like any other branch. Provide a
short-lived token through `env` and let the agent's own commands (or your script) push a branch you
open a PR from.

```python
sb = flax.create_sandbox(
    template="agent-cli",
    memory_mb=1024,
    env={
        "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"],
        "GITHUB_TOKEN": os.environ["GITHUB_TOKEN"],
    },
)
repo = "github.com/your-org/your-repo.git"
sb.git.clone(f"https://x-access-token:{os.environ['GITHUB_TOKEN']}@{repo}", "/workspace/repo")

sb.run('claude -p "Fix the bug in the checkout flow" --dangerously-skip-permissions',
       working_directory="/workspace/repo", timeout=900)

with sb.create_session() as s:
    s.run("cd /workspace/repo")
    s.run("git checkout -b agent/checkout-fix")
    s.run('git -c user.email=agent@example.com -c user.name=agent commit -am "Fix checkout"')
    s.run("git push origin agent/checkout-fix")
```

Open the pull request from your own code with the GitHub API once the branch is pushed.

## Notes

- **Memory.** Agents and their toolchains want headroom. Start at `memory_mb=1024` and raise it if a
  build or test run gets killed. Per-plan caps apply (Free 1 GB, Pro 2 GB, Builder 4 GB, Team
  8 GB); see [Plans & limits](./plans-and-limits.md).
- **Secrets.** Pass `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and git tokens through `env=` at create
  time, never in the template or a snapshot. They stay scoped to that sandbox.
- **Any agent works.** A sandbox is a full Linux machine, so anything you can `npm install` or
  `pip install` (OpenCode, Amp, your own harness) runs the same way. Add it to the template's
  `npm_install`/`pip_install` list.
- **Cleanup.** Destroy sandboxes when done, or [stop](./concepts.md#lifecycle) them to resume later.
  Idle sandboxes are reclaimed automatically.
