Two AI coding agents — one to build new services in Igropyr's actor model, one to port an existing Node/Express app.
Self-contained Markdown · API map labeled for Igropyr 1.3.0
Hand it a spec, a single endpoint, or the framework itself. igropyr-dev writes idiomatic Chez Scheme on Igropyr — direct blocking style, let-it-crash error handling, and a v1.3.0 API it reads from source before writing a line.
No async/await, no callbacks. mysql-query, http-get and receive park only the calling green process — Node's await chains flatten into ordinary sequential lines.
Rule zero: it greps the .sc source headers before writing. A map of the app-facing modules stops it from hallucinating procedure names — it is a subset, not a census, so when the map is silent the source wins.
define-checked-record for structured data, define-checked at module boundaries — the type discipline that replaces TS interfaces, and that knows never to break a tail call.
Covers the actor model, the app-facing module map, records & contracts, the build/test incantation (.sc extension mapping and all), and the gotchas that bite first — one Markdown file, fed to any AI coding agent as its instructions.
Hand it a Node.js route, a middleware, or a whole small Express app — get back the Igropyr port. Node's single-threaded event loop with Promises, async/await and defensive try/catch becomes Igropyr's actor model with spawn/send/receive, a supervised worker pool, and Let It Crash — Scheme a maintainer would have written, not JavaScript wearing parentheses.
The scheduler yields for you. I/O calls already block only the calling process, so promise chains flatten into ordinary sequential code — no promise type invented.
Blanket 500-catchers are deleted: that is the worker supervisor's job. A guard stays only where there is a specific, meaningful recovery.
A module-level Map shared across requests becomes a process (gen-server), because workers never share mutable state except through a process.
| Node.js / Express | Igropyr |
|---|---|
async function / await p | straight-line synchronous Scheme; I/O parks the calling process only |
try { } catch → 500 | delete it — the supervisor retries then answers 500 (Let It Crash) |
app.get('/x', h) | (app-get app "/x" (lambda (req res) ...)) |
(req, res, next) | (lambda (req res next) ...) — call (next) |
req.params.id | (req-param req "id") |
req.body (json) | (req-json req); read with (json-ref j "name") |
res.json(obj) | (send-json! res alist) — string keys, and an array is a vector |
res.status(c) | (set-status! res c) then a send-*! |
SSE / res.write | (sse-start! res) + (sse-send! res msg) in a spawned process |
ws.on('message') | (app-ws app "/p" (lambda (ws req) ...)), loop on (ws-recv ws) |
setInterval / worker | (spawn (lambda () (let loop () ... (sleep-ms n) (loop)))) |
EventEmitter / pub-sub | (igropyr pubsub): subscribe / publish |
| stateful singleton | a gen-server ((igropyr gen-server)) |
JSON.parse / stringify | (string->json s) / (json->string v) |
Hard rules it never breaks: round parens only, English comments, and it reads the real .sc source before writing — flagging anything with no clean Igropyr equivalent rather than inventing an API.