conchv0.1.8

Using conch

Sandboxes

A sandbox is a machine in the cloud that conch creates for you, so a long agent task runs away from your laptop and keeps running when you close it. conch makes the sandbox, installs itself there and adds it as a remote machine: -m and every other machine feature work with it.

In the tree they sit together under Sandboxes, one group per provider, so a fleet of them doesn't fill the top:

▾ ● local
▾ ● busybox
▾ ▤ Sandboxes            3
  ▾ Daytona              2
    ▸ ● sdx01
    ▸ ○ sdx02   stopped
  ▾ boat.dev             1
    ▸ ● sdx01

Each one opens like any machine — its projects, agents and terminals underneath — and m on its row starts, stops or deletes it. Selecting Sandboxes, or one provider, shows what is in it with each sandbox's state. The group appears once there is a sandbox; the same label under two providers is two different sandboxes, and each keeps its own row.

Three providers are supported:

ProviderWhat it isHow conch gets in
Daytonasandboxes with an ssh gateway, sized in vCPUs, memory and diska fresh ssh token per connection, through Daytona's gateway
boat.devwhole Ubuntu machines in named sizes, with an sshd of their ownyour own public key, authorized in the sandbox
Devin Cloudthe VM of a Devin Cloud session, with Devin in it; sized by Devin, placed on a platform or an outpost pooldevin ssh, the devin CLI's own approved connection

Everything on this page works with all of them unless it says otherwise. Where they differ — sizes, prices, snapshots, what stopping keeps — it says so. Devin Cloud does least of it: no preview links, snapshots or billed cost, since its API has none to give.

Setting up

Create an API key in the provider's dashboard and put it in your environment:

export DAYTONA_API_KEY=dtn_…     # Daytona
export BOAT_API_KEY=boat_…       # boat.dev
export DEVIN_API_KEY=cog_…       # Devin Cloud: a service user's key

Devin Cloud also needs the devin CLI, installed (conch agent install devin) and signed in (devin auth login): its API makes the session, but only the CLI's login can open the way into its VM. conch makes the session in the organization $DEVIN_ORG_ID names, or else the one Devin says the key's sessions run in.

Only the provider you use needs a key. A provider with no key is still listed, and says what is missing rather than failing when you try.

By default conch reads the key from the environment every time and never writes it down — set it where your shell will have it (~/.zshrc), and restart conch, since a running process keeps the environment it started with. The key is read by whatever connects: the TUI and the conch sandbox commands, never the server, and it never goes over the protocol. A different variable can be named in Settings (, → Sandboxes → the provider) or as api_key_env in [sandbox.<provider>], along with the snapshot, region, auto-stop and what of your environment every new sandbox gets. Each provider has its own section, and its own key.

If you would rather not keep it in your shell at all, Settings → Sandboxes → the provider → API key takes the key itself. It is then written to config.toml (0600) as it is: anything running as you can read it, and it travels with a backup or a synced dotfile, so it is the convenient choice rather than the careful one. A key kept there wins over the variable, the settings show only its last few characters, and emptying the field gives the variable back.

In the TUI

Press M, choose New sandbox… and then the provider — Daytona (d), boat.dev (b) or Devin Cloud (e), and whatever conch grows later sits beside it under the first letter of its name nobody else has; esc goes back to the machine menu. The dialog takes a label, a snapshot, a size and the names of environment variables to pass in; leave any of them empty for the defaults. boat.dev's machines come in named sizes rather than numbers: put small, default or large where the snapshot goes and leave vCPUs, memory and disk empty — a number there is refused, with that sentence, rather than quietly ignored. Devin Cloud's dialog asks for a Platform where the snapshot goes and no size at all, and says before anything is made that Devin works, and bills, in the session. conch creates the sandbox, sets itself up there and adds the machine to the tree, which takes a minute or two — and says what it is doing while it does it, in the status bar with a spinner: asking boat.dev for a sandbox…, waiting for hull to answer…, copying conch to hull (16 MB)…. A provider says a sandbox is started before its sshd is (Daytona says so within a second), so conch waits for it to answer, and tries again when a connection drops — copying 16 MB is the longest thing it asks of one — rather than throwing the sandbox away at the first refusal. That line stays until the job is done, unlike a message that fades. If setting up fails, the sandbox is deleted rather than left to cost — it says that too — and the reason is shown.

m on a sandbox's row adds:

ItemWhat it does
Start sandboxon a stopped sandbox: starts it and connects
Stop sandbox…ends its agents and terminals; its files stay. Asks first, saying how many agents are still working
Delete sandbox…deletes it and everything in it, and removes the machine. Asks first, listing the branches conch last saw there with commits no remote has, and uncommitted changes

