Agents

June 4, 2026 ยท View on GitHub

Instructions for AI coding agents working on this project.

Project Overview

Livery is a BEAM-native web framework that serves one handler set over HTTP/1.1, HTTP/2, and HTTP/3 from a single service runtime (the spirit of Axum + Tower + Hyper on the BEAM). The wire layer is delegated to sibling hex libraries; Livery owns the developer surface. One OTP application; core runtime stays at the src/ top level, with subsystems grouped into domain subdirectories (rebar3 compiles src/ recursively):

src/            Core runtime, flat: livery, livery_req, livery_resp,
                livery_router, livery_service, livery_h1/h2/h3,
                livery_ws*, livery_wt, livery_mcp, livery_openapi,
                livery_static, livery_metrics, ... (+ livery.app.src)
src/client/     Outbound HTTP client: livery_client + its layers,
                stores, adapter, and discovery
src/middleware/ livery_middleware framework + cross-cutting middlewares
                (request_id, access_log, cors, etag, timeout, ratelimit
                (+ store), concurrency, security_headers, alt_svc,
                instrument_*, openapi_validate, body_limit)
src/auth/       Auth middlewares + helpers (bearer, introspect, session,
                jwks, oidc, livery_auth)
src/codec/      Content-coding subsystem (livery_compress + codecs)
include/    Shared records (livery.hrl)
test/       EUnit + Common Test (incl. the cross-adapter parity SUITE
            and livery_e2e_SUITE, a full journey over H1/H2/H3)
bench/      Load/latency harness (separate `bench` rebar profile)
examples/   Runnable example apps (`examples` rebar profile)
docs/       Diataxis docs (tutorials/guides/concepts) + design notes

Wire dependencies (h1, h2, quic, ws, webtransport, instrument, barrel_mcp) are git deps in rebar.config. Local development overrides them with _checkouts/ symlinks to the sibling projects; _checkouts/ is gitignored. Never reimplement HPACK/QPACK/frame/QUIC logic here: that lives in the wire libs; keep the adapters thin and push changes upstream.

Authoritative behaviour is the test suites under test/ (the parity SUITE diffs externally observable behaviour across all four adapters) and the module docs.

Required Checks

Every change must be formatted and pass all checks before committing:

rebar3 fmt          # Auto-format (always run first)
rebar3 compile      # Must compile cleanly (warnings_as_errors)
rebar3 lint         # Elvis linter
rebar3 xref         # Cross-reference analysis
rebar3 dialyzer     # Type checking
rebar3 eunit        # Unit + property tests
rebar3 ct           # Common Test (adapters, parity, service, ws, wt, mcp)

CI (.github/workflows/ci.yml) runs format, lint, xref, and dialyzer as fast-fail static checks, then gates build/eunit/ ct on them.

Build & Development Commands

rebar3 compile                              # Build
rebar3 shell                                # Boot a dev shell
rebar3 ct --suite=test/livery_parity_SUITE  # One suite
rebar3 fmt                                   # Auto-format (erlfmt)
rebar3 fmt --check                           # Format check, no writes
rebar3 lint                                  # Elvis linter
rebar3 as bench shell                        # Benchmark harness (livery_bench)

Architecture

Service runtime

livery:start_service/1 brings H3 (UDP), H2 (TLS), and H1 (TCP) up together under one livery_service gen_server, sharing one router and middleware stack, and advertising Alt-Svc for H3 on H1/H2 responses. livery:start_listener/2 serves a single protocol. livery_drain gives graceful shutdown (stop accepting, wait for in-flight, then stop).

Per-request worker model

Each adapter, on a new request, spawns a livery_req_proc child of the single global livery_req_sup (simple_one_for_one) and returns immediately. A per-stream "translator" process forwards wire events ({h1_stream,_} / {h2,_} / {quic_h3,_}) to the worker as {livery_body, Ref, _} messages; the translator monitors the worker and exits on its DOWN. Middleware and the handler run in the worker, which may block/receive (the cowboy_loop analogue).

Middleware (Tower/Axum style)

call(Req, Next, State) -> Resp: a continuation over immutable values, not the legacy (req, res, next) mutate-and-next model. Routes may carry a per-route stack under the route Meta's middleware key.

Dispatch and emit

The handler returns a #livery_resp{}; livery:emit/3 walks the body variant (full / chunked / sse / file / empty / upgrade) and drives Adapter:send_headers/send_data/send_trailers. The taken_over sentinel tells emit/3 to stop because the stream was handed off (WebSocket, WebTransport, MCP).

Adapter behaviour

livery_adapter defines start/3, stop/1, send_headers/4, send_data/3, send_trailers/2, reset/2, peer_info/1, capabilities/1. Implemented by livery_h1, livery_h2, livery_h3, and livery_test_adapter (in-memory, used by the parity SUITE).

Conventions

  • Run rebar3 fmt before committing; elvis must pass. New per-module elvis ignores belong in rebar.config with a one-line reason.
  • Commit messages: one imperative subject line, body only for non-obvious "why". No phase references, no diff restatement, no "generated by" / "co-authored-by" trailers.
  • Do not use the em-dash character in code, docs, or messages.
  • Keep adapters thin; cross-protocol behaviour is locked by the parity SUITE from Phase 2 forward.
  • TLS verify_none is test-only; never ship it in src/.
  • File responses (livery_resp:file/2,3) serve the exact path given; confine any handler-built path before serving.