ADR 0007 — A third SDK, in Elixir, in this repository
A record of the decision as it was taken; the numbers in it are as of its date. Today’s numbers are in chapter 25.
Status: accepted, 2026-09-26. Prompted by the first Elixir embedder, a
content-management product whose two drivers shelled out to the zygo CLI.
Context
The first product built on Zygo from Elixir ran every script by starting the
zygo binary: about a thousand lines across two drivers. Most of those lines
worked around being on the far side of a command line. Stdin went through
/bin/sh. On macOS the order of stdout and stderr was lost in the shim.
Whether a zygote had gone was guessed from the text of an error. A table of
which functions were warm lived in ETS behind a global lock, and every event
went through a payload file and a chmod.
zygo api already answers every one of those. It returns structured JSON, a
status and a code, a request id and Retry-After. It cancels, holds limits
and secrets per tenant, and has runtime pools — one pool, ten thousand
scripts, which is exactly that product’s shape. What was missing was a client.
Three questions followed: where it lives, what it depends on, and how it reports failure.
Decision
It lives in this repository, as sdk/elixir, and is published to Hex as
zygo_sdk. The Python and Node clients already set a contract here: a
stand-in API in place of a sandbox, the method table in chapter 17, one test
per OpenAPI operation, make test-sdk, make bump, and a release job that
refuses an SDK whose version is not the tag’s. A client in another repository
would have to follow all of that by hand. The cost is that its version is
Zygo’s (0.1.x), as the other two already are.
It has two dependencies, Mint and NimblePool. The other SDKs have none,
because their standard libraries have an HTTP client that a unix socket fits
under in thirty lines. Erlang’s :httpc can reach a unix socket, but only as
a setting of a whole profile — a process under inets, shared by every
request made through it — and its connection reuse happens inside it, out of
the caller’s sight. The rules the other two clients keep and test (drop a
connection idle for 20 s; send again only when nothing of an answer came
back) could not be held there. Mint reaches a unix socket per connection, and
it is HTTP/1.1 as a data structure rather than a process. A data structure can be lent, and
NimblePool is what lends it: one caller at a time, the socket’s ownership
moving with it, a caller that crashes taking its socket down with it. Both
are from the same authors as Finch; Mint brings one more small package,
hpax. req and finch were left out as too heavy for a library, and both
want a process started for them. JSON is the standard library’s, so the
floor is Elixir 1.18.
It has one exception, Zygo.Error, with a kind. Python has eleven
classes because except Busy: is how Python branches. Elixir branches on
data, with case and pattern matching, so the eleven become eleven atoms —
:busy, :handler, :timeout and the rest — in the same order and with the
same fields. Every function returns {:ok, value} or {:error, error}, and a
! twin is generated for each so the two cannot drift.
Consequences
- Chapter 17’s method table has an Elixir column, and its error table an
Elixir
kindcolumn. A test reads the error table and checks every row, so the book and the client cannot disagree without a failure. make test-sdkruns three suites. Without Elixir on the machine the third says it skipped rather than failing; CI has a job that installs Elixir 1.18 and a current release and runs it on both.- There is no function handle, as there is in Python (
client.fn(name)):fnis a keyword in Elixir, and&Zygo.call(client, name, &1)is one line. - A stream is a lazy
Streamwhose last item is{:result, _}or{:error, _}, not an iterator that raises. A caller that wants it to raise usesZygo.stream!/4. - Publishing needs a
HEX_API_KEYsecret, and the first version has to be pushed by hand to claim the name.
What would reopen this
- An
:httpcthat lets a caller hold and reuse its own connections, which would let the client drop Mint. - A second Elixir client worth more to its users than this one — at which point it is better to link to it than to keep two.