boat prompt hands one of them a task, streams back what it does, keeps its memory, runs many tasks at once, survives the sandbox stopping and resuming, and lets you switch model or harness mid-task.
None of that is magic. It is exactly what you would build if you rented a bare cloud computer and put an agent on it. This page shows that boilerplate, how Boat implements it, and where you plug in your own instructions, tools, keys, or even your own harness.
What you’d have to build
Put a coding agent on a plain cloud machine and you always end up writing the same pieces:
You can still do any of these by hand. Boat never stops you from
boat ssh-ing in and running whatever you like (see Bring your own harness).
How it fits together
The agent server is a small always-on process on every sandbox. Each conversation owns one harness process and one history. The harnesses are ordinary processes running asuser in /home/user, reading ordinary config files and credentials, which is why everything below is inspectable and replaceable.
Harnesses and models
Pick the harness per prompt with--provider, or omit it to use the one you chose on the Agents dashboard.
Every model accepts a different set of reasoning levels, and the catalog changes often, so it is served live rather than written here:
boat prompt --help prints it, GET /provider-models returns it as JSON. Change model or thinking level on any prompt, in the same conversation:
Whose keys: yours or your users’
Two situations, one mechanism. The harness reads its credentials from the sandbox’s environment and auth files, and Boat fills those from one of two sources:
Subscriptions connect on the Agents dashboard the same way for all three vendors: Claude (Pro or Max), ChatGPT (Plus, Pro or Team) and Kimi (Kimi Code). Click Sign in, approve on the vendor’s page (ChatGPT and Kimi show a short code to confirm), and the dashboard reports the subscription as connected. Boat keeps the sign-in alive for you: the token is refreshed server-side before every prompt and every sandbox start, so a sandbox never comes up on an expired one. Sign out on the same row to drop it everywhere.
--no-env withholds every credential of yours; the -e values are the only ones the sandbox ever has. That is the shape of a product where each end user brings their own key or subscription and picks a harness in a selector:
Variables the harnesses read (any subset works; a harness whose vendor key is missing refuses the prompt with a credential error, the others keep working):
Kimi Code reads no credential from the environment itself; the sandbox writes these into its
~/.kimi-code/config.toml and token file before the first prompt, then the CLI refreshes the subscription token on its own.
Keys are per sandbox, not per conversation. A Sandbox shared by several users runs on one set of credentials; give each paying user their own sandbox when their keys must stay apart, and share one sandbox across conversations when the keys are yours.
DeepSeek
Claude Code and Codex can run on DeepSeek’s models instead of Anthropic’s or OpenAI’s. One credential covers both: a DeepSeek platform key, a single string starting withsk-. DeepSeek publishes an Anthropic-shaped endpoint and an OpenAI-shaped one against the same key, so neither harness is modified and neither needs a gateway in front of it.
What the sandbox exports to each harness, and which model ids to pass:
openrouter:deepseek/deepseek-v4.1-flash, openrouter:deepseek/deepseek-v4-flash-0731 and openrouter:deepseek/deepseek-v4-pro-0813, and they need only OPENROUTER_API_KEY.
Amazon Bedrock
Claude Code and Codex can run on models served by your AWS account instead of Anthropic’s or OpenAI’s API. The simplest credential is a Bedrock API key: one string starting withABSK, generated in the Bedrock console under API keys, valid in every region. IAM access keys (access key id, secret, optional session token) work too.
What the sandbox exports to each harness, and which model ids to pass:
Model access is granted per AWS account and region
The catalog above lists what Amazon Bedrock offers for each harness, not what your account may call today. Bedrock grants model access per AWS account and per region, so a model is listed here as soon as Bedrock serves it, including frontier models AWS has not granted you yet. Whether you can call one is between your account and AWS. To request access: open the Amazon Bedrock console in the region your credential uses, go to Model catalog, find the model, and choose Request model access. Some models ask for a short use case form and are approved by AWS rather than instantly. Access granted in one region does not carry to another. Pick a model your account has not been granted and the prompt comes back naming the model, the reason and the fix, for example:The provided model identifier is invalid: the us. profiles above only resolve from US regions, so from eu-west-1 pass eu.anthropic.claude-sonnet-4-6 instead, and likewise au. or jp.. A global. prefix works from any supported source region and needs a wider IAM policy, see global cross-region inference.
Conversations: the harness’s memory
The firstboat prompt on a sandbox starts a conversation: a thread of prompts and responses with its own memory, backed by the harness’s native session. Every later prompt continues it. You never handle the session id, but it is there when you want it: every prompt prints its conversation id (conversation: <id> under queued:, conversationId in --json and in the API response) and every event carries one.
boat conversations (GET /sandboxes/{id}/conversations) lists them newest first with prompt count, whether a turn is running, the last harness and model, a preview of the last prompt, and which one is the sandbox’s current conversation. That is how a thread started yesterday, or from another shell, gets its id back for --resume.
Current is per shell. Like the current Sandbox id, the current conversation is scoped to the shell you are in: --new makes the new conversation this shell’s current one, and a bare boat prompt continues it. Two shells, two people, or two machines prompting the same sandbox each keep their own thread and never trip over each other.
Parallel conversations
Conversations run at the same time, each in its own harness process, with isolated histories: The rules are the ones you would write yourself:- One turn at a time per conversation. A second prompt to the same conversation waits for the current turn, because it needs that turn’s context. Prompts to different conversations run concurrently.
- A per-Sandbox cap on concurrent turns, sized to the sandbox’s memory: a
default(8 GB) Boat runs roughly two dozen at once,largemore,smallfewer. Past the cap, prompts queue and start as turns finish; nothing is dropped. Pin an exact number with theASCII_MAX_PARALLEL_CONVERSATIONSenvironment variable on the sandbox, or size the sandbox (Machine Capabilities). - List them with
boat conversations: each row says whether a turn is running in it, so you can see the parallel work at a glance. - Events stream all conversations by default, tagged with
conversationId. Watch one withboat events --convo <id>. - Interrupt is scoped.
boat interrupt --convo <id>stops one turn; the others keep running. Bareboat interruptstops everything on the sandbox. - Steer is scoped too.
boat steer --convo <id> "..."changes one running turn without stopping it. See Steering a running turn.
Steering a running turn
A secondboat prompt waits for the running turn. boat interrupt throws it away. boat steer is the third option: it hands the running turn a new message, the agent takes it into account, and it keeps everything it was doing. This is what typing into a coding agent while it works does.
--convo and it steers the conversation this shell last prompted, exactly like a bare boat prompt continues it.
How it behaves per harness. Four of the six take a mid-turn message natively, so nothing is stopped. OpenCode and Kimi Code have no such primitive (their protocol has “prompt” and “cancel”, nothing in between), so Boat does the next best thing transparently:
A steer is always acted on, never just accepted. Handing a message to a harness is not the same as the harness doing something about it, so Boat does not take the harness’s word for it. It watches the harness’s own steering queue where there is one, waits for the queue to drain before it calls the turn finished, and where it cannot see delivery it runs the instruction as its own turn the moment the turn ends. So when
boat steer returns and the turn later reports finished, the instruction has been carried out, not merely delivered.
The API response and the event both carry native, and the event carries the exact mode: native, native-continued, fallback, or late. native-continued is the honest middle case: the harness accepted the message but its turn ended without acting on it, so Boat ran it immediately as its own turn on the same session. Nothing was interrupted and nothing was forgotten, the instruction simply lands a turn boundary later.
The fallback path keeps the harness session and all of its memory too, so nothing is forgotten there either; what it costs is the one tool call that was in flight, and the agent may redo a little of what it had just started.
A steer is its own event in the stream, never a queued prompt:
no_running_turn (409). That is deliberate: you asked to change a turn that is running, and quietly queueing a new one instead would be a surprise. Use boat prompt for that.
Agent lifecycle
A prompt moves through a small state machine you can observe withboat events or GET /prompts/{promptId}:
A steer never creates a state of its own: the turn that was already running is still the turn that finishes, and the work your message causes is part of it.
The Sandbox lifecycle sits underneath, and conversations ride along with it:
Stopping snapshots the disk, including every conversation’s history and the harness’s native session files under /home/user. A resumed or forked sandbox restores them, so --resume <id> picks up with full memory. Processes the harness started by hand (a dev server it launched, a tunnel) do not survive a stop, same as on any reboot; conversations and every config file below do.
Switching harness or model mid-conversation
Continue a conversation on a different harness and it keeps the thread:provider and model on POST /prompt.
Computer use
Every Sandbox runs a Linux desktop, and every harness on it can see and drive that desktop as ordinary tools. Nothing to install, nothing to register:computer for all six harnesses:
The daemon runs as the desktop user, so the tools act on the very screen boat desktop streams: open a stream in one window and watch the agent work in it.
What the agent gets
Clicks and keystrokes are delivered in the background by default: they land on the target window without raising it or moving the real pointer, so several windows can be driven without a focus fight. When a widget only accepts input while focused, the tool says so and the agent can retry with
delivery_mode: "foreground", which activates the window, acts, and puts focus back.
Tool names reach boat events namespaced by the harness, for example mcp__computer__click on Claude Code and Kimi Code, and mcp:computer/click on Codex.
Where it is registered
The registration is an ordinary entry in each harness’s own config file, written for you at sandbox start, and merged with anything you put there yourself:
Your own MCP servers in those files are untouched. Remove
computer from a file and that harness simply loses the tools; it is rewritten on the next sandbox start, so delete the sandbox’s daemon if you want it gone for good.
One tool, browser_prepare, is switched off for OpenCode in opencode.json tools, because OpenCode forwards MCP tool schemas to the model provider unchanged and the Anthropic API rejects that tool’s schema, which would otherwise fail every OpenCode turn on an Anthropic model. Prime Agent reaches the server from inside its Python tool, as await mcp.call_tool("computer", "click", {...}).
Practical notes
- One screen, shared. All conversations on a sandbox drive the same desktop. Two agents clicking at once will fight over it. Give each parallel GUI task its own sandbox, or serialize them.
- It survives stop, resume and fork. The daemon is a systemd user service on the sandbox, brought back automatically. The socket lives on
/run, so a resumed sandbox never inherits a stale one. - Logins persist. A Chrome profile the agent signed into is part of the sandbox’s disk, so it comes back on resume and travels to a fork.
- Screenshots are large. A full-desktop screenshot is a real image in the model’s context. Ask for a specific window (
get_window_statewith a window id) when a task loops. - Watch it, and record it. Open the stream with
boat desktopto see the agent work, and capture the run to an MP4 withascii-record-desktop. See Desktop Streaming.
Customize the harness
Every harness reads its own config files from the sandbox home. Boat adds nothing on top except its own short system prompt (which tells the harness it is running headless in a sandbox and that theboat CLI exists) and the boat skill. Everything else is yours to add, over boat ssh, boat exec, boat scp, a file the agent writes, an environment setup script, or a named snapshot so every new Sandbox starts with it.
All of it was verified end to end through boat prompt on the current harness versions; each row names the file that made the harness change its answer.
Instructions and hidden rules
Drop a rules file and every prompt on that sandbox obeys it, without the prompt mentioning it:
One
~/AGENTS.md plus one ~/CLAUDE.md therefore covers all six. APPEND_SYSTEM.md (pi, Prime) is appended to the system prompt itself rather than to the project context. When an environment clones a single repository, Claude Code starts inside that repository, so a CLAUDE.md there applies too.
MCP servers
Remote (HTTP) servers work the same way with a URL instead of a command. Claude Code’s MCP resource browsing tools are disabled on a sandbox; MCP tools are not.
Every Sandbox already has one server registered this way,
computer, in exactly these files. Adding yours next to it changes nothing about it, and it never overwrites yours. See Computer use.
Reach your own app from the sandbox
A Sandbox has no route back to your laptop, so an MCP server, a local model, or a webhook receiver running onlocalhost is invisible to the agents inside it. boat forward --reverse opens that route: a port on your machine starts answering at 127.0.0.1:<port> inside the sandbox, over the same SSH session boat ssh uses, with nothing exposed publicly on either end.
boat stop, boat resume, and boat fork; the tunnel does not, and has to be started again next time you want the agent to reach you. A local model works the same way: boat forward <id> --reverse --local 11434 puts Ollama on http://127.0.0.1:11434 inside the sandbox, ready for any harness pointed at that base URL. See boat forward --reverse for the flags and the redial behaviour.
Products that do not ship the CLI get the same tunnel in three lines, because the CLI is only wrapping stock OpenSSH:
Skills
A skill is a folder with aSKILL.md (frontmatter name and description, then the instructions). Every harness on a sandbox already has a skills directory with the boat skill in it; add yours next to it:
Command-line tools
Anything onPATH is a tool. Put a script in ~/.local/bin or /usr/local/bin (or npm i -g, pip install, apt install it) and ask for it by name; all six harnesses run it through their shell tool.
Custom in-process tools and extensions
For a tool that should show up as a native function call rather than a shell command:What is shared, what persists
- Config is per sandbox, not per conversation. Every parallel conversation on a sandbox, whatever its harness, reads the same home directory: one
AGENTS.md, one skills folder, one MCP list. Per-user rules need per-user sandboxes, or rules stated in the prompt. - Everything in
/home/useris captured on stop. Rules, MCP registrations, skills, extensions, tools you installed under home, and the harness sessions all come back onboat resumeandboat fork. Tools installed outside home (apt,/usr/local) are part of the system snapshot too. - Bake it once. An environment setup script or a named snapshot gives every new Sandbox the same rules, tools, and servers from the first prompt.
Bring your own harness
Boat’s built-in harnesses are ordinary binaries onPATH with the same credentials the agent server uses. Three ways to go beyond boat prompt:
- Drive a built-in harness yourself.
boat sshorboat execand runclaude,codex,pi,opencode,prime-agent, orkimidirectly, in any mode they support. The agent server does not lock the files or the processes. - Install a harness Boat does not ship.
boat exec "npm i -g <harness>"(or bake it into an environment or snapshot) and drive it overboat execor SSH. It coexists with the built-in ones and reads the same per-Sandbox-ekeys. - Run your own agent loop. Put a small HTTP daemon in the sandbox and talk to it directly: the Platform Guide walks through it.
boat hostgives it a URL, Webhooks tell your control plane when the sandbox is up.
boat events, attachments under ~/attachments, desktop streaming, snapshots, and forks keep working around it.
Reference
- CLI flags:
boat prompt,boat conversations,boat steer,boat events --convo,boat interrupt --convo - API: Prompt, Conversations, Events, Steer, Interrupt, with the same
new,conversationId, andconversationfields - SDKs: Python, TypeScript
- Building a product on top: Use in code, Platform Guide