@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 CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  **Version scheme**: Stable releases are `0.x.y`; `0.x.0-beta.N` / `-rc.N` are pre-releases; `0.1.0-wave.N` were internal wave-milestone previews.
9
9
 
10
+ ## [0.4.0] - 2026-07-25
11
+
12
+ ### Added
13
+ - **Two-seat unified config** (HS-1): New `seats` configuration block allows swapping both worker AND orchestrator models from a single config block — eliminates split configuration and enables model experimentation without code changes. No-op default: existing installations remain unchanged (install-time safety).
14
+ - **Live orchestrator-seat swap** (HS-2): Added `final_catch` gate in the wave loop with block/quarantine + crash-only degradation — orchestrator model can be swapped mid-flight without restarting the entire fleet. Failure path quarantines the wave and logs the incident.
15
+ - **Microkernel architecture guide** (HS-3): New `docs/MICROKERNEL.md` explains the proof-of-concept model-swap seam, multi-model verification bounds, and a 60-second "swap a seat's model" quickstart for adopters.
16
+ - **Scaffold completeness** (`npx aesop init`): driver/ directory now scaffolds on first run (was omitted, breaking adopter first-run experience). Added --force idempotent re-scaffold and child process timeout protection.
17
+
18
+ ### Security / Hardening
19
+ - **IPv6 SSRF + DNS-resolution validation**: Requests to orchestrator's `base_url` now validate against IPv6 loopback and route-local semantics; DNS resolution blocked for external hostnames; `is_local` pinned to loopback only.
20
+ - **API key environment variable allowlist** (primary enforcement): `api_key_env` rejects obvious secret patterns (AWS_SECRET, AZURE_KEY, etc.) and logs a loud NOTICE for non-standard names; fail-closed on read errors.
21
+ - **Worker model field validation**: `worker.model` config field no longer silently ignored if orchestrator uses a non-Claude driver; now validated at load time.
22
+ - **Wave execution timeouts**: Added `run_command` timeouts to prevent indefinite wave hangs on subprocess failures; child processes now respect explicit timeout boundaries (no more wave-level hard stops).
23
+ - **CI merge-wait fail-closed**: `ci_merge_wait` gate now exits with explicit failure codes (not silent pass) on timeout or missing CI status, preventing accidental merges of untested code.
24
+ - **Windows CI shard concurrency cap**: Reduced `max-parallel` for Windows runner to cut container contention flakes; Windows shard now stable on standard GitHub Actions.
25
+ - **Path redaction completeness** (bench tooling): Bench results and logs redact full paths before reporting; `is_local` validation prevents accidental leakage of internal network topology.
26
+ - **JSON-boundary fail-open holes patched**: Orchestrator/driver seam now validates JSON boundaries at worker<->orchestrator message crossings; malformed messages fail-closed (not skipped).
27
+
28
+ ### Fixed
29
+ - **`npx aesop init` driver scaffolding**: Adopters running init on a fresh repo now get a complete driver/ directory tree; was silently omitting driver/, causing "module not found" on first wave.
30
+ - **Scaffold idempotency + child timeouts**: Re-running init --force no longer corrupts existing templates; child scaffold processes timeout cleanly instead of hanging.
31
+ - **Gate incumbent-validation**: Pre-gate validation of model incumbent (before swap attempt) catches incompatibilities early and logs them for forensics.
32
+
10
33
  ## [0.3.2] - 2026-07-23
11
34
 
12
35
  ### Fixed
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <em>Autonomous Developer for Any Repository</em>
6
+ <em>Crash-only multi-agent orchestration for any repository</em>
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -14,7 +14,13 @@
14
14
 
15
15
  ## What It Does
16
16
 
