To try n2 without building anything, open the Playground. To drive your own Mac, install Yutori MCP — it ships a local loop already wired up.
Components
The loop is
N2ComputerAgent from the Python SDK: it owns the turns, the message history, the screenshot window, and the coordinate math. Everything specific to Daytona lives in one adapter class, DaytonaComputer. Both are in examples/navigator_n2_daytona.py, along with a main that creates the sandbox, runs the agent, and deletes the sandbox.
n2 typically answers with one computer_batch call, which chains several actions against a single screenshot. It can also call bash, or one of the file tools (read, write, edit) that the example serves through the SDK’s ShellFileToolsMixin. N2ComputerAgent runs whatever comes back and replies with a fresh screenshot for the batch, or the tool’s own output otherwise.
Prerequisites
- uv — the script declares its dependencies inline (PEP 723), so
uv runpicks a Python 3.10+ interpreter and installs them into an isolated environment. Nothing to pin by hand. - A Yutori API key — see Authentication.
- A Daytona API key from the Daytona dashboard.
YUTORI_API_KEY, then the stored login. A stale YUTORI_API_KEY
left in your shell silently shadows yutori auth login and the run fails with 401.
Run it
Run the script straight from the SDK repository with a task —uv fetches it, resolves its inline dependencies, and runs it:
finally block, so it does not outlive the run even when the run fails.
Two flags are worth knowing: --max-steps N raises the turn budget from its default of 50,
and --record saves a screen recording of the desktop to n2-daytona-run.mp4 when the run
ends, which is how you review a run after the sandbox is gone.
Adapting it with a coding agent
Point your coding agent to the script, and describe what needs to change for your setup.Adapter details
DaytonaComputer is the class N2ComputerAgent calls to observe and act. Most methods pass through to Daytona unchanged. The exceptions are below:
- Sandbox lifecycle — wait for
computer_use.start()to finish before interacting with the sandbox, and always delete it in afinallyblock to stop billing. - Coordinates — n2 predicts them in a normalized 1000×1000 space.
N2ComputerAgentrescales them to the desktop’s native pixel size before calling the adapter, so the adapter does not do any conversions. - Typing —
keyboard.typefails on control characters and silently truncates long strings, so send\nand\tas key presses and chunk the rest. - Scrolling — Daytona wants wheel notches. The adapter declares a
model_actionparameter, so the loop hands it the model’s own call, whosedirectionandamountare already notches. The pixel distance the loop passes otherwise — one notch ofamountbeing a tenth of the screen — is converted back as the fallback. bashresults — the command’s output, with anExit code Nheader prepended on failure. The SDK’sformat_shell_outputtruncates each result at 30,000 characters, andN2ComputerAgentholds a 256 KiB backstop above that, so a largecatcannot push the request over the 10 MB limit.
bash promises a working directory that persists across calls, but every exec is a fresh process, so a cd would be forgotten by the next command. The adapter honors the contract: each command prints its final $PWD on a sentinel line, and the next command starts there through exec’s cwd. run_in_background detaches the command with nohup and returns its pid and output file.Knowing when to stop
Without a limit, a loop will keep going on a task it cannot finish.N2ComputerAgent’s max_steps caps the number of model turns; the script defaults to 50 and exposes it as --max-steps. When that budget is spent, agent.stopped_by reads "max_steps", and the script spends one last completion asking the model to summarize how far it got before exiting non-zero. For a stop condition of your own, a callback’s on_run_continue runs before each turn and ends the run when it returns False.
Execution errors are not fatal to the run. A batch stops at the first action that fails, and the tool result tells the model which action failed along with a screenshot of the resulting state; a bash call the adapter cannot run at all comes back as an [ERROR] bash failed: ... result, and a nonzero exit code is simply part of the output. A timeout is deliberately not an error: the adapter reports it as an ordinary Command timed out after 120s result, because an expiry is something the model should react to rather than a failure of the harness. In every case the model sees what happened and routes around it.
For anything unattended on a machine that is not disposable, add an action_confirmation_callback. N2ComputerAgent calls it before every action except screenshot, wait, mouse_move, and scroll, and skips the call if it returns False. The sandbox here is disposable, so the script does not confirm anything. On a harness driving a real machine, keep shell confirmation mandatory.
Going further
- Pick a snapshot with the apps you need.
daytonaio/sandbox:0.6.0is a bare XFCE desktop at 1024×768. Build your own to give the model a browser, an editor, or a dataset. - Watch the run.
(await sandbox.get_preview_link(6080)).urlis a noVNC view of the desktop, which is the fastest way to see what a failing run is doing. - Send the whole history.
N2ComputerAgentkeeps every turn and strips only the images outside the two newest image-bearing messages, which is what the server would drop anyway. Trimming messages yourself risks the model repeating work it has already done. A run long enough to approach the context window compacts instead of truncating —compactor="auto"is the default, and each pass fires theon_compactioncallback the script prints from.
Related
Navigator n2
The model reference — tools, actions, coordinates, and request fields.