claude-dev-env 2.8.0 → 2.10.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 +8 -2
- package/_shared/pr-loop/CLAUDE.md +1 -1
- package/_shared/pr-loop/audit-contract.md +17 -6
- package/_shared/pr-loop/audit-reply-template.md +4 -4
- package/_shared/pr-loop/code-rules-gate.md +3 -5
- package/_shared/pr-loop/fix-protocol.md +2 -3
- package/_shared/pr-loop/gh-payloads.md +1 -1
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/README.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
- package/_shared/pr-loop/scripts/preflight.py +9 -4
- package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
- package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
- package/_shared/pr-loop/state-schema.md +5 -14
- package/agents/CLAUDE.md +2 -2
- package/agents/clean-coder.md +62 -562
- package/agents/code-quality-agent.md +10 -2
- package/agents/code-verifier.md +1 -1
- package/agents/test_agent_frontmatter.py +58 -40
- package/audit-rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/audit-categories.json +704 -0
- package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
- package/bin/CLAUDE.md +16 -5
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/install-plan.mjs +402 -0
- package/bin/install-transaction.mjs +455 -0
- package/bin/install.mjs +593 -147
- package/bin/install.plan.test.mjs +194 -0
- package/bin/install.profile-root.test.mjs +154 -0
- package/bin/install.profiles.test.mjs +253 -0
- package/bin/install.settings-defaults.test.mjs +200 -0
- package/bin/install.transaction.test.mjs +400 -0
- package/bin/install.uninstall-transaction.test.mjs +418 -0
- package/bin/merge_managed_permissions.mjs +130 -0
- package/bin/resolve-install-root.mjs +181 -0
- package/bin/select-install-targets.mjs +401 -0
- package/commands/CLAUDE.md +0 -2
- package/docs/CODE_RULES.md +4 -2
- package/docs/references/CLAUDE.md +3 -2
- package/docs/references/advisor-tool.md +44 -6
- package/docs/references/prose-style-enforcement.md +25 -0
- package/docs/references/team-advisor-skill.md +14 -8
- package/hooks/blocking/CLAUDE.md +6 -6
- package/hooks/blocking/_path_setup.py +9 -5
- package/hooks/blocking/code_rules_docstrings.py +124 -30
- package/hooks/blocking/code_rules_enforcer.py +161 -16
- package/hooks/blocking/code_rules_shared.py +40 -23
- package/hooks/blocking/config/CLAUDE.md +3 -5
- package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
- package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
- package/hooks/blocking/eli11_reply_enforcer.py +70 -113
- package/hooks/blocking/hedging_language_blocker.py +103 -20
- package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
- package/hooks/blocking/intent_only_ending_blocker.py +6 -0
- package/hooks/blocking/plain_language_blocker.py +139 -20
- package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
- package/hooks/blocking/state_description_blocker.py +7 -1
- package/hooks/blocking/tdd_enforcer.py +8 -0
- package/hooks/blocking/test__path_setup.py +28 -0
- package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
- package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
- package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
- package/hooks/blocking/test_hedging_language_blocker.py +120 -1
- package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
- package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
- package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
- package/hooks/blocking/test_plain_language_blocker.py +129 -19
- package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
- package/hooks/blocking/test_state_description_blocker.py +45 -2
- package/hooks/blocking/test_stop_dispatcher.py +11 -7
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
- package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
- package/hooks/hooks.json +15 -0
- package/hooks/hooks_constants/CLAUDE.md +14 -3
- package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
- package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
- package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
- package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
- package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
- package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/project_paths_reader.py +31 -4
- package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
- package/hooks/hooks_constants/pytest_invocation.py +354 -0
- package/hooks/hooks_constants/session_start_injector.py +163 -0
- package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
- package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
- package/hooks/hooks_constants/shell_command_segments.py +5 -0
- package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
- package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
- package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
- package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
- package/hooks/hooks_constants/test_session_start_injector.py +168 -0
- package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
- package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
- package/hooks/observability/CLAUDE.md +2 -0
- package/hooks/observability/prose_matcher_advisory.py +237 -0
- package/hooks/observability/test_prose_matcher_advisory.py +143 -0
- package/hooks/session/CLAUDE.md +9 -1
- package/hooks/session/_path_setup.py +13 -0
- package/hooks/session/issue_tracker_session_starter.py +135 -0
- package/hooks/session/orchestrator_auto_starter.py +100 -0
- package/hooks/session/test__path_setup.py +28 -0
- package/hooks/session/test_issue_tracker_session_starter.py +104 -0
- package/hooks/session/test_orchestrator_auto_starter.py +99 -0
- package/hooks/session/test_working_style_prompt.py +47 -0
- package/hooks/session/untracked_repo_detector.py +1 -24
- package/hooks/session/working_style_prompt.py +36 -0
- package/hooks/validators/_path_setup.py +19 -0
- package/hooks/validators/run_all_validators.py +8 -13
- package/installable-surfaces.manifest.json +21 -0
- package/output-styles/CLAUDE.md +15 -0
- package/package.json +5 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/code-standards.md +33 -7
- package/rules/durable-post-artifacts.md +2 -2
- package/rules/eli11-replies.md +7 -2
- package/rules/hedging-claims.md +4 -2
- package/rules/long-horizon-autonomy.md +3 -1
- package/rules/opus5-communication-contract.md +45 -0
- package/rules/plain-language.md +2 -2
- package/rules/research-mode.md +1 -1
- package/scripts/CLAUDE.md +13 -2
- package/scripts/Sync-RepoMain.ps1 +215 -0
- package/scripts/active_capability_references.py +218 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
- package/scripts/claude_chain_runner.py +394 -6
- package/scripts/claude_chain_usage.py +1 -1
- package/scripts/codex_compat_materializer.py +105 -85
- package/scripts/dev_env_scripts_constants/CLAUDE.md +3 -1
- package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
- package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
- package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -0
- package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
- package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
- package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
- package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
- package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
- package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
- package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
- package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
- package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
- package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
- package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
- package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
- package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
- package/scripts/spawn_grok_batch.py +556 -9
- package/scripts/test_active_capability_references.py +108 -0
- package/scripts/test_claude_chain_runner.py +414 -82
- package/scripts/test_claude_chain_usage.py +12 -12
- package/scripts/test_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- package/scripts/test_resolve_worker_spawn.py +2 -2
- package/scripts/test_spawn_grok_batch.py +295 -0
- package/scripts/test_verify_installable_package.py +208 -0
- package/scripts/tests/test_codex_compat_materializer.py +33 -0
- package/scripts/verify_installable_package.py +612 -0
- package/settings.json +10 -0
- package/skills/CLAUDE.md +6 -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 +9 -1
- package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
- package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
- package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
- package/skills/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -0
- package/skills/autoconverge/reference/convergence.md +2 -1
- package/skills/autoconverge/reference/stop-conditions.md +5 -3
- package/skills/beat-sheet/SKILL.md +54 -0
- package/skills/beat-sheet/reference/visual-beats.md +29 -0
- package/skills/bugteam/CONSTRAINTS.md +4 -4
- package/skills/bugteam/EXAMPLES.md +1 -1
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/e-code-review/SKILL.md +29 -3
- package/skills/e-code-review/reference/effort-evaluation.md +35 -0
- package/skills/e-code-review/reference/medium.md +15 -4
- package/skills/e-code-review/reference/runner-selection.md +40 -0
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
- 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/finding_pipeline_constants.py +49 -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/effort_defaults_evidence.json +186 -0
- package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
- package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
- package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
- package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
- package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
- package/skills/e-code-review/scripts/grok_code_review.py +230 -0
- package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
- package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
- package/skills/e-code-review/scripts/test_grok_code_review.py +289 -0
- package/skills/grok-spawn/SKILL.md +5 -0
- package/skills/orchestrator/SKILL.md +5 -0
- package/skills/plan-to-pr/scripts/create_packet.py +4 -4
- package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
- package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
- package/skills/plan-to-pr/scripts/validate_run.py +4 -1
- package/skills/pr-converge/scripts/check_convergence.py +21 -19
- package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
- package/skills/pr-converge/scripts/conftest.py +35 -0
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/rebase/SKILL.md +15 -3
- package/skills/reviewer-gates/SKILL.md +2 -2
- package/skills/show/SKILL.md +51 -0
- package/skills/show/references/accessibility.md +7 -0
- package/skills/show/references/art.md +3 -0
- package/skills/show/references/charts.md +3 -0
- package/skills/show/references/core-design.md +14 -0
- package/skills/show/references/erds.md +3 -0
- package/skills/show/references/flowcharts.md +3 -0
- package/skills/show/references/host-and-html.md +3 -0
- package/skills/show/references/illustrative-diagrams.md +10 -0
- package/skills/show/references/interaction.md +3 -0
- package/skills/show/references/mockups.md +3 -0
- package/skills/show/references/quality-gates.md +7 -0
- package/skills/show/references/structural-diagrams.md +3 -0
- package/skills/show/references/subject-inventory.md +21 -0
- package/skills/show/references/svg-contract.md +22 -0
- package/skills/show/routing.yaml +30 -0
- package/skills/show/samples/pr1262-v2.svg +222 -0
- package/skills/show/scripts/README.md +6 -0
- package/skills/show/scripts/validate-artifact.py +91 -0
- package/skills/show/scripts/validate-package.py +18 -0
- package/skills/show/templates/html-widget.html +4 -0
- package/skills/show/templates/svg-base.svg +19 -0
- package/skills/show/tests/fixtures/css-var.svg +6 -0
- package/skills/show/tests/fixtures/dead-ref.svg +7 -0
- package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
- package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
- package/skills/show/tests/fixtures/invalid.svg +1 -0
- package/skills/show/tests/fixtures/large-canvas.svg +21 -0
- package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
- package/skills/show/tests/fixtures/valid.html +1 -0
- package/skills/show/tests/test_validate-artifact.py +74 -0
- package/skills/show/tests/test_validators.py +59 -0
- package/skills/show/workflows/create-visual.md +13 -0
- package/skills/show/workflows/review-visual.md +20 -0
- package/skills/split-pr/SKILL.md +85 -0
- package/skills/split-pr/reference/path-layers.md +16 -0
- package/skills/split-pr/reference/proposal-format.md +15 -0
- package/skills/split-pr/reference/split-further-loop.md +10 -0
- package/skills/split-pr/reference/splitting-principles.md +26 -0
- package/skills/split-pr/scripts/analyze_pr.py +279 -0
- package/skills/split-pr/scripts/categorize_files.py +106 -0
- package/skills/split-pr/scripts/config/__init__.py +1 -0
- package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
- package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
- package/skills/split-pr/scripts/config/packing_constants.py +61 -0
- package/skills/split-pr/scripts/config/plan_constants.py +49 -0
- package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
- package/skills/split-pr/scripts/execute_split_slices.py +82 -0
- package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
- package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
- package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
- package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
- package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
- package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
- package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
- package/skills/split-pr/scripts/split_pr_title.py +41 -0
- package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
- package/skills/split-pr/scripts/test_categorize_files.py +55 -0
- package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
- package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
- package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
- package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
- package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
- package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
- package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
- package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
- package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
- package/skills/split-pr/scripts/test_verify_plan.py +56 -0
- package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
- package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
- package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
- package/skills/split-pr/scripts/verify_plan.py +139 -0
- package/skills/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -44
- package/system-prompts/software-engineer.xml +17 -5
- package/commands/initialize.md +0 -90
- package/commands/stubcheck.md +0 -88
- 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
|
|
@@ -82,16 +84,323 @@ from dev_env_scripts_constants.grok_worker_constants import (
|
|
|
82
84
|
WORKER_SPEC_ROLE_NAME_KEY,
|
|
83
85
|
WORKER_SPEC_TIMEOUT_KEY,
|
|
84
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,
|
|
85
115
|
)
|
|
86
116
|
from dev_env_scripts_constants.timing import WORKER_STAGGER_SECONDS
|
|
87
117
|
from grok_headless_runner import GrokRunnerOutcome, run_headless_worker
|
|
88
118
|
from grok_worker_preflight import PreflightOutcome, run_preflight
|
|
89
119
|
|
|
120
|
+
class AdvisorFailureError(ValueError):
|
|
121
|
+
"""Raised when an advisor bind, resume, or launcher call fails closed."""
|
|
122
|
+
|
|
123
|
+
|
|
90
124
|
batch_sleep = time.sleep
|
|
91
125
|
batch_headless_runner = run_headless_worker
|
|
92
126
|
batch_preflight = run_preflight
|
|
93
127
|
|
|
94
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
|
+
Raises:
|
|
243
|
+
AdvisorFailureError: When the launcher is missing or times out.
|
|
244
|
+
"""
|
|
245
|
+
all_command_arguments = [
|
|
246
|
+
launcher,
|
|
247
|
+
ADVISOR_CLI_PRINT_FLAG,
|
|
248
|
+
ADVISOR_CLI_MODEL_FLAG,
|
|
249
|
+
model,
|
|
250
|
+
ADVISOR_CLI_EFFORT_FLAG,
|
|
251
|
+
effort,
|
|
252
|
+
ADVISOR_CLI_OUTPUT_FORMAT_FLAG,
|
|
253
|
+
ADVISOR_CLI_OUTPUT_FORMAT_JSON,
|
|
254
|
+
]
|
|
255
|
+
if session_id is not None:
|
|
256
|
+
all_command_arguments.extend([ADVISOR_CLI_RESUME_FLAG, session_id])
|
|
257
|
+
try:
|
|
258
|
+
completion = subprocess.run(
|
|
259
|
+
all_command_arguments,
|
|
260
|
+
input=prompt_text,
|
|
261
|
+
capture_output=True,
|
|
262
|
+
text=True,
|
|
263
|
+
encoding=UTF8_ENCODING,
|
|
264
|
+
check=False,
|
|
265
|
+
timeout=MAXIMUM_ADVISOR_TIMEOUT_SECONDS,
|
|
266
|
+
)
|
|
267
|
+
except FileNotFoundError as missing_launcher:
|
|
268
|
+
raise AdvisorFailureError(
|
|
269
|
+
f"advisor launcher not found: {launcher}"
|
|
270
|
+
) from missing_launcher
|
|
271
|
+
except subprocess.TimeoutExpired as timed_out:
|
|
272
|
+
raise AdvisorFailureError(
|
|
273
|
+
f"advisor launcher timed out after {MAXIMUM_ADVISOR_TIMEOUT_SECONDS}s"
|
|
274
|
+
) from timed_out
|
|
275
|
+
stdout_text = completion.stdout or ""
|
|
276
|
+
resolved_session = _session_id_from_advisor_stdout(stdout_text)
|
|
277
|
+
if session_id is not None and resolved_session is None:
|
|
278
|
+
resolved_session = session_id
|
|
279
|
+
advisor_body_text = _advisor_body_text_from_stdout(stdout_text)
|
|
280
|
+
return resolved_session, advisor_body_text, completion.returncode
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
batch_invoke_advisor = invoke_advisor_launcher
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def bind_unique_worker_advisor(
|
|
287
|
+
*,
|
|
288
|
+
advisor_spec: AdvisorSpec,
|
|
289
|
+
role_name: str,
|
|
290
|
+
all_used_session_ids: set[str],
|
|
291
|
+
) -> tuple[str, str]:
|
|
292
|
+
"""Bind one unique advisor session for a worker; refuse duplicates and sentinels.
|
|
293
|
+
|
|
294
|
+
::
|
|
295
|
+
|
|
296
|
+
bind_unique_worker_advisor(...)
|
|
297
|
+
ok: returns (session_id, ENDORSE)
|
|
298
|
+
flag: duplicate session id across workers raises ValueError
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
advisor_spec: Lead-supplied launcher/model/effort.
|
|
302
|
+
role_name: Worker role used in the bind prompt.
|
|
303
|
+
all_used_session_ids: Sessions already issued in this batch.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
``(session_id, opening_signal)``.
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
ValueError: On bind failure, sentinel, missing signal, or duplicate id.
|
|
310
|
+
"""
|
|
311
|
+
if advisor_spec.launcher == DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER:
|
|
312
|
+
raise ValueError(
|
|
313
|
+
"advisor.launcher still holds the placeholder; supply a real launcher in the batch spec"
|
|
314
|
+
)
|
|
315
|
+
if advisor_spec.launcher == PENDING_BIND_SENTINEL:
|
|
316
|
+
raise ValueError(f"advisor.launcher is {PENDING_BIND_SENTINEL}")
|
|
317
|
+
prompt_text = ADVISOR_BIND_PROMPT_TEMPLATE.format(role_name=role_name)
|
|
318
|
+
session_id, advisor_body_text, returncode = batch_invoke_advisor(
|
|
319
|
+
launcher=advisor_spec.launcher,
|
|
320
|
+
model=advisor_spec.model,
|
|
321
|
+
effort=advisor_spec.effort,
|
|
322
|
+
prompt_text=prompt_text,
|
|
323
|
+
)
|
|
324
|
+
if returncode != 0 or not session_id or session_id == PENDING_BIND_SENTINEL:
|
|
325
|
+
raise AdvisorFailureError(
|
|
326
|
+
f"advisor bind failed for role {role_name}: returncode={returncode}"
|
|
327
|
+
)
|
|
328
|
+
if session_id in all_used_session_ids:
|
|
329
|
+
raise AdvisorFailureError(
|
|
330
|
+
f"duplicate advisor session id {session_id} for role {role_name}"
|
|
331
|
+
)
|
|
332
|
+
signal = extract_advisor_signal(advisor_body_text)
|
|
333
|
+
if signal is None:
|
|
334
|
+
raise ValueError(f"malformed advisor signal for role {role_name}")
|
|
335
|
+
if signal == ADVISOR_SIGNAL_STOP:
|
|
336
|
+
raise ValueError(f"advisor STOP for role {role_name}")
|
|
337
|
+
if signal != ADVISOR_SIGNAL_ENDORSE:
|
|
338
|
+
raise ValueError(
|
|
339
|
+
f"advisor pre-dispatch signal {signal} for role {role_name}; require ENDORSE"
|
|
340
|
+
)
|
|
341
|
+
all_used_session_ids.add(session_id)
|
|
342
|
+
return session_id, signal
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def obtain_advisor_completion_verdict(
|
|
346
|
+
*,
|
|
347
|
+
advisor_spec: AdvisorSpec,
|
|
348
|
+
role_name: str,
|
|
349
|
+
session_id: str,
|
|
350
|
+
report_text: str,
|
|
351
|
+
) -> str:
|
|
352
|
+
"""Resume the same advisor session until ENDORSE or the correction cap.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
advisor_spec: Lead-supplied launcher/model/effort.
|
|
356
|
+
role_name: Worker role name.
|
|
357
|
+
session_id: Session from pre-dispatch bind.
|
|
358
|
+
report_text: Worker report body under review.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
The final accepted signal (``ENDORSE``).
|
|
362
|
+
|
|
363
|
+
Raises:
|
|
364
|
+
ValueError: On STOP, malformed signal, or correction cap exceeded.
|
|
365
|
+
"""
|
|
366
|
+
if session_id == PENDING_BIND_SENTINEL:
|
|
367
|
+
raise ValueError(f"advisor session_id is {PENDING_BIND_SENTINEL}")
|
|
368
|
+
correction_count = 0
|
|
369
|
+
while True:
|
|
370
|
+
prompt_text = ADVISOR_VERDICT_PROMPT_TEMPLATE.format(
|
|
371
|
+
role_name=role_name,
|
|
372
|
+
session_id=session_id,
|
|
373
|
+
report_text=report_text,
|
|
374
|
+
)
|
|
375
|
+
_, advisor_body_text, returncode = batch_invoke_advisor(
|
|
376
|
+
launcher=advisor_spec.launcher,
|
|
377
|
+
model=advisor_spec.model,
|
|
378
|
+
effort=advisor_spec.effort,
|
|
379
|
+
prompt_text=prompt_text,
|
|
380
|
+
session_id=session_id,
|
|
381
|
+
)
|
|
382
|
+
if returncode != 0:
|
|
383
|
+
raise ValueError(
|
|
384
|
+
f"advisor resume failed for role {role_name}: returncode={returncode}"
|
|
385
|
+
)
|
|
386
|
+
signal = extract_advisor_signal(advisor_body_text)
|
|
387
|
+
if signal is None:
|
|
388
|
+
raise ValueError(f"malformed advisor signal for role {role_name}")
|
|
389
|
+
if signal == ADVISOR_SIGNAL_ENDORSE:
|
|
390
|
+
return signal
|
|
391
|
+
if signal == ADVISOR_SIGNAL_STOP:
|
|
392
|
+
raise ValueError(f"advisor STOP for role {role_name}")
|
|
393
|
+
if signal in (ADVISOR_SIGNAL_CORRECTION, ADVISOR_SIGNAL_PLAN):
|
|
394
|
+
correction_count += 1
|
|
395
|
+
if correction_count > MAXIMUM_WORKER_ADVISOR_CORRECTIONS:
|
|
396
|
+
raise ValueError(
|
|
397
|
+
f"advisor correction cap exceeded for role {role_name}"
|
|
398
|
+
)
|
|
399
|
+
continue
|
|
400
|
+
raise ValueError(f"unexpected advisor signal {signal} for role {role_name}")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
95
404
|
def _require_known_worker_keys(all_worker_fields: dict[str, object]) -> None:
|
|
96
405
|
"""Reject a worker entry carrying a key the launcher does not accept.
|
|
97
406
|
|
|
@@ -137,6 +446,20 @@ class WorkerSpec:
|
|
|
137
446
|
agent_name: str | None = None
|
|
138
447
|
|
|
139
448
|
|
|
449
|
+
@dataclass(frozen=True)
|
|
450
|
+
class AdvisorSpec:
|
|
451
|
+
"""Lead-supplied worker advisor binding configuration for one batch.
|
|
452
|
+
|
|
453
|
+
The launcher name is runtime-only: the committed default is the placeholder
|
|
454
|
+
``DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER``. The lead replaces it in the batch
|
|
455
|
+
specification; real account-scoped launcher names never land as constants.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
launcher: str
|
|
459
|
+
model: str = DEFAULT_ADVISOR_MODEL
|
|
460
|
+
effort: str = DEFAULT_ADVISOR_EFFORT
|
|
461
|
+
|
|
462
|
+
|
|
140
463
|
@dataclass(frozen=True)
|
|
141
464
|
class BatchSpec:
|
|
142
465
|
"""Full batch specification for one fleet launch."""
|
|
@@ -144,6 +467,7 @@ class BatchSpec:
|
|
|
144
467
|
role: str
|
|
145
468
|
should_ping: bool
|
|
146
469
|
all_workers: tuple[WorkerSpec, ...]
|
|
470
|
+
advisor: AdvisorSpec | None = None
|
|
147
471
|
|
|
148
472
|
|
|
149
473
|
@dataclass(frozen=True)
|
|
@@ -170,6 +494,9 @@ class WorkerReport:
|
|
|
170
494
|
leader_socket: str
|
|
171
495
|
prompt_path: str
|
|
172
496
|
debug_path: str
|
|
497
|
+
advisor_session_id: str | None = None
|
|
498
|
+
advisor_completion_signal: str | None = None
|
|
499
|
+
advisor_launcher: str | None = None
|
|
173
500
|
|
|
174
501
|
|
|
175
502
|
@dataclass(frozen=True)
|
|
@@ -408,10 +735,12 @@ def load_batch_spec(specification_path: Path) -> BatchSpec:
|
|
|
408
735
|
if not isinstance(each_entry, dict):
|
|
409
736
|
raise ValueError("each worker must be an object")
|
|
410
737
|
all_parsed_workers.append(_parse_worker_entry(each_entry))
|
|
738
|
+
advisor_spec = _parse_advisor_spec(parsed_payload.get(BATCH_SPEC_ADVISOR_KEY))
|
|
411
739
|
return BatchSpec(
|
|
412
740
|
role=role,
|
|
413
741
|
should_ping=should_ping,
|
|
414
742
|
all_workers=tuple(all_parsed_workers),
|
|
743
|
+
advisor=advisor_spec,
|
|
415
744
|
)
|
|
416
745
|
|
|
417
746
|
|
|
@@ -459,11 +788,20 @@ def _write_assembled_prompt(
|
|
|
459
788
|
*,
|
|
460
789
|
worker_spec: WorkerSpec,
|
|
461
790
|
prompt_path: Path,
|
|
791
|
+
advisor_session_id: str | None = None,
|
|
792
|
+
advisor_spec: AdvisorSpec | None = None,
|
|
462
793
|
) -> None:
|
|
463
794
|
prompt_text = assemble_worker_prompt(
|
|
464
795
|
all_prompt_part_paths=worker_spec.all_prompt_part_paths,
|
|
465
796
|
tool_profile=worker_spec.tool_profile,
|
|
466
797
|
)
|
|
798
|
+
if advisor_session_id is not None and advisor_spec is not None:
|
|
799
|
+
header = ADVISOR_PROMPT_HEADER_TEMPLATE.format(
|
|
800
|
+
session_id=advisor_session_id,
|
|
801
|
+
model=advisor_spec.model,
|
|
802
|
+
effort=advisor_spec.effort,
|
|
803
|
+
)
|
|
804
|
+
prompt_text = f"{header}{prompt_text}"
|
|
467
805
|
prompt_path.write_text(prompt_text, encoding=UTF8_ENCODING)
|
|
468
806
|
|
|
469
807
|
|
|
@@ -497,6 +835,9 @@ def _worker_report(
|
|
|
497
835
|
classification: str,
|
|
498
836
|
is_ok: bool,
|
|
499
837
|
report_text: str,
|
|
838
|
+
advisor_session_id: str | None = None,
|
|
839
|
+
advisor_completion_signal: str | None = None,
|
|
840
|
+
advisor_launcher: str | None = None,
|
|
500
841
|
) -> WorkerReport:
|
|
501
842
|
with suppress(OSError):
|
|
502
843
|
_write_report_file(scratch_paths.report_path, report_text)
|
|
@@ -511,6 +852,9 @@ def _worker_report(
|
|
|
511
852
|
leader_socket=str(scratch_paths.leader_socket_path),
|
|
512
853
|
prompt_path=str(scratch_paths.prompt_path),
|
|
513
854
|
debug_path=str(scratch_paths.debug_path),
|
|
855
|
+
advisor_session_id=advisor_session_id,
|
|
856
|
+
advisor_completion_signal=advisor_completion_signal,
|
|
857
|
+
advisor_launcher=advisor_launcher,
|
|
514
858
|
)
|
|
515
859
|
|
|
516
860
|
|
|
@@ -546,28 +890,218 @@ def _error_report_for_exception(
|
|
|
546
890
|
)
|
|
547
891
|
|
|
548
892
|
|
|
893
|
+
|
|
894
|
+
def _bind_worker_advisor_session(
|
|
895
|
+
*,
|
|
896
|
+
advisor_spec: AdvisorSpec,
|
|
897
|
+
role_name: str,
|
|
898
|
+
all_used_session_ids: set[str],
|
|
899
|
+
session_id_lock: object | None,
|
|
900
|
+
) -> tuple[str, str]:
|
|
901
|
+
if session_id_lock is not None:
|
|
902
|
+
with session_id_lock: # type: ignore[attr-defined] # Lock protocol from threading.Lock
|
|
903
|
+
return bind_unique_worker_advisor(
|
|
904
|
+
advisor_spec=advisor_spec,
|
|
905
|
+
role_name=role_name,
|
|
906
|
+
all_used_session_ids=all_used_session_ids,
|
|
907
|
+
)
|
|
908
|
+
return bind_unique_worker_advisor(
|
|
909
|
+
advisor_spec=advisor_spec,
|
|
910
|
+
role_name=role_name,
|
|
911
|
+
all_used_session_ids=all_used_session_ids,
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _advisor_blocked_report(
|
|
916
|
+
*,
|
|
917
|
+
worker_spec: WorkerSpec,
|
|
918
|
+
scratch_paths: WorkerScratchPaths,
|
|
919
|
+
report_text: str,
|
|
920
|
+
advisor_session_id: str | None = None,
|
|
921
|
+
advisor_launcher: str | None = None,
|
|
922
|
+
) -> WorkerReport:
|
|
923
|
+
return _worker_report(
|
|
924
|
+
worker_spec=worker_spec,
|
|
925
|
+
scratch_paths=scratch_paths,
|
|
926
|
+
returncode=WORKER_EXCEPTION_RETURN_CODE,
|
|
927
|
+
classification=CLASSIFICATION_ADVISOR_BLOCKED,
|
|
928
|
+
is_ok=False,
|
|
929
|
+
report_text=report_text,
|
|
930
|
+
advisor_session_id=advisor_session_id,
|
|
931
|
+
advisor_completion_signal=None,
|
|
932
|
+
advisor_launcher=advisor_launcher,
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def _attach_advisor_completion_if_configured(
|
|
938
|
+
*,
|
|
939
|
+
worker_spec: WorkerSpec,
|
|
940
|
+
scratch_paths: WorkerScratchPaths,
|
|
941
|
+
worker_report: WorkerReport,
|
|
942
|
+
advisor_spec: AdvisorSpec | None,
|
|
943
|
+
advisor_session_id: str | None,
|
|
944
|
+
advisor_launcher: str | None,
|
|
945
|
+
) -> WorkerReport:
|
|
946
|
+
if advisor_spec is None or advisor_session_id is None:
|
|
947
|
+
return worker_report
|
|
948
|
+
try:
|
|
949
|
+
completion_signal = obtain_advisor_completion_verdict(
|
|
950
|
+
advisor_spec=advisor_spec,
|
|
951
|
+
role_name=worker_spec.role_name,
|
|
952
|
+
session_id=advisor_session_id,
|
|
953
|
+
report_text=worker_report.report_text,
|
|
954
|
+
)
|
|
955
|
+
except ValueError as advisor_error:
|
|
956
|
+
return _advisor_blocked_report(
|
|
957
|
+
worker_spec=worker_spec,
|
|
958
|
+
scratch_paths=scratch_paths,
|
|
959
|
+
report_text=str(advisor_error),
|
|
960
|
+
advisor_session_id=advisor_session_id,
|
|
961
|
+
advisor_launcher=advisor_launcher,
|
|
962
|
+
)
|
|
963
|
+
return _worker_report(
|
|
964
|
+
worker_spec=worker_spec,
|
|
965
|
+
scratch_paths=scratch_paths,
|
|
966
|
+
returncode=worker_report.returncode,
|
|
967
|
+
classification=worker_report.classification,
|
|
968
|
+
is_ok=worker_report.is_ok,
|
|
969
|
+
report_text=worker_report.report_text,
|
|
970
|
+
advisor_session_id=advisor_session_id,
|
|
971
|
+
advisor_completion_signal=completion_signal,
|
|
972
|
+
advisor_launcher=advisor_launcher,
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def _maybe_bind_advisor_for_worker(
|
|
977
|
+
*,
|
|
978
|
+
worker_spec: WorkerSpec,
|
|
979
|
+
scratch_paths: WorkerScratchPaths,
|
|
980
|
+
advisor_spec: AdvisorSpec | None,
|
|
981
|
+
all_used_session_ids: set[str] | None,
|
|
982
|
+
session_id_lock: object | None,
|
|
983
|
+
) -> tuple[str | None, str | None, WorkerReport | None]:
|
|
984
|
+
if advisor_spec is None:
|
|
985
|
+
return None, None, None
|
|
986
|
+
used_ids = all_used_session_ids if all_used_session_ids is not None else set()
|
|
987
|
+
advisor_session_id, pre_signal = _bind_worker_advisor_session(
|
|
988
|
+
advisor_spec=advisor_spec,
|
|
989
|
+
role_name=worker_spec.role_name,
|
|
990
|
+
all_used_session_ids=used_ids,
|
|
991
|
+
session_id_lock=session_id_lock,
|
|
992
|
+
)
|
|
993
|
+
if (
|
|
994
|
+
advisor_session_id == PENDING_BIND_SENTINEL
|
|
995
|
+
or pre_signal == PENDING_BIND_SENTINEL
|
|
996
|
+
):
|
|
997
|
+
blocked = _advisor_blocked_report(
|
|
998
|
+
worker_spec=worker_spec,
|
|
999
|
+
scratch_paths=scratch_paths,
|
|
1000
|
+
report_text=f"{PENDING_BIND_SENTINEL} sentinel before launch",
|
|
1001
|
+
advisor_session_id=advisor_session_id,
|
|
1002
|
+
advisor_launcher=advisor_spec.launcher,
|
|
1003
|
+
)
|
|
1004
|
+
return advisor_session_id, advisor_spec.launcher, blocked
|
|
1005
|
+
return advisor_session_id, advisor_spec.launcher, None
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def _map_launch_exception(
|
|
1009
|
+
*,
|
|
1010
|
+
worker_spec: WorkerSpec,
|
|
1011
|
+
scratch_paths: WorkerScratchPaths,
|
|
1012
|
+
raised_exception: BaseException,
|
|
1013
|
+
advisor_spec: AdvisorSpec | None,
|
|
1014
|
+
advisor_session_id: str | None,
|
|
1015
|
+
advisor_launcher: str | None,
|
|
1016
|
+
) -> WorkerReport:
|
|
1017
|
+
if advisor_spec is not None and (
|
|
1018
|
+
isinstance(raised_exception, AdvisorFailureError)
|
|
1019
|
+
or PENDING_BIND_SENTINEL in str(raised_exception)
|
|
1020
|
+
or "advisor" in str(raised_exception).lower()
|
|
1021
|
+
):
|
|
1022
|
+
return _advisor_blocked_report(
|
|
1023
|
+
worker_spec=worker_spec,
|
|
1024
|
+
scratch_paths=scratch_paths,
|
|
1025
|
+
report_text=f"{type(raised_exception).__name__}: {raised_exception}",
|
|
1026
|
+
advisor_session_id=advisor_session_id,
|
|
1027
|
+
advisor_launcher=advisor_launcher,
|
|
1028
|
+
)
|
|
1029
|
+
return _error_report_for_exception(
|
|
1030
|
+
worker_spec=worker_spec,
|
|
1031
|
+
scratch_paths=scratch_paths,
|
|
1032
|
+
raised_exception=raised_exception,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def _run_worker_body(
|
|
1037
|
+
*,
|
|
1038
|
+
worker_spec: WorkerSpec,
|
|
1039
|
+
scratch_paths: WorkerScratchPaths,
|
|
1040
|
+
run_state_directory: Path,
|
|
1041
|
+
advisor_spec: AdvisorSpec | None,
|
|
1042
|
+
advisor_session_id: str | None,
|
|
1043
|
+
advisor_launcher: str | None,
|
|
1044
|
+
) -> WorkerReport:
|
|
1045
|
+
_write_assembled_prompt(
|
|
1046
|
+
worker_spec=worker_spec,
|
|
1047
|
+
prompt_path=scratch_paths.prompt_path,
|
|
1048
|
+
advisor_session_id=advisor_session_id,
|
|
1049
|
+
advisor_spec=advisor_spec,
|
|
1050
|
+
)
|
|
1051
|
+
outcome = _invoke_worker(
|
|
1052
|
+
worker_spec=worker_spec,
|
|
1053
|
+
scratch_paths=scratch_paths,
|
|
1054
|
+
run_state_directory=run_state_directory,
|
|
1055
|
+
)
|
|
1056
|
+
worker_report = _build_worker_report(
|
|
1057
|
+
worker_spec=worker_spec,
|
|
1058
|
+
outcome=outcome,
|
|
1059
|
+
scratch_paths=scratch_paths,
|
|
1060
|
+
)
|
|
1061
|
+
return _attach_advisor_completion_if_configured(
|
|
1062
|
+
worker_spec=worker_spec,
|
|
1063
|
+
scratch_paths=scratch_paths,
|
|
1064
|
+
worker_report=worker_report,
|
|
1065
|
+
advisor_spec=advisor_spec,
|
|
1066
|
+
advisor_session_id=advisor_session_id,
|
|
1067
|
+
advisor_launcher=advisor_launcher,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
|
|
549
1071
|
def _launch_one_worker(
|
|
550
1072
|
*,
|
|
551
1073
|
worker_spec: WorkerSpec,
|
|
552
1074
|
worker_index: int,
|
|
553
1075
|
run_state_directory: Path,
|
|
1076
|
+
advisor_spec: AdvisorSpec | None = None,
|
|
1077
|
+
all_used_session_ids: set[str] | None = None,
|
|
1078
|
+
session_id_lock: object | None = None,
|
|
554
1079
|
) -> WorkerReport:
|
|
555
1080
|
batch_sleep(worker_index * WORKER_STAGGER_SECONDS)
|
|
556
1081
|
scratch_paths = _mint_worker_scratch_paths(run_state_directory)
|
|
1082
|
+
advisor_session_id: str | None = None
|
|
1083
|
+
advisor_launcher: str | None = None
|
|
557
1084
|
try:
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
1085
|
+
(
|
|
1086
|
+
advisor_session_id,
|
|
1087
|
+
advisor_launcher,
|
|
1088
|
+
early_block,
|
|
1089
|
+
) = _maybe_bind_advisor_for_worker(
|
|
563
1090
|
worker_spec=worker_spec,
|
|
564
1091
|
scratch_paths=scratch_paths,
|
|
565
|
-
|
|
1092
|
+
advisor_spec=advisor_spec,
|
|
1093
|
+
all_used_session_ids=all_used_session_ids,
|
|
1094
|
+
session_id_lock=session_id_lock,
|
|
566
1095
|
)
|
|
567
|
-
|
|
1096
|
+
if early_block is not None:
|
|
1097
|
+
return early_block
|
|
1098
|
+
return _run_worker_body(
|
|
568
1099
|
worker_spec=worker_spec,
|
|
569
|
-
outcome=outcome,
|
|
570
1100
|
scratch_paths=scratch_paths,
|
|
1101
|
+
run_state_directory=run_state_directory,
|
|
1102
|
+
advisor_spec=advisor_spec,
|
|
1103
|
+
advisor_session_id=advisor_session_id,
|
|
1104
|
+
advisor_launcher=advisor_launcher,
|
|
571
1105
|
)
|
|
572
1106
|
except (
|
|
573
1107
|
OSError,
|
|
@@ -576,14 +1110,19 @@ def _launch_one_worker(
|
|
|
576
1110
|
TypeError,
|
|
577
1111
|
AttributeError,
|
|
578
1112
|
LookupError,
|
|
1113
|
+
AdvisorFailureError,
|
|
579
1114
|
) as raised_exception:
|
|
580
|
-
return
|
|
1115
|
+
return _map_launch_exception(
|
|
581
1116
|
worker_spec=worker_spec,
|
|
582
1117
|
scratch_paths=scratch_paths,
|
|
583
1118
|
raised_exception=raised_exception,
|
|
1119
|
+
advisor_spec=advisor_spec,
|
|
1120
|
+
advisor_session_id=advisor_session_id,
|
|
1121
|
+
advisor_launcher=advisor_launcher,
|
|
584
1122
|
)
|
|
585
1123
|
|
|
586
1124
|
|
|
1125
|
+
|
|
587
1126
|
def run_grok_batch(
|
|
588
1127
|
*,
|
|
589
1128
|
batch_spec: BatchSpec,
|
|
@@ -617,6 +1156,8 @@ def run_grok_batch(
|
|
|
617
1156
|
preflight_reason=None,
|
|
618
1157
|
all_worker_reports=(),
|
|
619
1158
|
)
|
|
1159
|
+
all_used_session_ids: set[str] = set()
|
|
1160
|
+
session_id_lock = threading.Lock()
|
|
620
1161
|
with ThreadPoolExecutor(max_workers=worker_count) as executor:
|
|
621
1162
|
all_futures = [
|
|
622
1163
|
executor.submit(
|
|
@@ -624,6 +1165,9 @@ def run_grok_batch(
|
|
|
624
1165
|
worker_spec=each_worker,
|
|
625
1166
|
worker_index=each_index,
|
|
626
1167
|
run_state_directory=run_state_directory,
|
|
1168
|
+
advisor_spec=batch_spec.advisor,
|
|
1169
|
+
all_used_session_ids=all_used_session_ids,
|
|
1170
|
+
session_id_lock=session_id_lock,
|
|
627
1171
|
)
|
|
628
1172
|
for each_index, each_worker in enumerate(batch_spec.all_workers)
|
|
629
1173
|
]
|
|
@@ -658,6 +1202,9 @@ def batch_summary_as_dict(batch_summary: BatchSummary) -> dict[str, object]:
|
|
|
658
1202
|
SUMMARY_LEADER_SOCKET_KEY: each_report.leader_socket,
|
|
659
1203
|
SUMMARY_PROMPT_FILE_KEY: each_report.prompt_path,
|
|
660
1204
|
SUMMARY_DEBUG_FILE_KEY: each_report.debug_path,
|
|
1205
|
+
SUMMARY_ADVISOR_SESSION_ID_KEY: each_report.advisor_session_id,
|
|
1206
|
+
SUMMARY_ADVISOR_SIGNAL_KEY: each_report.advisor_completion_signal,
|
|
1207
|
+
SUMMARY_ADVISOR_LAUNCHER_KEY: each_report.advisor_launcher,
|
|
661
1208
|
}
|
|
662
1209
|
for each_report in batch_summary.all_worker_reports
|
|
663
1210
|
]
|