envend
guide

Working from a harness

A harness is whatever is driving the agent: Claude Code, Codex, an SDK agent, an editor, a cron job. envend does not care which. There are two ways in — an MCP endpoint and a CLI — and most sessions use both, because they are good at different things.

Register the MCP endpoint

The gateway serves MCP at /mcp over streamable HTTP, behind a bearer token. Any MCP client that can send an Authorization header can register it. For Claude Code, against the deployed gateway:

claude mcp add --transport http -s user envend \
  https://gateway.envend.dev/mcp \
  --header "Authorization: Bearer $ENVEND_API_TOKEN"

Against a gateway you are running locally, there is no token to send:

claude mcp add --transport http envend http://localhost:4600/mcp

Any other MCP client is the same three facts: the URL https://gateway.envend.dev/mcp, the transport streamable HTTP, and one header. That gets you thirteen tools. The gateway's own instructions travel with them, so a model reading the tool list is told the five things that change what it does — work in /work, every exec is a fresh shell, there is a real ssh way in, sandboxes are cattle, and some backends sleep and some cannot.

The shortest useful loop

env_create(profile: "omega")     # returns a generated name, e.g. omega-k4zt
env_logs(env: "omega-k4zt")      # poll until it says ready — setup is still running
env_exec(env: "omega-k4zt", cmd: "mix test")
env_destroy(env: "omega-k4zt")

The second line is the one people skip. env_create returns as soon as the machine exists, but a profile's create hook — a clone, an image pull, a container build — is still running inside it, and on a first boot that is minutes. Working before env_logs says ready is working in a directory that is still being written. Every answer ends with next since: <N>; pass it back as since and the next call returns only what is new, so polling costs nothing.

Then stop using env_exec for everything

env_exec is a control plane. It is fine for "run this and tell me what happened" and poor at everything else: no interactive commands, no file transfer, output capped at the last 8000 characters per stream, and one round trip per file you want to read. If your harness has a Bash tool — and most do — the two commands below are worth more than the whole tool list.

envend mount omega-k4zt          # the sandbox's /work, on this machine
envend ssh omega-k4zt -- mix test

envend mount is one command doing five things, because each is useless without the others: it looks up the sandbox's tunnel address, fetches both of its keys, starts a background tunnel on a port the kernel picks, mounts /work over NFSv3 through that tunnel, and writes a Host block into ~/.envend/ssh_config — so plain ssh omega-k4zt and scp local.txt omega-k4zt:/work/ work too. It wakes a sleeping sandbox on the way, so it does not matter what state the machine is in.

On macOS this needs no sudo and no password: macOS lets a user mount NFS onto a directory they own, which matters because a harness cannot answer a password prompt. On Linux the kernel NFS client needs root, so envend mount has to run as root there; if it cannot, it says so in one sentence and you work over ssh without a mount.

The instruction block

The split a harness has to understand is two facts wide: files are here, execution is over there. envend mount writes exactly that next to the files, as ~/envend/<name>/ENVEND.md, so a harness that finds the mount finds the instructions beside it. envend instructions <name> prints the same block if you would rather paste it into a system prompt:

Working in the envend sandbox "omega-k4zt".

Its files are on this machine at ~/envend/omega-k4zt/work — the whole of the
sandbox's /work, including node_modules, deps and _build. Read, search and
edit there with your ordinary tools; you are editing the sandbox's own disk.

Run anything with `envend ssh omega-k4zt -- <command>`: tests, servers, builds,
git, package installs. Commands run where the sandbox's profile says, which
may be inside a container it brought up. For the VM itself, and its docker
and disk, say `envend ssh omega-k4zt --host -- <command>`.

Builds happen only in the sandbox. Never compile or install dependencies
against the mount from here — the toolchain is over there.

Two habits follow from it. Never build against the mount — the toolchain is in the sandbox, and a compiler pulled across NFS is both wrong and slow. And search over ssh, not through the mount: envend ssh omega-k4zt -- grep -rn pattern /work is much faster than the same grep walking a network filesystem.

Handing something to a person

An agent that finishes a report and prints its absolute path has produced nothing the person reading can open — they are on a different machine. Two commands close that gap, and both are also MCP tools:

envend link omega-k4zt /work/report.html   # a signed URL, expires in an hour
envend share omega-k4zt 4000              # a dev server, on https://omega-k4zt-4000.envend.dev

Prefer either over pasting a file's contents back through the conversation, which wastes an enormous amount of context and mangles anything binary. See Sharing for what a link does and does not protect.

Where a command actually lands

If the sandbox came from a profile with an exec prefix — usually into a container — then that is where your commands run, with the toolchain, services and paths the repository's own instructions describe. When you want the VM around it instead, for docker ps or disk space or the hook log, ask for the host.

You runIt lands
env_exec(env:, cmd:)through the prefix
env_exec(env:, cmd:, host: true)on the VM
envend ssh <name> -- <cmd>through the prefix
envend ssh <name> --host -- <cmd>on the VM
scp, sftp, rsync, the mountthe VM, always

A sandbox with no exec prefix runs everything on the VM, and --host makes no difference to it.

Two mistakes worth naming

Leaving a machine running for a week. Some backends stop an idle sandbox and some cannot. One that cannot runs, and bills, until env_destroy. env_list says which, per sandbox, in a phrase.

Losing a build to a wake you did not expect. On a backend that rebuilds the root filesystem, everything outside /work — /root, /tmp, anything apt installed — is gone after a sleep. Work in /work, declare long-running servers as services so they come back by themselves, and call env_hold before starting a long detached job.