Skip to content

Assync Mission Orchestrator

Purpose

Replaces the manual "copy result between agents" workflow with a structured, auditable pipeline:

Mission → Orchestrator → Structured Agent Runs → Findings → Synthesis → Human Decision Queue

Mission Model (missions/models.py)

A Mission is a strictly bounded, typed unit of work: id, project_id, title, objective, scope_include/scope_exclude, authorization_type, required_roles, stages, state, risk_level, acceptance_criteria, allow_optional_run_failure, plus an append-only transition_history. Created and validated via missions/service.py::create_mission, which rejects unknown projects, unknown or non-executable roles, missing acceptance criteria, and authorization_type=ABORT at creation time (missions/validation.py).

State Machine (missions/state_machine.py)

READY → ACTIVE → WAITING_FOR_AUTHORIZATION → ACTIVE → COMPLETED
READY → ABORTED
ACTIVE → BLOCKED | ABORTED
WAITING_FOR_AUTHORIZATION → ABORTED
BLOCKED → ACTIVE | ABORTED

COMPLETED and ABORTED are terminal — no transition is defined out of either. A transition to COMPLETED is rejected if unresolved blocking findings remain (has_unresolved_blocking_findings). Every transition is recorded with previous/next state, timestamp, actor, reason, and an optional evidence reference — never edited after the fact.

Design note: the canonical transition table has no WAITING_FOR_AUTHORIZATION → BLOCKED path and no separate CHANGES_REQUESTED mission state (that concept exists only for decisions, see decision-queue.md). When a human rejects a risk-acceptance decision, the mission is explicitly transitioned to ABORTED — the only valid, already-defined "does not continue" state reachable from WAITING_FOR_AUTHORIZATION. See tests/test_e2e_mvp.py::BlockedEndToEndScenarioTest for the full, verified sequence.

Workflow (orchestrator/service.py::start_mission)

  1. Verify the mission is READY and the supplied authorization matches mission.authorization_type and is still usable.
  2. Acquire the mission lock (storage/locking.py).
  3. Select roles (orchestrator/selection.py::select_roles) — mission-required roles plus deterministic, risk-driven policy additions (e.g. medium+ risk adds technical_reviewer). No LLM is involved in role selection.
  4. Detect separation-of-duty violations across the resolved role→adapter assignments.
  5. Transition to ACTIVE.
  6. Execute each selected role's configured adapter sequentially, in role-ID order — the MVP never simulates background/parallel execution.
  7. Normalize findings (findings/normalization.py) and run deterministic synthesis (synthesis/engine.py).
  8. If synthesis reports blockers or required human decisions, create a Decision and transition to WAITING_FOR_AUTHORIZATION. Otherwise transition straight to COMPLETED.
  9. Release the mission lock in a finally block — even on an unhandled exception, which additionally transitions the mission to BLOCKED before re-raising.

It never commits, pushes, merges, or releases — no code path in this MVP calls a Git write operation.

Locking (storage/locking.py)

One lock per mission, containing mission ID, PID, hostname, owner, and creation timestamp. A held lock is never silently stolen — acquire_lock always raises LockConflictError if a lock file exists, even if it looks stale; only an explicit assync lock clear --force removes it (is_stale() informs that decision, it never triggers it automatically).

MVP Limitations

  • Execution is synchronous and single-process; the model field for "parallel" execution exists conceptually in the stage list but has no parallel implementation.
  • Retries default to zero and are not yet exposed via CLI.
  • There is no persisted "project-level default risk policy" — risk level is set per mission at creation time.