fix(stella-cli,stella-pipeline): seed and announce the candidate task board in isolated runs (#1719) - #1995
Merged
Conversation
`cargo clippy -p stella-pipeline --all-targets -- -D warnings` fails on b5ab7f8. A compile error in the lib masks the test target entirely, so this reads as one failure and is really four, from two different merges. From #1953 (the #1778 research stage): 1. `management_prompt/tests.rs` — `ModelCallRole::Research` is a new variant and `management_system_block`'s match is exhaustive on purpose (E0004). Research rides the sub-agent primitive, so its system prompt travels on the `SubAgentSpec`, never through `metered_raw_call`: it joins the never-dispatched arm, and `ALL_ROLES` grows to 15. 2. `pipeline.rs` — the new `research` parameter pushed `plan_stage` to 8 arguments, one over clippy's cap. Bundled `budget`/`total` into the `Spend` struct every stage downstream of the fan-out already takes, rather than `#[allow]`-ing the lint. From #1951, which rewrote `tests/verification_hardening.rs` wholesale and dropped three items #1945 had added to it hours earlier — a same-seam clobber, in a file #1951's own subject (per-candidate verifier degradation) never needed to touch: 3. `PassingShell` and `shell_call_result` went with it, leaving the child module `flip_halt_arming.rs` referencing two helpers that exist nowhere in the tree (E0425 ×2). Restored to their original home, which the child reaches through `use super::*`. 4. `a_revision_halts_at_the_step_where_the_tracked_test_flips` went too — the configured-command **witness for #1793**. Deleting it did not fail any gate, because the crate stopped compiling for reason 3 first: #1793 has been shipping with half its witness silently gone. Restored verbatim. Both #1793 witnesses now run and pass. Neither is vacuous: each asserts a scripted-prompt count, so a `PassingShell` that omitted the `[exit code: 0]` marker `flip_halt::exit_status` parses would leave the halt unarmed, the revision would consume the steps scripted beyond the flip, and the count would be wrong. `cargo test -p stella-pipeline`: 605 passed, 0 failed. `cargo clippy -p stella-pipeline --all-targets -- -D warnings`: clean.
Restoring `a_revision_halts_at_the_step_where_the_tracked_test_flips` in the parent commit took `tests/verification_hardening.rs` to 1557 lines, which `file-size` rejects outright — the baseline takes no new entries. Split rather than exempted, and the split is the one the content was asking for: both #1793 witnesses and the two doubles they share (`PassingShell`, `shell_call_result`) now live in `verification_hardening/flip_halt_arming.rs`, the module already named for the concern. The parent drops to 1434. That the two witnesses were ever in separate files is what let #1951's clobber happen quietly: it rewrote the parent wholesale, taking the configured-command witness and both doubles with it, and nothing failed that named the missing test — the crate had already stopped compiling for the missing doubles. With the cluster in one file the same rewrite is a merge conflict instead of a silent deletion, so the module doc says so. `cargo test -p stella-pipeline`: 605 passed, 0 failed — both witnesses among them, at their new path.
… board in isolated runs An isolated candidate's registry built its own empty task board with no path to any surface, so the worker's task_start "3" answered UnknownTask for a step the scope gate had already numbered, and zero TaskUpdate events reached the deck - the PLAN rail froze at hollow rings for the whole turn (execution 66: 1168 events, none of them task_update). Two halves, matching the issue's two failures: - Seeding: CandidateWorkspace grows a default-no-op seed_task_board; the fan-out stage - the one seam that knows both the approved plan and n - hands every created workspace the gate's exact rendered step strings, so ordinals and titles match the session board seeded from ScopeReview. - Announcing: the candidate tool stack gains a TaskTap-shaped decorator (candidate_ws/task_events.rs, the board half of the attach_read_events sweep) that snapshots the board onto the turn's channel after any task_* call - but only when the pipeline armed the announce latch, which it does solely for a lone candidate: TaskUpdate snapshots carry no candidate tag, so several private boards reporting onto one channel would splice into a checklist that is nobody's (the TextDelta muting reasoning). Boards stay per-candidate at every width: a step is completable exactly once per board, and the second sibling to finish it must not be handed a terminal-state tool error for work it really did. Closes #1719
Contributor
There was a problem hiding this comment.
Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Reviewer's GuideExtend the candidate pipeline so each isolated candidate workspace’s private task board is seeded from the approved plan and optionally announces task updates, while adding flip-halt and management-accounting tests and small refactors to keep budget tracking and model roles consistent. Sequence diagram for seeding candidate task boards during fan-outsequenceDiagram
participant Pipeline
participant CandidateWorkspacePort as CandidateWorkspacePort
participant WS as CandidateWorkspace
participant Board as CandidateTaskBoard
Pipeline->>Pipeline: create_candidate_workspaces(port, n, plan)
Pipeline->>Pipeline: steps = plan.map(step.description)
loop for each candidate (0..n-1)
Pipeline->>CandidateWorkspacePort: create()
CandidateWorkspacePort-->>Pipeline: Ok(WS)
alt [workspace created]
Pipeline->>WS: seed_task_board(&steps, n == 1)
WS->>Board: seed(steps, announce)
Board->>Board: seed_from_plan(steps)
Board->>Board: announce.store(announce)
else [create failed]
Pipeline-->>Pipeline: record isolation failure
end
end
Sequence diagram for candidate task_* calls announcing TaskUpdatesequenceDiagram
actor Candidate
participant WS as GitCandidateWorkspace
participant Tools as CandidateTaskTap
participant Inner as ToolExecutor
participant Board as TaskBoardHandle
participant Events as EventSender
Candidate->>WS: tools()
WS-->>Candidate: Tools
Candidate->>Tools: execute("task_start", input)
Tools->>Inner: execute("task_start", input)
Inner-->>Tools: ToolOutput
alt [name starts_with("task_") and announce is true]
Tools->>Board: lock()
Tools->>Board: items()
Board-->>Tools: Vec<TaskItem>
Tools->>Events: send(AgentEvent::TaskUpdate{tasks})
else [not a task_* call or announce is false]
Tools-->>Candidate: ToolOutput
end
Tools-->>Candidate: ToolOutput
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Owner
Author
|
Follow-ups filed per AGENTS.md § "Nothing left behind":
|
macanderson
added a commit
that referenced
this pull request
Aug 7, 2026
…hree things and the merge kept both #1985 and #1971/#1995 independently repaired the breaks #1953 left, converged on the same designs, and landed within minutes of each other. Git merged the two additively rather than conflicting, so `main` at e0fbbe0 carries each fix twice and fails `cargo clippy -p stella-pipeline --all-targets -- -D warnings` three ways: 1. `management_prompt/tests.rs` — `ModelCallRole::Research` appears twice in the same or-pattern (`unreachable_patterns`). Kept one. 2. `pipeline/scope_stage.rs` — both PRs bundled `plan_stage`'s budget+total into `Spend`, but the call site kept #1985's per-iteration reborrow AND the other's hoisted `let mut spend`, now unused (`unused_variables` + `unused_mut`). Kept #1985's: the loop replans after a rejected scope card, and only a reborrow per attempt survives that. 3. `tests/verification_hardening.rs` — both restored `PassingShell` and `shell_call_result` after #1951 deleted them, one into this file and one into its `flip_halt_arming` child, leaving the parent's pair dead (`dead_code` ×3, counting `SHELL_TOOL`). For (3) the two copies were not equivalent, so this is not an arbitrary pick: #1985's are better documented — they name `SHELL_TOOL` as a const distinct from `WRITING_TOOL` and say why the `[exit code: 0]` marker is load-bearing (without it the halt never latches and the arming test passes for no reason). Those are the ones kept. They move to the child, which is where both #1793 witnesses now live, because co-location is what makes the next wholesale rewrite of the parent a merge conflict instead of the silent deletion that started this (#1997). The parent's now-stale `mod` doc is corrected in place rather than left describing a layout that no longer holds. `cargo clippy -p stella-pipeline --all-targets -- -D warnings`: clean.
macanderson
added a commit
that referenced
this pull request
Aug 7, 2026
…e same three things, plus the file-size ratchet blocking every PR (#2008) >⚠️ **Overlaps #2000 — merge exactly one of these, never both.** We built the same unbreak in parallel and reached the *identical* resolution on all three collisions. This PR additionally fixes a fourth break (the file-size ratchet) that is currently failing #2000's checks and every other open PR. If #2000 picks up that one commit, close this; otherwise close #2000. Merging both is how the collision being fixed here happened. ## Why main is red `main` at e0fbbe0 fails `cargo clippy -p stella-pipeline --all-targets -- -D warnings` **and** `file size ratchet`. Two unbreak PRs (#1985, and #1971 via #1995) independently repaired the breaks #1953 left, **converged on the same designs**, and landed minutes apart. Git merged them additively rather than conflicting, so main now carries each fix twice: | # | Break | Where | |---|---|---| | 1 | `ModelCallRole::Research` twice in one or-pattern (`unreachable_patterns`) | `management_prompt/tests.rs` | | 2 | Both a hoisted `let mut spend` **and** a per-iteration reborrow (`unused_variables` + `unused_mut`) | `pipeline/scope_stage.rs` | | 3 | `PassingShell`/`shell_call_result` restored into *both* the parent and its child (`dead_code` ×3) | `tests/verification_hardening.rs` | | 4 | Two grandfathered files one line over their ceiling | `scripts/file-size-baseline.txt` | ## The judgment calls **(2) — kept #1985's per-iteration reborrow, not the hoisted binding.** Not arbitrary: `plan_with_review` loops, replanning after a rejected scope card, and only a `Spend` reborrowed per attempt survives that. The hoisted version would have been moved on the first iteration. **(3) — kept #1985's doubles, in the child.** The two copies were *not* equivalent. #1985's are better documented: they name `SHELL_TOOL` as a const distinct from `WRITING_TOOL`, and say why the trailing `[exit code: 0]` marker is load-bearing — without it `FlipHalt::observe` never latches and the arming test passes for no reason. Those are the ones kept. They live in `flip_halt_arming` with both #1793 witnesses, because co-location is what turns the next wholesale rewrite of the parent into a merge conflict instead of the silent deletion that started this (#1997). The parent's `mod` doc is corrected in place rather than left describing a layout that no longer holds. **(4) — recording growth that already merged, and saying so.** Two ceilings go **up** by one line each: ``` crates/stella-core/src/driver.rs 2571 → 2572 crates/stella-pipeline/src/pipeline/tests.rs 2536 → 2537 ``` Per CLAUDE.md, a raised ceiling to turn a gate green is normally a defect against the PR that raises it, so this is flagged rather than slipped through. The difference: **this branch touches neither file.** Both grew on main via #1979 and #1962, which did not regenerate the baseline in the same commit. The choice is therefore not "grow or don't" but "record what already merged, or leave main red for everyone". The two lines are somebody's to reclaim; neither is mine to judge irreducible. The same regeneration **tightens** `pipeline.rs` from 3451 to 3181 — 270 lines of stale headroom now closed off, which is the ratchet working as intended and more than offsets the two. Regenerated via `make file-size-update`, never hand-edited. ## Verification - `cargo clippy -p stella-pipeline --all-targets -- -D warnings` — clean - `cargo test -p stella-pipeline --lib` — **596 passed, 0 failed**, both #1793 witnesses among them - `make guards-fast` — green, `file-size` and `god-files` included ## Related - #1997 — why a deleted test failed no gate in the first place - #1985, #1995, #2000 — the colliding unbreaks ## Summary by Sourcery Unbreaks main by reconciling overlapping clippy and test fixes in stella-pipeline, consolidating flip-halt arming test doubles, and updating the file-size baseline so guards and ratchet checks pass again. Bug Fixes: - Resolve unreachable pattern warning in management_prompt tests by removing the duplicate ModelCallRole::Research arm - Fix clippy unused variable warnings in scope_stage by relying on per-iteration Spend reborrows - Restore and colocate shell tooling doubles for flip halt arming tests so dead-code warnings are cleared while preserving #1793 coverage Enhancements: - Clarify documentation and structure of flip halt arming tests by moving shared shell doubles into the child module and updating the parent module description Build: - Regenerate file-size baseline to reflect recent growth in driver.rs and pipeline tests while tightening the pipeline.rs ceiling so file-size ratchet gates pass again
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1719
The defect
The PLAN rail shows every step as a hollow grey
○for the whole turn. The panel's own dot moves (PLAN ● approved 0/7) but no individual step ever reaches started / complete — the rail reports a plan and never reports progress through it.A step's ring moves only on
AgentEvent::TaskUpdate, whose only emitter is the deck's sessionTaskTap.Pipeline::run_isolated_candidatebuilds its engine onws.tools()— the candidate workspace's own stack — andTaskTapis not in that chain. Two independent failures follow, either of which alone freezes the rail:ToolRegistrybuilds its own empty one.task_start "3"answersUnknownTaskfor a step the gate already numbered 3. A worker routing around it withtask_createthen builds rows whose subjects overwrite the approved step titles.Execution 66 of a real session: 1168 events, zero
task_update, while the model calledtask_create×3 andtask_start×1 exactly as the system prompt asks.This is the same shape as the already-fixed "isolated runs blind the Files tab" bug — see the
attach_read_eventscomment incandidate_ws.rs. The task board was missed in that sweep.The fix
Seeding —
CandidateWorkspacegrows a default-no-opseed_task_board(steps, announce). The fan-out stage drives it right aftercreate, before any dispatch, so the worker's very firsttask_startalready resolves the gate's ordinals. It passes the plan's step descriptions verbatim, which is whatscope::build_proposalrenders the approval card from — so a candidate board and the session board seeded fromScopeReviewcannot disagree on a step's id or title.Announcing — the candidate tool stack gains a
TaskTap-shaped decorator (candidate_ws/task_events.rs), outermost over registry + customs + candidate MCP + policy, so it observes every route atask_*call can take.Why the
announcelatch, and why boards stay private. The issue notesCandidateWorkspacePort::createcarries no fan-out width, so the "how many candidates may report" decision cannot readn. It is taken increate_candidate_workspaces, which is the one seam that knows bothnand the plan.TaskUpdatecarries a full board snapshot with no candidate tag, so several boards reporting onto one channel would splice into a checklist that is nobody's — the same reasoning that already mutesTextDelta/Reasoningon a shared event lane. Only a lone candidate announces.Boards are never shared, at any width:
TaskBoard::set_statusrejects a transition out of a terminal state, so the second sibling to finish a step would receive a tool error for work it really did.Definition of done, item by item
task_*moves the gate-numbered step and emitsTaskUpdatea_seeded_lone_candidate_moves_the_approved_step_and_announces_itfanout_candidates_keep_separate_and_silent_boards,best_of_two_adopts_only_the_winner_and_removes_every_workspaceevents: Noneskips the tap entirely; the witness author's pristine snapshot is created viaport.create()directly (witness_stage.rs:615), never seeded, so its latch staysfalseWitness
The new API means the tests cannot compile on
main(the feature is genuinely absent). That is the weaker half of the proof, so both halves of the fix were independently neutered on this branch to show each is load-bearing:Seeding neutered — reproduces the issue's exact symptom:
Announcing neutered:
(The fan-out test correctly still passes with the tap dead — it asserts silence, which a dead tap trivially satisfies. Its real job, the separate-boards guarantee, fails in the seeding run above.)
Restored, both pass. The two CLI-side witnesses run against real git worktrees through the production
GitCandidateWorkspacesport, not a fake.Constraints honoured
candidate_ws.rswas near the 1500-line ceiling with no baseline entry, so it could not cross it at all: it takes +14 lines (1480 → 1494) and all new logic lands in the new siblingcandidate_ws/task_events.rs.pipeline.rsis a god file — +0 net lines (unchanged at its 3462 ceiling).Verification
cargo test -p stella-pipeline -p stella-cli— 2121 passed, 0 failed (20 binaries)cargo clippy -p stella-pipeline -p stella-cli --all-targets -- -D warnings— cleanmake guards-fast— green,file-sizeandgod-filesincludedSummary by Sourcery
Ensure isolated candidate workspaces share the approved task plan and surface task progress while tightening flip-halt and management accounting coverage.
New Features:
Enhancements:
Tests: