Keel
Hull's default event loop and transport layer

Built for events you need to trust.

Keel is the default event loop and transport layer for Hull. It is swappable, but it is the first-class path for HTTP, WebSocket, SSE, async clients, file descriptors, timers, and worker wakeups over epoll, kqueue, io_uring, or poll.

101K req/s benchmark peak
epoll · kqueue · io_uring · poll
Pluggable TLS · parser · allocator
pledge/unveil-friendly init/run split
keel server compilable C11
#include <keel/keel.h>

static void handle_run(KlRequest *req,
                       KlResponse *res,
                       void *ctx) {
    (void)req;
    (void)ctx;

    static const char body[] =
        "{\"ok\":true,\"transport\":\"keel\"}";
    kl_response_json(res, 200, body,
        sizeof(body) - 1);
}

int main(void) {
    KlServer s;
    KlConfig cfg = { .port = 7070 };

    kl_server_init(&s, &cfg);
    kl_server_route(&s, "GET", "/run",
        handle_run, NULL, NULL);
    kl_server_run(&s);
    kl_server_free(&s);
}

Keel does not replace Hull's capability boundary. It is the default event and transport substrate beneath Hull: a narrow, auditable path to sockets, timers, local native services, side-car binaries, device bridges, and high-throughput protocol edges. OTTO and other Artalis systems use the same substrate where C eventing and transport matter.

Build & test

One library. Four event backends.

Keel builds as a static C library. Use kqueue on macOS and BSD, epoll on Linux, io_uring where it makes sense, and poll when portability matters more than edge-triggered performance.

shell local build
# Build libkeel.a with the platform default backend.
$ make

# Linux 5.6+: readiness events through io_uring.
$ make BACKEND=iouring

# Universal POSIX fallback.
$ make BACKEND=poll

# Run the test suite and example programs.
$ make test
$ make examples
101K
req/s
Peak reported by the repo benchmark suite.
671
tests
40 suites with ASan/UBSan.
4
backends
epoll, kqueue, io_uring, poll.
1
vendored dep
llhttp parser by default.

Benchmark context: make bench runs a dedicated local benchmark server across four endpoints with wrk latency reporting. Defaults are 4 threads, 100 connections, and 10s duration; the benchmark workflow also runs Linux epoll, Linux io_uring, and macOS kqueue with 2 threads, 50 connections, and 5s duration.

Scope

Keel is Hull's default event loop and transport layer.

What it is

A minimal C11 event loop and protocol substrate with async server handlers, sync/async clients, WebSocket, SSE, timers, thread-pool wakeups, body readers, streaming, and connection pooling. Hull uses it by default; OTTO and other Artalis systems reuse it where they need the same C-level eventing and transport.

What it is not

Not a web framework, not an application runtime, not a policy engine, not an RPC religion. Hull owns capabilities. Keel is the default byte-moving layer, and remains replaceable at the boundary.

Why it exists

Local-first and embedded systems need a predictable event loop and network edge that can be compiled, audited, fuzzed, sandboxed, and carried into constrained deployments.

Where it fits

Beneath Hull's capability-limited orchestration layer, in native Artalis services, and between Hull and side-cars that perform routing, optimization, inference, device IO, or other compute-heavy work.

Principles

Small pieces. Explicit edges.

Orthogonal modules

Allocator, parser, body reader, TLS, resolver, router, client, and server modules are independently testable and replaceable.

No forced buffering

Buffer, file, and streaming response modes let services avoid copying when sendfile, writev, chunking, or backpressure are the right tool.

Sandbox-shaped

The init/run split, fixed resource limits, explicit vtables, and narrow file descriptor model make syscall lockdown natural.

Hull + OTTO + side-cars

The default event and transport substrate for constrained systems.

Hull should not need to embed every native capability directly into the scripting runtime. Keel is the default event loop and local transport path to native binaries that can be sandboxed, restarted, versioned, audited, and replaced independently.

The important architectural point is the seam: Keel is the standard substrate, but Hull does not hard-code trust into it. Capability checks stay above; side-car process isolation stays below. The same eventing and transport layer can be reused directly by native services that do not need the Hull scripting runtime.

Hull HTTP server, routing, middleware, and static-file edge.
Hull timers, SSE, async requests, and thread-pool wakeups.
Native optimizer, router, and inference side-car gateways.
Reusable C service substrate for OTTO-style systems.

In Hull, Keel is wired into server startup, route dispatch, middleware, timers, SSE, async work, and static-file serving. See Hull's architecture notes and dependency table.

layers local system boundary
+-------------------------------------------------------+
| Hull app code                                         |
| Lua / JS orchestration, routes, workflows             |
+--------------------------+----------------------------+
                           |
                           | manifest-declared calls
                           v
