@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,434 @@
1
+ #!/usr/bin/env node
2
+ // strip-tools-hook.mjs — SPIKE PROTOTYPE (wave 11, NOT wired into live settings).
3
+ //
4
+ // PreToolUse hook enforcing the tiered cognition/execution architecture:
5
+ // Haiku is the ONLY tier that reads/writes/executes; Sonnet/Opus/Fable are
6
+ // pure cognition (reason + message + dispatch). Two enforcement layers in
7
+ // one script (selected by payload.tool_name):
8
+ //
9
+ // LAYER 1 — dispatch guard (matcher "Agent|Task"):
10
+ // A subagent dispatch resolving to a cognition-tier model (sonnet/opus/
11
+ // fable) is rewritten to the `aesop-cognition` shim agent type, whose
12
+ // definition frontmatter allowlists ONLY cognition tools
13
+ // (Agent, SendMessage, TaskStop, Monitor). The original requested type is
14
+ // preserved in a COGNITION CONTRACT header prepended to the prompt.
15
+ // NOTE: the Agent tool input schema has NO `tools` field
16
+ // (additionalProperties: false), so per-dispatch tool lists CANNOT be set
17
+ // via updatedInput — the agent-definition `tools:` frontmatter is the only
18
+ // supported stripping mechanism today. Hence the shim rewrite.
19
+ //
20
+ // LAYER 2 — call-time backstop (matcher on the denied tools below):
21
+ // When an I/O/exec tool call fires inside a session whose transcript shows
22
+ // a non-haiku model, the call is DENIED with a reason that redirects the
23
+ // agent to emit a WORK-ORDER v1 and dispatch a Haiku executor instead
24
+ // (see DESIGN.md §3). Haiku sessions pass untouched.
25
+ //
26
+ // Escape hatch: [[ALLOW-TIER-TOOLS]] in the dispatch prompt keeps the agent's
27
+ // tools ([[ALLOW-NON-HAIKU]] is honored too during migration — today's
28
+ // escalations are hands-on missions). Never silent: every use is announced via
29
+ // permissionDecisionReason and appended as a JSON line to
30
+ // ${AESOP_ROOT:-~/aesop}/state/TIER-POLICY-ESCAPES.log.
31
+ //
32
+ // Reliability: fail-open everywhere (malformed payload / missing transcript /
33
+ // unreadable agents dir => no opinion, pass). stdin raced against a 2s timer.
34
+ // See FINDINGS.md for the fail-open-vs-fail-closed discussion.
35
+ //
36
+ // Self-test: `node strip-tools-hook.mjs --self-test` (no stdin needed; builds
37
+ // its own fixtures in a temp dir, exits 0 on PASS / 1 on FAIL).
38
+
39
+ import fs from 'node:fs';
40
+ import os from 'node:os';
41
+ import path from 'node:path';
42
+
43
+ // ---------------------------------------------------------------- constants
44
+
45
+ export const ESCAPE_TOKENS = ['[[ALLOW-TIER-TOOLS]]', '[[ALLOW-NON-HAIKU]]'];
46
+ export const SHIM_AGENT = 'aesop-cognition';
47
+ export const ESCAPE_LOG_NAME = 'TIER-POLICY-ESCAPES.log';
48
+ const STDIN_TIMEOUT_MS = 2000;
49
+
50
+ // Cognition tiers KEEP exactly these (reason + message + dispatch):
51
+ export const KEEP_TOOLS = ['Agent', 'SendMessage', 'TaskStop', 'Monitor'];
52
+
53
+ // Cognition tiers are DENIED all I/O + exec + retrieval (Haiku's job):
54
+ export const DENY_TOOLS = [
55
+ 'Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit',
56
+ 'Bash', 'PowerShell', 'BashOutput', 'KillShell',
57
+ 'Glob', 'Grep', 'WebFetch', 'WebSearch',
58
+ 'Skill', 'Artifact', 'ToolSearch', 'EnterWorktree', 'ExitWorktree',
59
+ ];
60
+
61
+ const DISPATCH_TOOLS = new Set(['Agent', 'Task']);
62
+ const DENY_SET = new Set(DENY_TOOLS);
63
+
64
+ // --------------------------------------------------------------- tier logic
65
+
66
+ /** Map a model string to a tier: 'haiku' | 'cognition' | null (unknown). */
67
+ export function tierOf(model) {
68
+ const m = String(model || '').toLowerCase();
69
+ if (!m) return null;
70
+ if (m.includes('haiku')) return 'haiku';
71
+ if (m.includes('sonnet') || m.includes('opus') || m.includes('fable')) return 'cognition';
72
+ return null; // unrecognized model string — no opinion
73
+ }
74
+
75
+ /** Resolve tier from an installed agent definition's `model:` frontmatter
76
+ * (matches basename or `name:` field). Returns tier or null. */
77
+ export function tierFromAgentDef(type, agentsDir) {
78
+ if (!type) return null;
79
+ let files = [];
80
+ try { files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')); } catch { return null; }
81
+ for (const f of files) {
82
+ let head = '';
83
+ try { head = fs.readFileSync(path.join(agentsDir, f), 'utf8').slice(0, 600); } catch { continue; }
84
+ const base = f.replace(/\.md$/, '').toLowerCase();
85
+ let match = base === type;
86
+ if (!match) {
87
+ const nm = head.match(/^name:\s*(.+)$/m);
88
+ match = !!nm && nm[1].trim().toLowerCase() === type;
89
+ }
90
+ if (match) {
91
+ const mm = head.match(/^model:\s*(.+)$/m);
92
+ return mm ? tierOf(mm[1].trim()) : null;
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+
98
+ // -------------------------------------------------------------- audit trail
99
+
100
+ function stateDir(opts) {
101
+ const root = (opts && opts.stateRoot) || process.env.AESOP_ROOT || path.join(os.homedir(), 'aesop');
102
+ return path.join(root, 'state');
103
+ }
104
+
105
+ /** Best-effort JSON-line audit record for escape-hatch use; never blocks. */
106
+ function logEscapeUse(payload, opts) {
107
+ try {
108
+ const dir = stateDir(opts);
109
+ fs.mkdirSync(dir, { recursive: true });
110
+ const input = payload.tool_input || {};
111
+ const rec = {
112
+ ts: new Date().toISOString(),
113
+ event: 'tier_policy_escape',
114
+ tool: payload.tool_name,
115
+ session_id: typeof payload.session_id === 'string' ? payload.session_id : null,
116
+ cwd: typeof payload.cwd === 'string' ? payload.cwd : null,
117
+ description: typeof input.description === 'string' ? input.description : null,
118
+ requested_model: typeof input.model === 'string' ? input.model : null,
119
+ prompt_head: typeof input.prompt === 'string' ? input.prompt.slice(0, 200) : null,
120
+ };
121
+ fs.appendFileSync(path.join(dir, ESCAPE_LOG_NAME), JSON.stringify(rec) + '\n');
122
+ } catch { /* audit is best-effort */ }
123
+ }
124
+
125
+ // ------------------------------------------------------- transcript sniffer
126
+
127
+ /** Read a Claude Code transcript (JSONL) and return { tier, escaped }.
128
+ * tier: from the LAST assistant entry carrying message.model.
129
+ * escaped: true if any user entry contains an escape token (the subagent's
130
+ * dispatch prompt is the first user message of its transcript).
131
+ * Fail-open: unreadable/unparseable => { tier: null, escaped: false }. */
132
+ export function sniffTranscript(transcriptPath) {
133
+ const out = { tier: null, escaped: false };
134
+ let raw = '';
135
+ try { raw = fs.readFileSync(transcriptPath, 'utf8'); } catch { return out; }
136
+ for (const line of raw.split('\n')) {
137
+ if (!line.trim()) continue;
138
+ let j;
139
+ try { j = JSON.parse(line); } catch { continue; }
140
+ const msg = j && j.message;
141
+ if (!msg || typeof msg !== 'object') continue;
142
+ if ((j.type === 'assistant' || msg.role === 'assistant') && typeof msg.model === 'string') {
143
+ const t = tierOf(msg.model);
144
+ if (t) out.tier = t; // keep the LAST seen — model can change mid-session
145
+ }
146
+ if (j.type === 'user' || msg.role === 'user') {
147
+ const text = typeof msg.content === 'string'
148
+ ? msg.content
149
+ : Array.isArray(msg.content)
150
+ ? msg.content.map(c => (c && typeof c.text === 'string') ? c.text : '').join('\n')
151
+ : '';
152
+ if (ESCAPE_TOKENS.some(tok => text.includes(tok))) out.escaped = true;
153
+ }
154
+ }
155
+ return out;
156
+ }
157
+
158
+ // ------------------------------------------------------------ contract text
159
+
160
+ export function contractHeader(originalType, tier) {
161
+ return (
162
+ `[COGNITION CONTRACT v1] You are a COGNITION-tier agent` +
163
+ (originalType ? ` acting in the role of '${originalType}'` : '') +
164
+ ` (tier: ${tier}). You have NO file/exec/retrieval tools. Do not attempt ` +
165
+ `Read/Write/Edit/Bash/Glob/Grep — they are stripped. Produce your ` +
166
+ `technical output as a WORK-ORDER v1 (unified diff + command list + ` +
167
+ `verify steps; schema in docs/spikes/tiered-cognition/DESIGN.md) and ` +
168
+ `dispatch a Haiku executor agent to apply it, or return the work-order ` +
169
+ `to your caller. All facts you need must arrive via your prompt or via ` +
170
+ `Haiku courier briefs you dispatch.\n\n`
171
+ );
172
+ }
173
+
174
+ // -------------------------------------------------------------- core policy
175
+
176
+ /**
177
+ * Pure decision function (testable). payload = parsed hook stdin JSON.
178
+ * opts = { agentsDir, stateRoot } (injectable for tests).
179
+ * Returns { action, output } where output is the object to write to stdout
180
+ * (or null for "no opinion").
181
+ * action: 'pass' | 'strip' | 'escape' | 'deny' | 'allow'
182
+ */
183
+ export function decide(payload, opts = {}) {
184
+ const pass = { action: 'pass', output: null };
185
+ if (!payload || typeof payload !== 'object') return pass;
186
+ const toolName = payload.tool_name;
187
+
188
+ // ---- LAYER 1: dispatch guard --------------------------------------
189
+ if (DISPATCH_TOOLS.has(toolName)) {
190
+ const input = payload.tool_input;
191
+ if (!input || typeof input !== 'object') return pass; // fail-open
192
+ const prompt = String(input.prompt || '');
193
+ const type = String(input.subagent_type || input.agentType || '').toLowerCase();
194
+ if (type === 'fork') return pass; // forks inherit parent context/model
195
+
196
+ const agentsDir = opts.agentsDir || path.join(os.homedir(), '.claude', 'agents');
197
+ // Tier resolution order: explicit model > agent-def frontmatter > default
198
+ // haiku (the model-policy hook lands every other dispatch on haiku).
199
+ const tier = tierOf(input.model) || tierFromAgentDef(type, agentsDir) || 'haiku';
200
+ if (tier === 'haiku') return pass;
201
+
202
+ if (ESCAPE_TOKENS.some(tok => prompt.includes(tok))) {
203
+ logEscapeUse(payload, opts);
204
+ return {
205
+ action: 'escape',
206
+ output: {
207
+ systemMessage: `⛓ tier policy BYPASSED via escape token — recorded in state/${ESCAPE_LOG_NAME}`,
208
+ hookSpecificOutput: {
209
+ hookEventName: 'PreToolUse',
210
+ permissionDecision: 'allow',
211
+ permissionDecisionReason:
212
+ `Tier policy BYPASSED via escape token: this ${toolName} dispatch ` +
213
+ `keeps its full toolset on model "${input.model || '(default)'}". ` +
214
+ `Use recorded in state/${ESCAPE_LOG_NAME}.`,
215
+ },
216
+ },
217
+ };
218
+ }
219
+
220
+ return {
221
+ action: 'strip',
222
+ strippedTools: DENY_TOOLS,
223
+ keptTools: KEEP_TOOLS,
224
+ output: {
225
+ systemMessage:
226
+ `⛓ cognition-tier dispatch (${tier}) → tools stripped via '${SHIM_AGENT}' shim ` +
227
+ `(kept: ${KEEP_TOOLS.join(', ')})`,
228
+ suppressOutput: true,
229
+ hookSpecificOutput: {
230
+ hookEventName: 'PreToolUse',
231
+ permissionDecision: 'allow',
232
+ permissionDecisionReason:
233
+ 'Tier policy: cognition tiers (sonnet/opus/fable) are message-only; ' +
234
+ 'Haiku executors perform all I/O ([[ALLOW-TIER-TOOLS]] to override).',
235
+ updatedInput: {
236
+ ...input,
237
+ subagent_type: SHIM_AGENT,
238
+ prompt: contractHeader(type, tier) + prompt,
239
+ },
240
+ },
241
+ },
242
+ };
243
+ }
244
+
245
+ // ---- LAYER 2: call-time backstop ----------------------------------
246
+ if (DENY_SET.has(toolName)) {
247
+ const t = payload.transcript_path;
248
+ if (!t || typeof t !== 'string') return pass; // fail-open
249
+ const { tier, escaped } = sniffTranscript(t);
250
+ if (tier === null) return pass; // unreadable/unknown transcript: fail-open, no opinion
251
+ if (tier === 'haiku' || escaped) {
252
+ return { action: 'allow', output: null }; // haiku substrate / escaped session
253
+ }
254
+ return {
255
+ action: 'deny',
256
+ output: {
257
+ hookSpecificOutput: {
258
+ hookEventName: 'PreToolUse',
259
+ permissionDecision: 'deny',
260
+ permissionDecisionReason:
261
+ `Tier policy: this session runs on a cognition-tier model, which never ` +
262
+ `touches files/exec directly. Do NOT retry ${toolName}. Instead emit a ` +
263
+ `WORK-ORDER v1 (unified diff / command list / verify steps — schema in ` +
264
+ `docs/spikes/tiered-cognition/DESIGN.md) and dispatch a Haiku executor ` +
265
+ `agent to apply it and report back an EXEC-BRIEF.`,
266
+ },
267
+ },
268
+ };
269
+ }
270
+
271
+ return pass; // tool not governed
272
+ }
273
+
274
+ // ----------------------------------------------------------------- plumbing
275
+
276
+ function readStdin(timeoutMs = STDIN_TIMEOUT_MS) {
277
+ return new Promise((resolve) => {
278
+ let settled = false;
279
+ const finish = (v) => { if (!settled) { settled = true; clearTimeout(timer); resolve(v); } };
280
+ const timer = setTimeout(() => finish(''), timeoutMs);
281
+ let data = '';
282
+ process.stdin.setEncoding('utf8');
283
+ process.stdin.on('data', (c) => { data += c; });
284
+ process.stdin.on('end', () => finish(data));
285
+ process.stdin.on('error', () => finish(''));
286
+ });
287
+ }
288
+
289
+ async function runAsHook() {
290
+ const raw = await readStdin();
291
+ let payload = null;
292
+ try { payload = JSON.parse(raw); } catch { /* fail-open */ }
293
+ const { output } = decide(payload);
294
+ if (output) process.stdout.write(JSON.stringify(output) + '\n');
295
+ process.exit(0);
296
+ }
297
+
298
+ // ----------------------------------------------------------------- selftest
299
+
300
+ function selfTest() {
301
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tiercog-'));
302
+ const agentsDir = path.join(tmp, 'agents');
303
+ fs.mkdirSync(agentsDir);
304
+ fs.writeFileSync(path.join(agentsDir, 'typescript-pro.md'),
305
+ '---\nname: typescript-pro\ndescription: test fixture\nmodel: claude-sonnet-5\n---\nbody\n');
306
+ fs.writeFileSync(path.join(agentsDir, 'hooktest-haiku.md'),
307
+ '---\nname: hooktest-haiku\ndescription: test fixture\nmodel: haiku\n---\nbody\n');
308
+
309
+ const mkTranscript = (name, lines) => {
310
+ const p = path.join(tmp, name);
311
+ fs.writeFileSync(p, lines.map(l => JSON.stringify(l)).join('\n') + '\n');
312
+ return p;
313
+ };
314
+ const sonnetTx = mkTranscript('sonnet.jsonl', [
315
+ { type: 'user', message: { role: 'user', content: 'implement the parser' } },
316
+ { type: 'assistant', message: { role: 'assistant', model: 'claude-sonnet-5', content: [] } },
317
+ ]);
318
+ const haikuTx = mkTranscript('haiku.jsonl', [
319
+ { type: 'user', message: { role: 'user', content: 'apply this work-order' } },
320
+ { type: 'assistant', message: { role: 'assistant', model: 'claude-haiku-4-5', content: [] } },
321
+ ]);
322
+ const escapedTx = mkTranscript('escaped.jsonl', [
323
+ { type: 'user', message: { role: 'user', content: [{ type: 'text', text: '[[ALLOW-NON-HAIKU]] mission spike, hands-on' }] } },
324
+ { type: 'assistant', message: { role: 'assistant', model: 'claude-fable-5', content: [] } },
325
+ ]);
326
+
327
+ const opts = { agentsDir, stateRoot: tmp };
328
+ const dispatch = (input) => ({ tool_name: 'Agent', tool_input: input, session_id: 's1', cwd: tmp });
329
+ const call = (tool, tx) => ({ tool_name: tool, tool_input: { file_path: 'x' }, transcript_path: tx });
330
+
331
+ let failures = 0;
332
+ const check = (name, cond, detail) => {
333
+ console.log(` ${cond ? 'PASS' : 'FAIL'} ${name}${cond ? '' : ' <-- ' + (detail || '')}`);
334
+ if (!cond) failures++;
335
+ };
336
+
337
+ console.log('LAYER 1 — dispatch guard');
338
+ // 1. sonnet dispatch -> stripped via shim
339
+ let r = decide(dispatch({ model: 'sonnet', prompt: 'design the schema', description: 'd' }), opts);
340
+ check('sonnet dispatch is stripped', r.action === 'strip', `action=${r.action}`);
341
+ const ui = r.output && r.output.hookSpecificOutput.updatedInput;
342
+ check(' rewritten to shim agent type', !!ui && ui.subagent_type === SHIM_AGENT, JSON.stringify(ui));
343
+ check(' contract header prepended', !!ui && ui.prompt.startsWith('[COGNITION CONTRACT v1]'));
344
+ check(' original prompt preserved', !!ui && ui.prompt.endsWith('design the schema'));
345
+ check(' all I/O+exec tools in stripped set',
346
+ ['Read', 'Write', 'Edit', 'Bash', 'PowerShell', 'Glob', 'Grep', 'NotebookEdit', 'WebFetch']
347
+ .every(t => r.strippedTools.includes(t)));
348
+ check(' messaging/dispatch tools kept',
349
+ ['Agent', 'SendMessage'].every(t => r.keptTools.includes(t)) &&
350
+ r.keptTools.every(t => !r.strippedTools.includes(t)));
351
+
352
+ // 2. opus + fable dispatches -> stripped
353
+ r = decide(dispatch({ model: 'opus', prompt: 'p' }), opts);
354
+ check('opus dispatch is stripped', r.action === 'strip', `action=${r.action}`);
355
+ r = decide(dispatch({ model: 'fable', prompt: 'p' }), opts);
356
+ check('fable dispatch is stripped', r.action === 'strip', `action=${r.action}`);
357
+
358
+ // 3. haiku dispatch -> untouched
359
+ r = decide(dispatch({ model: 'haiku', prompt: 'apply the patch' }), opts);
360
+ check('haiku dispatch passes untouched', r.action === 'pass' && r.output === null, `action=${r.action}`);
361
+
362
+ // 4. blank model, generic type -> defaults haiku (model-policy hook lands it there) -> untouched
363
+ r = decide(dispatch({ prompt: 'p', subagent_type: 'general-purpose' }), opts);
364
+ check('blank-model generic dispatch passes (defaults haiku)', r.action === 'pass', `action=${r.action}`);
365
+
366
+ // 5. blank model, specialist type with sonnet frontmatter -> stripped
367
+ r = decide(dispatch({ prompt: 'p', subagent_type: 'typescript-pro' }), opts);
368
+ check('blank-model sonnet specialist is stripped', r.action === 'strip', `action=${r.action}`);
369
+
370
+ // 6. blank model, haiku-frontmatter agent -> untouched
371
+ r = decide(dispatch({ prompt: 'p', subagent_type: 'hooktest-haiku' }), opts);
372
+ check('haiku-frontmatter agent passes', r.action === 'pass', `action=${r.action}`);
373
+
374
+ // 7. escape-token dispatch -> allowed + audited
375
+ r = decide(dispatch({ model: 'sonnet', prompt: '[[ALLOW-TIER-TOOLS]] hands-on mission' }), opts);
376
+ check('escape-token dispatch keeps tools', r.action === 'escape', `action=${r.action}`);
377
+ check(' no updatedInput on escape', !r.output.hookSpecificOutput.updatedInput);
378
+ const logPath = path.join(tmp, 'state', ESCAPE_LOG_NAME);
379
+ let audited = false;
380
+ try { audited = fs.readFileSync(logPath, 'utf8').includes('tier_policy_escape'); } catch { }
381
+ check(' escape use audited to state log', audited, logPath);
382
+
383
+ // 8. legacy [[ALLOW-NON-HAIKU]] also escapes (migration grandfathering)
384
+ r = decide(dispatch({ model: 'opus', prompt: '[[ALLOW-NON-HAIKU]] mission' }), opts);
385
+ check('legacy ALLOW-NON-HAIKU token also escapes', r.action === 'escape', `action=${r.action}`);
386
+
387
+ // 9. fork passes untouched
388
+ r = decide(dispatch({ model: 'sonnet', prompt: 'p', subagent_type: 'fork' }), opts);
389
+ check('fork dispatch passes untouched', r.action === 'pass', `action=${r.action}`);
390
+
391
+ // 10. malformed payloads fail open
392
+ check('malformed payload fails open', decide(null, opts).action === 'pass');
393
+ check('missing tool_input fails open', decide({ tool_name: 'Agent' }, opts).action === 'pass');
394
+
395
+ console.log('LAYER 2 — call-time backstop');
396
+ // 11. Write/Bash under a sonnet transcript -> denied with redirect reason
397
+ r = decide(call('Write', sonnetTx), opts);
398
+ check('Write under sonnet session is DENIED', r.action === 'deny', `action=${r.action}`);
399
+ check(' deny reason redirects to WORK-ORDER + Haiku executor',
400
+ r.action === 'deny' && /WORK-ORDER/.test(r.output.hookSpecificOutput.permissionDecisionReason) &&
401
+ r.output.hookSpecificOutput.permissionDecision === 'deny');
402
+ r = decide(call('Bash', sonnetTx), opts);
403
+ check('Bash under sonnet session is DENIED', r.action === 'deny', `action=${r.action}`);
404
+
405
+ // 12. same tools under a haiku transcript -> allowed
406
+ r = decide(call('Write', haikuTx), opts);
407
+ check('Write under haiku session is allowed', r.action === 'allow', `action=${r.action}`);
408
+ r = decide(call('Bash', haikuTx), opts);
409
+ check('Bash under haiku session is allowed', r.action === 'allow', `action=${r.action}`);
410
+
411
+ // 13. escaped fable session (this spike!) keeps its tools
412
+ r = decide(call('Edit', escapedTx), opts);
413
+ check('escape-token session keeps I/O tools', r.action === 'allow', `action=${r.action}`);
414
+
415
+ // 14. missing/unreadable transcript fails open
416
+ r = decide(call('Write', path.join(tmp, 'nope.jsonl')), opts);
417
+ check('missing transcript fails open (pass)', r.action === 'pass', `action=${r.action}`);
418
+
419
+ // 15. ungoverned tool passes
420
+ r = decide({ tool_name: 'SendMessage', tool_input: {}, transcript_path: sonnetTx }, opts);
421
+ check('ungoverned tool (SendMessage) passes', r.action === 'pass', `action=${r.action}`);
422
+
423
+ console.log(failures === 0 ? '\nSELF-TEST: ALL PASS' : `\nSELF-TEST: ${failures} FAILURE(S)`);
424
+ try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { }
425
+ process.exit(failures === 0 ? 0 : 1);
426
+ }
427
+
428
+ // ----------------------------------------------------------------- dispatch
429
+
430
+ if (process.argv.includes('--self-test')) {
431
+ selfTest();
432
+ } else {
433
+ runAsHook();
434
+ }
@@ -0,0 +1,61 @@
1
+ # Case Study: Portfolio Site Build with Aesop
2
+
3
+ ## What Happened (Timeline)
4
+
5
+ | Time | Event | Duration |
6
+ |------|-------|----------|
7
+ | 17:09 | User prompt: "build me a portfolio site" | — |
8
+ | 17:09–17:20 | 6 discovery scouts (GitHub API, Medium, LinkedIn, Reddit ×2, Playwright fallback, repo deep-dive) run in parallel; 4 ranked decision points surfaced | 11 min |
9
+ | 17:20–17:24 | User answers 4 prioritization questions | 4 min |
10
+ | 17:24–17:36 | 10 Haiku agents (scaffold + 8 section builders on file-disjoint Astro components + integrator) execute | 12 min |
11
+ | 17:36–17:37 | 9th section added from late Reddit data; Astro build green in 914ms | 1 min |
12
+ | 17:37–17:40 | Playwright screenshot proof round 1: CSS 404 detected (styles linked as `/src/` instead of `/dist/`) | 3 min |
13
+ | 17:40–17:42 | Repair agent fixes CSS paths; proof round 2 validates fix | 2 min |
14
+ | 17:42–17:48 | GitHub agent: secret-scan (10,066 files clean), create public repo, merge PR #1 | 6 min |
15
+ | 18:04 | GitHub Pages deployed and live at https://matt82198.github.io/ (verified 200 status) | **55 min total** |
16
+
17
+ ## The Architecture
18
+
19
+ **Parallel discovery with verified facts only**: Six independent scouts (HTTP fallback to Playwright for bot-blocked sites) collected portfolio inspiration, ranked by relevance; each returned structured data on a single contract. No hallucination—every section claim traces to a source.
20
+
21
+ **File-disjoint fan-out**: 8 section agents each owned one `.astro` component with zero file overlap; no merge conflicts, no serialization bottleneck. Integrator assembled in 12 minutes.
22
+
23
+ **Proof-driven QA**: Screenshot verification caught a real ship-blocker—styles in dist/ referenced `/src/` paths that vanished at build time. No visual inspection could have found this; Playwright proved it was broken, repair proved it was fixed.
24
+
25
+ **Gated ship**: Secret-scan gate (10,066 files) ran before public repo creation; zero credentials leaked. PR merged, Pages deploy triggered, liveness confirmed.
26
+
27
+ **Orchestrator final-catch**: Fable verified theme coherence across 9 sections after assembly; caught one color inconsistency and one missing accent variant before ship.
28
+
29
+ ## What Failed (and Was Caught)
30
+
31
+ The site rendered unstyled in production. Astro build succeeded locally (914ms), but the distributed `.astro` component agents—writing styles inline—used `import` statements pointing to source paths. After minification and bundling into `/dist/`, those paths no longer existed. The site shipped with no CSS.
32
+
33
+ Playwright screenshot automation caught this in production-like conditions (rendering against the compiled bundle). A repair agent updated path resolution, regenerated the build, and Playwright proved the fix before merge. This is not a testing infrastructure story—this is proof automation as a first-class gate.
34
+
35
+ ## Numbers
36
+
37
+ - **Wall-clock time**: 55 minutes (first prompt to live site)
38
+ - **Total agents used**: ~30 (6 scouts, 10 builders, 1 repair, 1 GitHub ops, 3 proof/integration, 9 audits)
39
+ - **Build fleet**: 10 Haiku agents (scaffold + 8 section builders + integrator)
40
+ - **Build time**: 12 minutes (including inter-agent coordination)
41
+ - **Sections delivered**: 9
42
+ - **Proof rounds**: 2 (CSS 404 caught in round 1; fix validated in round 2)
43
+ - **Ship-blockers caught**: 1 (CSS 404)
44
+ - **Files secret-scanned**: 10,066 (0 issues)
45
+ - **Subagent tokens**: ~328K
46
+ - **PR**: #1, merged
47
+ - **Deploy**: GitHub Pages, live 200 status
48
+
49
+ ## How to Reproduce
50
+
51
+ 1. Fork this repo or set up Aesop in your environment.
52
+ 2. Run the [buildsystem skill](/skills/CLAUDE.md) for a portfolio project.
53
+ 3. Provide 4–6 source URLs or inspiration topics; Aesop will dispatch 6 discovery scouts.
54
+ 4. Approve the ranked backlog (user decision round).
55
+ 5. Aesop dispatches the 10-agent build workflow in parallel on Astro or your framework.
56
+ 6. Screenshot proof runs automatically; Aesop fixes any visual defects and re-proves.
57
+ 7. Secret-scan gate runs; if clean, Aesop creates the public repo and merges the PR.
58
+ 8. Pages (or your host) deploys automatically.
59
+
60
+ The 55-minute bound is achievable when discovery sources are web-accessible (not gated PDFs) and the component schema is well-defined (Astro's `.astro` files are ideal; monolithic templates require serialization).
61
+
@@ -0,0 +1,11 @@
1
+ {
2
+ "_source": "Populated by orchestrator at wave close via fleet telemetry aggregation",
3
+ "_updated": "Awaiting orchestrator population",
4
+ "total_sessions": null,
5
+ "total_turns": null,
6
+ "total_user_prompts": null,
7
+ "max_tokens_single_turn": null,
8
+ "cumulative_agent_runs": null,
9
+ "cumulative_tokens": null,
10
+ "total_coding_hours": null
11
+ }
@@ -0,0 +1,27 @@
1
+ # Fleet-Ops Analysis Summary — Cycle N
2
+
3
+ **Generated:** YYYY-MM-DDTHH:MM:SSZ
4
+
5
+ ## Top Failure Modes (cumulative this session)
6
+
7
+ | Category | Count | Severity | Notes |
8
+ |----------|-------|----------|-------|
9
+ | [FAILURE_MODE_NAME] | 0 | [HIGH/MED/LOW] | Brief description of failure pattern, root cause hypothesis, and reference to any related recommendations or PRs. |
10
+ | | | | |
11
+ | | | | |
12
+
13
+ ## Token Spend Summary
14
+
15
+ Brief summary of session token spend patterns:
16
+ - [Agent/component type]: ~XXXk tokens — brief observation
17
+ - Heavy [category]: XXXk — specific tasks or drivers
18
+ - Typical [operation type]: XXk-XXXk; [context or pattern]
19
+
20
+ Include observations about:
21
+ - Which domains consumed the most tokens
22
+ - Whether spend is aligned with task scope
23
+ - Any unexpected patterns or efficiency issues
24
+
25
+ ## Notes
26
+
27
+ (Optional section for additional context, anomalies, or deferred investigations)
@@ -0,0 +1,24 @@
1
+ # Fleet-Ops Recommendations — machinery fixes for wave open
2
+
3
+ Rolling, append-only list maintained by the orchestration/fleet-ops monitor.
4
+ The buildsystem skill reads this at every wave open (Phase 0.3). Mark items
5
+ ADOPTED/REJECTED/IN-PROGRESS with wave number when actioned; do not delete rows.
6
+
7
+ | # | Date | Source | Severity | Recommendation | Status |
8
+ |---|------|--------|----------|----------------|--------|
9
+ | 1 | YYYY-MM-DD | [source/agent/session phase] | [HIGH/MED/LOW] | Clear, actionable recommendation describing the problem, suggested fix, and rationale. Include specific file paths or component names where applicable. | OPEN |
10
+ | 2 | YYYY-MM-DD | [source/agent/session phase] | [HIGH/MED/LOW] | Second example recommendation with similar detail level. Link to related findings if applicable. | OPEN |
11
+ | | | | | | |
12
+
13
+ ## How to Use This Template
14
+
15
+ 1. **Record findings at wave close:** The orchestration monitor appends new rows for every machinery issue or systemic improvement identified.
16
+ 2. **Mark status transitions:** When a recommendation is adopted (wave-N or PR reference), update Status to `ADOPTED wave-N`. For rejected items, mark `REJECTED wave-N` with brief rationale.
17
+ 3. **Track implementation:** Use Status column to indicate OPEN (unactioned), IN-PROGRESS (wave-X, assigned to agent/owner), ADOPTED (implemented, wave-Y), or REJECTED (not pursued, wave-Y).
18
+ 4. **Preserve history:** Never delete rows — the log is append-only. This creates an auditable trail for machinery decisions.
19
+
20
+ ## Notes
21
+
22
+ - **Source field:** Where did this insight come from? (session coordinator, CI failure, agent forensics, monitor signal, PR review, etc.)
23
+ - **Severity:** HIGH = blocks reliable operation or impacts multiple waves; MED = worth addressing in near term; LOW = nice-to-have optimization.
24
+ - **Implementation scope:** Recommendations should be specific enough for a developer to act on, but can span multiple waves if complex.
@@ -0,0 +1,64 @@
1
+ # Orchestration Refinement Proposals — Action Log
2
+
3
+ Actioned proposals moved from PROPOSALS.md (grounded in signals from YYYY-MM-DD). Per standing order, move decided entries here with date and outcome.
4
+
5
+ ---
6
+
7
+ ## ACCEPTED-IMPLEMENTED — YYYY-MM-DD
8
+
9
+ ### 1. [Proposal Title]
10
+
11
+ **Outcome:** [Brief description of what was implemented, where code/docs changed, wave number if applicable, and verification of completion.]
12
+
13
+ ---
14
+
15
+ ## IN-PROGRESS — YYYY-MM-DD
16
+
17
+ ### 1. [Proposal Title]
18
+
19
+ **Status:** IN-PROGRESS (wave-N, [assigned-agent or owner])
20
+ **Rationale:** [Why this proposal was accepted and what timeline/dependencies exist.]
21
+
22
+ ---
23
+
24
+ ## DEFERRED — YYYY-MM-DD
25
+
26
+ ### 1. [Proposal Title]
27
+
28
+ **Status:** DEFERRED ([reason: blocked-by/low-priority/waiting-for-data])
29
+ **Rationale:** [Why this proposal was deferred, under what conditions it might be reconsidered.]
30
+
31
+ ---
32
+
33
+ ## REJECTED — YYYY-MM-DD
34
+
35
+ ### 1. [Proposal Title]
36
+
37
+ **Status:** REJECTED (wave-N, [reason])
38
+ **Rationale:** [Explanation of rejection, any alternatives considered, and future implications.]
39
+
40
+ ---
41
+
42
+ ## No further action required (MONITORING ONGOING)
43
+
44
+ ### 1. [Proposal Title]
45
+
46
+ **Status:** MONITORING
47
+ [Brief note on what is being tracked and how the team will know if action becomes necessary.]
48
+
49
+ ---
50
+
51
+ ## How to Use This Template
52
+
53
+ 1. **Capture proposals:** When orchestration/monitor/coordinator identifies a potential process or machinery improvement, record it in a staging PROPOSALS.md file with date, source, and rationale.
54
+ 2. **Move to log on decision:** Once a proposal is decided (ACCEPTED/REJECTED/DEFERRED), move the entry here with date and outcome.
55
+ 3. **Track implementation:** Use status sections (ACCEPTED-IMPLEMENTED, IN-PROGRESS, DEFERRED, REJECTED) to organize proposals by lifecycle stage.
56
+ 4. **Preserve history:** Keep all entries, do not delete. This creates an auditable trail of organizational decisions and learning.
57
+ 5. **Link to execution:** When implemented, reference the wave number or PR. When in-progress, name the owner.
58
+
59
+ ## Key Fields
60
+
61
+ - **Proposal Title:** Concise statement of the proposed change or investigation.
62
+ - **Outcome/Status:** What happened as a result (implemented, rejected, deferred, monitoring).
63
+ - **Rationale:** Why this decision was made, dependencies, or conditions for change.
64
+ - **Wave/PR/Agent:** Link to the work that carried out the decision (if applicable).