Captures intellectually interesting moments from your Claude sessions and saves them as Obsidian notes. Optionally queues Threads post drafts for review.
Works two ways:
- Claude Code (automatic) -- a
Stophook that fires after every session across CLI, desktop app, web app, and IDE extensions - Claude Desktop (on demand) -- an MCP server that Claude can call during conversations
- Every time a Claude Code session ends, the
Stophook fires - The hook reads the session transcript
- It sends a trimmed version to a local LLM (Ollama) and asks: "Was anything interesting discovered here? Score it 1-10."
- If the score meets your threshold (default: 7/10), it writes a markdown note to your Obsidian vault
- Optionally: it saves a Threads post draft to a separate folder for you to review, edit, and publish on your own schedule
mkdir -p ~/.claude/hooks
cp interesting-moments.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/interesting-moments.py# Install Ollama: https://ollama.com
ollama pull gemma4The hook uses Ollama by default so transcript analysis runs locally with no API keys. You can swap in any model you prefer (see config below).
pip3 install -r requirements.txtCopy the config template and edit it:
cp interesting-moments.json ~/.claude/interesting-moments.json| Field | Description |
|---|---|
obsidian_vault |
Absolute path to your Obsidian vault root |
obsidian_folder |
Subfolder inside vault for AI moment notes (default: AI-Moments) |
llm_provider |
"ollama" (default, local) or "anthropic" (cloud API) |
ollama_model |
Ollama model to use (default: gemma4) |
ollama_url |
Ollama server URL (default: http://localhost:11434) |
anthropic_api_key |
Only if llm_provider is "anthropic" (prefer ANTHROPIC_API_KEY env var) |
min_interest_score |
Minimum score (1-10) to trigger a capture (default: 7) |
threads_drafts_enabled |
Set true to save Threads post drafts for review |
threads_drafts_folder |
Subfolder in vault for draft posts (default: Threads-Drafts) |
threads_voice |
Voice description for Threads drafts (see Customizing your voice) |
slack_channel_id |
Slack channel ID to post to (e.g. C0ASZLCE3S4). Queues a post for the next session |
max_transcript_chars |
Trim transcripts longer than this before LLM call (default: 12000) |
tag_prefix |
Obsidian tag added to every captured note (default: ai-moment) |
If you set slack_channel_id in the config, the Stop hook queues a pending Slack post. The SessionStart hook picks it up at the start of your next session and asks Claude to review and post it via the Slack MCP tool.
cp session-start-hook.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/session-start-hook.pyOpen ~/.claude/settings.json (create it if it doesn't exist) and merge in:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/session-start-hook.py"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/interesting-moments.py"
}
]
}
]
}
}If you already have a hooks key, append to the arrays rather than replacing them. The SessionStart hook is optional -- skip it if you don't need Slack integration.
The MCP server lets Claude Desktop save interesting moments on demand. No Ollama needed -- Claude Desktop evaluates the conversation itself.
pip3 install -r requirements-mcp.txtSame config file as the hook. If you haven't already:
cp interesting-moments.json ~/.claude/interesting-moments.jsonEdit obsidian_vault to point to your vault.
Open Claude Desktop settings and add this to your MCP servers config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"interesting-moments": {
"command": "python3",
"args": ["/path/to/interesting-moments-mcp.py"]
}
}
}Replace /path/to/ with the actual path to this repo or wherever you copy the file.
Create a Project in Claude Desktop and add this to the system prompt:
When something intellectually interesting comes up in our conversation -- a novel technique, surprising insight, clever solution, or good mental model -- use the
save_interesting_momenttool to capture it. Score the moment 1-10 for interestingness. Write in Cory's voice for the Threads draft: builder, civic tech practitioner, learning in public.After saving a moment that scores 7 or above, also post a concise version to the #_how-i-ai-cory Slack channel (channel ID: C0ASZLCE3S4) using the slack_send_message tool. Keep the Slack post short and conversational -- what was interesting and why it matters. No need to post the full key insight, just the hook.
This nudges Claude to proactively capture moments and share them to Slack. Without it, you can still ask Claude to save or post manually.
Note: The Slack integration requires the Slack MCP connection in Claude Desktop. The Claude Code hook cannot post to Slack via MCP since it runs outside Claude's session -- see Roadmap for planned Slack API support in the hook.
Each captured note looks like this:
---
title: "Unexpected ColBERT Retrieval Win on Short Queries"
date: 2026-04-10T14:32:00
tags:
- ai-moment
- retrieval
- colbert
source: claude-code
project: "/Users/yourname/code/my-project"
session: "sess_abc123"
interest_score: 9
---
# Unexpected ColBERT Retrieval Win on Short Queries
> ColBERT dramatically outperformed BM25 and dense retrieval on sub-5-word
> queries in the Arlington municipal code benchmark.
## Key Insight
Late interaction scoring appears to preserve token-level semantics even for
very short queries where dense retrieval collapses meaning into a single
vector prematurely.
---
*Captured automatically by interesting-moments hook on 2026-04-10*
*Project: `/Users/yourname/code/my-project`*When threads_drafts_enabled is true, the hook also saves a short-form post draft to your vault's Threads-Drafts folder. Each draft is a markdown file you can review and edit in Obsidian:
---
status: draft
date: 2026-04-10T14:32:00
linked_note: "[[2026-04-10-1432-unexpected-colbert-retrieval-win]]"
interest_score: 9
title: "Unexpected ColBERT Retrieval Win"
---
TIL ColBERT crushes dense retrieval on short queries. We benchmarked
against Arlington municipal code and the gap was huge for sub-5-word
procedural queries like "permit fees." Late interaction > single vector
when tokens matter. #civictech #retrieval #buildinpublicThe draft links back to the full Obsidian note via [[wikilink]]. You can:
- Review and edit the post text directly in Obsidian
- Change the status frontmatter (
draft/approved/published) to track what's ready - Use Obsidian Dataview to query all drafts by status, score, or date
- Publish on your own schedule -- copy-paste or use a future Threads API integration
No Threads API keys are required for the draft queue.
The threads_voice config field tells the LLM how to write Threads drafts. The default is tuned for Cory's writing style. To match your own voice, replace it with a short description of how you write:
{
"threads_voice": "Enthusiastic frontend dev. Uses analogies. Prefers short sentences. Skips jargon. Emoji-friendly."
}A good voice description is 1-3 sentences covering: tone (casual/formal), domain (what you write about), and style quirks (sentence length, humor, emoji use). Read a few of your own posts and distill what makes them sound like you.
The analysis prompt uses a tags.md file in your AI-Moments folder to keep tags consistent across notes. This drives cross-linking -- notes that share tags get a "Related Moments" section with [[wikilinks]].
The file lives at <your-vault>/AI-Moments/tags.md and looks like:
# Tags
## Domains
- civic-tech
- gov-tech
- developer-tooling
## Technologies
- python
- llms
- ollama
## Concepts
- system-architecture
- concurrency
- securityThe LLM is told to prefer tags from this list and only create new ones if nothing fits. To add a new tag, edit tags.md directly in Obsidian -- it takes effect on the next session.
The MCP server also exposes a get_canonical_tags tool so Claude Desktop can read the list before choosing tags.
The scoring prompt asks the LLM to evaluate based on:
- Novel technique or pattern discovered
- Clever or non-obvious solution to a hard problem
- Surprising insight about a technology, tool, or domain
- Something that would make a good Threads post or blog seed
Typical sessions score 3-5 (routine work). Interesting architecture discussions, debugging breakthroughs, or benchmark surprises tend to score 7-9.
Adjust min_interest_score to taste.
The --dry-run flag runs the full analysis pipeline but prints the result to stderr instead of writing files:
echo '{"transcript": "USER: I just discovered that ColBERT dramatically outperforms dense retrieval on short queries"}' \
| python3 interesting-moments.py --dry-runThis is useful for testing your config, tuning the interest threshold, or checking what the LLM produces before going live.
The hook forks to the background after reading stdin, so it never blocks Claude Code from starting your next session. The Ollama call (which can take 10-60+ seconds depending on model and hardware) runs in a detached child process. You won't see output in your terminal, but notes will appear in your vault.
Nothing is being captured
- Check
~/.claude/settings.jsonhas the hook registered with the correct path - Test with dry-run:
echo '{"transcript": "..."}' | python3 ~/.claude/hooks/interesting-moments.py --dry-run - If using Ollama: make sure it's running (
ollama serve) and the model is pulled - If using Anthropic: ensure
ANTHROPIC_API_KEYis set in your environment or config file
Notes are in the wrong folder
- Confirm
obsidian_vaultis an absolute path (expand~manually or use the full path) - The folder is created automatically if it doesn't exist
- MCP server for Claude Desktop
- Slack integration via MCP (Claude Desktop)
- Slack integration for Claude Code hook (SessionStart context injection)
- Threads API integration for publishing approved drafts
- LinkedIn draft queue (similar format)
- Interest score dashboard via Obsidian Dataview
- Weekly digest: summarize all AI-Moments notes into one Threads thread