@matt82198/aesop 0.1.0-beta.3 → 0.1.0-beta.5

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 (234) hide show
  1. package/CHANGELOG.md +79 -95
  2. package/CLAUDE-TEMPLATE.md +115 -0
  3. package/README.md +126 -245
  4. package/aesop.config.example.json +34 -26
  5. package/bin/CLAUDE.md +67 -0
  6. package/bin/cli.js +593 -36
  7. package/daemons/CLAUDE.md +36 -0
  8. package/daemons/backup-fleet.sh +92 -21
  9. package/daemons/run-watchdog.sh +95 -12
  10. package/dash/CLAUDE.md +32 -0
  11. package/dash/dash-extra.mjs +194 -21
  12. package/dash/watchdog-gui.sh +23 -6
  13. package/docs/BEHAVIORAL-PR-REVIEW.md +86 -0
  14. package/docs/CHECKPOINTING.md +100 -0
  15. package/docs/DISPATCH-MODEL.md +11 -7
  16. package/docs/FORENSICS.md +204 -0
  17. package/docs/GOVERNANCE.md +4 -2
  18. package/docs/HOOK-INSTALL.md +307 -0
  19. package/docs/HOW-THE-LOOP-WORKS.md +175 -0
  20. package/docs/MEMORY-TEMPLATE.md +61 -0
  21. package/docs/README.md +44 -0
  22. package/docs/RELEASING.md +159 -0
  23. package/docs/RELIABILITY.md +61 -0
  24. package/docs/RESTORE.md +397 -0
  25. package/docs/SCRIPTS-POLICY.md +97 -0
  26. package/docs/archive/README.md +3 -0
  27. package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +125 -0
  28. package/docs/archive/spikes/tiered-cognition/DESIGN.md +287 -0
  29. package/docs/archive/spikes/tiered-cognition/FINDINGS.md +113 -0
  30. package/docs/archive/spikes/tiered-cognition/README.md +27 -0
  31. package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +32 -0
  32. package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +673 -0
  33. package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +434 -0
  34. package/docs/case-study-portfolio.md +61 -0
  35. package/docs/self-stats-data.json +11 -0
  36. package/docs/templates/FLEET-OPS-ANALYSIS.example.md +27 -0
  37. package/docs/templates/FLEET-OPS-RECOMMENDATIONS.example.md +24 -0
  38. package/docs/templates/PROPOSALS-LOG.example.md +64 -0
  39. package/hooks/CLAUDE.md +61 -0
  40. package/hooks/claude/force-model-policy.mjs +159 -0
  41. package/hooks/pre-push-policy.sh +753 -0
  42. package/mcp/CLAUDE.md +213 -0
  43. package/mcp/package.json +26 -0
  44. package/mcp/server.mjs +543 -0
  45. package/monitor/.signal-state.json +3 -0
  46. package/monitor/ACTIONS.log +1 -0
  47. package/monitor/BRIEF.md +24 -0
  48. package/monitor/CHARTER.md +75 -88
  49. package/monitor/CLAUDE.md +40 -0
  50. package/monitor/SIGNALS.json +54 -0
  51. package/monitor/collect-signals.mjs +613 -61
  52. package/package.json +15 -3
  53. package/scan/fleet-scan.example.mjs +292 -0
  54. package/skills/CLAUDE.md +3 -0
  55. package/skills/healthcheck/SKILL.md +44 -0
  56. package/skills/power/SKILL.md +161 -0
  57. package/state_store/CLAUDE.md +39 -0
  58. package/state_store/__init__.py +24 -0
  59. package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
  60. package/state_store/__pycache__/api.cpython-314.pyc +0 -0
  61. package/state_store/__pycache__/export.cpython-314.pyc +0 -0
  62. package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
  63. package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
  64. package/state_store/__pycache__/store.cpython-314.pyc +0 -0
  65. package/state_store/api.py +35 -0
  66. package/state_store/export.py +19 -0
  67. package/state_store/ingest.py +24 -0
  68. package/state_store/projections.py +52 -0
  69. package/state_store/store.py +102 -0
  70. package/tools/CLAUDE.md +72 -0
  71. package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
  72. package/tools/__pycache__/buildlog.cpython-314.pyc +0 -0
  73. package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
  74. package/tools/__pycache__/ensure_state.cpython-314.pyc +0 -0
  75. package/tools/__pycache__/eod_sweep.cpython-314.pyc +0 -0
  76. package/tools/__pycache__/fleet_ledger.cpython-314.pyc +0 -0
  77. package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
  78. package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
  79. package/tools/__pycache__/heartbeat.cpython-314.pyc +0 -0
  80. package/tools/__pycache__/inbox_drain.cpython-314.pyc +0 -0
  81. package/tools/__pycache__/launch_tui.cpython-314.pyc +0 -0
  82. package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
  83. package/tools/__pycache__/orchestrator_status.cpython-314.pyc +0 -0
  84. package/tools/__pycache__/power_selftest.cpython-314.pyc +0 -0
  85. package/tools/__pycache__/prepublish_scan.cpython-314.pyc +0 -0
  86. package/tools/__pycache__/rotate_logs.cpython-314.pyc +0 -0
  87. package/tools/__pycache__/scanner_selftest.cpython-314.pyc +0 -0
  88. package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
  89. package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
  90. package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
  91. package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
  92. package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
  93. package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
  94. package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
  95. package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
  96. package/tools/agent-forensics.sh +186 -0
  97. package/tools/alert_bridge.py +449 -0
  98. package/tools/buildlog.py +182 -0
  99. package/tools/ci_merge_wait.py +317 -0
  100. package/tools/ensure_state.py +94 -0
  101. package/tools/eod_sweep.py +239 -0
  102. package/tools/fleet_ledger.py +272 -0
  103. package/tools/fleet_prompt_extractor.py +134 -0
  104. package/tools/healthcheck.py +296 -0
  105. package/tools/heartbeat.py +131 -0
  106. package/tools/inbox_drain.py +249 -0
  107. package/tools/launch_tui.py +51 -3
  108. package/tools/lock.mjs +211 -0
  109. package/tools/metrics_gate.py +205 -0
  110. package/tools/orchestrator_status.py +99 -0
  111. package/tools/power_selftest.py +386 -0
  112. package/tools/prepublish_scan.py +84 -0
  113. package/tools/proposals.mjs +248 -0
  114. package/tools/reconstitute.sh +467 -0
  115. package/tools/rotate_logs.py +228 -0
  116. package/tools/scanner_selftest.py +303 -0
  117. package/tools/secret_scan.py +138 -31
  118. package/tools/self_stats.py +509 -0
  119. package/tools/session_usage_summary.py +198 -0
  120. package/tools/stall_check.py +172 -0
  121. package/tools/svg_to_png.mjs +50 -0
  122. package/tools/transcript_replay.py +236 -0
  123. package/tools/transcript_timeline.py +184 -0
  124. package/tools/verify_dash.py +513 -0
  125. package/tools/verify_submit_encoding.py +202 -0
  126. package/ui/CLAUDE.md +125 -0
  127. package/ui/__pycache__/agents.cpython-314.pyc +0 -0
  128. package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
  129. package/ui/__pycache__/config.cpython-314.pyc +0 -0
  130. package/ui/__pycache__/cost.cpython-314.pyc +0 -0
  131. package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
  132. package/ui/__pycache__/handler.cpython-314.pyc +0 -0
  133. package/ui/__pycache__/render.cpython-314.pyc +0 -0
  134. package/ui/__pycache__/serve.cpython-314.pyc +0 -0
  135. package/ui/__pycache__/sse.cpython-314.pyc +0 -0
  136. package/ui/agents.py +183 -0
  137. package/ui/api/__init__.py +84 -0
  138. package/ui/api/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
  140. package/ui/api/__pycache__/tracker.cpython-314.pyc +0 -0
  141. package/ui/api/submit.py +97 -0
  142. package/ui/api/tracker.py +116 -0
  143. package/ui/collectors.py +735 -0
  144. package/ui/config.py +123 -0
  145. package/ui/cost.py +260 -0
  146. package/ui/csrf.py +144 -0
  147. package/ui/handler.py +676 -0
  148. package/ui/render.py +49 -0
  149. package/ui/serve.py +68 -796
  150. package/ui/sse.py +183 -0
  151. package/ui/web/.gitattributes +13 -0
  152. package/ui/web/dist/assets/index-2LZDQirC.js +9 -0
  153. package/ui/web/dist/assets/index-D4M1qyOv.css +1 -0
  154. package/ui/web/dist/index.html +14 -0
  155. package/ui/web/index.html +13 -0
  156. package/ui/web/package-lock.json +2225 -0
  157. package/ui/web/package.json +26 -0
  158. package/ui/web/src/App.test.tsx +74 -0
  159. package/ui/web/src/App.tsx +142 -0
  160. package/ui/web/src/CONTRIBUTING-UI.md +49 -0
  161. package/ui/web/src/components/AgentRow.css +187 -0
  162. package/ui/web/src/components/AgentRow.test.tsx +209 -0
  163. package/ui/web/src/components/AgentRow.tsx +207 -0
  164. package/ui/web/src/components/AgentsPanel.css +108 -0
  165. package/ui/web/src/components/AgentsPanel.test.tsx +41 -0
  166. package/ui/web/src/components/AgentsPanel.tsx +58 -0
  167. package/ui/web/src/components/AlertsPanel.css +88 -0
  168. package/ui/web/src/components/AlertsPanel.test.tsx +51 -0
  169. package/ui/web/src/components/AlertsPanel.tsx +67 -0
  170. package/ui/web/src/components/BacklogPanel.test.tsx +126 -0
  171. package/ui/web/src/components/BacklogPanel.tsx +122 -0
  172. package/ui/web/src/components/CostChart.css +110 -0
  173. package/ui/web/src/components/CostChart.test.tsx +144 -0
  174. package/ui/web/src/components/CostChart.tsx +152 -0
  175. package/ui/web/src/components/CostTable.css +93 -0
  176. package/ui/web/src/components/CostTable.test.tsx +165 -0
  177. package/ui/web/src/components/CostTable.tsx +94 -0
  178. package/ui/web/src/components/EventsFeed.css +68 -0
  179. package/ui/web/src/components/EventsFeed.test.tsx +36 -0
  180. package/ui/web/src/components/EventsFeed.tsx +31 -0
  181. package/ui/web/src/components/HealthHeader.css +137 -0
  182. package/ui/web/src/components/HealthHeader.test.tsx +278 -0
  183. package/ui/web/src/components/HealthHeader.tsx +281 -0
  184. package/ui/web/src/components/InboxForm.css +135 -0
  185. package/ui/web/src/components/InboxForm.test.tsx +208 -0
  186. package/ui/web/src/components/InboxForm.tsx +116 -0
  187. package/ui/web/src/components/MessagesTail.module.css +144 -0
  188. package/ui/web/src/components/MessagesTail.test.tsx +176 -0
  189. package/ui/web/src/components/MessagesTail.tsx +94 -0
  190. package/ui/web/src/components/ReposPanel.css +90 -0
  191. package/ui/web/src/components/ReposPanel.test.tsx +45 -0
  192. package/ui/web/src/components/ReposPanel.tsx +67 -0
  193. package/ui/web/src/components/Scorecard.css +106 -0
  194. package/ui/web/src/components/Scorecard.test.tsx +117 -0
  195. package/ui/web/src/components/Scorecard.tsx +85 -0
  196. package/ui/web/src/components/Timeline.module.css +151 -0
  197. package/ui/web/src/components/Timeline.test.tsx +215 -0
  198. package/ui/web/src/components/Timeline.tsx +99 -0
  199. package/ui/web/src/components/TrackerBoard.test.tsx +121 -0
  200. package/ui/web/src/components/TrackerBoard.tsx +107 -0
  201. package/ui/web/src/components/TrackerCard.test.tsx +180 -0
  202. package/ui/web/src/components/TrackerCard.tsx +160 -0
  203. package/ui/web/src/components/TrackerForm.test.tsx +189 -0
  204. package/ui/web/src/components/TrackerForm.tsx +144 -0
  205. package/ui/web/src/lib/api.ts +218 -0
  206. package/ui/web/src/lib/format.test.ts +89 -0
  207. package/ui/web/src/lib/format.ts +103 -0
  208. package/ui/web/src/lib/sanitizeUrl.test.ts +84 -0
  209. package/ui/web/src/lib/sanitizeUrl.ts +38 -0
  210. package/ui/web/src/lib/types.ts +230 -0
  211. package/ui/web/src/lib/useHashRoute.test.ts +60 -0
  212. package/ui/web/src/lib/useHashRoute.ts +23 -0
  213. package/ui/web/src/lib/useSSE.ts +175 -0
  214. package/ui/web/src/main.tsx +10 -0
  215. package/ui/web/src/styles/global.css +179 -0
  216. package/ui/web/src/styles/theme.css +184 -0
  217. package/ui/web/src/styles/work.css +572 -0
  218. package/ui/web/src/test/fixtures.ts +385 -0
  219. package/ui/web/src/test/setup.ts +49 -0
  220. package/ui/web/src/views/Activity.module.css +43 -0
  221. package/ui/web/src/views/Activity.test.tsx +89 -0
  222. package/ui/web/src/views/Activity.tsx +31 -0
  223. package/ui/web/src/views/Cost.css +87 -0
  224. package/ui/web/src/views/Cost.test.tsx +142 -0
  225. package/ui/web/src/views/Cost.tsx +54 -0
  226. package/ui/web/src/views/Overview.css +51 -0
  227. package/ui/web/src/views/Overview.test.tsx +76 -0
  228. package/ui/web/src/views/Overview.tsx +46 -0
  229. package/ui/web/src/views/Work.test.tsx +82 -0
  230. package/ui/web/src/views/Work.tsx +79 -0
  231. package/ui/web/src/vite-env.d.ts +10 -0
  232. package/ui/web/tsconfig.json +22 -0
  233. package/ui/web/vite.config.ts +25 -0
  234. package/ui/web/vitest.config.ts +12 -0
