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 run | It 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 mount | the 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.