17
- **Aesop** is a source-available orchestration harness that runs multi-agent workflows on any repository. It ranks backlog, dispatches parallel Haiku workers (scoped to disjoint file ownership), verifies merges with adversarial safety, and feeds learnings into the next wave. Durable state (SQLite + git-rendered checkpoints) survives machine wipes and enables team coordination. This repo's own 223 PRs across 880 commits were delivered by Aesop's own `/buildsystem` wave loop—a supervised loop under a human operator who sets goals and owns outward gates (npm publish, releases, history rewrites). The stack is portable: swappable backends (Claude Code reference, OpenAI-compatible Ollama/OpenRouter, Codex bridge), with auto-tuned verification safety calibrated per backend.
17
+ **Aesop** is a **crash-recoverable orchestration harness** for multi-agent workflows on any repository. One-line theme: *stateless agent execution over git-backed durable memory*.
18
+
19
+ Core idea: **agent behavior is source code.** Every decision lives in durable, human-diffable files—git history, plain-text STATE.md, append-only BUILDLOG.md, Python guardrails. When a machine fails, you re-read from disk. No vector DBs, no distributed consensus, no magic. This repo's own 251 merged PRs across 1088 commits were delivered by Aesop's own `/buildsystem` wave loop—a supervised loop under a human operator who sets goals and owns outward gates (npm publish, releases, history rewrites).
20
+
21
+ **Why it matters:** crash recovery is not a special path; it is how the system *always* starts. Stateless workers, persistent filesystem brain, Haiku-first dispatch (4.3× cheaper than hierarchical design, proven by real A/B), fail-closed guardrails (pre-push secret gate, kill-switch, cost ceiling), and observable heartbeats. The result: 251 PRs in 11 days; Haiku at 39/39 on a 39-task benchmark vs Opus 38/39, at ~1/3 the cost.
22
+
23
+ **Why it's built this way:** [The Aesop Hypothesis](./docs/THE-AESOP-HYPOTHESIS.md) — the design philosophy, the trade-offs, the cancelled architectures with published data.
18
24
 
19
25
  ## Feature Demo
20
26
 
@@ -86,16 +92,17 @@ Aesop is built entirely by its own `/buildsystem` wave cycle—running parallel
86
92
 
87
93
  | Metric | Value |
88
94
  | --- | --- |
89
- | Merged PRs | 244 <!-- metrics-verified: self_stats.py (git log) --> |
90
- | Total Commits | 1065 <!-- metrics-verified: self_stats.py (git log) --> |
95
+ | Merged PRs | 251 <!-- metrics-verified: self_stats.py (git log) --> |
96
+ | Total Commits | 1088 <!-- metrics-verified: self_stats.py (git log) --> |
91
97
  | Project Age | 11 days <!-- metrics-verified: self_stats.py (git log) --> |
92
98
  | Waves | 30 <!-- metrics-verified: self_stats.py (git log) --> |
93
- | Insertions + Deletions | 185,174 <!-- metrics-verified: self_stats.py (git log) --> |
94
- | Files Tracked | 541 <!-- metrics-verified: self_stats.py (git log) --> |
99
+ | Insertions + Deletions | 186,587 <!-- metrics-verified: self_stats.py (git log) --> |
100
+ | Files Tracked | 546 <!-- metrics-verified: self_stats.py (git log) --> |
95
101
  | Distinct Co-authors | 11 <!-- metrics-verified: self_stats.py (git log) --> |
96
102
 
97
103
  <!-- STATS:END -->
98
104
 
105
+
99
106
  *Wave: one complete build cycle (intake → dispatch → verify → ship) run by the orchestration engine.*
100
107
 
101
108
 
