Skip to content

Commit 82a1176

Browse files
HaibaraAi137claude
andcommitted
Revert fast profile; keep post-Writer parallel fan-out
The fast profile was redundant — users who want to skip Presentation memo or other best-effort passes can already disable those agents through the architecture editor. Carrying both a "profile" knob and per-agent toggles meant two ways to express the same thing. Removed: WorkflowOptions.profile, the six branches in workflow.py that switched on it, the cadence parameters on _strategist_should_run / _rolling_summary_should_run, and tests/test_fast_profile.py. Kept: the parallel fan-out of Skeptic paper review + Analytical math review + Presentation memo (saves a couple LLM round-trips per iteration on a real API). Replaced the old test file with tests/test_post_writer_parallel.py which only pins the parallel vs serial equivalence — the reason the fan-out is safe to keep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cde5d21 commit 82a1176

3 files changed

Lines changed: 65 additions & 299 deletions

File tree

src/halfseed/workflow.py

Lines changed: 12 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,6 @@ class WorkflowOptions:
124124
enable_latex_gate: bool = True
125125
enable_publishing: bool = True
126126
director_options: DirectorOptions | None = None
127-
# "default" — full pipeline.
128-
# "fast" — skip best-effort passes that don't shape the next
129-
# iteration's input: presentation memo (briefing
130-
# reads fine without it), analytical math review
131-
# (Analytical's specialist round already covers it),
132-
# LaTeX compile gate. Strategist + rolling summary
133-
# cadence stretched to every 5 iterations, library
134-
# refactor to every 10. The trade-off is one
135-
# iteration of extra latency on Strategist
136-
# observations and a paper.tex that may carry a
137-
# LaTeX warning until the user runs latexmk
138-
# manually. Wall-clock saving on a real API is
139-
# roughly 30-50% per iteration.
140-
profile: str = "default"
141127

142128

