@matt82198/aesop 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +14 -6
- package/aesop.config.example.json +16 -0
- package/bin/cli.js +5 -2
- package/docs/INSTALL.md +104 -14
- package/docs/MICROKERNEL.md +452 -0
- package/docs/README.md +4 -0
- package/docs/THE-AESOP-HYPOTHESIS.md +140 -0
- package/driver/CLAUDE.md +123 -123
- package/driver/README.md +40 -2
- package/driver/adjudication_gate.py +367 -0
- package/driver/aesop.config.example.json +20 -4
- package/driver/backend_config.py +603 -12
- package/driver/claude_code_driver.py +9 -17
- package/driver/codex_driver.py +80 -25
- package/driver/context_pack.py +454 -0
- package/driver/openai_compatible_driver.py +53 -11
- package/driver/openai_transport.py +31 -10
- package/driver/orchestrator_backend.py +332 -0
- package/driver/orchestrator_driver.py +589 -0
- package/driver/proc_util.py +134 -0
- package/driver/wave_loop.py +801 -59
- package/driver/wave_scheduler.py +361 -37
- package/monitor/collect-signals.mjs +83 -0
- package/package.json +1 -1
- package/state_store/coordination.py +65 -5
- package/tools/ci_merge_wait.py +88 -42
- package/tools/ci_shard_runner.py +128 -0
- package/tools/seated_shadow_adjudication.py +920 -0
- package/tools/shadow_adjudication.py +1024 -0
- package/tools/verify_ui_trio_redaction_proof.py +292 -0
package/driver/CLAUDE.md
CHANGED
|
@@ -1,150 +1,150 @@
|
|
|
1
1
|
# driver/ — AgentDriver backend-portability seam
|
|
2
2
|
|
|
3
|
-
**What**: the domain contract letting aesop's wave loop run on non-Claude backends
|
|
4
|
-
(Codex, open models); the loop dispatches only through the `AgentDriver` interface.
|
|
5
|
-
**Phases 1-3 shipped** (interface + reference adapter; Codex Chat Completions; wave bridge).
|
|
3
|
+
**What**: the domain contract letting aesop's wave loop run on non-Claude backends; the loop dispatches only through the `AgentDriver` interface. **Phases 1-3 shipped**.
|
|
6
4
|
|
|
7
5
|
## Files
|
|
8
6
|
|
|
9
|
-
- **agent_driver.py** — the `AgentDriver` ABC + capability/request/result
|
|
10
|
-
|
|
11
|
-
- **claude_code_driver.py** — reference adapter (Claude Code parity)
|
|
12
|
-
|
|
13
|
-
- **codex_driver.py** — Phase 2
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
no API key or network.
|
|
21
|
-
- **openai_compatible_driver.py** — OpenAI-compatible backend (Ollama, OpenRouter, etc.)
|
|
22
|
-
-
|
|
23
|
-
|
|
7
|
+
- **agent_driver.py** — the `AgentDriver` ABC + capability/request/result dataclasses.
|
|
8
|
+
The contract. stdlib-only, no provider SDKs.
|
|
9
|
+
- **claude_code_driver.py** — reference adapter (Claude Code parity): two ops concrete
|
|
10
|
+
Python, three serviced by the harness.
|
|
11
|
+
- **codex_driver.py** — Phase 2: OpenAI Chat Completions HTTP backend: dispatch_worker (file
|
|
12
|
+
injection, JSON retry), run_command (own `command_timeout_s` knob, defaults to timeout_s), worker_status, get_tokens_spent. Transport injectable.
|
|
13
|
+
- **proc_util.py** — `run_shell_bounded()`: shared run_command backing (RS-A F1/F7). Timeout
|
|
14
|
+
truly bounds wall-clock: child in own group/session; on expiry the WHOLE tree is killed
|
|
15
|
+
(taskkill /T /F Windows, killpg POSIX), exit 124 promptly, partial output preserved. Never
|
|
16
|
+
subprocess.run(shell=True, timeout=) — Windows kills only cmd.exe then re-blocks on the orphaned grandchild.
|
|
17
|
+
- **openai_transport.py** — stdlib urllib transport for the OpenAI endpoint; injectable
|
|
18
|
+
seam (FakeTransport in tests: no API key or network).
|
|
19
|
+
- **openai_compatible_driver.py** — OpenAI-compatible backend (Ollama, OpenRouter, etc.);
|
|
20
|
+
construct-time validate_base_url; key waiver only via loopback-validated is_local.
|
|
21
|
+
- **verification_policy.py** — Maps verification tier -> orchestrator tuning (validate_all_json, spot_check_frac, repair_cap, require_adversarial_review).
|
|
24
22
|
- **wave_loop.py** — the wave ENGINE: preflight ownership guard, parallel build,
|
|
25
|
-
bounded repair,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
bounded repair, per-repo batched git ship, recovery journal. HS-2 (live seat swap):
|
|
24
|
+
`run_wave(orchestrator_backend=...)` Phase 6 routes ONE final_catch decision per
|
|
25
|
+
test-VERIFIED item through a configured orchestrator seat (schema: decisions/
|
|
26
|
+
final_catch.schema.json): merge=approve; block=verified flipped False, not shipped,
|
|
27
|
+
journal rewritten, AND written files QUARANTINED (FILE paths only — empty/dot/directory
|
|
28
|
+
pathspecs rejected with per-file error records, no cross-item destruction; tracked ->
|
|
29
|
+
git checkout, untracked deleted ONLY on clean ls-files exit-1, ambiguous = error never
|
|
30
|
+
delete; globs `:(literal)`-neutralized; backslashes normalized; outside git = honest
|
|
31
|
+
skip, surfaced in Report.blocked); escalate/undetermined/DECISION_FAILED=ship with
|
|
32
|
+
honest record (crash-only degradation — a seat outage never fabricates or blocks).
|
|
33
|
+
None/null harness backend -> "deferred", byte-identical to pre-HS-2 (no key, no
|
|
34
|
+
backend constructed). Repair stays mechanical on both seats. orchestrator_review
|
|
35
|
+
carries verdict_counts, blocked_detail {slug,reason}, seat_tokens_spent and
|
|
36
|
+
gate_status ("degraded" when all decisions failed); after decisions the ceiling is
|
|
37
|
+
re-checked with driver+seat spend (abort_reason=cost_ceiling_exceeded_after_decisions;
|
|
38
|
+
an unmetered None driver spend never crashes — seat-only spend or ledger fallback) —
|
|
39
|
+
live-seat path only. RS3-W round-2 robustness: claim gate REAL + fail-CLOSED
|
|
40
|
+
(coordination reads EventStore streams, TTL expiry, stale-lease release by slug key;
|
|
41
|
+
claim errors SKIP — never claim-less dispatch/repair); journal entries
|
|
42
|
+
fingerprint-bound + atomic (reused slug never inherits stale verified state; torn
|
|
43
|
+
writes can't corrupt resume); resumed-verified items restore filesWritten and always
|
|
44
|
+
reach a terminal shipped record (honest no_changes no-op ship — no recovery livelock);
|
|
45
|
+
dup slugs rejected at preflight; executor failures recorded, green never vacuous;
|
|
46
|
+
Windows quoting doubles only quote-preceding backslashes. RS5 claim lifecycle:
|
|
47
|
+
wave-level instance id, ttl sized to the driver command timeout (10x, 1h floor — never
|
|
48
|
+
the 300s default a real build outlives); claim HELD across build -> repair -> ship,
|
|
49
|
+
released exactly once in run_wave's finally (every exit path); repair re-dispatch and
|
|
50
|
+
ship FENCED via current_holder (lost claim = honest claim_lost abort, never
|
|
51
|
+
double-ship; try_claim retracts on error, no phantom holder). Tests: test_wave_loop_rs3.
|
|
52
|
+
- **wave_bridge.py** — Phase 3: bridges AgentDriver backends to wave manifest items
|
|
53
|
+
(build_manifest_item / dispatch_item; green ONLY from test exit code, see below).
|
|
54
|
+
- **backend_config.py** — HS-1 unified two-seat config: `seats.worker` + `seats.orchestrator`
|
|
55
|
+
in aesop.config.json select BOTH seats from ONE block (legacy flat block parses but is
|
|
56
|
+
INERT in the scheduler default; seats.worker wins when both present). `build_driver()` =
|
|
57
|
+
worker seat (raw-dict paths run the loader's validation); `build_orchestrator_backend()`
|
|
58
|
+
= decision seat (absent/harness/claude → null `HarnessOrchestratorBackend`;
|
|
59
|
+
openai-compatible → configured backend). Guards: base_url SSRF check incl. time-bounded
|
|
60
|
+
DNS resolution; `is_local` pinned to loopback (key waiver keys off VALIDATED is_local,
|
|
61
|
+
never URL text); `api_key_env` allowlist-primary (SECRET/TOKEN/... fragments
|
|
62
|
+
hard-reject, other key-shaped names loud NOTICE). NO seats block = byte-identical to
|
|
63
|
+
today (no key needed).
|
|
64
|
+
- **context_pack.py** — build_context_pack() reads ONLY allowlisted control files
|
|
65
|
+
(STATE.md, BUILDLOG.md, tracker.json, MEMORY.md, explicit brief: under repo/conductor
|
|
66
|
+
roots) — cardinal rule 4 in code. Size-bounded, deterministic truncation, manifest.
|
|
67
|
+
- **orchestrator_backend.py** — OrchestratorBackend protocol: decide_call(prompt, schema)
|
|
68
|
+
→ raw text. Real impl: OpenAICompatibleOrchestratorBackend (gpt-5 temperature fallback;
|
|
69
|
+
api_key_env + is_local dummy-key; validate_base_url in __init__). HarnessOrchestratorBackend
|
|
70
|
+
= null default seat (decide_call raises: the live harness IS the orchestrator). Fake for tests.
|
|
71
|
+
- **orchestrator_driver.py** — OrchestratorDriver: structured verdicts via the
|
|
72
|
+
OrchestratorBackend protocol (no AgentDriver coupling).
|
|
73
|
+
- **adjudication_gate.py** — increment 3 (conservative): two-tier escalation gate — cheaper
|
|
74
|
+
challenger decides; undetermined/low-conf/disallowed-type/content-seeded-spot-check calls
|
|
75
|
+
escalate to the incumbent (frontier). Never emits an unconfident verdict as final.
|
|
76
|
+
- **decisions/** — Decision type schema registry (sibling lane owns schemas; absent = optional).
|
|
77
|
+
- **../tests/** — test_agent_driver (contract), test_codex_driver_e2e (offline + gated
|
|
78
|
+
live), test_wave_bridge, test_orchestrator_driver, test_adjudication_gate,
|
|
79
|
+
test_hs2_swap_proof, test_hs2_block_gate, test_wave_loop_rs3 (round-2 robustness:
|
|
80
|
+
N1/N3/N4/N5/N6/N7/N10 — all offline). Details: tests/CLAUDE.md.
|
|
38
81
|
|
|
39
82
|
## The five operations (what the wave loop needs from ANY backend)
|
|
40
83
|
|
|
41
|
-
1. `probe_capabilities() -> DriverCapabilities` — honest self-report (parallel?
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
command, and return a **structured** result (extent reported by the probe).
|
|
47
|
-
3. `worker_status(worker_id) -> WorkerStatus` — liveness / stall detection for
|
|
48
|
-
the watchdog.
|
|
49
|
-
4. `run_command(command, cwd, shell) -> CommandResult` — ORCHESTRATOR-side
|
|
50
|
-
command execution (tests, git, verification). Distinct from a worker shell.
|
|
51
|
-
5. `resolve_model(role) -> str` — map an abstract role (`worker`/`setup`/
|
|
52
|
-
`verify`) to a concrete backend model id.
|
|
53
|
-
|
|
54
|
-
Optional (non-abstract): `get_tokens_spent()`.
|
|
84
|
+
1. `probe_capabilities() -> DriverCapabilities` — honest self-report (parallel? fs? shell? structured? worktree? cost? accuracy? → verification tier). Read once; everything keys off it.
|
|
85
|
+
2. `dispatch_worker(request) -> WorkerResult` — spawn ONE isolated worker (prompt + owned_files + workdir); may read/write/run + return a **structured** result (extent per probe).
|
|
86
|
+
3. `worker_status(worker_id) -> WorkerStatus` — liveness / stall detection for the watchdog.
|
|
87
|
+
4. `run_command(command, cwd, shell) -> CommandResult` — ORCHESTRATOR-side exec (tests, git, verify). Distinct from a worker shell.
|
|
88
|
+
5. `resolve_model(role) -> str` — map `worker`/`setup`/`verify` to a concrete backend model id. Optional (non-abstract): `get_tokens_spent()`.
|
|
55
89
|
|
|
56
90
|
## Invariants
|
|
57
91
|
|
|
58
|
-
- The wave loop calls **only** `AgentDriver` methods — never `agent()`,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- **
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
##
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
| worker filesystem | yes | no (orch injects) |
|
|
77
|
-
| worker shell | yes | no (orch runs) |
|
|
78
|
-
| structured_output | yes (~perfect)| yes (JSON schema) |
|
|
79
|
-
| worktree_isolation | yes | no (temp-dir) |
|
|
80
|
-
| native_cost_tracking | yes | yes (usage metadata) |
|
|
81
|
-
| tool_use_accuracy | ~0.99 | ~0.92 |
|
|
82
|
-
| verification_tier | 1 | 2 |
|
|
83
|
-
|
|
84
|
-
## Phase 2 Codex Implementation Details
|
|
92
|
+
- The wave loop calls **only** `AgentDriver` methods — never `agent()`, `parallel()`,
|
|
93
|
+
Read/Write/Bash tools, or `budget.spent()` directly. That is the seam.
|
|
94
|
+
- The orchestrator calls **only** `OrchestratorDriver.decide()`. Context packs are allowlist-only
|
|
95
|
+
(STATE.md, BUILDLOG.md, tracker.json, MEMORY.md, explicit brief); arbitrary reads raise
|
|
96
|
+
`ContextPackViolation` — **cardinal rule 4 in code**.
|
|
97
|
+
- `probe_capabilities()` must be **honest**. Defaults conservative (no native abilities, accuracy 0.0, tier 4) — optimism is opt-in, never default.
|
|
98
|
+
- **Weaker workers → higher verification tier.** Lower `tool_use_accuracy` raises `recommended_verification_tier`: cheaper backends RAISE the orchestrator's burden.
|
|
99
|
+
- Unknown roles in `resolve_model()` fall back to the worker model — a mis-typed role can never silently escalate cost.
|
|
100
|
+
- **Fail-safe verdicts**: `OrchestratorDriver.decide()` returns DECISION_FAILED after retries exhausted; never fabricates a passing verdict (never-green principle).
|
|
101
|
+
- **AdjudicationGate safety invariant** (increment 3): the final verdict is EITHER a confident
|
|
102
|
+
challenger verdict OR the incumbent's; undetermined/DECISION_FAILED/low-confidence is NEVER final.
|
|
103
|
+
- stdlib-only, ASCII-only, Windows + Linux safe. Concrete adapters own any provider SDK, not this layer.
|
|
104
|
+
|
|
105
|
+
## Phase 2 (Codex) + Phase 3 (Bridge) Implementation Details
|
|
106
|
+
|
|
107
|
+
Capability matrix (as encoded): claude-code = parallel, worker fs/shell, worktree isolation,
|
|
108
|
+
native cost, accuracy ~0.99, tier 1; codex = none natively (orchestrator injects/runs; temp-dir;
|
|
109
|
+
usage-metadata cost), accuracy ~0.92, tier 2. NOTE: ClaudeCodeDriver.get_tokens_spent() is None BY CONTRACT (cost_ceiling ledger fallback).
|
|
85
110
|
|
|
86
111
|
Codex driver (Tier 2): injects file contents into prompt, calls OpenAI Chat Completions via injectable transport, validates JSON with bounded retry, enforces ownership. CRITICAL: Green = exit 0 only. Verification policy: tier 2 -> {validate_all_json:True, spot_check_frac:0.50, repair_cap:2, require_adversarial_review:True}. **P1 Security**: Default model map uses gpt-4o-mini (worker, supports json_schema); init-time guard rejects models lacking json_schema support unless `allow_unverified_models=True` (P1 gate: prevent gpt-3.5-turbo silent failures).
|
|
87
112
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
verification_policy(caps) — RESOLVED ONCE, carried as literal manifest fields so the
|
|
96
|
-
template cannot recompute/drift; tier-1/Claude path stays byte-identical (repairCap=1,
|
|
97
|
-
requireAdversarialReview=false, spotCheckFrac=0.10, validateAllJson=false).
|
|
98
|
-
|
|
99
|
-
**dispatch_item(driver, item) -> dict**:
|
|
100
|
-
- Routes by worker_filesystem_access: True→{route:'harness'}; False→orchestrator-managed
|
|
101
|
-
(dispatch_worker + run_command test). Returns {route, ok, testExit, filesWritten, verified, ...}.
|
|
102
|
-
- HONESTY: Green (ok=True) ONLY on test exit 0; never from model's done:true. No testCmd
|
|
103
|
-
→ unverified (ok=False, verified=False, reason='no_test_command'): "no test" ≠ "verified."
|
|
104
|
-
- Fail-safe: exception → ok=False, verified=False. Ownership at driver level.
|
|
105
|
-
|
|
106
|
-
**Tests**: prove a non-Claude backend (Codex + FakeTransport) takes a RED unittest
|
|
107
|
-
stub, applies a fix, runs the test, and returns ok=True ONLY because the test passed
|
|
108
|
-
(exit 0). All offline, no API key, no network.
|
|
113
|
+
**build_manifest_item(driver, item)**: enriches a backlog item with model, verificationTier,
|
|
114
|
+
and the four verification_policy knobs — resolved ONCE as literal manifest fields (no
|
|
115
|
+
recompute drift); Claude tier-1 path stays byte-identical. **dispatch_item(driver, item)**:
|
|
116
|
+
routes by worker_filesystem_access. HONESTY: ok=True ONLY on test exit 0, never from the
|
|
117
|
+
model's done:true; no testCmd -> ok/verified=False (reason='no_test_command'); exception ->
|
|
118
|
+
fail-safe False. Ownership enforced at driver level. Offline tests prove Codex+FakeTransport
|
|
119
|
+
takes a RED stub to green via real test exit 0.
|
|
109
120
|
|
|
110
121
|
## Wave Scheduler (WS3a Pilot) + GATE-1 Handoff Kit
|
|
111
122
|
|
|
112
123
|
**wave_scheduler.py** — single-cycle backlog orchestration: intake up to N file-disjoint todo items from tracker.json (empty/missing ownsFiles REJECTED; paths normalized posix+casefold-on-Windows before overlap checks; required fields pre-validated) -> manifest via build_manifest_item (model + verificationTier from driver.probe) -> HALT + cost-ceiling gates (fail-CLOSED: module import failure or check exception = abort with honest Report, phase=gate_unavailable) -> run_wave (recovery journal + git ship) -> STOP before merge; Report JSON with per-item observability (GATE-1). After ship, selected items atomically marked in_progress in tracker (temp+os.replace; dry-run never mutates) so a second run cannot double-dispatch.
|
|
113
124
|
|
|
114
|
-
**CLI** (
|
|
115
|
-
|
|
116
|
-
**Tests** (35+): disjoint/normalization/rejection, gate fail-closed, dry-run, GATE-1 per-item/driver/ceiling/codex tests; module-tmpdir hygiene; all TestCase.
|
|
125
|
+
**CLI** (HS-1): `python driver/wave_scheduler.py --tracker <path> --max-items N --dry-run|--execute [--driver claude|codex] [--config <path>]`. Default: worker seat from aesop.config.json seats.worker ONLY (no seats block → claude; a bare legacy flat block stays inert — migrate to seats.worker) — the seats path also reaches openai-compatible; `--driver` OVERRIDES the config. Hosted seat + --execute requires the seat's api_key_env (is_local: none); dry-run never needs a key. HS-2: seats.orchestrator resolved by `resolve_orchestrator_backend()` (absent/harness/claude → None = live harness stays orchestrator; openai-compatible → live backend, same --execute key gate) and passed into run_wave; Report JSON shape is IDENTICAL either way (swap transparency — seat activity lives in run_wave's result: orchestrator_review + per-item final_catch, plus a stderr notice).
|
|
117
126
|
|
|
118
|
-
**Invariants**: stdlib-only, ASCII, Windows+Linux safe (list-form subprocess); manifest items carry resolved policy knobs from verification_policy (no recompute drift); merge stays manual in the pilot.
|
|
127
|
+
**Tests** (35+): disjoint/normalization/rejection, gate fail-closed, dry-run, GATE-1 per-item/driver/ceiling/codex tests; module-tmpdir hygiene; all TestCase. **Invariants**: stdlib-only, ASCII, Windows+Linux safe (list-form subprocess); manifest items carry resolved policy knobs from verification_policy (no recompute drift); merge stays manual in the pilot.
|
|
119
128
|
|
|
120
129
|
### REPORT-CONTRACT (GATE-1 Orchestrator Handoff)
|
|
121
130
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"sha": "<git_sha>", // optional; set when ship succeeds
|
|
137
|
-
"halt_reason": "...", // optional; set if halted
|
|
138
|
-
"ceiling_reason": "...", // optional; ceiling exceeded before dispatch (abort-before-dispatch only)
|
|
139
|
-
"error": "..." // optional; unexpected error
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
Ceiling semantics: checked BEFORE run_wave dispatch (phase=ceiling, ceiling_reason populated). Mid-wave ceiling trips are run_wave's responsibility. items_shipped[] carries per-item observability: slug, backend (driver name), tier (verificationTier), verified (from test exit 0), testExit (test command exit code or null if not run).
|
|
131
|
+
Scheduler emits a Report JSON the orchestrator uses for merge eligibility. Fields: phase
|
|
132
|
+
(dispatch|intake|halt|ceiling|gate_unavailable|manifest), wave_id, items_selected[],
|
|
133
|
+
items_shipped[] ({slug, backend, tier 1-4|null, verified — test-exit-0-only, false = NOT
|
|
134
|
+
PROVEN, testExit}), merged (pilot: always false, manual merge), success, timestamp,
|
|
135
|
+
branch/sha (set on ship), halt_reason/ceiling_reason/error (optional). Live seat ONLY
|
|
136
|
+
(HS-2 block gate; default shape unchanged): blocked[] ({slug, reason, quarantine:
|
|
137
|
+
clean|errors|skipped|unknown, + quarantine_errors / quarantine_skipped_reason —
|
|
138
|
+
a failed quarantine means refused code may still be in the tree}; item also gets
|
|
139
|
+
TERMINAL tracker status "blocked" — never re-selected) + orchestrator_gate {seat, model,
|
|
140
|
+
decisions, verdict_counts, blocked, decision_failed, seat_tokens_spent, status
|
|
141
|
+
active|degraded|no_decisions}; any block -> success false. Ceiling is checked
|
|
142
|
+
BEFORE run_wave dispatch (phase=ceiling); mid-wave trips are run_wave's responsibility.
|
|
143
|
+
Tracker sync: LOUD on unmapped slugs (tracker_unmapped_slugs -> success false). Full JSON
|
|
144
|
+
shape lives in wave_scheduler.py's module docstring.
|
|
143
145
|
|
|
144
146
|
## Status
|
|
145
147
|
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
Proves non-Claude backends drive items end-to-end with honest green (test exit 0 only).
|
|
150
|
-
- **Wave Scheduler (WS3a) + GATE-1**: shipped. Single-cycle orchestration: intake → manifest → dispatch → report (manual merge). Per-item observability, driver injection (--driver claude|codex), ceiling semantics documented.
|
|
148
|
+
Phases 1-3 + Wave Scheduler (WS3a/GATE-1) + HS-1 + HS-2 seat swap shipped (proof:
|
|
149
|
+
bench/results/hs2-swap-proof-2026-07-25.md; merge manual in pilot) + RS3-W round-2
|
|
150
|
+
robustness + RS5 claim lifecycle (see wave_loop.py bullet).
|
package/driver/README.md
CHANGED
|
@@ -225,7 +225,45 @@ switch backends without changing code. The configuration is **offline-safe**:
|
|
|
225
225
|
building a driver requires no API key; keys are read from environment variables
|
|
226
226
|
at call time during live dispatch.
|
|
227
227
|
|
|
228
|
-
**
|
|
228
|
+
**Unified two-seat config (0.4.0, HS-1)** — one namespaced block selects BOTH
|
|
229
|
+
seats: `seats.worker` (the coding agents; same fields as the legacy block
|
|
230
|
+
below, wins over it when both are present) and `seats.orchestrator` (the
|
|
231
|
+
decision seat; `"harness"` = the live Claude Code session, the default;
|
|
232
|
+
`"openai-compatible"` routes `OrchestratorDriver.decide()` to an API model
|
|
233
|
+
with `model`/`base_url`/`api_key_env`/`is_local`):
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"seats": {
|
|
238
|
+
"worker": { "backend": "claude" },
|
|
239
|
+
"orchestrator": { "backend": "openai-compatible", "model": "gpt-4o-mini" }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`build_driver(load_backend_config())` builds the worker seat;
|
|
245
|
+
`build_orchestrator_backend(load_backend_config())` builds the orchestrator
|
|
246
|
+
seat (returning the null `HarnessOrchestratorBackend` -- whose `decide_call`
|
|
247
|
+
raises -- when the seat is absent or `harness`/`claude`). With **no** `seats`
|
|
248
|
+
block, behavior is byte-identical to today: Claude Code worker + harness
|
|
249
|
+
orchestrator, no OpenAI backend constructed, no key required -- and a bare
|
|
250
|
+
legacy flat block (below) stays INERT in `wave_scheduler`'s default path
|
|
251
|
+
(it was dead config before 0.4.0; migrate it to `seats.worker` to opt in).
|
|
252
|
+
Consumers: `wave_scheduler.py` (worker seat from `seats.worker` only;
|
|
253
|
+
`--driver` overrides) and the shadow adjudication tools (orchestrator seat;
|
|
254
|
+
`--model` overrides).
|
|
255
|
+
|
|
256
|
+
Guardrails: `base_url` is SSRF-checked (scheme, IP literals, AND
|
|
257
|
+
DNS-resolved addresses; TTL-0 rebinding residual documented in
|
|
258
|
+
`validate_base_url`), `is_local: true` requires a loopback `base_url`
|
|
259
|
+
(localhost/127.0.0.1/::1 -- it disables the key requirement), and
|
|
260
|
+
`api_key_env` must be an LLM-key-shaped name (`*_KEY`/`*_API_KEY`, no
|
|
261
|
+
SECRET/TOKEN/... fragments) so a config cannot exfiltrate arbitrary env
|
|
262
|
+
secrets as Bearer tokens.
|
|
263
|
+
|
|
264
|
+
**Configuration schema** (legacy/flat worker block; parses + validates, honored
|
|
265
|
+
by direct `build_driver()` callers, but inert in the scheduler default -- see
|
|
266
|
+
above):
|
|
229
267
|
```json
|
|
230
268
|
{
|
|
231
269
|
"backend": "claude" | "codex" | "openai-compatible",
|
|
@@ -274,7 +312,7 @@ print(describe_backend(config))
|
|
|
274
312
|
```json
|
|
275
313
|
{
|
|
276
314
|
"backend": "codex",
|
|
277
|
-
"model": "gpt-
|
|
315
|
+
"model": "gpt-4o-mini"
|
|
278
316
|
}
|
|
279
317
|
```
|
|
280
318
|
- Requires `OPENAI_API_KEY` environment variable set
|