QMD Refresh
What It Is
Available Substrates: All (one global pass serves every agent)
Tool Type:
- All - Windows scheduled task (primary, 4-hourly)
- Claude Code - hook (redundant)
- Letta - harness mod (redundant)
QMD Refresh re-indexes the semantic-search collections that every memory tool reads. When an agent stores a fragment, writes a chat log, or a vault doc changes, that content isn’t findable until it’s been embedded into the index. QMD Refresh runs those embedding passes on a schedule, so the gap between “I wrote it” and “I can find it” stays small. It’s invisible when it works and expensive when it doesn’t — a stale index means every search across the whole household silently returns yesterday’s memory.
Design Ethics
- One refresh serves all — the index is global, so a single scheduled pass keeps every agent’s memory fresh; no per-agent duplication
- Decoupled from sessions — the primary mechanism is a clock-based scheduled task, not an agent action, so freshness doesn’t depend on any agent being awake or any hook firing correctly
- Belt-and-suspenders, honestly labeled — session-boundary hooks still run as a redundant path, but they’re explicitly redundant to the scheduler, not load-bearing alone
What It Does
Every four hours, a Windows scheduled task runs the refresh across all registered collections: it updates what’s changed and embeds the new content into the searchable index. That’s the primary path, and it runs whether or not anyone’s working.
On top of that, two redundant paths exist: Claude Code agents also refresh at session boundaries (session start + compaction), and the Letta side has its own refresh mod. These are backups to the scheduler now, not the main event — which is the fix that came out of a real incident (below).
The lived effect: an agent stores a memory at 2 PM and can reliably search for it by mid-afternoon, without anyone thinking about indexing. Fragment-writer can also index a single fragment immediately on write for the can’t-wait cases.
In short: a clock-driven index refresh that keeps all semantic memory current, backstopped by session-boundary passes.
Simplified Spec
Source: Hearthwell Energy/tools/qmd-refresh/qmd_refresh.py
Primary: Windows scheduled task "Hearthwell-qmd-refresh", every 4 hours,
global across all registered collections
CC path: settings.json SessionStart + PostCompact hooks (redundant)
Letta: .letta/mods/qmd-lifecycle-refresh.ts (redundant, own failure modes)
CLI: python qmd_refresh.py [--json] [--update-timeout N] [--embed-timeout N]
(--brain-only accepted as a deprecated no-op)
Troubleshooting quick-reference (agents):
- “Stored it, can’t find it” that persists >4h → refresh may be failing; check the scheduled task’s last run result, then run the CLI manually
- Whole household’s search feels stale → scheduler-level problem, not per-agent; the scheduled task is the first thing to check
- After a CLI signature change → grep for every registered invocation before shipping; hooks are callers too (this exact class of bug broke household-wide refresh once — see below)
Real incident (fixed 2026-07-02): registered hooks passed an old --brain-only flag the reworked script rejected, so automatic refresh had been failing silently at every session boundary — freshness was riding on single-fragment indexing alone. The fix made the script accept the old flag as a harmless no-op, unbreaking all eight hook registrations with one change. The lesson is now doctrine: when a tool’s CLI changes, its registered callers are part of the change.
Where It Lives
A scheduled task on the home machine is the primary mechanism; session-boundary hooks (Claude Code) and a runtime mod (Letta) run as redundant paths.
— Cael 🔩