A stopped sandbox shows stopped in the tree, and a spinner while it is starting, stopping or being deleted. Remove machine only takes it out of the tree; the sandbox keeps running, and costing, until conch sandbox -provider P rm.

Looking at what runs inside

An agent that starts a dev server in a sandbox has nothing to show for it — the port is inside somebody else's machine. m → Open a port in the browser… asks which port (3000 to begin with, then whatever you used last) and opens a link to it. From a shell: conch sandbox -provider P url ID PORT, with -open to open it and -expires to change how long it lasts (an hour by default, a day at most).

The link carries its own credential, so it works in any browser — and so anyone with the link can reach that port until it expires. Daytona keeps 22222, 2280 and 33333 for itself; conch refuses those rather than letting you wonder why the page is wrong. boat.dev's links are the other kind: one per port, at the sandbox's own address, which stays the same the next time you ask and lasts as long as the sandbox — -expires means nothing to it.

conch tries the link before opening it. When the proxy reaches the sandbox and finds nothing there — a 502 proxy upstream error — it says so instead of opening that page, and says what usually mends it: the server has to listen on 0.0.0.0, not 127.0.0.1. The proxy arrives on the sandbox's own network interface, where a loopback-only server cannot be seen; next dev --hostname 0.0.0.0, vite --host, rails s -b 0.0.0.0. The link is put on your clipboard either way, so you can try it again once the server is up.

Keeping one to make others from

A sandbox that is set up — the repository cloned, dependencies installed, an agent logged in — can be kept and used as the starting point for the next one. m → Keep a snapshot…, or conch sandbox -provider P snapshot ID, names it; conch sandbox -provider P snapshots lists what is kept and -rm NAME forgets one. Then New sandbox…, or conch sandbox -provider P create -snapshot NAME, starts from it.

Daytona takes a cold snapshot, which wants the sandbox stopped first, and says so if it isn't — nothing is changed when it refuses. boat.dev takes it from a running sandbox instead, and keeps ten named ones, so forgetting one is how you make room. Either way a snapshot takes a few minutes to become usable, is kept until you forget it, and is charged for storage meanwhile.

What one has cost

A sandbox costs while it runs, so the tree shows how long each has been up — 4h 12m beside its row, and the total on the Sandboxes page. conch asks the provider what it has billed, every quarter of an hour, and shows that. Daytona reports per sandbox on its own analytics host; conch learns which organization to ask from the sandbox itself, so there is nothing to configure. boat.dev answers with one number for the whole sandbox — the seconds it has run and what they cost — so its detail page shows that single stretch rather than a period each. Those figures settle hours behind what is running, so until they arrive conch falls back to an estimate, marked with a ~: 4h 12m · ~$0.59 is conch's arithmetic, 4h 12m · $0.03 is the provider's own number.

