"` — if the concept
-appears, the KG is loaded but the synonym is not. Confirm the synonym is on
-the `synonyms::` line (case-insensitive; commas separate entries). Flush
-the cache (section 3) and retry.
-
-**`Failed to load thesaurus: NotFound("thesaurus_...")` in stderr.**
-Cosmetic. The agent looked for a pre-compiled JSON thesaurus first, didn't
-find one, and fell back to building from markdown. Expected on first run.
-
-**Hook does nothing in OpenCode.**
-Check the plugin loaded: `grep terraphim-hooks
-~/.local/share/opencode/log/$(ls -t ~/.local/share/opencode/log/ | head -1)`.
-You should see a line like `service=plugin path=...terraphim-hooks.js
-loading plugin`. If absent, the plugin file is in the wrong directory —
-OpenCode autoloads from `~/.config/opencode/plugin/` and
-`~/.config/opencode/plugins/`.
-
-**Commands get double-rewritten on retry.**
-The hook only touches `tool.execute.before`; the agent does not loop back
-through the hook on its own retries. If you see double rewrites, check
-whether `input.tool === "Bash"` is spelt exactly — OpenCode passes
-`"Bash"`, not `"bash"`.
+*Updated 13 October 2026. The video uses synthetic British female narration and readable excerpts from actual released-binary and host outputs. The Harbour project is fictional public sample data.*
diff --git a/content/posts/native-hook-support.md b/content/posts/native-hook-support.md
index 9e3553e..d57fbc2 100644
--- a/content/posts/native-hook-support.md
+++ b/content/posts/native-hook-support.md
@@ -10,6 +10,8 @@ toc = true
comments = true
+++
+> This is the February 2026 announcement for v1.8.1. For the tested failure → reviewed correction → execution flow, follow the [current command-rewriting guide](/how-tos/command-rewriting-howto/).
+
We are announcing **native hook support** for terraphim-agent v1.8.1. This feature captures failed commands from AI agents (Claude Code, Codex, OpenCode) and learns from them, creating a personal knowledge base of mistakes and corrections. No more jq dependency, no more bash wrappers: just `terraphim-agent learn hook`.
diff --git a/publication/agent-memory-hooks/hooks/render_demo.py b/publication/agent-memory-hooks/hooks/render_demo.py
new file mode 100644
index 0000000..5d219ac
--- /dev/null
+++ b/publication/agent-memory-hooks/hooks/render_demo.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+"""Render readable excerpts from the verified released hook and host receipts."""
+import html
+import json
+from pathlib import Path
+import subprocess
+from playwright.sync_api import sync_playwright
+
+ROOT = Path(__file__).resolve().parent
+EVIDENCE = ROOT / 'evidence/fresh-run'
+OUT = ROOT / 'rendered'
+OUT.mkdir(exist_ok=True)
+(OUT / 'screenshots').mkdir(exist_ok=True)
+read = lambda name: json.loads((EVIDENCE / name).read_text())
+verification = read('verification.json')
+assert verification['pass'] and verification['baseline_exit'] == 2 and verification['corrected_exit'] == 0
+events = [json.loads(line) for line in (EVIDENCE / 'host-events.jsonl').read_text().splitlines()]
+after = [event['data'] for event in events if event['type'] == 'native-after']
+assert len(after) == 2
+baseline, corrected = after
+assert corrected['input']['command'] == verification['host_executed']
+assert corrected['result']['metadata']['exit'] == 0
+saved = read('validation.json')
+assert json.loads(corrected['result']['output']['output']) == saved
+plan = json.loads((ROOT / 'storyboard.json').read_text())
+identity = read('identities.json')
+esc = html.escape
+
+def box(label, value, kind=''):
+ return f''
+
+disabled = json.loads(read('rewrite-disabled.json')['stdout'])
+enabled = json.loads(read('rewrite-enabled.json')['stdout'])
+learning = (EVIDENCE / 'learning-before-correction.md').read_text()
+learning_excerpt = '\n'.join(line for line in learning.splitlines() if line.startswith(('id:', 'command:', 'exit_code:', 'captured_at:')))
+items = [
+ box('Actual host command', baseline['input']['command'], 'command') + box('Actual tool output · exit 2', baseline['result']['output']['output'], 'result') + 'A mistake worth
remembering.
',
+ box('Saved learning · Markdown excerpt', learning_excerpt) + box('Error retained in the learning', baseline['result']['output']['output']) + 'Actual after-tool event → project adapter → released learning hook
',
+ box('Explicit reviewed mapping', 'python3 validate-brief.py --chek\n→ python3 validate-brief.py --check') + box('Actual compilation output', read('compile-corrections.json')['stdout'], 'result') + 'Manual promotion to a tool preference · selected role loads compiled JSON
',
+ box('Rewrite disabled · returned command', disabled['tool_input']['command'], 'command') + box('Rewrite enabled · actual returned JSON', json.dumps(enabled, indent=2), 'result') + 'Standalone hook result. No validator has executed in this comparison.
',
+ box('New host process · original attempt', verification['host_attempted'], 'command') + box('Actual after-tool event · excerpt', json.dumps({'command': corrected['input']['command'], 'exit': corrected['result']['metadata']['exit']}, indent=2), 'result') + 'Included OpenCode V2 adapter applies the command through event.input.
',
+ box('validation.json · actual saved file', json.dumps(saved, indent=2), 'result') + 'Unrelated command: unchanged
Unrelated role: unchanged
Baseline: exit 2 → corrected execution: exit 0
',
+ 'A reviewed correction.
A result you can check.
Public brief + local adapter + saved traces
Release download + reproduction script
terraphim.ai/how-tos/command-rewriting-howto/
'
+]
+css = '*{box-sizing:border-box}body{margin:0;background:#0c1422;color:#f5f7fa;font-family:Arial,sans-serif}.frame{width:1440px;height:960px;padding:38px 64px;display:flex;flex-direction:column}.top{font-size:19px;letter-spacing:.06em;color:#a2b8ce;display:flex;justify-content:space-between}.title{font-size:42px;margin:30px 0 24px;line-height:1.12}.content{flex:1;overflow:hidden}.box{background:#142237;border:1px solid #36516a;border-radius:14px;padding:20px 25px;margin-bottom:18px}h2{font-size:19px;color:#aec6dc;margin:0 0 12px;font-weight:normal}pre{font:25px/1.42 Menlo,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.command pre{color:#9edac2;font-size:22px}.result pre{font-size:23px}.hero{font-size:55px;line-height:1.3;margin:27px 0}.hero em{font-style:normal;color:#a7e5c9}.note{font-size:24px;line-height:1.5;color:#aec6dc}.controls{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin:25px 0}.control{border:1px solid #36516a;border-radius:14px;padding:28px;background:#142237}.control strong{display:block;font-size:76px;color:#a7e5c9;margin:18px 0}.control p{font-size:23px}.download{font-size:30px;line-height:1.6;color:#c8d5e3}.url{font-size:25px;color:#a7e5c9;margin-top:40px}.footer{border-top:1px solid #36516a;padding-top:17px;font-size:18px;color:#a2b8ce;display:flex;justify-content:space-between}.badge{color:#f5ce84}'
+with sync_playwright() as p:
+ browser = p.chromium.launch()
+ page = browser.new_page(viewport={'width':1440, 'height':960}, device_scale_factor=1)
+ for i, (beat, body) in enumerate(zip(plan['beats'], items)):
+ doc = f'TERRAPHIM · LEARNING AND HOOKSAGENT 1.21.19 · OPENCODE 2.0.20
{esc(beat["title"])}
{body}
'
+ (OUT / f'scene-{i}.html').write_text(doc)
+ page.set_content(doc)
+ assert page.locator('.content').evaluate('(e)=>e.scrollHeight<=e.clientHeight'), f'overflow in scene {i}'
+ page.screenshot(path=str(OUT / f'scene-{i}.png'))
+ if i in (0,3,5):
+ name = {0:'poster',3:'hook-output',5:'executed-result'}[i]
+ page.screenshot(path=str(OUT / 'screenshots' / f'{name}.jpg'), type='jpeg', quality=92)
+ browser.close()
+concat=[]
+for i,beat in enumerate(plan['beats']):
+ concat += [f"file 'scene-{i}.png'", f"duration {beat['end']-beat['start']}"]
+concat.append("file 'scene-6.png'")
+(OUT / 'frames.txt').write_text('\n'.join(concat)+'\n')
+subprocess.run(['ffmpeg','-hide_banner','-loglevel','error','-y','-f','concat','-safe','0','-i',str(OUT/'frames.txt'),'-i',str(ROOT/'narration.wav'),'-t',str(plan['durationSeconds']),'-vf','fps=25,format=yuv420p','-c:v','libx264','-preset','medium','-crf','19','-c:a','aac','-b:a','160k','-movflags','+faststart',str(OUT/'agent-hooks.mp4')],check=True)
+(OUT/'render-receipt.json').write_text(json.dumps({'qualification':'released-binary','identities':identity,'durationSeconds':plan['durationSeconds'],'source':'evidence/fresh-run','outputPresentation':'Readable excerpts preserving actual source values; no simulated terminal execution.','manualPromotion':True,'adapterRequired':True},indent=2)+'\n')
+print(OUT/'agent-hooks.mp4')
diff --git a/publication/agent-memory-hooks/hooks/render_voice.py b/publication/agent-memory-hooks/hooks/render_voice.py
new file mode 100644
index 0000000..07ed4a5
--- /dev/null
+++ b/publication/agent-memory-hooks/hooks/render_voice.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""Use the existing local Emma voice at its natural rate, then verify timings."""
+from pathlib import Path
+import hashlib
+import json
+import wave
+import numpy as np
+from kokoro_onnx import Kokoro
+
+ROOT = Path(__file__).resolve().parent
+plan = json.loads((ROOT / "storyboard.json").read_text())
+voice_dir = ROOT / "voice"
+voice_dir.mkdir(exist_ok=True)
+engine = Kokoro('/private/tmp/terraphim-kokoro-v1.0.onnx', '/private/tmp/terraphim-kokoro-voices-v1.0.bin')
+engine.sess.disable_telemetry_events() if hasattr(engine.sess, 'disable_telemetry_events') else None
+audio = np.zeros(plan['durationSeconds'] * 24000, dtype=np.float64)
+for i, beat in enumerate(plan['beats']):
+ path = voice_dir / (hashlib.sha256(beat['text'].encode()).hexdigest()[:16] + '.wav')
+ if not path.exists():
+ samples, rate = engine.create(beat['text'], voice='bf_emma', speed=1.0, lang='en-gb')
+ with wave.open(str(path), 'wb') as f:
+ f.setnchannels(1); f.setsampwidth(2); f.setframerate(rate)
+ f.writeframes(np.round(np.clip(samples, -1, 1) * 32767).astype(' .01
+audio *= 10**(-2/20) / peak
+with wave.open(str(ROOT / 'narration.wav'), 'wb') as f:
+ f.setnchannels(1); f.setsampwidth(2); f.setframerate(24000)
+ f.writeframes(np.round(audio * 32767).astype(' {stamp(t1)}', ' '.join(words[start:end]), '']
+(ROOT / 'captions.vtt').write_text('\n'.join(vtt))
+(ROOT / 'transcript.txt').write_text(plan['title'] + '\nSynthetic British female voice: Emma (Kokoro).\n\n' + '\n\n'.join(f"{b['start']}–{b['end']}s | {b['title']}\n{b['text']}" for b in plan['beats']) + '\n')
diff --git a/publication/agent-memory-hooks/hooks/storyboard.json b/publication/agent-memory-hooks/hooks/storyboard.json
new file mode 100644
index 0000000..6dddd62
--- /dev/null
+++ b/publication/agent-memory-hooks/hooks/storyboard.json
@@ -0,0 +1,77 @@
+{
+ "title": "A failed command, a saved correction, and the next run",
+ "durationSeconds": 75,
+ "voice": {
+ "name": "Emma",
+ "id": "bf_emma",
+ "locale": "en-gb",
+ "engine": "Kokoro ONNX",
+ "synthetic": true,
+ "processing": "Fixed gain and scheduled silence; no time stretching or pitch change"
+ },
+ "beats": [
+ {
+ "start": 0,
+ "end": 9.575,
+ "title": "One wrong flag",
+ "text": "The Harbour brief needs checking. In a real coding session, the agent uses a misspelled flag. The validator fails, with exit code two.",
+ "audio": "voice/bad0a08847a137dc.wav",
+ "audioDuration": 8.174625,
+ "audioSha256": "213f8e94b96e57d3933a8d516e1b9f96b6c5d6e48fc70b102a694f045af0995d"
+ },
+ {
+ "start": 9.575,
+ "end": 18.354,
+ "title": "Keep the failure that happened",
+ "text": "The project adapter passes that actual tool result to Terraphim. A saved learning now contains the command and its error.",
+ "audio": "voice/ce6d7a2756191e29.wav",
+ "audioDuration": 7.379333333333333,
+ "audioSha256": "2a462357d0cc4a02da1c85a1a0fd4ccd4ab3a96c2a5ed22104a4687f21086b28"
+ },
+ {
+ "start": 18.354,
+ "end": 31.027,
+ "title": "Review, then promote",
+ "text": "I record the correction and explicitly promote it to a tool preference. Compilation produces one rule. This review step matters: a captured failure alone does not activate a replacement.",
+ "audio": "voice/f3bde5a82c37a918.wav",
+ "audioDuration": 11.273333333333333,
+ "audioSha256": "711842fdec9632c84818c1a8e131c1192265c97ff929387c802862f28836b1bf"
+ },
+ {
+ "start": 31.027,
+ "end": 41.859,
+ "title": "Hook output is not execution",
+ "text": "Without rewriting, the hook keeps the typo and warns. With rewriting enabled, it returns the corrected command. This JSON has not executed anything yet.",
+ "audio": "voice/d0bd0d86536766b5.wav",
+ "audioDuration": 9.432,
+ "audioSha256": "81e7404e468d38f1a937e38486df32316966b08aed962579929b25ccbee03968"
+ },
+ {
+ "start": 41.859,
+ "end": 53.945,
+ "title": "A fresh host run applies it",
+ "text": "A new coding session tries the same typo. The included OpenCode adapter applies the returned command. The real tool event now records the correct flag and exit code zero.",
+ "audio": "voice/ec8edda1eb08ffd7.wav",
+ "audioDuration": 10.686,
+ "audioSha256": "7287805f718df3e7b839dd9080b0124dd1be598fc3e71f9f7e0819b5942d2758"
+ },
+ {
+ "start": 53.945,
+ "end": 64.4,
+ "title": "Inspect the result on disk",
+ "text": "The validator writes this result file. Unrelated commands and an unrelated role remain unchanged. These are actual outputs from the released binary.",
+ "audio": "voice/40574a95452b9fa0.wav",
+ "audioDuration": 9.054666666666666,
+ "audioSha256": "c92b32a8b6b7882df5abc86bb28662f4b4d5c473b4a2d88f9f8904c37e964bce"
+ },
+ {
+ "start": 64.4,
+ "end": 75,
+ "title": "Try the public fixture",
+ "text": "Download the brief, adapter, saved traces and reproduction script. Follow one reviewed correction all the way from failure to a command that actually runs.",
+ "audio": "voice/2b85beee1296d458.wav",
+ "audioDuration": 9.054666666666666,
+ "audioSha256": "89f0f2498ffb3f8a8210012e7528088c47f5fb6cc7d826304570ba812b07162b"
+ }
+ ]
+}
diff --git a/scripts/test_agent_hooks_publication.py b/scripts/test_agent_hooks_publication.py
new file mode 100644
index 0000000..95086b1
--- /dev/null
+++ b/scripts/test_agent_hooks_publication.py
@@ -0,0 +1,70 @@
+"""Bind hook claims to actual captured failures, correction and executed output."""
+import hashlib
+import json
+from pathlib import Path
+import unittest
+import zipfile
+
+ROOT = Path(__file__).resolve().parents[1]
+MEDIA = ROOT / 'static/demos/agent-hooks'
+
+
+class AgentHooksPublicationTests(unittest.TestCase):
+ def test_media_matches_reviewed_manifest(self):
+ manifest = json.loads((MEDIA / 'manifest.json').read_text())
+ self.assertEqual(manifest['qualification'], 'released-binary')
+ self.assertEqual(manifest['durationSeconds'], 75)
+ self.assertTrue(manifest['manualPromotion'])
+ self.assertTrue(manifest['adapterRequired'])
+ self.assertTrue(manifest['voice']['speechRateUnchanged'])
+ for entry in manifest['files']:
+ data = (MEDIA / entry['file']).read_bytes()
+ self.assertEqual(hashlib.sha256(data).hexdigest(), entry['sha256'])
+ self.assertEqual(len(data), entry['bytes'])
+
+ def test_bundle_proves_hook_result_and_execution_separately(self):
+ with zipfile.ZipFile(MEDIA / 'harbour-hooks-example.zip') as z:
+ self.assertIsNone(z.testzip())
+ self.assertTrue(all(not n.startswith('/') and '..' not in Path(n).parts for n in z.namelist()))
+ load = lambda n: json.loads(z.read('recorded/evidence/' + n))
+ result = load('verification.json')
+ self.assertTrue(result['pass'])
+ self.assertEqual(result['baseline_exit'], 2)
+ self.assertEqual(result['corrected_exit'], 0)
+ self.assertTrue(result['manual_promotion'])
+ self.assertTrue(result['native_adapter_required'])
+ off = json.loads(load('rewrite-disabled.json')['stdout'])
+ on = json.loads(load('rewrite-enabled.json')['stdout'])
+ self.assertEqual(off['tool_input']['command'], result['host_attempted'])
+ self.assertEqual(on['tool_input']['command'], result['host_executed'])
+ events = [json.loads(line) for line in z.read('recorded/evidence/host-events.jsonl').decode().splitlines()]
+ after = [e['data'] for e in events if e['type'] == 'native-after']
+ self.assertEqual(after[0]['result']['metadata']['exit'], 2)
+ self.assertEqual(after[-1]['input']['command'], result['host_executed'])
+ self.assertEqual(after[-1]['result']['metadata']['exit'], 0)
+ self.assertEqual(json.loads(after[-1]['result']['output']['output']), json.loads(z.read('recorded/validation.json')))
+ learning = z.read('recorded/evidence/learning-before-correction.md').decode()
+ self.assertIn(result['host_attempted'], learning)
+ self.assertIn('exit_code: 2', learning)
+ self.assertIn('Compiled 0 correction(s)', load('learning-only-compile.json')['stdout'])
+ self.assertIn('Compiled 1 correction(s)', load('compile-corrections.json')['stdout'])
+ for row in z.read('SHA256SUMS').decode().splitlines():
+ digest, name = row.split(' ', 1)
+ self.assertEqual(hashlib.sha256(z.read(name)).hexdigest(), digest)
+
+ def test_article_preserves_scope_and_downloads(self):
+ page = (ROOT / 'content/how-tos/command-rewriting-howto.md').read_text()
+ self.assertIn('date=2026-04-15', page)
+ self.assertIn('updated=2026-10-13', page)
+ for name in ['agent-hooks.mp4','captions.vtt','transcript.txt','harbour-hooks-example.zip']:
+ self.assertIn('/demos/agent-hooks/' + name, page)
+ self.assertTrue((MEDIA / name).is_file())
+ self.assertIn('explicit human review', page)
+ self.assertIn('included project-local adapter', page)
+ self.assertIn('It has not executed Python', page)
+ self.assertIn('redaction gaps', page)
+ self.assertIn('synonyms:: python3 validate-brief.py --chek', page)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/static/demos/agent-hooks/agent-hooks.mp4 b/static/demos/agent-hooks/agent-hooks.mp4
new file mode 100644
index 0000000..306adad
Binary files /dev/null and b/static/demos/agent-hooks/agent-hooks.mp4 differ
diff --git a/static/demos/agent-hooks/captions.vtt b/static/demos/agent-hooks/captions.vtt
new file mode 100644
index 0000000..4251cab
--- /dev/null
+++ b/static/demos/agent-hooks/captions.vtt
@@ -0,0 +1,49 @@
+WEBVTT
+
+00:00:00.200 --> 00:00:04.110
+The Harbour brief needs checking. In a real coding session, the
+
+00:00:04.110 --> 00:00:08.375
+agent uses a misspelled flag. The validator fails, with exit code two.
+
+00:00:09.775 --> 00:00:13.465
+The project adapter passes that actual tool result to Terraphim.
+
+00:00:13.465 --> 00:00:17.154
+A saved learning now contains the command and its error.
+
+00:00:18.554 --> 00:00:22.053
+I record the correction and explicitly promote it to
+
+00:00:22.053 --> 00:00:25.940
+a tool preference. Compilation produces one rule. This review step
+
+00:00:25.940 --> 00:00:29.827
+matters: a captured failure alone does not activate a replacement.
+
+00:00:31.227 --> 00:00:35.943
+Without rewriting, the hook keeps the typo and warns. With rewriting enabled,
+
+00:00:35.943 --> 00:00:40.659
+it returns the corrected command. This JSON has not executed anything yet.
+
+00:00:42.059 --> 00:00:45.375
+A new coding session tries the same typo. The
+
+00:00:45.375 --> 00:00:49.060
+included OpenCode adapter applies the returned command. The real tool
+
+00:00:49.060 --> 00:00:52.745
+event now records the correct flag and exit code zero.
+
+00:00:54.145 --> 00:00:58.672
+The validator writes this result file. Unrelated commands and an unrelated
+
+00:00:58.672 --> 00:01:03.200
+role remain unchanged. These are actual outputs from the released binary.
+
+00:01:04.600 --> 00:01:09.127
+Download the brief, adapter, saved traces and reproduction script. Follow one reviewed
+
+00:01:09.127 --> 00:01:13.655
+correction all the way from failure to a command that actually runs.
diff --git a/static/demos/agent-hooks/harbour-hooks-example.zip b/static/demos/agent-hooks/harbour-hooks-example.zip
new file mode 100644
index 0000000..9ac6960
Binary files /dev/null and b/static/demos/agent-hooks/harbour-hooks-example.zip differ
diff --git a/static/demos/agent-hooks/manifest.json b/static/demos/agent-hooks/manifest.json
new file mode 100644
index 0000000..f1174d6
--- /dev/null
+++ b/static/demos/agent-hooks/manifest.json
@@ -0,0 +1,68 @@
+{
+ "edition": "agent-hooks-20261013",
+ "qualification": "released-binary",
+ "durationSeconds": 75,
+ "release": "1.21.19",
+ "host": "OpenCode 2.0.20",
+ "voice": {
+ "name": "Emma",
+ "engine": "Kokoro ONNX",
+ "locale": "en-GB",
+ "synthetic": true,
+ "speechRateUnchanged": true
+ },
+ "manualPromotion": true,
+ "adapterRequired": true,
+ "render": {
+ "qualification": "released-binary",
+ "identities": {
+ "terraphim_agent": "terraphim-agent 1.21.19",
+ "binary_sha256": "78542870b76e2a90fa7b2a767ba7fd6126b32304a925feb83e16db01faf0f4d1",
+ "host": "opencode v2.0.20",
+ "model": "kimi-code-plan-global/k3",
+ "note": "Only public fixture sent to the coding model. Hook processing is local; no blanket no-network claim."
+ },
+ "durationSeconds": 75,
+ "source": "evidence/fresh-run",
+ "outputPresentation": "Readable excerpts preserving actual source values; no simulated terminal execution.",
+ "manualPromotion": true,
+ "adapterRequired": true
+ },
+ "files": [
+ {
+ "file": "agent-hooks.mp4",
+ "sha256": "2c5cd802c6c0f9223f2df0c0ade39afcf93a496592ac07bf192fb2d2be7cd906",
+ "bytes": 1670771
+ },
+ {
+ "file": "captions.vtt",
+ "sha256": "9cfb9bc6759539ae8c0c8648c085e7778491b756ab6ebe888904375376b296ad",
+ "bytes": 1576
+ },
+ {
+ "file": "harbour-hooks-example.zip",
+ "sha256": "f17baaaf504380209e9574d0990336d517a66f974a6f9bf27bbbab704c9acb71",
+ "bytes": 30326
+ },
+ {
+ "file": "screenshots/executed-result.jpg",
+ "sha256": "c5216ee5e77668289e4f5120fd3d34d51f2ad156c68b5a82c6cd40bfed49a825",
+ "bytes": 79598
+ },
+ {
+ "file": "screenshots/hook-output.jpg",
+ "sha256": "dee70fc14ce70ae409032211ec4560ed716024b5f0c4cabb40a5dcbc215f6e5e",
+ "bytes": 89396
+ },
+ {
+ "file": "screenshots/poster.jpg",
+ "sha256": "2994137b31f736bfcf17dbda20ad9f843ceba3b057db312ad7f6bf6f97a67b76",
+ "bytes": 90467
+ },
+ {
+ "file": "transcript.txt",
+ "sha256": "c0d1fe56b0778d3fdb020c87dca679be9315c9c50a308a80602487a0ad251d42",
+ "bytes": 1474
+ }
+ ]
+}
diff --git a/static/demos/agent-hooks/screenshots/executed-result.jpg b/static/demos/agent-hooks/screenshots/executed-result.jpg
new file mode 100644
index 0000000..0038154
Binary files /dev/null and b/static/demos/agent-hooks/screenshots/executed-result.jpg differ
diff --git a/static/demos/agent-hooks/screenshots/hook-output.jpg b/static/demos/agent-hooks/screenshots/hook-output.jpg
new file mode 100644
index 0000000..5cf2d8d
Binary files /dev/null and b/static/demos/agent-hooks/screenshots/hook-output.jpg differ
diff --git a/static/demos/agent-hooks/screenshots/poster.jpg b/static/demos/agent-hooks/screenshots/poster.jpg
new file mode 100644
index 0000000..adfd7d8
Binary files /dev/null and b/static/demos/agent-hooks/screenshots/poster.jpg differ
diff --git a/static/demos/agent-hooks/transcript.txt b/static/demos/agent-hooks/transcript.txt
new file mode 100644
index 0000000..62dfcf6
--- /dev/null
+++ b/static/demos/agent-hooks/transcript.txt
@@ -0,0 +1,23 @@
+A failed command, a saved correction, and the next run
+Synthetic British female voice: Emma (Kokoro).
+
+0–9.575s | One wrong flag
+The Harbour brief needs checking. In a real coding session, the agent uses a misspelled flag. The validator fails, with exit code two.
+
+9.575–18.354s | Keep the failure that happened
+The project adapter passes that actual tool result to Terraphim. A saved learning now contains the command and its error.
+
+18.354–31.027s | Review, then promote
+I record the correction and explicitly promote it to a tool preference. Compilation produces one rule. This review step matters: a captured failure alone does not activate a replacement.
+
+31.027–41.859s | Hook output is not execution
+Without rewriting, the hook keeps the typo and warns. With rewriting enabled, it returns the corrected command. This JSON has not executed anything yet.
+
+41.859–53.945s | A fresh host run applies it
+A new coding session tries the same typo. The included OpenCode adapter applies the returned command. The real tool event now records the correct flag and exit code zero.
+
+53.945–64.4s | Inspect the result on disk
+The validator writes this result file. Unrelated commands and an unrelated role remain unchanged. These are actual outputs from the released binary.
+
+64.4–75s | Try the public fixture
+Download the brief, adapter, saved traces and reproduction script. Follow one reviewed correction all the way from failure to a command that actually runs.