@matt82198/aesop 0.1.0 → 0.2.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +89 -2
  2. package/README.md +63 -12
  3. package/bin/cli.js +164 -41
  4. package/daemons/run-watchdog.sh +16 -4
  5. package/daemons/selfheal.sh +231 -0
  6. package/docs/ANY-REPO.md +427 -0
  7. package/docs/CONTRIBUTING.md +72 -0
  8. package/docs/DEMO.md +334 -0
  9. package/docs/INSTALL.md +70 -1
  10. package/docs/QUICKSTART.md +80 -0
  11. package/docs/README.md +33 -3
  12. package/docs/TEAM-STATE.md +540 -0
  13. package/driver/CLAUDE.md +148 -0
  14. package/driver/README.md +383 -0
  15. package/driver/aesop.config.example.json +80 -0
  16. package/driver/agent_driver.py +355 -0
  17. package/driver/backend_config.py +253 -0
  18. package/driver/claude_code_driver.py +198 -0
  19. package/driver/codex_driver.py +626 -0
  20. package/driver/openai_compatible_driver.py +249 -0
  21. package/driver/openai_transport.py +146 -0
  22. package/driver/verification_policy.py +75 -0
  23. package/driver/wave_bridge.py +246 -0
  24. package/driver/wave_loop.py +1041 -0
  25. package/hooks/pre-push-policy.sh +109 -28
  26. package/mcp/server.mjs +320 -4
  27. package/package.json +23 -15
  28. package/skills/CLAUDE.md +132 -2
  29. package/skills/buildsystem/SKILL.md +330 -0
  30. package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
  31. package/skills/fleet/SKILL.md +113 -0
  32. package/skills/power/SKILL.md +246 -131
  33. package/state_store/__init__.py +2 -1
  34. package/state_store/api.py +19 -4
  35. package/state_store/coordination.py +209 -0
  36. package/state_store/identity.py +51 -0
  37. package/state_store/store.py +185 -73
  38. package/templates/wave-presets/data.json +65 -0
  39. package/templates/wave-presets/library.json +65 -0
  40. package/templates/wave-presets/saas.json +64 -0
  41. package/tools/audit_report.py +388 -0
  42. package/tools/bench_runner.py +100 -3
  43. package/tools/ci_merge_wait.py +256 -35
  44. package/tools/ci_workflow_lint.py +430 -0
  45. package/tools/claudemd_drift.py +394 -0
  46. package/tools/claudemd_lint.py +359 -0
  47. package/tools/common.py +39 -3
  48. package/tools/cost_ceiling.py +63 -31
  49. package/tools/cost_econ.py +480 -0
  50. package/tools/defect_escape.py +252 -0
  51. package/tools/doctor.js +1 -1
  52. package/tools/eod_sweep.py +58 -0
  53. package/tools/fleet.js +260 -0
  54. package/tools/fleet_ledger.py +209 -7
  55. package/tools/git_identity_check.py +315 -0
  56. package/tools/health-score.js +40 -0
  57. package/tools/health_score.py +361 -0
  58. package/tools/metrics_gate.py +13 -4
  59. package/tools/mutation_test.py +401 -0
  60. package/tools/portability_check.py +206 -0
  61. package/tools/reconcile.py +7 -4
  62. package/tools/secret_scan.py +137 -50
  63. package/tools/self_stats.py +20 -0
  64. package/tools/transcript_digest.py +380 -0
  65. package/tools/verify_activity_filter.py +437 -0
  66. package/tools/verify_agent_inspector.py +2 -0
  67. package/tools/verify_dash.py +2 -0
  68. package/tools/verify_dispatch_panel.py +301 -0
  69. package/tools/verify_failure_drilldown.py +188 -0
  70. package/tools/verify_prboard.py +2 -0
  71. package/tools/verify_scorecards.py +281 -0
  72. package/tools/verify_submit_encoding.py +2 -0
  73. package/tools/verify_ui_trio.py +409 -0
  74. package/tools/verify_wave_telemetry.py +268 -0
  75. package/tools/wave_backlog_analyzer.py +490 -0
  76. package/tools/wave_ledger_hook.py +150 -0
  77. package/tools/wave_preflight.py +512 -0
  78. package/tools/wave_resume.py +215 -0
  79. package/tools/wave_templates.py +215 -0
  80. package/ui/agents.py +68 -14
  81. package/ui/collectors.py +0 -55
  82. package/ui/config.py +7 -2
  83. package/ui/cost.py +231 -12
  84. package/ui/handler.py +183 -0
  85. package/ui/quality_scorecard.py +232 -0
  86. package/ui/wave_audit_tail.py +213 -0
  87. package/ui/wave_dispatch.py +280 -0
  88. package/ui/wave_failure.py +288 -0
  89. package/ui/wave_gantt.py +152 -0
  90. package/ui/wave_reasoning_tail.py +176 -0
  91. package/ui/wave_telemetry.py +377 -0
  92. package/ui/web/dist/assets/index-BGbgw2Nh.js +9 -0
  93. package/ui/web/dist/assets/index-DqZLgwNg.css +1 -0
  94. package/ui/web/dist/index.html +2 -2
  95. package/bin/CLAUDE.md +0 -76
  96. package/daemons/CLAUDE.md +0 -36
  97. package/dash/CLAUDE.md +0 -32
  98. package/docs/archive/README.md +0 -3
  99. package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
  100. package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
  101. package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
  102. package/docs/archive/spikes/tiered-cognition/README.md +0 -27
  103. package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
  104. package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
  105. package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
  106. package/hooks/CLAUDE.md +0 -89
  107. package/mcp/CLAUDE.md +0 -213
  108. package/monitor/CLAUDE.md +0 -40
  109. package/scan/CLAUDE.md +0 -30
  110. package/state_store/CLAUDE.md +0 -39
  111. package/tools/CLAUDE.md +0 -79
  112. package/ui/CLAUDE.md +0 -127
  113. package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
  114. package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env python3
