Lab Notes
Research

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

Loading diagram…
Mission DAG: F0 seeds F1, F1 fans out to F2/F4/Y1 and optionally P3, Y1 feeds Y2, everything feeds F6.

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_synthesis flag is set in the run configuration.

Phase Reference

F0 — Init

AspectDetail
Filescout/phases/f0_init.py
Statussucceeded only. No retry.
NeedsNothing (root phase).
Producestask.json (typed: Task)
InputsThe raw user query (string) and the mission_id (string).
OutputsA run directory under runs_dir/<mission_id>/ with task.json and a phase.json checkpoint.
ParallelismSequential (always runs first).
Skip ruleNever 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

AspectDetail
Filescout/phases/f1_candidates.py
Statussucceeded, succeeded_partial, or failed_terminal.
Needstask.json from F0.
Producescandidates.json (typed: CandidateList)
Inputstask.json. The phase uses the parsed topic and location to build search queries.
OutputsA list of Candidate records, each with a Source. Deduplicated and normalized.
ParallelismSequential (after F0).
Skip ruleNever 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

AspectDetail
Filescout/phases/f2_reviews.py
Statussucceeded, succeeded_partial, or failed_retryable.
Needscandidates.json from F1.
Producesreviews.json (typed: ReviewSummary)
InputsEach candidate's URL and primary identifier.
OutputsA ReviewSummary per candidate with sentiment and topic aggregates.
ParallelismParallel (parallel_group: 1).
Retry2 attempts.
Skip ruleSkipped if candidates.json is empty or has fewer than 1 candidate.

F4 — Price

AspectDetail
Filescout/phases/f4_price.py
Statussucceeded, succeeded_partial, or failed_retryable.
Needscandidates.json from F1.
Producesprices.json (typed: PriceSeries)
InputsEach candidate's identifier and the run's date range.
OutputsA PriceSeries per candidate with snapshots and statistics.
ParallelismParallel (parallel_group: 1).
Retry2 attempts.
Skip ruleSkipped if the query has no budget_max constraint.
AspectDetail
Filescout/phases/y1_youtube.py
Statussucceeded, succeeded_partial, or failed_retryable.
Needscandidates.json from F1.
Producesyoutube_search.json (typed: list of video metadata)
InputsEach candidate's name and the query topic.
OutputsA list of YouTube video records per candidate.
ParallelismParallel (parallel_group: 1).
Retry1 attempt (YouTube rate limits are strict).
Skip ruleSkipped if enable_youtube_research is false.

Y2 — YouTube Extract

AspectDetail
Filescout/phases/y2_extract.py
Statussucceeded, succeeded_partial, or failed_retryable.
Needsyoutube_search.json from Y1.
Producestranscripts.json (typed: list of Transcript)
InputsThe YouTube URLs from Y1.
OutputsTranscripts and chunked segments per video.
ParallelismSequential (after Y1).
Retry1 attempt.
Skip ruleSkipped if Y1 produced no results.

P3 — Perplexity Synthesis (Optional)

AspectDetail
Filescout/phases/p3_perplexity_synthesis.py
Statussucceeded or failed_terminal.
Needscandidates.json from F1.
Producessynthesis.json (typed: list of narrative summaries)
InputsThe top N candidates (default 5).
OutputsA narrative summary per candidate written by Perplexity.
ParallelismParallel (parallel_group: 1) if enabled.
Retry2 attempts.
Skip ruleSkipped 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

AspectDetail
Filescout/phases/f6_report.py
Statussucceeded only. No retry.
NeedsAll phase artifacts (F1, F2, F4, Y2, P3).
Producesreport.md (markdown, not typed)
InputsEvery artifact under the run directory.
OutputsThe final markdown report.
ParallelismSequential (always runs last).
Skip ruleNever 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

StatusMeaningAction
pendingThe phase has not started yet.Wait for the orchestrator.
runningThe phase is currently executing.Wait.
succeededThe phase completed and produced all expected outputs.Mark downstream phases ready.
succeeded_partialThe phase completed but some outputs are missing.Mark downstream ready; flag in the report.
failed_retryableThe phase failed but can be retried.Orchestrator retries.
failed_terminalThe phase failed and cannot be retried.Orchestrator stops the mission; notify the user.
skippedThe phase was intentionally not executed.Mark downstream as blocked_missing_input.
blocked_missing_inputThe phase could not start because an input is missing.Skip; mark downstream.
staleThe 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:

Phaseallow_partialRationale
F0falseWithout F0, no run.
F1trueSome candidates are better than none.
F2trueReviews are nice-to-have, not critical.
F4truePricing is critical but partial is better than nothing.
Y1trueYouTube coverage is optional.
Y2trueY2 cannot succeed without Y1.
P3trueSynthesis is optional.
F6falseReport 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(...).

from scout.orchestrator.dag import PhaseConfig, PhaseDAG, PhaseKind
 
dag = PhaseDAG([
    PhaseConfig(name="F0", needs=[], produces=["task.json"]),
    PhaseConfig(name="F1", needs=["task.json"], produces=["candidates.json"]),
    PhaseConfig(name="F2", needs=["candidates.json"], produces=["reviews.json"],
                parallel_group=1, retry=2, allow_partial=True),
    PhaseConfig(name="F4", needs=["candidates.json"], produces=["prices.json"],
                parallel_group=1, retry=2, allow_partial=True),
    PhaseConfig(name="Y1", needs=["candidates.json"], produces=["youtube_search.json"],
                parallel_group=1, retry=1, allow_partial=True),
    PhaseConfig(name="Y2", needs=["youtube_search.json"], produces=["transcripts.json"],
                retry=1, allow_partial=True),
    PhaseConfig(name="P3", needs=["candidates.json"], produces=["synthesis.json"],
                parallel_group=1, retry=2, allow_partial=True),
    PhaseConfig(name="F6", needs=["reviews.json", "prices.json", "transcripts.json",
                                  "synthesis.json"], produces=["report.md"]),
])

Custom DAGs are not used in production but are useful for testing individual phases or for research workflows that need a different shape.

See Also

On this page