@matt82198/aesop 0.2.0 → 0.3.1
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/CHANGELOG.md +28 -0
- package/README.md +50 -260
- package/bin/cli.js +7 -3
- package/docs/HOOK-INSTALL.md +15 -56
- package/docs/INSTALL.md +4 -3
- package/docs/PORTING.md +166 -0
- package/docs/TEAM-STATE.md +16 -184
- package/docs/reproduce.md +33 -3
- package/driver/CLAUDE.md +50 -48
- package/driver/codex_driver.py +59 -12
- package/driver/openai_transport.py +33 -0
- package/driver/wave_bridge.py +9 -1
- package/driver/wave_loop.py +437 -70
- package/driver/wave_scheduler.py +890 -0
- package/hooks/pre-push-policy.sh +22 -5
- package/monitor/collect-signals.mjs +69 -0
- package/package.json +4 -4
- package/state_store/__init__.py +2 -1
- package/state_store/projections.py +63 -0
- package/state_store/read_api.py +156 -0
- package/state_store/write_api.py +462 -0
- package/tools/cost_ceiling.py +106 -15
- package/tools/cost_projection.py +559 -0
- package/tools/crossos_drift.py +394 -0
- package/tools/eod_sweep.py +137 -33
- package/tools/mutation_test.py +130 -8
- package/tools/proposals.mjs +47 -2
- package/tools/reproduce.js +405 -0
- package/tools/secret_scan.py +73 -18
- package/tools/stall_check.py +247 -16
- package/tools/stateapi_lint.py +325 -0
- package/tools/test_battery.py +173 -0
- package/tools/verify_cost_panel.py +345 -0
- package/tools/wave_preflight.py +296 -29
- package/tools/wave_templates.py +170 -45
- package/ui/collectors.py +81 -0
- package/ui/handler.py +230 -85
- package/ui/sse.py +3 -3
- package/ui/wave_telemetry.py +24 -18
- package/ui/web/dist/assets/{index-DqZLgwNg.css → index-CNQxaiOW.css} +1 -1
- package/ui/web/dist/assets/index-CP68RIh3.js +9 -0
- package/ui/web/dist/index.html +2 -2
- package/docs/QUICKSTART.md +0 -80
- package/ui/web/dist/assets/index-BGbgw2Nh.js +0 -9
package/driver/codex_driver.py
CHANGED
|
@@ -63,15 +63,25 @@ except ImportError:
|
|
|
63
63
|
default_openai_transport = None
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
# Abstract-role -> OpenAI model mapping. Workers map to gpt-
|
|
66
|
+
# Abstract-role -> OpenAI model mapping. Workers map to gpt-4o-mini (supports json_schema);
|
|
67
67
|
# setup/verify to gpt-4-turbo (stronger). User decision #1 (plan Section 7)
|
|
68
|
-
# allows upgrading to gpt-4o
|
|
68
|
+
# allows upgrading to gpt-4o; gpt-3.5-turbo does NOT support response_format json_schema.
|
|
69
69
|
_DEFAULT_MODEL_MAP = {
|
|
70
|
-
ROLE_WORKER: "gpt-
|
|
70
|
+
ROLE_WORKER: "gpt-4o-mini",
|
|
71
71
|
ROLE_SETUP: "gpt-4-turbo",
|
|
72
72
|
ROLE_VERIFY: "gpt-4-turbo",
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
# Models that support response_format with type json_schema.
|
|
76
|
+
# Raise ValueError at __init__ if a mapped model is not in this set.
|
|
77
|
+
JSON_SCHEMA_CAPABLE = {
|
|
78
|
+
"gpt-4o",
|
|
79
|
+
"gpt-4o-mini",
|
|
80
|
+
"gpt-4-turbo",
|
|
81
|
+
"gpt-4.1",
|
|
82
|
+
"gpt-4.1-preview",
|
|
83
|
+
}
|
|
84
|
+
|
|
75
85
|
# Default schema for structured worker output: full-file replacements.
|
|
76
86
|
# See plan Section 2.1.
|
|
77
87
|
WORKER_PATCH_SCHEMA = {
|
|
@@ -165,6 +175,7 @@ class CodexDriver(AgentDriver):
|
|
|
165
175
|
max_owned_bytes: int = 200_000,
|
|
166
176
|
max_retries: int = 2,
|
|
167
177
|
timeout_s: float = 120.0,
|
|
178
|
+
allow_unverified_models: bool = False,
|
|
168
179
|
):
|
|
169
180
|
"""Initialize the CodexDriver with optional overrides.
|
|
170
181
|
|
|
@@ -175,11 +186,27 @@ class CodexDriver(AgentDriver):
|
|
|
175
186
|
max_owned_bytes: max total bytes of owned files before pre-dispatch fail (default 200KB).
|
|
176
187
|
max_retries: max in-turn retries on malformed JSON (default 2).
|
|
177
188
|
timeout_s: HTTP timeout in seconds (default 120).
|
|
189
|
+
allow_unverified_models: if True, allow models not known to support json_schema
|
|
190
|
+
(default False). Set to True only for experimental backends.
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
ValueError: if any mapped model is not in JSON_SCHEMA_CAPABLE and
|
|
194
|
+
allow_unverified_models is False.
|
|
178
195
|
"""
|
|
179
196
|
self._model_map = dict(_DEFAULT_MODEL_MAP)
|
|
180
197
|
if model_map:
|
|
181
198
|
self._model_map.update(model_map)
|
|
182
199
|
|
|
200
|
+
# Validate that all mapped models support json_schema response_format.
|
|
201
|
+
if not allow_unverified_models:
|
|
202
|
+
for role, model in self._model_map.items():
|
|
203
|
+
if model not in JSON_SCHEMA_CAPABLE:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f"Model '{model}' mapped to role '{role}' does not support "
|
|
206
|
+
f"response_format json_schema. Known capable models: {sorted(JSON_SCHEMA_CAPABLE)}. "
|
|
207
|
+
f"Pass allow_unverified_models=True to override (for experimental backends)."
|
|
208
|
+
)
|
|
209
|
+
|
|
183
210
|
self._transport = transport or default_openai_transport
|
|
184
211
|
self._now = now or time.time
|
|
185
212
|
self._max_owned_bytes = max_owned_bytes
|
|
@@ -414,18 +441,36 @@ class CodexDriver(AgentDriver):
|
|
|
414
441
|
last_error = str(exc)
|
|
415
442
|
# If we have retries left, append error feedback and retry.
|
|
416
443
|
if attempt < self._max_retries:
|
|
444
|
+
# Before appending retry messages, check if total payload would exceed budget.
|
|
445
|
+
# Serialize the current payload + proposed new messages to estimate size.
|
|
446
|
+
error_msg = f"(attempt {attempt+1} failed: {last_error})"
|
|
447
|
+
nudge_msg = "Previous response was not valid JSON per the schema; return ONLY the JSON object."
|
|
448
|
+
|
|
449
|
+
# Estimate size of new messages to be added
|
|
450
|
+
test_payload = json.dumps(payload)
|
|
451
|
+
new_messages = [
|
|
452
|
+
{"role": "assistant", "content": error_msg},
|
|
453
|
+
{"role": "user", "content": nudge_msg},
|
|
454
|
+
]
|
|
455
|
+
test_payload_with_retry = json.dumps({
|
|
456
|
+
**payload,
|
|
457
|
+
"messages": payload["messages"] + new_messages
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
if len(test_payload_with_retry.encode("utf-8")) > self._max_owned_bytes:
|
|
461
|
+
return WorkerResult(
|
|
462
|
+
worker_id=worker_id,
|
|
463
|
+
status=WORKER_FAILED,
|
|
464
|
+
ok=False,
|
|
465
|
+
error=f"budget_exceeded_on_retry: retry would exceed context budget ({len(test_payload_with_retry)} > {self._max_owned_bytes})",
|
|
466
|
+
)
|
|
467
|
+
|
|
417
468
|
payload["messages"].append(
|
|
418
469
|
{
|
|
419
470
|
"role": "assistant",
|
|
420
|
-
"content":
|
|
421
|
-
f"(attempt {attempt+1} failed: {last_error})"
|
|
422
|
-
),
|
|
471
|
+
"content": error_msg,
|
|
423
472
|
}
|
|
424
473
|
)
|
|
425
|
-
# Deterministic nudge: instructs model to return valid JSON only.
|
|
426
|
-
# Temperature remains 0 (no sampling change). This is reproducible:
|
|
427
|
-
# same input + same nudge + temp=0 -> same output (idempotent retry).
|
|
428
|
-
nudge_msg = "Previous response was not valid JSON per the schema; return ONLY the JSON object."
|
|
429
474
|
payload["messages"].append(
|
|
430
475
|
{
|
|
431
476
|
"role": "user",
|
|
@@ -449,11 +494,12 @@ class CodexDriver(AgentDriver):
|
|
|
449
494
|
for file_entry in structured.get("files", []):
|
|
450
495
|
path_str = file_entry.get("path", "")
|
|
451
496
|
if path_str not in request.owned_files:
|
|
497
|
+
# Distinguish: path not in the owned set (security/isolation violation).
|
|
452
498
|
return WorkerResult(
|
|
453
499
|
worker_id=worker_id,
|
|
454
500
|
status=WORKER_FAILED,
|
|
455
501
|
ok=False,
|
|
456
|
-
error=f"worker attempted to write
|
|
502
|
+
error=f"out-of-scope: worker attempted to write {path_str} (not in owned set)",
|
|
457
503
|
)
|
|
458
504
|
files_to_write.append((path_str, file_entry["contents"]))
|
|
459
505
|
|
|
@@ -465,11 +511,12 @@ class CodexDriver(AgentDriver):
|
|
|
465
511
|
full_path.write_text(new_contents, encoding="utf-8")
|
|
466
512
|
written_paths.append(path_str)
|
|
467
513
|
except OSError as exc:
|
|
514
|
+
# Distinguish: owned path exists but write failed (OS error).
|
|
468
515
|
return WorkerResult(
|
|
469
516
|
worker_id=worker_id,
|
|
470
517
|
status=WORKER_FAILED,
|
|
471
518
|
ok=False,
|
|
472
|
-
error=f"
|
|
519
|
+
error=f"write_failed: {path_str}: {exc}",
|
|
473
520
|
)
|
|
474
521
|
|
|
475
522
|
# 10. Cost tracking: read usage.total_tokens (fail-closed-honest).
|
|
@@ -140,6 +140,39 @@ def default_openai_transport(
|
|
|
140
140
|
|
|
141
141
|
return json.loads(body)
|
|
142
142
|
|
|
143
|
+
except urllib.error.HTTPError as exc:
|
|
144
|
+
# Extract error details from response body if available.
|
|
145
|
+
# HTTPError.fp contains the response body with potentially actionable
|
|
146
|
+
# error codes (e.g., insufficient_quota vs rate_limit_exceeded).
|
|
147
|
+
error_code = None
|
|
148
|
+
error_message = None
|
|
149
|
+
if exc.fp:
|
|
150
|
+
try:
|
|
151
|
+
# Read bounded response body (~500 bytes) to avoid memory issues.
|
|
152
|
+
error_body = exc.fp.read(500).decode("utf-8", errors="replace")
|
|
153
|
+
error_data = json.loads(error_body)
|
|
154
|
+
if isinstance(error_data, dict) and "error" in error_data:
|
|
155
|
+
error_obj = error_data["error"]
|
|
156
|
+
if isinstance(error_obj, dict):
|
|
157
|
+
error_code = error_obj.get("code")
|
|
158
|
+
error_message = error_obj.get("message")
|
|
159
|
+
except (json.JSONDecodeError, ValueError, AttributeError):
|
|
160
|
+
# If we can't parse the body, fall back to plain message.
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# Build exception message with extracted details if available.
|
|
164
|
+
# Never include request headers or API key material.
|
|
165
|
+
if error_code or error_message:
|
|
166
|
+
details = []
|
|
167
|
+
if error_code:
|
|
168
|
+
details.append(error_code)
|
|
169
|
+
if error_message:
|
|
170
|
+
details.append(error_message)
|
|
171
|
+
exc_msg = f"OpenAI API request failed: HTTP Error {exc.code}: {exc.msg} [{', '.join(details)}]"
|
|
172
|
+
else:
|
|
173
|
+
exc_msg = f"OpenAI API request failed: HTTP Error {exc.code}: {exc.msg}"
|
|
174
|
+
raise RuntimeError(exc_msg) from exc
|
|
175
|
+
|
|
143
176
|
except urllib.error.URLError as exc:
|
|
144
177
|
raise RuntimeError(f"OpenAI API request failed: {exc}") from exc
|
|
145
178
|
except json.JSONDecodeError as exc:
|
package/driver/wave_bridge.py
CHANGED
|
@@ -94,9 +94,17 @@ def build_manifest_item(driver: AgentDriver, item: Dict[str, Any]) -> Dict[str,
|
|
|
94
94
|
model = driver.resolve_model(ROLE_WORKER)
|
|
95
95
|
|
|
96
96
|
# Resolve all four policy knobs from the backend's verification tier.
|
|
97
|
-
|
|
97
|
+
# Wrap to provide driver name in error.
|
|
98
|
+
try:
|
|
99
|
+
policy = verification_policy(caps)
|
|
100
|
+
except ValueError as e:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Unsupported verification tier {caps.recommended_verification_tier} "
|
|
103
|
+
f"from driver '{driver.name}': {e}"
|
|
104
|
+
)
|
|
98
105
|
|
|
99
106
|
# Copy the input item and enrich with model, tier, and all four policy knobs.
|
|
107
|
+
# Preserve all original fields including the optional `repo` field.
|
|
100
108
|
result = dict(item)
|
|
101
109
|
result["model"] = model
|
|
102
110
|
result["verificationTier"] = caps.recommended_verification_tier
|