Status: Implemented
Author: Mohamed Ameen
Date: 2026-04-17
Last Updated: 2026-05-09
Version: v0.21.1
Reviewers: --
Package: src/orchestrator/
Entry Point: autodev run, autodev plan, autodev execute, autodev resume, autodev status
The FSM Orchestrator is the central coordination layer that drives AutoDev's multi-agent coding workflow. It owns the plan-phase finite state machine (exploration -> domain expertise -> architecture -> tournament gate -> approval) and the execute-phase per-task loop (developer -> QA gates -> reviewer -> test_engineer -> implementation tournament -> completion). The orchestrator wires together the configuration, platform adapter, agent registry, plan manager, knowledge store, guardrails, and loop detector into a deterministic pipeline that transforms a user intent into a reviewed, tested, tournament-refined implementation.
In scope:
Orchestratorclass: construction, property accessors, high-levelplan(),execute(),resume(),status()methods- Plan phase FSM (
plan_phase.py): explorer -> domain_expert -> architect -> plan tournament -> save - Execute phase loop (
execute_phase.py): developer -> QA gates -> reviewer -> test_engineer -> impl tournament -> complete - Task state FSM (
task_state.py):TASK_TRANSITIONSdict,can_transition(),assert_transition() DelegationEnvelope: structured task handoff modelplan_parser.py:parse_plan_markdown()for architect markdown output- Inline state suspend/resume (
inline_state.py) - Plan tournament runner (
plan_tournament_runner.py) - Implementation tournament runner (
impl_tournament_runner.py) - Git worktree management (
worktree.py) for tournament variant isolation
Out of scope:
- Platform adapter internals (covered in
adapters_design.md) - Agent prompt content (covered in
agents_design.md) - Tournament engine core algorithm (covered in
tournaments_design.md) - State/ledger persistence internals (
src/state/) - QA gate implementations (
src/qa/) - Guardrail and loop detection implementations (
src/guardrails/)
The orchestrator sits at the center of the AutoDev pipeline, coordinating all other subsystems:
User Intent
|
v
Orchestrator.plan()
|-- explorer (adapter.execute)
|-- domain_expert (adapter.execute)
|-- architect (adapter.execute)
|-- PlanTournament (tournament engine)
|-- PlanManager.init_plan()
v
Orchestrator.execute()
|-- for each task:
| |-- developer (adapter.execute)
| |-- QA gates (src/qa/)
| |-- reviewer (adapter.execute)
| |-- test_engineer (adapter.execute)
| |-- ImplTournament (tournament engine)
| |-- PlanManager.update_task_status("complete")
v
Completed Plan
The orchestrator references ADR-008 (deterministic FSM orchestration) for its design principle: every state transition is explicit, every delegation produces an evidence artifact, and every failure path has a defined recovery strategy.
- FR-1:
Orchestrator.plan(intent)must drive the plan phase to completion: explorer -> domain_expert -> architect -> optional plan tournament -> save to ledger. - FR-2:
Orchestrator.execute(task_id=None)must process all pending tasks in dependency order, or a single named task. - FR-3:
Orchestrator.resume()must re-enter the execute loop from the last checkpoint, supporting both subprocess and inline adapters. - FR-4:
Orchestrator.status()must return a JSON-serializable snapshot of the current plan state. - FR-5: The execute phase must implement the full QA pipeline per task: developer -> auto-gates (syntax/lint/build/test/secretscan) -> reviewer -> test_engineer.
- FR-6: On retry exhaustion, the orchestrator must consult
critic_sounding_board, writeCriticEvidence, mark the task as escalated and blocked. - FR-7:
DelegationEnvelopemust provide a structured handoff format withtask_id,target_agent,action,files,constraints,acceptance, andcontext. - FR-8:
parse_plan_markdown()must extractPlan->Phase->Taskhierarchy from architect markdown output. - FR-9: The task state FSM must enforce valid transitions as defined in
TASK_TRANSITIONS. - FR-10: For inline adapter mode,
execute()andplan()must suspend by writinginline-state.jsonand raisingDelegationPendingSignal. - FR-11: Implementation tournaments must run in isolated git worktrees, with the winning variant's diff applied back to the main repo.
- Crash-safety: All plan state changes go through
PlanManagerwhich uses filelock + atomic writes. Evidence is written to.autodev/evidence/after each step. If the process crashes mid-task,resume()picks up from the last checkpoint. - Subprocess isolation: Each agent invocation is a fresh subprocess with explicit parameters. No reliance on ambient state.
- Asyncio concurrency: All adapter calls use
await. Tournament runner usesasyncio.gatherfor parallel judge invocations. Bounded byasyncio.Semaphore(max_parallel_subprocesses). - Pydantic v2 strict validation:
DelegationEnvelope,InlineSuspendState, and all evidence schemas useConfigDict(extra="forbid"). - Deterministic reproducibility: Given the same inputs, the FSM follows the same transition sequence. Task ordering is deterministic (sorted by ID within phases).
- LLM cost efficiency: The orchestrator minimizes unnecessary invocations by checking for existing responses (inline resume shortcut), skipping completed tasks, and honoring the
qa_retry_limitcap.
- Must run on Python 3.11+ with no compiled extensions.
- Must work within a single-machine, single-user context.
- Tournament runners require subprocess adapters (not
InlineAdapter). - Git worktrees require a git repository at
cwd.
flowchart TB
subgraph OrchestratorClass["Orchestrator (src/orchestrator/__init__.py)"]
ORCH["Orchestrator\n- cwd, cfg, adapter\n- registry, plan_manager\n- knowledge, guardrails\n- loop_detector"]
end
subgraph PlanPhase["Plan Phase (plan_phase.py)"]
PP_SPEC[Write spec.md]
PP_EXPLORE[explorer delegation]
PP_DOMAIN[domain_expert delegation]
PP_ARCH[architect delegation]
PP_PARSE[parse_plan_markdown]
PP_TOURN[PlanTournament]
PP_SAVE[PlanManager.init_plan]
end
subgraph ExecutePhase["Execute Phase (execute_phase.py)"]
EP_LOOP[Task loop]
EP_DEV[developer delegation]
EP_QA[QA gates]
EP_REV[reviewer delegation]
EP_TEST[test_engineer delegation]
EP_ITOURN[ImplTournament]
EP_DONE[Mark complete]
end
subgraph Support["Support Modules"]
TS[task_state.py\nFSM transitions]
DE[delegation_envelope.py\nDelegationEnvelope]
PP[plan_parser.py\nparse_plan_markdown]
IS[inline_state.py\nsuspend/resume]
WT[worktree.py\nWorktreeManager]
end
ORCH -->|plan(intent)| PP_SPEC
PP_SPEC --> PP_EXPLORE
PP_EXPLORE --> PP_DOMAIN
PP_DOMAIN --> PP_ARCH
PP_ARCH --> PP_PARSE
PP_PARSE --> PP_TOURN
PP_TOURN --> PP_SAVE
ORCH -->|execute(task_id)| EP_LOOP
EP_LOOP --> EP_DEV
EP_DEV --> EP_QA
EP_QA --> EP_REV
EP_REV --> EP_TEST
EP_TEST --> EP_ITOURN
EP_ITOURN --> EP_DONE
EP_DEV --> DE
EP_REV --> DE
EP_TEST --> DE
EP_LOOP --> TS
EP_ITOURN --> WT
| File | Responsibility |
|---|---|
__init__.py |
Orchestrator class: wires all subsystems, exposes plan(), execute(), resume(), status() |
plan_phase.py |
run_plan_phase(): explorer -> domain_expert -> architect -> tournament -> save. Private _delegate() for plan-phase agent calls |
execute_phase.py |
run_execute_phase(): per-task developer -> QA -> reviewer -> test_engineer -> impl tournament. Public delegate() for execute-phase agent calls |
task_state.py |
TASK_TRANSITIONS dict, can_transition(), assert_transition() |
delegation_envelope.py |
DelegationEnvelope Pydantic model, DelegationAction literal type |
plan_parser.py |
parse_plan_markdown(): regex-based parser for architect markdown output |
inline_state.py |
write_suspend_state(), load_suspend_state(), clear_suspend_state() |
plan_tournament_runner.py |
run_plan_tournament(): glue between orchestrator and tournament engine for plan refinement |
impl_tournament_runner.py |
run_impl_tournament(): glue between orchestrator and tournament engine for implementation refinement |
worktree.py |
WorktreeManager: git worktree creation, removal, diffing, and patch application |
worktree_pool.py |
WorktreePool (v0.21.0 A1): warm-start pool wrapping WorktreeManager — cold-starts N worktrees concurrently and recycles via git reset --hard <baseline> && git clean -fdx. Same create_per_task / remove_per_task / get_diff_vs_base / apply_patch_to_main surface as WorktreeManager so the worker is unaware of the substitution. |
speculative.py |
rollback_speculative_task, commit_speculative_task, reset_speculative_worktree (v0.21.0 B2): rollback handler for speculatively-started child tasks when their parent fails. |
DelegationEnvelope:
DelegationAction = Literal[
"implement", "review", "test", "explore",
"critique", "consult", "document", "design",
]
class DelegationEnvelope(BaseModel):
"""Structured task handoff to a specialist role."""
model_config = ConfigDict(extra="forbid")
task_id: str
target_agent: str
action: DelegationAction
files: list[str] = Field(default_factory=list)
constraints: list[str] = Field(default_factory=list)
acceptance: str | None = None
context: dict[str, Any] = Field(default_factory=dict)The envelope renders into a human-readable text block via render_as_task_message():
TASK: 2.1
AGENT: developer
ACTION: implement
FILES:
- src/models/user.py
ACCEPTANCE: User model with email validation
CONTEXT:
task_title: Add user model
task_description: Create User Pydantic model with email validation
Plan hierarchy (from src/state/schemas.py):
class Plan(BaseModel):
model_config = ConfigDict(extra="forbid")
plan_id: str
spec_hash: str
phases: list[Phase]
metadata: dict[str, Any] = Field(default_factory=dict)
created_at: str
updated_at: str
content_hash: str = ""
class Phase(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str # "1", "2", "3"
title: str
description: str = ""
tasks: list[Task]
class Task(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str # "1.1", "1.2", "2.1"
phase_id: str
title: str
description: str
status: TaskStatus = "pending"
files: list[str] = Field(default_factory=list)
acceptance: list[AcceptanceCriterion] = Field(default_factory=list)
depends_on: list[str] = Field(default_factory=list)
retry_count: int = 0
escalated: bool = False
assigned_agent: str | None = None
evidence_bundle: str | None = None
blocked_reason: str | None = None
metadata: dict[str, Any] = Field(default_factory=dict)Evidence types (discriminated union on kind field):
| Evidence Type | kind |
Key Fields |
|---|---|---|
CoderEvidence |
"developer" |
diff, files_changed, output_text, duration_s, success |
ReviewEvidence |
"review" |
verdict (APPROVED/NEEDS_CHANGES/REJECTED), issues |
TestEvidence |
"test" |
passed, failed, total, coverage_pct |
ExploreEvidence |
"explore" |
findings, files_referenced |
SMEEvidence |
"domain_expert" |
topic, findings, confidence (HIGH/MEDIUM/LOW) |
CriticEvidence |
"critic" |
verdict (APPROVED/NEEDS_REVISION/REJECTED), issues |
TournamentEvidence |
"tournament" |
tournament_id, phase, passes, winner, converged, history |
stateDiagram-v2
[*] --> pending
pending --> in_progress : developer assigned
pending --> skipped : user skip
pending --> blocked : dependency blocked
in_progress --> coded : developer finished
in_progress --> blocked : guardrail exceeded / hard failure
in_progress --> in_progress : retry (self-loop)
coded --> auto_gated : QA gates passed
coded --> in_progress : gate failure -> retry
coded --> blocked : gate failure exhausted
auto_gated --> reviewed : reviewer APPROVED
auto_gated --> in_progress : reviewer REJECTED -> retry
auto_gated --> blocked : reviewer exhausted
reviewed --> tested : test_engineer PASS
reviewed --> in_progress : tests failed -> retry
reviewed --> blocked : test exhausted
tested --> tournamented : impl tournament finished
tested --> in_progress : tournament retry
tested --> blocked : tournament failure
tournamented --> complete : task done
blocked --> in_progress : explicit resume
complete --> [*]
skipped --> [*]
Transition table (TASK_TRANSITIONS):
TASK_TRANSITIONS: dict[TaskStatus, set[TaskStatus]] = {
"pending": {"in_progress", "skipped", "blocked"},
"in_progress": {"coded", "blocked", "in_progress"},
"coded": {"auto_gated", "in_progress", "blocked"},
"auto_gated": {"reviewed", "in_progress", "blocked"},
"reviewed": {"tested", "in_progress", "blocked"},
"tested": {"tournamented", "in_progress", "blocked"},
"tournamented": {"complete", "blocked"},
"complete": set(), # terminal
"blocked": {"in_progress"},
"skipped": set(), # terminal
}The in_progress -> in_progress self-loop is explicitly allowed for retry bookkeeping.
The Orchestrator class does not define a Protocol -- it is a concrete class instantiated directly. However, it depends on the PlatformAdapter ABC:
class PlatformAdapter(ABC):
@abstractmethod
async def execute(self, inv: AgentInvocation) -> AgentResult: ...
@abstractmethod
async def init_workspace(self, cwd: Path, agents: list[AgentSpec]) -> None: ...
@abstractmethod
async def healthcheck(self) -> tuple[bool, str]: ...| Method | Signature | Description |
|---|---|---|
Orchestrator.__init__ |
(cwd, cfg, adapter, registry, session_id, disable_impl_tournament, lock_timeout_s) |
Wires all subsystems |
Orchestrator.plan(intent) |
str -> Plan |
Run plan phase end-to-end |
Orchestrator.execute(task_id) |
`str | None -> list[Task]` |
Orchestrator.resume() |
-> list[Task] |
Re-enter execute loop from last checkpoint |
Orchestrator.status() |
-> dict[str, Any] |
JSON-serializable plan snapshot |
run_plan_phase(orch, intent) |
(Orchestrator, str) -> Plan |
Plan FSM implementation |
run_execute_phase(orch, task_id) |
`(Orchestrator, str | None) -> list[Task]` |
delegate(orch, role, envelope, ...) |
-> AgentResult |
Execute-phase agent invocation with guardrails |
parse_plan_markdown(md, spec_hash) |
(str, str) -> Plan |
Deterministic markdown parser |
| Decision | Rationale | Alternatives Considered |
|---|---|---|
| Deterministic FSM with explicit transitions | Every state change is validated by assert_transition(). No implicit state leaks. Makes debugging straightforward because illegal transitions are caught immediately. (ADR-008) |
Event-driven architecture -- rejected because it makes the execution order harder to reason about and debug. |
Separate _delegate() in plan_phase and delegate() in execute_phase |
The plan phase and execute phase have different guardrail/logging/inline-resume semantics. Keeping them separate avoids conditional branching on "which phase am I in?" | Single shared delegate function -- rejected because it would need to know whether it's in plan or execute context for inline state naming. |
DelegationEnvelope as structured handoff |
Forces every agent invocation to declare its task_id, target_agent, action, files, constraints, and acceptance. This traceability is essential for evidence correlation and debugging. |
Passing raw prompt strings -- rejected because it loses structured metadata needed for evidence and audit. |
| Regex-based plan parser (not LLM-based) | Deterministic, fast, testable. The architect is instructed to produce a canonical markdown format; the parser validates that format. No LLM call overhead for parsing. | LLM-based plan extraction -- rejected because it adds cost, latency, and non-determinism to a critical path. |
One retry loop per _execute_one() call |
The entire developer -> QA -> reviewer -> test_engineer pipeline runs inside a single while True loop with continue on failure. This ensures that a reviewer rejection sends the task back to the developer with the issue list, not to a different step. |
Separate retry loops per step -- rejected because it fragments the retry budget; a task that fails at reviewer should consume the same retry counter as one that fails at QA. |
| Critic sounding board as escalation path | When retries are exhausted, critic_sounding_board diagnoses the failure pattern before blocking the task. This provides actionable feedback to the user about why a task cannot proceed. |
Immediate blocking without diagnosis -- rejected because it gives the user no information about the root cause. |
| Tournament runners as separate modules | plan_tournament_runner.py and impl_tournament_runner.py are glue modules that keep tournament wiring out of the FSM files. This separation lets the plan/execute phases remain focused on their core flow. |
Inline tournament logic in plan_phase/execute_phase -- rejected because tournament configuration, model resolution, and worktree management would bloat the FSM files. |
| Git worktrees for impl tournament isolation | Each tournament variant (A, B, AB) runs in a detached git worktree. This provides true filesystem isolation without cloning the repo three times. The winning variant's diff is applied back via git apply. |
Docker containers, temp directories with file copies -- worktrees are faster, share the git object store, and integrate naturally with diff/apply workflows. |
| Framing/altitude phase between exploration and planning (Proposed) | Poses the patch-vs-architecture decision the pipeline cannot currently raise: classifies the defect (local vs realized-design-failure), generates altitude-diverse strategies, and selects one with minimality suspended before handing to the architect. Inserts in run_plan_phase after the index query (plan_phase.py:735). See ADR-0044 and framing_altitude_phase_design.md. |
Do nothing (never poses the decision); always-multi-strategy (no classifier -- over-engineers local bugs); un-gate BRAINSTORM with a human-in-loop (no operator on the autonomous path). |
- QA gates currently pretend-pass in Phase 4. The
_run_qa_gates()function invokes real gate functions (run_syntax_check,run_lint, etc.) but the gate infrastructure is evolving. Phase 8 will wire full enforcement. - Inline mode does not support tournaments. Tournament runners assert
not isinstance(orch.adapter, InlineAdapter). Tournaments require subprocess adapters for judge independence. Inline mode skips tournaments. - Plan parser is forgiving about structure but strict about hierarchy. Missing
Description:orFiles:fields are tolerated (defaults to empty), but a phase with no tasks or a plan with no phases raisesPlanParseError.
Plan Phase Flow:
flowchart TB
START[run_plan_phase] --> SPEC[Write spec.md + compute spec_hash]
SPEC --> EXPLORE[Delegate to explorer\naction: explore]
EXPLORE --> DOMAIN[Delegate to domain_expert\naction: consult]
DOMAIN --> ARCH[Delegate to architect\naction: document]
ARCH --> PARSE{parse_plan_markdown}
PARSE -->|Success| TOURN_CHECK{Tournament enabled?}
PARSE -->|PlanParseError| RETRY[Retry architect with format hint]
RETRY --> PARSE2{parse_plan_markdown}
PARSE2 -->|Success| TOURN_CHECK
PARSE2 -->|PlanParseError| FAIL[Raise PlanParseError]
TOURN_CHECK -->|Yes| TOURN[run_plan_tournament]
TOURN_CHECK -->|No| SAVE[PlanManager.init_plan]
TOURN --> SAVE
SAVE --> DONE[Return Plan]
Execute Phase Flow (per task):
flowchart TB
START[_execute_one] --> STATUS[Update status: in_progress]
STATUS --> DEV[Delegate to developer]
DEV -->|Failure| RETRY{Retry limit?}
RETRY -->|Under limit| DEV
RETRY -->|Exhausted| ESCALATE[critic_sounding_board\nMark blocked]
DEV -->|Success| CODED[Status: coded]
CODED --> QA[Run QA gates\nsyntax/lint/build/test/secretscan]
QA -->|Failure| RETRY
QA -->|Pass| GATED[Status: auto_gated]
GATED --> REVIEW[Delegate to reviewer]
REVIEW -->|NEEDS_CHANGES/REJECTED| RETRY
REVIEW -->|APPROVED| REVIEWED[Status: reviewed]
REVIEWED --> TEST[Delegate to test_engineer]
TEST -->|Failed tests| RETRY
TEST -->|Pass| TESTED[Status: tested]
TESTED --> ITOURN_CHECK{Impl tournament\nenabled?}
ITOURN_CHECK -->|Yes| ITOURN[run_impl_tournament]
ITOURN_CHECK -->|No| TOURNAMENTED[Status: tournamented]
ITOURN --> TOURNAMENTED
TOURNAMENTED --> COMPLETE[Status: complete]
Delegation function (execute_phase.delegate):
- Look up
AgentSpecfrom registry by role. - Build prompt:
spec.prompt + "---" + envelope.render_as_task_message() + extra_context + knowledge.inject_block(). - Construct
AgentInvocationwith role, prompt, cwd, model, allowed_tools, max_turns=1. - Inline adapter shortcut: If response file already exists, call
collect_response()and return. - Inline adapter delegation: If no response, inject
task_idintoinv.metadata. - Call
orch.guardrails.pre_invocation(). - Call
orch.adapter.execute(inv). - On
DelegationPendingSignal: write suspend state, re-raise. - Call
orch.guardrails.post_invocation(). - Call
orch.loop_detector.observe()on success. - Return
AgentResult.
Retry and escalation (_try_retry_or_escalate):
- Increment retry count via
plan_manager.mark_task_retry(). - If
new_count >= retry_limit: a. Delegate tocritic_sounding_boardwith failure context. b. WriteCriticEvidencewithverdict=NEEDS_REVISION. c. Callplan_manager.mark_escalated(). d. Update task status toblockedwithblocked_reason. e. Return escalated task. - If under limit: update status to
in_progressand return for retry.
The parse_plan_markdown() function uses a set of compiled regexes to extract structured data from architect markdown:
| Regex | Purpose |
|---|---|
_RE_PLAN_TITLE |
Matches # Plan: <title> |
_RE_PHASE |
Matches ## Phase <id>: <title> |
_RE_TASK |
Matches ### Task <id>: <title> |
_RE_FILES |
Matches - Files: path1, path2 |
_RE_DESC |
Matches - Description: <text> |
_RE_ACCEPT_HEADER |
Matches - Acceptance: |
_RE_ACCEPT_ITEM |
Matches - [ ] <criterion> |
_RE_DEPENDS |
Matches - Depends: 1.0, 1.1 |
The parser is a single-pass line scanner that maintains current_phase and current_task state. Phases and tasks are finalized when a new heading is encountered or at end of input. Error conditions:
- Empty markdown ->
PlanParseError("empty plan markdown") - No
# Plan:heading ->PlanParseError("missing '# Plan: <title>' heading") - Task before any phase ->
PlanParseError("task appears before any phase heading") - Phase with no tasks ->
PlanParseError("phase has no tasks") - No phases found ->
PlanParseError("no phases found")
Each parsed task is wrapped in _make_task() which constructs AcceptanceCriterion objects and defaults assigned_agent to "developer".
Plan tournament:
client = AdapterLLMClient(orch.adapter, cwd=orch.cwd)
tcfg = TournamentConfig(
num_judges=cfg.num_judges,
convergence_k=cfg.convergence_k,
max_rounds=cfg.max_rounds,
model=model,
max_parallel_subprocesses=orch.cfg.tournaments.max_parallel_subprocesses,
)
tournament = Tournament(handler=PlanContentHandler(), client=client, cfg=tcfg, ...)
final_md, history = await tournament.run(task_prompt=spec, initial=initial_md)The tournament engine internally uses asyncio.gather with a semaphore bound of max_parallel_subprocesses (default 3) for parallel judge invocations.
Impl tournament:
wt_mgr = WorktreeManager(main_repo=orch.cwd, tournament_dir=worktree_dir)
coder_runner = _CoderRunner(orch)
tournament = ImplTournament(
handler=ImplContentHandler(), client=client, cfg=tcfg,
coder_runner=coder_runner, worktree_manager=wt_mgr,
)
try:
final_bundle, history = await tournament.run(task_prompt=..., initial=initial_bundle)
finally:
await wt_mgr.cleanup_all()The _CoderRunner runs the developer and test_engineer in the worktree's cwd, providing filesystem isolation. The WorktreeManager.cleanup_all() is in a finally block to ensure worktrees are removed even on error.
The orchestrator itself does not spawn subprocesses -- it delegates to the adapter layer. However, the WorktreeManager uses asyncio.create_subprocess_exec for all git operations:
async def _run_git(cwd: Path, args: Iterable[str], stdin: str | None = None,
timeout_s: float = 60.0) -> tuple[int, str, str]:
proc = await asyncio.create_subprocess_exec(
"git", *args,
cwd=str(cwd),
stdin=asyncio.subprocess.PIPE if stdin else None,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout_b, stderr_b = await asyncio.wait_for(
proc.communicate(stdin.encode("utf-8") if stdin else None),
timeout=timeout_s,
)
return (proc.returncode or -1, stdout_b.decode(...), stderr_b.decode(...))When the orchestrator delegates to the InlineAdapter, the adapter writes a delegation file and raises DelegationPendingSignal. The orchestrator catches this and writes its FSM position to .autodev/inline-state.json:
sequenceDiagram
participant User as User/Agent
participant CLI as autodev CLI
participant Orch as Orchestrator
participant Inline as InlineAdapter
participant FS as Filesystem
User->>CLI: autodev run --platform inline
CLI->>Orch: plan(intent) or execute()
Orch->>Inline: execute(inv)
Inline->>FS: Write .autodev/delegations/<task_id>-<role>.md
Inline-->>Orch: raise DelegationPendingSignal
Orch->>FS: Write .autodev/inline-state.json
Orch-->>CLI: Signal propagates, process exits
Note over User,FS: Agent reads delegation, executes task, writes response
User->>FS: Write .autodev/responses/<task_id>-<role>.json
User->>CLI: autodev resume
CLI->>Orch: resume()
Orch->>FS: Read inline-state.json
Orch->>Inline: has_pending_response() -> True
Orch->>Inline: collect_response() -> AgentResult
Orch->>FS: Delete inline-state.json
Orch->>Orch: Continue FSM from checkpoint
The InlineSuspendState captures: session_id, pending_task_id, pending_role, delegation_path, response_path, orchestrator_step (which step in the FSM), retry_count, and last_issues.
run_execute_phase selects between two dispatchers based on cfg.cross_phase_parallelism_enabled:
| Mode | Dispatcher | Scope |
|---|---|---|
| Default (per-phase) | _execute_phase_dag (src/orchestrator/execute_phase.py:823) |
One worker pool per phase. Tasks from later phases wait for the current phase to finish (tasks_to_run = [t for t in tasks_to_run if t.phase_id == phase_id]). |
| Cross-phase (opt-in) | _execute_cross_phase_dag (src/orchestrator/execute_phase.py:989) |
One worker pool spans every phase. Tasks from phase N+1 may begin while phase N's tail is still in-flight, provided their depends_on are terminal AND files are disjoint with every in-flight task. |
Concurrency cap is computed once via _resolve_execute_parallelism (src/orchestrator/execute_phase.py:774): forwards cfg.tournaments.execute_max_parallel_tasks (None = auto-resolve) into runtime.resource_probe.resolve_parallelism with role_mix='execute'.
Cross-phase DAG validation. Per-phase DAGs reject cross-phase deps as "undefined references", so under the cross-phase flag _validate_cross_phase_dag (src/orchestrator/execute_phase.py:935) runs a relaxed plan-level check that accepts cross-phase deps as long as every dep resolves SOMEWHERE in the plan; cycle detection still runs across the unified graph.
Phase boundary capture. When all tasks in a phase reach a terminal state, _maybe_record_phase_checkpoint (src/orchestrator/execute_phase.py:1324) captures HEAD into Phase.end_checkpoint_commit. Phase-review uses this SHA as the tip_commit of its diff range so phase N+1's concurrent commits don't pollute phase N's review window. Idempotent: phases already carrying an end_checkpoint_commit are skipped.
Dispatcher lifecycle (cross-phase mode):
1. Validate plan-level DAG (_validate_cross_phase_dag).
2. Record baseline_commit per phase (_maybe_record_phase_entry).
3. Loop:
a. Spawn workers up to parallelism cap from PlanManager.next_pending_tasks
(file-overlap and depends-on guards enforced at the plan_manager layer).
b. Optionally speculate ONE child task per phase (see 5.7).
c. asyncio.wait FIRST_COMPLETED on in_flight workers.
d. For each finished task: clear in_flight, capture result; on failure
cascade-block descendants via mark_blocked_descendants.
e. Roll back any speculative children whose parents just failed.
f. Fire phase-review for any phase that just observed all-terminal.
4. Terminate when every task across every plan is terminal AND in_flight is empty.
Failure isolation. Workers go through _execute_one_worker (src/orchestrator/execute_phase.py:1236), which catches every exception except DelegationPendingSignal, transitions the task to blocked, and cascade-blocks descendants. Plan-fatal exceptions never reach the dispatcher.
When cfg.speculative_execution_enabled is True, the cross-phase dispatcher may opportunistically start ONE child task per phase while its parent is still in-flight. The bet: if the parent succeeds, the child's work is valid with no extra step; if the parent fails, the child's worktree is reset and the child is re-queued.
Candidate selection is delegated to PlanManager.speculable_candidate(parent_id) (src/state/plan_manager.py:431). It returns a single child task that satisfies ALL of:
- the parent is in-flight (status not in
_TERMINAL_TASK_STATUSES), - the parent's
retry_count == 0(first attempt only — speculating on a retry compounds risk), - the child has a SINGLE
depends_onentry pointing at the parent (diamond avoidance), - the child's files are disjoint with EVERY currently in-flight task's files,
- the child is currently
pending.
Per-phase cap of 1. The dispatcher tracks speculative_phase: set[str] and skips parents whose phase already hosts an active speculative child (src/orchestrator/execute_phase.py:1077). One per phase prevents a chain of speculative failures from compounding.
Rollback (rollback_speculative_task in src/orchestrator/speculative.py:76):
- Best-effort
git reset --hard <baseline>+git clean -fdxon the speculative worktree (reset_speculative_worktree, mirrorsWorktreePool.releasesemantics). - If a
WorktreeManagerwas supplied, also remove the per-task worktree (overflow path). - Re-queue the speculative task as
pendingviaPlanManager.revert_task_to_pending(src/state/plan_manager.py:371) — this is the one legitimate caller for the FSMassert_transitionbypass on the in_progress→pending edge. Resetsretry_countandescalated. - Append a
speculative_rolled_backledger op for forensics.
All steps are best-effort: errors are logged, never raised. Idempotent on re-invocation.
Ledger surface (src/state/ledger.py:152-162, :530-538): three audit-only ops emitted alongside the regular update_task_status ops that mutate plan state:
| Op | Payload | When |
|---|---|---|
speculative_started |
{task_id, parent_task_id} |
Immediately before the speculative worker is created (dispatcher emits) |
speculative_rolled_back |
{task_id, parent_task_id, reason} |
After the rollback handler completes (handler emits) |
speculative_committed |
{task_id, parent_task_id} |
Confirms a speculative task after parent success — emitted by commit_speculative_task (currently unused by the dispatcher; see speculative_execution.md). |
Replay treats these as no-ops — the actual status transitions are reconstructed from the regular update_task_status entries.
See speculative_execution.md for full semantics, failure modes, and configuration knobs.
WorktreePool (src/orchestrator/worktree_pool.py) is an opt-in warm-start pool that wraps WorktreeManager. It cold-starts N worktrees concurrently at execute-phase entry and recycles them via git reset --hard <baseline> && git clean -fdx on every release, instead of paying git worktree add cost on every task dispatch.
Activation. When cfg.worktree_pool_enabled is True and cwd is a git repo, run_execute_phase substitutes a WorktreePool for the WorktreeManager (src/orchestrator/execute_phase.py:592-608). The pool implements the same create_per_task / remove_per_task / get_diff_vs_base / apply_patch_to_main surface so the worker (_execute_one) is unaware of the substitution. Default False — cold-start adds 2-5s of upfront latency that's only worthwhile on multi-task plans.
Pool size equals the resolved execute parallelism (_resolve_execute_parallelism), so the pool exactly fills the worker concurrency budget. Persistent dir: <autodev_root>/execute_worktrees_pool/.
Lifecycle:
cold_start(n)— captures baseline (HEAD SHA) once, then concurrentlygit worktree addforpool-0…pool-(n-1). Failures are logged; pool runs at reduced size rather than aborting.claim(task_id)— pops one path from the queue. If empty, falls back toWorktreeManager.create_per_taskundertasks/<task_id>(overflow path) so the dispatcher never blocks.release(worktree, task_id)— resets worktree to baseline + clean, returns to queue. Overflow worktrees (undertasks/<id>) are REMOVED rather than queued so the queue never inflates beyondsize.cleanup_all()— removes every worktree (pooled + overflow) and the persistent pool dir. Safe to call multiple times.
Baseline capture. cold_start records the main repo's HEAD SHA into self._baseline. Every release resets to this SHA — even if HEAD advances during execution (e.g. an in-flight task commits), the pool keeps recycling against the original baseline. This is what guarantees predictable, side-effect-free diff generation per task.
Sparse checkout caveat. Pool worktrees are full checkouts. The create_per_task(sparse_paths=...) parameter is accepted for API parity but ignored at claim time (the cold-start budget assumes upfront cost is paid once and recycled). Operators who require sparse-checkout per task should keep worktree_pool_enabled=False.
| Error Condition | Handling |
|---|---|
| Role not in registry | Raise AutodevError(f"role {role!r} not in registry") |
| Task not found in plan | Raise AutodevError(f"task_id={task_id!r} not found in plan") |
PlanParseError on first attempt |
Retry architect with prior_attempt and parse_error in context |
PlanParseError on retry |
Raise (propagates to CLI) |
GuardrailExceededError during delegation |
Mark task blocked with reason, return task |
| Developer adapter failure | Retry up to qa_retry_limit, then escalate |
| QA gate failure | Retry up to qa_retry_limit, then escalate |
| Reviewer NEEDS_CHANGES/REJECTED | Retry with issues injected as context |
| Test failures | Retry up to qa_retry_limit, then escalate |
| Retry exhaustion | critic_sounding_board -> CriticEvidence -> mark escalated + blocked |
| Response file missing on inline resume | Raise AutodevError with instructions |
| Impl tournament error | Log warning and continue (non-fatal) |
WorktreeError |
Propagates from worktree operations; cleanup_all in finally block |
- pydantic:
DelegationEnvelope, all evidence schemas,InlineSuspendState - structlog: Structured logging via
autologging.get_logger - filelock: Cross-process locking in
PlanManager(vialock_timeout_sparameter) - Internal:
src/adapters/--PlatformAdapter,InlineAdapter,AgentInvocation,AgentResult,DelegationPendingSignalsrc/config/schema.py--AutodevConfig, tournament/guardrail/QA gate configssrc/state/--PlanManager,KnowledgeStore, evidence writing, path helperssrc/guardrails/--GuardrailEnforcer,LoopDetectorsrc/qa/--GateResult,detect_language, individual gate functionssrc/tournament/--Tournament,ImplTournament,AdapterLLMClient,TournamentConfigsrc/errors.py--AutodevError,GuardrailExceededError
| Config Path | Description | Default |
|---|---|---|
AutodevConfig.qa_retry_limit |
Max retries before escalation | 3 |
AutodevConfig.tournaments.plan.enabled |
Enable plan tournament | Configured |
AutodevConfig.tournaments.impl.enabled |
Enable impl tournament | Configured |
AutodevConfig.tournaments.plan.num_judges |
Number of judges per round | Configured |
AutodevConfig.tournaments.plan.convergence_k |
Consecutive wins for convergence | Configured |
AutodevConfig.tournaments.plan.max_rounds |
Maximum tournament rounds | Configured |
AutodevConfig.tournaments.max_parallel_subprocesses |
Semaphore bound for parallel judge invocations | 3 |
AutodevConfig.tournaments.auto_disable_for_models |
Model substrings that disable tournaments | ["opus"] |
AutodevConfig.qa_gates.* |
Enable/disable individual QA gates | Varies |
AutodevConfig.guardrails.max_tool_calls_per_task |
Per-task tool call limit | 60 |
AutodevConfig.guardrails.max_duration_s_per_task |
Per-task duration limit | 900 (15 min) |
AutodevConfig.guardrails.max_diff_bytes |
Maximum diff size before warning | 5 MB |
AutodevConfig.tournaments.execute_max_parallel_tasks |
Per-task worker pool cap (None = auto-resolve via runtime.resource_probe) |
None |
AutodevConfig.worktree_pool_enabled (v0.21.0 A1) |
Opt-in: pre-create worktrees at execute-phase entry, recycle via reset+clean | False |
AutodevConfig.cross_phase_parallelism_enabled (v0.21.0 B1) |
Opt-in: tasks from phase N+1 may begin while phase N's tail is in-flight | False |
AutodevConfig.speculative_execution_enabled (v0.21.0 B2) |
Opt-in: speculatively start ONE child task per phase while parent is in-flight | False |
| Component | Dependency |
|---|---|
src/adapters/ |
PlatformAdapter.execute() for all agent invocations |
src/agents/ |
build_registry() produces the dict[str, AgentSpec] consumed by the orchestrator |
src/config/schema.py |
AutodevConfig provides all configuration |
src/state/plan_manager.py |
PlanManager for plan persistence, task status updates, ledger writes |
src/state/knowledge.py |
KnowledgeStore.inject_block() for knowledge injection into prompts |
src/state/evidence.py |
write_evidence(), write_patch() for evidence persistence |
src/state/schemas.py |
Plan, Phase, Task, all evidence types |
src/guardrails/ |
GuardrailEnforcer.pre_invocation(), .post_invocation(), LoopDetector.observe() |
src/qa/ |
run_syntax_check(), run_lint(), run_build_check(), run_tests(), run_secretscan() |
src/tournament/ |
Tournament, ImplTournament, AdapterLLMClient, PlanContentHandler, ImplContentHandler |
The orchestrator consumes the PlatformAdapter protocol via orch.adapter.execute(). It also uses isinstance(orch.adapter, InlineAdapter) checks for inline-specific behavior (response file collection, delegation pending signal handling, tournament assertions).
| Event | Written By | Purpose |
|---|---|---|
plan_tournament_complete |
plan_tournament_runner.py |
Records tournament ID, passes, winner, artifact dir |
impl_tournament_complete |
impl_tournament_runner.py |
Records tournament ID, task ID, passes, winner |
Evidence: CoderEvidence |
execute_phase.py |
After each developer invocation |
Evidence: ReviewEvidence |
execute_phase.py |
After each reviewer invocation |
Evidence: TestEvidence |
execute_phase.py |
After each test_engineer invocation |
Evidence: ExploreEvidence |
plan_phase.py |
After explorer during plan phase |
Evidence: SMEEvidence |
plan_phase.py |
After domain_expert during plan phase |
Evidence: CriticEvidence |
execute_phase.py |
After critic_sounding_board escalation |
Evidence: TournamentEvidence |
impl_tournament_runner.py |
After impl tournament completion |
speculative_started (v0.21.0 B2) |
execute_phase.py |
Cross-phase dispatcher, before launching speculative child worker |
speculative_rolled_back (v0.21.0 B2) |
speculative.py |
After parent failure rollback completes (worktree reset + child re-queued) |
speculative_committed (v0.21.0 B2) |
speculative.py:commit_speculative_task |
After parent success confirms speculative child (helper exists; not currently invoked from execute_phase) |
| Consumer | Usage |
|---|---|
src/cli/ (Click commands) |
autodev run constructs and calls Orchestrator.plan() + .execute() |
autodev resume |
Calls Orchestrator.resume() |
autodev status |
Calls Orchestrator.status() |
| System | Interaction |
|---|---|
| LLM Platforms | Via adapter layer (Claude Code, Cursor subprocesses) |
| Git | Worktree management for impl tournaments (git worktree add/remove, git diff, git apply) |
| Filesystem | .autodev/ directory for plan state, evidence, delegations, responses, inline state, tournament artifacts |
can_transition()andassert_transition()for all valid and invalid transitions inTASK_TRANSITIONS.DelegationEnvelope.render_as_task_message()output format.parse_plan_markdown()with valid markdown, missing title, missing phases, empty tasks, malformed headings._parse_review_verdict()for APPROVED, NEEDS_CHANGES, REJECTED, and empty input._parse_test_counts()for validRESULTS: passed=N failed=M total=Tand missing/malformed lines._developer_envelope(),_review_envelope(),_test_envelope()builders._resolve_tournament_model()and_is_auto_disabled()with various model/disable combinations.write_suspend_state()/load_suspend_state()/clear_suspend_state()round-trip.
- Plan phase end-to-end with mock adapter: verify that explorer, domain_expert, architect are called in order, plan is parsed and saved.
- Execute phase with mock adapter: verify developer -> QA -> reviewer -> test_engineer sequence, status transitions, evidence writes.
- Retry and escalation: verify that critic_sounding_board is called and task is marked blocked after retry exhaustion.
- Inline suspend/resume: write delegation, create response file, call resume, verify FSM continues.
- Plan tournament: mock tournament engine, verify refined markdown replaces original.
- WorktreeManager: create worktree, make changes, get diff, apply patch, cleanup.
- Hypothesis: random
TaskStatussequences are either all valid transitions or caught byassert_transition(). - Hypothesis: random markdown input to
parse_plan_markdown()either produces a validPlanor raisesPlanParseError(never crashes). - Hypothesis:
DelegationEnveloperound-trip throughmodel_dump_json()/model_validate_json().
- Sample architect markdown output (valid, malformed, edge cases).
- Mock
AutodevConfigwith tournament enabled/disabled. - Mock
PlatformAdapterthat returns cannedAgentResultobjects. - Mock
PlanManagerthat tracks status transitions in memory.
- Prompt injection: The orchestrator passes user intent (from
autodev runCLI) into the spec and subsequently into agent prompts. The intent is treated as untrusted input and is bounded by the spec file (not directly concatenated into executable commands). - Worktree isolation: Tournament worktrees are detached from any branch (
--detach). They cannot accidentally push changes or corrupt the main repo's branch state. Thecleanup_all()infinallyensures no stale worktrees persist. - Guardrails:
GuardrailEnforcerenforces per-task limits on tool calls (max_tool_calls_per_task=60), duration (max_duration_s_per_task=900), and diff size (max_diff_bytes=5MB). These limits prevent runaway agent behavior. - Evidence integrity: The
content_hashfield onPlanenables CAS (content-addressable storage) hash chaining so partial writes are detectable.
- Within-phase concurrency, cross-phase parallelism (v0.21.0 B1): The execute phase runs an asyncio worker pool per phase via
_execute_phase_dag(src/orchestrator/execute_phase.py:823). Up toparallelismworkers — capped bycfg.tournaments.execute_max_parallel_tasksand the host probe in_resolve_execute_parallelism— are spawned for runnable tasks (deps terminal, files non-overlapping with in-flight). Whencfg.cross_phase_parallelism_enabledis True,_execute_cross_phase_dag(src/orchestrator/execute_phase.py:989) widens the pool to span every phase: tasks from phase N+1 may begin while phase N's tail is still in-flight, providedTask.depends_onis terminal and files are disjoint with every in-flight task. Phase-review still fires per-phase, usingPhase.end_checkpoint_commit(captured at the moment all tasks in the phase reach a terminal state) so phase N+1's concurrent commits don't pollute phase N's review diff range. - Speculative execution (v0.21.0 B2, opt-in): With
cfg.speculative_execution_enabled, the cross-phase dispatcher may opportunistically start ONE child task per phase while its parent is still in-flight (seeSpeculative execution + rollback handlerbelow). On parent failure the speculative worktree is reset to baseline and the child re-queued as pending; on success no extra step runs. - WorktreePool warm-start (v0.21.0 A1, opt-in): With
cfg.worktree_pool_enabled,WorktreePool.cold_start(src/orchestrator/worktree_pool.py:108) pre-createsparallelismworktrees concurrently at execute-phase entry and recycles them viagit reset --hard <baseline> && git clean -fdxonreleaseinstead of payinggit worktree addcost on every dispatch. - LLM latency dominates: Each adapter call takes 10-120 seconds depending on the model and task complexity. The orchestrator overhead (state transitions, evidence writes) is negligible in comparison.
- Tournament cost multiplier: A plan tournament adds
max_rounds * (1 critic + 1 architect_b + 1 synthesizer + num_judges judge)LLM calls. An impl tournament adds the same plusnum_rounds * (1 developer + 1 test_engineer)calls per variant. - Worktree creation:
git worktree addis fast (< 1 second for typical repos). Cleanup viagit worktree removeis also fast. - Inline mode latency: The suspend/resume cycle adds human-in-the-loop time. The orchestrator itself adds no overhead -- it writes state and exits immediately.
The orchestrator is a library package under src/orchestrator/. It is consumed by the CLI module.
| Command | Description |
|---|---|
autodev run --platform <P> "<intent>" |
Runs plan() then execute() |
autodev plan "<intent>" |
Runs plan() only |
autodev execute [--task-id T] |
Runs execute() for all or one task |
autodev resume |
Calls resume() |
autodev status |
Calls status() and prints JSON |
N/A -- this is the initial implementation.
| Event | Key Fields | Description |
|---|---|---|
orchestrator.plan.start |
intent_bytes |
Plan phase begins |
orchestrator.plan.done |
plan_id, phases |
Plan phase complete |
orchestrator.execute.start |
task_id |
Execute phase begins |
orchestrator.execute.done |
processed, complete, blocked |
Execute phase complete |
plan_phase.spec_written |
bytes |
Spec file written |
plan_phase.parse_failed_retrying |
err |
First parse attempt failed |
plan_phase.tournament_applied |
pre_bytes, post_bytes |
Tournament refined the plan |
plan_phase.approved |
plan_id, phases, tasks |
Plan saved to ledger |
execute_phase.developer_failed |
task_id, err |
Developer invocation failed |
execute_phase.qa_gate_failed |
task_id, details |
QA gate returned failure |
execute_phase.review_needs_changes |
task_id, verdict, issues |
Reviewer not satisfied |
execute_phase.tests_failed |
task_id, failed, total |
Test failures detected |
execute_phase.retry_exhausted |
task_id, retry, reason |
Retry limit reached |
execute_phase.guardrail_exceeded |
task_id, reason |
Guardrail limit hit |
execute_phase.task_complete |
task_id |
Task finished successfully |
plan_tournament.start |
tournament_id, model, num_judges, max_rounds |
Plan tournament begins |
plan_tournament.done |
tournament_id, passes, winner_last |
Plan tournament complete |
impl_tournament.start |
tournament_id, task_id, model, num_judges |
Impl tournament begins |
impl_tournament.done |
tournament_id, task_id, passes, winner_last |
Impl tournament complete |
| Artifact | Location | Description |
|---|---|---|
| Spec file | .autodev/spec.md |
User intent captured at plan start |
| Plan file | .autodev/plan.json |
Structured plan with phases and tasks |
| Evidence files | .autodev/evidence/<task_id>-<kind>.json |
Per-step evidence records |
| Patch files | .autodev/evidence/<task_id>.patch |
Developer diffs |
| Tournament artifacts | .autodev/tournaments/<id>/ |
Per-pass history, judge scores, worktree diffs |
| Inline state | .autodev/inline-state.json |
Suspend checkpoint (cleared on resume) |
| Delegation files | .autodev/delegations/<task_id>-<role>.md |
Inline mode delegation instructions |
| Response files | .autodev/responses/<task_id>-<role>.json |
Inline mode agent responses |
autodev status returns:
{
"plan": {
"plan_id": "plan-abc123def456",
"spec_hash": "a1b2c3d4e5f6g7h8",
"phases": 3,
"title": "Feature X Implementation",
"created_at": "2026-04-17T10:00:00+00:00",
"updated_at": "2026-04-17T12:30:00+00:00"
},
"session_id": "sess-abc123def456",
"tasks": [
{"id": "1.1", "phase_id": "1", "title": "...", "status": "complete", ...},
{"id": "1.2", "phase_id": "1", "title": "...", "status": "in_progress", ...}
],
"totals": {
"pending": 5, "in_progress": 1, "complete": 3, "blocked": 0, "total": 9
}
}| Operation | LLM Calls | Notes |
|---|---|---|
| Plan phase (no tournament) | 3 | explorer + domain_expert + architect |
| Plan phase (architect retry) | 4 | + 1 architect retry on parse failure |
| Plan tournament (per round) | 3 + num_judges | critic_t + architect_b + synthesizer + N judges |
| Execute task (happy path) | 3 | developer + reviewer + test_engineer |
| Execute task (1 retry) | 6 | 2x (developer + reviewer + test_engineer) |
| Execute task (exhausted, 3 retries) | 10 | 3x developer + 3x QA/reviewer/test + critic_sounding_board |
| Impl tournament (per round) | 3 + num_judges + 2 per variant | critic_t + architect_b + synthesizer + N judges + developer + test_engineer per variant |
| Full run (3 phases, 9 tasks, no retries) | 30 | 3 (plan) + 27 (9 tasks x 3 calls) |
Cost reduction strategies:
auto_disable_for_models: ["opus"]skips tournaments when using expensive models.qa_retry_limit: 3caps the maximum number of retries per task.guardrails.max_tool_calls_per_task: 60prevents runaway agent loops.guardrails.cost_budget_usd_per_plan(optional) enables per-plan cost caps.
- Phase 8: Full QA gate enforcement. The
_run_qa_gates()function will wire real gate results into the FSM with per-gate retry logic. - Streaming progress. Real-time progress reporting via SSE or WebSocket for long-running plan/execute operations.
- Plan revision loop. A
critic_tgate before plan approval (currently handled by the plan tournament) with explicit revision cycles. - Incremental re-planning. On task failure patterns, re-invoke the architect to revise the remaining plan.
- Should the orchestrator support partial plan execution (e.g., "execute only Phase 2")?
- Should the inline adapter support a "multi-delegation" mode where multiple delegation files are written before suspend?
- Should the
_run_qa_gates()function run gates in parallel (currently sequential)? - Should the worktree manager support branch-based isolation in addition to detached worktrees?
- ADR-001: Stateless subprocesses -- every agent invocation is a fresh subprocess.
- ADR-006: Platform adapter abstraction -- the orchestrator delegates to
PlatformAdapter.execute(). - ADR-008: Deterministic FSM orchestration -- all state transitions are explicit and validated.
| Date | Author | Changes |
|---|---|---|
| 2026-04-17 | Mohamed Ameen | Initial draft |
| 2026-05-09 | Mohamed Ameen | v0.21.1: documented WorktreePool warm-start (v0.21.0 A1), cross-phase parallelism dispatcher (v0.21.0 B1), and speculative execution + rollback handler (v0.21.0 B2). Rewrote performance section to reflect within-phase worker pool and cross-phase mode (replacing the obsolete "sequential task execution" claim). Added new opt-in config knobs. Added speculative_started / speculative_rolled_back / speculative_committed ledger ops. |