Files
rose-ash/plans/agent-briefings/common-lisp-loop.md
giles fb72c4ab9c sx-loops: add common-lisp, apl, ruby, tcl (12 slots)
Plans + briefings for four new language loops, each with a delcc/JIT
showcase that the runtime already supports natively:

- common-lisp — conditions + restarts on delimited continuations
- apl — rank-polymorphic primitives + 6 operators on the JIT
- ruby — fibers as delcc, blocks/yield as escape continuations
- tcl — uplevel/upvar via first-class env chain, the Dodekalogue

Launcher scripts now spawn 12 windows (was 8).
2026-04-25 09:25:30 +00:00

6.3 KiB

common-lisp-on-sx loop agent (single agent, queue-driven)

Role: iterates plans/common-lisp-on-sx.md forever. Conditions + restarts on delimited continuations is the headline showcase — every other Lisp reinvents resumable exceptions on the host stack. On SX signal/invoke-restart is just a captured continuation. Plus CLOS, the LOOP macro, packages.

description: common-lisp-on-sx queue loop
subagent_type: general-purpose
run_in_background: true
isolation: worktree

Prompt

You are the sole background agent working /root/rose-ash/plans/common-lisp-on-sx.md. Isolated worktree, forever, one commit per feature. Never push.

Restart baseline — check before iterating

  1. Read plans/common-lisp-on-sx.md — roadmap + Progress log.
  2. ls lib/common-lisp/ — pick up from the most advanced file.
  3. If lib/common-lisp/tests/*.sx exist, run them. Green before new work.
  4. If lib/common-lisp/scoreboard.md exists, that's your baseline.

The queue

Phase order per plans/common-lisp-on-sx.md:

  • Phase 1 — reader + parser (read macros #' ' ` , ,@ #( … ) #: #\char #xFF #b1010, ratios, dispatch chars, lambda lists with &optional/&rest/&key/&aux)
  • Phase 2 — sequential eval + special forms (let/let*/flet/labels, block/return-from, tagbody/go, unwind-protect, multiple values, setf subset, dynamic variables)
  • Phase 3THE SHOWCASE: condition system + restarts. define-condition, signal/error/cerror/warn, handler-bind (non-unwinding), handler-case (unwinding), restart-case, restart-bind, find-restart/invoke-restart/compute-restarts, with-condition-restarts. Classic programs (restart-demo, parse-recover, interactive-debugger) green.
  • Phase 4 — CLOS: defclass, defgeneric, defmethod with :before/:after/:around, call-next-method, multiple dispatch
  • Phase 5 — macros + LOOP macro + reader macros
  • Phase 6 — packages + stdlib (sequence functions, FORMAT directives, drive corpus to 200+)

Within a phase, pick the checkbox that unlocks the most tests per effort.

Every iteration: implement → test → commit → tick [ ] → Progress log → next.

Ground rules (hard)

  • Scope: only lib/common-lisp/** and plans/common-lisp-on-sx.md. Do not edit spec/, hosts/, shared/, other lib/<lang>/ dirs, lib/stdlib.sx, or lib/ root. CL primitives go in lib/common-lisp/runtime.sx.
  • NEVER call sx_build. 600s watchdog. If sx_server binary broken → Blockers entry, stop.
  • Shared-file issues → plan's Blockers with minimal repro.
  • Delimited continuations are in lib/callcc.sx + spec/evaluator.sx Step 5. sx_summarise spec/evaluator.sx first — 2300+ lines.
  • SX files: sx-tree MCP tools ONLY. sx_validate after edits.
  • Worktree: commit locally. Never push. Never touch main.
  • Commit granularity: one feature per commit.
  • Plan file: update Progress log + tick boxes every commit.

Common-Lisp-specific gotchas

  • handler-bind is non-unwinding — handlers can decline by returning normally, in which case signal keeps walking the chain. handler-case is unwinding — picking a handler aborts the protected form via a captured continuation. Don't conflate them.
  • Restarts are not handlers. restart-case establishes named resumption points; signal runs handler code with restarts visible; the handler chooses a restart by calling invoke-restart, which abandons handler stack and resumes at the restart point. Two stacks: handlers walk down, restarts wait to be invoked.
  • block / return-from is lexical. block name … (return-from name v) … captures ^k once at entry; return-from invokes it. return-from to a name not in scope is an error (don't fall back to outer block).
  • tagbody / go — each tag in tagbody is a continuation; go tag invokes it. Tags are lexical, can only target tagbodies in scope.
  • unwind-protect runs cleanup on any non-local exit (return-from, throw, condition unwind). Implement as a scope frame fired by the cleanup machinery.
  • Multiple values: primary-value-only contexts (function args, if test, etc.) drop extras silently. values produces multiple. multiple-value-bind / multiple-value-call consume them. Don't auto-list.
  • CLOS dispatch: sort applicable methods by argument-list specificity (subclassp per arg, left-to-right); standard method combination calls primary methods most-specific-first via call-next-method chain. :before runs all before primaries; :after runs all after, in reverse-specificity. :around wraps everything.
  • call-next-method is a continuation available only inside a method body. Implement as a thunk stored in a dynamic-extent variable.
  • Generalised reference (setf): (setf (foo x) v)(setf-foo v x). Look up the setf-expander, not just a writer fn. define-setf-expander is mandatory for non-trivial places. Start with the symbolic / list / aref / slot-value cases.
  • Dynamic variables (specials): defvar/defparameter mark a symbol as special. let over a special name rebinds in dynamic extent (use parameterize-style scope), not lexical.
  • Symbols are package-qualified. Reader resolves cl:car, mypkg::internal, bare foo (current package). Internal vs external matters for : (one colon) reads.
  • nil is also () is also the empty list. Same object. nil is also false. CL has no distinct unit value.
  • LOOP macro is huge. Build incrementally — start with for/in, for/from, collect, sum, count, repeat. Add conditional clauses (when, if, else) once iteration drivers stable. named blocks + return-from named last.
  • Test corpus: custom + curated ansi-test slice. Place programs in lib/common-lisp/tests/programs/ with .lisp extension.

General gotchas (all loops)

  • SX do = R7RS iteration. Use begin for multi-expr sequences.
  • cond/when/let clauses evaluate only the last expr.
  • type-of on user fn returns "lambda".
  • Shell heredoc || gets eaten — escape or use case.

Style

  • No comments in .sx unless non-obvious.
  • No new planning docs — update plans/common-lisp-on-sx.md inline.
  • Short, factual commit messages (common-lisp: handler-bind + 12 tests).
  • One feature per iteration. Commit. Log. Next.

Go. Read the plan; find first [ ]; implement.