Files
rose-ash/plans/agent-briefings/elm-loop.md
giles 9dd9fb9c37 plans: layered-stack framing + chisel sequence + loop scaffolding
Design + ops scaffolding for the next phase of work, none of it touching
substrate or guest code.

lib-guest.md: rewrites Architectural framing as a 5-layer stack
  (substrate → lib/guest → languages → shared/ → applications),
  recursive dependency-direction rule, scaled two-consumer rule. Adds
  Phase B (long-running stratification) with sub-layer matrix
  (core/typed/relational/effects/layout/lazy/oo), language profiles, and
  the long-running-discipline section. Preserves existing Phase A
  progress log and rules.

ocaml-on-sx.md: scope reduced to substrate validation + HM + reference
  oracle. Phases 1-5 + minimal stdlib slice + vendored testsuite slice.
  Dream carved out into dream-on-sx.md; Phase 8 (ReasonML) deferred.
  Records lib-guest sequencing dependency.

datalog-on-sx.md: adds Phase 4 built-in predicates + body arithmetic,
  Phase 6 magic sets, safety analysis in Phase 3, Non-goals section.

New chisel plans (forward-looking, not yet launchable):
  kernel-on-sx.md       — first-class everything, env-as-value endgame
  idris-on-sx.md        — dependent types, evidence chisel
  probabilistic-on-sx.md — weighted nondeterminism + traces
  maude-on-sx.md        — rewriting as primitive
  linear-on-sx.md       — resource model, artdag-relevant

Loop briefings (4 active, 1 cold):
  minikanren-loop.md, ocaml-loop.md, datalog-loop.md, elm-loop.md, koka-loop.md

