Skip to content

fix: deliver inter-step output via beads scope, not inlined into the dispatch (millworks-c30)#3

Merged
richardkiene merged 3 commits into
mainfrom
fix/beads-native-step-delivery
Jun 6, 2026
Merged

fix: deliver inter-step output via beads scope, not inlined into the dispatch (millworks-c30)#3
richardkiene merged 3 commits into
mainfrom
fix/beads-native-step-delivery

Conversation

@richardkiene
Copy link
Copy Markdown
Contributor

The bug (found in real project use)

A workflow step's task template interpolates upstream outputs via {step.X.output}/{previous_output}. substituteVariables inlined the entire upstream output (a ~10KB requirements doc) into the task string, and the Claude dispatcher then typed that whole string into the pane via tmux send-keys -l — which blew past send-keys' length ceiling, so the dispatch failed before the subagent started. It's a ceiling on inter-step payload size: every downstream step (architecture, optimization, code-gen) embeds the same doc and fails identically. pi dodged it only via a wrapper-file argv (higher ARG_MAX ceiling, same inline smell).

The output is already in beads (STEP notes, since inc5) — so re-serializing it through a keystroke channel was both redundant and unscalable.

The fix (lockstep, beads-native)

Deliver the upstream output through the beads-scoped context bundle (a file the assembler builds) instead of inlining it into the typed/argv task:

  • {step.X.output}/{previous_output} resolve to a short reference (stepOutputRef) — identical on both surfaces — keeping the author's intent without the payload. Validation (dependsOn membership, settled) is unchanged.
  • The dependency steps' beads records are scoped in at dispatch (pi dispatchStep; Claude threads beadsScopeIds through assembleContextassembleContextViaCli--beads-scope, which the Claude surface never passed before).
  • The assembler surfaces the deps' output: run_bd_show was parsing bd show --json as an object (it returns an array) and reading a nonexistent body field capped at 3 lines, so beads-scoped records rendered ~nothing. Split into a pure, array-aware summarize_bd_record that surfaces the full STEP notes under a step:<id> heading. Large notes are budget-pruned by the assembler's existing 80% pruner — a graceful cap, vs the send-keys hard fail.

Net: beads is the source the data flows from; the typed/argv task shrinks to the instruction; no payload through send-keys/ARG_MAX.

Testing

  • 4 new Rust unit tests for summarize_bd_record (array, full notes, step-label, fallback, none-cases).
  • Both surfaces' substitution + drive tests updated to the reference contract; pi 128 + Claude 270 green, typecheck clean.
  • Verified end-to-end against real bd: the built context-pack-assembler --beads-scope <stepid> surfaces the step's notes labeled by step:<id> in the bundle.
  • Remaining: live verification in the blocked greenfield-compile run (resume past requirements→feasibility) after a plugin rebuild — owner to run.

Notes

  • Lockstep across pi + Claude + the shared Rust assembler (a run from either surface stays read-back-compatible).
  • The deeper "steps emit structured DECISION/RISK/REQ records as canonical output" direction is scoped separately as epic millworks-cn8.
  • Pre-existing context-pack-assembler bd-prime test fragility (millworks-rrp) is untouched and unrelated.

…d (millworks-c30 pt1)

run_bd_show parsed bd show --json as an object (it returns an array) and read a
nonexistent 'body' field capped at 3 lines — so beads-scoped records rendered
essentially empty. Split a pure, array-aware summarize_bd_record that surfaces the
full STEP 'notes' (the produced output) under a step:<id> heading; the assembler's
80% budget prunes oversized notes. Foundational piece for routing inter-step
output delivery through beads instead of inlining it into the typed task.
…d into the task (millworks-c30 pt2)

{step.X.output}/{previous_output} now resolve to a short REFERENCE (stepOutputRef)
instead of inlining the upstream output into the task string — the actual text is
delivered via the context bundle: the dependency steps' beads records are scoped in
and the assembler surfaces their STEP notes (pt1). So a large upstream output no
longer blows the dispatch channel's length ceiling (Claude tmux send-keys; pi
ARG_MAX), and beads is the source the data flows from. Lockstep: identical
stepOutputRef + resolveOneVar change on both surfaces; pi scopes deps in dispatchStep;
Claude threads beadsScopeIds through assembleContext -> assembleContextViaCli ->
--beads-scope (the Claude assembler never passed a scope before). Validation
(dependsOn membership, settled) is unchanged. Verified end-to-end against real bd:
--beads-scope <stepid> surfaces the STEP notes labeled by step. Tests updated to the
reference contract; pi 128 + Claude 270 green.
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.

1 participant