@@ -0,0 +1,204 @@
1
+ # Forensics: Making Agent Failures Git-Bisectable
2
+
3
+ ## The Pillar
4
+
5
+ Agent behavior is source code. When an agent misbehaves, you can reconstruct exactly what rules, memory, and policies were active at the time of failure — then bisect the codebase to find the commit that broke it. This turns vague "the agent was weird yesterday" into a reproducible root cause.
6
+
7
+ **Key principle**: Rules (CARDINAL-RULES.md, CLAUDE.md), checkpoints (STATE.md, BUILDLOG.md), and policies (hooks/, monitor/CHARTER.md) are the agent's executable spec. Versioning them in git means agent failures become debuggable like code regressions.
8
+
9
+ ## Tools
10
+
11
+ ### `tools/agent-forensics.sh`
12
+
13
+ Read-only incident-forensics tool. Reconstructs agent behavior from git history using only git plumbing (no checkout, no working-tree mutation).
14
+
15
+ **Usage:**
16
+
17
+ ```bash
18
+ # Snapshot agent behavior at a commit
19
+ bash tools/agent-forensics.sh <commit>
20
+
21
+ # Show what behavior changed between two commits
22
+ bash tools/agent-forensics.sh --diff <commitA> <commitB>
23
+ ```
24
+
25
+ **Output:**
26
+ - Commit header (hash, date, subject)
27
+ - First ~40 lines of rules (docs/CARDINAL-RULES.md)
28
+ - CLAUDE.md snapshot (if present)
29
+ - STATE.md snapshot (if present)
30
+ - Last 30 lines of BUILDLOG.md (if tracked)
31
+ - For `--diff`: side-by-side changes in CLAUDE.md, STATE.md, docs/, hooks/, monitor/CHARTER.md
32
+
33
+ **Exit codes:**
34
+ - `0` on success
35
+ - `1` on error (unknown commit, I/O failure); clean error message printed to stderr, never raw stack traces
36
+
37
+ **Examples:**
38
+
39
+ ```bash
40
+ # Forensics at HEAD
41
+ bash tools/agent-forensics.sh HEAD
42
+
43
+ # Forensics at a specific commit
44
+ bash tools/agent-forensics.sh 858ac08
45
+
46
+ # What changed in behavior between two releases?
47
+ bash tools/agent-forensics.sh --diff v0.1.0-beta.2 v0.1.0-beta.3
48
+ ```
49
+
50
+ ## Recipe: Bisect an Agent Regression
51
+
52
+ When an agent's behavior regressed (e.g., "rules started being ignored on July 10"):
53
+
54
+ 1. **Identify the failure time window.** (e.g., "agent started proposing without filtering on 2026-07-10 18:00 UTC")
55
+
56
+ 2. **Run forensics at the last known-good commit** and the first known-bad commit:
57
+ ```bash
58
+ bash tools/agent-forensics.sh <good-commit>
59
+ bash tools/agent-forensics.sh <bad-commit>
60
+ ```
61
+ Compare the output manually. Often you'll spot the culprit immediately (e.g., a rule was deleted, CLAUDE.md lost a policy).
62
+
63
+ 3. **If manual inspection doesn't find it, bisect:**
64
+ ```bash
65
+ git bisect start
66
+ git bisect bad <bad-commit>
67
+ git bisect good <good-commit>
68
+ ```
69
+
70
+ 4. **Write a behavior predicate** (a test that reproduces the failure):
71
+ ```bash
72
+ # Example: check that docs/CARDINAL-RULES.md mentions "Haiku" at this commit
73
+ git bisect run bash -c '
74
+ git show HEAD:docs/CARDINAL-RULES.md | grep -q "Haiku" && exit 0 || exit 1
75
+ '
76
+ ```
77
+ Bisect will pin-point the exact commit that broke the behavior.
78
+
79
+ 5. **Review the offending commit** using `--diff`:
80
+ ```bash
81
+ bash tools/agent-forensics.sh --diff <parent-of-bad> <bad-commit>
82
+ ```
83
+
84
+ 6. **Fix the issue** (restore the rule, reinstate the policy, etc.) in a new commit on a feature branch.
85
+
86
+ ## What to Check When an Incident Happens
87
+
88
+ ### Incident Discovery
89
+ 1. Capture the **current behavior** (what went wrong, when, which agent):
90
+ - Run `bash tools/agent-forensics.sh HEAD` to see rules + state at time of report.
91
+ - Check logs in `state/FLEET-BACKUP.log`, `state/ACTIONS.log`, or agent transcript.
92
+ - Note the **exact timestamp** of the first wrong behavior.
93
+
94
+ 2. **Cross-reference the timestamp** with `git log --since="<time>"` to find commits near that time.
95
+
96
+ ### Root Cause Narrowing
97
+ 3. **Did a rule change?**
98
+ ```bash
99
+ git log -p --since="<before-incident>" -- docs/CARDINAL-RULES.md CLAUDE.md | head -100
100
+ ```
101
+ Look for deletions, edits to retry logic, dispatch budgets, or policy changes.
102
+
103
+ 4. **Did state get corrupted?**
104
+ ```bash
105
+ bash tools/agent-forensics.sh <commit-before-incident>
106
+ bash tools/agent-forensics.sh <commit-at-incident>
107
+ # Compare STATE.md and BUILDLOG.md for anomalies (e.g., PHASE stuck, cost spike)
108
+ ```
109
+
110
+ 5. **Did a hook fail silently?**
111
+ ```bash
112
+ git log -p --since="<before-incident>" -- hooks/ monitor/CHARTER.md
113
+ cat state/SECURITY-AUDIT.log | tail -50
114
+ ```
115
+
116
+ ### Confirmation via Bisect
117
+ 6. Once you have a suspect commit or time range, use the bisect recipe above to narrow it down.
118
+
119
+ 7. **Write the fix** in a feature branch. Update CLAUDE.md if the rule itself was wrong; add an explanatory comment referencing the incident date.
120
+
121
+ 8. **Add a regression test:** if the incident was "agent ignored retry cap," add a check in pre-push-policy.sh or a future test-harness to catch that rule drifting again.
122
+
123
+ ## Durable Artifacts to Check
124
+
125
+ | File | Purpose | Checked via |
126
+ |------|---------|-------------|
127
+ | **docs/CARDINAL-RULES.md** | Dispatch, cost, retry, TDD policy | `bash tools/agent-forensics.sh <commit>` |
128
+ | **CLAUDE.md** | Project-level rules, branch discipline, commit gates | manual forensics; git log |
129
+ | **STATE.md** | Current phase, locked decisions, next steps | forensics snapshot |
130
+ | **BUILDLOG.md** | Append-only cycle log (cost, actions, phase changes) | forensics (last 5 entries) |
131
+ | **hooks/** | Pre-push policy, secret-scan gate, lint checks | git show or forensics --diff |
132
+ | **monitor/CHARTER.md** | Orchestration monitor config (AUTO/PROPOSE thresholds) | forensics --diff |
133
+
134
+ ## Example: Debugging a Cost Spike
135
+
136
+ **Symptom**: Agent token spend jumped from 50k to 200k tokens on 2026-07-10.
137
+
138
+ 1. Run forensics at the spike time:
139
+ ```bash
140
+ bash tools/agent-forensics.sh <commit-on-2026-07-10>
141
+ ```
142
+ Check STATE.md and BUILDLOG.md. Look for:
143
+ - Did `parallelism` increase? (more agents spawned → more tokens)
144
+ - Did `retries` increase? (retry cap lowered → more respawns)
145
+ - Did `model` change to Opus? (rule 1 violation)
146
+
147
+ 2. Narrow the window:
148
+ ```bash
149
+ git log --oneline --since="2026-07-09" --until="2026-07-11"
150
+ bash tools/agent-forensics.sh --diff <2026-07-09-commit> <2026-07-10-spike-commit>
151
+ ```
152
+
153
+ 3. If a rule changed (e.g., retries bumped from 3 to 6), fix it:
154
+ ```bash
155
+ git log -p --since="2026-07-09" -- docs/CARDINAL-RULES.md | grep -A5 -B5 retries
156
+ ```
157
+
158
+ 4. Restore or correct the rule, commit, and monitor spend in the next cycle.
159
+
160
+ ## Integration with the Refinement Loop
161
+
162
+ Forensic replay strengthens the orchestration refinement cycle by enabling rapid root-cause analysis when the fleet exhibits unexpected behavior changes.
163
+
164
+ **When to invoke forensics in refinement:**
165
+
166
+ 1. **Suspected behavior regression** — If PROPOSALS.md or monitoring signals suggest rules changed unexpectedly:
167
+ ```bash
168
+ # Find the suspect commit range
169
+ git log --oneline --all | head -20
170
+
171
+ # Compare behavior-controlling files between two commits
172
+ bash tools/agent-forensics.sh --diff <last-known-good> <suspect-bad>
173
+ ```
174
+ This immediately shows what changed in CLAUDE.md, STATE.md, docs/, hooks/, and monitor/CHARTER.md.
175
+
176
+ 2. **Cost or performance spike** — If SIGNALS.json reports elevated token spend or cost anomalies:
177
+ ```bash
178
+ # Snapshot state at the anomaly time
179
+ bash tools/agent-forensics.sh <commit-at-spike-time>
180
+ ```
181
+ Check the BUILDLOG.md excerpt for cost-related entries, parallelism changes, or dispatch budget adjustments.
182
+
183
+ 3. **After merging a PR** — Before rolling to production, verify no unintended behavior changes were introduced:
184
+ ```bash
185
+ # Compare main...feature-branch
186
+ bash tools/agent-forensics.sh --diff main <feature-branch-head>
187
+ ```
188
+ If behavior-controlling files show unexpected changes, halt the merge and investigate.
189
+
190
+ **Output interpretation:**
191
+
192
+ - **CLAUDE.md or STATE.md changed** → review what rules or phase-state was altered
193
+ - **docs/CARDINAL-RULES.md modified** → check if dispatch budgets, retry caps, or cost levers were adjusted
194
+ - **hooks/ or monitor/CHARTER.md changed** → verify policy enforcement or signal thresholds are intentional
195
+ - **No changes in behavior-controlling files** → regression is likely in agent logic (non-behavior files) or external state
196
+
197
+ **Integration point:** The monitor/collect-signals.mjs can emit a PROPOSAL when it detects rule drift or state anomalies, prompting operators to run forensics on the linked commits to verify the change was intentional.
198
+
199
+ ## See Also
200
+
201
+ - **docs/CARDINAL-RULES.md** — The executable spec for agent behavior.
202
+ - **docs/DISPATCH-MODEL.md** — Cost levers, parallelism, watchdog stall detection.
203
+ - **docs/GOVERNANCE.md** — Memory/state single-writer guarantees, proposal flow.
204
+ - **README.md** — Quick start; includes examples of running forensics on a live incident.
@@ -1,6 +1,8 @@
1
1
  # Governance & Control
2
2
 
3
- This guide covers the operational patterns that keep orchestration systems coherent, scalable, and auditable at production scale: single-instance loops, single-writer files, asynchronous inbox coordination, log rotation, branching discipline, and security gates.
3
+ **TL;DR**: Keep the system coherent: one instance of each loop (heartbeat protocol), single writer per control file (MEMORY.md, STATE.md), append-only logs, secret-scan gate on every push, feature branches only (never direct-to-main). Prevents races, data loss, credential leaks, and makes work auditable.
4
+
5
+ ---
4
6
 
5
7
  ## Single-instance loops
6
8
 
@@ -41,7 +43,7 @@ Some files must have exactly one writer to prevent race conditions and data loss
41
43
 
42
44
  ## Inbox pattern for coordination
43
45
 
44
- Loops that need to request memory updates or signal decisions use an append-only inbox. The orchestrator reviews inbox entries on every `/power` cycle.
46
+ Loops that need to request memory updates or signal decisions use an append-only inbox. The orchestrator reviews inbox entries on every `/power` cycle. See [skills/power/SKILL.md](../skills/power/SKILL.md) for orchestrator priming details.
45
47
 
46
48
  ### Inbox format
47
49
 
@@ -0,0 +1,307 @@
1
+ # Git Pre-Push Hook Installation Guide
2
+
3
+ **Ship a hook, not a memo.** The pre-push policy hook is **auto-installed during scaffold** (see below). This guide explains customization, verification, and org-wide distribution.
4
+
5
+ ## Security Model: Local Convenience Defense Only
6
+
7
+ **IMPORTANT**: The pre-push hook is a **local-machine convenience defense** — it is **NOT cryptographic protection**. Any developer can bypass it with `git push --no-verify` or by editing/deleting `.git/hooks/pre-push`. The audit log (`SECURITY-AUDIT.log`) is stored locally and can also be edited by a user with file system access.
8
+
9
+ **Real enforcement requires server-side branch protection.** See GitHub Configuration below.
10
+
11
+ ## What the Hook Does
12
+
13
+ `hooks/pre-push-policy.sh` enforces two checks at git push time:
14
+
15
+ 1. **Branch Policy**: Blocks direct pushes to `main` or `master` branches (feature branches only).
16
+ 2. **Secret Scan**: Runs `tools/secret_scan.py --staged` to detect credentials before they reach the remote.
17
+
18
+ Both blocks append a JSON audit record to `state/SECURITY-AUDIT.log` with timestamp, repo, reason, and user — creating a reviewable trail of policy enforcement.
19
+
20
+ ## Auto-Installation During Scaffold
21
+
22
+ **Default Behavior:** When you scaffold a new aesop fleet with `npx @matt82198/aesop [target-dir]`, the CLI automatically installs the pre-push hook into `.git/hooks/pre-push`.
23
+
24
+ - **On Unix/macOS/Git Bash**: Creates a symlink so hook updates are automatic
25
+ - **On Windows**: Copies the hook directly (symlinks don't work reliably on all NTFS setups)
26
+ - **Idempotent**: Re-running scaffold doesn't clobber a user-customized hook
27
+ - **Preserve Existing**: If you have a different pre-push hook, scaffold warns and preserves it
28
+ - **Force Replace**: Use `npx @matt82198/aesop [target-dir] --force` to replace any existing hook
29
+
30
+ **Example:**
31
+
32
+ ```bash
33
+ # Initial scaffold (creates and installs hook automatically)
34
+ npx @matt82198/aesop my-fleet
35
+
36
+ # Later: re-scaffold the same directory (preserves customizations)
37
+ npx @matt82198/aesop my-fleet
38
+
39
+ # Force replace (even if hook was customized)
40
+ npx @matt82198/aesop my-fleet --force
41
+ ```
42
+
43
+ ## Manual Installation
44
+
45
+ If you're installing into an existing repo (not scaffolded), or you need to manually add the hook:
46
+
47
+ ### Option 1: Symlink (Linux / macOS / Git Bash on Windows)
48
+
49
+ The cleanest method — hook stays in sync with repo updates:
50
+
51
+ ```bash
52
+ ln -s ../../hooks/pre-push-policy.sh .git/hooks/pre-push
53
+ chmod +x .git/hooks/pre-push
54
+ ```
55
+
56
+ ### Option 2: Copy (Windows, or to break sync)
57
+
58
+ Copy the hook directly into `.git/hooks/`:
59
+
60
+ ```powershell
61
+ Copy-Item hooks\pre-push-policy.sh .git\hooks\pre-push
62
+ ```
63
+
64
+ On Windows PowerShell, mark it executable if your git respects file mode:
65
+
66
+ ```bash
67
+ git config core.filemode false
68
+ ```
69
+
70
+ ## Testing
71
+
72
+ Before committing to org-wide deployment, verify the hook works:
73
+
74
+ ```bash
75
+ bash hooks/pre-push-policy.sh --test
76
+ ```
77
+
78
+ Expected output: **PASS** for all three checks (branch policy, feature branch allowance, audit log format).
79
+
80
+ ## GitHub Configuration (Server-Side Enforcement)
81
+
82
+ To pair this local hook with real enforcement, enable branch protection on GitHub:
83
+
84
+ ### Step 1: Create a Protected Branch
85
+
86
+ 1. Go to your GitHub repo **Settings** > **Branches**.
87
+ 2. Click **Add rule** under "Branch protection rules".
88
+ 3. Enter branch name pattern: `main` (or `master`, depending on your default branch).
89
+
90
+ ### Step 2: Enable Required Protections
91
+
92
+ - **Require pull request reviews before merging**: ✓ (enforces PR review workflow)
93
+ - **Require status checks to pass before merging**: ✓ (use this for CI)
94
+ - **Require branches to be up to date before merging**: ✓
95
+ - **Restrict who can push to matching branches**: ✓ (optional; allows only admins to push to main)
96
+
97
+ ### Step 3: Dismiss Stale Reviews (Recommended)
98
+
99
+ - **Dismiss stale pull request approvals when new commits are pushed**: ✓
100
+
101
+ ### Example: Minimal Protected Branch Rule for `main`
102
+
103
+ | Setting | Value |
104
+ |---------|-------|
105
+ | Branch name pattern | `main` |
106
+ | Require pull request reviews | Yes (1 approval) |
107
+ | Require status checks to pass | Yes (if you have CI) |
108
+ | Require branches up to date | Yes |
109
+ | Restrict pushes to | Admins only |
110
+
111
+ Once this is configured, even if a developer bypasses the local hook with `--no-verify`, the remote will **refuse the push**.
112
+
113
+ ## Audit Log Format & Integrity
114
+
115
+ Each block writes one JSON line to `${AESOP_ROOT:-$HOME/aesop}/state/SECURITY-AUDIT.log`:
116
+
117
+ ```json
118
+ {"seq":1,"prev_hash":"GENESIS","ts":"2025-07-12T14:32:01Z","repo":"aesop","event":"push_blocked","reason":"secret_scan_failure","user":"alice"}
119
+ {"seq":2,"prev_hash":"f4b92becb47baa447e839330cf3c0c6e8dea947acc9ec372bb99063ee416d036","ts":"2025-07-12T14:32:02Z","repo":"aesop","event":"push_blocked","reason":"secret_scan_failure","user":"bob"}
120
+ ```
121
+
122
+ **Fields:**
123
+ - `seq`: Monotonically increasing sequence number (starts at 1). Enables detection of truncation or missing entries.
124
+ - `prev_hash`: SHA-256 hash of the previous line (without trailing newline). First entry uses `"GENESIS"`. Enables tampering detection.
125
+ - `ts`: ISO-8601 UTC timestamp
126
+ - `repo`: Repository basename
127
+ - `event`: Always `push_blocked` (or `secret_scan_unavailable`)
128
+ - `reason`: `push_to_protected_branch`, `secret_scan_failure`, or other block reason
129
+ - `user`: Git user.name (fallback: "unknown"); all special characters and control chars are JSON-escaped
130
+
131
+ **Write Safety & Concurrent Access:**
132
+ All audit log writes are protected by a file-system atomic lock (`.audit-log-lock/` directory). This ensures that concurrent pushes from different repositories (or simultaneous local pushes) do not corrupt the hash chain. The lock has a 300-second stale-lock recovery mechanism to handle crashed holders.
133
+
134
+ **Tail Hash Anchor:**
135
+ Each write also updates `${AESOP_ROOT:-$HOME/aesop}/state/.audit-tail-hash` with the SHA-256 hash of the newly appended line. This sidecar file acts as an anchor against tail truncation.
136
+
137
+ ### Verifying Audit Log Integrity
138
+
139
+ The hash chain and truncation anchor allow you to detect if someone edited, deleted, or truncated audit log entries:
140
+
141
+ ```bash
142
+ bash hooks/pre-push-policy.sh --verify-audit-log
143
+ ```
144
+
145
+ Output on intact log:
146
+ ```
147
+ Audit log verification OK (42 entries)
148
+ ```
149
+
150
+ Output if line tampering is detected:
151
+ ```
152
+ Error: Hash chain broken at line 15
153
+ Expected prev_hash: f4b92becb47baa447e839330cf3c0c6e8dea947acc9ec372bb99063ee416d036
154
+ Actual prev_hash: abc123...
155
+ ```
156
+
157
+ Output if tail truncation is detected:
158
+ ```
159
+ TRUNCATION SUSPECTED: Tail hash mismatch (stored: f4b..., actual: 8e6...)
160
+ ```
161
+
162
+ The truncation detection compares the stored tail hash (`state/.audit-tail-hash`) against the actual SHA-256 hash of the current log's last line. If someone deletes the last N lines, the hashes will not match.
163
+
164
+ **Note**: Verification is a convenience check; it does not prevent tampering on a machine where the attacker has file system access. For real auditability, centralize audit logs to a secure remote (e.g., CloudWatch, Datadog, or a separate immutable log server).
165
+
166
+ Parse the log with standard JSON tools:
167
+
168
+ ```bash
169
+ cat state/SECURITY-AUDIT.log | jq '.reason' | sort | uniq -c
170
+ ```
171
+
172
+ ## Org Distribution & Customization
173
+
174
+ ### Standard Distribution
175
+
176
+ 1. Commit the hook to a shared repo or policy repository.
177
+ 2. Instruct teams to symlink or copy per option above.
178
+ 3. Communicate audit log location and parsing examples.
179
+
180
+ ### Customizing Checks
181
+
182
+ To add org-specific rules (e.g., branch naming conventions, required CI status):
183
+
184
+ 1. Edit `hooks/pre-push-policy.sh` and add a new check function:
185
+ ```bash
186
+ check_my_org_policy() {
187
+ # your logic here
188
+ return 0 or 1
189
+ }
190
+ ```
191
+
192
+ 2. Call it from `main()` before exit, and log blocks:
193
+ ```bash
194
+ if ! check_my_org_policy; then
195
+ log_block "my_org_policy_violation"
196
+ exit 1
197
+ fi
198
+ ```
199
+
200
+ 3. Update `run_test_mode()` to include test cases for the new check.
201
+
202
+ 4. Commit and redistribute.
203
+
204
+ ### Disabling in CI/CD
205
+
206
+ If CI systems need to bypass the hook (e.g., for automated releases), set:
207
+
208
+ ```bash
209
+ export GIT_SKIP_HOOKS=pre-push
210
+ git push
211
+ ```
212
+
213
+ Or use `git push --no-verify` (explicitly allowing humans to opt out).
214
+
215
+ ## Audit Log Rotation
216
+
217
+ The audit log is append-only. For long-running deployments, consider log rotation:
218
+
219
+ ```bash
220
+ if [ -f state/SECURITY-AUDIT.log ] && [ $(stat -f%z state/SECURITY-AUDIT.log 2>/dev/null || stat -c%s state/SECURITY-AUDIT.log) -gt 1000000 ]; then
221
+ mv state/SECURITY-AUDIT.log "state/SECURITY-AUDIT.$(date +%Y%m%d-%H%M%S).log"
222
+ fi
223
+ ```
224
+
225
+ ## Troubleshooting
226
+
227
+ **Hook not running?**
228
+ - Verify `.git/hooks/pre-push` exists and is executable: `ls -la .git/hooks/pre-push`
229
+ - Check symlink target if using option 1: `readlink .git/hooks/pre-push`
230
+ - Git version must be 1.8.2+; run `git --version`
231
+
232
+ **"Push blocked" but no reason in audit log?**
233
+ - Check `${AESOP_ROOT:-$HOME/aesop}/state/` directory exists and is writable.
234
+ - Verify git config: `git config user.name` is not empty.
235
+
236
+ **Secret scan warns but doesn't block?**
237
+ - `secret_scan.py` must exist at `AESOP_ROOT/tools/secret_scan.py`.
238
+ - If missing, the hook warns but allows push (fail-open default).
239
+ - For fail-closed behavior, edit the hook and remove the early `return 0`.
240
+
241
+ ## References
242
+
243
+ - Hook source: `hooks/pre-push-policy.sh`
244
+ - Secret scanner: `tools/secret_scan.py`
245
+ - Audit log location: `state/SECURITY-AUDIT.log` (git-ignored)
246
+ - Cardinal rule: [CARDINAL-RULES.md](CARDINAL-RULES.md)
247
+
248
+ ## Claude Code hooks
249
+
250
+ Beyond git hooks, aesop ships policy for the agent harness itself.
251
+ `hooks/claude/force-model-policy.mjs` is a Claude Code **PreToolUse** hook that
252
+ enforces the "subagents are always Haiku" cardinal rule in code: every `Agent`
253
+ or `Task` dispatch whose `model` is absent or non-compliant is rewritten to the
254
+ policy model before the subagent launches. Versioned in git, it is org policy
255
+ you can review, diff, and test — not a memo agents can forget.
256
+
257
+ ### What it enforces
258
+
259
+ - **Model policy**: subagent dispatches run on `haiku` by default. If
260
+ `aesop.config.json` defines `cardinal_rules.subagent_model` (looked up in
261
+ `$AESOP_ROOT`, then the working directory), that model is enforced instead.
262
+ - **Rewrite, not block**: non-compliant dispatches are allowed through with
263
+ `model` rewritten via the hook output contract
264
+ (`hookSpecificOutput.updatedInput`), so work proceeds at the right cost tier.
265
+ - **Fail-open reliability**: malformed input produces no output and exit 0.
266
+ The hook never crashes the harness and never logs payload contents.
267
+
268
+ ### Registration (settings.json)
269
+
270
+ Add to `.claude/settings.json` in the project (or `~/.claude/settings.json`
271
+ for user-wide enforcement):
272
+
273
+ ```json
274
+ {
275
+ "hooks": {
276
+ "PreToolUse": [
277
+ {
278
+ "matcher": "Agent|Task",
279
+ "hooks": [
280
+ {
281
+ "type": "command",
282
+ "command": "node \"$CLAUDE_PROJECT_DIR/hooks/claude/force-model-policy.mjs\""
283
+ }
284
+ ]
285
+ }
286
+ ]
287
+ }
288
+ }
289
+ ```
290
+
291
+ If the hook lives outside the project (e.g., a shared policy checkout), use an
292
+ absolute path to the `.mjs` file instead of `$CLAUDE_PROJECT_DIR`.
293
+
294
+ ### Escape hatch
295
+
296
+ For dispatches that genuinely need a bigger model, include the literal marker
297
+ `[[ALLOW-NON-HAIKU]]` anywhere in the subagent prompt. The hook passes the
298
+ dispatch through untouched — and because the marker sits in the prompt, every
299
+ opt-out is visible in the transcript and auditable after the fact.
300
+
301
+ ### Testing
302
+
303
+ ```bash
304
+ node --test tests/force-model-policy.test.mjs
305
+ ```
306
+
307
+ Covers the rewrite, escape-hatch, malformed-stdin, and config-override cases.