Skip to content

script: compose the compile pipeline as typed weft arrows#16

Merged
vinodhalaharvi merged 1 commit into
mainfrom
claude/arrow-compose-core
May 24, 2026
Merged

script: compose the compile pipeline as typed weft arrows#16
vinodhalaharvi merged 1 commit into
mainfrom
claude/arrow-compose-core

Conversation

@vinodhalaharvi-claude
Copy link
Copy Markdown
Collaborator

What

Step 1 of unifying on one arrow-driven, type-safe, composable pipeline. The compile phases were already arrow-shaped (func(ctx, A) (B, error)) but hand-chained with if err != nil ceremony. Now they're composed as actual weft.Arrow values via weft.Pipe — composition is real, type-checked at every seam, and the pipeline is itself an arrow that composes further.

Why

The mandate: everything type-safe, arrow-driven, composable — nothing built that can't be composed. A pipeline of bare functions glued by error checks isn't composition; it just resembles it. Building on weft's Arrow[A,B] + Pipe makes the composition literal and the type-safety structural (a phase whose output doesn't match the next phase's input won't compile).

Changes (pkg/script/submit.go)

  • CompileArrow(reg) weft.Arrow[Source, sibyl.Plan] — the compile pipeline as one composed arrow:
    Pipe5(Parse, ResolveWith(reg), Lower, Finalize, Validate)
    weft.Compose handles error short-circuiting; the five if err blocks are gone.
  • Compile is now CompileArrow(reg)(ctx, src) — same signature, same behavior, composed implementation.
  • TranslateAndCompile composes Translate >=> Compile as Pipe2(translateArrow, CompileArrow(reg)).

weft promoted indirect → direct (now used directly). No behavior change — the demo compiles to the identical 2-node Plan; all existing tests pass.

Tests prove the composition property, not just that it works

compose_test.go:

  • CompileArrow is assignable to weft.Arrow[Source, Plan] — compile-time proof of shape
  • It composes into a larger arrow: Pipe2(CompileArrow, countNodes) runs end-to-end — the real composability test, the pipeline slots into Pipe like any arrow
  • Error short-circuits through the composition (unknown builtin propagates, no partial plan)

loom is untouched (verified, not asserted)

loom's required surface — Compile, Submit, Translate, DefaultRegistry, CompleteFunc, ArityError, UnknownBuiltinError, registry.Registry — is preserved exactly. I built and tested loom against this commit with loom's own code unchanged: it builds clean, all tests pass. The change is in agentscript internals; loom doesn't notice.

Step Result
go vet -structtag=false ./...
gofmt -l .
staticcheck ./pkg/script/...
go test -race ./pkg/script/...
go build ./...
loom builds+tests against this commit

Next (the unification roadmap, all on this composed spine)

  • Step 2: add the memory interpreter branch — the same resolved AST runs in-process when the backend keyword is memory; temporal lowers to a Plan. One parser, two interpreters.
  • Step 3: promote control-flow (if/foreach/match/fmap) from string-arg verbs to first-class composable AST nodes whose sub-expressions are arrows — never string-encoded sub-programs.
  • Unify on the one complete registry-driven grammar; retire the redundant subset parser internals.

Step 1 of unifying on one arrow-driven, type-safe, composable pipeline.
The phases were already arrow-SHAPED (func(ctx, A) (B, error)) but were
hand-chained with if-err ceremony. Now they are composed as actual
weft.Arrow values via weft.Pipe — so composition is real, type-checked
at every seam, and the pipeline itself is an arrow that composes further.

Changes (pkg/script/submit.go):
  - CompileArrow(reg) weft.Arrow[Source, sibyl.Plan] — the compile
    pipeline as a single composed arrow:
      Pipe5(Parse, ResolveWith(reg), Lower, Finalize, Validate)
    Each seam is type-checked: a phase whose output doesn't match the
    next phase's input won't compile. weft.Compose handles error
    short-circuiting; the five if-err blocks are gone.
  - Compile(ctx, reg, src) is now CompileArrow(reg)(ctx, src) — same
    signature, same behavior, composed implementation.
  - TranslateAndCompile composes Translate >=> Compile as
    Pipe2(translateArrow, CompileArrow(reg)).

weft promoted from indirect to direct dependency (it is now used
directly for Arrow + Pipe). No behavior change: the demo compiles to
the identical 2-node Plan; all existing tests pass.

Tests (compose_test.go): prove the COMPOSITION property, not just that
it works —
  - CompileArrow is assignable to weft.Arrow[Source, Plan] (compile-time
    proof of shape).
  - It composes into a larger arrow: Pipe2(CompileArrow, countNodes)
    runs end-to-end (the real composability test — the pipeline slots
    into Pipe like any arrow).
  - Error short-circuits through the composition (unknown builtin
    propagates, no partial plan).

loom is UNTOUCHED: its required surface (Compile, Submit, Translate,
DefaultRegistry, CompleteFunc, ArityError, UnknownBuiltinError,
registry.Registry) is preserved exactly. Verified by building and
testing loom against this commit with loom's own code unchanged — it
builds clean and all tests pass.

This is the spine for the unification: subsequent steps add the memory
interpreter branch and promote control-flow (if/foreach/match) to
composable AST nodes, all on this arrow-composed pipeline.

CI: go vet -structtag=false, gofmt, staticcheck, go test -race
./pkg/script/..., go build ./... — all pass.
@vinodhalaharvi vinodhalaharvi merged commit fac934b into main May 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants