@@ -821,50 +821,63 @@ def _run_iteration_inner(
821821 detail = latex_result .detail ,
822822 )
823823
824- # Skeptic paper review: after the Writer has produced a fresh
825- # paper.tex, hand it back to the Skeptic to surface citation
826- # gaps, mismatched cites (where the cited paper's abstract
827- # doesn't support the surrounding claim), and prose without
828- # any artifact backing. The critiques are stored as critique
829- # artifacts so the Director sees them when planning the next
830- # iteration; we deliberately do NOT loop the Writer in this
831- # iteration — paper-review feedback is asynchronous, one
832- # iteration of latency, to keep budget predictable.
824+ # Post-Writer review fan-out. Three reads-only-on-publish_pool
825+ # passes that have no data dependency on each other:
826+ # * Skeptic paper review (citation gaps, prose without
827+ # artifact backing) — comments on paper.tex.
828+ # * Analytical math review (dimensional / index / notation
829+ # errors in the equations).
830+ # * Presentation memo (briefing-friendly summary of the
831+ # curated artifacts).
832+ # All three are best-effort, all three only ever WRITE their
833+ # own outputs (no shared mutable state), so we run them in
834+ # parallel when options.parallel is on. Wall-clock saving is
835+ # roughly two LLM round-trips per iteration on a real API.
836+ # The Skeptic paper review feedback is intentionally one-
837+ # iteration-asynchronous — the Writer is NOT looped here.
833838 self ._check_cancel ()
834- paper_critiques = self ._run_skeptic_paper_review (project , publish_pool )
839+ paper_critiques : list [ResearchArtifact ] = []
840+ math_critiques : list [ResearchArtifact ] = []
841+ presentation_memo = ""
842+ if self .options .parallel :
843+ with ThreadPoolExecutor (max_workers = 3 ) as executor :
844+ fut_paper = executor .submit (
845+ self ._run_skeptic_paper_review , project , publish_pool ,
846+ )
847+ fut_math = executor .submit (
848+ self ._run_analytical_math_review , project , publish_pool ,
849+ )
850+ fut_memo = executor .submit (
851+ self ._maybe_run_presentation_memo ,
852+ project .research_question , curated , ranked ,
853+ )
854+ paper_critiques = fut_paper .result () or []
855+ math_critiques = fut_math .result () or []
856+ presentation_memo = fut_memo .result () or ""
857+ else :
858+ paper_critiques = self ._run_skeptic_paper_review (
859+ project , publish_pool ,
860+ ) or []
861+ math_critiques = self ._run_analytical_math_review (
862+ project , publish_pool ,
863+ ) or []
864+ presentation_memo = self ._maybe_run_presentation_memo (
865+ project .research_question , curated , ranked ,
866+ ) or ""
867+
835868 if paper_critiques :
836869 state .artifacts_all .extend (paper_critiques )
837870 self ._emit (
838871 "skeptic_paper_review_done" ,
839872 critiques = len (paper_critiques ),
840873 )
841-
842- # Math review: parallel pass to the Skeptic's, but specifically
843- # for equation-level structural errors (dimensional, index,
844- # boundary, notation). Different agent because math sanity is
845- # the Analytical role's specialty, not the Skeptic's.
846- self ._check_cancel ()
847- math_critiques = self ._run_analytical_math_review (project , publish_pool )
848874 if math_critiques :
849875 state .artifacts_all .extend (math_critiques )
850876 self ._emit (
851877 "analytical_math_review_done" ,
852878 critiques = len (math_critiques ),
853879 )
854880
855- # Presentation memo: if the architecture has an enabled Presentation
856- # node and the curator can reach it, ask it to draft a concise memo
857- # over the curated report. The memo is added to the iteration
858- # briefing so the user reading briefings/iteration-N.md sees a
859- # human-readable summary, not just metadata. Earlier versions of
860- # this flow only ran Presentation in the legacy single-shot run()
861- # path — this is the iteration-mode equivalent. See
862- # docs/graph-workflow-spec.md §7 (default architecture).
863- self ._check_cancel ()
864- presentation_memo = self ._maybe_run_presentation_memo (
865- project .research_question , curated , ranked
866- )
867-
868881 store .save_artifacts (project .id , iteration .id , state .artifacts_all )
869882 store .save_threads (state .threads )
870883 if scheduled_task is not None :
0 commit comments