Core / framework split, like Node and Express
the core exposes one entry point, (http-listen port (lambda (req res) ...)); the bundled (igropyr express) layer (create-app, app-get, send-json!, ...) is optional, and alternative frameworks can be built on the same core
Green processes thousands of lightweight processes scheduled over one OS thread; continuation-based context switching with preemption, so even a CPU-spinning handler cannot freeze the system
Pure message passing
spawn / send / receive / link / monitor; no shared state between processes
Fault tolerant by default a fixed worker pool behind a supervisor: crashed workers are replaced and the task retried (at most 3 times, then the client gets a 500); workers stuck for more than 30 s are killed and replaced; a slow or half-sent request only ever blocks its own reader process
Failure hook (remote retry ring)
when retries are exhausted or a stuck worker is killed (killed first, so no execution is in flight), an optional on-failure handler answers a structured JSON fault instead of the plain 500, on the same keep-alive connection — the client resubmits (changed parameters, carried state) and gets a fresh retry round; unset, the plain 500 remains
Conversations (process-per-dialogue)
a multi-request dialogue runs as one green process holding live state — even an open database transaction — across rounds; suspend! answers and parks, conversation-resume! continues, and death for any reason (crash, TTL) means guaranteed rollback: a later resume gets gone
Hot code swapping replace the handler (or individual routes) on a live server: the listener, open connections and worker pool stay up, in-flight requests finish on the old code
WebSocket RFC 6455 upgrade on the same port; each socket is its own green process, so server push is just a message send
Streaming responses & SSE
chunked response body via res-begin!/res-write!/res-end!; Server-Sent Events helpers on top
OTP building blocks
gen-server (call/cast/info), a process registry (register/whereis), and topic PubSub with automatic cleanup of dead subscribers
JSON
a safe recursive-descent parser (no read; full escape and surrogate handling) and writer
(igropyr sexpr) is a safe whitelisted parser (no read, depth-limited), and app-rpc / send-sexpr! / ws-send-sexpr! / sse-send-sexpr! carry one datum per message — exact ratios and bignums cross intact. The browser end is Goeteia's (web rpc/ws/sse)
Forms & cookies
req-form parses urlencoded and multipart bodies (file uploads included); req-cookie / set-cookie!
Middleware suite cookie sessions (gen-server store, CSPRNG sids), CORS with preflight, security headers, and an access logger
Chunked transfer-encoding
Transfer-Encoding: chunked request bodies are decoded transparently
Non-blocking Redis and MySQL clients pure Scheme, same event loop; callers park their green process while the OS thread keeps serving; MySQL comes with a self-healing connection pool
Non-blocking HTTP & WebSocket clients
outbound http-get / http-post and ws-connect, both with async DNS (libuv thread pool) and the same park-the-caller model
Async file reads static files are read on libuv's thread pool, so a large or cold read never blocks the scheduler
gzip compression
responses negotiated via Accept-Encoding; static files cache their compressed form
Ops-ready
rate limiting, a global error handler, and a Prometheus /metrics endpoint
Runtime introspection & graceful shutdown
http-stats (live connection/request/pool counters), http-shutdown! (drain in-flight requests, refuse new connections)
Multi-process scaling
SO_REUSEPORT bind option for kernel-balanced multi-process listening on Linux (pair with pm2 or systemd)
HTTP/1.1 keep-alive & pipelining persistent connections by default on 1.1; each connection's reader process loops over successive requests
Fast
~35 k req/s at 500 concurrent connections on an Apple Silicon laptop (ab -n 50000 -c 500, zero failed requests)