bunx smthrs graph.
Each is documented elsewhere in full; this page collects all five in one read
before you write JSX, instead of hitting them one at a time across failed
runs. See
the postmortem
this page was written to close.
1. Output schemas cannot reuse the reserved key columns
Output tables get a fixedrun_id / node_id / iteration key prefix; input
tables get run_id only. Why: these columns correlate a row back to the
run, node, and loop iteration that produced it, so a schema field named
runId, nodeId, or iteration collides 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 throws
NESTED_LOOP at graph-extraction time (before any agent runs). Why: with
nothing between them, there’s no clear “whose iteration is this” for the
inner loop, the same gap
Effect combinator builder’s G.loop rejects
unconditionally.
<MergeQueue> and re-enter via the outer loop’s next iteration.
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 or isolated <Worktree>) may run its own bounded
correction loop, scoped to the outer loop’s iteration: the sanctioned
“per-item lanes, each with a correction loop” pattern, 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>), regression-guarded by
nested-loop-runtime.test.jsx
(issue #117): the inner loop’s state and cache reset correctly per outer
iteration. Avoid only 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.outputMaybe(schema, { nodeId }) with no explicit iteration
resolves the current render iteration, which equals the loop’s own
iteration only for a single, non-nested loop, and is 0 when several
coexist (siblings, or one nested under a <Parallel>/<Worktree> fork per
rule 2). An outputMaybe-based until built on that ambient iteration can
silently never advance: it spins to maxIterations and returns the last
result, with no error explaining why. To use outputMaybe directly, pass the
loop’s own scoped node id and iteration:
ctx.outputMaybe(schema, { nodeId: "review", iteration: N }).
4. Workflow tests must render the real graph via renderWorkflow, not a hand-built one
Authoring a workflow and writing its testing-library test are one indivisible
change. A workflow delivered without that test is unfinished work, not a
follow-up opportunity.
Import the actual workflow module and drive it through
renderWorkflow
(smthrs/testing):
extractGraph/buildPlanTree) can pass while the real workflow file has a
typo, a NESTED_LOOP, or a reserved-column collision: it validates a stand-in
that merely resembles the workflow. renderWorkflow exercises the same
extraction path bunx smthrs graph/bunx smthrs up do, so a
graph-level defect fails the test the same way it fails a real run.
Assert real graph behavior: exact node ids and dependency order, representative
valid and malformed values through each task’s outputSchema, and branch or
loop behavior under the outputs that drive it. A truthiness smoke test such as
expect(graph).toBeTruthy() does not meet this rule.
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. An
unregistered test file 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 this; run it after adding a test file, or just add the path
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.