Principle — pipeline tooling itself must be block-composition, not monolithic Go
Status: ratified — warp#698 Filed under: ADR-113 (principles tier above initiative) Decision document: ADR-124 (pipeline tooling is block-composition) Enforced at PR review by: principle-aware reviewers per ADR-115; 4-layer enforcement gate stack per warp#705
Statement
Pipeline tooling itself must be block-composition pipelines with declarative logic-routing. Imperative Go orchestration is not the pipeline's runtime — it is at most the executor's implementation detail behind a runner.Executor port.
The shape of a pipeline-step contribution:
- Block names are abstract primitives.
read-json,validate-schema,transform,write-json,http-probe,assert-equal. They name a generic capability and are reusable across pipelines. - Pipeline names are domain-specific.
discovery-brief-enhancement,api-contract-extract,provision-and-deploy-warp. They name the business concern and compose blocks into it. - Logic routing lives in the pipeline file, not in the block.
if,foreach,cache,retry,timeoutare routing primitives expressed declaratively in YAML/JSON between block invocations. A block with branchingswitchstatements over its inputs is a smell — that is pipeline-shape leaking into the block. - Single concern per block. A block does one thing. If it needs to know about two upstream concerns to decide what to do, it is two blocks plus routing.
- Typed I/O ports per ADR-085. Block declarations carry
inputs:andoutputs:with named, typed ports. The validator catches reference typos and type mismatches at design time, before the runtime ever loads the pipeline.
Why this rule
Today's empirical evidence: ai-research/cmd/server/pipeline/ contains 367 .go files; exactly one (library_bridge.go) imports gyrum-pipelines/pkg/block. The bridge file's own header comment names the shape as Phase 1 of a multi-PR migration that has not graduated. Without a citable principle, reviewers seeing a new monolithic RunContext-shaped step had no canonical rule to push back with — the shape kept reproducing.
The 600s sub-agent watchdog incident on warp#622 (memory: feedback_subagent_watchdog_600s.md) was the same monolith failure mode: a single agent shipping a single large change because the substrate offered no smaller atomic unit. ADR-123 named the same failure one layer up — Build phase was a single opus-driven monolith generating 80% of every project from scratch — and the resolution was the same shape: deterministic templates plus declarative composition plus scoped AI gap-fill.
Recursive application
This principle is the recursive application of the generic-primitives principle (warp#724, re-mint of cancelled warp#693) to pipeline tooling itself:
- Library packages (sister principle) — package symbols are generic verbs/nouns; domain shape lives in product code.
- Pipeline blocks (this principle) — block names are abstract verbs; pipeline names are domain. ADR-124 cites the generic-primitives principle as the foundation.
- Module guidelines (ADR-117) — each module-type's guideline says "this module-type's symbols must be generic for that module shape, not domain-specific."
pipeline-step@v1rests on this principle directly.
How to apply
- At write time — when adding a pipeline step, ask: "is the name an abstract primitive (good — file as a block) or a domain-specific flow (good — file as a pipeline composing blocks)?" A
RunContext-shaped imperative step is the smell — split it into typed-port blocks plus pipeline routing. - At PR time — principle-aware reviewers per ADR-115 cite this principle when blocking PRs that introduce a new monolithic step. The 4-layer gate stack (warp#705) rejects new monolithic steps at author / PR / persona / drift time.
- At graduation time — the existing 366 imperative
cmd/server/pipeline/files are tracked as legacy graduation backlog (warp#707), prioritised by churn. New pipeline tooling MUST be authored in the block shape; legacy migrates incrementally, not in a big-bang rewrite. - At catalog time — block names are a fleet-wide artefact. Domain leakage into a block name is a bug; ADR-117 module guidelines treat
pipeline-stepas a module type withblock-name = generic primitiveas a forbidden-pattern check.
Applies to
cmd/server/pipeline/**, pkg/pipeline/**, pkg/block/**, and any new directory whose go.mod imports gyrum-pipelines/pkg/block.
Cross-link
- warp#698 — this principle's live ticket
- ADR-124 — the decision document ratifying this principle
- generic-primitives — the sister principle this one recursively applies (warp#724)
- warp#705 — 4-layer enforcement gate stack (author / PR / persona / drift)
- warp#707 — graduation epic: convert 366 imperative
cmd/server/pipeline/files to block-composition - ADR-085 — typed I/O ports (the contract block-composition rests on)
- ADR-090 — AI as scaffold, pipelines as runtime (this principle refines for pipeline tooling)
- ADR-113 — principles tier (the schema this is filed under)
- ADR-115 — principle-aware reviewers (how the gate cites this principle)
- ADR-117 — module guidelines (
pipeline-step@v1rests on this principle) - ADR-123 — templates as generator (the same shape one layer up)
- Memory
feedback_modular_pipelines_default.md— operator preference, 2026-04-29 (motivates this principle) - Memory
feedback_subagent_watchdog_600s.md— the failure mode this principle structurally prevents