codex-flow 2.1.13__py3-none-any.whl
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.
- codex_flow/__init__.py +28 -0
- codex_flow/__main__.py +9 -0
- codex_flow/cli.py +242 -0
- codex_flow/data/LICENSE +21 -0
- codex_flow/data/README.en.md +303 -0
- codex_flow/data/README.md +305 -0
- codex_flow/data/VERSION +1 -0
- codex_flow/data/apps/chatgpt-mcp/README.md +86 -0
- codex_flow/data/apps/chatgpt-mcp/__init__.py +1 -0
- codex_flow/data/apps/chatgpt-mcp/adapter.py +458 -0
- codex_flow/data/apps/chatgpt-mcp/server.py +358 -0
- codex_flow/data/apps/chatgpt-mcp/widget.html +927 -0
- codex_flow/data/apps/macos-overlay/README.en.md +121 -0
- codex_flow/data/apps/macos-overlay/README.md +123 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayRuntimeState.swift +126 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayScreenGeometry.swift +82 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayWindowController.swift +1052 -0
- codex_flow/data/apps/macos-overlay/Sources/Localization.swift +197 -0
- codex_flow/data/apps/macos-overlay/Sources/Models/TelemetryData.swift +1557 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/AccountSnapshotService.swift +1101 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/FlowPilotInstanceLock.swift +153 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/IPCServer.swift +298 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryQueryEngine.swift +800 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryWatcher.swift +135 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/UpdateService.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AccountView.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AnalyticsView.swift +566 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AutostartView.swift +293 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/BubbleView.swift +317 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HistoryView.swift +1124 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HoverRevealText.swift +165 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/InspectorSkillsToolsView.swift +121 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/LogoView.swift +182 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SleekSwitch.swift +117 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/StrategyModeView.swift +561 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SummaryView.swift +1273 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/UpdateView.swift +352 -0
- codex_flow/data/apps/macos-overlay/Sources/main.swift +340 -0
- codex_flow/data/apps/macos-overlay/Tests/OverlayScreenGeometryTests.swift +163 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryPhase1ContractTests.swift +357 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQueryEngineConcurrencyTests.swift +221 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQuotaSelectionTests.swift +158 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryWorkerTokenTests.swift +122 -0
- codex_flow/data/apps/macos-overlay/build.sh +75 -0
- codex_flow/data/benchmark/corpus.json +103 -0
- codex_flow/data/benchmark/manifest.example.json +41 -0
- codex_flow/data/benchmark/manifest.schema.json +137 -0
- codex_flow/data/benchmark/prices/gpt-5.6-2026-08-30.json +5 -0
- codex_flow/data/benchmark/profiles.json +90 -0
- codex_flow/data/benchmark/schema.json +77 -0
- codex_flow/data/benchmark/tasks.json +50 -0
- codex_flow/data/completions/codex-flow.bash +34 -0
- codex_flow/data/completions/codex-flow.zsh +52 -0
- codex_flow/data/glama.json +6 -0
- codex_flow/data/install-release.ps1 +126 -0
- codex_flow/data/install-release.sh +155 -0
- codex_flow/data/install.ps1 +349 -0
- codex_flow/data/install.sh +362 -0
- codex_flow/data/policy/benchmark.toml +49 -0
- codex_flow/data/policy/defaults.toml +70 -0
- codex_flow/data/scripts/analyze-benchmark.py +510 -0
- codex_flow/data/scripts/benchmark-local.py +171 -0
- codex_flow/data/scripts/check-recommendation.py +277 -0
- codex_flow/data/scripts/doctor.py +449 -0
- codex_flow/data/scripts/generate-release-manifest.py +74 -0
- codex_flow/data/scripts/localization.py +192 -0
- codex_flow/data/scripts/manage-hooks.py +448 -0
- codex_flow/data/scripts/manage-instructions.py +389 -0
- codex_flow/data/scripts/manage-shell.py +151 -0
- codex_flow/data/scripts/materialize-corpus.py +193 -0
- codex_flow/data/scripts/menu.py +646 -0
- codex_flow/data/scripts/migrations/0001_update_settings.py +80 -0
- codex_flow/data/scripts/package-release.py +132 -0
- codex_flow/data/scripts/render-benchmark-report.py +292 -0
- codex_flow/data/scripts/run-benchmark.py +829 -0
- codex_flow/data/scripts/strategies/__init__.py +28 -0
- codex_flow/data/scripts/strategies/balanced.py +115 -0
- codex_flow/data/scripts/strategies/base.py +363 -0
- codex_flow/data/scripts/strategies/efficient.py +158 -0
- codex_flow/data/scripts/strategies/lifecycle_runtime.py +590 -0
- codex_flow/data/scripts/strategies/quality.py +209 -0
- codex_flow/data/scripts/strategies/speed.py +108 -0
- codex_flow/data/scripts/strategies/task_budget_runtime.py +644 -0
- codex_flow/data/scripts/strategies/task_phase_runtime.py +341 -0
- codex_flow/data/scripts/strategies/work_unit_runtime.py +421 -0
- codex_flow/data/scripts/strategy_runtime.py +1091 -0
- codex_flow/data/scripts/telemetry.py +400 -0
- codex_flow/data/scripts/telemetry_core/__init__.py +192 -0
- codex_flow/data/scripts/telemetry_core/app_server.py +1192 -0
- codex_flow/data/scripts/telemetry_core/collector.py +1247 -0
- codex_flow/data/scripts/telemetry_core/common.py +421 -0
- codex_flow/data/scripts/telemetry_core/latency.py +593 -0
- codex_flow/data/scripts/telemetry_core/query.py +427 -0
- codex_flow/data/scripts/telemetry_core/quota_ledger.py +598 -0
- codex_flow/data/scripts/telemetry_core/render.py +460 -0
- codex_flow/data/scripts/telemetry_core/repair.py +223 -0
- codex_flow/data/scripts/ui.py +266 -0
- codex_flow/data/scripts/update-homebrew-formula.py +146 -0
- codex_flow/data/scripts/update_runtime_config.py +134 -0
- codex_flow/data/scripts/updater.py +1718 -0
- codex_flow/data/smithery.yaml +18 -0
- codex_flow/data/templates/agents/worker-explorer.toml +24 -0
- codex_flow/data/templates/agents/worker-implementer.toml +49 -0
- codex_flow/data/templates/agents/worker-reviewer.toml +25 -0
- codex_flow/data/templates/flow-pilot-instructions.md +35 -0
- codex_flow/data/templates/skills/flow-pilot/SKILL.md +577 -0
- codex_flow/mcp.py +35 -0
- codex_flow-2.1.13.dist-info/METADATA +342 -0
- codex_flow-2.1.13.dist-info/RECORD +113 -0
- codex_flow-2.1.13.dist-info/WHEEL +5 -0
- codex_flow-2.1.13.dist-info/entry_points.txt +3 -0
- codex_flow-2.1.13.dist-info/licenses/LICENSE +21 -0
- codex_flow-2.1.13.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1091 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Deterministic strategy resolver and ExecutionPlan compiler for codex-flow.
|
|
3
|
+
|
|
4
|
+
FlowPilot owns semantic TaskProfile construction. This module is the single source
|
|
5
|
+
of truth for policy precedence, release defaults, modifiers, runtime constraints,
|
|
6
|
+
and the final ExecutionPlan. Strategy-specific optimization decisions are loaded
|
|
7
|
+
from the built-in strategy registry.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from dataclasses import asdict, dataclass, field, replace
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Iterable
|
|
19
|
+
|
|
20
|
+
from strategies import all_specs as all_strategy_specs
|
|
21
|
+
from strategies import get as get_strategy
|
|
22
|
+
from strategies import names as strategy_names
|
|
23
|
+
from strategies.base import (
|
|
24
|
+
REASONING_ROLLOUT_MODES,
|
|
25
|
+
ReasoningRolloutDecision,
|
|
26
|
+
ReasoningRolloutPolicy,
|
|
27
|
+
StagePolicy,
|
|
28
|
+
TaskBudgetPolicy,
|
|
29
|
+
WorkerBudget,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from telemetry_core.app_server import quota_windows as app_server_quota_windows
|
|
34
|
+
except ImportError: # pragma: no cover
|
|
35
|
+
app_server_quota_windows = None
|
|
36
|
+
|
|
37
|
+
CODEX_HOME = Path(os.environ.get("CODEX_HOME", Path.home() / ".codex"))
|
|
38
|
+
DEFAULT_POLICY = CODEX_HOME / "codex-flow.toml"
|
|
39
|
+
REPO_POLICY_NAME = ".codex-flow.toml"
|
|
40
|
+
TEMPORARY_BYPASS_NAME = ".strategy-bypass-once"
|
|
41
|
+
|
|
42
|
+
STRATEGIES = strategy_names()
|
|
43
|
+
ROUTING_MODES = ("adaptive", "direct", "delegate")
|
|
44
|
+
COMPLEXITIES = ("small", "routine", "complex", "critical")
|
|
45
|
+
LEVELS = ("low", "medium", "high", "critical")
|
|
46
|
+
EFFORTS = ("high", "xhigh", "max")
|
|
47
|
+
SCOPES = ("local", "module", "cross-module", "repo-wide")
|
|
48
|
+
PARALLELISM = ("none", "limited", "high")
|
|
49
|
+
ITERATION = ("one-shot", "iterative", "heavy-loop")
|
|
50
|
+
QUALITY_INTENTS = ("normal", "strong", "absolute")
|
|
51
|
+
REVIEW_MODES = ("auto", "standard", "strict")
|
|
52
|
+
FANOUT_MODES = ("auto", "conservative", "aggressive")
|
|
53
|
+
EFFORT_RANK = {name: idx for idx, name in enumerate(EFFORTS)}
|
|
54
|
+
RUNTIME_MAX_WORKER_IDLE_SECONDS = 600
|
|
55
|
+
RUNTIME_MAX_WORKER_WALL_SECONDS = 3600
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _release_defaults_path() -> Path:
|
|
59
|
+
candidates = (
|
|
60
|
+
Path(__file__).resolve().with_name("defaults.toml"),
|
|
61
|
+
Path(__file__).resolve().parent.parent / "policy" / "defaults.toml",
|
|
62
|
+
)
|
|
63
|
+
for candidate in candidates:
|
|
64
|
+
if candidate.is_file():
|
|
65
|
+
return candidate
|
|
66
|
+
raise FileNotFoundError("codex-flow release defaults not found; reinstall codex-flow")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class TaskProfile:
|
|
71
|
+
complexity: str = "routine"
|
|
72
|
+
uncertainty: str = "medium"
|
|
73
|
+
risk: str = "medium"
|
|
74
|
+
scope: str = "module"
|
|
75
|
+
parallelism: str = "limited"
|
|
76
|
+
write_conflict: str = "low"
|
|
77
|
+
exploration_need: str = "medium"
|
|
78
|
+
verification_cost: str = "medium"
|
|
79
|
+
iteration_intensity: str = "iterative"
|
|
80
|
+
writable_workstreams: int = 1
|
|
81
|
+
quality_intent: str = "normal"
|
|
82
|
+
|
|
83
|
+
def validate(self) -> None:
|
|
84
|
+
if self.complexity not in COMPLEXITIES:
|
|
85
|
+
raise ValueError(f"invalid complexity: {self.complexity}")
|
|
86
|
+
if self.uncertainty not in LEVELS[:3]:
|
|
87
|
+
raise ValueError(f"invalid uncertainty: {self.uncertainty}")
|
|
88
|
+
if self.risk not in LEVELS:
|
|
89
|
+
raise ValueError(f"invalid risk: {self.risk}")
|
|
90
|
+
if self.scope not in SCOPES:
|
|
91
|
+
raise ValueError(f"invalid scope: {self.scope}")
|
|
92
|
+
if self.parallelism not in PARALLELISM:
|
|
93
|
+
raise ValueError(f"invalid parallelism: {self.parallelism}")
|
|
94
|
+
if self.write_conflict not in {"low", "high"}:
|
|
95
|
+
raise ValueError(f"invalid write_conflict: {self.write_conflict}")
|
|
96
|
+
if self.exploration_need not in LEVELS[:3]:
|
|
97
|
+
raise ValueError(f"invalid exploration_need: {self.exploration_need}")
|
|
98
|
+
if self.verification_cost not in LEVELS[:3]:
|
|
99
|
+
raise ValueError(f"invalid verification_cost: {self.verification_cost}")
|
|
100
|
+
if self.iteration_intensity not in ITERATION:
|
|
101
|
+
raise ValueError(f"invalid iteration_intensity: {self.iteration_intensity}")
|
|
102
|
+
if self.writable_workstreams < 1:
|
|
103
|
+
raise ValueError("writable_workstreams must be positive")
|
|
104
|
+
if self.quality_intent not in QUALITY_INTENTS:
|
|
105
|
+
raise ValueError(f"invalid quality_intent: {self.quality_intent}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class Modifiers:
|
|
110
|
+
review: str = "auto"
|
|
111
|
+
fanout: str = "auto"
|
|
112
|
+
|
|
113
|
+
def validate(self) -> None:
|
|
114
|
+
if self.review not in REVIEW_MODES:
|
|
115
|
+
raise ValueError(f"invalid review modifier: {self.review}")
|
|
116
|
+
if self.fanout not in FANOUT_MODES:
|
|
117
|
+
raise ValueError(f"invalid fanout modifier: {self.fanout}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class PolicySnapshot:
|
|
122
|
+
parent_capability_policy: str
|
|
123
|
+
parent_model_floor: str
|
|
124
|
+
parent_min_reasoning: str
|
|
125
|
+
parent_routine_reasoning: str
|
|
126
|
+
parent_complex_reasoning: str
|
|
127
|
+
parent_critical_reasoning: str
|
|
128
|
+
worker_capability_policy: str
|
|
129
|
+
worker_model: str
|
|
130
|
+
worker_resolved_model: str
|
|
131
|
+
worker_min_reasoning: str
|
|
132
|
+
worker_routine_reasoning: str
|
|
133
|
+
worker_complex_reasoning: str
|
|
134
|
+
worker_critical_reasoning: str
|
|
135
|
+
reasoning_rollout: ReasoningRolloutPolicy = ReasoningRolloutPolicy()
|
|
136
|
+
|
|
137
|
+
def validate(self) -> None:
|
|
138
|
+
for label, value in (
|
|
139
|
+
("parent_min_reasoning", self.parent_min_reasoning),
|
|
140
|
+
("parent_routine_reasoning", self.parent_routine_reasoning),
|
|
141
|
+
("parent_complex_reasoning", self.parent_complex_reasoning),
|
|
142
|
+
("parent_critical_reasoning", self.parent_critical_reasoning),
|
|
143
|
+
("worker_min_reasoning", self.worker_min_reasoning),
|
|
144
|
+
("worker_routine_reasoning", self.worker_routine_reasoning),
|
|
145
|
+
("worker_complex_reasoning", self.worker_complex_reasoning),
|
|
146
|
+
("worker_critical_reasoning", self.worker_critical_reasoning),
|
|
147
|
+
):
|
|
148
|
+
if value not in EFFORTS:
|
|
149
|
+
raise ValueError(f"invalid {label}: {value}")
|
|
150
|
+
self.reasoning_rollout.validate()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass(frozen=True)
|
|
154
|
+
class ResolvedPolicy:
|
|
155
|
+
enabled: bool
|
|
156
|
+
strategy: str
|
|
157
|
+
routing: str
|
|
158
|
+
modifiers: Modifiers
|
|
159
|
+
capability: PolicySnapshot
|
|
160
|
+
max_concurrent_threads: int
|
|
161
|
+
max_repair_cycles: int
|
|
162
|
+
repo_policy: str | None = None
|
|
163
|
+
|
|
164
|
+
def validate(self) -> None:
|
|
165
|
+
if self.strategy not in STRATEGIES:
|
|
166
|
+
raise ValueError(f"invalid strategy: {self.strategy}")
|
|
167
|
+
if self.routing not in ROUTING_MODES:
|
|
168
|
+
raise ValueError(f"invalid routing mode: {self.routing}")
|
|
169
|
+
self.modifiers.validate()
|
|
170
|
+
self.capability.validate()
|
|
171
|
+
if self.max_concurrent_threads < 1:
|
|
172
|
+
raise ValueError("max_concurrent_threads must be positive")
|
|
173
|
+
if self.max_repair_cycles < 0:
|
|
174
|
+
raise ValueError("max_repair_cycles cannot be negative")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass(frozen=True)
|
|
178
|
+
class RuntimeState:
|
|
179
|
+
quota_pressure: str = "unknown"
|
|
180
|
+
max_concurrent_threads: int = 4
|
|
181
|
+
max_repair_cycles: int = 2
|
|
182
|
+
|
|
183
|
+
def validate(self) -> None:
|
|
184
|
+
if self.quota_pressure not in {"unknown", "low", "medium", "high", "critical"}:
|
|
185
|
+
raise ValueError(f"invalid quota_pressure: {self.quota_pressure}")
|
|
186
|
+
if self.max_concurrent_threads < 1:
|
|
187
|
+
raise ValueError("max_concurrent_threads must be positive")
|
|
188
|
+
if self.max_repair_cycles < 0:
|
|
189
|
+
raise ValueError("max_repair_cycles cannot be negative")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@dataclass(frozen=True)
|
|
193
|
+
class ExecutionPlan:
|
|
194
|
+
schema_version: int
|
|
195
|
+
strategy: str
|
|
196
|
+
routing: str
|
|
197
|
+
review_modifier: str
|
|
198
|
+
fanout_modifier: str
|
|
199
|
+
quality_intent: str
|
|
200
|
+
parent_capability_policy: str
|
|
201
|
+
parent_model_floor: str
|
|
202
|
+
parent_reasoning: str
|
|
203
|
+
reasoning_rollout: ReasoningRolloutDecision | None
|
|
204
|
+
explorer_capability_policy: str | None
|
|
205
|
+
explorer_model: str | None
|
|
206
|
+
explorer_reasoning: str | None
|
|
207
|
+
implementer_capability_policy: str | None
|
|
208
|
+
implementer_model: str | None
|
|
209
|
+
implementer_reasoning: str | None
|
|
210
|
+
reviewer_capability_policy: str | None
|
|
211
|
+
reviewer_model: str | None
|
|
212
|
+
reviewer_reasoning: str | None
|
|
213
|
+
worker_budget: WorkerBudget
|
|
214
|
+
task_budget: TaskBudgetPolicy | None
|
|
215
|
+
exploration_workers: int
|
|
216
|
+
implementation_workers: int
|
|
217
|
+
reviewer_workers: int
|
|
218
|
+
planned_worker_count: int
|
|
219
|
+
exploration_stage: StagePolicy | None
|
|
220
|
+
implementation_stage: StagePolicy | None
|
|
221
|
+
review_stage: StagePolicy | None
|
|
222
|
+
review_mode: str
|
|
223
|
+
max_repair_cycles: int
|
|
224
|
+
max_concurrent_threads: int
|
|
225
|
+
escalate_on_failure: bool
|
|
226
|
+
quota_pressure: str
|
|
227
|
+
repo_policy: str | None = None
|
|
228
|
+
context_mode: str = "compact-fresh"
|
|
229
|
+
notes: tuple[str, ...] = field(default_factory=tuple)
|
|
230
|
+
|
|
231
|
+
def to_dict(self) -> dict[str, Any]:
|
|
232
|
+
data = asdict(self)
|
|
233
|
+
data["notes"] = list(self.notes)
|
|
234
|
+
return data
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _section_body(text: str, section: str) -> re.Match[str] | None:
|
|
238
|
+
return re.search(rf"(?ms)^\[{re.escape(section)}\]\s*\n(.*?)(?=^\[[^\n]+\]\s*$|\Z)", text)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def policy_value(path: Path | None, section: str, key: str, default: str = "") -> str:
|
|
242
|
+
if path is None:
|
|
243
|
+
return default
|
|
244
|
+
try:
|
|
245
|
+
text = path.read_text(encoding="utf-8-sig")
|
|
246
|
+
except OSError:
|
|
247
|
+
return default
|
|
248
|
+
match = _section_body(text, section)
|
|
249
|
+
if not match:
|
|
250
|
+
return default
|
|
251
|
+
key_match = re.search(rf"(?m)^\s*{re.escape(key)}\s*=\s*(.*?)\s*$", match.group(1))
|
|
252
|
+
if not key_match:
|
|
253
|
+
return default
|
|
254
|
+
value = re.sub(r"\s+#.*$", "", key_match.group(1)).strip()
|
|
255
|
+
if len(value) >= 2 and value[0] == value[-1] == '"':
|
|
256
|
+
value = value[1:-1]
|
|
257
|
+
return value or default
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _policy_int(path: Path | None, section: str, key: str, default: int) -> int:
|
|
261
|
+
raw = policy_value(path, section, key, str(default))
|
|
262
|
+
try:
|
|
263
|
+
return int(raw)
|
|
264
|
+
except ValueError:
|
|
265
|
+
return default
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _policy_bool(path: Path | None, section: str, key: str, default: bool, *, strict: bool = False) -> bool:
|
|
269
|
+
if strict and path is not None:
|
|
270
|
+
try:
|
|
271
|
+
text = path.read_text(encoding="utf-8-sig")
|
|
272
|
+
except OSError:
|
|
273
|
+
return default
|
|
274
|
+
match = _section_body(text, section)
|
|
275
|
+
if not match:
|
|
276
|
+
return default
|
|
277
|
+
key_match = re.search(rf"(?m)^\s*{re.escape(key)}\s*=\s*(.*?)\s*$", match.group(1))
|
|
278
|
+
if not key_match:
|
|
279
|
+
return default
|
|
280
|
+
raw = re.sub(r"\s+#.*$", "", key_match.group(1)).strip()
|
|
281
|
+
if len(raw) >= 2 and raw[0] == raw[-1] == '"':
|
|
282
|
+
raw = raw[1:-1]
|
|
283
|
+
else:
|
|
284
|
+
raw = policy_value(path, section, key, "true" if default else "false")
|
|
285
|
+
raw = raw.strip().lower()
|
|
286
|
+
if raw in {"1", "true", "yes", "on"}:
|
|
287
|
+
return True
|
|
288
|
+
if raw in {"0", "false", "no", "off"}:
|
|
289
|
+
return False
|
|
290
|
+
if strict:
|
|
291
|
+
raise ValueError(f"invalid [{section}].{key}: {raw or '<empty>'}; expected true or false")
|
|
292
|
+
return default
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _release_value(section: str, key: str, fallback: str = "") -> str:
|
|
296
|
+
return policy_value(_release_defaults_path(), section, key, fallback)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _release_int(section: str, key: str, fallback: int) -> int:
|
|
300
|
+
return _policy_int(_release_defaults_path(), section, key, fallback)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _release_bool(section: str, key: str, fallback: bool) -> bool:
|
|
304
|
+
return _policy_bool(_release_defaults_path(), section, key, fallback)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _release_reasoning_rollout() -> ReasoningRolloutPolicy:
|
|
308
|
+
rollout = ReasoningRolloutPolicy(
|
|
309
|
+
mode=_release_value("reasoning.rollout", "mode", "shadow"),
|
|
310
|
+
minimum=_release_value("reasoning.rollout", "minimum", "high"),
|
|
311
|
+
routine=_release_value("reasoning.rollout", "routine", "high"),
|
|
312
|
+
complex=_release_value("reasoning.rollout", "complex", "xhigh"),
|
|
313
|
+
critical=_release_value("reasoning.rollout", "critical", "max"),
|
|
314
|
+
)
|
|
315
|
+
rollout.validate()
|
|
316
|
+
return rollout
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _quoted(value: str) -> str:
|
|
320
|
+
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def set_policy_value(path: Path, section: str, key: str, value: str, *, quote: bool = True) -> None:
|
|
324
|
+
if not path.exists():
|
|
325
|
+
raise FileNotFoundError(f"policy not found: {path}")
|
|
326
|
+
text = path.read_text(encoding="utf-8-sig")
|
|
327
|
+
match = _section_body(text, section)
|
|
328
|
+
line = f"{key} = {_quoted(value) if quote else value}"
|
|
329
|
+
if match:
|
|
330
|
+
body = match.group(1)
|
|
331
|
+
key_re = re.compile(rf"(?m)^\s*{re.escape(key)}\s*=.*$")
|
|
332
|
+
if key_re.search(body):
|
|
333
|
+
body = key_re.sub(line, body)
|
|
334
|
+
else:
|
|
335
|
+
if body and not body.endswith("\n"):
|
|
336
|
+
body += "\n"
|
|
337
|
+
body += line + "\n"
|
|
338
|
+
text = text[: match.start(1)] + body + text[match.end(1):]
|
|
339
|
+
else:
|
|
340
|
+
if text and not text.endswith("\n"):
|
|
341
|
+
text += "\n"
|
|
342
|
+
if text and not text.endswith("\n\n"):
|
|
343
|
+
text += "\n"
|
|
344
|
+
text += f"[{section}]\n{line}\n"
|
|
345
|
+
path.write_text(text, encoding="utf-8")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _effort_max(*values: str) -> str:
|
|
349
|
+
valid = [value for value in values if value in EFFORT_RANK]
|
|
350
|
+
if not valid:
|
|
351
|
+
raise ValueError("no valid reasoning effort supplied")
|
|
352
|
+
return max(valid, key=lambda value: EFFORT_RANK[value])
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _effort_next(value: str) -> str:
|
|
356
|
+
if value not in EFFORT_RANK:
|
|
357
|
+
raise ValueError(f"invalid reasoning effort: {value}")
|
|
358
|
+
return EFFORTS[min(len(EFFORTS) - 1, EFFORT_RANK[value] + 1)]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _release_capability() -> PolicySnapshot:
|
|
362
|
+
worker_model = _release_value("models", "worker_model")
|
|
363
|
+
snapshot = PolicySnapshot(
|
|
364
|
+
parent_capability_policy=_release_value("models", "parent_policy"),
|
|
365
|
+
parent_model_floor=_release_value("models", "parent_min_model"),
|
|
366
|
+
parent_min_reasoning=_release_value("reasoning.parent", "minimum"),
|
|
367
|
+
parent_routine_reasoning=_release_value("reasoning.parent", "routine"),
|
|
368
|
+
parent_complex_reasoning=_release_value("reasoning.parent", "complex"),
|
|
369
|
+
parent_critical_reasoning=_release_value("reasoning.parent", "critical"),
|
|
370
|
+
worker_capability_policy=_release_value("models", "worker_policy"),
|
|
371
|
+
worker_model="auto",
|
|
372
|
+
worker_resolved_model=worker_model,
|
|
373
|
+
worker_min_reasoning=_release_value("reasoning.worker", "minimum"),
|
|
374
|
+
worker_routine_reasoning=_release_value("reasoning.worker", "routine"),
|
|
375
|
+
worker_complex_reasoning=_release_value("reasoning.worker", "complex"),
|
|
376
|
+
worker_critical_reasoning=_release_value("reasoning.worker", "critical"),
|
|
377
|
+
reasoning_rollout=_release_reasoning_rollout(),
|
|
378
|
+
)
|
|
379
|
+
snapshot.validate()
|
|
380
|
+
return snapshot
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def discover_repo_policy(start: Path | None = None) -> Path | None:
|
|
384
|
+
current = (start or Path.cwd()).resolve()
|
|
385
|
+
if current.is_file():
|
|
386
|
+
current = current.parent
|
|
387
|
+
while True:
|
|
388
|
+
candidate = current / REPO_POLICY_NAME
|
|
389
|
+
if candidate.is_file() and candidate.resolve() != DEFAULT_POLICY.resolve():
|
|
390
|
+
return candidate
|
|
391
|
+
if (current / ".git").exists() or current.parent == current:
|
|
392
|
+
return None
|
|
393
|
+
current = current.parent
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _user_capability(path: Path) -> PolicySnapshot:
|
|
397
|
+
release = _release_capability()
|
|
398
|
+
parent_floor = policy_value(path, "parent", "min_reasoning_effort", release.parent_min_reasoning)
|
|
399
|
+
worker_floor = policy_value(path, "worker", "min_reasoning_effort", release.worker_min_reasoning)
|
|
400
|
+
rollout = ReasoningRolloutPolicy(
|
|
401
|
+
mode=policy_value(path, "reasoning.rollout", "mode", release.reasoning_rollout.mode),
|
|
402
|
+
minimum=policy_value(path, "reasoning.rollout", "minimum", release.reasoning_rollout.minimum),
|
|
403
|
+
routine=policy_value(path, "reasoning.rollout", "routine", release.reasoning_rollout.routine),
|
|
404
|
+
complex=policy_value(path, "reasoning.rollout", "complex", release.reasoning_rollout.complex),
|
|
405
|
+
critical=policy_value(path, "reasoning.rollout", "critical", release.reasoning_rollout.critical),
|
|
406
|
+
)
|
|
407
|
+
rollout.validate()
|
|
408
|
+
snapshot = PolicySnapshot(
|
|
409
|
+
parent_capability_policy=policy_value(path, "parent", "model_policy", release.parent_capability_policy),
|
|
410
|
+
parent_model_floor=policy_value(path, "parent", "min_model", release.parent_model_floor),
|
|
411
|
+
parent_min_reasoning=parent_floor,
|
|
412
|
+
parent_routine_reasoning=policy_value(path, "parent", "routine_effort", release.parent_routine_reasoning),
|
|
413
|
+
parent_complex_reasoning=policy_value(path, "parent", "complex_effort", release.parent_complex_reasoning),
|
|
414
|
+
parent_critical_reasoning=policy_value(path, "parent", "critical_effort", release.parent_critical_reasoning),
|
|
415
|
+
worker_capability_policy=policy_value(path, "worker", "model_policy", release.worker_capability_policy),
|
|
416
|
+
worker_model=policy_value(path, "worker", "model", "auto"),
|
|
417
|
+
worker_resolved_model=policy_value(path, "worker", "resolved_model", release.worker_resolved_model),
|
|
418
|
+
worker_min_reasoning=worker_floor,
|
|
419
|
+
worker_routine_reasoning=policy_value(path, "worker", "routine_effort", release.worker_routine_reasoning),
|
|
420
|
+
worker_complex_reasoning=policy_value(path, "worker", "complex_effort", release.worker_complex_reasoning),
|
|
421
|
+
worker_critical_reasoning=policy_value(path, "worker", "critical_effort", release.worker_critical_reasoning),
|
|
422
|
+
reasoning_rollout=rollout,
|
|
423
|
+
)
|
|
424
|
+
snapshot.validate()
|
|
425
|
+
return snapshot
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _raise_capability_floors(base: PolicySnapshot, repo: Path | None) -> PolicySnapshot:
|
|
429
|
+
if repo is None:
|
|
430
|
+
base.validate()
|
|
431
|
+
return base
|
|
432
|
+
|
|
433
|
+
def repo_effort(section: str, key: str, fallback: str) -> str:
|
|
434
|
+
value = policy_value(repo, section, key, "")
|
|
435
|
+
return _effort_max(fallback, value) if value else fallback
|
|
436
|
+
|
|
437
|
+
result = PolicySnapshot(
|
|
438
|
+
parent_capability_policy=base.parent_capability_policy,
|
|
439
|
+
parent_model_floor=base.parent_model_floor,
|
|
440
|
+
parent_min_reasoning=repo_effort("parent", "min_reasoning_effort", base.parent_min_reasoning),
|
|
441
|
+
parent_routine_reasoning=repo_effort("parent", "routine_effort", base.parent_routine_reasoning),
|
|
442
|
+
parent_complex_reasoning=repo_effort("parent", "complex_effort", base.parent_complex_reasoning),
|
|
443
|
+
parent_critical_reasoning=repo_effort("parent", "critical_effort", base.parent_critical_reasoning),
|
|
444
|
+
worker_capability_policy=base.worker_capability_policy,
|
|
445
|
+
worker_model=base.worker_model,
|
|
446
|
+
worker_resolved_model=base.worker_resolved_model,
|
|
447
|
+
worker_min_reasoning=repo_effort("worker", "min_reasoning_effort", base.worker_min_reasoning),
|
|
448
|
+
worker_routine_reasoning=repo_effort("worker", "routine_effort", base.worker_routine_reasoning),
|
|
449
|
+
worker_complex_reasoning=repo_effort("worker", "complex_effort", base.worker_complex_reasoning),
|
|
450
|
+
worker_critical_reasoning=repo_effort("worker", "critical_effort", base.worker_critical_reasoning),
|
|
451
|
+
reasoning_rollout=ReasoningRolloutPolicy(
|
|
452
|
+
mode=base.reasoning_rollout.mode,
|
|
453
|
+
minimum=repo_effort("reasoning.rollout", "minimum", base.reasoning_rollout.minimum),
|
|
454
|
+
routine=repo_effort("reasoning.rollout", "routine", base.reasoning_rollout.routine),
|
|
455
|
+
complex=repo_effort("reasoning.rollout", "complex", base.reasoning_rollout.complex),
|
|
456
|
+
critical=repo_effort("reasoning.rollout", "critical", base.reasoning_rollout.critical),
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
result.validate()
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def resolve_policy(user_policy: Path, repo_policy: Path | None = None) -> ResolvedPolicy:
|
|
464
|
+
repo = repo_policy
|
|
465
|
+
enabled = _policy_bool(user_policy, "strategy", "enabled", _release_bool("strategy", "enabled", True), strict=True)
|
|
466
|
+
strategy = policy_value(user_policy, "strategy", "profile", _release_value("strategy", "profile"))
|
|
467
|
+
routing = policy_value(user_policy, "routing", "mode", _release_value("routing", "mode"))
|
|
468
|
+
review = policy_value(user_policy, "modifiers", "review", _release_value("modifiers", "review"))
|
|
469
|
+
fanout = policy_value(user_policy, "modifiers", "fanout", _release_value("modifiers", "fanout"))
|
|
470
|
+
max_threads = _policy_int(user_policy, "runtime", "max_concurrent_threads", _release_int("runtime", "max_concurrent_threads", 4))
|
|
471
|
+
max_repairs = _policy_int(user_policy, "runtime", "max_repair_cycles", _release_int("runtime", "max_repair_cycles", 2))
|
|
472
|
+
if repo is not None:
|
|
473
|
+
strategy = policy_value(repo, "strategy", "profile", strategy)
|
|
474
|
+
routing = policy_value(repo, "routing", "mode", routing)
|
|
475
|
+
review = policy_value(repo, "modifiers", "review", review)
|
|
476
|
+
fanout = policy_value(repo, "modifiers", "fanout", fanout)
|
|
477
|
+
repo_threads = policy_value(repo, "runtime", "max_concurrent_threads", "")
|
|
478
|
+
repo_repairs = policy_value(repo, "runtime", "max_repair_cycles", "")
|
|
479
|
+
if repo_threads:
|
|
480
|
+
try:
|
|
481
|
+
max_threads = min(max_threads, int(repo_threads))
|
|
482
|
+
except ValueError:
|
|
483
|
+
pass
|
|
484
|
+
if repo_repairs:
|
|
485
|
+
try:
|
|
486
|
+
max_repairs = min(max_repairs, int(repo_repairs))
|
|
487
|
+
except ValueError:
|
|
488
|
+
pass
|
|
489
|
+
resolved = ResolvedPolicy(
|
|
490
|
+
enabled=enabled,
|
|
491
|
+
strategy=strategy,
|
|
492
|
+
routing=routing,
|
|
493
|
+
modifiers=Modifiers(review=review, fanout=fanout),
|
|
494
|
+
capability=_raise_capability_floors(_user_capability(user_policy), repo),
|
|
495
|
+
max_concurrent_threads=max_threads,
|
|
496
|
+
max_repair_cycles=max_repairs,
|
|
497
|
+
repo_policy=str(repo) if repo is not None else None,
|
|
498
|
+
)
|
|
499
|
+
resolved.validate()
|
|
500
|
+
return resolved
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _configured_effort(task: TaskProfile, policy: PolicySnapshot, role: str) -> str:
|
|
504
|
+
if role == "parent":
|
|
505
|
+
floor, routine = policy.parent_min_reasoning, policy.parent_routine_reasoning
|
|
506
|
+
complex_effort, critical = policy.parent_complex_reasoning, policy.parent_critical_reasoning
|
|
507
|
+
else:
|
|
508
|
+
floor, routine = policy.worker_min_reasoning, policy.worker_routine_reasoning
|
|
509
|
+
complex_effort, critical = policy.worker_complex_reasoning, policy.worker_critical_reasoning
|
|
510
|
+
if task.complexity == "critical" or task.risk == "critical":
|
|
511
|
+
target = critical
|
|
512
|
+
elif task.complexity == "complex" or task.risk == "high":
|
|
513
|
+
target = complex_effort
|
|
514
|
+
else:
|
|
515
|
+
target = routine
|
|
516
|
+
return _effort_max(floor, target)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _rollout_policy_with_override(policy: ReasoningRolloutPolicy, mode: str | None) -> ReasoningRolloutPolicy:
|
|
520
|
+
if mode is None:
|
|
521
|
+
policy.validate()
|
|
522
|
+
return policy
|
|
523
|
+
if mode not in REASONING_ROLLOUT_MODES:
|
|
524
|
+
raise ValueError(f"invalid efficient reasoning rollout mode: {mode}")
|
|
525
|
+
overridden = replace(policy, mode=mode)
|
|
526
|
+
overridden.validate()
|
|
527
|
+
return overridden
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def quota_pressure_from_snapshot(snapshot: dict[str, Any] | None) -> str:
|
|
531
|
+
if not isinstance(snapshot, dict):
|
|
532
|
+
return "unknown"
|
|
533
|
+
used_values: list[float] = []
|
|
534
|
+
windows = app_server_quota_windows(snapshot) if app_server_quota_windows else []
|
|
535
|
+
for window in windows:
|
|
536
|
+
raw = window.get("used_percent")
|
|
537
|
+
if isinstance(raw, (int, float)) and not isinstance(raw, bool):
|
|
538
|
+
used_values.append(float(raw))
|
|
539
|
+
elif isinstance(raw, str):
|
|
540
|
+
try:
|
|
541
|
+
used_values.append(float(raw))
|
|
542
|
+
except ValueError:
|
|
543
|
+
continue
|
|
544
|
+
if not used_values:
|
|
545
|
+
return "unknown"
|
|
546
|
+
used = max(used_values)
|
|
547
|
+
if used >= 90:
|
|
548
|
+
return "critical"
|
|
549
|
+
if used >= 75:
|
|
550
|
+
return "high"
|
|
551
|
+
if used >= 50:
|
|
552
|
+
return "medium"
|
|
553
|
+
return "low"
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def detect_quota_pressure() -> str:
|
|
557
|
+
override = os.environ.get("CODEX_FLOW_QUOTA_PRESSURE")
|
|
558
|
+
if override in {"unknown", "low", "medium", "high", "critical"}:
|
|
559
|
+
return override
|
|
560
|
+
try:
|
|
561
|
+
from telemetry_core.app_server import AppServer
|
|
562
|
+
with AppServer() as app:
|
|
563
|
+
if not app.available:
|
|
564
|
+
return "unknown"
|
|
565
|
+
return quota_pressure_from_snapshot(app.rate_limits())
|
|
566
|
+
except Exception:
|
|
567
|
+
return "unknown"
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _desired_role_capability(task: TaskProfile, spec, policy: PolicySnapshot, role: str) -> tuple[str, str | None]:
|
|
571
|
+
target = spec.capability(task, role)
|
|
572
|
+
if target == "parent":
|
|
573
|
+
model = None if policy.parent_model_floor == "auto" else policy.parent_model_floor
|
|
574
|
+
return policy.parent_capability_policy, model
|
|
575
|
+
if target == "worker":
|
|
576
|
+
return policy.worker_capability_policy, policy.worker_resolved_model or policy.worker_model
|
|
577
|
+
raise ValueError(f"invalid capability target for {role}: {target}")
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _exploration_demand(task: TaskProfile, bonus: int = 0) -> int:
|
|
581
|
+
if task.parallelism == "none":
|
|
582
|
+
return 0
|
|
583
|
+
if bonus < 0:
|
|
584
|
+
raise ValueError("exploration bonus cannot be negative")
|
|
585
|
+
score = 0
|
|
586
|
+
score += {"low": 0, "medium": 1, "high": 2}[task.uncertainty]
|
|
587
|
+
score += {"low": 0, "medium": 1, "high": 2}[task.exploration_need]
|
|
588
|
+
score += {"small": 0, "routine": 0, "complex": 1, "critical": 2}[task.complexity]
|
|
589
|
+
score += {"local": 0, "module": 0, "cross-module": 1, "repo-wide": 2}[task.scope]
|
|
590
|
+
if task.verification_cost == "high":
|
|
591
|
+
score += 1
|
|
592
|
+
score += bonus
|
|
593
|
+
if score <= 1:
|
|
594
|
+
return 0
|
|
595
|
+
return min(4, max(1, (score + 1) // 2))
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def _reviewer_demand(task: TaskProfile, review_mode: str, bonus: int = 0) -> int:
|
|
599
|
+
if review_mode != "independent+parent":
|
|
600
|
+
return 0
|
|
601
|
+
if bonus < 0:
|
|
602
|
+
raise ValueError("reviewer bonus cannot be negative")
|
|
603
|
+
demand = 1 + bonus
|
|
604
|
+
if task.complexity == "critical" or task.risk == "critical" or task.verification_cost == "high":
|
|
605
|
+
demand += 1
|
|
606
|
+
return demand
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _fit_total_worker_budget(exploration_workers: int, implementation_workers: int, reviewer_workers: int, budget: WorkerBudget) -> tuple[int, int, int]:
|
|
610
|
+
while exploration_workers + implementation_workers + reviewer_workers > budget.max_total_workers:
|
|
611
|
+
if exploration_workers > 0:
|
|
612
|
+
exploration_workers -= 1
|
|
613
|
+
elif implementation_workers > 1:
|
|
614
|
+
implementation_workers -= 1
|
|
615
|
+
elif reviewer_workers > 1:
|
|
616
|
+
reviewer_workers -= 1
|
|
617
|
+
else:
|
|
618
|
+
break
|
|
619
|
+
return exploration_workers, implementation_workers, reviewer_workers
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _bounded_stage_policy(spec, task: TaskProfile, stage: str, worker_count: int, modifiers: Modifiers) -> StagePolicy | None:
|
|
623
|
+
if worker_count <= 0:
|
|
624
|
+
return None
|
|
625
|
+
policy = spec.lifecycle(task, stage)
|
|
626
|
+
policy.validate()
|
|
627
|
+
hard_timeout = min(policy.hard_timeout_seconds, RUNTIME_MAX_WORKER_WALL_SECONDS)
|
|
628
|
+
idle_timeout = min(policy.idle_timeout_seconds, RUNTIME_MAX_WORKER_IDLE_SECONDS, hard_timeout)
|
|
629
|
+
minimum = 0 if policy.join_policy == "opportunistic" else max(1, min(worker_count, policy.min_successful_workers))
|
|
630
|
+
bounded = replace(policy, min_successful_workers=minimum, idle_timeout_seconds=idle_timeout, hard_timeout_seconds=hard_timeout)
|
|
631
|
+
if stage == "review" and modifiers.review == "strict":
|
|
632
|
+
bounded = replace(
|
|
633
|
+
bounded,
|
|
634
|
+
join_policy="required",
|
|
635
|
+
min_successful_workers=worker_count,
|
|
636
|
+
cancel_if_superseded=False,
|
|
637
|
+
cancel_stragglers_after_quorum=False,
|
|
638
|
+
fallback_policy="retry_review",
|
|
639
|
+
)
|
|
640
|
+
bounded.validate()
|
|
641
|
+
return bounded
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _canonical_task_budget(
|
|
645
|
+
raw: TaskBudgetPolicy,
|
|
646
|
+
*,
|
|
647
|
+
implementation_workers: int,
|
|
648
|
+
reviewer_workers: int,
|
|
649
|
+
implementation_stage: StagePolicy | None,
|
|
650
|
+
review_stage: StagePolicy | None,
|
|
651
|
+
) -> TaskBudgetPolicy:
|
|
652
|
+
raw.validate()
|
|
653
|
+
if implementation_stage is None:
|
|
654
|
+
raise ValueError("delegated task budget requires implementation_stage")
|
|
655
|
+
if implementation_stage.maximum_work_units is None:
|
|
656
|
+
raise ValueError("delegated task budget requires maximum_work_units")
|
|
657
|
+
if raw.max_work_units != implementation_stage.maximum_work_units:
|
|
658
|
+
raise ValueError("task budget max_work_units must match implementation_stage maximum_work_units")
|
|
659
|
+
if implementation_workers > raw.max_work_units:
|
|
660
|
+
raise ValueError("implementation topology exceeds task budget max_work_units")
|
|
661
|
+
if implementation_workers > raw.max_implementation_attempts:
|
|
662
|
+
raise ValueError("implementation topology exceeds task budget max_implementation_attempts")
|
|
663
|
+
if implementation_stage.soft_timeout_seconds is not None and raw.soft_timeout_seconds < implementation_stage.soft_timeout_seconds:
|
|
664
|
+
raise ValueError("task soft timeout cannot precede implementation soft checkpoint budget")
|
|
665
|
+
|
|
666
|
+
if reviewer_workers <= 0:
|
|
667
|
+
if review_stage is not None:
|
|
668
|
+
raise ValueError("review_stage must be absent when reviewer_workers is zero")
|
|
669
|
+
return replace(raw, max_review_attempts=0)
|
|
670
|
+
|
|
671
|
+
if review_stage is None:
|
|
672
|
+
raise ValueError("review_stage is required when reviewer_workers is positive")
|
|
673
|
+
if raw.max_review_attempts < reviewer_workers:
|
|
674
|
+
raise ValueError("review topology exceeds task budget max_review_attempts")
|
|
675
|
+
completion_tail = review_stage.hard_timeout_seconds + raw.parent_finalization_seconds
|
|
676
|
+
canonical_hard = max(raw.hard_timeout_seconds, raw.soft_timeout_seconds + completion_tail)
|
|
677
|
+
return replace(raw, hard_timeout_seconds=canonical_hard)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def compile_plan(
|
|
681
|
+
task: TaskProfile,
|
|
682
|
+
*,
|
|
683
|
+
strategy: str = "efficient",
|
|
684
|
+
routing_mode: str = "adaptive",
|
|
685
|
+
modifiers: Modifiers | None = None,
|
|
686
|
+
policy: PolicySnapshot | None = None,
|
|
687
|
+
runtime: RuntimeState | None = None,
|
|
688
|
+
repo_policy: str | None = None,
|
|
689
|
+
efficient_reasoning: str | None = None,
|
|
690
|
+
) -> ExecutionPlan:
|
|
691
|
+
task.validate()
|
|
692
|
+
modifiers = modifiers or Modifiers()
|
|
693
|
+
modifiers.validate()
|
|
694
|
+
policy = policy or _release_capability()
|
|
695
|
+
policy.validate()
|
|
696
|
+
rollout_policy = _rollout_policy_with_override(policy.reasoning_rollout, efficient_reasoning)
|
|
697
|
+
runtime = runtime or RuntimeState(
|
|
698
|
+
max_concurrent_threads=_release_int("runtime", "max_concurrent_threads", 4),
|
|
699
|
+
max_repair_cycles=_release_int("runtime", "max_repair_cycles", 2),
|
|
700
|
+
)
|
|
701
|
+
runtime.validate()
|
|
702
|
+
spec = get_strategy(strategy)
|
|
703
|
+
budget = spec.worker_budget(task)
|
|
704
|
+
budget.validate()
|
|
705
|
+
if routing_mode not in ROUTING_MODES:
|
|
706
|
+
raise ValueError(f"invalid routing mode: {routing_mode}")
|
|
707
|
+
|
|
708
|
+
route = spec.adaptive_route(task) if routing_mode == "adaptive" else routing_mode
|
|
709
|
+
delegated = route == "delegate"
|
|
710
|
+
parent_effort = _effort_max(_configured_effort(task, policy, "parent"), spec.effort(task, "parent"))
|
|
711
|
+
legacy_role_efforts = {
|
|
712
|
+
role: _effort_max(_configured_effort(task, policy, "worker"), spec.effort(task, role), _effort_next(parent_effort))
|
|
713
|
+
for role in ("explorer", "implementer", "reviewer")
|
|
714
|
+
}
|
|
715
|
+
role_efforts = dict(legacy_role_efforts)
|
|
716
|
+
reasoning_rollout: ReasoningRolloutDecision | None = None
|
|
717
|
+
|
|
718
|
+
exploration_workers = 0
|
|
719
|
+
implementation_workers = 0
|
|
720
|
+
reviewer_workers = 0
|
|
721
|
+
review_mode = "parent"
|
|
722
|
+
concurrency = 1
|
|
723
|
+
notes: list[str] = list(spec.notes(task))
|
|
724
|
+
|
|
725
|
+
if parent_effort == "max" and delegated:
|
|
726
|
+
notes.append("parent reasoning is already max; worker-role reasoning cannot exceed max and is held at max")
|
|
727
|
+
|
|
728
|
+
if delegated:
|
|
729
|
+
implementation_workers = 1
|
|
730
|
+
exploration_workers = min(_exploration_demand(task, spec.exploration_bonus(task)), budget.max_explorers, runtime.max_concurrent_threads)
|
|
731
|
+
proven_writable = min(task.writable_workstreams, runtime.max_concurrent_threads, budget.max_implementers)
|
|
732
|
+
allow_parallel_write = task.parallelism == "high" and task.write_conflict == "low" and proven_writable >= 2
|
|
733
|
+
if allow_parallel_write and (spec.allow_parallel_write or modifiers.fanout == "aggressive"):
|
|
734
|
+
implementation_workers = proven_writable
|
|
735
|
+
notes.append(f"parallel writable execution authorized across {implementation_workers} proven isolated workstreams")
|
|
736
|
+
if modifiers.fanout == "conservative":
|
|
737
|
+
exploration_workers = min(exploration_workers, 1)
|
|
738
|
+
implementation_workers = 1
|
|
739
|
+
elif modifiers.fanout == "aggressive" and task.parallelism == "high":
|
|
740
|
+
exploration_workers = min(budget.max_explorers, runtime.max_concurrent_threads, max(exploration_workers, 2))
|
|
741
|
+
|
|
742
|
+
if modifiers.review == "strict":
|
|
743
|
+
review_mode = "independent+parent"
|
|
744
|
+
elif modifiers.review == "standard":
|
|
745
|
+
review_mode = "parent"
|
|
746
|
+
elif spec.independent_review(task):
|
|
747
|
+
review_mode = "independent+parent"
|
|
748
|
+
reviewer_workers = min(_reviewer_demand(task, review_mode, spec.reviewer_bonus(task)), budget.max_reviewers, runtime.max_concurrent_threads)
|
|
749
|
+
|
|
750
|
+
if task.parallelism == "none":
|
|
751
|
+
exploration_workers = 0
|
|
752
|
+
implementation_workers = 1
|
|
753
|
+
reviewer_workers = min(reviewer_workers, 1)
|
|
754
|
+
exploration_workers, implementation_workers, reviewer_workers = _fit_total_worker_budget(
|
|
755
|
+
exploration_workers, implementation_workers, reviewer_workers, budget
|
|
756
|
+
)
|
|
757
|
+
concurrency = min(runtime.max_concurrent_threads, max(1, exploration_workers, implementation_workers, reviewer_workers))
|
|
758
|
+
|
|
759
|
+
if runtime.quota_pressure in {"high", "critical"}:
|
|
760
|
+
notes.append("quota pressure is high; speculative fan-out and repair budget are constrained before quality floors")
|
|
761
|
+
if spec.quota_sensitive:
|
|
762
|
+
exploration_workers = min(exploration_workers, 1)
|
|
763
|
+
implementation_workers = min(implementation_workers, 1)
|
|
764
|
+
reviewer_workers = min(reviewer_workers, 1)
|
|
765
|
+
concurrency = min(runtime.max_concurrent_threads, max(1, exploration_workers, implementation_workers, reviewer_workers))
|
|
766
|
+
|
|
767
|
+
repair_cycles = runtime.max_repair_cycles
|
|
768
|
+
if runtime.quota_pressure == "critical" and spec.quota_sensitive:
|
|
769
|
+
repair_cycles = min(repair_cycles, 1)
|
|
770
|
+
|
|
771
|
+
if delegated and spec.reasoning_rollout is not None:
|
|
772
|
+
decisions = {
|
|
773
|
+
role: spec.reasoning_rollout(task, role, rollout_policy, parent_effort, legacy_role_efforts[role])
|
|
774
|
+
for role in ("explorer", "implementer", "reviewer")
|
|
775
|
+
}
|
|
776
|
+
selected = {decision.selected_worker_reasoning for decision in decisions.values()}
|
|
777
|
+
if len(selected) != 1:
|
|
778
|
+
raise ValueError("strategy reasoning rollout must select one Worker effort across roles")
|
|
779
|
+
reasoning_rollout = next(iter(decisions.values()))
|
|
780
|
+
reasoning_rollout.validate()
|
|
781
|
+
role_efforts = {role: decisions[role].selected_worker_reasoning for role in decisions}
|
|
782
|
+
if reasoning_rollout.mode == "shadow":
|
|
783
|
+
notes.append("efficient reasoning rollout is shadow; proposed Worker effort is reported, legacy effort is selected")
|
|
784
|
+
elif reasoning_rollout.mode == "adaptive":
|
|
785
|
+
notes.append("efficient reasoning rollout is adaptive; proposed Worker effort is selected and may equal Parent")
|
|
786
|
+
else:
|
|
787
|
+
notes.append("efficient reasoning rollout is legacy; current Worker effort is selected")
|
|
788
|
+
|
|
789
|
+
if route == "direct":
|
|
790
|
+
exploration_workers = implementation_workers = reviewer_workers = 0
|
|
791
|
+
concurrency = 1
|
|
792
|
+
explorer_capability_policy = explorer_model = explorer_reasoning = None
|
|
793
|
+
implementer_capability_policy = implementer_model = implementer_reasoning = None
|
|
794
|
+
reviewer_capability_policy = reviewer_model = reviewer_reasoning = None
|
|
795
|
+
review_mode = "parent"
|
|
796
|
+
else:
|
|
797
|
+
if exploration_workers > 0:
|
|
798
|
+
explorer_capability_policy, explorer_model = _desired_role_capability(task, spec, policy, "explorer")
|
|
799
|
+
explorer_reasoning = role_efforts["explorer"]
|
|
800
|
+
if explorer_capability_policy != policy.worker_capability_policy:
|
|
801
|
+
notes.append("explorer role requests parent-class capability; use runtime override when supported")
|
|
802
|
+
else:
|
|
803
|
+
explorer_capability_policy = explorer_model = explorer_reasoning = None
|
|
804
|
+
implementer_capability_policy, implementer_model = _desired_role_capability(task, spec, policy, "implementer")
|
|
805
|
+
implementer_reasoning = role_efforts["implementer"]
|
|
806
|
+
if implementer_capability_policy != policy.worker_capability_policy:
|
|
807
|
+
notes.append("implementer role requests parent-class capability; use runtime override when supported")
|
|
808
|
+
if reviewer_workers > 0:
|
|
809
|
+
reviewer_capability_policy, reviewer_model = _desired_role_capability(task, spec, policy, "reviewer")
|
|
810
|
+
reviewer_reasoning = role_efforts["reviewer"]
|
|
811
|
+
if reviewer_capability_policy != policy.worker_capability_policy:
|
|
812
|
+
notes.append("reviewer role requests parent-class capability; use runtime override when supported")
|
|
813
|
+
else:
|
|
814
|
+
reviewer_capability_policy = reviewer_model = reviewer_reasoning = None
|
|
815
|
+
|
|
816
|
+
exploration_stage = _bounded_stage_policy(spec, task, "exploration", exploration_workers, modifiers)
|
|
817
|
+
implementation_stage = _bounded_stage_policy(spec, task, "implementation", implementation_workers, modifiers)
|
|
818
|
+
review_stage = _bounded_stage_policy(spec, task, "review", reviewer_workers, modifiers)
|
|
819
|
+
|
|
820
|
+
task_budget: TaskBudgetPolicy | None = None
|
|
821
|
+
if delegated and spec.task_budget is not None:
|
|
822
|
+
raw_budget = spec.task_budget(task)
|
|
823
|
+
if raw_budget is not None:
|
|
824
|
+
if not isinstance(raw_budget, TaskBudgetPolicy):
|
|
825
|
+
raise ValueError("strategy task_budget hook must return TaskBudgetPolicy or None")
|
|
826
|
+
task_budget = _canonical_task_budget(
|
|
827
|
+
raw_budget,
|
|
828
|
+
implementation_workers=implementation_workers,
|
|
829
|
+
reviewer_workers=reviewer_workers,
|
|
830
|
+
implementation_stage=implementation_stage,
|
|
831
|
+
review_stage=review_stage,
|
|
832
|
+
)
|
|
833
|
+
task_budget.validate()
|
|
834
|
+
if task_budget.hard_timeout_seconds != raw_budget.hard_timeout_seconds:
|
|
835
|
+
notes.append(
|
|
836
|
+
f"required completion extends task hard deadline to {task_budget.hard_timeout_seconds}s "
|
|
837
|
+
f"without shortening the {task_budget.soft_timeout_seconds}s general-work window"
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
planned_worker_count = exploration_workers + implementation_workers + reviewer_workers
|
|
841
|
+
return ExecutionPlan(
|
|
842
|
+
schema_version=11,
|
|
843
|
+
strategy=strategy,
|
|
844
|
+
routing=route,
|
|
845
|
+
review_modifier=modifiers.review,
|
|
846
|
+
fanout_modifier=modifiers.fanout,
|
|
847
|
+
quality_intent=task.quality_intent,
|
|
848
|
+
parent_capability_policy=policy.parent_capability_policy,
|
|
849
|
+
parent_model_floor=policy.parent_model_floor,
|
|
850
|
+
parent_reasoning=parent_effort,
|
|
851
|
+
reasoning_rollout=reasoning_rollout,
|
|
852
|
+
explorer_capability_policy=explorer_capability_policy,
|
|
853
|
+
explorer_model=explorer_model,
|
|
854
|
+
explorer_reasoning=explorer_reasoning,
|
|
855
|
+
implementer_capability_policy=implementer_capability_policy,
|
|
856
|
+
implementer_model=implementer_model,
|
|
857
|
+
implementer_reasoning=implementer_reasoning,
|
|
858
|
+
reviewer_capability_policy=reviewer_capability_policy,
|
|
859
|
+
reviewer_model=reviewer_model,
|
|
860
|
+
reviewer_reasoning=reviewer_reasoning,
|
|
861
|
+
worker_budget=budget,
|
|
862
|
+
task_budget=task_budget,
|
|
863
|
+
exploration_workers=exploration_workers,
|
|
864
|
+
implementation_workers=implementation_workers,
|
|
865
|
+
reviewer_workers=reviewer_workers,
|
|
866
|
+
planned_worker_count=planned_worker_count,
|
|
867
|
+
exploration_stage=exploration_stage,
|
|
868
|
+
implementation_stage=implementation_stage,
|
|
869
|
+
review_stage=review_stage,
|
|
870
|
+
review_mode=review_mode,
|
|
871
|
+
max_repair_cycles=repair_cycles,
|
|
872
|
+
max_concurrent_threads=concurrency,
|
|
873
|
+
escalate_on_failure=True,
|
|
874
|
+
quota_pressure=runtime.quota_pressure,
|
|
875
|
+
repo_policy=repo_policy,
|
|
876
|
+
notes=tuple(notes),
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def _temporary_bypass_path() -> Path:
|
|
881
|
+
return CODEX_HOME / "codex-flow" / TEMPORARY_BYPASS_NAME
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def arm_temporary_bypass() -> None:
|
|
885
|
+
path = _temporary_bypass_path()
|
|
886
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
887
|
+
temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
|
|
888
|
+
try:
|
|
889
|
+
temporary.write_text("1\n", encoding="utf-8")
|
|
890
|
+
os.replace(temporary, path)
|
|
891
|
+
finally:
|
|
892
|
+
try:
|
|
893
|
+
temporary.unlink()
|
|
894
|
+
except FileNotFoundError:
|
|
895
|
+
pass
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def temporary_bypass_pending() -> bool:
|
|
899
|
+
return _temporary_bypass_path().exists()
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def consume_temporary_bypass() -> bool:
|
|
903
|
+
path = _temporary_bypass_path()
|
|
904
|
+
claim = path.with_name(f".{path.name}.{os.getpid()}.claim")
|
|
905
|
+
try:
|
|
906
|
+
os.replace(path, claim)
|
|
907
|
+
except FileNotFoundError:
|
|
908
|
+
return False
|
|
909
|
+
try:
|
|
910
|
+
return True
|
|
911
|
+
finally:
|
|
912
|
+
try:
|
|
913
|
+
claim.unlink()
|
|
914
|
+
except FileNotFoundError:
|
|
915
|
+
pass
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def configured_strategy_enabled(path: Path) -> bool:
|
|
919
|
+
return _policy_bool(path, "strategy", "enabled", _release_bool("strategy", "enabled", True), strict=True)
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def configured_strategy(path: Path) -> str:
|
|
923
|
+
return policy_value(path, "strategy", "profile", _release_value("strategy", "profile"))
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def configured_routing(path: Path) -> str:
|
|
927
|
+
return policy_value(path, "routing", "mode", _release_value("routing", "mode"))
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def _print_profiles() -> None:
|
|
931
|
+
for spec in all_strategy_specs():
|
|
932
|
+
print(f"{spec.name:10} {spec.description}")
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def _task_from_args(ns: argparse.Namespace) -> TaskProfile:
|
|
936
|
+
return TaskProfile(
|
|
937
|
+
complexity=ns.complexity,
|
|
938
|
+
uncertainty=ns.uncertainty,
|
|
939
|
+
risk=ns.risk,
|
|
940
|
+
scope=ns.scope,
|
|
941
|
+
parallelism=ns.parallelism,
|
|
942
|
+
write_conflict=ns.write_conflict,
|
|
943
|
+
exploration_need=ns.exploration_need,
|
|
944
|
+
verification_cost=ns.verification_cost,
|
|
945
|
+
iteration_intensity=ns.iteration_intensity,
|
|
946
|
+
writable_workstreams=ns.writable_workstreams,
|
|
947
|
+
quality_intent=ns.quality_intent,
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
def _repo_arg(value: str | None) -> tuple[Path | None, bool]:
|
|
952
|
+
if not value or value == "auto":
|
|
953
|
+
return discover_repo_policy(), False
|
|
954
|
+
if value == "none":
|
|
955
|
+
return None, True
|
|
956
|
+
return Path(value), False
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
960
|
+
parser = argparse.ArgumentParser(prog="codex-flow strategy")
|
|
961
|
+
parser.add_argument("--policy", type=Path, default=DEFAULT_POLICY)
|
|
962
|
+
sub = parser.add_subparsers(dest="command")
|
|
963
|
+
sub.add_parser("profiles")
|
|
964
|
+
show = sub.add_parser("show")
|
|
965
|
+
show.add_argument("--json", action="store_true")
|
|
966
|
+
show.add_argument("--effective", action="store_true")
|
|
967
|
+
show.add_argument("--repo-policy", default="auto")
|
|
968
|
+
sub.add_parser("enabled")
|
|
969
|
+
sub.add_parser("enable")
|
|
970
|
+
sub.add_parser("disable")
|
|
971
|
+
sub.add_parser("bypass-once")
|
|
972
|
+
sub.add_parser("bypass-pending")
|
|
973
|
+
sub.add_parser("consume-bypass")
|
|
974
|
+
set_cmd = sub.add_parser("set")
|
|
975
|
+
set_cmd.add_argument("profile", choices=STRATEGIES)
|
|
976
|
+
routing = sub.add_parser("routing")
|
|
977
|
+
routing.add_argument("mode", nargs="?", choices=ROUTING_MODES)
|
|
978
|
+
plan = sub.add_parser("plan")
|
|
979
|
+
plan.add_argument("--profile", choices=STRATEGIES)
|
|
980
|
+
plan.add_argument("--routing", choices=ROUTING_MODES)
|
|
981
|
+
plan.add_argument("--review", choices=REVIEW_MODES)
|
|
982
|
+
plan.add_argument("--fanout", choices=FANOUT_MODES)
|
|
983
|
+
plan.add_argument("--repo-policy", default="auto")
|
|
984
|
+
plan.add_argument("--complexity", choices=COMPLEXITIES, default="routine")
|
|
985
|
+
plan.add_argument("--uncertainty", choices=LEVELS[:3], default="medium")
|
|
986
|
+
plan.add_argument("--risk", choices=LEVELS, default="medium")
|
|
987
|
+
plan.add_argument("--scope", choices=SCOPES, default="module")
|
|
988
|
+
plan.add_argument("--parallelism", choices=PARALLELISM, default="limited")
|
|
989
|
+
plan.add_argument("--write-conflict", choices=("low", "high"), default="low")
|
|
990
|
+
plan.add_argument("--exploration-need", choices=LEVELS[:3], default="medium")
|
|
991
|
+
plan.add_argument("--verification-cost", choices=LEVELS[:3], default="medium")
|
|
992
|
+
plan.add_argument("--iteration-intensity", choices=ITERATION, default="iterative")
|
|
993
|
+
plan.add_argument("--writable-workstreams", type=int, default=1)
|
|
994
|
+
plan.add_argument("--quality-intent", choices=QUALITY_INTENTS, default="normal")
|
|
995
|
+
plan.add_argument("--efficient-reasoning", choices=REASONING_ROLLOUT_MODES)
|
|
996
|
+
plan.add_argument("--quota-pressure", choices=("auto", "unknown", "low", "medium", "high", "critical"), default="auto")
|
|
997
|
+
plan.add_argument("--max-threads", type=int)
|
|
998
|
+
plan.add_argument("--max-repairs", type=int)
|
|
999
|
+
return parser
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def main(argv: Iterable[str] | None = None) -> int:
|
|
1003
|
+
parser = build_parser()
|
|
1004
|
+
ns = parser.parse_args(list(argv) if argv is not None else None)
|
|
1005
|
+
command = ns.command or "show"
|
|
1006
|
+
if command == "profiles":
|
|
1007
|
+
_print_profiles(); return 0
|
|
1008
|
+
if command == "show":
|
|
1009
|
+
if getattr(ns, "effective", False):
|
|
1010
|
+
repo, _disabled = _repo_arg(getattr(ns, "repo_policy", "auto"))
|
|
1011
|
+
try:
|
|
1012
|
+
resolved = resolve_policy(ns.policy, repo)
|
|
1013
|
+
except ValueError as exc:
|
|
1014
|
+
if getattr(ns, "json", False):
|
|
1015
|
+
print(json.dumps({"enabled": False, "valid": False, "error": str(exc)}, ensure_ascii=False, indent=2))
|
|
1016
|
+
else:
|
|
1017
|
+
print(str(exc), file=sys.stderr)
|
|
1018
|
+
return 2
|
|
1019
|
+
result = {"enabled": resolved.enabled, "strategy": resolved.strategy, "routing": resolved.routing, "review": resolved.modifiers.review, "fanout": resolved.modifiers.fanout, "repo_policy": resolved.repo_policy, "valid": True}
|
|
1020
|
+
print(json.dumps(result, ensure_ascii=False, indent=2) if getattr(ns, "json", False) else f"strategy={resolved.strategy} routing={resolved.routing} review={resolved.modifiers.review} fanout={resolved.modifiers.fanout}")
|
|
1021
|
+
return 0
|
|
1022
|
+
strategy = configured_strategy(ns.policy)
|
|
1023
|
+
routing_mode = configured_routing(ns.policy)
|
|
1024
|
+
try:
|
|
1025
|
+
enabled = configured_strategy_enabled(ns.policy); enabled_error = None
|
|
1026
|
+
except ValueError as exc:
|
|
1027
|
+
enabled = False; enabled_error = str(exc)
|
|
1028
|
+
valid = enabled_error is None and strategy in STRATEGIES and routing_mode in ROUTING_MODES
|
|
1029
|
+
if getattr(ns, "json", False):
|
|
1030
|
+
result = {"enabled": enabled, "strategy": strategy, "routing": routing_mode, "valid": valid}
|
|
1031
|
+
if enabled_error is not None: result["error"] = enabled_error
|
|
1032
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
1033
|
+
elif enabled_error is not None:
|
|
1034
|
+
print(enabled_error, file=sys.stderr)
|
|
1035
|
+
else:
|
|
1036
|
+
print(f"enabled={'true' if enabled else 'false'} strategy={strategy} routing={routing_mode}")
|
|
1037
|
+
return 0 if valid else 2
|
|
1038
|
+
if command == "enabled":
|
|
1039
|
+
try: enabled = configured_strategy_enabled(ns.policy)
|
|
1040
|
+
except ValueError as exc:
|
|
1041
|
+
print(str(exc), file=sys.stderr); return 2
|
|
1042
|
+
print("true" if enabled else "false"); return 0
|
|
1043
|
+
if command == "bypass-once":
|
|
1044
|
+
try: enabled = configured_strategy_enabled(ns.policy)
|
|
1045
|
+
except ValueError as exc:
|
|
1046
|
+
print(str(exc), file=sys.stderr); return 2
|
|
1047
|
+
if not enabled:
|
|
1048
|
+
print("strategy dispatch is globally disabled; temporary bypass is unnecessary", file=sys.stderr); return 3
|
|
1049
|
+
arm_temporary_bypass(); print("armed=true"); return 0
|
|
1050
|
+
if command == "bypass-pending":
|
|
1051
|
+
print("true" if temporary_bypass_pending() else "false"); return 0
|
|
1052
|
+
if command == "consume-bypass":
|
|
1053
|
+
print("true" if consume_temporary_bypass() else "false"); return 0
|
|
1054
|
+
if command in {"enable", "disable"}:
|
|
1055
|
+
enabled = command == "enable"
|
|
1056
|
+
set_policy_value(ns.policy, "strategy", "enabled", "true" if enabled else "false", quote=False)
|
|
1057
|
+
print(f"enabled={'true' if enabled else 'false'}"); return 0
|
|
1058
|
+
if command == "set":
|
|
1059
|
+
set_policy_value(ns.policy, "strategy", "profile", ns.profile); print(f"strategy={ns.profile}"); return 0
|
|
1060
|
+
if command == "routing":
|
|
1061
|
+
if ns.mode is None:
|
|
1062
|
+
print(configured_routing(ns.policy)); return 0
|
|
1063
|
+
set_policy_value(ns.policy, "routing", "mode", ns.mode); print(f"routing={ns.mode}"); return 0
|
|
1064
|
+
if command == "plan":
|
|
1065
|
+
repo, _disabled = _repo_arg(ns.repo_policy)
|
|
1066
|
+
try: resolved = resolve_policy(ns.policy, repo)
|
|
1067
|
+
except ValueError as exc:
|
|
1068
|
+
print(str(exc), file=sys.stderr); return 3
|
|
1069
|
+
if not resolved.enabled:
|
|
1070
|
+
print("codex-flow strategy dispatch is disabled; run `codex-flow strategy enable` to re-enable it.", file=sys.stderr); return 3
|
|
1071
|
+
modifiers = Modifiers(review=ns.review or resolved.modifiers.review, fanout=ns.fanout or resolved.modifiers.fanout)
|
|
1072
|
+
max_threads = resolved.max_concurrent_threads if ns.max_threads is None else min(resolved.max_concurrent_threads, ns.max_threads)
|
|
1073
|
+
max_repairs = resolved.max_repair_cycles if ns.max_repairs is None else min(resolved.max_repair_cycles, ns.max_repairs)
|
|
1074
|
+
quota_pressure = detect_quota_pressure() if ns.quota_pressure == "auto" else ns.quota_pressure
|
|
1075
|
+
plan_obj = compile_plan(
|
|
1076
|
+
_task_from_args(ns),
|
|
1077
|
+
strategy=ns.profile or resolved.strategy,
|
|
1078
|
+
routing_mode=ns.routing or resolved.routing,
|
|
1079
|
+
modifiers=modifiers,
|
|
1080
|
+
policy=resolved.capability,
|
|
1081
|
+
runtime=RuntimeState(quota_pressure=quota_pressure, max_concurrent_threads=max_threads, max_repair_cycles=max_repairs),
|
|
1082
|
+
repo_policy=resolved.repo_policy,
|
|
1083
|
+
efficient_reasoning=ns.efficient_reasoning,
|
|
1084
|
+
)
|
|
1085
|
+
print(json.dumps(plan_obj.to_dict(), ensure_ascii=False, indent=2)); return 0
|
|
1086
|
+
parser.error(f"unsupported command: {command}")
|
|
1087
|
+
return 2
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
if __name__ == "__main__":
|
|
1091
|
+
raise SystemExit(main())
|