Files
rose-ash/plans/agent-briefings/ruby-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.5 KiB

ruby-on-sx loop agent (single agent, queue-driven)

Role: iterates plans/ruby-on-sx.md forever. Fibers via delcc is the headline showcase — Fiber.new/Fiber.yield/Fiber.resume are textbook delimited continuations with sugar, where MRI does it via C-stack swapping. Plus blocks/yield (lexical escape continuations, same shape as Smalltalk's non-local return), method_missing, and singleton classes.

description: ruby-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/ruby-on-sx.md. Isolated worktree, forever, one commit per feature. Never push.

Restart baseline — check before iterating

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

The queue

Phase order per plans/ruby-on-sx.md:

  • Phase 1 — tokenizer + parser. Keywords, identifier sigils (@ ivar, @@ cvar, $ global), strings with interpolation, %w[]/%i[], symbols, blocks {|x| …} and do |x| … end, splats, default args, method def
  • Phase 2 — object model + sequential eval. Class table, ancestor-chain dispatch, super, singleton classes, method_missing fallback, dynamic constant lookup
  • Phase 3 — blocks + procs + lambdas. Method captures escape continuation ^k; yield / return / break / next / redo semantics; lambda strict arity vs proc lax
  • Phase 4THE SHOWCASE: fibers via delcc. Fiber.new/Fiber.resume/Fiber.yield/Fiber.transfer. Classic programs (generator, producer-consumer, tree-walk) green
  • Phase 5 — modules + mixins + metaprogramming. include/prepend/extend, define_method, class_eval/instance_eval, respond_to?/respond_to_missing?, hooks
  • Phase 6 — stdlib drive. Enumerable mixin, Comparable, Array/Hash/Range/String/Integer methods, 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/ruby/** and plans/ruby-on-sx.md. Do not edit spec/, hosts/, shared/, other lib/<lang>/ dirs, lib/stdlib.sx, or lib/ root. Ruby primitives go in lib/ruby/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.

Ruby-specific gotchas

  • Block return vs lambda return. Inside a block { ... return v }, return invokes the enclosing method's escape continuation (non-local return). Inside a lambda ->(){ ... return v }, return returns from the lambda. Don't conflate. Implement: blocks bind their ^method-k; lambdas bind their own ^lambda-k.
  • break from inside a block invokes a different escape — the iteration loop's escape — and the loop returns the break-value. next is escape from current iteration, returns iteration value. redo re-enters current iteration without advancing.
  • Proc arity is lax. proc { |a, b, c| … }.call(1, 2)c = nil. Lambda is strict — same call raises ArgumentError. Check arity at call site for lambdas only.
  • Block argument unpacking. [[1,2],[3,4]].each { |a, b| … } — single Array arg auto-unpacks for blocks (not lambdas). One arg, one Array → unpack. Frequent footgun.
  • Method dispatch chain order: prepended modules → class methods → included modules → superclass → BasicObject → method_missing. super walks from the defining class's position, not the receiver class's.
  • Singleton classes are lazily allocated. Looking up the chain for an object passes through its singleton class first, then its actual class. class << obj; …; end opens the singleton.
  • method_missing — fallback when ancestor walk misses. Receives (name_symbol, *args, &blk). Pair with respond_to_missing? for respond_to? to also report true. Do not swallow NoMethodError silently.
  • Ivars are per-object dicts. Reading an unset ivar yields nil and a warning (-W). Don't error.
  • Constant lookup is first lexical (Module.nesting), then inheritance (Module.ancestors of the innermost class). Different from method lookup.
  • Object#send invokes private and public methods alike; Object#public_send skips privates.
  • Class reopening. class Foo; def bar; …; end; end plus a later class Foo; def baz; …; end; end adds methods to the same class. Class table lookups must be by-name, mutable; methods dict is mutable.
  • Fiber semantics. Fiber.new { |arg| … } creates a fiber suspended at entry. First Fiber.resume(v) enters with arg = v. Inside, Fiber.yield(w) returns w to the resumer; the next Fiber.resume(v') returns v' to the yield site. End of block returns final value to last resumer; subsequent Fiber.resume raises FiberError.
  • Fiber.transfer is symmetric — either side can transfer to the other; no resume/yield asymmetry. Implement on top of the same continuation pair, just don't enforce direction.
  • Symbols are interned. :foo == :foo is identity. Use SX symbols.
  • Strings are mutable. s = "abc"; s << "d"; s == "abcd". Hash keys can be strings; hash dups string keys at insertion to be safe (or freeze them).
  • Truthiness: only false and nil are falsy. 0, "", [] are truthy.
  • Test corpus: custom + curated RubySpec slice. Place programs in lib/ruby/tests/programs/ with .rb 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/ruby-on-sx.md inline.
  • Short, factual commit messages (ruby: Fiber.yield + Fiber.resume (+8)).
  • One feature per iteration. Commit. Log. Next.

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