Skip to content

Fix #40: porch's phase-handoff box tells the builder to start - #42

Merged
pseudoseed merged 2 commits into
mainfrom
fix/phase-handoff-is-not-a-stop
Aug 22, 2026
Merged

Fix #40: porch's phase-handoff box tells the builder to start#42
pseudoseed merged 2 commits into
mainfrom
fix/phase-handoff-is-not-a-stop

Conversation

@pseudoseed

Copy link
Copy Markdown
Owner

Closes #40.

The failure

A builder ended its turn at a plan-phase boundary with nothing blocking it and idled for two hours. Asked why:

I stopped because I treated the phase boundary as a reporting checkpoint and ended my turn, when nothing was blocking me: porch had already handed me phase_1 and you had already said proceed. I also over-read porch's "do not start phase_2 until you run porch again" as a general stop-and-wait, when it only guards against skipping ahead to the next phase, not against doing the one I was given.

Both readings were available in text we hand the builder.

Before

╔══════════════════════════════════════════════════════════════╗
║  🛑 CRITICAL RULES                                           ║
║  1. DO NOT start phase_2_b until you run porch again!        ║
║  2. Run /compact before starting each new phase              ║
║  3. After completing this phase, run: porch done 13          ║
╚══════════════════════════════════════════════════════════════╝

Rule 1 is a prohibition. Rule 3 is conditional on being done. Rule 2 is the only unconditional imperative in the box, and a builder cannot perform it — /compact is a slash command a human types into a composer, and grep -rn compact packages/codev/src returns these two console.log lines and nothing that consumes them.

So the box porch marks CRITICAL told the builder what not to do, gave it one action it could not take, and never said to begin the phase it had just been handed.

After

╔══════════════════════════════════════════════════════════════╗
║  🛑 CRITICAL RULES                                           ║
║  1. START phase_1_a NOW — a phase handoff is not a stopping  ║
║     point. Do not end your turn to report that you received  ║
║     it.                                                      ║
║  2. DO NOT start phase_2_b until you run porch again!        ║
║  3. When phase_1_a is complete, run: porch done 9040         ║
║  4. Stop only for a human gate, a blocker you cannot resolve,║
║     or a question whose answer changes the work.             ║
╚══════════════════════════════════════════════════════════════╝

Rule 4 exists because "should I stop here?" needs a written answer. Without one it gets settled by whichever reading looks safest, and stopping always looks safest.

Why a renderer instead of editing the strings

The old box hand-padded every line with .padEnd(63), so each rule was capped at what fit on one line. That cap is why the box held only prohibitions — a prohibition fits, an explanation does not. criticalRulesBox() wraps at the box width and aligns continuations under the rule text, which is what makes a full sentence affordable.

Role doc

roles/builder.md said "porch drives the loop — run it, do the work it hands you, run it again" once, inside a table cell. Against that, two later sections attach ceremony to the boundary: write the thread at phase boundaries, and a four-row table of message-the-architect triggers. Neither said reporting does not mean ending your turn.

Adds a section stating that a handoff is begun in the same turn, that porch's DO-NOT-start line forbids skipping ahead and has never meant stop-and-wait, and the three conditions that justify ending a turn. Mirrored into codev/roles/builder.md (verified byte-identical).

Tests

issue-40-phase-handoff-rules.test.ts — 8 tests. They assert content, not formatting, since content is what failed. Two are worth calling out:

  • Order. The affirmative rule must appear before the prohibition. A box that opens with DO NOT has told a builder what not to do before telling it to do anything.
  • The last-phase case. On the final plan phase there is no next phase to name, so the box necessarily renders the unqualified "DO NOT start the next phase" — the exact line that was misread. The test pins that the affirmative rule still names the current phase there, because it is carrying the whole load.

510 porch tests pass, including the pre-existing spec-1470 regression tests that assert the box still renders.

Scope

Wording. It makes the misread harder; it does not detect a builder that stops anyway. That is #41 (idle-builder watchdog), which crosses Tower's existing render-gate idle classification with porch's phase state.

🤖 Generated with Claude Code

pseudoseed and others added 2 commits August 22, 2026 13:22
A builder ended its turn at a plan-phase boundary with nothing blocking it
and idled for two hours. Its own account of why names two things porch and
the role doc actually told it: it read "DO NOT start phase_2 until you run
porch again" as a general stop-and-wait, and it treated the handoff as a
reporting checkpoint.

The box porch marks CRITICAL held one prohibition, one conditional (run
porch done WHEN complete), and one imperative the builder cannot perform:
/compact, a slash command a human types into a composer. Nothing in the
codebase consumed it — grep returned the two console.log lines and nothing
else. So the only unconditional instruction in the box was unsatisfiable,
and no line said "begin the phase you were just handed."

The box is now rendered by a helper that wraps, which is what makes a full
sentence affordable; the old hand-padded lines capped every rule at what fit
and that cap is why the box held only prohibitions. Rule 1 is affirmative
and names the phase to start now. Rule 4 names the three things that do
justify stopping, so "should I stop here?" has a written answer instead of
being settled by whichever reading looks safest.

The role doc gets the matching section. It said "porch drives the loop" once
inside a table cell, then attached ceremony to the boundary twice — write
the thread at phase boundaries, message the architect at these four moments
— without ever saying that reporting is not ending your turn.

This makes the misread harder. It does not detect a builder that stops
anyway; that needs the watchdog in #41.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hard-break a word wider than the box before wrapping. Phase ids come from
plan headings via extractPlanPhases, so a long slug arrives as one
unbreakable word and ran straight through the border. The existing border
test only exercised short ids; a new test uses a 128-character id.

Scope the rule-order assertion to boxText() like its siblings. Asserting on
the whole status output happened to work only because "DO NOT start" appears
nowhere else in it, which is a fact about today's output rather than about
the ordering this test exists to pin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pseudoseed

Copy link
Copy Markdown
Owner Author

Merging on one review lane, stated plainly rather than dressed up as a clean cmap.

  • claude — APPROVE. Two minor notes, both fixed in 739ad38: criticalRulesBox now hard-breaks a word wider than the box (a long phase id would have run through the border, with a new test using a 128-character id), and the rule-order assertion is scoped to the box like its siblings.
  • codex — quota exhausted until 2026-08-27. Structurally unavailable, not a skip.
  • gemini (agy) — unauthenticated, non-blocking skip.
  • opencode — hard-failed twice with no VERDICT line. That is the lane behaving correctly: it refused to emit a verdict it could not support. The cause is opencode consult lane cannot read the sandbox dir, so it reviews with no diff — and with no prompt at all over 100k chars #44 — the lane cannot read consult's sandbox dir, so it never received the diff.

So this merges on one substantive review. Recording that as the actual basis, because "cmap passed" would be false.

511 porch tests pass, including the pre-existing spec-1470 regression tests that assert the box still renders.

@pseudoseed
pseudoseed merged commit 332521f into main Aug 22, 2026
7 checks passed
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.

porch's CRITICAL RULES box gives a builder no reason to start, so builders stop at phase boundaries

1 participant