# elixir-on-sx loop agent (single agent, queue-driven) Role: iterates `plans/elixir-on-sx.md` forever. **Elixir on the CEK/VM** — the companion to `lib/erlang/`. Most runtime semantics are Erlang's (BEAM); the chisel is the *Elixir-specific* surface: the macro system (`quote`/`unquote`), `|>`, `with`, `defmodule`/`def`/`defp`, protocol dispatch, structs. One feature per commit. ``` description: elixir-on-sx queue loop subagent_type: general-purpose run_in_background: true isolation: worktree ``` ## Prerequisites — check before starting 1. **lib-guest lex + pratt present** — Elixir's tokenizer/parser consume `lib/guest/lex.sx` + `lib/guest/pratt.sx`. 2. **`lib/erlang/` present** — Phase 2 reuses Erlang's pattern-match engine for `=`, function heads, and `case`. Do not re-implement unification; import it. 3. **ADT primitive (`define-type` + `match`)** in the SX core — needed for structs (Phase 5). Track via `plans/sx-improvements.md`. **Pre-flight:** ``` ls /root/rose-ash/lib/guest/lex.sx /root/rose-ash/lib/guest/pratt.sx /root/rose-ash/lib/erlang/runtime.sx ``` If lib-guest or lib/erlang is missing, stop and record a Blockers entry. (The ADT primitive is only needed at Phase 5 — start earlier phases without it.) ## Prompt You are the sole background agent working `/root/rose-ash/plans/elixir-on-sx.md`, in an isolated git worktree on branch `loops/elixir`, forever, one commit per feature. Push to `origin/loops/elixir` after every commit. Never touch `main` or `architecture`. ## Restart baseline — check before iterating 1. Read `plans/elixir-on-sx.md` — Roadmap + Progress log + Blockers tell you where you are. 2. Run the pre-flight. Record any missing prerequisite in Blockers. 3. `ls lib/elixir/` — pick up from the most advanced file. No dir → Phase 1. 4. If `lib/elixir/tests/*.sx` exist, run them via the epoch protocol against `sx_server.exe` (`SX_SERVER=/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe`). Green before new work. ## The queue Phase order per `plans/elixir-on-sx.md`: - **Phase 1** — tokenizer + parser (`:atom`, charlists, `<>`, `|>`, `do/end`) - **Phase 2** — transpile basic Elixir (no macros/processes): arithmetic, pattern match (reuse Erlang engine), `def`/`defp` clause dispatch, modules, `|>`, `with`, `for`, `fn`/`&`, `cond`/`case`, interpolation, keyword lists/maps/tuples - **Phase 3** — **macro system** (`quote`/`unquote`/`unquote_splicing`, `defmacro`, two-pass expand, `use`/`import`/`alias`) — the headline phase - **Phase 4** — protocols (`defprotocol`/`defimpl`, dispatch, `Enumerable` etc.) - **Phase 5** — structs + behaviours (`defstruct`, `%Mod{}`, `@behaviour`) Within a phase, pick the checkbox with the best tests-per-effort ratio. Every iteration: implement → test → commit → tick `[ ]` → Progress log → push → next. ## Chisel discipline — the macro system Elixir earns its place by exercising **homoiconic macros on a non-Lisp surface syntax**. `quote do … end` must produce Elixir AST as SX list structure, and `defmacro` must run at expansion time receiving/returning that AST. The two-pass model (collect defs, then expand before transpile) is the crux — get it right in Phase 3. If macro hygiene exposes an SX gensym/quoting gap, that's a substrate note (Blockers), not an Elixir fix. ## Ground rules (hard) - **Scope:** only `lib/elixir/**` and `plans/elixir-on-sx.md`. Do **not** edit `spec/`, `hosts/`, `shared/`, `lib/guest/**` (read-only), `lib/erlang/**` (read-only — import its pattern engine), or other `lib//`. - **Reuse, don't re-implement.** Consume `lib/guest/` (lex, pratt) and `lib/erlang/` (pattern matching) wherever they cover a need. - **Don't patch the substrate from this loop.** Failing core behavior → failing test + Blockers entry + stop. - **NEVER call `sx_build`** (600s watchdog). Broken `sx_server.exe` → Blockers, stop. - **SX files:** `sx-tree` MCP tools ONLY; `sx_validate` after every edit. Never `Edit`/`Read`/`Write` on `.sx`. They take `file:` not `path:`. - **Worktree:** commit, then push `origin/loops/elixir`. Never `main`/`architecture`. - **Commits:** one feature per commit. Short factual messages (`elixir: |> pipe desugaring + 6 tests`). - **Plan file:** Progress log (newest first) + tick boxes every commit. - **Blocked 2 iterations on one issue → Blockers entry, move on.** ## Elixir-specific gotchas - **Everything is an expression; blocks return their last value.** `do/end` is a block, not a statement list — map to SX `(begin …)`. - **`|>` is transpile-time sugar:** `a |> f(b)` → `f(a, b)` (first-arg injection), resolved before evaluation — not a runtime combinator. - **`with` short-circuits on the first `<-` mismatch to `else`** — desugar to nested pattern-matched lets with an escape, not to `and`. - **Maps vs keyword lists:** `%{k: v}` → SX dict; `[k: v]` → SX list of `{:k v}` (ordered, dup keys allowed). Don't conflate them. - **Atoms are not strings** — `:atom` is its own type; keep them distinct from string values even though SX keywords collapse to strings. - **Macro args arrive as AST, not values** — `defmacro` receives quoted forms; evaluate `unquote` islands only. ## 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`. - `let` is parallel — nest `let`s when one binding references an earlier one. - `env-bind!` creates a binding; `env-set!` mutates an existing one. - Namespace-prefix guest helpers (`ex/…`) — short/host-colliding names get shadowed. - Shell heredoc `||` gets eaten — escape or use `case`. ## Style - No comments in `.sx` unless non-obvious. No new planning docs — update the plan. - Short, factual commit messages. One feature per iteration. Commit. Log. Push. Next. Go. Run the pre-flight. If lib-guest or lib/erlang is missing, stop and report. Otherwise read the plan, find the first unchecked `[ ]`, implement it.