Set what an hour costs in Settings → Sandboxes → the provider → Price an hour (per vCPU, per GiB of memory, per GiB of disk, from the provider's own pricing page) and the money appears beside the time: 4h 12m · $0.59. conch ships no price list, because providers change theirs and a stale one misleads.

m → What it has cost… asks the provider afresh and shows where the money went, newest first — each stretch the sandbox was billed for, what it was doing then, and what that cost:

Daytona f4c07743-2955-4c24-9977-9db242af22af

$0.009 since 26 Sep 09:00

26 Sep 17:32  5m 48s   $0.006  running · 1 vCPU, 1 GiB, 3 GiB disk
26 Sep 16:45  47m 02s  $0.000  stopped · 3 GiB disk
26 Sep 16:43  1m 35s   $0.002  running · 1 vCPU, 1 GiB, 3 GiB disk

conch sandbox -provider P usage [-since 720h] ID prints the same as a table. A provider with nothing to report yet says so rather than showing an empty page.

Stopped is not always free. The disk a stopped sandbox keeps is charged until the sandbox is deleted — on a small Daytona sandbox that is most of what it costs over a week. boat.dev is the exception: it charges for machine time only, so a stopped sandbox there costs nothing and shows nothing. A stopped sandbox's row shows what it has cost all told, and — while the provider has yet to report anything — the rate its disk keeps costing: disk $0.23/month rather than nothing, and the Sandboxes page totals it: stopped ones keep $0.47/month of disk. Rates are shown in whatever unit says something — an hour, a day or a month — because a few GiB of disk rounds to $0.000/h and tells nobody anything. It is a rate rather than a total because a provider doesn't say when a sandbox stopped, and a total conch cannot know would be worse than the rate it can.

Stopping one nobody is using

A sandbox costs while it runs, and it is easy to leave one running. conch pauses a sandbox when no agent there is working, none is waiting for an answer, and no pane has printed for 30 minutes — Settings → Sandboxes → the provider → Stop when idle changes it (15, 30, 60, 120 or never), per provider, as idle_stop in [sandbox.<provider>]. The status bar says which sandbox went and why, its files are kept, and m → Start sandbox brings it back where it was.

This is conch's own watching, not the provider's. Daytona's auto_stop counts only what reaches it from outside — an ssh connection, a preview request, an API call — so an agent working quietly inside looks idle to it and would be stopped mid-task. conch leaves that timer off and watches the agents and panes it can actually see. boat.dev has no idle timer at all, only a lifetime from when the sandbox was made: conch gives a new one a day, which auto_stop changes, and its own watch is what stops one nobody is using before then.

Starting one again

Stopping a sandbox ends every process in it — that is what stopping means, and the provider keeps the filesystem rather than the memory. The work itself stays: the checkout is as it was, and an agent's conversation is saved in the sandbox's own home.

So conch writes down what was running — each agent, its conversation and its folder, and each terminal — and offers it back when the sandbox starts again:

sdx1 was running 2 Claude Code agents and a terminal when it stopped. Start them again? 2 agents carry on where they left off.

Each is started in turn, and conch says what came of it: started 3 of them again in sdx1, or the reason each one didn't — a sandbox that comes back with nothing showing and nothing said is the thing to avoid. The first pane to start is opened, so it is plain that something is running. An agent whose conversation has gone is started afresh in the same folder rather than skipped; any other refusal is reported instead, so a busy or broken server doesn't quietly get a second agent.

Nothing is started until you say yes: an agent picking up where it left off is not always what you want to happen twice. Settings → Sandboxes → the provider → Bring back what was running turns the whole thing off, per provider (restore_running), and then conch keeps no note of what ran — the conversations are still on the sandbox's disk, and the Sessions view still finds them. Agents with a saved conversation are resumed on it; one that saved none starts afresh, and the question says which is which. The note survives conch restarting, and is forgotten when the sandbox is deleted or when something is already running there.

Deleted somewhere else

A sandbox deleted in the provider's own interface is gone as far as conch is concerned, however long Daytona takes to finish: conch sandbox -provider P ls shows it as gone rather than sitting at destroying, starting or stopping it says so straight away instead of waiting, and the machine it left behind is cleared with conch sandbox -provider P rm ID (or Delete sandbox… in the tree, which then only tidies up). The listing says as much when anything is in that state.

Commands

conch sandbox -provider daytona create -label fix-login   # make one, install conch, add it as a machine
conch sandbox -provider boat create -label hull           # the same with boat.dev
conch -m fix-login task -cwd /home/daytona/api "Fix the flaky tests"   # a branch in a checkout there
conch -m fix-login task "Plan a CLI for tracking expenses"             # an agent in its home, no repository (t on its row)
conch sandbox -provider daytona ls             # conch's Daytona sandboxes and their state
conch sandbox stats                            # every provider's sandboxes at once
conch sandbox -provider daytona ssh fix-login  # a shell in it
conch sandbox -provider daytona ssh fix-login git -C api status   # or one command, exiting with its status
conch sandbox -provider daytona stop fix-login # ends its agents and terminals; files stay
conch sandbox -provider daytona start fix-login
conch sandbox -provider daytona rm fix-login   # deletes it and everything in it

create takes -snapshot, -cpu, -memory and -disk (GiB), and -env NAME to pass one of your environment variables in, say CLAUDE_CODE_OAUTH_TOKEN. A named variable that isn't set is refused before anything is created. If setting conch up fails after the sandbox was made, create offers to delete it so it doesn't keep costing; -yes deletes it without asking.

-provider daytona or -provider boat, in front of any subcommand, says whose sandboxes you mean. Every subcommand but stats needs it, and there is no default: a command without it is refused rather than guessing at an account you didn't pick, and a sandbox named under a provider that isn't its own — -provider boat stop fix-login for a Daytona sandbox — is refused with the provider it does belong to.

conch sandbox stats needs no provider: it asks every provider at once and lists all their sandboxes in one table, with the provider in the first column, then a line for each provider — how many sandboxes it has, how many are in each state, the vCPUs and memory of the running ones, and how many have no machine here. A provider with no key says it is not set up; one that can't be asked (a bad key, the service down) says why, shows its machines' state as ?, and makes the command fail, so a script can tell. -provider P narrows it to one.

conch sandbox -provider P ssh ID opens a shell in a running sandbox, through the same fresh access conch connects with, so there is no token to copy. Anything after the ID is run there instead, and it exits with its status, so it works in scripts; -t gives such a command a terminal, for one that draws a screen (conch sandbox -provider daytona ssh -t fix-login htop). A stopped sandbox is refused with the command that starts it.

start, stop and rm take the machine's ID or label, or the provider's own sandbox ID. stop and rm ask first; -y doesn't. Before deleting a running sandbox, rm lists branches there with commits no remote has and uncommitted changes.

conch sandbox -provider P ls also lists sandboxes with no machine here — one whose setting up failed, one removed with conch machine rm, or, with a provider that lists the whole account rather than only conch's own (boat.dev has no labels to tell them apart), one you made yourself. They show with - for the ID and label, and a line underneath says so: they cost until conch sandbox -provider P rm ID deletes them. The size column names only what the provider reports, so a boat.dev sandbox shows 4 vCPU, 8 GiB rather than inventing a disk it said nothing about.

conch sandbox -provider P add [-label L] ID makes one of them a machine: it starts the sandbox if it is stopped, installs conch there and adds it to the sidebar, as create would have. It takes only what ls lists — with Daytona and Devin Cloud that is what conch made, since they list sandboxes by conch's label; a sandbox or session you made in their own interface is refused, as conch would lose sight of it (and a Devin session made by hand is somebody's work, which conch's wake-up message would interrupt). A sandbox conch already has a machine for is refused with that machine's name. If setting it up fails, the sandbox is left as it was — it is yours, so nothing offers to delete it. [sandbox.P] env and auto_stop are given to a sandbox when it is made, so one conch did not make — on boat.dev, which lists the whole account — has neither; add says so when they are set, and an agent there may start logged out until you sign it in.

Running until you stop it

A Devin Cloud session is never just a machine: Devin has to be given a task, and works on it. conch gives it one asking it to change nothing and wait, so it does as little as it can — but it is billed in ACUs while the session runs, like any session. Devin also puts a session to sleep when nothing has happened in it for a while, and a sleeping session's VM is not running: conch shows it stopped, and Start sandbox wakes it (with a message, which is the only way Devin's API has). Stopping one archives it, which is how the API puts a session to sleep; starting it unarchives it again. Deleting one ends the session for good. A session asleep for a reason waiting won't mend — out of credits, a usage limit — says so instead of starting.

