Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 kind column. A test reads the error table and checks every row, so the book and the client cannot disagree without a failure.
  • make test-sdk runs 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)): fn is a keyword in Elixir, and &Zygo.call(client, name, &1) is one line.
  • A stream is a lazy Stream whose last item is {:result, _} or {:error, _}, not an iterator that raises. A caller that wants it to raise uses Zygo.stream!/4.
  • Publishing needs a HEX_API_KEY secret, and the first version has to be pushed by hand to claim the name.

What would reopen this

  • An :httpc that 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.