claude-dev-env 2.7.1 → 2.9.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.
- package/CLAUDE.md +7 -1
- package/_shared/CLAUDE.md +1 -0
- package/_shared/advisor/advisor-protocol.md +19 -9
- package/_shared/pr-loop/audit-contract.md +4 -4
- package/_shared/pr-loop/precatch-rubric.md +2 -2
- package/_shared/process-tree/CLAUDE.md +41 -0
- package/_shared/process-tree/scripts/config/process_tree_scripts_constants/__init__.py +1 -0
- package/_shared/process-tree/scripts/config/process_tree_scripts_constants/process_tree_kill_constants.py +27 -0
- package/_shared/process-tree/scripts/process_tree_kill.py +141 -0
- package/_shared/process-tree/scripts/pyproject.toml +16 -0
- package/_shared/process-tree/scripts/test_process_tree_kill.py +278 -0
- package/agents/clean-coder.md +9 -19
- package/agents/code-quality-agent.md +6 -5
- package/agents/deep-research.md +7 -24
- package/agents/docs-agent.md +1 -27
- package/agents/issue-tracker.md +1 -7
- package/agents/skill-writer-agent.md +1 -2
- package/agents/test_agent_frontmatter.py +335 -12
- package/docs/CODE_RULES.md +4 -2
- package/docs/references/CLAUDE.md +2 -2
- package/docs/references/advisor-tool.md +44 -6
- package/docs/references/team-advisor-skill.md +14 -8
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/fable_spawn_gate.py +187 -0
- package/hooks/blocking/piped_pytest_blocker.py +1223 -0
- package/hooks/blocking/plain_language_blocker.py +287 -15
- package/hooks/blocking/test_fable_spawn_gate.py +374 -0
- package/hooks/blocking/test_piped_pytest_blocker.py +587 -0
- package/hooks/blocking/test_plain_language_blocker.py +277 -2
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +34 -2
- package/hooks/git-hooks/CLAUDE.md +2 -2
- package/hooks/git-hooks/git_hooks_constants/__init__.py +28 -0
- package/hooks/git-hooks/pre_push.py +343 -54
- package/hooks/git-hooks/test_pre_push.py +852 -6
- package/hooks/hooks.json +9 -19
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +62 -0
- package/hooks/hooks_constants/piped_pytest_blocker_constants.py +360 -0
- package/hooks/hooks_constants/plain_language_blocker_constants.py +64 -1
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -0
- package/hooks/hooks_constants/shell_command_segments.py +1 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +27 -0
- package/hooks/hooks_constants/test_prose_metrics_parity.py +124 -0
- package/output-styles/CLAUDE.md +17 -0
- package/output-styles/caveman-agent.md +37 -0
- package/package.json +2 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/ask-user-question-required.md +26 -0
- package/rules/claims-as-quotes.md +65 -0
- package/rules/code-standards.md +33 -7
- package/rules/eli11-replies.md +1 -1
- package/scripts/CLAUDE.md +3 -3
- package/scripts/_code_review_test_support.py +6 -0
- package/scripts/check.ps1 +18 -5
- package/scripts/claude_chain_runner.py +203 -31
- package/scripts/codec_forwarding_test_support.py +2 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -4
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +38 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +403 -2
- package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +132 -24
- package/scripts/grok_headless_runner.py +16 -83
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -0
- package/scripts/spawn_grok_batch.py +591 -10
- package/scripts/test_claude_chain_runner.py +358 -0
- package/scripts/test_grok_headless_runner.py +18 -63
- package/scripts/test_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- package/scripts/test_invoke_code_review.py +298 -0
- package/scripts/test_resolve_worker_spawn.py +6 -0
- package/scripts/test_spawn_grok_batch.py +396 -0
- package/scripts/tests/CLAUDE.md +1 -0
- package/scripts/tests/test_grok_worker_constants.py +59 -0
- package/skills/CLAUDE.md +4 -2
- package/skills/_shared/CLAUDE.md +37 -4
- package/skills/_shared/advisor/CLAUDE.md +9 -0
- package/skills/_shared/advisor/advisor-protocol.md +5 -0
- package/skills/_shared/advisor/scripts/README.md +9 -0
- package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
- package/skills/_shared/pr-loop/CLAUDE.md +18 -1
- package/skills/_shared/pr-loop/audit-contract.md +5 -0
- package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
- package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
- package/skills/_shared/pr-loop/fix-protocol.md +5 -0
- package/skills/_shared/pr-loop/gh-payloads.md +5 -0
- package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
- package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +8 -1
- package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +46 -0
- package/skills/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +105 -2
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +34 -1
- package/skills/autoconverge/workflow/converge.mjs +66 -33
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -4
- package/skills/codex-review/scripts/codex_usage_probe.py +20 -33
- package/skills/codex-review/scripts/run_codex_review.py +16 -64
- package/skills/codex-review/scripts/test_codex_usage_probe.py +46 -41
- package/skills/codex-review/scripts/test_run_codex_review.py +1 -33
- package/skills/e-code-review/SKILL.md +15 -9
- package/skills/e-code-review/reference/fix.md +29 -7
- package/skills/e-code-review/reference/loop.md +230 -14
- package/skills/e-code-review/reference/low.md +33 -15
- package/skills/e-code-review/reference/medium.md +55 -21
- package/skills/e-code-review/reference/runner-selection.md +40 -0
- package/skills/e-code-review/reference/xhigh.md +30 -12
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
- package/skills/e-code-review/scripts/grok_code_review.py +221 -0
- package/skills/e-code-review/scripts/test_grok_code_review.py +212 -0
- package/skills/fresh-branch/CLAUDE.md +5 -5
- package/skills/fresh-branch/SKILL.md +14 -6
- package/skills/fresh-branch/scripts/create_fresh_branch.py +122 -39
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +10 -3
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +251 -0
- package/skills/grok-spawn/SKILL.md +10 -0
- package/skills/orchestrator/SKILL.md +9 -1
- package/skills/orchestrator-refresh/SKILL.md +5 -1
- package/skills/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -41
- package/system-prompts/software-engineer.xml +6 -3
- package/skills/test_markdown_link_integrity.py +0 -107
|
@@ -15,9 +15,11 @@ Import ``run_grok_batch`` for the summary object, or run the module as a CLI::
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
|
+
import subprocess
|
|
18
19
|
import json
|
|
19
20
|
import sys
|
|
20
21
|
import time
|
|
22
|
+
import threading
|
|
21
23
|
import uuid
|
|
22
24
|
from concurrent.futures import ThreadPoolExecutor
|
|
23
25
|
from contextlib import suppress
|
|
@@ -26,6 +28,7 @@ from pathlib import Path
|
|
|
26
28
|
|
|
27
29
|
from dev_env_scripts_constants.grok_worker_constants import (
|
|
28
30
|
ALL_KNOWN_TOOL_PROFILES,
|
|
31
|
+
ALL_KNOWN_WORKER_SPEC_KEYS,
|
|
29
32
|
BATCH_LAUNCH_ERROR_STDERR_PREFIX,
|
|
30
33
|
BATCH_SPEC_ROLE_KEY,
|
|
31
34
|
BATCH_SPEC_SHOULD_PING_KEY,
|
|
@@ -70,25 +73,363 @@ from dev_env_scripts_constants.grok_worker_constants import (
|
|
|
70
73
|
SUMMARY_WORKERS_KEY,
|
|
71
74
|
TOOL_PROFILE_BUILD,
|
|
72
75
|
TOOL_PROFILE_READONLY,
|
|
76
|
+
UNKNOWN_WORKER_KEY_ERROR_TEMPLATE,
|
|
73
77
|
UTF8_ENCODING,
|
|
74
78
|
WORKER_EXCEPTION_RETURN_CODE,
|
|
75
79
|
WORKER_SPEC_AGENT_NAME_KEY,
|
|
76
80
|
WORKER_SPEC_CWD_KEY,
|
|
77
81
|
WORKER_SPEC_IS_REPO_ONLY_KEY,
|
|
82
|
+
WORKER_SPEC_KEY_JOIN_SEPARATOR,
|
|
78
83
|
WORKER_SPEC_PROMPT_PARTS_KEY,
|
|
79
84
|
WORKER_SPEC_ROLE_NAME_KEY,
|
|
80
85
|
WORKER_SPEC_TIMEOUT_KEY,
|
|
81
86
|
WORKER_SPEC_TOOL_PROFILE_KEY,
|
|
87
|
+
BATCH_SPEC_ADVISOR_KEY,
|
|
88
|
+
ADVISOR_SPEC_LAUNCHER_KEY,
|
|
89
|
+
ADVISOR_SPEC_MODEL_KEY,
|
|
90
|
+
ADVISOR_SPEC_EFFORT_KEY,
|
|
91
|
+
DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER,
|
|
92
|
+
DEFAULT_ADVISOR_MODEL,
|
|
93
|
+
DEFAULT_ADVISOR_EFFORT,
|
|
94
|
+
MAXIMUM_WORKER_ADVISOR_CORRECTIONS,
|
|
95
|
+
MAXIMUM_ADVISOR_TIMEOUT_SECONDS,
|
|
96
|
+
ADVISOR_SIGNAL_ENDORSE,
|
|
97
|
+
ADVISOR_SIGNAL_CORRECTION,
|
|
98
|
+
ADVISOR_SIGNAL_PLAN,
|
|
99
|
+
ADVISOR_SIGNAL_STOP,
|
|
100
|
+
ALL_KNOWN_ADVISOR_SIGNALS,
|
|
101
|
+
CLASSIFICATION_ADVISOR_BLOCKED,
|
|
102
|
+
PENDING_BIND_SENTINEL,
|
|
103
|
+
ADVISOR_PROMPT_HEADER_TEMPLATE,
|
|
104
|
+
SUMMARY_ADVISOR_SESSION_ID_KEY,
|
|
105
|
+
SUMMARY_ADVISOR_SIGNAL_KEY,
|
|
106
|
+
SUMMARY_ADVISOR_LAUNCHER_KEY,
|
|
107
|
+
ADVISOR_CLI_PRINT_FLAG,
|
|
108
|
+
ADVISOR_CLI_MODEL_FLAG,
|
|
109
|
+
ADVISOR_CLI_EFFORT_FLAG,
|
|
110
|
+
ADVISOR_CLI_OUTPUT_FORMAT_FLAG,
|
|
111
|
+
ADVISOR_CLI_OUTPUT_FORMAT_JSON,
|
|
112
|
+
ADVISOR_CLI_RESUME_FLAG,
|
|
113
|
+
ADVISOR_BIND_PROMPT_TEMPLATE,
|
|
114
|
+
ADVISOR_VERDICT_PROMPT_TEMPLATE,
|
|
82
115
|
)
|
|
83
116
|
from dev_env_scripts_constants.timing import WORKER_STAGGER_SECONDS
|
|
84
117
|
from grok_headless_runner import GrokRunnerOutcome, run_headless_worker
|
|
85
118
|
from grok_worker_preflight import PreflightOutcome, run_preflight
|
|
86
119
|
|
|
120
|
+
class AdvisorFailureError(ValueError):
|
|
121
|
+
"""Raised when an advisor bind, resume, or launcher call fails closed."""
|
|
122
|
+
|
|
123
|
+
|
|
87
124
|
batch_sleep = time.sleep
|
|
88
125
|
batch_headless_runner = run_headless_worker
|
|
89
126
|
batch_preflight = run_preflight
|
|
90
127
|
|
|
91
128
|
|
|
129
|
+
def extract_advisor_signal(advisor_text: str) -> str | None:
|
|
130
|
+
"""Return the first allowed opening signal token from advisor text.
|
|
131
|
+
|
|
132
|
+
::
|
|
133
|
+
|
|
134
|
+
extract_advisor_signal("ENDORSE\\nlooks good")
|
|
135
|
+
ok: "ENDORSE"
|
|
136
|
+
extract_advisor_signal("hello ENDORSE")
|
|
137
|
+
flag: None
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
advisor_text: Raw advisor reply body.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
One of the four known signals, or ``None`` when the first token is
|
|
144
|
+
missing or outside the set.
|
|
145
|
+
"""
|
|
146
|
+
stripped = (advisor_text or "").strip()
|
|
147
|
+
if not stripped:
|
|
148
|
+
return None
|
|
149
|
+
first_line = stripped.splitlines()[0].strip()
|
|
150
|
+
first_token = first_line.split()[0] if first_line else ""
|
|
151
|
+
if first_token in ALL_KNOWN_ADVISOR_SIGNALS:
|
|
152
|
+
return first_token
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _parse_advisor_spec(raw_advisor: object) -> AdvisorSpec | None:
|
|
157
|
+
if raw_advisor is None:
|
|
158
|
+
return None
|
|
159
|
+
if not isinstance(raw_advisor, dict):
|
|
160
|
+
raise ValueError("batch advisor must be an object when present")
|
|
161
|
+
launcher = raw_advisor.get(
|
|
162
|
+
ADVISOR_SPEC_LAUNCHER_KEY, DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER
|
|
163
|
+
)
|
|
164
|
+
model = raw_advisor.get(ADVISOR_SPEC_MODEL_KEY, DEFAULT_ADVISOR_MODEL)
|
|
165
|
+
effort = raw_advisor.get(ADVISOR_SPEC_EFFORT_KEY, DEFAULT_ADVISOR_EFFORT)
|
|
166
|
+
if not isinstance(launcher, str) or not launcher:
|
|
167
|
+
raise ValueError("advisor.launcher must be a non-empty string")
|
|
168
|
+
if launcher == PENDING_BIND_SENTINEL:
|
|
169
|
+
raise ValueError(f"advisor.launcher must not be {PENDING_BIND_SENTINEL}")
|
|
170
|
+
if not isinstance(model, str) or not model:
|
|
171
|
+
raise ValueError("advisor.model must be a non-empty string")
|
|
172
|
+
if not isinstance(effort, str) or not effort:
|
|
173
|
+
raise ValueError("advisor.effort must be a non-empty string")
|
|
174
|
+
return AdvisorSpec(launcher=launcher, model=model, effort=effort)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _advisor_body_text_from_stdout(stdout_text: str) -> str:
|
|
178
|
+
stripped = (stdout_text or "").strip()
|
|
179
|
+
if not stripped:
|
|
180
|
+
return ""
|
|
181
|
+
try:
|
|
182
|
+
parsed = json.loads(stripped)
|
|
183
|
+
except json.JSONDecodeError:
|
|
184
|
+
return stripped
|
|
185
|
+
if isinstance(parsed, dict):
|
|
186
|
+
advisor_body_field = parsed.get("result")
|
|
187
|
+
if isinstance(advisor_body_field, str):
|
|
188
|
+
return advisor_body_field
|
|
189
|
+
return stripped
|
|
190
|
+
if isinstance(parsed, list):
|
|
191
|
+
for each_record in reversed(parsed):
|
|
192
|
+
if (
|
|
193
|
+
isinstance(each_record, dict)
|
|
194
|
+
and each_record.get("type") == "result"
|
|
195
|
+
and isinstance(each_record.get("result"), str)
|
|
196
|
+
):
|
|
197
|
+
return each_record["result"]
|
|
198
|
+
return stripped
|
|
199
|
+
return stripped
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _session_id_from_advisor_stdout(stdout_text: str) -> str | None:
|
|
203
|
+
stripped = (stdout_text or "").strip()
|
|
204
|
+
if not stripped:
|
|
205
|
+
return None
|
|
206
|
+
try:
|
|
207
|
+
parsed = json.loads(stripped)
|
|
208
|
+
except json.JSONDecodeError:
|
|
209
|
+
return None
|
|
210
|
+
candidates: list[object] = []
|
|
211
|
+
if isinstance(parsed, dict):
|
|
212
|
+
candidates.append(parsed)
|
|
213
|
+
elif isinstance(parsed, list):
|
|
214
|
+
candidates.extend(each for each in parsed if isinstance(each, dict))
|
|
215
|
+
for each_record in candidates:
|
|
216
|
+
session_id = each_record.get("session_id")
|
|
217
|
+
if isinstance(session_id, str) and session_id and session_id != PENDING_BIND_SENTINEL:
|
|
218
|
+
return session_id
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def invoke_advisor_launcher(
|
|
223
|
+
*,
|
|
224
|
+
launcher: str,
|
|
225
|
+
model: str,
|
|
226
|
+
effort: str,
|
|
227
|
+
prompt_text: str,
|
|
228
|
+
session_id: str | None = None,
|
|
229
|
+
) -> tuple[str | None, str, int]:
|
|
230
|
+
"""Run one advisor launcher call via direct subprocess.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
launcher: Spec-supplied executable name (runtime only).
|
|
234
|
+
model: Advisor model name.
|
|
235
|
+
effort: Advisor effort level.
|
|
236
|
+
prompt_text: Prompt body piped on stdin.
|
|
237
|
+
session_id: When set, pass ``--resume`` for a post-report consult.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
``(session_id_or_none, advisor_body_text, returncode)``.
|
|
241
|
+
"""
|
|
242
|
+
all_command_arguments = [
|
|
243
|
+
launcher,
|
|
244
|
+
ADVISOR_CLI_PRINT_FLAG,
|
|
245
|
+
ADVISOR_CLI_MODEL_FLAG,
|
|
246
|
+
model,
|
|
247
|
+
ADVISOR_CLI_EFFORT_FLAG,
|
|
248
|
+
effort,
|
|
249
|
+
ADVISOR_CLI_OUTPUT_FORMAT_FLAG,
|
|
250
|
+
ADVISOR_CLI_OUTPUT_FORMAT_JSON,
|
|
251
|
+
]
|
|
252
|
+
if session_id is not None:
|
|
253
|
+
all_command_arguments.extend([ADVISOR_CLI_RESUME_FLAG, session_id])
|
|
254
|
+
try:
|
|
255
|
+
completion = subprocess.run(
|
|
256
|
+
all_command_arguments,
|
|
257
|
+
input=prompt_text,
|
|
258
|
+
capture_output=True,
|
|
259
|
+
text=True,
|
|
260
|
+
encoding=UTF8_ENCODING,
|
|
261
|
+
check=False,
|
|
262
|
+
timeout=MAXIMUM_ADVISOR_TIMEOUT_SECONDS,
|
|
263
|
+
)
|
|
264
|
+
except FileNotFoundError as missing_launcher:
|
|
265
|
+
raise AdvisorFailureError(
|
|
266
|
+
f"advisor launcher not found: {launcher}"
|
|
267
|
+
) from missing_launcher
|
|
268
|
+
except subprocess.TimeoutExpired as timed_out:
|
|
269
|
+
raise AdvisorFailureError(
|
|
270
|
+
f"advisor launcher timed out after {MAXIMUM_ADVISOR_TIMEOUT_SECONDS}s"
|
|
271
|
+
) from timed_out
|
|
272
|
+
stdout_text = completion.stdout or ""
|
|
273
|
+
resolved_session = _session_id_from_advisor_stdout(stdout_text)
|
|
274
|
+
if session_id is not None and resolved_session is None:
|
|
275
|
+
resolved_session = session_id
|
|
276
|
+
advisor_body_text = _advisor_body_text_from_stdout(stdout_text)
|
|
277
|
+
return resolved_session, advisor_body_text, completion.returncode
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
batch_invoke_advisor = invoke_advisor_launcher
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def bind_unique_worker_advisor(
|
|
284
|
+
*,
|
|
285
|
+
advisor_spec: AdvisorSpec,
|
|
286
|
+
role_name: str,
|
|
287
|
+
all_used_session_ids: set[str],
|
|
288
|
+
) -> tuple[str, str]:
|
|
289
|
+
"""Bind one unique advisor session for a worker; refuse duplicates and sentinels.
|
|
290
|
+
|
|
291
|
+
::
|
|
292
|
+
|
|
293
|
+
bind_unique_worker_advisor(...)
|
|
294
|
+
ok: returns (session_id, ENDORSE)
|
|
295
|
+
flag: duplicate session id across workers raises ValueError
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
advisor_spec: Lead-supplied launcher/model/effort.
|
|
299
|
+
role_name: Worker role used in the bind prompt.
|
|
300
|
+
all_used_session_ids: Sessions already issued in this batch.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
``(session_id, opening_signal)``.
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
ValueError: On bind failure, sentinel, missing signal, or duplicate id.
|
|
307
|
+
"""
|
|
308
|
+
if advisor_spec.launcher == DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
"advisor.launcher still holds the placeholder; supply a real launcher in the batch spec"
|
|
311
|
+
)
|
|
312
|
+
if advisor_spec.launcher == PENDING_BIND_SENTINEL:
|
|
313
|
+
raise ValueError(f"advisor.launcher is {PENDING_BIND_SENTINEL}")
|
|
314
|
+
prompt_text = ADVISOR_BIND_PROMPT_TEMPLATE.format(role_name=role_name)
|
|
315
|
+
session_id, advisor_body_text, returncode = batch_invoke_advisor(
|
|
316
|
+
launcher=advisor_spec.launcher,
|
|
317
|
+
model=advisor_spec.model,
|
|
318
|
+
effort=advisor_spec.effort,
|
|
319
|
+
prompt_text=prompt_text,
|
|
320
|
+
)
|
|
321
|
+
if returncode != 0 or not session_id or session_id == PENDING_BIND_SENTINEL:
|
|
322
|
+
raise AdvisorFailureError(
|
|
323
|
+
f"advisor bind failed for role {role_name}: returncode={returncode}"
|
|
324
|
+
)
|
|
325
|
+
if session_id in all_used_session_ids:
|
|
326
|
+
raise AdvisorFailureError(
|
|
327
|
+
f"duplicate advisor session id {session_id} for role {role_name}"
|
|
328
|
+
)
|
|
329
|
+
signal = extract_advisor_signal(advisor_body_text)
|
|
330
|
+
if signal is None:
|
|
331
|
+
raise ValueError(f"malformed advisor signal for role {role_name}")
|
|
332
|
+
if signal == ADVISOR_SIGNAL_STOP:
|
|
333
|
+
raise ValueError(f"advisor STOP for role {role_name}")
|
|
334
|
+
if signal != ADVISOR_SIGNAL_ENDORSE:
|
|
335
|
+
raise ValueError(
|
|
336
|
+
f"advisor pre-dispatch signal {signal} for role {role_name}; require ENDORSE"
|
|
337
|
+
)
|
|
338
|
+
all_used_session_ids.add(session_id)
|
|
339
|
+
return session_id, signal
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def obtain_advisor_completion_verdict(
|
|
343
|
+
*,
|
|
344
|
+
advisor_spec: AdvisorSpec,
|
|
345
|
+
role_name: str,
|
|
346
|
+
session_id: str,
|
|
347
|
+
report_text: str,
|
|
348
|
+
) -> str:
|
|
349
|
+
"""Resume the same advisor session until ENDORSE or the correction cap.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
advisor_spec: Lead-supplied launcher/model/effort.
|
|
353
|
+
role_name: Worker role name.
|
|
354
|
+
session_id: Session from pre-dispatch bind.
|
|
355
|
+
report_text: Worker report body under review.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
The final accepted signal (``ENDORSE``).
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
ValueError: On STOP, malformed signal, or correction cap exceeded.
|
|
362
|
+
"""
|
|
363
|
+
if session_id == PENDING_BIND_SENTINEL:
|
|
364
|
+
raise ValueError(f"advisor session_id is {PENDING_BIND_SENTINEL}")
|
|
365
|
+
correction_count = 0
|
|
366
|
+
while True:
|
|
367
|
+
prompt_text = ADVISOR_VERDICT_PROMPT_TEMPLATE.format(
|
|
368
|
+
role_name=role_name,
|
|
369
|
+
session_id=session_id,
|
|
370
|
+
report_text=report_text,
|
|
371
|
+
)
|
|
372
|
+
_, advisor_body_text, returncode = batch_invoke_advisor(
|
|
373
|
+
launcher=advisor_spec.launcher,
|
|
374
|
+
model=advisor_spec.model,
|
|
375
|
+
effort=advisor_spec.effort,
|
|
376
|
+
prompt_text=prompt_text,
|
|
377
|
+
session_id=session_id,
|
|
378
|
+
)
|
|
379
|
+
if returncode != 0:
|
|
380
|
+
raise ValueError(
|
|
381
|
+
f"advisor resume failed for role {role_name}: returncode={returncode}"
|
|
382
|
+
)
|
|
383
|
+
signal = extract_advisor_signal(advisor_body_text)
|
|
384
|
+
if signal is None:
|
|
385
|
+
raise ValueError(f"malformed advisor signal for role {role_name}")
|
|
386
|
+
if signal == ADVISOR_SIGNAL_ENDORSE:
|
|
387
|
+
return signal
|
|
388
|
+
if signal == ADVISOR_SIGNAL_STOP:
|
|
389
|
+
raise ValueError(f"advisor STOP for role {role_name}")
|
|
390
|
+
if signal in (ADVISOR_SIGNAL_CORRECTION, ADVISOR_SIGNAL_PLAN):
|
|
391
|
+
correction_count += 1
|
|
392
|
+
if correction_count > MAXIMUM_WORKER_ADVISOR_CORRECTIONS:
|
|
393
|
+
raise ValueError(
|
|
394
|
+
f"advisor correction cap exceeded for role {role_name}"
|
|
395
|
+
)
|
|
396
|
+
continue
|
|
397
|
+
raise ValueError(f"unexpected advisor signal {signal} for role {role_name}")
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _require_known_worker_keys(all_worker_fields: dict[str, object]) -> None:
|
|
402
|
+
"""Reject a worker entry carrying a key the launcher does not accept.
|
|
403
|
+
|
|
404
|
+
::
|
|
405
|
+
|
|
406
|
+
{"role_name": "lens", ..., "timeout_second": <seconds>}
|
|
407
|
+
flag: unknown worker key(s): timeout_second; accepted keys: agent_name, ...
|
|
408
|
+
|
|
409
|
+
A dropped key reads as a setting that took effect. Naming it here makes
|
|
410
|
+
that a startup error rather than a silent gap.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
all_worker_fields: One raw worker entry straight from the JSON spec.
|
|
414
|
+
|
|
415
|
+
Raises:
|
|
416
|
+
ValueError: When the entry carries any key outside the accepted set.
|
|
417
|
+
"""
|
|
418
|
+
all_unknown_keys = set(all_worker_fields) - ALL_KNOWN_WORKER_SPEC_KEYS
|
|
419
|
+
if not all_unknown_keys:
|
|
420
|
+
return
|
|
421
|
+
joined_unknown = WORKER_SPEC_KEY_JOIN_SEPARATOR.join(sorted(all_unknown_keys))
|
|
422
|
+
joined_accepted = WORKER_SPEC_KEY_JOIN_SEPARATOR.join(
|
|
423
|
+
sorted(ALL_KNOWN_WORKER_SPEC_KEYS)
|
|
424
|
+
)
|
|
425
|
+
raise ValueError(
|
|
426
|
+
UNKNOWN_WORKER_KEY_ERROR_TEMPLATE.format(
|
|
427
|
+
unknown_keys=joined_unknown,
|
|
428
|
+
accepted_keys=joined_accepted,
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
92
433
|
@dataclass(frozen=True)
|
|
93
434
|
class WorkerSpec:
|
|
94
435
|
"""One worker entry from a batch specification."""
|
|
@@ -102,6 +443,20 @@ class WorkerSpec:
|
|
|
102
443
|
agent_name: str | None = None
|
|
103
444
|
|
|
104
445
|
|
|
446
|
+
@dataclass(frozen=True)
|
|
447
|
+
class AdvisorSpec:
|
|
448
|
+
"""Lead-supplied worker advisor binding configuration for one batch.
|
|
449
|
+
|
|
450
|
+
The launcher name is runtime-only: the committed default is the placeholder
|
|
451
|
+
``DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER``. The lead replaces it in the batch
|
|
452
|
+
specification; real account-scoped launcher names never land as constants.
|
|
453
|
+
"""
|
|
454
|
+
|
|
455
|
+
launcher: str
|
|
456
|
+
model: str = DEFAULT_ADVISOR_MODEL
|
|
457
|
+
effort: str = DEFAULT_ADVISOR_EFFORT
|
|
458
|
+
|
|
459
|
+
|
|
105
460
|
@dataclass(frozen=True)
|
|
106
461
|
class BatchSpec:
|
|
107
462
|
"""Full batch specification for one fleet launch."""
|
|
@@ -109,6 +464,7 @@ class BatchSpec:
|
|
|
109
464
|
role: str
|
|
110
465
|
should_ping: bool
|
|
111
466
|
all_workers: tuple[WorkerSpec, ...]
|
|
467
|
+
advisor: AdvisorSpec | None = None
|
|
112
468
|
|
|
113
469
|
|
|
114
470
|
@dataclass(frozen=True)
|
|
@@ -135,6 +491,9 @@ class WorkerReport:
|
|
|
135
491
|
leader_socket: str
|
|
136
492
|
prompt_path: str
|
|
137
493
|
debug_path: str
|
|
494
|
+
advisor_session_id: str | None = None
|
|
495
|
+
advisor_completion_signal: str | None = None
|
|
496
|
+
advisor_launcher: str | None = None
|
|
138
497
|
|
|
139
498
|
|
|
140
499
|
@dataclass(frozen=True)
|
|
@@ -290,6 +649,7 @@ def _require_timeout_within_bounds(raw_field: object) -> int:
|
|
|
290
649
|
|
|
291
650
|
|
|
292
651
|
def _parse_worker_entry(all_worker_fields: dict[str, object]) -> WorkerSpec:
|
|
652
|
+
_require_known_worker_keys(all_worker_fields)
|
|
293
653
|
role_name = _require_string(
|
|
294
654
|
_require_worker_field(all_worker_fields, WORKER_SPEC_ROLE_NAME_KEY),
|
|
295
655
|
WORKER_SPEC_ROLE_NAME_KEY,
|
|
@@ -348,7 +708,8 @@ def load_batch_spec(specification_path: Path) -> BatchSpec:
|
|
|
348
708
|
The validated batch specification.
|
|
349
709
|
|
|
350
710
|
Raises:
|
|
351
|
-
ValueError: When the JSON shape is invalid
|
|
711
|
+
ValueError: When the JSON shape is invalid, a required field is wrong,
|
|
712
|
+
or a worker entry carries a key outside the accepted set.
|
|
352
713
|
OSError: When the specification file cannot be read.
|
|
353
714
|
json.JSONDecodeError: When the file is not valid JSON.
|
|
354
715
|
"""
|
|
@@ -371,10 +732,12 @@ def load_batch_spec(specification_path: Path) -> BatchSpec:
|
|
|
371
732
|
if not isinstance(each_entry, dict):
|
|
372
733
|
raise ValueError("each worker must be an object")
|
|
373
734
|
all_parsed_workers.append(_parse_worker_entry(each_entry))
|
|
735
|
+
advisor_spec = _parse_advisor_spec(parsed_payload.get(BATCH_SPEC_ADVISOR_KEY))
|
|
374
736
|
return BatchSpec(
|
|
375
737
|
role=role,
|
|
376
738
|
should_ping=should_ping,
|
|
377
739
|
all_workers=tuple(all_parsed_workers),
|
|
740
|
+
advisor=advisor_spec,
|
|
378
741
|
)
|
|
379
742
|
|
|
380
743
|
|
|
@@ -422,11 +785,20 @@ def _write_assembled_prompt(
|
|
|
422
785
|
*,
|
|
423
786
|
worker_spec: WorkerSpec,
|
|
424
787
|
prompt_path: Path,
|
|
788
|
+
advisor_session_id: str | None = None,
|
|
789
|
+
advisor_spec: AdvisorSpec | None = None,
|
|
425
790
|
) -> None:
|
|
426
791
|
prompt_text = assemble_worker_prompt(
|
|
427
792
|
all_prompt_part_paths=worker_spec.all_prompt_part_paths,
|
|
428
793
|
tool_profile=worker_spec.tool_profile,
|
|
429
794
|
)
|
|
795
|
+
if advisor_session_id is not None and advisor_spec is not None:
|
|
796
|
+
header = ADVISOR_PROMPT_HEADER_TEMPLATE.format(
|
|
797
|
+
session_id=advisor_session_id,
|
|
798
|
+
model=advisor_spec.model,
|
|
799
|
+
effort=advisor_spec.effort,
|
|
800
|
+
)
|
|
801
|
+
prompt_text = f"{header}{prompt_text}"
|
|
430
802
|
prompt_path.write_text(prompt_text, encoding=UTF8_ENCODING)
|
|
431
803
|
|
|
432
804
|
|
|
@@ -460,6 +832,9 @@ def _worker_report(
|
|
|
460
832
|
classification: str,
|
|
461
833
|
is_ok: bool,
|
|
462
834
|
report_text: str,
|
|
835
|
+
advisor_session_id: str | None = None,
|
|
836
|
+
advisor_completion_signal: str | None = None,
|
|
837
|
+
advisor_launcher: str | None = None,
|
|
463
838
|
) -> WorkerReport:
|
|
464
839
|
with suppress(OSError):
|
|
465
840
|
_write_report_file(scratch_paths.report_path, report_text)
|
|
@@ -474,6 +849,9 @@ def _worker_report(
|
|
|
474
849
|
leader_socket=str(scratch_paths.leader_socket_path),
|
|
475
850
|
prompt_path=str(scratch_paths.prompt_path),
|
|
476
851
|
debug_path=str(scratch_paths.debug_path),
|
|
852
|
+
advisor_session_id=advisor_session_id,
|
|
853
|
+
advisor_completion_signal=advisor_completion_signal,
|
|
854
|
+
advisor_launcher=advisor_launcher,
|
|
477
855
|
)
|
|
478
856
|
|
|
479
857
|
|
|
@@ -509,28 +887,218 @@ def _error_report_for_exception(
|
|
|
509
887
|
)
|
|
510
888
|
|
|
511
889
|
|
|
890
|
+
|
|
891
|
+
def _bind_worker_advisor_session(
|
|
892
|
+
*,
|
|
893
|
+
advisor_spec: AdvisorSpec,
|
|
894
|
+
role_name: str,
|
|
895
|
+
all_used_session_ids: set[str],
|
|
896
|
+
session_id_lock: object | None,
|
|
897
|
+
) -> tuple[str, str]:
|
|
898
|
+
if session_id_lock is not None:
|
|
899
|
+
with session_id_lock: # type: ignore[attr-defined] # Lock protocol from threading.Lock
|
|
900
|
+
return bind_unique_worker_advisor(
|
|
901
|
+
advisor_spec=advisor_spec,
|
|
902
|
+
role_name=role_name,
|
|
903
|
+
all_used_session_ids=all_used_session_ids,
|
|
904
|
+
)
|
|
905
|
+
return bind_unique_worker_advisor(
|
|
906
|
+
advisor_spec=advisor_spec,
|
|
907
|
+
role_name=role_name,
|
|
908
|
+
all_used_session_ids=all_used_session_ids,
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def _advisor_blocked_report(
|
|
913
|
+
*,
|
|
914
|
+
worker_spec: WorkerSpec,
|
|
915
|
+
scratch_paths: WorkerScratchPaths,
|
|
916
|
+
report_text: str,
|
|
917
|
+
advisor_session_id: str | None = None,
|
|
918
|
+
advisor_launcher: str | None = None,
|
|
919
|
+
) -> WorkerReport:
|
|
920
|
+
return _worker_report(
|
|
921
|
+
worker_spec=worker_spec,
|
|
922
|
+
scratch_paths=scratch_paths,
|
|
923
|
+
returncode=WORKER_EXCEPTION_RETURN_CODE,
|
|
924
|
+
classification=CLASSIFICATION_ADVISOR_BLOCKED,
|
|
925
|
+
is_ok=False,
|
|
926
|
+
report_text=report_text,
|
|
927
|
+
advisor_session_id=advisor_session_id,
|
|
928
|
+
advisor_completion_signal=None,
|
|
929
|
+
advisor_launcher=advisor_launcher,
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def _attach_advisor_completion_if_configured(
|
|
935
|
+
*,
|
|
936
|
+
worker_spec: WorkerSpec,
|
|
937
|
+
scratch_paths: WorkerScratchPaths,
|
|
938
|
+
worker_report: WorkerReport,
|
|
939
|
+
advisor_spec: AdvisorSpec | None,
|
|
940
|
+
advisor_session_id: str | None,
|
|
941
|
+
advisor_launcher: str | None,
|
|
942
|
+
) -> WorkerReport:
|
|
943
|
+
if advisor_spec is None or advisor_session_id is None:
|
|
944
|
+
return worker_report
|
|
945
|
+
try:
|
|
946
|
+
completion_signal = obtain_advisor_completion_verdict(
|
|
947
|
+
advisor_spec=advisor_spec,
|
|
948
|
+
role_name=worker_spec.role_name,
|
|
949
|
+
session_id=advisor_session_id,
|
|
950
|
+
report_text=worker_report.report_text,
|
|
951
|
+
)
|
|
952
|
+
except ValueError as advisor_error:
|
|
953
|
+
return _advisor_blocked_report(
|
|
954
|
+
worker_spec=worker_spec,
|
|
955
|
+
scratch_paths=scratch_paths,
|
|
956
|
+
report_text=str(advisor_error),
|
|
957
|
+
advisor_session_id=advisor_session_id,
|
|
958
|
+
advisor_launcher=advisor_launcher,
|
|
959
|
+
)
|
|
960
|
+
return _worker_report(
|
|
961
|
+
worker_spec=worker_spec,
|
|
962
|
+
scratch_paths=scratch_paths,
|
|
963
|
+
returncode=worker_report.returncode,
|
|
964
|
+
classification=worker_report.classification,
|
|
965
|
+
is_ok=worker_report.is_ok,
|
|
966
|
+
report_text=worker_report.report_text,
|
|
967
|
+
advisor_session_id=advisor_session_id,
|
|
968
|
+
advisor_completion_signal=completion_signal,
|
|
969
|
+
advisor_launcher=advisor_launcher,
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def _maybe_bind_advisor_for_worker(
|
|
974
|
+
*,
|
|
975
|
+
worker_spec: WorkerSpec,
|
|
976
|
+
scratch_paths: WorkerScratchPaths,
|
|
977
|
+
advisor_spec: AdvisorSpec | None,
|
|
978
|
+
all_used_session_ids: set[str] | None,
|
|
979
|
+
session_id_lock: object | None,
|
|
980
|
+
) -> tuple[str | None, str | None, WorkerReport | None]:
|
|
981
|
+
if advisor_spec is None:
|
|
982
|
+
return None, None, None
|
|
983
|
+
used_ids = all_used_session_ids if all_used_session_ids is not None else set()
|
|
984
|
+
advisor_session_id, pre_signal = _bind_worker_advisor_session(
|
|
985
|
+
advisor_spec=advisor_spec,
|
|
986
|
+
role_name=worker_spec.role_name,
|
|
987
|
+
all_used_session_ids=used_ids,
|
|
988
|
+
session_id_lock=session_id_lock,
|
|
989
|
+
)
|
|
990
|
+
if (
|
|
991
|
+
advisor_session_id == PENDING_BIND_SENTINEL
|
|
992
|
+
or pre_signal == PENDING_BIND_SENTINEL
|
|
993
|
+
):
|
|
994
|
+
blocked = _advisor_blocked_report(
|
|
995
|
+
worker_spec=worker_spec,
|
|
996
|
+
scratch_paths=scratch_paths,
|
|
997
|
+
report_text=f"{PENDING_BIND_SENTINEL} sentinel before launch",
|
|
998
|
+
advisor_session_id=advisor_session_id,
|
|
999
|
+
advisor_launcher=advisor_spec.launcher,
|
|
1000
|
+
)
|
|
1001
|
+
return advisor_session_id, advisor_spec.launcher, blocked
|
|
1002
|
+
return advisor_session_id, advisor_spec.launcher, None
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def _map_launch_exception(
|
|
1006
|
+
*,
|
|
1007
|
+
worker_spec: WorkerSpec,
|
|
1008
|
+
scratch_paths: WorkerScratchPaths,
|
|
1009
|
+
raised_exception: BaseException,
|
|
1010
|
+
advisor_spec: AdvisorSpec | None,
|
|
1011
|
+
advisor_session_id: str | None,
|
|
1012
|
+
advisor_launcher: str | None,
|
|
1013
|
+
) -> WorkerReport:
|
|
1014
|
+
if advisor_spec is not None and (
|
|
1015
|
+
isinstance(raised_exception, AdvisorFailureError)
|
|
1016
|
+
or PENDING_BIND_SENTINEL in str(raised_exception)
|
|
1017
|
+
or "advisor" in str(raised_exception).lower()
|
|
1018
|
+
):
|
|
1019
|
+
return _advisor_blocked_report(
|
|
1020
|
+
worker_spec=worker_spec,
|
|
1021
|
+
scratch_paths=scratch_paths,
|
|
1022
|
+
report_text=f"{type(raised_exception).__name__}: {raised_exception}",
|
|
1023
|
+
advisor_session_id=advisor_session_id,
|
|
1024
|
+
advisor_launcher=advisor_launcher,
|
|
1025
|
+
)
|
|
1026
|
+
return _error_report_for_exception(
|
|
1027
|
+
worker_spec=worker_spec,
|
|
1028
|
+
scratch_paths=scratch_paths,
|
|
1029
|
+
raised_exception=raised_exception,
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def _run_worker_body(
|
|
1034
|
+
*,
|
|
1035
|
+
worker_spec: WorkerSpec,
|
|
1036
|
+
scratch_paths: WorkerScratchPaths,
|
|
1037
|
+
run_state_directory: Path,
|
|
1038
|
+
advisor_spec: AdvisorSpec | None,
|
|
1039
|
+
advisor_session_id: str | None,
|
|
1040
|
+
advisor_launcher: str | None,
|
|
1041
|
+
) -> WorkerReport:
|
|
1042
|
+
_write_assembled_prompt(
|
|
1043
|
+
worker_spec=worker_spec,
|
|
1044
|
+
prompt_path=scratch_paths.prompt_path,
|
|
1045
|
+
advisor_session_id=advisor_session_id,
|
|
1046
|
+
advisor_spec=advisor_spec,
|
|
1047
|
+
)
|
|
1048
|
+
outcome = _invoke_worker(
|
|
1049
|
+
worker_spec=worker_spec,
|
|
1050
|
+
scratch_paths=scratch_paths,
|
|
1051
|
+
run_state_directory=run_state_directory,
|
|
1052
|
+
)
|
|
1053
|
+
worker_report = _build_worker_report(
|
|
1054
|
+
worker_spec=worker_spec,
|
|
1055
|
+
outcome=outcome,
|
|
1056
|
+
scratch_paths=scratch_paths,
|
|
1057
|
+
)
|
|
1058
|
+
return _attach_advisor_completion_if_configured(
|
|
1059
|
+
worker_spec=worker_spec,
|
|
1060
|
+
scratch_paths=scratch_paths,
|
|
1061
|
+
worker_report=worker_report,
|
|
1062
|
+
advisor_spec=advisor_spec,
|
|
1063
|
+
advisor_session_id=advisor_session_id,
|
|
1064
|
+
advisor_launcher=advisor_launcher,
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
|
|
512
1068
|
def _launch_one_worker(
|
|
513
1069
|
*,
|
|
514
1070
|
worker_spec: WorkerSpec,
|
|
515
1071
|
worker_index: int,
|
|
516
1072
|
run_state_directory: Path,
|
|
1073
|
+
advisor_spec: AdvisorSpec | None = None,
|
|
1074
|
+
all_used_session_ids: set[str] | None = None,
|
|
1075
|
+
session_id_lock: object | None = None,
|
|
517
1076
|
) -> WorkerReport:
|
|
518
1077
|
batch_sleep(worker_index * WORKER_STAGGER_SECONDS)
|
|
519
1078
|
scratch_paths = _mint_worker_scratch_paths(run_state_directory)
|
|
1079
|
+
advisor_session_id: str | None = None
|
|
1080
|
+
advisor_launcher: str | None = None
|
|
520
1081
|
try:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1082
|
+
(
|
|
1083
|
+
advisor_session_id,
|
|
1084
|
+
advisor_launcher,
|
|
1085
|
+
early_block,
|
|
1086
|
+
) = _maybe_bind_advisor_for_worker(
|
|
526
1087
|
worker_spec=worker_spec,
|
|
527
1088
|
scratch_paths=scratch_paths,
|
|
528
|
-
|
|
1089
|
+
advisor_spec=advisor_spec,
|
|
1090
|
+
all_used_session_ids=all_used_session_ids,
|
|
1091
|
+
session_id_lock=session_id_lock,
|
|
529
1092
|
)
|
|
530
|
-
|
|
1093
|
+
if early_block is not None:
|
|
1094
|
+
return early_block
|
|
1095
|
+
return _run_worker_body(
|
|
531
1096
|
worker_spec=worker_spec,
|
|
532
|
-
outcome=outcome,
|
|
533
1097
|
scratch_paths=scratch_paths,
|
|
1098
|
+
run_state_directory=run_state_directory,
|
|
1099
|
+
advisor_spec=advisor_spec,
|
|
1100
|
+
advisor_session_id=advisor_session_id,
|
|
1101
|
+
advisor_launcher=advisor_launcher,
|
|
534
1102
|
)
|
|
535
1103
|
except (
|
|
536
1104
|
OSError,
|
|
@@ -539,14 +1107,19 @@ def _launch_one_worker(
|
|
|
539
1107
|
TypeError,
|
|
540
1108
|
AttributeError,
|
|
541
1109
|
LookupError,
|
|
1110
|
+
AdvisorFailureError,
|
|
542
1111
|
) as raised_exception:
|
|
543
|
-
return
|
|
1112
|
+
return _map_launch_exception(
|
|
544
1113
|
worker_spec=worker_spec,
|
|
545
1114
|
scratch_paths=scratch_paths,
|
|
546
1115
|
raised_exception=raised_exception,
|
|
1116
|
+
advisor_spec=advisor_spec,
|
|
1117
|
+
advisor_session_id=advisor_session_id,
|
|
1118
|
+
advisor_launcher=advisor_launcher,
|
|
547
1119
|
)
|
|
548
1120
|
|
|
549
1121
|
|
|
1122
|
+
|
|
550
1123
|
def run_grok_batch(
|
|
551
1124
|
*,
|
|
552
1125
|
batch_spec: BatchSpec,
|
|
@@ -580,6 +1153,8 @@ def run_grok_batch(
|
|
|
580
1153
|
preflight_reason=None,
|
|
581
1154
|
all_worker_reports=(),
|
|
582
1155
|
)
|
|
1156
|
+
all_used_session_ids: set[str] = set()
|
|
1157
|
+
session_id_lock = threading.Lock()
|
|
583
1158
|
with ThreadPoolExecutor(max_workers=worker_count) as executor:
|
|
584
1159
|
all_futures = [
|
|
585
1160
|
executor.submit(
|
|
@@ -587,6 +1162,9 @@ def run_grok_batch(
|
|
|
587
1162
|
worker_spec=each_worker,
|
|
588
1163
|
worker_index=each_index,
|
|
589
1164
|
run_state_directory=run_state_directory,
|
|
1165
|
+
advisor_spec=batch_spec.advisor,
|
|
1166
|
+
all_used_session_ids=all_used_session_ids,
|
|
1167
|
+
session_id_lock=session_id_lock,
|
|
590
1168
|
)
|
|
591
1169
|
for each_index, each_worker in enumerate(batch_spec.all_workers)
|
|
592
1170
|
]
|
|
@@ -621,6 +1199,9 @@ def batch_summary_as_dict(batch_summary: BatchSummary) -> dict[str, object]:
|
|
|
621
1199
|
SUMMARY_LEADER_SOCKET_KEY: each_report.leader_socket,
|
|
622
1200
|
SUMMARY_PROMPT_FILE_KEY: each_report.prompt_path,
|
|
623
1201
|
SUMMARY_DEBUG_FILE_KEY: each_report.debug_path,
|
|
1202
|
+
SUMMARY_ADVISOR_SESSION_ID_KEY: each_report.advisor_session_id,
|
|
1203
|
+
SUMMARY_ADVISOR_SIGNAL_KEY: each_report.advisor_completion_signal,
|
|
1204
|
+
SUMMARY_ADVISOR_LAUNCHER_KEY: each_report.advisor_launcher,
|
|
624
1205
|
}
|
|
625
1206
|
for each_report in batch_summary.all_worker_reports
|
|
626
1207
|
]
|