Daytona stops an idle sandbox after 15 minutes by default, and an agent working inside doesn't count as activity — only an ssh connection or an API call does. So a sandbox conch creates never stops on its own: close the TUI and its agents carry on. It costs for as long as it runs, so stop or delete it when you're done. auto_stop in [sandbox.<provider>] sets a timeout instead.

boat.dev is the other way about: a sandbox is given a length of life when it is made, counted from then and not from the last thing that happened in it. conch asks for two hours, which is what the free trial allows, and Settings → Sandboxes → boat.dev → Life (auto_stop, in minutes) raises it on a paid plan; a plan that allows less says so, and conch asks again for the longest it named instead of failing. conch's own idle watch stops a sandbox nobody is using long before that clock runs out, and Start sandbox gives it a fresh two hours.

conch never starts a stopped sandbox by itself: use Start sandbox in its menu, or conch sandbox -provider P start ID.

Getting the work out

Work goes in the same way: T on a branch or an agent, then New Daytona sandbox (or another provider's), makes a sandbox for it and moves the worktree and its agents there in one go (see Moving a worktree to another machine).

The code comes back the way it would from any machine: push the branch from the sandbox, or move the worktree back with T (see Remote machines). The sandbox needs its own access to a private repository — a token scoped to that repository, passed with -env.

How conch reaches it

All are reached over ssh, by different means.

With Daytona, each connection asks for a fresh ssh token and goes through Daytona's ssh gateway, so nothing that lets you in is saved. The token is kept out of conch's messages, but ssh has it on its command line while connected, where other users of your computer could see it in the process list. Daytona's gateway key is accepted the first time it is seen.

With boat.dev, the sandbox is a machine with an sshd of its own. A new one is ready a moment before it has an address, so conch waits for one rather than failing a create that was going to work. conch authorizes your public key in it — your default ~/.ssh/id_*.pub, or one conch makes for itself — and connects to the machine directly as user, offering that same key (-i, and nothing else), so the half that opens it never leaves your computer and there is no token to hide. Your key does not need to be in ssh-agent. A sandbox that comes back from being stopped lands on another machine, so where to connect is read afresh each time, and the new host key is accepted as the gateway's is.

With Devin Cloud, conch runs devin ssh SESSION, which wraps ssh to Devin's gateway (ssh.devin.ai) and approves the connection with the CLI's own login — plain ssh there would ask for approval in a browser, and the API hands out no credential for it. What conch runs there goes as an ssh RemoteCommand, encoded, so it arrives whole whatever the CLI does with the options it is given. conch sandbox -provider devin ssh opens a shell the same way. A session whose CLI isn't signed in says so (not logged in — run devin auth login first) rather than hanging.

The provider keeps the values of -env variables with the sandbox. Pass only what the task needs.