@@ -113,6 +120,7 @@ The benchmark proves it: across 39 judgment tasks (code review, severity calibra
113
120
  ## Learn More
114
121
 
115
122
  - **[docs/INSTALL.md](./docs/INSTALL.md)** — Setup and first wave
123
+ - **[docs/MICROKERNEL.md](./docs/MICROKERNEL.md)** — The two swappable seats (worker + orchestrator), the invariant Report/state boundary, and a 60-second quickstart for swapping either seat's model
116
124
  - **[docs/PORTING.md](./docs/PORTING.md)** — Adopter's guide: port Aesop to your repo (prerequisites, scaffold, 10 failure modes)
117
125
  - **[docs/HOW-THE-LOOP-WORKS.md](./docs/HOW-THE-LOOP-WORKS.md)** — Concrete walkthrough of a wave cycle
118
126
  - **[docs/DISPATCH-MODEL.md](./docs/DISPATCH-MODEL.md)** — Cost analysis and scaling
@@ -46,6 +46,22 @@
46
46
  "cardinal_rules": {
47
47
  "subagent_model": "haiku"
48
48
  },
49
+ "seats": {
50
+ "_comment": "Unified two-seat model selection (0.4.0). ONE block swaps BOTH seats: 'worker' = the coding agents (AgentDriver), 'orchestrator' = the decision seat (OrchestratorBackend). The values below ARE the defaults -- Claude Code worker + live-harness orchestrator -- so this block as shipped changes nothing; deleting it changes nothing either. To swap a seat, replace its block with one of the _worker_examples/_orchestrator_examples. Keys are read from env vars named by api_key_env at CALL time, never stored in this file. 'is_local': true = LOOPBACK endpoint only (localhost/127.0.0.1/::1 -- rejected otherwise): no API key needed, honest verification tier 3. Separate surface: cardinal_rules.subagent_model (above) pins the model for LIVE-HARNESS Claude Code subagents; seats.worker swaps the AgentDriver worker seam itself.",
51
+ "worker": { "backend": "claude" },
52
+ "orchestrator": { "backend": "harness" },
53
+ "_worker_examples": {
54
+ "_note": "Asymmetry: worker openai-compatible seats REQUIRE base_url; the orchestrator seat may omit base_url (defaults to the hosted OpenAI endpoint).",
55
+ "codex_hosted_openai": { "backend": "codex", "model": "gpt-4o-mini" },
56
+ "openai_compatible_openrouter": { "backend": "openai-compatible", "base_url": "https://openrouter.ai/api/v1", "model": "openai/gpt-4-turbo", "api_key_env": "OPENROUTER_API_KEY" },
57
+ "openai_compatible_local_ollama": { "backend": "openai-compatible", "base_url": "http://localhost:11434/v1", "model": "mistral", "is_local": true }
58
+ },
59
+ "_orchestrator_examples": {
60
+ "harness_default": { "backend": "harness" },
61
+ "openai_hosted": { "backend": "openai-compatible", "model": "gpt-4o-mini", "api_key_env": "OPENAI_API_KEY" },
62
+ "local_ollama": { "backend": "openai-compatible", "base_url": "http://localhost:11434/v1", "model": "mistral", "is_local": true }
63
+ }
64
+ },
49
65
  "limits": {
50
66
  "_comment": "Cost ceiling / kill switch (tools/cost_ceiling.py, tools/halt.py). null disables a given ceiling (feature opt-in). When spend meets or exceeds a configured ceiling, cost_ceiling.py trips tools/halt.py's .HALT sentinel, which daemons/run-watchdog.sh checks at the top of every cycle and refuses to do work (no backup, no push, no scan) until cleared via 'python tools/halt.py --clear'.",
51
67
  "max_wave_tokens": null,
package/bin/cli.js CHANGED
@@ -259,7 +259,7 @@ const repoUrlsStr = getFlag('--repo-urls');
259
259
  if (fs.existsSync(targetDir)) {
260
260
  const contents = fs.readdirSync(targetDir);
261
261
  // Allow .git and aesop scaffolded files to already exist (for idempotency)
262
- const aesopDirs = ['daemons', 'dash', 'monitor', 'tools', 'ui', 'docs', 'state_store', 'skills', 'mcp', 'scan', 'hooks', '.git', 'state'];
262
+ const aesopDirs = ['daemons', 'dash', 'monitor', 'tools', 'ui', 'docs', 'state_store', 'skills', 'mcp', 'scan', 'hooks', 'driver', '.git', 'state'];
263
263
  const aesopFiles = [
264
264
  'aesop.config.example.json',
265
265
  'aesop.config.json',
@@ -317,6 +317,7 @@ const filesToCopy = [
317
317
  'mcp',
318
318
  'scan',
319
319
  'hooks',
320
+ 'driver',
320
321
  'aesop.config.example.json',
321
322
  'README.md',
322
323
  'LICENSE',
@@ -421,8 +422,10 @@ function generateConfigJson(targetDir, templateRoot, projectName, reposStr, repo
421
422
  port: 8770
422
423
  },
423
424
  cardinal_rules: {
425
+ // NOTE: orchestrator_model was RETIRED in 0.4.0 (HS-1): it was written
426
+ // here but read by nothing. The orchestrator seat's model now lives in
427
+ // seats.orchestrator.model (see aesop.config.example.json).
424
428
  subagent_model: 'haiku',
425
- orchestrator_model: 'opus',
426
429
  tdd_first: true,
427
430
  never_push_main: true,
428
431
  secret_scan_gates_push: true
package/docs/INSTALL.md CHANGED
@@ -200,19 +200,103 @@ export DEBUG=1
200
200
 
201
201
  By default, Aesop uses Claude Code (the orchestration harness) as its backend. You can configure it to use other models via the **AgentDriver abstraction**—enabling Ollama, OpenAI-compatible endpoints, OpenRouter, and more.
202
202
 
203
- ### Configure a backend in aesop.config.json
203
+ For the conceptual picture (the two swappable seats, what's proven vs. bounded about swapping them, and a 60-second copy-paste quickstart), see **[MICROKERNEL.md](MICROKERNEL.md)**. The rest of this section is the config-mechanics reference.
204
204
 
205
- Add or modify the `backend` section:
205
+ ### The unified two-seat config (0.4.0)
206
+
207
+ One namespaced `seats` block in `aesop.config.json` selects BOTH seats:
208
+
209
+ - **`seats.worker`** — the coding agents (AgentDriver). Same fields as the
210
+ legacy flat block below; takes precedence over it when both are present.
211
+ - **`seats.orchestrator`** — the decision seat (`OrchestratorDriver.decide()`).
212
+ `"harness"` (default) means the live Claude Code session itself makes
213
+ decisions; `"openai-compatible"` routes decisions to an API model.
214
+
215
+ **Swap a seat's model** — change just its block:
216
+
217
+ ```json
218
+ {
219
+ "seats": {
220
+ "worker": {
221
+ "backend": "openai-compatible",
222
+ "base_url": "http://localhost:11434/v1",
223
+ "model": "mistral",
224
+ "is_local": true
225
+ },
226
+ "orchestrator": {
227
+ "backend": "openai-compatible",
228
+ "model": "gpt-4o-mini",
229
+ "api_key_env": "OPENAI_API_KEY"
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ That is a local-Ollama worker fleet with a hosted `gpt-4o-mini` decision seat.
236
+ To swap the worker to OpenRouter, replace `seats.worker` with
237
+ `{"backend": "openai-compatible", "base_url": "https://openrouter.ai/api/v1",
238
+ "model": "openai/gpt-4-turbo", "api_key_env": "OPENROUTER_API_KEY"}` — nothing
239
+ else changes. API keys are read from the env var named by `api_key_env` at
240
+ call time and are never stored in the config; `"is_local": true` endpoints
241
+ need no key at all — and for exactly that reason `is_local` is only accepted
242
+ with a loopback `base_url` (`localhost`, `127.0.0.1`, `::1`).
243
+
244
+ `api_key_env` is checked with a **best-effort heuristic**, allowlist-primary:
245
+ known LLM-provider key names (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
246
+ `OPENROUTER_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`,
247
+ `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `OLLAMA_API_KEY`,
248
+ `AZURE_OPENAI_API_KEY`, `GOOGLE_API_KEY`) are accepted silently; names that
249
+ don't look like key env vars or contain obvious non-LLM secret fragments
250
+ (`SECRET`/`TOKEN`/`PASSWORD`/`ACCESS`/...) are rejected; any other
251
+ key-shaped name (custom LLM gateways) is **allowed but prints a loud
252
+ NOTICE** — the env var's value will be sent as a Bearer token to your
253
+ `base_url`, so that NOTICE is the real signal: review it whenever a
254
+ non-provider name shows up at load time.
255
+
256
+ Asymmetry worth knowing: a **worker** `openai-compatible` seat REQUIRES
257
+ `base_url`; the **orchestrator** seat may omit it (defaults to the hosted
258
+ OpenAI endpoint).
259
+
260
+ **No `seats` block? Nothing changes.** Existing installs keep today's exact
261
+ behavior: Claude Code workers + harness orchestrator, no OpenAI backend
262
+ constructed, no key required. A pre-0.4.0 **legacy flat** backend block
263
+ (`{"backend": "codex", ...}` at the top level) also changes nothing by
264
+ itself: it still parses and validates, but it stays **inert** in the wave
265
+ scheduler's default path until you migrate it to `seats.worker` — on older
266
+ installs that block was documented but consumed by nothing, so activating it
267
+ silently would change behavior under you. (`cardinal_rules.orchestrator_model`
268
+ from older scaffolds was write-only and is retired; the orchestrator seat's
269
+ model now lives in `seats.orchestrator.model`.)
270
+
271
+ Consumers: `driver/wave_scheduler.py` builds its worker driver from this
272
+ config (CLI `--driver claude|codex` remains an override), and
273
+ `tools/shadow_adjudication.py` / `tools/seated_shadow_adjudication.py` build
274
+ their live orchestrator backend from `seats.orchestrator` (CLI `--model`
275
+ remains an override).
276
+
277
+ ### The legacy flat block (parse-compatible, but migrate it)
278
+
279
+ Pre-0.4.0 docs described a flat top-level block:
206
280
 
207
281
  ```json
208
282
  {
209
283
  "backend": "openai-compatible",
210
284
  "model": "ollama-mistral",
211
285
  "base_url": "http://localhost:11434/v1",
212
- "api_key_env": "OLLAMA_API_KEY"
286
+ "api_key_env": "OLLAMA_API_KEY",
287
+ "is_local": true
213
288
  }
214
289
  ```
215
290
 
291
+ It still parses and validates (and direct `build_driver()` callers honor
292
+ it), but the wave scheduler's default path treats it as **inert** and keeps
293
+ the Claude Code worker: to actually activate a configured worker there, put
294
+ the same fields under `seats.worker` (`{"seats": {"worker": { ...this
295
+ block... }}}`).
296
+
297
+ Set `"is_local": true` for local/small models (Ollama etc.) — it raises the
298
+ verification tier honestly (tier 3 instead of hosted tier 2).
299
+
216
300
  Supported backends:
217
301
  - `"claude"` (default) — Claude Code CLI harness
218
302
  - `"openai-compatible"` — OpenAI Chat Completions API (Ollama, OpenRouter, etc.)
@@ -229,17 +313,22 @@ ollama serve
229
313
  # 2. In another terminal, pull a model
230
314
  ollama pull mistral
231
315
 
232
- # 3. Configure Aesop to use it
316
+ # 3. Configure Aesop to use it (seats.worker is the opt-in surface)
233
317
  cat > aesop.config.json <<EOF
234
318
  {
235
- "backend": "openai-compatible",
236
- "model": "mistral",
237
- "base_url": "http://localhost:11434/v1",
238
- "api_key_env": "OLLAMA_API_KEY"
319
+ "seats": {
320
+ "worker": {
321
+ "backend": "openai-compatible",
322
+ "model": "mistral",
323
+ "base_url": "http://localhost:11434/v1",
324
+ "api_key_env": "OLLAMA_API_KEY",
325
+ "is_local": true
326
+ }
327
+ }
239
328
  }
240
329
  EOF
241
330
 
242
- # 4. Start Aesop (it will use Mistral for subagent dispatch)
331
+ # 4. Start Aesop (components that dispatch through the AgentDriver seam will use Mistral)
243
332
  npx @matt82198/aesop my-fleet --name "my-api"
244
333
  ```
245
334
 
@@ -250,16 +339,16 @@ The AgentDriver framework applies **honest verification tiers** — weaker backe
250
339
  | Backend | Accuracy | Verification Tier | What it means |
251
340
  |---------|----------|-------------------|---------------|
252
341
  | Claude Code | ~0.99 | 1 (minimal) | Orchestrator trusts output; spot-check tests |
253
- | OpenAI (GPT-4) | ~0.95 | 2 | Validate all JSON, run full test suite |
254
- | Ollama (Mistral) | ~0.70 | 4 (maximum) | Validate all JSON, adversarial review, expensive repair cap |
342
+ | Hosted OpenAI-compatible (codex / OpenRouter) | ~0.92 | 2 | Validate all JSON, ~50% spot-check, adversarial review |
343
+ | Local small model (`"is_local": true`, e.g. Ollama) | ~0.80 | 3 | Validate all JSON, heavy spot-check, adversarial review |
255
344
 
256
- Lower tiers cost less but require more orchestrator work. See [driver/README.md](../driver/README.md) for full verification-policy details.
345
+ Higher tiers mean MORE orchestrator verification work: weaker backends raise, never lower, the orchestrator's burden. See [driver/README.md](../driver/README.md) for full verification-policy details.
257
346
 
258
347
  ### Troubleshooting
259
348
 
260
349
  **Backend won't connect**: Check `OLLAMA_API_KEY` (or your backend's API key env var) is set and the `base_url` is reachable.
261
350
 
262
- **Verification tier too strict**: If your backend is over-verified (tier 4 when it should be tier 2), update `probe_capabilities()` in the driver to report higher accuracy scores honestly.
351
+ **Verification tier too strict**: The tier comes from the driver's `probe_capabilities()` honesty contract — never inflate accuracy to lower it. For hosted-quality models, leave `"is_local"` unset (tier 2); reserve `"is_local": true` (tier 3) for genuinely small local models.
263
352
 
264
353
  For more details, see [driver/README.md](../driver/README.md).
265
354
 
@@ -333,6 +422,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -File daemons/install-tasks.ps1 -D
333
422
  2. **Read [CONFIGURE.md](CONFIGURE.md)** — Customize repos, ports, and brain root
334
423
  3. **Run [FIRST-WAVE.md](FIRST-WAVE.md)** — Test a full `/power` → `/buildsystem` cycle
335
424
  4. **Understand [CONCEPTS.md](CONCEPTS.md)** — Learn the dispatch model and state model
336
- 4. **Explore the dashboard** — `python3 ui/serve.py` then open http://localhost:8770
425
+ 5. **Read [MICROKERNEL.md](MICROKERNEL.md)** — Swap the worker or orchestrator seat to a non-Claude model
426
+ 6. **Explore the dashboard** — `python3 ui/serve.py` then open http://localhost:8770
337
427
 
338
428
  For troubleshooting, see the [Aesop README](../README.md#troubleshooting) or [GOVERNANCE.md](GOVERNANCE.md) for operational policies.