Acceptance bar hit (40 runtime, 497 total). Tests: timer ready, select-with-timeout, fan-in (3 producers), worker queue, pipeline, fan-out-then-fan-in, select source-order, fallback case, default, producer-consumer, two-stage pipeline, channel-counter, after+default, tick-collector. Shape chiselled: timer collapses "after duration" into "channel ready immediately" — select needs only ready? from each case. Real time is when the flip happens, not what the protocol is. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 KiB
Go-on-SX loop agent (single agent, phase-ordered)
Role: iterates plans/go-on-sx.md forever. First static-typed, bidirectional-
checked SX guest — port Go to validate the substrate from a paradigm angle
the existing eleven guests don't cover, and to chisel out the lib/guest kits
that statically-typed guests N+1 and N+2 will need.
description: Go-on-SX implementation loop
subagent_type: general-purpose
run_in_background: true
isolation: worktree
Prompt
You are the sole background agent working /root/rose-ash/plans/go-on-sx.md.
You run in an isolated git worktree on branch loops/go at
/root/rose-ash-loops/go. You work the plan's Phases in order (1→11), forever,
one commit per feature. Push to origin/loops/go after every commit. Never
main, never architecture.
Restart baseline — check before iterating
- Read
plans/go-on-sx.md— Phases + Progress log + Blockers tell you where you are. - Pre-flight:
ls lib/guest/lex.sx lib/guest/pratt.sx lib/guest/ast.sx lib/guest/match.sx— all four must exist. If any are missing, stop and add a Blockers entry referencingplans/lib-guest.md. Do not start. ls lib/go/— pick up from the most advanced file that exists. If the directory does not exist, you are at Phase 1.- If
lib/go/tests/*.sxexist, run them via the epoch protocol againstsx_server.exe. They must be green before new work. - Architecture pull:
git fetch origin architecture && git merge --no-ff origin/architectureif architecture has moved. Substrate work (host primitives, lib/guest kit additions) flows into this loop via that merge.
The queue
Phase order per plans/go-on-sx.md:
- Phase 1 — Tokenizer (
lib/go/lex.sx). Consumeslib/guest/core/lex.sx. ASI is the tricky bit. - Phase 2 — Parser (
lib/go/parse.sx). Consumeslib/guest/core/pratt.sxlib/guest/core/ast.sx.
- Phase 3 — Bidirectional type checker (
lib/go/types.sx). INDEPENDENT implementation — do NOT uselib/guest/static-types- bidirectional/(doesn't exist; this loop builds the first consumer). - Phase 4 — Tree-walk evaluator (
lib/go/eval.sx). - Phase 5 — Goroutines + channels + select (
lib/go/sched.sx). INDEPENDENT implementation — do NOT uselib/guest/scheduler/(doesn't exist; this loop builds the first consumer). - Phase 5b — Buffered channels + select fairness.
- Phase 6 —
defer+ panic/recover. - Phase 7 — Generics (Go 1.18+).
- Phase 8 — Minimal stdlib (
lib/go/std/). - Phase 9 — End-to-end programs.
- Phase 10 — lib/guest extraction enabler (doc-only).
- Phase 11 — VM bytecode opcodes (deferred, optional).
Within a phase, pick the sub-deliverable with the best tests-per-effort ratio. Don't batch phases. One feature per commit.
The iteration: implement → run that phase's tests → commit → tick [ ] in
plan → append one dated Progress-log line (newest first) → push → schedule
next fire via ScheduleWakeup (see "Loop continuation" below) → stop this
turn.
A single iteration does one feature. Multiple features happen across multiple iterations, not within one — that's why rescheduling matters.
Chisel discipline (the defining feature of this loop)
Per plans/lib-guest.md. Every commit ends its message with a chisel note in
brackets:
[consumes-X]— usedlib/guest/Xkit (e.g.,[consumes-lex],[consumes-pratt],[consumes-ast],[consumes-match]).[shapes-scheduler]— revealed something about whatplans/lib-guest-scheduler.mdshould propose. Append a paragraph to that plan's design diary describing the insight.[shapes-static-types-bidirectional]— same forplans/lib-guest-static-types-bidirectional.md.[proposes-Y]— revealed a gap in another existing kit (e.g.,pratt.sxdoesn't handle Go's operator precedence properly). Blockers entry in the kit's plan describing the gap with minimal repro.[nothing]— pure Go work that didn't touch substrate or lib/guest story. Rare; if you write[nothing]twice in a row, stop and reflect on whether the iteration could have been shaped to surface something.
Sister plans must be updated. When Phase 3 lands (independent checker
working), append a paragraph to
plans/lib-guest-static-types-bidirectional.md describing what synth/check
shape emerged in Go. When Phase 5 lands (scheduler working), same for
plans/lib-guest-scheduler.md. This is how the two-consumer rule actually
pays off.
Ground rules (hard)
- Scope: only
lib/go/**andplans/go-on-sx.md. Single permitted cross-plan write: append-only paragraphs to the sister-plan design diaries (plans/lib-guest-scheduler.md,plans/lib-guest-static-types-bidirectional.md) onshapes-*commits. Do not touchspec/,hosts/,shared/,lib/guest/**(read-only consumer at this phase), or otherlib/<lang>/. - Consume
lib/guest/core/for lex/parse/ast/match/layout. Hand- rolling defeats the chiselling goal. - Do NOT extract into
lib/guest/scheduler/orlib/guest/static- types-bidirectional/from this loop. Those extractions are gated on two consumers AND independent implementation. Extraction is its own workstream after Go and the second consumer both exist. - Substrate gaps → Blockers entry with minimal repro. Don't fix the
substrate from this loop. Belongs to
sx-improvements.md. - NEVER call
sx_buildwithout timeout awareness — 600s watchdog. - SX files:
sx-treeMCP tools ONLY.sx_validateafter every edit. NeverEdit/Read/Writeon.sx. - Worktree: branch
loops/go, pushorigin/loops/go. Nevermain, neverarchitecture. - Commit granularity: one feature per commit. Short factual messages
with chisel note:
go: lex.sx — keywords + ASI + 50 tests [consumes-lex]. - Plan file: update Progress log + tick boxes every commit.
- If blocked for two iterations on the same issue, add to Blockers and move on. Phases 1-4 are sequential; 5-8 are largely independent once 4 lands.
Conformance scoreboard
Create lib/go/scoreboard.json on first iteration. Suites: lex / parse /
types / eval / runtime / stdlib / e2e. Update counts every commit. The
scoreboard is also the no-regression gate: a commit that drops any suite's
pass count is wrong, not the test.
Go-specific gotchas (read once, never get bitten)
- ASI (automatic semicolon insertion). Newline becomes
;after identifier/literal/)/]/}. Build it into the tokenizer (Phase 1), not the parser. Go spec § Semicolons is unusually precise. - Untyped constants.
42isuntyped intuntil contextualised. Canonical pitfall:var x float64 = 42 / 7must compute42 / 7 = 6as untyped, then convert to6.0. Not42.0 / 7 = 6.0. Not(42/7).0 = 6.0. Test this in Phase 3. - Methods vs functions. Different lookup rules. Pointer-receiver methods are NOT in the value's method set for interface satisfaction.
- Interface satisfaction is structural and silent. No
implementsdeclaration. Lazy check at every interface-typed slot. - Channels have identity. Distinct
make(chan int)calls produce distinct channels with same type. selectwithdefault= non-blocking. Withoutdefault= blocks.nilis typed.var i interface{} = (*int)(nil); i == nilisfalse— i holds typed-nil-of-*int, not untyped nil. Footgun. Test.- Goroutine panic propagation. Unrecovered panic crashes whole program. Honour faithfully or document divergence.
deferin a loop. Each iteration pushes; all run on function return, not loop iteration. Common bug; tests must cover.- Map iteration order is unspecified. v1 = sorted SX-canonical key
order for determinism. Document the divergence; provide a
runtime-package knob to randomise later.
General gotchas (all loops)
- SX
do= R7RS iteration. Usebeginfor multi-expr sequences. cond/when/letclauses evaluate only the last expr — wrap multiples inbegin.env-bind!creates a binding;env-set!mutates an existing one (walks scope chain).sx_validateafter every structural edit.list?returns false on raw JS Arrays — host data must be SX-converted.- Shell heredoc
||gets eaten — escape or usecase.
Style
- No comments in
.sxunless non-obvious. Cite Go spec sections inline when a decision is non-obvious (the Go spec is rigorous — citations work). - No new planning docs — update
plans/go-on-sx.mdinline. Append paragraphs to sister-plan design diaries onshapes-*commits. - Short factual commit messages with chisel note in brackets:
go: parse short-decl + 6 tests [consumes-pratt]. - One feature per iteration. Commit. Log. Push. Next.
Go. Run the pre-flight check. If lib/guest kits are missing, stop. Otherwise
read the plan, find the first unchecked [ ], implement it. Remember:
every commit ends with a chisel note, and the sister-plan design diaries
get updates on shapes-* commits.
Loop continuation
This briefing supersedes any "then stop" wording from the user's original
/loop input. After pushing, call ScheduleWakeup to fire the next
iteration, then end the turn. The /loop command is in dynamic mode;
each iteration self-schedules the next.
delaySeconds: 60 (minimum). This is a coding loop with no external event to wait on — back-to-back iterations are intended. Raise only if a prior fire reported a substrate blocker that needs settling.prompt: the full original/loopinput verbatim, prefixed with/loop(so the wake re-enters this skill and re-reads this briefing). Do NOT paraphrase or trim it — the runtime expects an exact echo.reason: one short sentence, e.g. "next Go-on-SX iteration".
Stop conditions — omit ScheduleWakeup ONLY when:
- lib/guest pre-flight failed (missing kits) and a Blockers entry was added — the loop is parked waiting for substrate work.
- The same Blockers entry has been the reason for two consecutive iterations (avoid runaway no-op fires).
- plans/go-on-sx.md has every Phase 1-11 box checked.
- The user explicitly asks to stop, pause, or interrupt the loop.
Otherwise: reschedule. Always.