+-------------------------------------------------------+
| Hull runtime boundary                                 |
| capabilities, audit, sandbox, WASM compute            |
|                                                       |
|   Keel default event + transport layer                |
|   HTTP / WS / SSE / clients / timers / watchers       |
+--------------------------+----------------------------+
                           |
                           | explicit local transport
                           v
+-------------------------------------------------------+
| Sandboxed native side-car                             |
| optimizer, router, inference, device bridge           |
+-------------------------------------------------------+
Compare

Less framework. More substrate.

Keel is deliberately closer to the metal than application servers, but more protocol-aware than a generic event loop.

Capability Keel libuv Nginx gRPC
Embeddable C library yes yes no part
HTTP server and client in one substrate yes no server rpc
Pluggable TLS/parser/allocator/body reader yes part no no
Designed for side-car sandbox boundaries yes generic edge service
Single small static-library deployment yes yes no heavy
Protocols & maturity

A mature HTTP core, with protocol edges around it.

Keel's center of gravity is event-loop integration plus HTTP/1.1 transport. WebSocket, SSE, client pooling, compression, proxying, redirects, generic FD watchers, timers, and HTTP/2 hooks extend that core without turning Keel into a monolithic application server.

Surface Status Notes
Event loop core stable epoll, kqueue, io_uring poll-add, and POSIX poll behind one small API; exercised by server, client, timers, and watcher tests.
Timers and worker wakeups stable One-shot timers, thread-pool resume, and pipe-based event-loop wakeup with public examples.
HTTP/1.1 server stable Routing, middleware, keep-alive, timeouts, buffered/file/stream responses; covered by the main test and example suite.
HTTP/1.1 client stable Blocking and async APIs, TLS hooks, proxy support, redirects, connection pooling, and decompression support.
WebSocket tested RFC 6455 server and client, shared frame parser, masked client frames, and dedicated fuzz corpus.
Server-Sent Events stable Zero-alloc SSE framing over chunked streaming responses.
Multipart form-data tested RFC 2046 parser with part, total-size, and part-count limits plus dedicated fuzz targets.
Compression / decompression stable Pluggable vtables; miniz gzip backend ships in-tree.
HTTP proxy / CONNECT stable HTTP forwarding and HTTPS tunneling for client-side transport.
HTTP/2 pluggable Server and client surfaces use session vtables; backend choice remains explicit.
TLS bring your own Keel defines the transport vtable and avoids vendoring TLS policy.
Async DNS bring your own Resolver vtable plus caching decorator; c-ares or thread-pool wrappers fit here.
Generic FD watchers stable Register arbitrary file descriptors for callbacks on the same event context.
Side-car local transport default Hull's default route to sandboxed native binaries; replaceable at explicit boundaries.
OTTO/native service substrate in use Reusable event loop and transport library for C services that sit outside Hull.
How it works

Event loop first. Protocols around it.

The composable event context owns readiness, watchers, timers, and allocator state. Server, client, thread pool, SSE, WebSocket, and side-car bridges sit on top of that same loop.

Event backends

Edge-triggered epoll/kqueue, io_uring poll-add, and POSIX poll expose the same small event API to higher layers.

Suspend and resume

Async handlers can suspend a connection and resume it from a file descriptor watcher, timer, client callback, or worker completion.

Body readers

Request bodies are routed into explicit readers: buffer, multipart, stream-oriented custom parsers, or application-specific sinks.

Streaming edges

Chunked responses, SSE, WebSocket frames, backpressure drain buffers, and client pools keep long-lived local services cheap.

Security model

Narrow transport makes sandboxing practical.

Keel is not the authorization layer. Hull decides what an app can do. Keel keeps the local transport path small enough to test, fuzz, constrain, and place behind kernel sandboxing.

Resource limits at the edge

Header size, body size, multipart part limits, drain buffers, connection timeouts, idle timers, and route-level readers make overload behavior explicit.

Fuzzed protocol surfaces

Parser, response parser, multipart, and WebSocket paths have dedicated fuzz targets and corpus fixtures.

Bring your own hardening

TLS, allocators, DNS, compression, file IO, and parsers are vtables, so production users can choose the backend appropriate for their threat model.

Trust

Built like infrastructure.

Keel is intentionally boring in the places that matter: C11, static library, CI, sanitizers, fuzzing, OpenSSF checks, and examples that compile against the public API.

quality gates repo workflow
$ make debug      # ASan + UBSan
$ make test       # unit and integration suites
$ make analyze    # clang static analyzer
$ make cppcheck   # static analysis
$ make fuzz       # parser and protocol fuzz targets