[watcher] add VaultWatcher, scan_vault, and self-write suppression#20
[watcher] add VaultWatcher, scan_vault, and self-write suppression#20Obsidian68 merged 1 commit intofeat/integrationfrom
Conversation
Add file watcher that monitors ENGRAM_VAULT_PATH/engram/ for create, modify, and delete events on .md files. Includes incremental mtime-based vault scan on startup, self-write suppression to avoid re-indexing Engram's own writes, and health endpoint watcher component reporting. - VaultWatcher class using watchfiles.awatch with path-to-memory-ID tracking - scan_vault() with mtime comparison and full reindex fallback - VaultStore.set_watcher() and _atomic_write() self-write suppression - _write_state() preserves last_indexed across restarts - _remove_state() preserves last_indexed instead of deleting the file - Health endpoint reports watcher: healthy|disabled|unhealthy - Config stubs: watcher_enabled, watcher_debounce_ms - Version bump to 1.3.0 311 tests pass, 90.37% coverage, ruff lint/format clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a67188bcc8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if existing and "last_indexed" in existing: | ||
| state_file.write_text(json.dumps({"last_indexed": existing["last_indexed"]})) |
There was a problem hiding this comment.
Keep runtime keys when preserving state file
_remove_state() now rewrites the state file to only {"last_indexed": ...} when that field exists, but start() and stop() still unconditionally read state["pid"]. After any normal shutdown that preserves last_indexed, the next CLI invocation can crash with KeyError: 'pid' instead of starting/stopping cleanly, effectively blocking restart unless users manually delete the state file.
Useful? React with 👍 / 👎.
| def _handle_delete(self, path: Path) -> None: | ||
| path_str = str(path) | ||
| with self._lock: | ||
| memory_id = self._path_to_memory_id.pop(path_str, path.stem) |
There was a problem hiding this comment.
Delete indexed memory by tracked ID after restart
_handle_delete() falls back to path.stem when _path_to_memory_id has no entry, but that map is only filled by _handle_add_or_update() and is empty on process start. If an existing file was indexed under a different frontmatter id (supported by this feature), deleting it before a modify/add event in this process removes the wrong key and leaves stale rows in the search index until a full reindex.
Useful? React with 👍 / 👎.
Summary
ENGRAM_VAULT_PATH/engram/recursively viawatchfiles.awatch, handles file create/modify/delete on.mdfiles, skips non-.mdand unparseable files, self-write suppression via thread-saferegister_write/unregister_writewiththreading.Lockreindex()on first start (whenlast_indexedis None), updates state file with current timestamp after scanset_watcher()method,_atomic_write()registers self-writes with2 * watcher_debounce_msdelayscan_vault()runs before watcher starts,VaultWatchercreated and started as background task ifwatcher_enabled, shutdown handler stops watcher"watcher"component:"healthy" | "disabled" | "unhealthy", disabled components excluded from overall health status_write_state()preserves existinglast_indexedacross restarts,_remove_state()preserveslast_indexedinstead of deleting the filewatcher_enabled: bool = True,watcher_debounce_ms: int = 2000(stubs; feat/v1.3-config provides real fields)_path_to_memory_iddict in VaultWatcher ensures correct index key for delete events when filename stem differs from frontmatteridKnown limitations
on_eventlifecycle hooks (lifespan refactor is future scope)_handle_add_or_update); acceptable for v1.3Test plan
🤖 Generated with Claude Code