The orchestratePhase() function runs one phase per tick using the phase-state machine:
.meta/meta.json files (no Qdrant dependency)meta.json for each discovered pathderivePhaseState() reconstructs _phaseState from legacy fields on first loadfailed → pendingselectPhaseCandidate() picks the highest-priority owed phase across the corpus (critic > builder > architect, weighted staleness tiebreak)runArchitect, runBuilder, or runCritic_phaseState and phase outputfresh, create snapshot and pruneEach meta carries _phaseState: { architect, builder, critic } where each value is one of: fresh, stale, pending, running, failed.
Invariant: At most one phase is pending or running, and it is the first non-fresh phase in pipeline order. When enforceInvariant() runs after every transition, stale phases that become the first non-fresh phase are promoted to pending; non-first pending phases are demoted to stale.
Key transitions:
architect: pending (downstream fresh phases become stale; already non-fresh phases keep their state). Triggers: structure hash change, steer change, cross-refs declaration change, _synthesisCount ≥ architectEvery, or first run (no _builder)._state present): structure changes invalidate builder instead of architect, since the cursor handles incremental processing.builder: pending; critic → stale if was fresh. When architect is not fresh, builder stays stale (not promoted to pending).architect: fresh, builder: pending (or stays failed), critic → stale if was fresh. Also resets _synthesisCount to 0.builder: fresh, critic: pending (or stays failed). Architect state preserved.critic: fresh; if all three fresh → full-cycle complete (archive via createSnapshot(), pruneArchive(), then increment _synthesisCount)failed; other phases untouched (surgical retry)running before executor spawnsfailed phases promoted to pending for auto-retry| Module | Responsibility |
|---|---|
orchestratePhase.ts |
Per-tick driver: discover → derive → select → execute one phase |
runPhase.ts |
Per-phase executors: runArchitect, runBuilder, runCritic; lock-staged persistence via persistPhaseState() |
failed; other phases are untouchedpending on the next scheduler tickSpawnTimeoutError): attempts to salvage advanced _state from partial output; if _state progressed, it is persisted alongside the failed phase stateResults are staged in .lock before being committed to meta.json. If the process crashes:
meta.json is untouchedmeta.json is untouched; stale .lock cleaned at next startup