Mission DAG
El DAG de fases del research framework: qué fases se ejecutan, en qué orden, qué produce cada una y de qué depende.
Propósito
Esta página es la referencia canónica del grafo de fases. El DAG es la estructura que ejecuta el orquestador. El framework de alto nivel está en Framework; el ciclo de vida de las fases (input, output, retry, failure) está en Mission Lifecycle.
Topología
Tres cosas a notar:
- F1 es el punto de fan-out. Todas las demás fases dependen de los candidatos producidos por F1. Esto es deliberado: fuerza al framework a operar sobre nombres de candidatos reales y normalizados en lugar de sobre la query cruda.
- F2, F4, Y1 se ejecutan en paralelo (
parallel_group: 1). No comparten estado y pueden ejecutarse concurrentemente. - P3 es opcional. Solo se ejecuta si el flag
enable_perplexity_synthesisestá activo en la configuración del run.
Referencia de fases
F0 — Init
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/f0_init.py |
| Status | solo succeeded. Sin retry. |
| Necesita | Nada (fase raíz). |
| Produce | task.json (tipado: Task) |
| Inputs | La query cruda del usuario (string) y el mission_id (string). |
| Outputs | Un directorio de run bajo runs_dir/<mission_id>/ con task.json y un checkpoint phase.json. |
| Paralelismo | Sequential (siempre corre primero). |
| Regla de skip | Nunca se omite. |
F0 parsea la query cruda en un ParsedQuery (topic, location, period, budget, constraints) usando un parser basado en keywords. La estructura parseada es lo que F1 usa para construir las queries de búsqueda de candidatos.
F1 — Candidates
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/f1_candidates.py |
| Status | succeeded, succeeded_partial o failed_terminal. |
| Necesita | task.json de F0. |
| Produce | candidates.json (tipado: CandidateList) |
| Inputs | task.json. La fase usa el topic y location parseados para construir queries de búsqueda. |
| Outputs | Una lista de registros Candidate, cada uno con un Source. Deduplicados y normalizados. |
| Paralelismo | Sequential (tras F0). |
| Regla de skip | Nunca se omite. |
F1 es la fase más importante. Su salida es sobre la que opera cada fase downstream. La fase llama a los adaptadores de búsqueda configurados en secuencia (Tavily → DuckDuckGo → Google), fusiona resultados, deduplica por URL y título normalizado, valida cada candidato contra el esquema Candidate y escribe el candidates.json tipado en el directorio del run.
F2 — Reviews
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/f2_reviews.py |
| Status | succeeded, succeeded_partial o failed_retryable. |
| Necesita | candidates.json de F1. |
| Produce | reviews.json (tipado: ReviewSummary) |
| Inputs | La URL de cada candidato y su identificador primario. |
| Outputs | Un ReviewSummary por candidato con agregados de sentimiento y temas. |
| Paralelismo | Parallel (parallel_group: 1). |
| Retry | 2 intentos. |
| Regla de skip | Se omite si candidates.json está vacío o tiene menos de 1 candidato. |
F4 — Price
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/f4_price.py |
| Status | succeeded, succeeded_partial o failed_retryable. |
| Necesita | candidates.json de F1. |
| Produce | prices.json (tipado: PriceSeries) |
| Inputs | El identificador de cada candidato y el rango de fechas del run. |
| Outputs | Un PriceSeries por candidato con snapshots y estadísticas. |
| Paralelismo | Parallel (parallel_group: 1). |
| Retry | 2 intentos. |
| Regla de skip | Se omite si la query no tiene restricción budget_max. |
Y1 — YouTube Search
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/y1_youtube.py |
| Status | succeeded, succeeded_partial o failed_retryable. |
| Necesita | candidates.json de F1. |
| Produce | youtube_search.json (tipado: lista de metadatos de vídeo) |
| Inputs | El nombre de cada candidato y el topic de la query. |
| Outputs | Una lista de registros de vídeo de YouTube por candidato. |
| Paralelismo | Parallel (parallel_group: 1). |
| Retry | 1 intento (los rate limits de YouTube son estrictos). |
| Regla de skip | Se omite si enable_youtube_research es false. |
Y2 — YouTube Extract
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/y2_extract.py |
| Status | succeeded, succeeded_partial o failed_retryable. |
| Necesita | youtube_search.json de Y1. |
| Produce | transcripts.json (tipado: lista de Transcript) |
| Inputs | Las URLs de YouTube de Y1. |
| Outputs | Transcripciones y segmentos chunked por vídeo. |
| Paralelismo | Sequential (tras Y1). |
| Retry | 1 intento. |
| Regla de skip | Se omite si Y1 no produjo resultados. |
P3 — Perplexity Synthesis (Opcional)
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/p3_perplexity_synthesis.py |
| Status | succeeded o failed_terminal. |
| Necesita | candidates.json de F1. |
| Produce | synthesis.json (tipado: lista de resúmenes narrativos) |
| Inputs | Los top N candidatos (default 5). |
| Outputs | Un resumen narrativo por candidato escrito por Perplexity. |
| Paralelismo | Parallel (parallel_group: 1) si está habilitado. |
| Retry | 2 intentos. |
| Regla de skip | Se omite si enable_perplexity_synthesis es false. |
P3 es la única fase que usa un servicio de LLM de pago (Perplexity) directamente dentro del DAG. Es opt-in porque cuesta créditos de API. La salida es prosa narrativa que el informe final (F6) puede incluir verbatim o parafrasear. P3 está documentado por separado en External Providers porque el patrón de adaptador, el token pool y el request journal viven todos allí.
F6 — Report
| Aspecto | Detalle |
|---|---|
| Fichero | scout/phases/f6_report.py |
| Status | solo succeeded. Sin retry. |
| Necesita | Todos los artefactos de fase (F1, F2, F4, Y2, P3). |
| Produce | report.md (markdown, no tipado) |
| Inputs | Todos los artefactos bajo el directorio del run. |
| Outputs | El informe markdown final. |
| Paralelismo | Sequential (siempre corre el último). |
| Regla de skip | Nunca se omite. |
F6 agrega todos los artefactos en un único informe markdown con una estructura estable: resumen ejecutivo, candidatos, reseñas, pricing, cobertura de vídeo, síntesis de fuentes (si P3 corrió) y una lista deduplicada de fuentes. El formato completo está en Artifacts → report.md.
Estados de fallo
| Status | Significado | Acción |
|---|---|---|
pending | La fase no ha empezado todavía. | Esperar al orquestador. |
running | La fase se está ejecutando. | Esperar. |
succeeded | La fase se completó y produjo todas las salidas esperadas. | Marcar las fases downstream como listas. |
succeeded_partial | La fase se completó pero faltan algunas salidas. | Marcar downstream como listas; marcar en el informe. |
failed_retryable | La fase falló pero se puede reintentar. | El orquestador reintenta. |
failed_terminal | La fase falló y no se puede reintentar. | El orquestador detiene la misión; notifica al usuario. |
skipped | La fase no se ejecutó intencionadamente. | Marcar downstream como blocked_missing_input. |
blocked_missing_input | La fase no pudo empezar porque falta un input. | Omitir; marcar downstream. |
stale | El input de la fase se actualizó después de que la fase corriera. | Re-ejecutar. |
Ejecución paralela
Las fases paralelas (F2, F4, Y1) se lanzan con un ThreadPoolExecutor. El número de workers por defecto es SCOUTE_PARALLEL_WORKERS (default 3). El runner coordina de forma que todas las fases paralelas arrancan cuando F1 termina, cada fase paralela escribe su propio artefacto de forma independiente, y el runner espera a que todas las fases paralelas alcancen un estado terminal antes de arrancar Y2 (si Y1 fue paralela) y F6.
El flag allow_partial de cada fase controla si se acepta una completion parcial:
| Fase | allow_partial | Justificación |
|---|---|---|
| F0 | false | Sin F0, no hay run. |
| F1 | true | Algunos candidatos son mejor que ninguno. |
| F2 | true | Las reseñas son nice-to-have, no críticas. |
| F4 | true | El pricing es crítico pero parcial es mejor que nada. |
| Y1 | true | La cobertura de YouTube es opcional. |
| Y2 | true | Y2 no puede succeed sin Y1. |
| P3 | true | La síntesis es opcional. |
| F6 | false | El informe siempre se produce si la misión corre. |
Checkpointing
El DAG es totalmente resumible. Tras cada fase, el StateStore escribe el estado actual a <run_dir>/phase.json. Si el orquestador crashea, la siguiente llamada a run_mission cargará el estado, identificará la siguiente fase a ejecutar según el DAG y el estado, y continuará desde ahí. El modelo completo de checkpointing está en Mission Lifecycle → Checkpointing.
Construcción del DAG
El DAG se construye en scout/orchestrator/dag.py. El DAG por defecto se construye en tiempo de import. Se pueden construir DAGs custom pasando una lista de PhaseConfig a PhaseDAG(...).
Los DAGs custom no se usan en producción pero son útiles para testear fases individuales o para flujos de investigación que necesiten una forma distinta.