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

Open
#343 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.