2
+ """OpenAI-compatible backend driver for aesop.
3
+
4
+ Implements AgentDriver for any OpenAI Chat Completions-COMPATIBLE endpoint
5
+ (e.g., OpenRouter, Together, local Ollama). Parameterized by base_url and
6
+ model, supporting optional custom API key environment variable.
7
+
8
+ REUSES Phase 2 CodexDriver execution contract (file injection, JSON schema,
9
+ full-file replacement, ownership enforcement, verification tier 2/3) but
10
+ overrides only the transport wiring and model resolution.
11
+
12
+ stdlib-only, ASCII-only, Windows + Linux safe.
13
+ """
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ from pathlib import Path
19
+ from typing import Dict, Optional
20
+
21
+ # Add driver to path for imports if needed.
22
+ DRIVER_DIR = Path(__file__).resolve().parent
23
+ if str(DRIVER_DIR) not in sys.path:
24
+ sys.path.insert(0, str(DRIVER_DIR))
25
+
26
+ from agent_driver import ( # noqa: E402
27
+ DriverCapabilities,
28
+ ROLE_SETUP,
29
+ ROLE_VERIFY,
30
+ ROLE_WORKER,
31
+ )
32
+ from codex_driver import CodexDriver # noqa: E402
33
+ from openai_transport import _AuthStripRedirectHandler # noqa: E402
34
+
35
+
36
+ def make_openai_compatible_transport(
37
+ base_url: str, api_key_env: str = "OPENAI_API_KEY", timeout_s: float = 120.0
38
+ ):
39
+ """Factory function: return a transport callable for an OpenAI-compatible endpoint.
40
+
41
+ Args:
42
+ base_url: e.g. "https://openrouter.ai/api/v1", "http://localhost:11434/v1"
43
+ api_key_env: environment variable name for the API key (default "OPENAI_API_KEY")
44
+ timeout_s: HTTP timeout in seconds
45
+
46
+ Returns:
47
+ A transport callable (payload dict) -> dict matching the CodexDriver contract.
48
+
49
+ Raises:
50
+ RuntimeError: if the API key env var is not set (unless base_url suggests local Ollama).
51
+ """
52
+ import urllib.error
53
+ import urllib.request
54
+
55
+ def transport(payload: dict) -> dict:
56
+ """POST to the OpenAI-compatible endpoint via urllib."""
57
+ # For local Ollama, the API key may be unused/dummy; for hosted services, get it.
58
+ # The pattern os.environ.get("OPENAI_API_KEY") does not trigger secret_scan
59
+ # because the RHS contains dots/parens; we use a variable for flexibility.
60
+ retrieved_key = os.environ.get(api_key_env)
61
+ if not retrieved_key and "localhost" not in base_url.lower():
62
+ raise RuntimeError(
63
+ f"{api_key_env} environment variable is not set, and "
64
+ f"base_url '{base_url}' does not look like localhost. "
65
+ f"Set {api_key_env} before running, or use a FakeTransport in tests."
66
+ )
67
+
68
+ # Use dummy key for local Ollama if not set.
69
+ if not retrieved_key:
70
+ retrieved_key = "local-only"
71
+
72
+ endpoint = f"{base_url}/chat/completions"
73
+
74
+ # Build the HTTP request.
75
+ payload_json = json.dumps(payload)
76
+ request = urllib.request.Request(
77
+ endpoint,
78
+ data=payload_json.encode("utf-8"),
79
+ headers={
80
+ "Authorization": f"Bearer {retrieved_key}",
81
+ "Content-Type": "application/json",
82
+ },
83
+ )
84
+
85
+ try:
86
+ # POST with hard timeout.
87
+ # Create a custom opener with auth-stripping redirect handler.
88
+ # This prevents Authorization header leakage on cross-origin redirects.
89
+ opener = urllib.request.build_opener(_AuthStripRedirectHandler())
90
+ with opener.open(request, timeout=timeout_s) as response:
91
+ status = response.status
92
+ body = response.read().decode("utf-8")
93
+
94
+ # Classify non-2xx as an error.
95
+ if not (200 <= status < 300):
96
+ raise RuntimeError(
97
+ f"OpenAI-compatible API returned HTTP {status}: {body[:200]}"
98
+ )
99
+
100
+ return json.loads(body)
101
+
102
+ except urllib.error.URLError as exc:
103
+ raise RuntimeError(f"OpenAI-compatible API request failed: {exc}") from exc
104
+ except json.JSONDecodeError as exc:
105
+ raise RuntimeError(f"OpenAI-compatible API returned invalid JSON: {exc}") from exc
106
+
107
+ return transport
108
+
109
+
110
+ class OpenAICompatibleDriver(CodexDriver):
111
+ """AgentDriver for OpenAI-Chat-Completions-COMPATIBLE endpoints.
112
+
113
+ Supports OpenRouter, Together, local Ollama, and any service offering
114
+ OpenAI-compatible endpoints. Configurable base_url, model, and API key env var.
115
+
116
+ REUSES CodexDriver's Phase 2 execution contract entirely (file injection,
117
+ JSON schema validation with retry, full-file replacement, ownership enforcement).
118
+ Overrides only:
119
+ - resolve_model: returns the configured model (not a role map)
120
+ - transport construction: points urllib at the configured base_url
121
+ - probe_capabilities: honest reporting of verification tier (2 for hosted, 3 for local small)
122
+ """
123
+
124
+ name = "openai-compatible"
125
+
126
+ def __init__(
127
+ self,
128
+ base_url: str,
129
+ model: str,
130
+ api_key_env: str = "OPENAI_API_KEY",
131
+ is_local: bool = False,
132
+ model_map: Optional[dict] = None,
133
+ transport: Optional[callable] = None,
134
+ now: Optional[callable] = None,
135
+ max_owned_bytes: int = 200_000,
136
+ max_retries: int = 2,
137
+ timeout_s: float = 120.0,
138
+ ):
139
+ """Initialize the OpenAI-compatible driver.
140
+
141
+ Args:
142
+ base_url: OpenAI-compatible endpoint URL (e.g., https://openrouter.ai/api/v1)
143
+ model: Model ID to use (e.g., "gpt-4-turbo" on OpenRouter, "neural-chat" on Ollama)
144
+ api_key_env: Environment variable name for API key (default "OPENAI_API_KEY").
145
+ For local Ollama, can be unused/dummy.
146
+ is_local: If True, marks this as a local/small model and sets verification tier to 3.
147
+ Default False (hosted models -> tier 2).
148
+ model_map: Optional role-to-model mapping for setup/verify roles (default=None, uses role-based fallback).
149
+ transport: Optional injectable transport callable for testing (default=None, builds from base_url).
150
+ now: callable returning time.time() for testing (default=time.time).
151
+ max_owned_bytes: max total bytes of owned files (default 200KB).
152
+ max_retries: max in-turn retries on malformed JSON (default 2).
153
+ timeout_s: HTTP timeout in seconds (default 120).
154
+ """
155
+ self._base_url = base_url
156
+ self._model = model
157
+ self._api_key_env = api_key_env
158
+ self._is_local = is_local
159
+
160
+ # Build the transport if not provided (for testing).
161
+ if transport is None:
162
+ transport = make_openai_compatible_transport(
163
+ base_url=base_url,
164
+ api_key_env=api_key_env,
165
+ timeout_s=timeout_s,
166
+ )
167
+
168
+ # If no model_map provided, use a simple single-model fallback.
169
+ # All roles use the same model.
170
+ if model_map is None:
171
+ model_map = {}
172
+
173
+ # Initialize CodexDriver with the transport.
174
+ # CodexDriver's __init__ will merge model_map with defaults.
175
+ super().__init__(
176
+ model_map=model_map,
177
+ transport=transport,
178
+ now=now,
179
+ max_owned_bytes=max_owned_bytes,
180
+ max_retries=max_retries,
181
+ timeout_s=timeout_s,
182
+ )
183
+
184
+ def probe_capabilities(self) -> DriverCapabilities:
185
+ """Honest capability matrix for OpenAI-compatible backend.
186
+
187
+ Reuses CodexDriver tier 2 (hosted strong models like OpenRouter GPT-4)
188
+ or tier 3 (local small models like Ollama neural-chat) based on is_local.
189
+
190
+ Same contract as CodexDriver:
191
+ - No native parallelism (orchestrator provides external loop).
192
+ - No worker filesystem/shell (orchestrator injects + runs).
193
+ - Structured output via JSON schema.
194
+ - No worktree isolation (temp-dir).
195
+ - Honest tool-use accuracy (lower for local small models).
196
+ """
197
+ if self._is_local:
198
+ # Local/small model: Tier 3 (higher verification burden).
199
+ # Tool-use accuracy lower than hosted models.
200
+ accuracy = 0.80
201
+ tier = 3
202
+ notes = (
203
+ "Local/small open model (e.g., Ollama neural-chat). Tier 3: "
204
+ "validate all output, heavy spot-check, adversarial review required. "
205
+ "Orchestrator-managed backend: no parallelism, no worker filesystem/shell."
206
+ )
207
+ else:
208
+ # Hosted strong model (e.g., OpenRouter GPT-4): Tier 2.
209
+ # Better accuracy than local models but lower than Claude.
210
+ accuracy = 0.92
211
+ tier = 2
212
+ notes = (
213
+ "Hosted OpenAI-compatible backend (e.g., OpenRouter, Together). "
214
+ "Tier 2: validate all JSON, ~50% spot-check, adversarial review. "
215
+ "Orchestrator-managed backend: no parallelism, no worker filesystem/shell."
216
+ )
217
+
218
+ return DriverCapabilities(
219
+ name=f"{self.name} ({self._model})",
220
+ parallel_dispatch=False, # no native async; orchestrator loops
221
+ worker_filesystem_access=False, # orchestrator injects files
222
+ worker_shell_access=False, # orchestrator runs tests
223
+ structured_output=True, # JSON schema + response_format
224
+ worktree_isolation=False, # temp-dir fallback; no git
225
+ native_cost_tracking=True, # most endpoints report usage
226
+ native_stall_detection=False, # orchestrator times out
227
+ tool_use_accuracy=accuracy,
228
+ recommended_verification_tier=tier,
229
+ available_models=(self._model,),
230
+ notes=notes,
231
+ )
232
+
233
+ def resolve_model(self, role: str) -> str:
234
+ """Resolve an abstract role to the configured model.
235
+
236
+ For an OpenAI-compatible endpoint, always use the configured model.
237
+ This overrides CodexDriver's role-based model mapping since we're a
238
+ single-model adapter (all roles use the same model).
239
+
240
+ Args:
241
+ role: abstract role (worker/setup/verify)
242
+
243
+ Returns:
244
+ The configured model string (same for all roles).
245
+ """
246
+ # Always use self._model for OpenAI-compatible drivers.
247
+ # They target a single endpoint/model, unlike CodexDriver which can
248
+ # vary models by role (worker=gpt-3.5, setup/verify=gpt-4).
249
+ return self._model
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+ """OpenAI HTTP transport for the AgentDriver codex_driver.
3
+
4
+ Provides a callable transport that POSTs to the OpenAI Chat Completions API
5
+ via stdlib urllib.request. This is the injectable seam that lets tests pass
6
+ canned responses without network/secrets, and lets the driver stay dependency-light.
7
+
8
+ Transport contract:
9
+ Transport = callable(payload: dict) -> dict
10
+ where payload is the OpenAI Chat Completions request body (with schema + messages)
11
+ and returns the parsed response JSON (with choices/usage).
12
+
13
+ The real transport reads OPENAI_API_KEY from os.environ at call time (NEVER
14
+ hardcoded). Use the pattern:
15
+ key = os.environ.get("OPENAI_API_KEY")
16
+ so the source code string does NOT trigger secret_scan rules.
17
+ """
18
+
19
+ import json
20
+ import os
21
+ import urllib.error
22
+ import urllib.parse
23
+ import urllib.request
24
+
25
+
26
+ # Type alias documenting the transport callable contract.
27
+ Transport = callable # (payload: dict) -> dict
28
+
29
+
30
+ class _AuthStripRedirectHandler(urllib.request.HTTPRedirectHandler):
31
+ """Custom redirect handler that strips Authorization on cross-origin redirects.
32
+
33
+ This prevents credentials from leaking to a different host if the base_url
34
+ or a network MITM redirects the request (e.g., 301/302 to evil.com).
35
+ Same-origin redirects preserve the Authorization header.
36
+ """
37
+
38
+ # Sensitive headers that must not leak on cross-origin redirects.
39
+ _SENSITIVE_HEADERS = {"authorization", "api-key", "x-api-key"}
40
+
41
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
42
+ """Handle redirect: strip sensitive headers if the origin changed.
43
+
44
+ Args:
45
+ req: original Request object
46
+ fp: file pointer (unused here)
47
+ code: HTTP redirect status code (301, 302, 307, etc.)
48
+ msg: HTTP reason message (unused here)
49
+ headers: response headers (unused here)
50
+ newurl: the redirect target URL
51
+
52
+ Returns:
53
+ A new urllib.request.Request with Authorization stripped if
54
+ newurl is a different origin; otherwise a Request preserving headers.
55
+ """
56
+ # Parse both URLs to extract origin (scheme + host + port).
57
+ orig_parsed = urllib.parse.urlparse(req.full_url)
58
+ new_parsed = urllib.parse.urlparse(newurl)
59
+
60
+ # Build comparable origins (normalize port if not specified).
61
+ orig_origin = (
62
+ orig_parsed.scheme,
63
+ orig_parsed.hostname or "",
64
+ orig_parsed.port or (443 if orig_parsed.scheme == "https" else 80),
65
+ )
66
+ new_origin = (
67
+ new_parsed.scheme,
68
+ new_parsed.hostname or "",
69
+ new_parsed.port or (443 if new_parsed.scheme == "https" else 80),
70
+ )
71
+
72
+ # Let the parent class build the redirected request normally.
73
+ redirected = super().redirect_request(
74
+ req, fp, code, msg, headers, newurl
75
+ )
76
+
77
+ # If origins differ, strip sensitive headers from the new request.
78
+ if orig_origin != new_origin and redirected:
79
+ for header_name in list(redirected.headers.keys()):
80
+ if header_name.lower() in self._SENSITIVE_HEADERS:
81
+ del redirected.headers[header_name]
82
+
83
+ return redirected
84
+
85
+
86
+ def default_openai_transport(
87
+ payload: dict, timeout_s: float = 120.0, base_url: str = "https://api.openai.com/v1"
88
+ ) -> dict:
89
+ """Default transport: POST to OpenAI Chat Completions via urllib.
90
+
91
+ Args:
92
+ payload: OpenAI Chat Completions request body (messages, model, etc.).
93
+ timeout_s: HTTP timeout in seconds (default 120).
94
+ base_url: OpenAI API base URL (default production).
95
+
96
+ Returns:
97
+ Parsed JSON response (choices, usage, etc.).
98
+
99
+ Raises:
100
+ RuntimeError: if OPENAI_API_KEY env var is not set, or if the HTTP
101
+ status is not 200-299.
102
+ urllib.error.URLError: if the HTTP request fails.
103
+ """
104
+ # Read API key at call time from environment; NEVER hardcoded.
105
+ # The pattern os.environ.get("OPENAI_API_KEY") does not trigger secret_scan
106
+ # because the RHS contains dots/parens.
107
+ api_key = os.environ.get("OPENAI_API_KEY")
108
+ if not api_key:
109
+ raise RuntimeError(
110
+ "OPENAI_API_KEY environment variable is not set. "
111
+ "Set it before running the codex driver, or pass a FakeTransport in tests."
112
+ )
113
+
114
+ endpoint = f"{base_url}/chat/completions"
115
+
116
+ # Build the HTTP request.
117
+ payload_json = json.dumps(payload)
118
+ request = urllib.request.Request(
119
+ endpoint,
120
+ data=payload_json.encode("utf-8"),
121
+ headers={
122
+ "Authorization": f"Bearer {api_key}",
123
+ "Content-Type": "application/json",
124
+ },
125
+ )
126
+
127
+ try:
128
+ # Create a custom opener with auth-stripping redirect handler.
129
+ # This prevents Authorization header leakage on cross-origin redirects.
130
+ opener = urllib.request.build_opener(_AuthStripRedirectHandler())
131
+ with opener.open(request, timeout=timeout_s) as response:
132
+ status = response.status
133
+ body = response.read().decode("utf-8")
134
+
135
+ # Classify non-2xx as an error.
136
+ if not (200 <= status < 300):
137
+ raise RuntimeError(
138
+ f"OpenAI API returned HTTP {status}: {body[:200]}"
139
+ )
140
+
141
+ return json.loads(body)
142
+
143
+ except urllib.error.URLError as exc:
144
+ raise RuntimeError(f"OpenAI API request failed: {exc}") from exc
145
+ except json.JSONDecodeError as exc:
146
+ raise RuntimeError(f"OpenAI API returned invalid JSON: {exc}") from exc
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """Verification policy mapping for AgentDriver backends.
3
+
4
+ Pure function: given a DriverCapabilities (with recommended_verification_tier),
5
+ return a dict of verification tuning parameters that the wave's integration
6
+ verifier can use to decide how aggressively to spot-check and repair.
7
+
8
+ This encodes spike-multitool-portability.md Section 4.3: the tier->policy
9
+ mapping that tells the template what verification bars each backend owes.
10
+
11
+ No I/O, no imports beyond agent_driver.
12
+ """
13
+
14
+ from agent_driver import DriverCapabilities
15
+
16
+
17
+ def verification_policy(caps: DriverCapabilities) -> dict:
18
+ """Map a backend's verification tier to orchestrator verification tuning.
19
+
20
+ Returns a dict with the following keys (all required):
21
+ validate_all_json: bool -- if True, validate every worker's JSON output
22
+ (Tier 2+); if False, trust it (Tier 1).
23
+ spot_check_frac: float in [0.0, 1.0] -- fraction of results to spot-check.
24
+ repair_cap: int -- maximum repair attempts per worker.
25
+ require_adversarial_review: bool -- if True, require refutation-style
26
+ review (reason about code vs contract, not just re-run tests).
27
+
28
+ The tier is a proxy for tool_use_accuracy and determines the orchestrator's
29
+ burden. Weaker backends (lower accuracy) require higher tiers + heavier
30
+ verification.
31
+
32
+ Args:
33
+ caps: DriverCapabilities from probe_capabilities().
34
+
35
+ Returns:
36
+ dict with verification tuning.
37
+
38
+ Raises:
39
+ ValueError: if tier is not in [1, 2, 3, 4].
40
+ """
41
+ tier = caps.recommended_verification_tier
42
+
43
+ # Map tier -> policy per spike Section 4.3.
44
+ if tier == 1:
45
+ return {
46
+ "validate_all_json": False,
47
+ "spot_check_frac": 0.10,
48
+ "repair_cap": 1,
49
+ "require_adversarial_review": False,
50
+ }
51
+ elif tier == 2:
52
+ return {
53
+ "validate_all_json": True,
54
+ "spot_check_frac": 0.50,
55
+ "repair_cap": 2,
56
+ "require_adversarial_review": True,
57
+ }
58
+ elif tier == 3:
59
+ return {
60
+ "validate_all_json": True,
61
+ "spot_check_frac": 1.00,
62
+ "repair_cap": 2,
63
+ "require_adversarial_review": True,
64
+ }
65
+ elif tier == 4:
66
+ return {
67
+ "validate_all_json": True,
68
+ "spot_check_frac": 1.00,
69
+ "repair_cap": 3,
70
+ "require_adversarial_review": True,
71
+ }
72
+ else:
73
+ raise ValueError(
74
+ f"Unknown verification tier {tier}; must be in [1, 2, 3, 4]"
75
+ )