Mission DAG
The phase DAG of the research framework — which phases run, in what order, what each one produces, and what it depends on.
Purpose
This page is the canonical reference for the phase graph. The DAG is the structure that the orchestrator executes. The high-level framework is in Framework; the phase lifecycle (input, output, retry, failure) is in Mission Lifecycle.
Topology
Three things to notice:
- F1 is the fan-out point. All other phases depend on the candidates produced by F1. This is deliberate: it forces the framework to operate on real, normalized candidate names rather than on the raw query.
- F2, F4, Y1 run in parallel (
parallel_group: 1). They share no state and can be executed concurrently. - P3 is optional. It runs only if the
enable_perplexity_synthesisflag is set in the run configuration.
Phase Reference
F0 — Init
| Aspect | Detail |
|---|---|
| File | scout/phases/f0_init.py |
| Status | succeeded only. No retry. |
| Needs | Nothing (root phase). |
| Produces | task.json (typed: Task) |
| Inputs | The raw user query (string) and the mission_id (string). |
| Outputs | A run directory under runs_dir/<mission_id>/ with task.json and a phase.json checkpoint. |
| Parallelism | Sequential (always runs first). |
| Skip rule | Never skipped. |
F0 parses the raw query into a ParsedQuery (topic, location, period, budget, constraints) using a keyword-based parser. The parsed structure is what F1 uses to build candidate search queries.
F1 — Candidates
| Aspect | Detail |
|---|---|
| File | scout/phases/f1_candidates.py |
| Status | succeeded, succeeded_partial, or failed_terminal. |
| Needs | task.json from F0. |
| Produces | candidates.json (typed: CandidateList) |
| Inputs | task.json. The phase uses the parsed topic and location to build search queries. |
| Outputs | A list of Candidate records, each with a Source. Deduplicated and normalized. |
| Parallelism | Sequential (after F0). |
| Skip rule | Never skipped. |
F1 is the most important phase. Its output is what every downstream phase operates on. The phase calls the configured search adapters in sequence (Tavily → DuckDuckGo → Google), merges results, deduplicates by URL and normalized title, validates each candidate against the Candidate schema, and writes the typed candidates.json to the run directory.
F2 — Reviews
| Aspect | Detail |
|---|---|
| File | scout/phases/f2_reviews.py |
| Status | succeeded, succeeded_partial, or failed_retryable. |
| Needs | candidates.json from F1. |
| Produces | reviews.json (typed: ReviewSummary) |
| Inputs | Each candidate's URL and primary identifier. |
| Outputs | A ReviewSummary per candidate with sentiment and topic aggregates. |
| Parallelism | Parallel (parallel_group: 1). |
| Retry | 2 attempts. |
| Skip rule | Skipped if candidates.json is empty or has fewer than 1 candidate. |
F4 — Price
| Aspect | Detail |
|---|---|
| File | scout/phases/f4_price.py |
| Status | succeeded, succeeded_partial, or failed_retryable. |
| Needs | candidates.json from F1. |
| Produces | prices.json (typed: PriceSeries) |
| Inputs | Each candidate's identifier and the run's date range. |
| Outputs | A PriceSeries per candidate with snapshots and statistics. |
| Parallelism | Parallel (parallel_group: 1). |
| Retry | 2 attempts. |
| Skip rule | Skipped if the query has no budget_max constraint. |
Y1 — YouTube Search
| Aspect | Detail |
|---|---|
| File | scout/phases/y1_youtube.py |
| Status | succeeded, succeeded_partial, or failed_retryable. |
| Needs | candidates.json from F1. |
| Produces | youtube_search.json (typed: list of video metadata) |
| Inputs | Each candidate's name and the query topic. |
| Outputs | A list of YouTube video records per candidate. |
| Parallelism | Parallel (parallel_group: 1). |
| Retry | 1 attempt (YouTube rate limits are strict). |
| Skip rule | Skipped if enable_youtube_research is false. |
Y2 — YouTube Extract
| Aspect | Detail |
|---|---|
| File | scout/phases/y2_extract.py |
| Status | succeeded, succeeded_partial, or failed_retryable. |
| Needs | youtube_search.json from Y1. |
| Produces | transcripts.json (typed: list of Transcript) |
| Inputs | The YouTube URLs from Y1. |
| Outputs | Transcripts and chunked segments per video. |
| Parallelism | Sequential (after Y1). |
| Retry | 1 attempt. |
| Skip rule | Skipped if Y1 produced no results. |
P3 — Perplexity Synthesis (Optional)
| Aspect | Detail |
|---|---|
| File | scout/phases/p3_perplexity_synthesis.py |
| Status | succeeded or failed_terminal. |
| Needs | candidates.json from F1. |
| Produces | synthesis.json (typed: list of narrative summaries) |
| Inputs | The top N candidates (default 5). |
| Outputs | A narrative summary per candidate written by Perplexity. |
| Parallelism | Parallel (parallel_group: 1) if enabled. |
| Retry | 2 attempts. |
| Skip rule | Skipped if enable_perplexity_synthesis is false. |
P3 is the only phase that uses a paid LLM service (Perplexity) directly inside the DAG. It is opt-in because it costs API credits. The output is narrative prose that the final report (F6) can include verbatim or paraphrase. P3 is documented separately in External Providers because the adapter pattern, the token pool, and the request journal all live there.
F6 — Report
| Aspect | Detail |
|---|---|
| File | scout/phases/f6_report.py |
| Status | succeeded only. No retry. |
| Needs | All phase artifacts (F1, F2, F4, Y2, P3). |
| Produces | report.md (markdown, not typed) |
| Inputs | Every artifact under the run directory. |
| Outputs | The final markdown report. |
| Parallelism | Sequential (always runs last). |
| Skip rule | Never skipped. |
F6 aggregates all artifacts into a single markdown report with a stable structure: executive summary, candidates, reviews, pricing, video coverage, source synthesis (if P3 ran), and a deduplicated source list. The full format is in Artifacts → report.md.
Failure Status
| Status | Meaning | Action |
|---|---|---|
pending | The phase has not started yet. | Wait for the orchestrator. |
running | The phase is currently executing. | Wait. |
succeeded | The phase completed and produced all expected outputs. | Mark downstream phases ready. |
succeeded_partial | The phase completed but some outputs are missing. | Mark downstream ready; flag in the report. |
failed_retryable | The phase failed but can be retried. | Orchestrator retries. |
failed_terminal | The phase failed and cannot be retried. | Orchestrator stops the mission; notify the user. |
skipped | The phase was intentionally not executed. | Mark downstream as blocked_missing_input. |
blocked_missing_input | The phase could not start because an input is missing. | Skip; mark downstream. |
stale | The phase's input was updated after the phase ran. | Re-run. |
Parallel Execution
The parallel phases (F2, F4, Y1) are launched with a ThreadPoolExecutor. The default worker count is SCOUTE_PARALLEL_WORKERS (default 3). The runner coordinates so that all parallel phases start when F1 finishes, each parallel phase writes its own artifact independently, and the runner waits for all parallel phases to reach a terminal state before starting Y2 (if Y1 was parallel) and F6.
The allow_partial flag on each phase controls whether a partial completion is acceptable:
| Phase | allow_partial | Rationale |
|---|---|---|
| F0 | false | Without F0, no run. |
| F1 | true | Some candidates are better than none. |
| F2 | true | Reviews are nice-to-have, not critical. |
| F4 | true | Pricing is critical but partial is better than nothing. |
| Y1 | true | YouTube coverage is optional. |
| Y2 | true | Y2 cannot succeed without Y1. |
| P3 | true | Synthesis is optional. |
| F6 | false | Report is always produced if the mission runs. |
Checkpointing
The DAG is fully resumable. After every phase, the StateStore writes the current state to <run_dir>/phase.json. If the orchestrator crashes, the next run_mission call will load the state, identify the next phase to run based on the DAG and the state, and continue from there. The full checkpointing model is in Mission Lifecycle → Checkpointing.
DAG Construction
The DAG is built in scout/orchestrator/dag.py. The default DAG is constructed at import time. Custom DAGs can be constructed by passing a list of PhaseConfig to PhaseDAG(...).
Custom DAGs are not used in production but are useful for testing individual phases or for research workflows that need a different shape.