@matt82198/aesop 0.1.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.
Files changed (130) hide show
  1. package/CHANGELOG.md +117 -2
  2. package/README.md +59 -218
  3. package/bin/cli.js +168 -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/HOOK-INSTALL.md +15 -56
  10. package/docs/INSTALL.md +74 -4
  11. package/docs/PORTING.md +166 -0
  12. package/docs/README.md +33 -3
  13. package/docs/TEAM-STATE.md +372 -0
  14. package/docs/reproduce.md +33 -3
  15. package/driver/CLAUDE.md +150 -0
  16. package/driver/README.md +383 -0
  17. package/driver/aesop.config.example.json +80 -0
  18. package/driver/agent_driver.py +355 -0
  19. package/driver/backend_config.py +253 -0
  20. package/driver/claude_code_driver.py +198 -0
  21. package/driver/codex_driver.py +673 -0
  22. package/driver/openai_compatible_driver.py +249 -0
  23. package/driver/openai_transport.py +179 -0
  24. package/driver/verification_policy.py +75 -0
  25. package/driver/wave_bridge.py +254 -0
  26. package/driver/wave_loop.py +1408 -0
  27. package/driver/wave_scheduler.py +890 -0
  28. package/hooks/pre-push-policy.sh +131 -33
  29. package/mcp/server.mjs +320 -4
  30. package/monitor/collect-signals.mjs +69 -0
  31. package/package.json +22 -14
  32. package/skills/CLAUDE.md +132 -2
  33. package/skills/buildsystem/SKILL.md +330 -0
  34. package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
  35. package/skills/fleet/SKILL.md +113 -0
  36. package/skills/power/SKILL.md +246 -131
  37. package/state_store/__init__.py +4 -2
  38. package/state_store/api.py +19 -4
  39. package/state_store/coordination.py +209 -0
  40. package/state_store/identity.py +51 -0
  41. package/state_store/projections.py +63 -0
  42. package/state_store/read_api.py +156 -0
  43. package/state_store/store.py +185 -73
  44. package/state_store/write_api.py +462 -0
  45. package/templates/wave-presets/data.json +65 -0
  46. package/templates/wave-presets/library.json +65 -0
  47. package/templates/wave-presets/saas.json +64 -0
  48. package/tools/audit_report.py +388 -0
  49. package/tools/bench_runner.py +100 -3
  50. package/tools/ci_merge_wait.py +256 -35
  51. package/tools/ci_workflow_lint.py +430 -0
  52. package/tools/claudemd_drift.py +394 -0
  53. package/tools/claudemd_lint.py +359 -0
  54. package/tools/common.py +39 -3
  55. package/tools/cost_ceiling.py +166 -43
  56. package/tools/cost_econ.py +480 -0
  57. package/tools/cost_projection.py +559 -0
  58. package/tools/crossos_drift.py +394 -0
  59. package/tools/defect_escape.py +252 -0
  60. package/tools/doctor.js +1 -1
  61. package/tools/eod_sweep.py +188 -26
  62. package/tools/fleet.js +260 -0
  63. package/tools/fleet_ledger.py +209 -7
  64. package/tools/git_identity_check.py +315 -0
  65. package/tools/health-score.js +40 -0
  66. package/tools/health_score.py +361 -0
  67. package/tools/metrics_gate.py +13 -4
  68. package/tools/mutation_test.py +523 -0
  69. package/tools/portability_check.py +206 -0
  70. package/tools/proposals.mjs +47 -2
  71. package/tools/reconcile.py +7 -4
  72. package/tools/reproduce.js +405 -0
  73. package/tools/secret_scan.py +207 -65
  74. package/tools/self_stats.py +20 -0
  75. package/tools/stall_check.py +247 -16
  76. package/tools/stateapi_lint.py +325 -0
  77. package/tools/test_battery.py +173 -0
  78. package/tools/transcript_digest.py +380 -0
  79. package/tools/verify_activity_filter.py +437 -0
  80. package/tools/verify_agent_inspector.py +2 -0
  81. package/tools/verify_cost_panel.py +345 -0
  82. package/tools/verify_dash.py +2 -0
  83. package/tools/verify_dispatch_panel.py +301 -0
  84. package/tools/verify_failure_drilldown.py +188 -0
  85. package/tools/verify_prboard.py +2 -0
  86. package/tools/verify_scorecards.py +281 -0
  87. package/tools/verify_submit_encoding.py +2 -0
  88. package/tools/verify_ui_trio.py +409 -0
  89. package/tools/verify_wave_telemetry.py +268 -0
  90. package/tools/wave_backlog_analyzer.py +490 -0
  91. package/tools/wave_ledger_hook.py +150 -0
  92. package/tools/wave_preflight.py +779 -0
  93. package/tools/wave_resume.py +215 -0
  94. package/tools/wave_templates.py +340 -0
  95. package/ui/agents.py +68 -14
  96. package/ui/collectors.py +81 -55
  97. package/ui/config.py +7 -2
  98. package/ui/cost.py +231 -12
  99. package/ui/handler.py +383 -55
  100. package/ui/quality_scorecard.py +232 -0
  101. package/ui/sse.py +3 -3
  102. package/ui/wave_audit_tail.py +213 -0
  103. package/ui/wave_dispatch.py +280 -0
  104. package/ui/wave_failure.py +288 -0
  105. package/ui/wave_gantt.py +152 -0
  106. package/ui/wave_reasoning_tail.py +176 -0
  107. package/ui/wave_telemetry.py +383 -0
  108. package/ui/web/dist/assets/index-CNQxaiOW.css +1 -0
  109. package/ui/web/dist/assets/index-CP68RIh3.js +9 -0
  110. package/ui/web/dist/index.html +2 -2
  111. package/bin/CLAUDE.md +0 -76
  112. package/daemons/CLAUDE.md +0 -36
  113. package/dash/CLAUDE.md +0 -32
  114. package/docs/archive/README.md +0 -3
  115. package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
  116. package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
  117. package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
  118. package/docs/archive/spikes/tiered-cognition/README.md +0 -27
  119. package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
  120. package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
  121. package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
  122. package/hooks/CLAUDE.md +0 -89
  123. package/mcp/CLAUDE.md +0 -213
  124. package/monitor/CLAUDE.md +0 -40
  125. package/scan/CLAUDE.md +0 -30
  126. package/state_store/CLAUDE.md +0 -39
  127. package/tools/CLAUDE.md +0 -79
  128. package/ui/CLAUDE.md +0 -127
  129. package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
  130. package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Aesop Fleet Dashboard</title>
