script: compose the compile pipeline as typed weft arrows#16
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 withif err != nilceremony. Now they're composed as actualweft.Arrowvalues viaweft.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]+Pipemakes 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:weft.Composehandles error short-circuiting; the fiveif errblocks are gone.Compileis nowCompileArrow(reg)(ctx, src)— same signature, same behavior, composed implementation.TranslateAndCompilecomposesTranslate >=> CompileasPipe2(translateArrow, CompileArrow(reg)).weftpromoted 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:CompileArrowis assignable toweft.Arrow[Source, Plan]— compile-time proof of shapePipe2(CompileArrow, countNodes)runs end-to-end — the real composability test, the pipeline slots intoPipelike any arrowloom 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.go vet -structtag=false ./...gofmt -l .staticcheck ./pkg/script/...go test -race ./pkg/script/...go build ./...Next (the unification roadmap, all on this composed spine)
memoryinterpreter branch — the same resolved AST runs in-process when the backend keyword ismemory;temporallowers to a Plan. One parser, two interpreters.if/foreach/match/fmap) from string-arg verbs to first-class composable AST nodes whose sub-expressions are arrows — never string-encoded sub-programs.