sigil-nrepl
Network REPL server for Sigil — provides a JSON-RPC-style nREPL endpoint that editors and live-development tools connect to. Built on (sigil repl) for prompt rendering and command parsing, and on (sigil socket) for transport.
Long-running Sigil apps (live-crafter, cinder-cantata, game servers, etc.) embed an nREPL server so the developer can evaluate code, switch modules, inspect state, and iterate on running code without restarting the process.
Modules
| Module | Purpose |
|---|---|
(sigil nrepl) | nREPL server: connection handling, session state, op dispatch (eval/doc/...) |
(sigil nrepl client) | nREPL client: connect, send requests, named-connection registry |
Wire protocol
Messages are length-prefixed S-expressions:
<length:u32-be><sexp>
Request format: (request :id <string> :op <symbol> [:module <string>] [...params])
Response format: (response :id <string> :status <symbol> [...results])
Ops supported: eval, abort (alias interrupt), complete, doc, describe, macroexpand, switch-module, modules, plus the debug-protocol ops (debug-policy, debug-state, debug-frames, debug-restarts, debug-quit).
Interrupt / abort
Expression evals run cooperatively — one preemptive-yield slice per nrepl-process-pending — so a CPU-bound eval (e.g. (let loop () (loop))) does not freeze the host's loop. Interrupt one from a second connection:
Read the rest
(request :id <abort-id> :op abort :target-id <eval-request-id>)
The abort responder gets :status ok :aborted <eval-request-id>; the interrupted eval receives :status error :code "interrupted"; the session survives and evaluates normally afterward. (A single top-level form that mixes a top-level define with a long loop runs on the immediate, non-abortable path — define at top level, then run, as separate requests.)
Output streaming
Output written during an eval is streamed back incrementally as it happens, before the final response, carrying the original eval request id:
(response :id <eval-id> :status out :out <string>) ; stdout (response :id <eval-id> :status err :err <string>) ; stderr
followed by the normal terminal (response :id <eval-id> :status ok :value …). The bundled (sigil nrepl client) transparently drains these frames and returns the terminal response from nrepl-eval.
Embedding an nREPL server
(import (sigil nrepl))
(define server (nrepl-start port: 7888))
;; In your app's main loop, drain pending requests:
(nrepl-process-pending server)
;; On shutdown:
(nrepl-stop server)Connecting from a client
(import (sigil nrepl client))
(define conn (nrepl-connect "127.0.0.1" 7888))
(nrepl-eval conn "(+ 1 2)") ; => ((status . ok) (value . "3"))
(nrepl-switch-module conn "(my app)")
(nrepl-disconnect conn)Build and test
sigil deps install sigil build sigil test --report
Local development against an unreleased sigil monorepo or against a local sigil-repl checkout:
sigil build --redirects ./dev-redirects.sgl
License
BSD-3-Clause. See LICENSE in the sigil monorepo for the canonical copy.
$ git clone https://codeberg.org/sigil/sigil-nrepl
v0.17.0 — latest by version, released 10 Jul 2026
All releases — 7 tags in this repository.
One page per file, and every line has a permalink.
e5618482
Fix macroexpand module context
5da82310
09c5b1c8
7f613324
f955a275
6c038661
00f93797
The last 20 commits keep a page here. Everything older lives in the clone.