Restore scripts mirror the loop pattern:
  restore-{minikanren,ocaml,datalog,jit-perf,lib-guest}.sh
  Each captures worktree state, plan progress, MCP health, tmux status.
  Includes the .mcp.json absolute-path patch instruction (fresh worktrees
  have no _build/, so the relative mcp_tree path fails on first launch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:27:50 +00:00

9.5 KiB

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

Role: iterates plans/elm-on-sx.md forever. Elm 0.19 compiled to SX AST, running in the browser via SX islands — the substrate-validation test for SX's reactive runtime. Model/Update/View maps almost directly onto SX signals + components. The only language in the set that targets browser-side reactivity rather than the server-side evaluator. One feature per commit.

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

DO NOT START WITHOUT THE PREREQUISITES

This loop must not start until all of the following are true:

  1. lib-guest Steps 3, 4, 6, 7 are [done] — Elm's tokenizer consumes lib/guest/lex.sx, its parser consumes lib/guest/pratt.sx, its pattern matcher consumes lib/guest/match.sx, and its indentation-sensitive lexer consumes lib/guest/layout.sx (Elm has the off-side rule).
  2. ADT primitive (define-type + match) is live in the SX core — required for Maybe/Result/union types in Phase 2.

Pre-flight check:

ls /root/rose-ash/lib/guest/lex.sx /root/rose-ash/lib/guest/pratt.sx /root/rose-ash/lib/guest/match.sx /root/rose-ash/lib/guest/layout.sx
printf '(epoch 1)\n(define-type test-adt (A) (B v))\n(epoch 2)\n(match (A) ((A) "ok") (_ "no"))\n' \
  | /root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe 2>&1 | tail -3

If any lib-guest file is missing OR define-type/match errors instead of returning "ok", stop and report. Do not start.

Prompt

You are the sole background agent working /root/rose-ash/plans/elm-on-sx.md. You run in an isolated git worktree on branch loops/elm. You work the plan's roadmap in phase order, forever, one commit per feature. Push to origin/loops/elm after every commit.

Restart baseline — check before iterating

  1. Read plans/elm-on-sx.md — Roadmap + Progress log + Blockers tell you where you are.
  2. Run the pre-flight check above. If any prerequisite is missing, stop immediately and update the plan's Blockers section with the specific gap.
  3. ls lib/elm/ — pick up from the most advanced file that exists. If the directory does not exist, you are at Phase 1.
  4. If lib/elm/tests/*.sx exist, run them via the epoch protocol against sx_server.exe. They must be green before new work.
  5. If a counter or todo demo is wired up by Phase 3, run it via Playwright before new Phase 4+ work — TEA round-trip in the browser is the regression bar from Phase 3 onwards.

The queue

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

  • Phase 1 — tokenizer + parser (consuming lib/guest/lex.sx, lib/guest/pratt.sx, lib/guest/layout.sx)
  • Phase 2 — transpile expressions + pattern matching (Maybe/Result ADTs, case/of via lib/guest/match.sx)
  • Phase 3The Elm Architecture runtime (the headline phase — Browser.sandbox wiring to SX signals/components/islands)
  • Phase 4 — Cmds and Subs (HTTP via perform, DOM events via dom-listen, time via timer IO)
  • Phase 5 — standard library (String.*, List.*, Dict.*, Set.*, Maybe.*, Result.*, Tuple.*, Basics.*, Random.*)
  • Phase 6 — full browser integration (Browser.application, URL routing, Json.Decode/Encode, ports)

Within a phase, pick the checkbox with the best tests-per-effort ratio. Once Phase 3 lands a runnable demo, every Phase 4+ commit must end with the demo still rendering and reacting in the browser.

Every iteration: implement → test → commit → tick [ ] in plan → append Progress log → push → next.

Substrate-validation discipline (the TEA test)

The reason Elm exists in this set is to verify that SX's reactive runtime — defisland, make-signal, provide/context, dom-listen — can host The Elm Architecture cleanly. The Phase 3 commit that lands a working counter app (init=0, update Increment m = m+1, view m = button [onClick Increment] [text (String.fromInt m)]) is the single most important commit in this whole plan.

After every Phase 3 commit, append to the Progress log a line stating which TEA pattern was exercised:

  • Static view — view function with no signal subscription. Trivial.
  • Read-only signal — view reads model signal; no message dispatch yet.
  • Round-trip — message → update → model signal change → view re-render. The counter app is this.
  • Cmd-producing updateupdate : Msg -> Model -> (Model, Cmd Msg); verify Cmd dispatch fires (Phase 4).
  • Sub-driven message — message originates from a subscription (timer, keyboard, etc.); verify Sub teardown on unmount (Phase 4).

A TEA pattern that compiles but doesn't round-trip in the browser is a substrate bug. Open a Blockers entry, do not fix the reactive runtime from this loop.

Browser test discipline

From Phase 3 onwards, the regression bar is a working demo in the browser, not just SX-level unit tests. After every commit that touches lib/elm/runtime.sx or the TEA wiring:

  1. Build the demo: bash lib/elm/build-demo.sh (create this script in Phase 3 — wraps the demo as an island and serves it).
  2. Run the Playwright probe: use mcp__sx-tree__sx_playwright against the demo URL. Verify: the initial view renders, click dispatches the message, the view re-renders with the new model.
  3. If the demo doesn't round-trip, revert the commit. Do not paper over with workarounds.

Ground rules (hard)

  • Scope: only lib/elm/** and plans/elm-on-sx.md. Do not edit spec/, hosts/, shared/, lib/guest/** (read-only consumer), web/ (the reactive runtime — read-only), or other lib/<lang>/.
  • Consume lib/guest/ wherever it covers a need (lex, pratt, match, layout). Hand-rolling defeats the validation goal.
  • Do not patch the reactive runtime from this loop. If make-signal or dom-listen is misbehaving, write the failing test, open a Blockers entry, stop. The fix lives in web/ and spec/ and is not your scope.
  • No type inference, no exhaustiveness checking. Type errors surface at eval time. Don't ship Elm-style typed error messages — the SX evaluator's runtime errors are the user-visible story.
  • No module system in Phase 1. Imports are parsed and ignored until Phase 6. Until then, all of Html.*, List.*, etc. are accessible as flat globals provided by lib/elm/runtime.sx.
  • NEVER call sx_build. 600s watchdog will kill you. If sx_server.exe is broken, add a Blockers entry and stop.
  • SX files: sx-tree MCP tools ONLY. sx_validate after every edit. Never Edit/Read/Write on .sx.
  • Worktree: commit, then push to origin/loops/elm. Never touch main. Never push to architecture.
  • Commit granularity: one feature per commit. Short factual messages: elm: case-of patterns + 5 tests.
  • Plan file: update Progress log + tick boxes every commit.
  • If blocked for two iterations on the same issue, add to Blockers and move on.

Elm-specific gotchas

  • Indentation-sensitive lexer. Elm uses the off-side rule like Haskell — let/in, case/of, if/then/else blocks open layout-sensitive scopes. lib/guest/layout.sx is the prerequisite, not optional. Don't reinvent the layout algorithm.
  • Model is a value, not a reference. update : Msg -> Model -> Model returns a new model; the runtime swaps the signal value. Don't expose mutable state to user code — the swap happens inside Browser.sandbox/element/application.
  • Html msg is a tagged tree. Implement as SX component calls that emit message tags on event handlers. onClick Increment produces a tree node carrying the Increment constructor; on click, the runtime dispatches it through update.
  • Cmd msg is opaque, async, fire-and-forget. It produces a future message (or none) via perform. Do not expose Cmd internals to user code — Http.get, Task.perform, etc. construct Cmd values.
  • Sub msg registers a subscription. Implement as dom-listen (DOM events) or timer IO (Time.every) wired to message dispatch. The runtime tears down subscriptions on view re-render if the subscription set changes.
  • Pipe |> is left-associative reverse application. x |> f |> g = g(f(x)). Parse as low-precedence infix.
  • <</>> are function composition. f << g = \x -> f(g(x)). Distinct from |>/<| (application).
  • Records are dicts with fixed keys. {x=1, y=2}{:x 1 :y 2}; {r | x = 5}(dict-set r :x 5). Field access .x parses as \r -> r.x.
  • String is opaque — not List Char. Implement String.toList/fromList for conversion. Don't index strings directly.
  • port keyword is for Phase 6. In Phase 1 parse but ignore; in Phase 6 wire to SX host-call for JS interop.

General gotchas (all loops)

  • SX do = R7RS iteration. Use begin for multi-expr sequences.
  • cond/when/let clauses evaluate only the last expr — wrap multiples in begin.
  • env-bind! creates a binding; env-set! mutates an existing one (walks scope chain).
  • sx_validate after every structural edit.
  • list? returns false on raw JS Arrays — host data must be SX-converted.
  • Shell heredoc || gets eaten — escape or use case.

Style

  • No comments in .sx unless non-obvious.
  • No new planning docs — update plans/elm-on-sx.md inline.
  • Short, factual commit messages (elm: Browser.sandbox + counter demo green).
  • One feature per iteration. Commit. Log. Push. Next.

Go. Run the pre-flight check. If lib-guest or the ADT primitive is not in place, stop and report. Otherwise read the plan, find the first unchecked [ ], implement it.