okstra 0.137.0 → 0.138.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,13 @@ from .final_report_paths import (
22
22
  final_report_data_path as _final_report_data_path,
23
23
  final_report_markdown_path as _final_report_markdown_path,
24
24
  )
25
+ from .initial_prompt_materialization import (
26
+ InitialPromptMaterializationError,
27
+ InitialPromptMaterializationRequest,
28
+ InitialPromptWorkerRequest,
29
+ PromptDeliveryMode,
30
+ materialize_initial_prompts,
31
+ )
25
32
  from .lead_events import LeadEvent, append_lead_event
26
33
  from .dispatch_state import (
27
34
  BACKEND_CLI_WRAPPER,
@@ -40,8 +47,6 @@ from .dispatch_state import (
40
47
  string_list as _string_list,
41
48
  string_value as _string_value,
42
49
  utc_now as _utc_now,
43
- validate_initial_prompts as _validate_initial_prompts,
44
- worker_prompt_path as _worker_prompt_path,
45
50
  worker_state as _worker_state,
46
51
  worktree_path as _worktree_path,
47
52
  write_json as _write_json,
@@ -51,28 +56,12 @@ from .report_finalize import (
51
56
  STEP_VALIDATE_RUN,
52
57
  FinalizeContext,
53
58
  FinalizeError,
54
- project_relative_path as _project_relative_path,
55
59
  resolve_optional_path as _resolve_optional_path,
56
60
  run_finalize,
57
61
  run_seq as _run_seq,
58
62
  )
59
63
  from .wrapper_status import status_path_for_prompt
60
- from .report_language import resolve_report_language
61
- from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
62
- from .worker_prompt_contract import PromptRecord, validate_initial_prompt_records
63
- from .worker_prompt_body import (
64
- analysis_input_lines as _analysis_input_lines,
65
- analysis_prompt_body as _analysis_prompt_body,
66
- existing_input_lines as _existing_input_lines,
67
- instruction_path as _instruction_path,
68
- mcp_pointer_line as _mcp_pointer_line,
69
- report_writer_prompt_body,
70
- REPORT_WRITER_WORKER_ID,
71
- )
72
- from .worker_prompt_policy import (
73
- PromptPlan,
74
- resolve_prompt_plan_for_manifest,
75
- )
64
+ from .worker_prompt_body import REPORT_WRITER_WORKER_ID
76
65
 
77
66
 
78
67
  SUPPORTED_CLI_WORKERS = {
@@ -109,6 +98,18 @@ class DispatchPlan:
109
98
  }
110
99
 
111
100
 
101
+ @dataclass(frozen=True)
102
+ class _DispatchInputs:
103
+ project_root: Path
104
+ manifest_path: Path
105
+ manifest: Mapping[str, Any]
106
+ selected_workers: tuple[str, ...]
107
+ team_state_path: Path
108
+ team_state: Mapping[str, Any]
109
+ active_context: Mapping[str, Any]
110
+ lead_events_path: Path
111
+
112
+
112
113
  def build_dispatch_plan(
113
114
  *,
114
115
  project_root: Path,
@@ -120,15 +121,60 @@ def build_dispatch_plan(
120
121
  enable_codex_report_writer: bool = False,
121
122
  report_writer_codex_model: str = "",
122
123
  ) -> DispatchPlan:
124
+ inputs = _load_dispatch_inputs(
125
+ project_root,
126
+ run_manifest_path,
127
+ requested_workers,
128
+ enable_codex_report_writer=enable_codex_report_writer,
129
+ )
130
+ worker_requests, prompt_paths = _materialize_selected_worker_prompts(
131
+ project_root=inputs.project_root,
132
+ manifest_path=inputs.manifest_path,
133
+ workspace_root=workspace_root,
134
+ team_state=inputs.team_state,
135
+ selected_workers=inputs.selected_workers,
136
+ report_writer_codex_model=report_writer_codex_model,
137
+ )
138
+ workers = _build_selected_worker_dispatches(
139
+ project_root=inputs.project_root,
140
+ manifest=inputs.manifest,
141
+ team_state=inputs.team_state,
142
+ active_context=inputs.active_context,
143
+ workspace_root=workspace_root,
144
+ okstra_bin=okstra_bin,
145
+ idle_timeout_seconds=idle_timeout_seconds,
146
+ selected_workers=inputs.selected_workers,
147
+ worker_requests=worker_requests,
148
+ prompt_paths=prompt_paths,
149
+ )
150
+ return DispatchPlan(
151
+ project_root=inputs.project_root,
152
+ workspace_root=workspace_root,
153
+ manifest_path=inputs.manifest_path,
154
+ team_state_path=inputs.team_state_path,
155
+ lead_events_path=inputs.lead_events_path,
156
+ manifest=inputs.manifest,
157
+ workers=workers,
158
+ )
159
+
160
+
161
+ def _load_dispatch_inputs(
162
+ project_root: Path,
163
+ run_manifest_path: Path,
164
+ requested_workers: Sequence[str],
165
+ *,
166
+ enable_codex_report_writer: bool,
167
+ ) -> _DispatchInputs:
123
168
  project_root = project_root.resolve()
124
169
  manifest_path = _resolve_project_path(project_root, str(run_manifest_path))
125
170
  manifest = _load_json_object(manifest_path, "run manifest")
126
171
  _validate_codex_manifest(manifest, manifest_path)
127
-
128
- selected_workers = _select_workers(
129
- manifest,
130
- requested_workers,
131
- enable_codex_report_writer=enable_codex_report_writer,
172
+ selected_workers = tuple(
173
+ _select_workers(
174
+ manifest,
175
+ requested_workers,
176
+ enable_codex_report_writer=enable_codex_report_writer,
177
+ )
132
178
  )
133
179
  team_state_path = _resolve_required_path(project_root, manifest, "teamStatePath")
134
180
  team_state = _load_json_object(team_state_path, "team-state")
@@ -145,17 +191,67 @@ def build_dispatch_plan(
145
191
  active_context = _load_optional_json_object(
146
192
  _resolve_optional_path(project_root, manifest.get("activeRunContextPath"))
147
193
  )
148
- lead_events_path = _resolve_required_path(project_root, manifest, "leadEventsPath")
149
- _materialize_missing_worker_prompts(
194
+ return _DispatchInputs(
150
195
  project_root=project_root,
196
+ manifest_path=manifest_path,
151
197
  manifest=manifest,
198
+ selected_workers=selected_workers,
199
+ team_state_path=team_state_path,
152
200
  team_state=team_state,
153
201
  active_context=active_context,
154
- selected_workers=selected_workers,
155
- workspace_root=workspace_root,
156
- report_writer_codex_model=report_writer_codex_model,
202
+ lead_events_path=_resolve_required_path(project_root, manifest, "leadEventsPath"),
157
203
  )
158
- workers = tuple(
204
+
205
+
206
+ def _materialize_selected_worker_prompts(
207
+ *,
208
+ project_root: Path,
209
+ manifest_path: Path,
210
+ workspace_root: Path,
211
+ team_state: Mapping[str, Any],
212
+ selected_workers: Sequence[str],
213
+ report_writer_codex_model: str,
214
+ ) -> tuple[tuple[InitialPromptWorkerRequest, ...], tuple[Path, ...]]:
215
+ worker_requests = tuple(
216
+ InitialPromptWorkerRequest(
217
+ worker_id=worker_id,
218
+ model=_requested_worker_model(
219
+ worker_id,
220
+ team_state,
221
+ report_writer_codex_model,
222
+ ),
223
+ )
224
+ for worker_id in selected_workers
225
+ )
226
+ try:
227
+ prompt_paths = materialize_initial_prompts(
228
+ InitialPromptMaterializationRequest(
229
+ project_root=project_root,
230
+ run_manifest_path=manifest_path,
231
+ runtime_root=workspace_root,
232
+ delivery_mode=PromptDeliveryMode.EAGER_INCLUDE,
233
+ workers=worker_requests,
234
+ )
235
+ )
236
+ except InitialPromptMaterializationError as exc:
237
+ raise DispatchError(f"{exc.reason}: {exc}") from exc
238
+ return worker_requests, prompt_paths
239
+
240
+
241
+ def _build_selected_worker_dispatches(
242
+ *,
243
+ project_root: Path,
244
+ manifest: Mapping[str, Any],
245
+ team_state: Mapping[str, Any],
246
+ active_context: Mapping[str, Any],
247
+ workspace_root: Path,
248
+ okstra_bin: Path | None,
249
+ idle_timeout_seconds: int,
250
+ selected_workers: Sequence[str],
251
+ worker_requests: Sequence[InitialPromptWorkerRequest],
252
+ prompt_paths: Sequence[Path],
253
+ ) -> tuple[WorkerJob, ...]:
254
+ return tuple(
159
255
  _build_worker_dispatch(
160
256
  worker_id=worker_id,
161
257
  project_root=project_root,
@@ -165,19 +261,15 @@ def build_dispatch_plan(
165
261
  workspace_root=workspace_root,
166
262
  okstra_bin=okstra_bin,
167
263
  idle_timeout_seconds=idle_timeout_seconds,
168
- report_writer_codex_model=report_writer_codex_model,
264
+ model_execution_value=worker_request.model,
265
+ prompt_path=prompt_path,
266
+ )
267
+ for worker_id, worker_request, prompt_path in zip(
268
+ selected_workers,
269
+ worker_requests,
270
+ prompt_paths,
271
+ strict=True,
169
272
  )
170
- for worker_id in selected_workers
171
- )
172
- _validate_initial_prompts(manifest, workers)
173
- return DispatchPlan(
174
- project_root=project_root,
175
- workspace_root=workspace_root,
176
- manifest_path=manifest_path,
177
- team_state_path=team_state_path,
178
- lead_events_path=lead_events_path,
179
- manifest=manifest,
180
- workers=workers,
181
273
  )
182
274
 
183
275
 
@@ -357,146 +449,6 @@ def _parser() -> argparse.ArgumentParser:
357
449
  return parser
358
450
 
359
451
 
360
- def _materialize_missing_worker_prompts(
361
- *,
362
- project_root: Path,
363
- manifest: Mapping[str, Any],
364
- team_state: Mapping[str, Any],
365
- active_context: Mapping[str, Any],
366
- selected_workers: Sequence[str],
367
- workspace_root: Path,
368
- report_writer_codex_model: str,
369
- ) -> None:
370
- for worker_id in selected_workers:
371
- prompt_path = _resolve_project_path(
372
- project_root, _worker_prompt_path(manifest, worker_id)
373
- )
374
- if prompt_path.is_file():
375
- continue
376
- prompt = _render_missing_worker_prompt(
377
- project_root=project_root,
378
- manifest=manifest,
379
- team_state=team_state,
380
- active_context=active_context,
381
- worker_id=worker_id,
382
- workspace_root=workspace_root,
383
- report_writer_codex_model=report_writer_codex_model,
384
- )
385
- prompt_path.parent.mkdir(parents=True, exist_ok=True)
386
- prompt_path.write_text(prompt, encoding="utf-8")
387
-
388
-
389
- def _render_missing_worker_prompt(
390
- *,
391
- project_root: Path,
392
- manifest: Mapping[str, Any],
393
- team_state: Mapping[str, Any],
394
- active_context: Mapping[str, Any],
395
- worker_id: str,
396
- workspace_root: Path,
397
- report_writer_codex_model: str,
398
- ) -> str:
399
- if worker_id == REPORT_WRITER_WORKER_ID:
400
- return _render_missing_report_writer_prompt(
401
- project_root=project_root,
402
- manifest=manifest,
403
- team_state=team_state,
404
- active_context=active_context,
405
- report_writer_codex_model=report_writer_codex_model,
406
- )
407
- return _render_missing_analysis_worker_prompt(
408
- project_root=project_root,
409
- manifest=manifest,
410
- team_state=team_state,
411
- active_context=active_context,
412
- worker_id=worker_id,
413
- workspace_root=workspace_root,
414
- )
415
-
416
-
417
- def _render_missing_analysis_worker_prompt(
418
- *,
419
- project_root: Path,
420
- manifest: Mapping[str, Any],
421
- team_state: Mapping[str, Any],
422
- active_context: Mapping[str, Any],
423
- worker_id: str,
424
- workspace_root: Path,
425
- ) -> str:
426
- worker_state = _worker_state(team_state, worker_id)
427
- result_rel = _require_string(worker_state, "resultPath")
428
- prompt_rel = _worker_prompt_path(manifest, worker_id)
429
- model = _require_string(worker_state, "modelExecutionValue")
430
- role = _analysis_pane_role(manifest, active_context, worker_id)
431
- plan = _initial_prompt_plan(manifest, worker_id)
432
- lines = _base_prompt_headers(
433
- project_root=project_root,
434
- manifest=manifest,
435
- active_context=active_context,
436
- worker_id=worker_id,
437
- prompt_rel=prompt_rel,
438
- result_rel=result_rel,
439
- )
440
- lines.extend(_worktree_headers(manifest, active_context, role))
441
- lines.extend(
442
- _analysis_prompt_body(
443
- manifest,
444
- active_context,
445
- worker_id,
446
- model,
447
- role,
448
- plan,
449
- )
450
- )
451
- executor_tail = _implementation_executor_tail(
452
- manifest,
453
- active_context,
454
- project_root,
455
- workspace_root,
456
- role,
457
- )
458
- if executor_tail:
459
- lines.extend(["", executor_tail.rstrip()])
460
- return "\n".join(lines).rstrip() + "\n"
461
-
462
-
463
- def _render_missing_report_writer_prompt(
464
- *,
465
- project_root: Path,
466
- manifest: Mapping[str, Any],
467
- team_state: Mapping[str, Any],
468
- active_context: Mapping[str, Any],
469
- report_writer_codex_model: str,
470
- ) -> str:
471
- worker_state = _worker_state(team_state, REPORT_WRITER_WORKER_ID)
472
- markdown_path = _resolve_required_path(project_root, manifest, "expectedReportPath")
473
- data_json_path = _final_report_data_path(markdown_path)
474
- result_rel = _project_relative_path(project_root, data_json_path)
475
- worker_result_rel = _require_string(worker_state, "resultPath")
476
- model = (report_writer_codex_model or "").strip()
477
- if not model:
478
- raise DispatchError(
479
- "Codex report-writer dispatch requires --report-writer-codex-model"
480
- )
481
- lines = _base_prompt_headers(
482
- project_root=project_root,
483
- manifest=manifest,
484
- active_context=active_context,
485
- worker_id=REPORT_WRITER_WORKER_ID,
486
- prompt_rel=_worker_prompt_path(manifest, REPORT_WRITER_WORKER_ID),
487
- result_rel=result_rel,
488
- audit_source_rel=worker_result_rel,
489
- )
490
- lines.extend(report_writer_prompt_body(
491
- manifest,
492
- active_context,
493
- team_state,
494
- model,
495
- resolve_report_language(project_root, manifest, active_context),
496
- ))
497
- return "\n".join(lines).rstrip() + "\n"
498
-
499
-
500
452
  def _build_worker_dispatch(
501
453
  *,
502
454
  worker_id: str,
@@ -507,7 +459,8 @@ def _build_worker_dispatch(
507
459
  workspace_root: Path,
508
460
  okstra_bin: Path | None,
509
461
  idle_timeout_seconds: int,
510
- report_writer_codex_model: str,
462
+ model_execution_value: str,
463
+ prompt_path: Path,
511
464
  ) -> WorkerJob:
512
465
  if worker_id == REPORT_WRITER_WORKER_ID:
513
466
  return _build_report_writer_dispatch(
@@ -517,15 +470,13 @@ def _build_worker_dispatch(
517
470
  workspace_root=workspace_root,
518
471
  okstra_bin=okstra_bin,
519
472
  idle_timeout_seconds=idle_timeout_seconds,
520
- report_writer_codex_model=report_writer_codex_model,
473
+ model_execution_value=model_execution_value,
474
+ prompt_path=prompt_path,
521
475
  )
522
476
  worker_state = _worker_state(team_state, worker_id)
523
- prompt_rel = _worker_prompt_path(manifest, worker_id)
524
- prompt_path = _resolve_project_path(project_root, prompt_rel)
525
477
  result_path = _resolve_project_path(
526
478
  project_root, _require_string(worker_state, "resultPath")
527
479
  )
528
- model_execution_value = _require_string(worker_state, "modelExecutionValue")
529
480
  wrapper = _resolve_wrapper(worker_id, workspace_root, okstra_bin)
530
481
  worktree_path = _worktree_path(manifest, active_context)
531
482
  role = _pane_role(prompt_path)
@@ -555,17 +506,10 @@ def _build_report_writer_dispatch(
555
506
  workspace_root: Path,
556
507
  okstra_bin: Path | None,
557
508
  idle_timeout_seconds: int,
558
- report_writer_codex_model: str,
509
+ model_execution_value: str,
510
+ prompt_path: Path,
559
511
  ) -> WorkerJob:
560
- model_execution_value = (report_writer_codex_model or "").strip()
561
- if not model_execution_value:
562
- raise DispatchError(
563
- "Codex report-writer dispatch requires --report-writer-codex-model"
564
- )
565
512
  worker_state = _worker_state(team_state, REPORT_WRITER_WORKER_ID)
566
- prompt_path = _resolve_project_path(
567
- project_root, _worker_prompt_path(manifest, REPORT_WRITER_WORKER_ID)
568
- )
569
513
  data_json_path = _final_report_data_path(
570
514
  _resolve_required_path(project_root, manifest, "expectedReportPath")
571
515
  )
@@ -573,7 +517,6 @@ def _build_report_writer_dispatch(
573
517
  worker_result_path = _resolve_project_path(
574
518
  project_root, _require_string(worker_state, "resultPath")
575
519
  )
576
- _replace_report_writer_model_line(prompt_path, model_execution_value)
577
520
  return WorkerJob(
578
521
  worker_id=REPORT_WRITER_WORKER_ID,
579
522
  # codex 경로의 report-writer 는 claude 가 아니라 codex wrapper 로 돈다.
@@ -597,157 +540,6 @@ def _build_report_writer_dispatch(
597
540
  )
598
541
 
599
542
 
600
- def _base_prompt_headers(
601
- *,
602
- project_root: Path,
603
- manifest: Mapping[str, Any],
604
- active_context: Mapping[str, Any],
605
- worker_id: str,
606
- prompt_rel: str,
607
- result_rel: str,
608
- audit_source_rel: str | None = None,
609
- ) -> list[str]:
610
- try:
611
- return worker_prompt_headers(
612
- project_root=project_root,
613
- prompt_rel=prompt_rel,
614
- result_rel=result_rel,
615
- audit_source_rel=audit_source_rel,
616
- worker_id=worker_id,
617
- dispatch_kind="initial",
618
- manifest=manifest,
619
- active_context=active_context,
620
- )
621
- except WorkerPromptHeaderError as exc:
622
- raise DispatchError(str(exc)) from exc
623
-
624
-
625
- def _initial_prompt_plan(
626
- manifest: Mapping[str, Any],
627
- worker_id: str,
628
- ) -> PromptPlan:
629
- try:
630
- return resolve_prompt_plan_for_manifest(
631
- manifest=manifest,
632
- worker_id=worker_id,
633
- dispatch_kind="initial",
634
- )
635
- except ValueError as exc:
636
- raise DispatchError(str(exc)) from exc
637
-
638
-
639
- def _analysis_pane_role(
640
- manifest: Mapping[str, Any],
641
- active_context: Mapping[str, Any],
642
- worker_id: str,
643
- ) -> str:
644
- if _require_string(manifest, "taskType") != "implementation":
645
- return "worker"
646
- if _executor_provider(manifest) != worker_id:
647
- return "worker"
648
- worktree = _worktree_path(manifest, active_context)
649
- if not worktree:
650
- raise DispatchError("implementation executor prompt generation requires a worktree path")
651
- return "executor"
652
-
653
-
654
- def _executor_provider(manifest: Mapping[str, Any]) -> str:
655
- team_contract = manifest.get("teamContract")
656
- if not isinstance(team_contract, Mapping):
657
- return ""
658
- executor = team_contract.get("executor")
659
- if not isinstance(executor, Mapping):
660
- return ""
661
- return _string_value(executor.get("provider"))
662
-
663
-
664
- def _worktree_headers(
665
- manifest: Mapping[str, Any],
666
- active_context: Mapping[str, Any],
667
- role: str,
668
- ) -> list[str]:
669
- task_type = _require_string(manifest, "taskType")
670
- worktree = _worktree_path(manifest, active_context)
671
- if task_type != "implementation" or not worktree:
672
- return []
673
- headers = [f"**Worktree:** {worktree}"]
674
- if role == "executor":
675
- headers.append(f"cwd for every mutating command: {worktree}")
676
- return headers
677
-
678
-
679
- def _implementation_executor_tail(
680
- manifest: Mapping[str, Any],
681
- active_context: Mapping[str, Any],
682
- project_root: Path,
683
- workspace_root: Path,
684
- role: str,
685
- ) -> str:
686
- if role != "executor" or _require_string(manifest, "taskType") != "implementation":
687
- return ""
688
- profiles = workspace_root / "prompts" / "profiles"
689
- parts: list[str] = []
690
- analysis_profile = _instruction_path(
691
- manifest,
692
- active_context,
693
- "analysisProfilePath",
694
- )
695
- if not analysis_profile:
696
- raise DispatchError("implementation executor profile path is missing")
697
- executor_profile = (
698
- _resolve_project_path(project_root, analysis_profile).parent
699
- / "implementation-executor.md"
700
- )
701
- if not executor_profile.is_file():
702
- raise DispatchError(
703
- f"resolved implementation executor profile not found: {executor_profile}"
704
- )
705
- parts.append(executor_profile.read_text(encoding="utf-8"))
706
- preflight_path = profiles / "_coding-conventions-preflight.md"
707
- if preflight_path.is_file():
708
- parts.append(preflight_path.read_text(encoding="utf-8"))
709
- else:
710
- parts.append("# Coding-conventions preflight\n\nApply project-local conventions before editing.")
711
- diff_review_path = profiles / "_implementation-diff-review.md"
712
- if diff_review_path.is_file():
713
- parts.append(diff_review_path.read_text(encoding="utf-8"))
714
- selfcheck_path = profiles / "_implementation-self-check.md"
715
- if selfcheck_path.is_file():
716
- parts.append(selfcheck_path.read_text(encoding="utf-8"))
717
- parts.extend(_clarification_response_section(manifest, active_context, project_root))
718
- return "\n\n".join(parts)
719
-
720
-
721
- def _clarification_response_section(
722
- manifest: Mapping[str, Any],
723
- active_context: Mapping[str, Any],
724
- project_root: Path,
725
- ) -> list[str]:
726
- """The user's answers, inlined for a CLI executor.
727
-
728
- A CLI worker runs in its own process and receives only the prompt text, so
729
- a path reference never reaches it. Passing the clarification response as a
730
- path meant the executor implemented the pre-answer plan while the report
731
- described the answers as carried in — the user answered and nothing
732
- downstream acted on it.
733
- """
734
- relative = _instruction_path(manifest, active_context, "clarificationResponsePath")
735
- if not relative:
736
- return []
737
- path = _resolve_project_path(project_root, relative)
738
- if not path.is_file():
739
- return []
740
- body = path.read_text(encoding="utf-8").strip()
741
- if not body:
742
- return []
743
- return [
744
- "# Clarification answers carried in (authoritative)\n\n"
745
- "The user answered these before this run. Where an answer conflicts "
746
- "with the approved plan text, the answer wins — implement the answer "
747
- f"and say so in your result.\n\nSource: `{relative}`\n\n{body}"
748
- ]
749
-
750
-
751
543
  def _validate_codex_manifest(manifest: Mapping[str, Any], path: Path) -> None:
752
544
  if manifest.get("leadRuntime") != "codex":
753
545
  raise DispatchError(f"run manifest is not a Codex lead run: {path}")
@@ -755,6 +547,24 @@ def _validate_codex_manifest(manifest: Mapping[str, Any], path: Path) -> None:
755
547
  raise DispatchError(f"run manifest has no leadEventsPath: {path}")
756
548
 
757
549
 
550
+ def _requested_worker_model(
551
+ worker_id: str,
552
+ team_state: Mapping[str, Any],
553
+ report_writer_codex_model: str,
554
+ ) -> str:
555
+ if worker_id != REPORT_WRITER_WORKER_ID:
556
+ return _require_string(
557
+ _worker_state(team_state, worker_id),
558
+ "modelExecutionValue",
559
+ )
560
+ model = report_writer_codex_model.strip()
561
+ if model:
562
+ return model
563
+ raise DispatchError(
564
+ "Codex report-writer dispatch requires --report-writer-codex-model"
565
+ )
566
+
567
+
758
568
  def _select_workers(
759
569
  manifest: Mapping[str, Any],
760
570
  requested_workers: Sequence[str],
@@ -1031,23 +841,6 @@ def _failure_reason(exit_code: int, missing_paths: Sequence[Path]) -> str:
1031
841
  return "worker dispatch failed"
1032
842
 
1033
843
 
1034
- def _replace_report_writer_model_line(
1035
- prompt_path: Path,
1036
- model_execution_value: str,
1037
- ) -> None:
1038
- if not prompt_path.is_file():
1039
- raise DispatchError(f"report-writer prompt not found: {prompt_path}")
1040
- lines = prompt_path.read_text(encoding="utf-8").splitlines()
1041
- marker = "**Model:** Report writer worker,"
1042
- replacement = f"{marker} {model_execution_value}"
1043
- updated = [
1044
- replacement if line.startswith(marker) else line
1045
- for line in lines
1046
- ]
1047
- if updated != lines:
1048
- prompt_path.write_text("\n".join(updated) + "\n", encoding="utf-8")
1049
-
1050
-
1051
844
  def _parse_workers(raw: str) -> list[str]:
1052
845
  return [item.strip() for item in raw.split(",") if item.strip()]
1053
846