7
7
  <script>window.__AESOP_CSRF_TOKEN__ = __AESOP_CSRF_SENTINEL__;</script>
8
- <script type="module" crossorigin src="/assets/index-0qQYnvMC.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-BdIlFieV.css">
8
+ <script type="module" crossorigin src="/assets/index-CP68RIh3.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-CNQxaiOW.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/bin/CLAUDE.md DELETED
@@ -1,76 +0,0 @@
1
- # bin/ — CLI scaffolder
2
-
3
- **Purpose**: Node.js CLI entry point that clones the aesop orchestration template into a target directory with idempotent validation, plus an interactive onboarding wizard for new adopters.
4
-
5
- ## Invocation
6
-
7
- - **npm registry**: `npx @matt82198/aesop [target-dir]` (default: `aesop-fleet`)
8
- - **Local dev**: `node bin/cli.js [target-dir]`
9
- - **Help**: `npx @matt82198/aesop --help` or `-h`
10
- - **Interactive wizard**: `npx @matt82198/aesop wizard` (on a TTY, prompts; with `--yes`, uses defaults)
11
-
12
- ## Runtime subcommands
13
-
14
- cli.js dispatches runtime management subcommands to `tools/{subcommand}.js`:
15
-
16
- - **`aesop doctor`** (or `node bin/cli.js doctor`) — Preflight readiness check; validates Node.js, Python, git, config, directories, hooks, and dashboard port availability.
17
- - **`aesop watch`** (or `node bin/cli.js watch`) — Launch the watchdog daemon; spawns `daemons/run-watchdog.sh` for continuous fleet monitoring.
18
- - **`aesop dash`** (or `node bin/cli.js dash`) — Launch the web dashboard; spawns `python ui/serve.py` to serve realtime fleet status at localhost:8770 (default).
19
- - **`aesop status`** (or `node bin/cli.js status`) — One-shot fleet status snapshot; displays heartbeats, dashboard port, and git branch status.
20
-
21
- ## What gets copied
22
-
23
- Files in `filesToCopy` array (cli.js lines 239–256):
24
- - **Directories**: `daemons/`, `dash/`, `monitor/`, `tools/`, `ui/`, `docs/`, `state_store/`, `skills/`, `mcp/`, `scan/`, `hooks/`
25
- - **Files**: `aesop.config.example.json`, `README.md`, `LICENSE`, `CHANGELOG.md`, `CLAUDE-TEMPLATE.md`
26
- - **Brain templates** (in docs/): `MEMORY-TEMPLATE.md` (via docs/ directory copy)
27
-
28
- ## What does NOT get copied
29
-
30
- - `aesop.config.json` (users must `cp aesop.config.example.json` and edit)
31
- - `state/` (runtime durable state, git-ignored, created by daemons)
32
- - `.git/`, `node_modules/`, build artifacts
33
-
34
- ## Post-scaffold guidance
35
-
36
- Scaffolder prints steps for users:
37
- 1. `cd target-dir && cp aesop.config.example.json aesop.config.json`
38
- 2. Edit config with paths and repos
39
- 3. Initialize brain: `cp CLAUDE-TEMPLATE.md ~/.claude/CLAUDE.md` (edit)
40
- 4. Initialize memory: `cp docs/MEMORY-TEMPLATE.md ~/.claude/MEMORY.md` (edit)
41
- 5. Test daemon: `bash daemons/run-watchdog.sh --once`
42
- 6. Launch dashboard: `python ui/serve.py`
43
-
44
- ## Interactive wizard (`aesop wizard`)
45
-
46
- The wizard mode provides an interactive onboarding flow for new adopters, guiding them through fleet setup in ~60 seconds:
47
-
48
- 1. **Trigger**: `npx @matt82198/aesop wizard` on a TTY (interactive terminal)
49
- 2. **Questions** (all have sensible defaults, press Enter-Enter-Enter to skip):
50
- - Project name (default: "my-fleet")
51
- - Repos to watch (auto-discovers git repos under `~`, offers choices)
52
- - Dashboard port (default: 8770, validates 1–65535)
53
- - Brain root directory (default: `~/.claude`)
54
- 3. **Output**:
55
- - Scaffolds template files
56
- - Generates CLAUDE.md (substituted with project name)
57
- - Generates aesop.config.json (with discovered repos)
58
- - Prints "next 3 commands" epilogue
59
- - Offers to run `watchdog --once` smoke test immediately
60
- 4. **Non-interactive mode** (`--yes` flag or non-TTY stdin): Uses defaults, zero prompts (CI-safe)
61
-
62
- ### Wizard implementation details
63
-
64
- - **Repo discovery** (`discoverRepos`): Scans `~` for `.git` directories at first level (non-recursive)
65
- - **Port validation** (`validatePort`): Rejects invalid ports, accepts 1–65535
66
- - **Portable paths**: All config paths use `~` form (`~/.claude`, `~/scripts`, etc.) for cross-platform compatibility
67
- - **Defaults**: Every prompt has a default so users can skip it (press Enter)
68
- - **Non-destructive**: Never overwrites existing `aesop.config.json` without user confirmation
69
-
70
- ## Invariants & gotchas
71
-
72
- - **Idempotent on empty targets**: Fails if `targetDir` exists and is non-empty (non-destructive). Safe to retry.
73
- - **Adding shipped files**: Any new file/dir added to `filesToCopy` array must also be added to `package.json` `files` array (lines 9–27 in package.json) so npm publish includes it.
74
- - **No machine-specific paths**: Use relative paths only; `__dirname` and `path.join()` handle cross-platform resolution.
75
- - **Wizard prompts are async**: Main execution is wrapped in async IIFE to support readline prompts
76
- - **Help text accuracy**: If invocation steps or output paths change, update help text
package/daemons/CLAUDE.md DELETED
@@ -1,36 +0,0 @@
1
- # daemons/ — Watchdog daemon
2
-
3
- **Purpose**: Long-running backup and secret-scan daemon for fleet-wide repo safety.
4
-
5
- ## Files
6
-
7
- - **run-watchdog.sh** (1.7K): Interactive daemon supervisor; spawns backup-fleet.sh every 150s, maintains heartbeat guard (200s dedupe window), logs to FLEET-BACKUP.log. Calls tools/alert_bridge.py --scan after each cycle to post HIGH/CRITICAL alerts to Slack/Discord (opt-in). Traps INT/TERM cleanly.
8
- - **backup-fleet.sh** (5K): Core backup worker; discovers repos (~/.*, ~/*, ~/dev/*), stashes uncommitted work to backup/* branches, pushes unpushed commits, scans tracked files for secrets. Blocks push if secret-scan fails (marks BLOCKED state).
9
-
10
- ## Contracts
11
-
12
- **Env vars consumed**: `AESOP_ROOT` (defaults to `.`) — root of project; prepends to all state/ and tools/ paths.
13
-
14
- **State files written** (git-ignored):
15
- - `$AESOP_ROOT/state/.watchdog-heartbeat` — epoch seconds; dedupe guard (200s window).
16
- - `$AESOP_ROOT/state/.watchdog-repos.json` — per-cycle snapshot: [{repo, state: CLEAN|PUSHED|SNAPSHOTTED|BLOCKED, age}].
17
- - `$AESOP_ROOT/state/FLEET-BACKUP.log` — append-only; cycle start/end, repo statuses, secret-scan blocks.
18
- - `$AESOP_ROOT/state/SECURITY-ALERTS.log` — append-only; security alerts (read by alert_bridge.py to post to webhooks).
19
- - `$AESOP_ROOT/state/.alert-bridge-cursor` — line number of last sent alert (ensures idempotent alert dispatch).
20
-
21
- **Exit behaviors**: run-watchdog exits 0 always (trap handles clean shutdown). backup-fleet exits 0 even on secret-scan block (marks repo BLOCKED, continues).
22
-
23
- **Cadence**: 150s cycle; heartbeat dedupe within 200s.
24
-
25
- ## Invariants & Gotchas
26
-
27
- 1. **Single-instance guard (atomic lock)**: run-watchdog.sh uses atomic mkdir-based lockfile (`.watchdog-lock/`) to prevent concurrent daemons.
28
- - Atomic acquire: `mkdir $LOCK_DIR` is guaranteed atomic on POSIX systems (returns 0 only to first caller).
29
- - Stale-lock recovery: Crashed holder won't wedge daemon forever; lock older than 300s is reclaimed atomically.
30
- - Guards both daemon mode and `--once` mode (no bypass).
31
- 2. **Testing override**: Set `AESOP_WATCHDOG_CYCLE_CMD` env var to replace backup-fleet.sh invocation (allows tests to use mock cycle without running real backup).
32
- 3. **CRLF-safe, no line continuations**: Use POSIX-safe heredocs; never add `\` for line wrap.
33
- 4. **Secret-scan gate**: `scan_tracked_files()` calls `$AESOP_ROOT/tools/secret_scan.py` on tracked modifications and untracked files; non-0 exit blocks push, marks repo BLOCKED.
34
- 5. **Append-only logs**: FLEET-BACKUP.log only grows; rotate via tools/rotate_logs.py.
35
- 6. **Path dedup via realpath**: Avoids processing symlinked repos or dot-dir aliases twice.
36
- 7. **Alert Bridge integration** (NEW wave-14): After each backup-fleet.sh cycle, run-watchdog.sh calls `python $AESOP_ROOT/tools/alert_bridge.py --scan || true` to post HIGH/CRITICAL security alerts and heartbeat staleness to Slack/Discord. No-op if webhook_url null/absent in aesop.config.json (feature opt-in). Uses cursor file for idempotent dispatch (no re-sends). See tools/alert_bridge.py for details.
package/dash/CLAUDE.md DELETED
@@ -1,32 +0,0 @@
1
- # dash/ — Dashboard TUI
2
-
3
- **Purpose**: Real-time TUI fleet monitoring — watchdog daemon + agent activity display.
4
-
5
- ## Files
6
-
7
- - **watchdog-gui.sh** — Read-only terminal UI (4s refresh, double-buffered, no flicker). Displays daemon status, backed-up repos, heartbeats, recent events. Launch in own window; never inside tool shell. CRLF-safe (no line continuations).
8
- - **dash-extra.mjs** — Node.js agent activity detector. Scans AESOP_TRANSCRIPTS_ROOT for agent-*.jsonl files (last 12 min). TUI output by default; --json for web endpoints. Requires node on PATH (unavailable fallback if missing).
9
-
10
- ## State contracts
11
-
12
- **watchdog-gui.sh reads** (heartbeat staleness thresholds applied):
13
- - `state/.watchdog-heartbeat` (epoch, threshold: 300s for watchdog)
14
- - `state/FLEET-BACKUP.log` (append-only, tail 3 lines)
15
- - `state/SECURITY-ALERTS.log` (counts HIGH/MED, grep filtering RESOLVED-FP)
16
- - `state/.watchdog-repos.json` (jq-parsed, status per repo)
17
- - `state/.heartbeats/*` (epoch, per-agent thresholds: 3600s monitor, 300s watchdog, 1800s default)
18
-
19
- **dash-extra.mjs reads**:
20
- - `~/.claude/projects/**/ agent-*.jsonl` (modified time, <12min filter, top 8 recent)
21
- - `state/SECURITY-ALERTS.log` (severity classification: HIGH/MED/DRIFT)
22
-
23
- **Refresh cadence**:
24
- - watchdog-gui.sh: 4s loop (infinite, Ctrl-C to exit)
25
- - dash-extra.mjs: called on-demand (no built-in loop)
26
-
27
- ## Invariants & TUI conventions
28
-
29
- - **Never modify fleet state** — both tools are read-only dashboards.
30
- - **Infinite loop design** — watchdog-gui.sh runs forever; launch in dedicated terminal window, never inside tool shells.
31
- - **CRLF-safe** — no bash line continuations; portable POSIX.
32
- - **Node optional** — dash-extra.mjs gracefully prints "unavailable" if node missing; doesn't fail watchdog-gui.sh.
@@ -1,3 +0,0 @@
1
- # Archived Spike Investigations
2
-
3
- Spike investigations that were cancelled or superseded; kept for reference and historical context.
@@ -1,125 +0,0 @@
1
- # ACTIVATION runbook — force-model-policy.merged.mjs
2
-
3
- Swap the staged merged hook in for the live model-policy hook. **Do not run
4
- this casually** — the live hook governs every subagent dispatch in every
5
- session. Perform the swap between waves, not mid-fleet.
6
-
7
- Paths used below:
8
-
9
- - LIVE hook: `C:\Users\matt8\.claude\hooks\force-haiku-subagents.mjs`
10
- - STAGED file: `<aesop>\docs\spikes\tiered-cognition\force-model-policy.merged.mjs`
11
- - Shim agent def: `<aesop>\docs\spikes\tiered-cognition\aesop-cognition.example.md`
12
-
13
- ## Phase A activation (recommended first step)
14
-
15
- Default behavior after the swap: model policy identical to today, plus
16
- opus/fable-frontmatter dispatches rewritten to the message-only
17
- `aesop-cognition` shim. Sonnet specialists untouched (no prompt loss —
18
- FINDINGS.md condition 3 deferred). Layer 2 stays dormant.
19
-
20
- 1. **Install the shim agent definition** (the merged hook rewrites cognition
21
- dispatches to `subagent_type: aesop-cognition`; without the definition
22
- those dispatches would fail):
23
-
24
- ```powershell
25
- Copy-Item <aesop>\docs\spikes\tiered-cognition\aesop-cognition.example.md `
26
- C:\Users\matt8\.claude\agents\aesop-cognition.md
27
- ```
28
-
29
- Then edit the copied file's `description:` to remove the "NOT INSTALLED"
30
- sentence. Keep `tools: Agent, SendMessage, TaskStop, Monitor` and do NOT
31
- add a `model:` line (the hook passes the effective model explicitly).
32
-
33
- 2. **Back up the live hook** (timestamped, same directory):
34
-
35
- ```powershell
36
- Copy-Item C:\Users\matt8\.claude\hooks\force-haiku-subagents.mjs `
37
- C:\Users\matt8\.claude\hooks\force-haiku-subagents.mjs.bak-$(Get-Date -Format yyyyMMdd-HHmmss)
38
- ```
39
-
40
- 3. **Verify the staged file before copying** (must print `SELF-TEST: ALL PASS`,
41
- exit 0):
42
-
43
- ```powershell
44
- node --check <aesop>\docs\spikes\tiered-cognition\force-model-policy.merged.mjs
45
- node <aesop>\docs\spikes\tiered-cognition\force-model-policy.merged.mjs --self-test
46
- ```
47
-
48
- 4. **Copy over the live hook, keeping the live FILENAME** so settings.json
49
- needs no edit:
50
-
51
- ```powershell
52
- Copy-Item <aesop>\docs\spikes\tiered-cognition\force-model-policy.merged.mjs `
53
- C:\Users\matt8\.claude\hooks\force-haiku-subagents.mjs -Force
54
- ```
55
-
56
- 5. **Verify the settings matcher is unchanged and correct.** In
57
- `C:\Users\matt8\.claude\settings.json` the `hooks.PreToolUse` entry with
58
- matcher `Agent|Task` must point at
59
- `~/.claude/hooks/force-haiku-subagents.mjs` (node command). Do NOT add any
60
- other hook on that matcher — this file must remain the ONLY `updatedInput`
61
- owner for `Agent|Task`; wiring a second rewriting hook there reintroduces
62
- the exact collision this merge exists to fix.
63
-
64
- 6. **Re-run the self-test on the installed copy** (proves the copy is intact):
65
-
66
- ```powershell
67
- node C:\Users\matt8\.claude\hooks\force-haiku-subagents.mjs --self-test
68
- ```
69
-
70
- 7. **Restart.** Claude Code snapshots hook config at session start — running
71
- sessions keep the old hook until restarted. Restart the orchestrator
72
- session and any long-lived fleet sessions/daemons that dispatch subagents.
73
-
74
- 8. **Smoke-check in a fresh session:** dispatch a generic Haiku agent (expect
75
- pass/route-to-haiku, no shim), a `bash-pro` (expect claude-sonnet-5, no
76
- shim), and — if any opus/fable-frontmatter agent def is installed — one of
77
- those (expect the `⛓ … tools stripped via 'aesop-cognition' shim` system
78
- message). Confirm escape uses append to
79
- `state/MODEL-POLICY-ESCAPES.log` / `state/TIER-POLICY-ESCAPES.log`.
80
-
81
- ## Phase B (later, deliberate)
82
-
83
- Set `TIER_STRIP_SCOPE=all` in the environment Claude Code launches from to
84
- extend the shim to sonnet specialists — ONLY after per-specialist cognition
85
- variants exist (FINDINGS.md condition 3), otherwise specialist system prompts
86
- are lost to the generic shim. `TIER_STRIP_SCOPE=off` reduces the hook to pure
87
- live-hook model-policy behavior (useful as a soft rollback).
88
-
89
- ## Phase C — arming Layer 2 (call-time backstop; later, deliberate)
90
-
91
- Dormant until BOTH of these are true:
92
-
93
- 1. `TIER_ENFORCE=1` in the environment.
94
- 2. settings.json gains a second `PreToolUse` entry running this same hook file
95
- with a matcher covering the denied tools, e.g.
96
- `Read|Write|Edit|MultiEdit|NotebookEdit|Bash|PowerShell|Glob|Grep|WebFetch|WebSearch|Skill|Artifact`.
97
- (That entry never emits `updatedInput` — deny/allow only — so the
98
- single-owner rule is preserved.)
99
-
100
- Before arming: validate transcript sniffing on real sidechain transcripts and
101
- decide the main-thread carve-out (FINDINGS.md condition 2 / risk 4).
102
-
103
- ## Rollback
104
-
105
- 1. Restore the newest backup:
106
-
107
- ```powershell
108
- Copy-Item C:\Users\matt8\.claude\hooks\force-haiku-subagents.mjs.bak-<TS> `
109
- C:\Users\matt8\.claude\hooks\force-haiku-subagents.mjs -Force
110
- ```
111
-
112
- 2. Optionally remove `C:\Users\matt8\.claude\agents\aesop-cognition.md`
113
- (harmless to leave installed; nothing routes to it without the merged hook).
114
- 3. Unset `TIER_ENFORCE` / `TIER_STRIP_SCOPE` if set; remove any Layer-2
115
- matcher added in Phase C.
116
- 4. Restart running sessions (same snapshot rule as step 7 above).
117
-
118
- ## Env knobs (merged hook)
119
-
120
- | Var | Default | Effect |
121
- |---|---|---|
122
- | `FORCE_ALL_HAIKU=1` | off | Every dispatch → haiku (bash-pro excepted) — unchanged from live hook |
123
- | `TIER_STRIP_SCOPE` | `opus-fable` | `opus-fable` (Phase A) / `all` (Phase B) / `off` (model policy only) |
124
- | `TIER_ENFORCE=1` | off | Arms the Layer-2 call-time backstop (needs the Phase C matcher too) |
125
- | `AESOP_ROOT` | `~/aesop` | Root for `state/*-ESCAPES.log` audit logs |
@@ -1,287 +0,0 @@
1
- # Tiered Cognition/Execution — Wave-11 Spike Design
2
-
3
- > **STATUS: SPIKE — NOTHING HERE IS ACTIVATED.** No live hook, settings.json,
4
- > or skill was modified. The prototype (`strip-tools-hook.mjs`) and shim agent
5
- > (`aesop-cognition.example.md`) live only in this directory. Rollout is a
6
- > wave-12 decision — see `FINDINGS.md` for the GO/NO-GO and steps.
7
-
8
- Source plan: `conductor3/plans/aesop-wave11-tiered-cognition.md`.
9
-
10
- ## 1. Architecture recap
11
-
12
- ```
13
- Fable 5 ⇄ Haiku ⇄ Opus ⇄ Haiku ⇄ Sonnet 5 ⇄ Haiku ⇄ (files / git / tools / tests)
14
- (cognition) courier (cognition) courier (cognition) executor
15
- ```
16
-
17
- | Tier | Models | May do | May NOT do |
18
- |---|---|---|---|
19
- | Cognition | Fable 5, Opus, Sonnet 5 | Reason, decide, emit WORK-ORDERs, dispatch/message Haiku | Read, Write, Edit, Bash, Glob, Grep, WebFetch — any direct I/O/exec |
20
- | Substrate | Haiku | Everything real: read/write/exec/git/tests + carry messages between tiers | Improvise, silently "fix" a work-order, summarize away errors |
21
-
22
- Two mechanisms realize this: **(A)** a PreToolUse hook that strips I/O/exec
23
- tools from cognition-tier subagents, and **(B)** a structured courier/executor
24
- hand-off (WORK-ORDER → EXEC-BRIEF) so cognition output is mechanically
25
- applicable and results flow back as typed briefs.
26
-
27
- ## 2. Mechanism A — the tool-stripping PreToolUse hook
28
-
29
- Prototype: [`strip-tools-hook.mjs`](strip-tools-hook.mjs) — standalone, pure
30
- `decide()` core, `--self-test` builds fixtures and asserts 27 cases (all pass).
31
-
32
- ### 2.1 Harness facts that shape the design
33
-
34
- 1. **The Agent tool input has NO `tools` field.** Its schema is
35
- `{description, prompt, subagent_type, model, isolation, run_in_background}`
36
- with `additionalProperties: false`. A hook's `updatedInput` therefore
37
- *cannot* inject a per-dispatch tool list (it would be an
38
- InputValidationError). The only supported way to constrain a subagent's
39
- tools today is the agent definition's `tools:` frontmatter
40
- (`~/.claude/agents/*.md`) — so dispatch-time stripping is implemented as a
41
- **rewrite to a shim agent type** whose definition carries the allowlist.
42
- 2. **PreToolUse hooks fire for subagents' own tool calls too**, which enables a
43
- call-time backstop independent of how the agent was dispatched.
44
- 3. **The hook payload does not name the session's model**
45
- (`{session_id, transcript_path, cwd, tool_name, tool_input, ...}`), so
46
- call-time tier detection is inferred from the transcript (§2.3).
47
- 4. **Hook composition hazard:** the live `force-haiku-subagents.mjs` already
48
- emits `updatedInput` on the same `Agent|Task` matcher. Two hooks rewriting
49
- the same call have no defined merge; at rollout the tier policy must be
50
- **merged into the model-policy hook as one file** (model routing decides the
51
- tier; the same decision then strips or keeps tools — one decision point, no
52
- ordering hazard).
53
-
54
- ### 2.2 Layer 1 — dispatch guard (matcher `Agent|Task`)
55
-
56
- **Tier detection**, in order:
57
-
58
- 1. Explicit `tool_input.model` — substring match: `haiku` → substrate;
59
- `sonnet` / `opus` / `fable` → cognition.
60
- 2. Blank model + `subagent_type` → the installed agent definition's `model:`
61
- frontmatter (same catalog walk the model-policy hook uses).
62
- 3. Neither → **haiku** (correct by construction: the model-policy hook lands
63
- every non-specialist, non-escaped dispatch on Haiku).
64
-
65
- **Decision table:**
66
-
67
- | Dispatch | Action |
68
- |---|---|
69
- | Tier = haiku (or `subagent_type: fork`) | pass untouched |
70
- | Tier = cognition, prompt has escape token | allow untouched + **audit** (§2.4) |
71
- | Tier = cognition, no escape | **strip** — rewrite `subagent_type` → `aesop-cognition`, prepend `[COGNITION CONTRACT v1]` header naming the originally requested type |
72
- | Malformed payload / unreadable catalog | pass (fail-open) |
73
-
74
- **Kept tools** (via the shim's `tools:` frontmatter, see
75
- [`aesop-cognition.example.md`](aesop-cognition.example.md)):
76
- `Agent, SendMessage, TaskStop, Monitor` — reason, message, dispatch Haiku,
77
- stop/monitor what it dispatched. Nothing else.
78
-
79
- **Stripped (denied) tools:**
80
- `Read, Write, Edit, MultiEdit, NotebookEdit, Bash, PowerShell, BashOutput,
81
- KillShell, Glob, Grep, WebFetch, WebSearch, Skill, Artifact, ToolSearch,
82
- EnterWorktree, ExitWorktree`.
83
-
84
- Note the mission's generalization: cognition tiers lose **Read/Grep/Glob too**
85
- — facts arrive via Haiku courier briefs, never raw file ingestion. This is the
86
- "orchestrator reads nothing" cardinal rule applied to every expensive tier.
87
-
88
- **Cost of the shim rewrite:** the original specialist's system prompt (its
89
- `.md` body) is lost; the role survives only as the contract header line. §5
90
- lists the wave-12 fix (generated per-specialist cognition variants).
91
-
92
- ### 2.3 Layer 2 — call-time backstop (matcher `Read|Write|Edit|NotebookEdit|Bash|PowerShell|Glob|Grep|WebFetch|WebSearch|Skill|Artifact`)
93
-
94
- Defense in depth for dispatches that bypass Layer 1 (pre-existing sessions,
95
- SDK-defined agents, the main thread, drift after a harness update).
96
-
97
- 1. Parse `transcript_path` (JSONL). Tier = model of the **last** assistant
98
- entry (`message.model`) — robust to mid-session model switches.
99
- 2. Escape check: any user entry containing an escape token (a subagent's
100
- dispatch prompt is the first user message of its transcript, so an escaped
101
- dispatch is visible here).
102
- 3. Decision: haiku or escaped → allow; cognition → **deny**; transcript
103
- missing/unreadable/model-free → pass (fail-open, no opinion).
104
-
105
- The deny is **self-correcting**: `permissionDecision: "deny"` returns the
106
- reason to the model, and the reason is an instruction —
107
-
108
- > "Do NOT retry {tool}. Instead emit a WORK-ORDER v1 … and dispatch a Haiku
109
- > executor agent to apply it and report back an EXEC-BRIEF."
110
-
111
- so a cognition agent that tries to touch a file is redirected into the courier
112
- pattern mid-flight instead of just failing.
113
-
114
- **Main-thread consequence:** the backstop as specified also denies a Fable
115
- main thread's direct writes — which *is* the cardinal rule ("orchestrator
116
- never hand-writes files") — but today's orchestrator legitimately runs short
117
- git one-liners. Rollout needs either a read-only Bash allowlist
118
- (`git status|log|diff --stat` patterns) or a main-thread carve-out. Flagged in
119
- FINDINGS; the spike prototype governs the listed tools uniformly.
120
-
121
- ### 2.4 Escape hatch — `[[ALLOW-TIER-TOOLS]]`
122
-
123
- Same contract as the model policy's `[[ALLOW-NON-HAIKU]]`:
124
-
125
- - Token in the dispatch prompt → the agent keeps its full toolset.
126
- - **Never silent**: announced via `permissionDecisionReason` in the transcript
127
- AND appended as a JSON line to
128
- `${AESOP_ROOT:-~/aesop}/state/TIER-POLICY-ESCAPES.log`
129
- (`{ts, event: "tier_policy_escape", tool, session_id, cwd, description,
130
- requested_model, prompt_head}`).
131
- - During migration, `[[ALLOW-NON-HAIKU]]` is grandfathered as implying tool
132
- retention (today's escalations are hands-on missions — this spike itself ran
133
- under it). Once cognition dispatch is the norm, escalate-model-but-stay-
134
- cognition becomes the default and `[[ALLOW-NON-HAIKU]]` stops implying tools.
135
-
136
- ## 3. Mechanism B — the courier/executor hand-off
137
-
138
- ### 3.1 WORK-ORDER v1 (cognition → executor)
139
-
140
- A cognition agent's entire real-world intent for one coherent change-set,
141
- batched into a single JSON document:
142
-
143
- ```json
144
- {
145
- "kind": "work-order",
146
- "version": 1,
147
- "id": "wo-<slug>",
148
- "intent": "one-line goal (for logs and the brief)",
149
- "worktree": "C:/abs/path/to/isolated/worktree",
150
- "branch": "expected branch (executor verifies before acting)",
151
- "patch": "<unified diff as a single string, or null>",
152
- "commands": [
153
- { "run": "exact command line", "cwd": "abs path", "expect_exit": 0, "timeout_s": 120 }
154
- ],
155
- "verify": [
156
- { "run": "exact command line", "expect_exit": 0, "expect_stdout_re": "optional regex" }
157
- ],
158
- "report": { "format": "exec-brief/v1", "max_words": 200 }
159
- }
160
- ```
161
-
162
- Semantics:
163
-
164
- - `patch` is applied with `git apply` (byte-exact context; a failed hunk is a
165
- FAILED brief, never hand-merged). `commands` run in listed order, stopping at
166
- the first unexpected exit. `verify` always runs after (even partially) —
167
- it is the evidence section of the brief.
168
- - Ambiguity is an error: if the executor cannot apply the order *exactly as
169
- written*, it stops and reports; it never substitutes judgment.
170
- - One work-order per coherent change-set (**batching amortizes the round-trip**
171
- — a whole patch + command plan, never line-by-line).
172
-
173
- ### 3.2 EXEC-BRIEF v1 (executor → cognition)
174
-
175
- ```json
176
- {
177
- "kind": "exec-brief",
178
- "version": 1,
179
- "work_order_id": "wo-<slug>",
180
- "status": "APPLIED | FAILED | PARTIAL",
181
- "steps": [ { "step": "apply-patch | cmd:<n> | verify:<n>", "ok": true, "detail": "≤1 line" } ],
182
- "verify_results": [ { "run": "…", "exit": 0, "stdout_tail": "last ≤3 lines" } ],
183
- "verbatim_errors": [ "exact error text, untrimmed — empty array iff none" ],
184
- "files_touched": [ "relative paths" ],
185
- "commit": "sha or null"
186
- }
187
- ```
188
-
189
- ### 3.3 Courier honesty rules
190
-
191
- 1. Apply **exactly** what the order says; no improvisation, no "small fixes".
192
- 2. Failures are reported **verbatim** in `verbatim_errors` — never summarized,
193
- never softened. The substrate must not editorialize.
194
- 3. A brief is **always** produced (inputs always produce outputs) — even a
195
- crash mid-order yields `status: FAILED` with whatever evidence exists.
196
- 4. Verification stays owner-owned: the executor *runs* the verify commands;
197
- the owning cognition tier *judges* the pass/fail brief
198
- (per `[[subagents-push-orchestrator-verifies]]`).
199
-
200
- ## 4. Worked example — a real round-trip (executed in this spike)
201
-
202
- Task: one-line real doc change — link this spike directory from
203
- `docs/README.md`. The cognition tier (Fable, this session) authored the
204
- work-order below; a **real Haiku executor subagent** applied it.
205
-
206
- ### 4.1 The cognition-authored WORK-ORDER
207
-
208
- ```json
209
- {
210
- "kind": "work-order",
211
- "version": 1,
212
- "id": "wo-w11-readme-spikes-link",
213
- "intent": "Add a Design Spikes section to docs/README.md linking the tiered-cognition spike",
214
- "worktree": "C:/Users/matt8/aesop-wt-wave11-mission-spike",
215
- "branch": "spike/wave11-tiered-cognition",
216
- "patch": "<the unified diff below, embedded verbatim>",
217
- "commands": [],
218
- "verify": [
219
- { "run": "git -C C:/Users/matt8/aesop-wt-wave11-mission-spike diff --numstat -- docs/README.md", "expect_exit": 0, "expect_stdout_re": "^4\\t0\\tdocs/README.md$" },
220
- { "run": "grep -n \"## Design Spikes\" docs/README.md", "expect_exit": 0, "expect_stdout_re": "^38:" },
221
- { "run": "grep -c \"spikes/tiered-cognition/\" docs/README.md", "expect_exit": 0, "expect_stdout_re": "^1$" }
222
- ],
223
- "report": { "format": "exec-brief/v1", "max_words": 200 }
224
- }
225
- ```
226
-
227
- ```diff
228
- --- a/docs/README.md
229
- +++ b/docs/README.md
230
- @@ -33,4 +33,8 @@
231
- ## Operational Guides
232
-
233
- - [PUBLISHING.md](PUBLISHING.md) — Release Aesop to npm using GitHub Actions with OIDC trusted publishing
234
- - [av-resilience.md](av-resilience.md) — Antivirus & behavioral-engine resilience patterns for reliable agent execution
235
- +
236
- +## Design Spikes
237
- +
238
- +- [spikes/tiered-cognition/](spikes/tiered-cognition/) — Wave-11 spike: tiered cognition/execution architecture (Haiku as sole I/O substrate; Sonnet/Opus/Fable cognition tiers emit structured work-orders)
239
- ```
240
-
241
- ### 4.2 The EXEC-BRIEF as received from the Haiku executor
242
-
243
- Captured verbatim from a live general-purpose sub-dispatch (which the live
244
- model policy landed on **Haiku**, as the architecture requires). Round-trip:
245
- **33.9 s wall-clock, 33,932 subagent tokens, 6 tool uses**; the change was then
246
- independently re-verified by the owner (`git diff --numstat` = `4 0
247
- docs/README.md`, nothing else touched, em-dash bytes intact).
248
-
249
- ```json
250
- {
251
- "kind": "exec-brief",
252
- "version": 1,
253
- "work_order_id": "wo-w11-readme-spikes-link",
254
- "status": "APPLIED",
255
- "steps": [
256
- { "step": "branch-check", "ok": true, "detail": "On spike/wave11-tiered-cognition" },
257
- { "step": "write-patch", "ok": true, "detail": "Patch file written to scratchpad" },
258
- { "step": "apply-patch", "ok": true, "detail": "git apply succeeded with exit 0" },
259
- { "step": "verify:1", "ok": true, "detail": "diff --numstat: 4 additions, 0 deletions" },
260
- { "step": "verify:2", "ok": true, "detail": "## Design Spikes found at line 38" },
261
- { "step": "verify:3", "ok": true, "detail": "tiered-cognition reference count: 1" }
262
- ],
263
- "verify_results": [
264
- { "run": "git diff --numstat -- docs/README.md", "exit": 0, "stdout_tail": "4\t0\tdocs/README.md" },
265
- { "run": "grep -n '## Design Spikes' docs/README.md", "exit": 0, "stdout_tail": "38:## Design Spikes" },
266
- { "run": "grep -c 'spikes/tiered-cognition/' docs/README.md", "exit": 0, "stdout_tail": "1" }
267
- ],
268
- "verbatim_errors": [],
269
- "files_touched": ["docs/README.md"],
270
- "commit": null
271
- }
272
- ```
273
-
274
- ## 5. Rollout sketch (wave 12 — only if GO)
275
-
276
- 1. Merge the tier policy into `force-haiku-subagents.mjs` (one decision point:
277
- route model → strip or keep tools) — eliminates the dual-`updatedInput`
278
- hazard of §2.1(4).
279
- 2. Install `aesop-cognition.md`; generate per-specialist cognition variants
280
- (`aesop-cognition-<specialist>.md`: specialist body + cognition tool
281
- allowlist) so the specialist system prompt survives stripping.
282
- 3. Add the Layer-2 backstop matcher to settings.json; decide the main-thread
283
- carve-out (read-only git allowlist vs. exempting the top-level session).
284
- 4. Teach `/buildsystem` dispatch prompts the WORK-ORDER/EXEC-BRIEF contract;
285
- add an `aesop-executor` Haiku agent definition embedding §3.3.
286
- 5. Verify transcript-sniff fidelity on real sidechain transcripts (the one
287
- mechanism this spike could not fully validate in-harness — see FINDINGS).