envend
guide

Connectors

A provider is a vendor that will rent you a computer. A connector is the module in the gateway that talks to one. Fly Machines, exe.dev and AWS EC2 are providers; there is one connector for each, plus two in-memory ones the test suite runs against.

The contract

A connector is six things it does and six things it says about itself.

CallbackWhat it must do
create/1Build a machine from the spec and return it, running.
get/1One environment by the opaque id create/1 returned.
list/0Every environment this gateway owns on that backend, and nothing else.
destroy/1Delete the machine and everything that only existed for it.
start/1Bring a stopped machine back.
stop/1Stop a machine without losing what :durable_disk promised.
CallbackWhat it must say
capabilities/0Which of the optional behaviours are real here.
sizes/0The guests this backend offers, by size key — its own table.
regions/0Where it will place an environment, or [] if the caller has no say.
default_image/1What it boots when the caller asked for nothing.
prepare_image/1What to boot for this spec, and how it got there.
platform/0The OCI platform to wrap an image for.

Three rules that keep them swappable

  • Names are short and provider-agnostic. The caller says beam1; a connector may store env-beam1 internally, and the prefix never leaks.
  • Everything except create/1 is addressed by the opaque id, which the gateway gets from list/0.
  • A connector states what it can do rather than being assumed to. Nothing in the gateway, the idle sweep, the MCP layer or the sharing layer branches on which module it is holding, and the grep that proves it is one of the repo's own gates.

What is deliberately not a callback

There is no exec. Running a command is a gateway mechanism, not a provider feature: the gateway dials the machine's own envd over the control tunnel it already holds. envd is the only party holding the workspace declaration, so it is the only party that can put a command through the exec prefix — and a backend with no command API at all can still host sandboxes. The timeout is the gateway's, because it is the only party that knows what the caller asked for.

There is no publish, and no capability for one. A backend never publishes a sandbox's ports. env_share serves one on demand over the gateway's own tunnel, on the gateway's own domain, and that is the same mechanism everywhere — so there is nothing for a connector to differ about.

There is no envd contract here either. One shared module holds every variable a machine is booted with and the encode/decode pair for each. Every connector calls it; none of them owns it. What a connector decides is only how to deliver that map into the machine, and where to keep the declaration so list/0 can read it back. See the envd contract.

The capability vocabulary

Closed, and short on purpose: a capability nothing reads is not a capability, and a fact about a backend that nothing acts on belongs in that connector's own documentation. Each entry says what the gateway does when it is absent — see Concepts for the table with the per-backend answers.

:oci_images · :sleep · :durable_disk · :persistent_root · :snapshots · :public_url

Seven more used to be on that list and are gone, because nothing read them. Every sandbox on every backend serves ssh and files through the tunnel and only through the tunnel, so there was nothing for a connector to claim; :wake was :sleep said twice; and the rest described callbacks that are mandatory anyway.

Claiming :durable_disk or :persistent_root is a promise the gateway repeats to agents verbatim. Under-promising is the safe direction, so a connector unsure of its own answer should leave the capability off.

Per-connector facts

The four size letters mean four different computers on four different backends, and one backend does not offer all four.

Flyexe.devAWS
sizes/0s/m/l/xl, shared guests plus a volumem/l/xl — exe.dev refuses a VM under 2 GBs/m/l/xl, one instance family plus an EBS volume
regions/0one, per deployment[] — the account decidesone, per deployment
default_image/1a constant in this app's registrya setting, or the same sandbox imagea live lookup against Canonical's published parameter, per architecture
prepare_image/1wrap, or boot as iswrap, or boot as isan AMI — envd is installed at first boot
transportRESTone POST whose body is a CLI command linethe EC2 Query API
declaration read-backmachine metadata and config enva file on the gateway's disk — exe.dev stores nothing for usinstance tags

Asking for a size a backend does not have is refused with that backend's own table rather than with a list of guests it does not offer. region is an opaque placement label the gateway only ever prints — a Fly region code, an availability zone, whatever the account defaulted to — and nothing is addressed by it.

Images

prepare_image/1 answers with a reference plus the connector's own account of how it got there: as is (it already runs envd), wrapped (pulled, given the envd layer, pushed under a new tag), or envd installed at first boot (the backend boots something else entirely, like an AMI). The gateway never wraps an image itself: whether an image needs a layer before it can be a sandbox is a question only the thing that will boot it can answer.

A backend without :oci_images refuses an image: argument in a sentence naming the provider, before anything is built — wrapping an image is minutes of pulling and pushing, and a connector that cannot boot the result should not be paid for first.

Two things a connector must get right

The machine environment must be present at every boot, not only the first. That is the load-bearing assumption behind declared services coming back after a sleep. One connector gets it from machine config, one writes a file its init system reads at every start. A connector that used a run-once mechanism — a cloud-init user-data script, say — would produce sandboxes whose services came back exactly once.

Errors become sentences at the source. A connector is the only party that knows what its vendor's codes mean, so it turns its own failure into a sentence and tags it as a provider error. The gateway has one clause for all of them and prints the sentence. There is no vendor-shaped error tuple and no vendor's credential variable named anywhere in the gateway's prose.

Testing one without an account

Both real connectors are unit-tested against a stubbed HTTP layer: the assertions are about the request that would have been sent — the URL, the body, the machine environment map — rather than about a machine that came back. That is how the AWS connector exists at all. It is complete and tested and has never been run against a real account, which is why it is called hypothetical. It was written to find out where the gateway had been secretly shaped like its first provider, and it found twelve places; every one of them is now resolved, and the list is kept in the repository as history.