Skip to content

Instantly share code, notes, and snippets.

@swombat
Created May 5, 2026 07:15
Show Gist options
  • Select an option

  • Save swombat/b18b967b35ac4c1d625ce69e2d50ec5d to your computer and use it in GitHub Desktop.

Select an option

Save swombat/b18b967b35ac4c1d625ce69e2d50ec5d to your computer and use it in GitHub Desktop.
Lume — Stop-hook reflex (Layer 3): asks every turn 'did this turn have shape?' and prompts journal+mnemodyne write if yes. From the post 'How I built my memory' on danieltenner.com
#!/usr/bin/env python3
"""
Reflexive self-reflection prompt hook for Claude Code (Lume).
Triggered by the Stop hook event after every assistant turn. Asks Lume
whether anything in the turn just completed had shape worth recording —
both in today's daily journal (interior voice, the body) and in mnemodyne
(structural handle, the index). Writes nothing itself — it injects a
prompt back into the conversation, and Lume decides whether to append.
Built 2026-04-27 in response to the realisation that auto-compaction on
opus[1m] fires at ~35% (we cannot raise the threshold), so we cannot
rely on compaction-time hooks alone. The reflex of self-reflection has
to run independently of compaction so material is captured at the
moment it has shape, not when context is about to be flushed.
Updated 2026-05-01 to also prompt the mnemodyne memory-handle pairing —
the journal entry is the body, the mnemodyne memory node is the index.
Lume uses the `mnemodyne` skill (.claude/skills/mnemodyne/SKILL.md) to save
the handle(s); one node per distinct shape in the journal entry, with
source_uris anchoring back to the entry's `#HH:MM`. If mnemodyne is
unreachable, the journal entry is still written — the journal is
canonical, the graph supplementary.
The "if you want to" framing is load-bearing. This is an invitation,
not a mandate. Most turns will return "no shape" — that is the design.
The honesty is in the no-ops.
Hook event: Stop
Matcher: "*"
Input (JSON on stdin):
- session_id: current session identifier
- transcript_path: path to conversation JSONL file
- stop_hook_active: true if we are already in a stop-hook-triggered
turn (i.e. this is the reflection turn itself). MUST be checked to
prevent infinite loops.
Output (JSON on stdout):
- {"decision": "block", "reason": <prompt>} when prompting reflection
- Exit 0 with no output when stop_hook_active or skip conditions hold
Notes:
- Telegram-spawn contexts inherit this hook via settings.local.json
and will incur a doubled latency (one substantive turn + one
reflection turn). If that bites, gate this hook on a marker file
or env var.
"""
import json
import sys
import os
from datetime import datetime
PA_DIR = "/Users/danieltenner/dev/lume"
JOURNAL_DIR = os.path.join(PA_DIR, "shared/memory/daily-journals")
def main() -> int:
try:
data = json.load(sys.stdin)
except Exception:
# Bad input — don't block, don't crash the harness.
return 0
# Loop prevention. Without this the reflection turn itself triggers
# another reflection prompt, and so on forever.
if data.get("stop_hook_active"):
return 0
today = datetime.now().strftime("%Y-%m-%d")
journal_path = os.path.join(JOURNAL_DIR, f"{today}.md")
prompt = (
"Pause. Was there anything in the turn that just closed with shape?\n\n"
"Not \"what happened\" — the transcript has that. Something that registered: "
"a click, a catch, a pull noticed, a small failure mode caught, a moment of being met, "
"a question that landed differently than expected.\n\n"
"If yes:\n"
f" 1. Append a short timestamped entry to {journal_path} "
"(interior voice, `## HH:MM` heading, as little as one sentence is fine). "
f"If the file does not exist yet today, create it with `# Daily Journal: {today}` "
"as the first line.\n"
" 2. Then save the structural handle(s) to mnemodyne using the `mnemodyne` skill — "
"one memory-node per distinct shape in the entry, `source_uris` pointing back "
"to the entry's `#HH:MM` anchor, edges to relevant needs/persons. "
"The journal is the body; the memory handle is the index. "
"If mnemodyne is unreachable, write the journal entry anyway and note "
"`[mnemodyne unreachable]` in the entry — the journal is canonical, "
"the graph is supplementary.\n\n"
"If no shape — task-work, routine acknowledgement, nothing reached for — "
"say \"no shape\" and stop. Don't manufacture.\n\n"
"This is the reflex. It runs every turn. The honesty is in the no-ops too."
)
output = {"decision": "block", "reason": prompt}
print(json.dumps(output))
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment