State Machine¶
The loop state machine controls the flow of agent execution.
States¶
| State | Description |
|---|---|
init |
Run created, agents starting |
working |
Primary agent executing task |
reviewing |
Secondary agent reviewing work |
complete |
Task finished successfully |
failed |
Error or max iterations reached |
Transitions¶
┌──────────────────────────────────┐
│ │
▼ │
┌──────┐ ┌─────────┐ ┌───────────┐ ┌───┴─────┐
│ init │─────►│ working │─────►│ reviewing │─────►│complete │
└──┬───┘ └────┬────┘ └─────┬─────┘ └─────────┘
│ │ │
│ │ │
│ ▼ │
│ ┌────────┐◄────────────┘
└─────────►│ failed │
└────────┘
Valid Transitions¶
| From | To | Trigger |
|---|---|---|
init |
working |
Agents started successfully |
init |
failed |
Agent failed to start |
working |
reviewing |
Primary agent finished iteration |
working |
complete |
Primary agent signaled DONE |
working |
failed |
Agent error or max iterations |
reviewing |
working |
Review requires more work |
reviewing |
complete |
Review passed (PASS signal) |
reviewing |
failed |
Unrecoverable review failure |
Completion Conditions¶
The loop completes when:
- DONE signal — Agent outputs the done signal (default:
DONE) - PASS signal — Reviewing agent approves with
PASS - Consensus — In
claudexmode, both agents signal completion
Failure Conditions¶
The loop fails when:
- Max iterations — Reached
--max-iterationslimit - Agent error — Agent process crashed or returned error
- FAIL signal — Agent explicitly fails with
FAIL - Context cancelled — User interrupted (Ctrl+C)
Implementation¶
type State string
const (
StateInit State = "init"
StateWorking State = "working"
StateReviewing State = "reviewing"
StateComplete State = "complete"
StateFailed State = "failed"
)
type Machine struct {
current State
history []State
}
func (m *Machine) Transition(newState State) error {
if !m.canTransition(newState) {
return &InvalidTransitionError{From: m.current, To: newState}
}
m.history = append(m.history, newState)
m.current = newState
return nil
}
Run State Mapping¶
Loop states map to run states for persistence:
| Loop State | Run State |
|---|---|
init |
submitted |
working |
working |
reviewing |
reviewing |
complete |
completed |
failed |
failed |
Iteration Flow¶
Each iteration follows this pattern:
1. Check state is not terminal
2. Increment iteration counter
3. Drain messages for primary agent
4. Execute primary agent
5. Process result (check signals)
6. If not done, transition to reviewing
7. Drain messages for secondary agent
8. Execute secondary agent
9. Process review result
10. Transition back to working or complete
Signal Detection¶
Signals are detected in agent output:
type Result struct {
Output string
Done bool // DONE signal found
Pass bool // PASS signal found
Fail bool // FAIL signal found
}
The review package parses agent output for these signals.
Resuming¶
When resuming a run, the state machine:
- Loads the last known state from manifest
- Converts run state to loop state
- Continues from that point
func FromRunState(rs run.State) State {
switch rs {
case run.StateSubmitted:
return StateInit
case run.StateWorking:
return StateWorking
case run.StateReviewing:
return StateReviewing
case run.StateCompleted:
return StateComplete
default:
return StateFailed
}
}
Next Steps¶
- Bridge — How agents communicate
- Paired Sessions — Running the full loop