143129
class ResearchWorkflow:
@@ -708,15 +694,7 @@ def _run_iteration_inner(
708694
# passes, or after the cap exhausts (in which case the final
709695
# failure is the recorded result).
710696
latex_result = None
711-
# Fast profile: skip the LaTeX compile gate + retry loop. The
712-
# paper.tex still gets written, the user can latexmk it
713-
# manually if they care; the gate is mainly a guardrail
714-
# against the Writer producing un-compilable output, which is
715-
# rare and one-iteration-asynchronous to fix anyway.
716-
if (
717-
self.options.enable_latex_gate
718-
and self.options.profile != "fast"
719-
):
697+
if self.options.enable_latex_gate:
720698
latex_result = self._run_latex_gate(project, state, store)
721699
attempt = 0
722700
MAX_LATEX_FIX_RETRIES = 2
@@ -1559,10 +1537,7 @@ def _emit_strategic_review(
15591537
recent_reviews = store.list_strategic_reviews(project.id, limit=1)
15601538
except Exception:
15611539
recent_reviews = []
1562-
cadence = 5 if self.options.profile == "fast" else 3
1563-
if not _strategist_should_run(
1564-
latest_snap, recent_reviews, cadence=cadence,
1565-
):
1540+
if not _strategist_should_run(latest_snap, recent_reviews):
15661541
return
15671542

15681543
director_id = self._agent_id_for_role(ROLE_KIND_DIRECTOR)
@@ -1766,10 +1741,8 @@ def _emit_rolling_summary(
17661741
error, garbage output) skips silently. The legacy prior_summary
17671742
path in `prior_iteration_summary` still works as a fallback.
17681743
"""
1769-
cadence = 5 if self.options.profile == "fast" else 3
17701744
if not _rolling_summary_should_run(
1771-
iteration.iteration_number, project.rolling_summary,
1772-
cadence=cadence,
1745+
iteration.iteration_number, project.rolling_summary
17731746
):
17741747
return
17751748

@@ -2493,13 +2466,8 @@ def _run_analytical_math_review(
24932466
for the Writer to address.
24942467
24952468
Returns empty when there is no Analytical agent, no paper, or
2496-
the LLM call fails — never raises. In ``profile="fast"`` we
2497-
skip this pass entirely; the Analytical specialist already
2498-
scrutinises equations during its own specialist round, so the
2499-
paper-level math sweep is redundant for fast iterations.
2469+
the LLM call fails — never raises.
25002470
"""
2501-
if self.options.profile == "fast":
2502-
return []
25032471
analytical_id = self._analytical_specialist_id()
25042472
if analytical_id is None or not self._agent_enabled(analytical_id):
25052473
return []
@@ -4089,9 +4057,7 @@ def _maybe_run_library_refactor(
40894057
list_modules,
40904058
)
40914059

4092-
LIBRARY_REFACTOR_PERIOD = (
4093-
10 if self.options.profile == "fast" else 5
4094-
)
4060+
LIBRARY_REFACTOR_PERIOD = 5
40954061
MIN_MODULES_TO_REFACTOR = 3
40964062
if iteration.iteration_number <= 0:
40974063
return
@@ -4400,13 +4366,7 @@ def _maybe_run_presentation_memo(
44004366
the memo as an agent_call transcript is intentional — it lets
44014367
the user audit it from the web UI in the same way as Director
44024368
plans and Writer edits.
4403-
4404-
``profile="fast"`` skips this pass — the briefing reads fine
4405-
without a memo, and the saving (one LLM call per iteration)
4406-
is what fast mode is for.
44074369
"""
4408-
if self.options.profile == "fast":
4409-
return ""
44104370
presentation_id = self._agent_id_for_role(ROLE_KIND_PRESENTATION)
44114371
if presentation_id is None or not self._agent_enabled(presentation_id):
44124372
return ""
@@ -5876,7 +5836,7 @@ def _refresh_outstanding_section(text: str) -> str:
58765836

58775837

58785838
def _rolling_summary_should_run(
5879-
iteration_number: int, current_summary: str, *, cadence: int = 3,
5839+
iteration_number: int, current_summary: str
58805840
) -> bool:
58815841
"""Decide whether to refresh the project's rolling summary.
58825842
@@ -5886,16 +5846,16 @@ def _rolling_summary_should_run(
58865846
context as early as possible. Without this a project that
58875847
started before P6 landed would not get a summary until
58885848
iter 3.
5889-
* iteration_number is a multiple of ``cadence`` (default 3;
5890-
fast profile passes 5 to keep token cost lower).
5849+
* iteration_number is a multiple of 3 (default cadence;
5850+
keeps token cost predictable).
58915851
58925852
Pure function so the trigger logic is independently testable.
58935853
"""
58945854
if iteration_number < 2:
58955855
return False
58965856
if not (current_summary or "").strip():
58975857
return True
5898-
return iteration_number % max(1, cadence) == 0
5858+
return iteration_number % 3 == 0
58995859

59005860

59015861
def _wants_deep_consolidation(pivots: list[dict[str, Any]] | None) -> bool:
@@ -5919,20 +5879,14 @@ def _wants_deep_consolidation(pivots: list[dict[str, Any]] | None) -> bool:
59195879
return False
59205880

59215881

5922-
def _strategist_should_run(
5923-
latest_snap: Any,
5924-
recent_reviews: list,
5925-
*,
5926-
cadence: int = 3,
5927-
) -> bool:
5882+
def _strategist_should_run(latest_snap: Any, recent_reviews: list) -> bool:
59285883
"""Decide whether to run the Strategist this iteration.
59295884
59305885
Triggers (any-of):
59315886
* iteration_number == 1 — establish a baseline review.
59325887
* latest_snap.risk_flags non-empty — react to trouble immediately
59335888
rather than waiting for the periodic cadence.
5934-
* ≥ ``cadence`` iterations since the last review — periodic
5935-
re-assess (default 3; fast profile passes 5).
5889+
* ≥ 3 iterations since the last review — periodic re-assess.
59365890
59375891
Pure function so the trigger logic is unit-testable without
59385892
needing a full workflow setup.
@@ -5946,7 +5900,7 @@ def _strategist_should_run(
59465900
if not recent_reviews:
59475901
return True # never run before; do an initial review now
59485902
last_review_iter = int(recent_reviews[0].iteration_number or 0)
5949-
return (latest_snap.iteration_number - last_review_iter) >= max(1, cadence)
5903+
return (latest_snap.iteration_number - last_review_iter) >= 3
59505904

59515905

59525906
def _clamp01(value: Any) -> float:

0 commit comments

Comments
 (0)