Complete architecture document set with multi-model review remediation: - Frozen interface contracts, runtime semantics, DB schemas - Event/tool/error/provider registries - Scheduler and main agent state machines - C4 module/code views, solution architecture, baseline V1 - Multi-model review reports and joint assessment - Phase-gate remediation complete (P0/P1/P2/UX resolved) - Implementation plan with T-000A through T-045 - Reference folders kept as placeholders only
14 KiB
AirCoding Scheduler State Machine V1
Date: 2026-05-27 Status: Canonical Scheduler state machine for V1.0.0 Alpha skeleton
This document defines the MVP Scheduler lifecycle, task states, retry policy, workspace/merge flow, heartbeat handling, and escalation rules.
The Scheduler is not a coding agent. It is the orchestration service that reads TaskGraph state, launches worker agents, monitors progress, persists transitions, and escalates when architecture or user decisions are required.
1. Inputs and Outputs
Primary inputs:
TaskSpec records
task_dependencies
RuntimeEvents
Architecture impact assessments
Permission decisions
WorkerResult records
Heartbeat/progress events
Resource availability snapshot
Primary outputs:
task/agent/workspace events
child agent processes
workspace creation/merge requests
retry attempts
blocked/escalation reports
ProjectionStore updates via EventBus
The Scheduler persists all durable state through EventStore and domain tables. In-memory queues are rebuildable from SQLite.
Task dependencies use one edge model across TaskSpec, events, DB, and Scheduler memory:
interface TaskDependencySpec {
depends_on_task_id: TaskID
dependency_type: "hard" | "soft" | "conflict" | "serialization"
reason?: string
source?: "architecture" | "scheduler" | "worker" | "user" | "system"
}
Architecture Designer may create planned hard/soft/serialization edges. Scheduler may dynamically add conflict or serialization edges after write-area/resource analysis, but those inferred edges must be persisted through task_dependencies and durable events so restart recovery does not need to rediscover them.
2. Task Status Model
Task table status values:
pending | running | completed | failed | blocked | cancelled | interrupted
State meaning:
| Status | Meaning | Scheduler action |
|---|---|---|
pending |
task exists but is not active | wait for dependencies/resources/write-set availability |
running |
task has an assigned agent/attempt | monitor heartbeat, events, timeout, result |
completed |
task met acceptance and produced WorkerResult | unblock dependents and consider merge/review |
failed |
task attempted and did not meet goal | retry, skip if allowed, or escalate |
blocked |
task cannot continue without higher-level decision | route to Main/Architecture Designer/user as needed |
cancelled |
task intentionally stopped | cancel dependents or replan |
interrupted |
task stopped but can be resumed or requeued | resume/requeue after requirement/control decision |
3. Task Attempt Model
Each run creates a task_attempts row.
task.created
→ task.started + task_attempts.insert(attempt_index=N)
→ agent.started
→ terminal event:
task.completed
task.failed
task.blocked
task.cancelled
task.interrupted
Attempt indexes are monotonically increasing per task.
Failure signature is normalized from:
error_kind + tool/command name + semantic diagnostic signature + failure location + concise failure summary
Repeated identical failure signatures consume retry budget faster than new failure signatures.
4. Scheduler Lifecycle
Top-level Scheduler states:
IDLE
→ LOADING_GRAPH
→ PLANNING_WAVE
→ DISPATCHING
→ MONITORING
→ COLLECTING_RESULTS
→ MERGING
→ REVIEWING_WAVE
→ REPAIRING_OR_CONTINUING
→ COMPLETED | BLOCKED | CANCELLED
IDLE
Scheduler has no active graph or all graphs are terminal.
Transitions:
task.created/ execution request →LOADING_GRAPH- requirement change affecting active work →
LOADING_GRAPH
LOADING_GRAPH
Reads tasks, dependencies, attempts, agents, workspaces, and recent architecture/permission events from DB.
Actions:
- Reconstruct TaskGraph.
- Detect orphaned running tasks/agents.
- Convert missing heartbeat/process to
agent.lostand thentask.failedortask.interrupted. - Validate dependency references.
- Compute blocked/unblocked sets.
Transitions:
- valid runnable graph →
PLANNING_WAVE - no runnable tasks and blocked tasks exist →
BLOCKED - all terminal success →
COMPLETED - graph invalid beyond scheduler repair →
BLOCKED
PLANNING_WAVE
Computes next wave of tasks.
Inputs:
hard dependencies
soft dependencies
conflict/serialization dependencies
write_area and expected_files
resource snapshot
model policy
permission profile
workspace strategy
Rules:
- Hard dependencies must be completed before dispatch.
- Soft dependencies influence priority/context but may run concurrently if Scheduler decides it is safe.
- Tasks with different write areas may run concurrently.
- Tasks with same file but provably different regions may run concurrently in separate worktrees.
- Same write area with uncertain conflict serializes by default.
- Reviewer tasks are read-only and may run concurrently with each other, but not against unstable unmerged write outputs unless they target a workspace snapshot.
- Debugger tasks may serialize with tasks touching the same failure surface.
- Machine resources constrain concurrency.
Wave output:
interface SchedulerWavePlan {
wave_id: string
runnable_task_ids: string[]
serialized_task_ids: string[]
workspace_assignments: Record<string, WorkspacePlan>
model_assignments: Record<string, ModelAssignment>
reason: string
}
Transitions:
- runnable wave exists →
DISPATCHING - no runnable wave but graph not terminal →
BLOCKED
DISPATCHING
Creates workspaces if needed, assembles context, and starts child agents.
Actions per task:
- Create workspace (
main,worktree, orisolated_copy). - Create
task.startedandagent.startedevents. - Write
agent.startcontrol message containingTaskSpec,ContextPack, and runtime context. - Record attempt row.
Dispatch failures:
- permission denied →
task.blocked - workspace create failure → retry if recoverable, otherwise
task.failed - context assembly failure →
task.failedortask.blockeddepending on reason - process start failure →
task.failed
Transition: MONITORING.
MONITORING
Tracks active agents.
Inputs:
agent.heartbeat
task.progress
tool/command events
process exit
soft timeout
hard timeout
user interruption
requirement.changed
Heartbeat policy:
agent emits heartbeat periodically
Scheduler updates agents.last_heartbeat_at and tasks.heartbeat_at
missing heartbeat past threshold → inspect process
process alive but silent → send status ping or soft cancel depending on timeout
process gone without result → agent.lost
Timeout policy:
| Timeout | Action |
|---|---|
| soft timeout | ask agent for checkpoint/status; Scheduler may extend if progress is credible |
| hard timeout | cancel/kill worker; emit agent.lost or agent.cancelled; mark task failed/interrupted |
Requirement change policy:
- Main Agent records
requirement.changed. - Scheduler pauses affected dispatch decisions.
- Architecture Designer assesses impact when change may affect interface/architecture/product goals.
- Implementation-only non-conflicting change may be silently absorbed.
- Architecture-level impact uses
architecture.impact.completedand may require user confirmation or replan.
Transitions:
- all active agents terminal →
COLLECTING_RESULTS - user/global cancellation →
CANCELLED - architecture/user blocker →
BLOCKED
COLLECTING_RESULTS
Consumes WorkerResult and terminal task events.
Actions:
- Validate WorkerResult against output contract.
- Persist artifacts/evidence references.
- Mark attempt terminal.
- Classify task terminal status.
- Compute dependency unblocks.
Status mapping:
| WorkerResult status | Task status |
|---|---|
completed |
completed |
failed |
failed pending retry/skip decision |
blocked |
blocked |
cancelled |
cancelled or interrupted depending on resumability |
Transition:
- write workspaces need merge →
MERGING - no merge needed but review required →
REVIEWING_WAVE - more work remains →
REPAIRING_OR_CONTINUING - all done →
COMPLETED
MERGING
Merges completed workspace outputs back into the target workspace.
Strategies:
main → no merge
worktree → git merge or patch apply
isolated_copy → copy-back or patch apply
Rules:
- Merge only tasks with successful WorkerResult unless explicitly recovering artifacts.
- Serialize merges touching overlapping write areas.
- Persist
workspace.merge.startedand terminal merge event. - Conflict creates
workspace.merge.conflictedand either schedules Debugger/repair or blocks.
Conflict handling:
| Conflict | Scheduler action |
|---|---|
| trivial patch conflict | schedule repair/debugger or retry serially |
| semantic conflict | route to Reviewer/Architecture Designer |
| architecture/interface conflict | block and request Architecture impact assessment |
| user requirement conflict | block and ask Main Agent/user |
Transition:
- merge success and review needed →
REVIEWING_WAVE - merge success and more tasks →
REPAIRING_OR_CONTINUING - merge conflict recoverable →
REPAIRING_OR_CONTINUING - unrecoverable conflict →
BLOCKED
REVIEWING_WAVE
Schedules review tasks after implementation/debug waves when required by plan or risk.
Reviewers are read-only unless a follow-up execute/debug task is created.
Review result handling:
approved → continue
minor issue with clear fix → create execute repair task
high-severity/security/architecture issue → block or Architecture Designer assessment
insufficient evidence → create verification task
Transition: REPAIRING_OR_CONTINUING.
REPAIRING_OR_CONTINUING
Decides next action after a wave.
Actions:
- Retry failed tasks within budget.
- Create repair tasks from review/debug output.
- Skip allowed failed tasks only when acceptance criteria permit fallback.
- Recompute graph and continue.
- Escalate if architecture/user decision is required.
Transition:
- more runnable tasks →
PLANNING_WAVE - blocked →
BLOCKED - all acceptance criteria met →
COMPLETED - cancelled →
CANCELLED
5. Retry Policy
Default retry budget: task-specific TaskSpec.constraints.retry_budget, usually 3–5.
Retry rules:
- Retry only when failure is plausibly recoverable.
- First retry may use same agent strategy with corrected context.
- Later retries should change one dimension: model, context, command strategy, serialization, or Debugger involvement.
- Identical failure signature after multiple attempts triggers escalation faster.
- Environment/kernel/toolchain impossibility becomes
blocked, not infinite retry. - Architecture/interface mismatch routes to Architecture Designer.
- If fallback is allowed and the failed task is non-critical, Scheduler may skip after recording evidence and risk.
Retry decision shape:
interface RetryDecision {
action: "retry" | "retry_serial" | "debug" | "skip" | "block" | "cancel"
reason: string
next_model_policy?: "scheduler_forced" | "agent_select"
required_context_refs?: string[]
}
6. Model Assignment Policy
Scheduler chooses per task:
scheduler_forced → exact model assigned by Scheduler
agent_select → agent may choose within allowed model/capability constraints
Guidelines:
- Implementation tasks with hidden complexity should use strong coding-capable model assignment or constrained agent choice.
- Cross-model review should be scheduler-forced.
- Debug tasks may switch models after repeated failures.
- Cheap/simple tasks may use lower-cost models when quality risk is low.
7. Resource-Aware Concurrency
Scheduler considers:
CPU cores
RAM pressure
disk space
active command count
LLM provider rate limits
toolchain locks
workspace write conflicts
user-configured max concurrency
Concurrency can shrink or expand dynamically. Running tasks are not killed solely because resources become tighter unless the system is at risk; new dispatch is paused first.
8. Escalation Model Hooks
Scheduler does not ask the user directly except through Main Agent/PermissionEngine.
Escalation routes:
| Trigger | Route |
|---|---|
| product/user choice needed | Main Agent |
| architecture/interface change | Architecture Designer → Main Agent/user if needed |
| permission decision | PermissionEngine → Main Agent/UI |
| environment impossible | Main Agent with Doctor/Debugger evidence |
| repeated implementation failure | Debugger or Main Agent based on recoverability |
| high-severity review finding | Main Agent + Architecture Designer if design-level |
9. Recovery on Restart
On startup/resume:
- Load
taskswhere status isrunningorinterrupted. - Load active
agentsandworkspaces. - Check process liveness where PID is known.
- If process is alive and IPC can reconnect, resume monitoring.
- If process is gone, emit
agent.lost; mark task failed/interrupted based on resumability. - Preserve workspaces until merge/cleanup decision is recorded.
- Rebuild queue from pending/failed-with-retry tasks.
10. Terminal Graph States
COMPLETED
All required tasks completed and required review/verification gates passed or documented as skipped by policy.
BLOCKED
No runnable work remains because a decision or external condition is required. Scheduler must produce a concise blocker report with:
blocked task IDs
reason
required decision
available evidence
safe suggested next step
CANCELLED
User/system cancellation propagated to running agents, task statuses terminalized, and workspaces left in a recoverable state.
11. V1.0.0 Alpha Cut Line
The V1.0.0 Alpha skeleton must implement:
- DB-backed TaskGraph loading.
- Hard/soft dependency handling.
- Write-area serialization and basic worktree workspace strategy.
- Child process dispatch via NDJSON IPC.
- Heartbeat timeout detection.
- Retry budget and failure signature tracking.
- Workspace merge success/conflict events.
- Review wave support.
- Restart recovery for running/lost agents.
- Escalation report generation.
Advanced optimization can wait:
fine-grained AST write-set prediction
multi-machine scheduling
provider rate-limit optimization
automatic semantic merge resolution
learning-based retry strategy