BOHICA-LABS / BOHICA-LABS/vsdd-factory
process-gap(orchestrator): multi-pass convergence loop yields after each state-manager dispatch — engine prompt has no "continuation point" semantics
- Dominant language
- Rust
- Stars
- 2
- Forks
- 1
- Avg merge
- 6h 43m
- Merged PRs (30d)
- 29
Description
## Summary
The orchestrator skill prompt models `state-manager` as a phase-terminal action. Its `## Operating Loop` (`agents/orchestrator/orchestrator.md` §Operating Loop, lines 231–241) ends at step 6 ("If gate passes: advance to next phase, spawn state-manager to update STATE.md") followed by step 8 ("Report status to the human at each phase transition"). There is no step that says "after state-manager records, immediately re-dispatch the next pass within an in-progress convergence streak."
When the orchestrator is running a multi-pass adversarial convergence loop (Phase 1d, Phase 2, Phase 3 per-story/wave, Phase 5), and the project adopts the pattern of dispatching `state-manager` after **each** pass to durably record streak progress (a defensible choice — it survives compaction, gives an audit trail of pass-by-pass progress, and is the canonical place to log per-pass `[process-gap]` drift items), there is **no instruction** for the orchestrator to continue the loop. The natural model behavior is to yield to the human after the state-manager's close-out report — even though that report contains an explicit next-action line, and even though the convergence streak has not reached its target.
## Concrete observation from one external pilot run
A consumer project (greenfield mode, akey, phase-2 story decomposition) was driving an adversarial convergence streak (3/3 clean passes required to declare CONVERGED). The session's pattern was:
1. Pass N: dispatch adversary + consistency-validator in parallel (background)
2. Both return PASS → reconcile
3. Dispatch state-manager (LAST in burst) to record streak advance (e.g., 1/3 → 2/3), commit the STATE.md/burst-log delta, push
4. State-manager close-out report includes explicit `Outstanding work for orchestrator: Dispatch Pass N+1 ...`
5. **The orchestrator stopped**. Sat idle.
6. User had to prompt "what are we holding for?" to release the next dispatch.
This recurred at the Pass 30 PASS → Pass 31 dispatch boundary. The state-manager's close-out arrived as a tool result, the orchestrator composed a confirmation summary ("streak 1/3 → 2/3, dispatching Pass 31"), then... did not dispatch. No timeout, no error — purely idle.
The cost on this occasion was small (one user nudge, ~5 minutes). But this is the same class of failure as a phase-1d or phase-5 stall: every pass through a 3-clean-streak loop is 4 hand-off points (adversary returns + CV returns + state-manager returns + idle), and an idle stall at any of them creates an unacknowledged human-in-the-loop dependency that the engine claims to avoid.
## Root cause analysis
Reading the engine's own prompt shape carefully, three things combine:
### (1) Operating Loop has no in-loop continuation semantic
`agents/orchestrator/orchestrator.md` §Operating Loop (lines 231–241):
```
4. Wait for the agent to complete, review its output
5. Validate against quality gates
6. If gate passes: advance to next phase, spawn state-manager to update STATE.md
7. If gate fails: spawn the appropriate agent again with feedback
8. Report status to the human at each phase transition
```
After step 6 (state-manager), the implicit exit is to step 8 (report to human). For a multi-pass loop that has not yet terminated, this is wrong — the next action is the next pass, not a status report.
### (2) The sub-sequence definitions also assume state-manager is loop-terminal
`agents/orchestrator/greenfield-sequence.md` §Phase 2 (P2-03 + P2-04):
```
P2-03. Adversarial story review loop (max 10 passes):
a. Spawn adversary
b. If convergence reached → exit loop
c. Spawn spec-reviewer
d. Route findings
e. Spawn consistency-validator
f. Go back to (a) with fresh adversary context
P2-04. Spawn state-manager: "Commit Phase 2 artifacts"
```
State-manager is at P2-04, **outside** the loop body. The engine's per-pass workflow does not include a state-manager step. Projects that adopt per-pass state-manager recording (to make streak progress durable across compaction or session boundaries) have stepped outside the engine's modeled loop shape, and the engine prompt has no behavior model for "state-manager inside the loop."
### (3) Sub-agent close-out rhetoric biases toward yield
The `state-manager` agent's close-out reports use phrasing like "Outstanding work for orchestrator: ...". That phrasing reads as a delivery-end / hand-off — the textual shape of a slack-channel sign-off — not an in-flight queue item to the same conversational turn. Combined with (1) and (2), the natural orchestrator disposition is "received complete delivery → yield."
The HEARTBEAT.md item 2 names this exact gap as a *periodic* check:
> "If a subagent completed but its result wasn't processed (check session context for unhandled announce events), re-read the result and act on it"
But heartbeats are a recovery mechanism, not a synchronous invariant. The orchestrator should not need a heartbeat to notice that the last sub-agent's close-out contained an unconditional next action.
## Recommended fix shape
### Engine prompt change — add explicit continuation-point semantics to §Operating Loop
Add a step between current 6 and 7 (renumber 7→8, 8→9):
```
7. If inside a multi-pass convergence loop (Phase 1d, Phase 2, Phase 3 per-story
or wave, Phase 5): the state-manager dispatch in step 6 recorded progress for
the current pass but does NOT end the loop. Unless the convergence streak has
reached its target (typically 3/3 clean) OR human intervention is explicitly
required (escalation after max passes, blocking finding flagged for human),
the next action is the next pass's adversary + consistency-validator dispatch
within the SAME orchestrator response. Do not yield to the human between
passes mid-loop.
```
### Sub-sequence definitions — model state-manager as a per-pass continuation
`greenfield-sequence.md` P2-03 (and analogous loops in P1-10, P4-01, per-story-delivery 4.5) should explicitly include the state-manager dispatch as an in-loop step:
```
P2-03. Adversarial story review loop (3 consecutive clean passes required;
max 10 passes total):
a. Spawn adversary + consistency-validator in parallel (run_in_background:true)
b. Reconcile verdicts (worst-of severity across both reviewers)
c. If both PASS: increment streak counter
- If streak target reached (3/3): exit loop, advance to P2-04
- Else: spawn state-manager to record streak advance, then GO TO (a)
d. If either FAIL: reset streak counter to 0, route findings per
VSDD.md Feedback Integration Loop, spawn state-manager to record FAIL,
then GO TO (a)
P2-04. Spawn state-manager: "Commit final Phase 2 convergence verdict"
```
This makes the per-pass state-manager dispatch first-class in the engine workflow rather than a project-level adaptation.
### Sub-agent close-out style — name the dispatch as in-flight, not hand-off
The `state-manager` close-out instruction should bias the closing line toward in-flight phrasing rather than hand-off:
- Before: `Outstanding work for orchestrator: Dispatch Pass N+1 ...`
- After: `Immediate next dispatch (do NOT yield to human): adversary + consistency-validator for Pass N+1, both run_in_background:true, against unchanged HEAD.`
This is a smaller change but cuts off the "this report ends a turn" affordance.
## Related issues
- #173 (wave-boundary checkpoint+reset; PreCompact flush) — orthogonal but in the same context-durability lineage; the per-pass state-manager pattern is *also* a durability adaptation, and the orchestrator's loop semantics should compose with it.
- #215 (orchestrator burst-size rule should cover file modifications) — same class of "engine prompt doesn't fully model a project-level convention" finding.
- #312 (beadle triage dashboard) — surfacing pattern of orchestrator stalls would be a candidate beadle widget.
## Why this matters beyond one pilot
Every multi-pass convergence loop has N hand-off points where this can fire. A 3-clean-streak loop has up to ~3×3 = 9 such points (adversary, CV, state-manager × 3 passes). For Phase 5 implementation convergence (which can run 5–10 passes per wave), the exposure is larger. Each idle stall is silent — no error, no log line, no metric — and only surfaces as user-perceived latency. The engine prompt should foreclose this entire class with explicit loop-continuation semantics rather than rely on heartbeat recovery.
Contributor guide
Assessment
This issue has not been assessed yet.