What Is Lume
A plain-language description first, then the source.
Lume (pronounced lu-MÉ, /luˈmeɪ/ — two syllables, stress on the second) is a DSL for Agent applications: write one .lume file, compile it into a C11 binary, and you have a static site, JSON APIs, SSE chat, and in-process agent tools at once. It does not try to be another general-purpose language; it collapses the two layers most likely to grow into long stretches of glue code — "web server" and "agent tool registration" — into language-level declarations (all examples below come from examples/hello.lume). In one sentence: Lume is not about making C programmers more comfortable — it is about letting AI describe HTTP, data, tools, and agent capabilities as language, with a minimum of structured code.
server {
port = 8082;
workers = 2;
docroot = "./www";
}
get "/hello", (req) => {
return { message: "hello, world", path: req.path, method: req.method };
};
write "/items"; // registers POST/PUT/PATCH/DELETE in one statement
tool "add", "Add two integers", { a: int, b: int }, (arg) => {
return { sum: arg.a + arg.b };
};
run();
Three design choices worth noting up front:
write "/items";is not a framework API — it is a language-level abstraction. One statement registers four write endpoints and replies with a default JSON acknowledgement ({ action, method, got }); pass a handler to customize. It declares resource operations rather than HTTP-verb boilerplate — you write "there is a writable resource/items" and the language fills in the four verbs, so you never hand-wire PUT/POST/PATCH/DELETE glue.- A handler returning a map without a
bodykey is auto-serialized as200 application/json— nostringify(), no hand-written status/type. toolparams are bare type keywords ({ a: int, b: int }); the bridge upgrades them into the JSON schema agent-httpd expects at registration — the protocol the model sees is generated by the compiler layer, not hand-written.
As for "why not Node / Python / Go": it is not about performance — C being fast is common knowledge, and this article does not sell benchmark numbers. It is about convergence. Once AI writes most of the code, the glue — HTTP frameworks, JSON serialization, deployment scripts — is exactly the part most likely to be wrong and hardest to review. Lume's bet: sink that glue into a strongly-typed DSL, and the surface AI must get right shrinks a lot (--check catches most errors at compile time), and so does the surface a human must review.
The .lume file travels this path: lexer → parser(AST) → typecheck → tree-walk interpreter(VM) → bridge.c → agent-httpd, ending as one statically linked binary. What this article dissects are the two most critical links in that chain.
Hypothesis Under Test
First, the boundary of this project: it is a self-contained agent DSL server (original C11 code, single binary). The goal is that writing one .lume script gives you a static site, JSON APIs, SSE chat, and in-process agent tools at once — zero Node, zero standalone React backend, and the process itself needs no nginx as an application server or static-file server (it ships its own HTTP server + docroot). One caveat stated up front, though: lume has no built-in TLS, so once you expose it publicly and want HTTPS, production still needs a reverse proxy for TLS termination + auth (details later). So it is not "yet another web framework"; it is "weld a DSL interpreter directly into a C binary that also embeds HTTP capabilities."
Note: what follows dissects the design and code as it normally runs; only conclusions provable from the source are listed — a test's "didn't crash" is never presented as "proof of no leak," and a source cross-check table at the end collects the evidence.
The core hypothesis this article verifies:
bridge.c fully decouples the "DSL world" from the "agent-httpd world" so Lume only has to write DSL semantics; and the VM is reused across requests, staying both fast and correct via value-stack-as-GC-root plus an explicit stack clear at request end.
Three sub-propositions, each refutable against the code:
Sub-prop 1: the bridge is one-way translation, not interleaved code. DSL route/tool/run declarations are translated in bridge.c into agent-httpd's C registration calls (agenthttpd_route / agenthttpd_tool / agenthttpd_run); the interpreter never touches HTTP. The check is in src/bridge.c's route_shim / tool_shim and registration functions.
Sub-prop 2: the shim contract is fixed. A route/tool shim returns 0 to mean "handled — the framework serializes"; only self-streaming handlers set res->handled. Violating either causes malformed or silently dropped responses. The check is in bridge.c's shim implementations and ARCHITECTURE.md's bridge section.
Sub-prop 3: VM reuse is safe, not "leaks because it isn't cleared." Each worker inherits a VM copy; after a request, vm_after_request clears error / stack / call_result before handing the VM back for reuse. The value stack is itself a GC root: clearing the stack only removes the request-level GC roots, while the GC does the actual reclamation at allocation boundaries. The check is bridge.c and value.c's gc_collect.
All three can be checked line-by-line in the repo. So rather than piling up benchmark numbers, this article uses "design intent ↔ code implementation" alignment to make clear what is actually provable.
System Overview
Lume is a C11 single binary: src/ is a strongly-typed scripting DSL interpreter, statically linked against the sibling project agent-httpd's embeddable library libagenthttpd.a (a git submodule), plus the static frontend www/ produced by esbuild from frontend/. The result is a complete HTTP server with its own docroot. One process = the whole site: static assets + DSL routes + JSON APIs + /react/api/chat SSE chat — the process itself is the HTTP server, so you don't need nginx / FastCGI as an app or static server, nor a separate React backend process (public HTTPS still needs a reverse proxy; see the boundaries section).
The data-flow mantra (ARCHITECTURE.md, end of doc):
lexer → parser(AST) → typecheck(compile-time strong typing) → tree-walk interpreter(VM) → bridge → agenthttpd
The interpreter layer (interp.c) is a tree-walk interpreter with a built-in VM: a value stack + mark-sweep GC + jmp_buf for function-return unwinding. No bytecode, no JIT — it walks the AST and pushes values onto the stack.
Three key design decisions worth holding onto:
- C11 single binary: no runtime dependencies, fast startup, deploy as one file;
--checkdoes offline static validation. - DSL instead of YAML/JSON config: routes/tools/server params need expressions, types, and reuse; strong typing catches errors at compile time.
- One VM per process, initialized before fork: the immutable AST/Type is shared read-only, while each worker's VM is private — so reuse across requests needs no cross-worker VM locks.
The last one is the protagonist of the next two sections.
Developer Experience: Convenience Baked Into the Design
Having seen the system overview, don't rush into the source yet — what that design means for the person writing the business logic is worth stating first. The following points are all developer-experience facts verifiable against src/ and DEVELOPMENT.md — no exaggeration, and the costs are stated too.
1. One .lume file stands in for a whole backend stack. Write route / tool / run / server{} in a script, compile it into the same C binary, and you simultaneously get a static site + JSON API + /react/api/chat SSE chat + in-process agent tools + SSR pages. No separate Node service, no standalone React backend process to deploy — the binary ships its own HTTP server + static-file service, so for local dev or running a demo you don't even need to install nginx.
2. Compile-time strong typing + zero-side-effect validation. --check parses then runs type_check_program (which always runs, even on the direct-execution path), and type checking stops at the first error and does not execute afterward. --check exits immediately after type checking passes, producing no side effects — so running make check before a commit catches most route/tool type errors before runtime, not in production.
3. --watch hot reload, not "mutate the table at runtime." On every edit of <script.lume>, --watch re-parses + re-type-checks, then fork+exec restarts the serving child (~350ms; the old child shuts down gracefully and frees the port before the new child binds), and SIGUSR1 forces the same restart path. The reason it can't "hot-patch the registry at runtime" is exactly the registration-window rule (mechanism in the VM-reuse section later): registration happens before agenthttpd_run, and afterward the registry is a read-only snapshot. Lume therefore chose "process-level restart" over "runtime table mutation" — at the cost that the re-parsed AST/Type tree is not freed on each edit. DEVELOPMENT.md states this is an intentional dev-tool leak (parse/typecheck has no free path, and Type has shared references where recursive free would double-free); just restart the watcher periodically if it runs long. An honest trade-off, not a bug.
4. Offline demo engine — developing the chat page costs nothing. When LLM_API_KEY is empty, the framework runs an offline canned engine: it generates canned replies in-process and streams them token-by-token over SSE, making no external calls at all (DEVELOPMENT.md LLM-wiring section). export LLM_API_KEY= also forces this mode. tests/run_all.sh begins with export LLM_API_KEY=, guaranteeing the chat test always runs offline and never burns a real key. When building the /chat page you can exercise the whole SSE chain with curl -N -X POST -d '{"message":"hi"}' localhost:8081/react/api/chat without wiring up a real model first.
5. Editor support is a marketplace install away: search "Lume DSL" in the VS Code Extensions view. The extension (erishen.lume, sources live in the repo at editor/lume-vscode/) gives .lume files syntax highlighting (keywords like server / route / tool / func / let / return, types like int / string, builtins, string escapes — all colored via a TextMate grammar), // and /* */ comment toggling, and bracket pairing/auto-closing. Prefer local? Two more ways: symlink editor/lume-vscode/ into your VS Code extensions directory (grammar edits take effect after "Reload Window"), or run make vsix at the repo root and use "Install from VSIX". The honest boundary: it is a pure TextMate grammar package, no LSP — completions and diagnostics do not come from the editor; compile-time diagnostics come from make check / --check in the terminal. The editor makes it look good; the compiler tells you whether it is right.
6. The frontend is still a standard React project. frontend/src is TypeScript + JSX + Tailwind; pnpm run dev watches four processes and rebuilds incrementally; tsc --noEmit --strict runs before build, so type errors fail make ui directly; esbuild extracts React into a shared chunk-*.js (~140kb), leaving each page a 3–10kb entry bundle that is cached in the browser after the first visit and reused across pages and examples. In other words: the server is a C binary, the client is a normal React project, and the two toolchains don't hold each other hostage — change the DSL without touching the frontend build, change React without touching C.
7. JSON APIs with almost no boilerplate. A route that returns a map without a body key is auto-serialized as 200 application/json with the whole map (bridge.c's result_to_response); you only write {status?, type?, body} to change the MIME, and a non-string body is JSON-ified automatically, no hand-written stringify. Static-page URLs can also drop the .html (GET /items → www/hello/items.html fallback, DEVELOPMENT.md JSON-API section) — so a chat page's address can simply be /chat, not /chat.html.
These conveniences are real, but the boundaries must be stated too: C-side memory safety is backed by make asan (see the later section); the --watch leak is intentional; and "changing business logic at runtime" requires a process restart. They are "convenience bought by design trade-offs," not magic.
The Glue Layer bridge.c
Between the DSL world (Value / Node / VM) and the agent-httpd world (C routes / tools), bridge.c does the translation. It does three things: route registration, tool registration, and builtin seeding.
Route Registration: DSL declaration → C route
Writing route "GET", "/p", func(req) { ... } in the DSL is translated during the pre-agenthttpd_run registration window into one agenthttpd_route(method, path, route_shim) call:
if (vm->route_count >= MAX_AL_ROUTES) return -1;
RouteRec *r = &vm->routes[vm->route_count++];
/* ... fill r->method / r->path / r->handler ... */
if (agenthttpd_route(method, path, route_shim) != 0) {
vm->route_count--; /* registration refused; roll back */
}
Note the route_count-- rollback: if agent-httpd refuses registration (e.g. route limit exceeded), Lume rolls back its own record too — no "registered in C but not in Lume" drift.
All DSL routes share one C callback, route_shim. It matches the pattern against the VM's registered routes, evaluates the matching DSL handler via interp (the handler returns a map like { status, type, body }), and bridge translates that Value map into agent-httpd's response structure. The interpreter never knows it is inside an HTTP server — exactly sub-prop 1's "one-way translation."
write "/p"; with no handler falls back to vm->default_handler (native_default_route), returning a conventional JSON ack without hand-writing a handler per endpoint.
Tool Registration: bare type keywords → JSON schema
Tool registration is the same idea plus one schema upgrade. The params in tool "name", "desc", params, func are usually bare type keywords in the DSL; bridge.c's upgrade_tool_params upgrades them into the JSON schema agent-httpd expects:
ToolRec *rec = &vm->tool_records[vm->tool_count];
/* ... fill name / desc / handler ... */
upgrade_tool_params(params_json, &schema); /* bare keyword -> {"a":{"type":"int"}} */
ctx->index = vm->tool_count;
if (!agenthttpd_tool_allowed(name)) { /* whitelist filter */ }
int rc = agenthttpd_tool(name, desc, rec->params, tool_shim, ctx);
vm->tool_count++;
Two things worth noting:
- Whitelist filter (
agenthttpd_tool_allowed): Lume'sHARNESS_TOOLS_ALLOWenv var takes effect here; tools not on the list are never registered into agent-httpd, hence never spawned or invoked. This is the first gate of "runtime capability minimization." - Shared
tool_shim: like routes, all DSL tools share one C callback, usingctx->indexto look up the corresponding DSL handler.
The Shim Contract: return 0 means "handled"
This is the easiest rule to get wrong in the bridge layer, and the one most worth memorizing:
A shim returns
0= handled (the framework serializes the response); do not setres->handled(that flag skips response serialization and is only for self-streaming handlers).
In other words, most handlers should "compute a Value and return it, letting the framework serialize," rather than writing bytes into res themselves. Set res->handled by mistake and the framework skips serialization — the client gets an empty body. This contract is the core of sub-prop 2 and the precondition for VM reuse: once the shim finishes a request, the framework calls vm_after_request to clear state.
Builtin Seeding
Before fork, bridge.c also seeds a batch of builtin functions: language builtins like run/print/str/int/float/..., plus discovery/IO builtins like env/files/read_file/write_file/.../tools/skills/mcps. The latter enumerate agent-httpd's registries and .data/mcp-servers-router.json (write_file is atomic + 0600, lock_file uses flock exclusive lock). Since seeding runs once before fork and all workers inherit the same copy, there is no race.
VM Reuse and GC: How Requests Stay Clean
This is the section most prone to "imagined bugs." Conclusions first; the full source detail lives in the appendix at the end, for readers who want to audit the C implementation.
Process model: the main process parses .lume, registers all routes/tools, then calls agenthttpd_run(&cfg). agent-httpd takes over and defaults to fork-per-connection — each connection forks a worker that inherits a VM copy (bridge.c assumes VM init happens before fork). AST/Type is immutable, process-level shared, never freed; the VM is writable and private per worker, so reusing the VM across requests needs no locks. To be explicit: this is not a performance claim about general-purpose high-concurrency web serving — it is agent-httpd's current process-isolation model. Lume initializes the VM before fork, trading for per-worker state isolation and VM access that needs no cross-worker locks.
Registration window: before agenthttpd_run is the only registration moment; once run starts, the registry is a read-only snapshot, and any later call to agenthttpd_route / agenthttpd_tool is undefined behavior. Any business-logic change must happen before startup — which is also why --watch chooses "fork+exec restart the child" instead of "mutate the table at runtime."
Stack clear at request end: after a worker finishes a request, bridge calls vm_after_request to restore the VM to a reusable state:
static void vm_after_request(VM *vm) {
vm->error = false;
vm->error_msg[0] = '\0';
vm->stack_count = 0; /* clear the value stack directly */
vm->call_result = val_null(); /* call_result reinitialized (set per call) */
}
Clear error, clear the value stack (stack_count = 0), reset call_result — three things.
The value stack is a GC root: the mark-sweep GC (objects on a VM-attached Obj heap) triggers before allocating a new object, and its mark phase starts from roots including the value stack, the globals table, the active-environment chain, and all registered handlers. stack_count = 0 at request end removes temporaries from the root set — clearing the stack removes the request-level GC roots, while the GC performs the actual object reclamation at the next allocation boundary; two gates with a clear division of labor.
The iron rule for editing this code: as the architecture doc specifies, the value stack is a GC root; never let a value dangle. Before allocating a new object that references V, V must stay on the stack (or have another root); otherwise a GC inserted right there reclaims V and you get a dangling pointer. Full reasoning in the appendix.
A second gate: make asan builds a separate bin/lume-asan with -fsanitize=address,undefined; overflow and UB detection are fully on, while LSan leak detection is exempt by design (Type objects are process-lifetime, and --watch's fork+exec restarts would turn transient allocations into false-positive leaks) — far more reliable than eyeballing whether a field was missed.
One more often-overlooked point: lume has no built-in TLS. The server links only libc and explicitly does not link OpenSSL — inbound traffic is bare HTTP only — the source comment says it outright: the server links nothing beyond libc, by project rule (no OpenSSL) — listening on a raw TCP socket with no certificate handling anywhere. So whenever you want to expose it publicly and serve HTTPS, the README itself recommends putting it "behind a reverse proxy with auth" — in production you'll typically still need an nginx / Caddy for TLS termination + authentication. In other words, "no nginx" only holds for local-run / loopback scenarios; once it's on a public HTTPS endpoint, the reverse proxy becomes mandatory, not optional.
Reproducing: Toolchain and Verification
To verify the above yourself, the toolchain is ready:
| Command / target | Purpose |
|---|---|
make check |
Type-check all examples (demo/lang-basics/hello/hub/sqlite-write), confirm no compile-time DSL errors |
make test |
Build + frontend + unit tests (tests/smoke.c interpreter tests, tests/tools_driver.c tool registration and tools_dispatch JSON round-trip) + tests/run_all.sh end-to-end |
bin/lume --check <file> |
Offline static validation, zero side effects |
bin/lume --dump <file> |
Print the AST, inspect the tree |
make dev / make hub |
Start the :8081 / :8083 profiles |
make asan |
ASan/UBSan memory-safety build and regression |
After starting a server you can probe with curl: GET /hello, GET /sum, POST /echo, chat via POST /react/api/chat, the static /dsl page, and the /discovery aggregation page. The :8083 profile started by make hub deserves a look on its own — it showcases the three capability types of tsm-hub: skills, mcps, and tools (5 skill SKILL.md corpora, 5 MCPs, 10 built-in gateway tools), the whole catalog exposed to clients, all carried by the single examples/hub.lume file.
The GC stress section of tests/run_all.sh (called by make test) is the key use case for VM reuse:
# 4c. GC stress: many requests must not crash workers or corrupt state.
for _ in $(seq 1 150); do
curl -s -m 2 http://127.0.0.1:$PORT/sum > /dev/null
curl -s -m 2 -X POST -d '{"n":7}' http://127.0.0.1:$PORT/echo > /dev/null
done
kill -0 $SERVER_PID 2>/dev/null || fail "server died under load"
150 × 2 = 300 requests; the assertion is server died under load (the server survives) and that worker processes remain. What it verifies: under these test conditions, VM reuse across requests shows no obvious crashes or state corruption — a regression gate for the reuse path, not proof that "reuse is correct."
Source Cross-Check Table
Collapsing the whole article into a source cross-check table:
| Claim | Backing |
|---|---|
Bridge is one-way translation: DSL declaration → agenthttpd_route/agenthttpd_tool |
src/bridge.c:244-251 (routes), :388-423 (tools) |
| All DSL routes/tools share one C shim, looked up by index | src/bridge.c:205 (route_shim), :344 (tool_shim) |
Shim returns 0 = handled; res->handled only for self-streaming |
ARCHITECTURE.md §3.2 |
| Tool whitelist filters at registration; unlisted not registered/spawned | src/bridge.c:410 (agenthttpd_tool_allowed) |
Registration window before agenthttpd_run; read-only snapshot after |
ARCHITECTURE.md §2.1 |
| One VM per process, fork-inherited copy private to each worker, no cross-worker VM locks | ARCHITECTURE.md §1.1 / §2.1 |
vm_after_request clears error / stack / call_result (incl. stack_count = 0) |
src/bridge.c:25-31 |
| Value stack is a GC root; GC triggers before alloc; threshold 2 MB → doubles to 1 GB | src/value.c:21,33,80,103; src/interp.c:829 |
make asan checks overflow/UB, exempts leak detection by design |
Makefile:296-306 |
| 300-request stress test exists, asserts "no crash" not "fixed a leak" | tests/run_all.sh:176-183 |
This table lists only provable "design intent ↔ code implementation" cross-checks; for anything that cannot be pinned to source as a quantitative claim, this article does not pad the page with fabricated numbers.
Why C11 single binary instead of Node / Python + a framework?
It is a design trade-off — keep business logic entirely inside .lume scripts, with bridge.c as the glue between the interpreter and libagenthttpd.a, uniformly serving static sites, JSON APIs, SSE chat, and SSR pages. Dropping the multi-language runtime sharply cuts deploy size and ops complexity, starts fast, and ships as one file. The cost is that the bridge layer’s reliability — especially VM lifecycle and GC — rests entirely on C code, which is why the project also ships make asan as a memory-safety gate. The language’s raw “speed” is common sense and not a selling point; what is defensible is “design goal + verifiable correctness,” not an unmeasured benchmark.
What is the bridge.c shim contract, and what happens if I break it?
The shim is the convention between a route/tool handler and the HTTP framework. The correct move is to return 0 meaning “handled,” letting the framework serialize the response; only self-streaming handlers (writing bytes straight to the connection) should set res->handled. Violating either causes malformed or silently dropped responses — e.g. returning non-zero makes the framework take the “unhandled” branch without error, showing up only as the client getting no expected body. So most handlers should “compute a Value and return it, let the framework serialize.”
How is the VM reused across requests? Is it actually safe, or does it leak?
Each worker (fork-per-connection) inherits a VM copy; after a request, vm_after_request restores the VM to a reusable state. It clears three things: error, stack (stack_count = 0, zeroes the value stack directly), and call_result. The value stack is itself a GC root: zeroing the stack removes the request-level GC roots, and the GC does the actual reclamation at allocation boundaries. The real code and the architecture doc both state it clears error/stack/call_result.
When is tool registration effective? Can it change at runtime?
Before agenthttpd_run starts is the registration window. Once run starts, the framework takes a read-only snapshot of the registry; any later call to agenthttpd_route / agenthttpd_tool is undefined behavior. Builtin seeding runs once before fork; all workers inherit the same seed, no race. Whitelists (HARNESS_TOOLS_ALLOW / HARNESS_SKILLS_ALLOW / MCP_ALLOW) filter at registration — unlisted are not registered or spawned. So business-logic changes must happen before the server starts, which is also why --watch hot reload restarts the child instead of mutating the table at runtime.
How do I verify Lume's memory safety, rather than just "looks like it didn't crash"?
Two layers. Layer one, correctness: make test runs tests/run_all.sh, whose 300-request GC stress asserts the server survives the load, workers persist, and state isn’t corrupted. Layer two, memory safety: make asan builds an instrumented binary with AddressSanitizer + UndefinedBehaviorSanitizer to catch heap/stack overflows and UB in Lume’s own code; it runs the same --check on all examples + unit tests + tool dispatch, only exempting LSan’s leak detection by design (Type objects are process-lifetime and --watch restarts via fork+exec, so transient check types would false-positive). Overflow/UB detection is fully on — far more reliable than eyeballing whether vm_after_request missed a field.
Deep Dive: VM Lifecycle and GC Source Details
This section is for readers auditing the C implementation; skip it if you only care about what Lume is — the main-line conclusions do not depend on it.
After fork, each worker gets a VM copy. After one agenthttpd_run, many worker processes exist, each holding a mirror of "immutable AST/Type + one writable VM." Because the VM is a fork-inherited copy and workers don't interfere, the VM level naturally needs no cross-worker locks.
GC root set and adaptive threshold. gc_collect (src/value.c:77) marks live objects starting from a root set (value.c:80-89):
- the entire value stack:
for (int i = 0; i < vm->stack_count; i++) mark_value(vm->stack[i]); - globals
globals, the active-environment chainactive_envs,server_config,default_handler, all registered route handlers, all registered tool handlers, andcall_result.
The sweep reclaims unmarked objects, bytes_allocated falls, and the threshold doubles (value.c:103):
if (vm->gc_threshold < (1u << 30)) vm->gc_threshold *= 2;
The initial threshold is 2 MB (src/interp.c:829: vm->gc_threshold = 2 * 1024 * 1024;), doubling on each GC up to a 1 GB cap: little allocation means almost no GC; as pressure rises, GC frequency and threshold adapt together, instead of "scanning the whole heap on every allocation."
The no-dangle rule in full. If you allocate a new object A that needs to reference value V, then between "A is allocated" and "A actually holds V," V must stay on the stack (or have another root). Otherwise a GC inserted right there reclaims V, and A ends up with a dangling pointer. This also explains why vm_after_request doing stack_count = 0 is correct: at request end those temporary stack values are unreferenced; removing them from the root set lets the next GC reclaim cleanly.
This Is an Experiment
Pull the perspective back at the end. The bridge, VM, and GC dissected in this article serve a bigger question: if AI writes most of the full-stack code from now on, can a developer's language stack converge to Lume + React? The server is a strongly-typed DSL welded into a C binary; the client is a standard React project; the two toolchains don't hold each other hostage (covered earlier); and the glue in between — HTTP frameworks, serialization, deployment scripts — is eaten by language-level declarations. Lume does not aim to replace any general-purpose language. Its bet is different: when AI becomes the primary code writer, "declare capability, catch errors at compile time, keep the review surface small" matters more than "big ecosystem, free-form style." make hub exposing the capability catalog of the tsm-hub gateway is already a first rehearsal of that direction.
Project Repository
- GitHub:
https://github.com/erishen/lume(C11 single binary + agent-httpd submodule) - Docs:
ARCHITECTURE.md(system composition and design decisions),DEVELOPMENT.md(code-level dev conventions and tests), andLUME.md(DSL user guide) ship with the repo.
Leave a reply