bunx smithers-orchestrator graph.
Each is already documented elsewhere in full; this page exists so a builder
(human or agent) hits all five in one read before writing JSX, not one at a
time across several failed runs. See
the postmortem
this page was written to close.
1. Output schemas cannot reuse the reserved key columns
Every output table gets a fixedrun_id / node_id / iteration key prefix;
an input table gets run_id only. Why: those columns are how Smithers
correlates a row back to the run, node, and loop iteration that produced it,
so a field named runId, nodeId, or iteration in your own schema would
collide with the reserved one.
INVALID_INPUT at construction (schema build time), not
at run time; see zodToTable.
2. No nested loops, use the queue-based backfill pattern instead
A<Loop>/<Ralph> as the literal immediate JSX child of another
<Loop>/<Ralph> throws NESTED_LOOP at graph-extraction time (before any
agent runs). Why: with nothing between the two loops, there’s no clear
“whose iteration is this” semantics for the inner one, the same gap the
Effect combinator builder’s G.loop rejects
unconditionally.
<MergeQueue> and re-enter via the outer loop’s next iteration, instead of
nesting loops.
This is narrower than it sounds. A <Loop> reached through a
<Sequence>/<Parallel>/<Worktree> wrapper (not the literal immediate
child) is a different, genuinely-supported shape: each <Parallel>-forked
lane (one per array item, one per isolated <Worktree>) may run its own
bounded correction loop, scoped to the outer loop’s iteration. That’s exactly
what close-issues.tsx and studio-parity-swarm.tsx do (outer discover step,
then a <Parallel> of per-item <Worktree> lanes, each with its own
correction <Loop>), and it’s regression-guarded by
nested-loop-runtime.test.jsx
(issue #117): the inner loop’s state and cache correctly reset per outer
iteration. Don’t avoid that shape; it’s the sanctioned pattern for “per-item
lanes, each with a correction loop.” What to avoid is a second loop
governing the same lane with nothing forking between them.
3. ctx.latest vs outputMaybe({ nodeId, iteration }) for loop bindings
Inside a <Loop>’s until, read the most recent iteration with
ctx.latest:
ctx.latest and not bare ctx.outputMaybe: ctx.outputMaybe(schema, { nodeId }) with no explicit iteration resolves the current render
iteration, which equals the loop’s iteration only for a single, non-nested
loop, and is 0 when several loops coexist (sibling loops, or a loop nested
under a <Parallel>/<Worktree> fork per rule 2). An outputMaybe-based
until built on that ambient iteration can silently never advance: the loop
just spins to maxIterations and returns the last result, with no error
telling you why. If you need outputMaybe directly (not ctx.latest), pass
the loop’s own scoped node id and iteration explicitly:
ctx.outputMaybe(schema, { nodeId: "review", iteration: N }).
4. Workflow tests must render the real graph via renderWorkflow, not a hand-built one
A workflow test imports the actual workflow module and drives it through
renderWorkflow
(smithers-orchestrator/testing):
extractGraph/buildPlanTree) can pass while the real workflow file has a
typo, a NESTED_LOOP, or a reserved-column collision; it’s testing a fiction
that happens to resemble the workflow, not the workflow. renderWorkflow
exercises the same extraction path bunx smithers-orchestrator graph/bunx smithers-orchestrator up
do, so a graph-level defect fails the test the same way it would fail a real
run.
5. New .smithers test files must be registered in .smithers/package.json
.smithers/package.json’s test script is an explicit, space-separated list
of test file paths, not a glob. Why: pack tests run outside the normal
per-package bun test tests convention (they share fixtures/agents across
many workflow files), so there’s no directory-wide default to fall back on. A
new test file that isn’t appended to that list is silently never run by
pnpm test or CI; it can sit green-looking in the repo indefinitely while
contributing zero coverage. node scripts/check-smithers-test-script.mjs
(part of the root pnpm test gate) catches an unregistered test file; run it
after adding one, or just add the new path to the list yourself.
See also
- Curated Workflows, the workflow pack this page’s rules apply to.
- Recipes, the implement to review loop pattern rule 3 refines.
- Effect combinator overview, the stricter builder API rule 2 contrasts with.
dbreference, the full reserved-column and table-helper contract behind rule 1.