resilient memory sync: decouple push/pull, startup full resync, auth failure handling
- Decouple push and pull in _sync_once() so pull always runs even if push fails - Add startup full resync to catch drift from other agents and schema changes - Add periodic full resync every ~10 minutes for continuous drift correction - Add auth failure detection (401/403) with graceful SQLite-only degradation - Add /api/auth-check endpoint for lightweight key validation - Add retry cap (5 attempts) on pending ops to prevent infinite queue buildup - Add orphan reconciliation: push local-only records with content dedup - Add memory_count MCP tool for sync diagnostics - Add version-based SQLite schema migration (PRAGMA user_version) - Fix API key in ~/.claude.json to match server - Update README with sync resilience docs, test structure, project layout - Add 30 new tests covering all new behaviors (155 total, all passing)
This commit is contained in:
parent
a18b94d310
commit
e47efee6b6
8 changed files with 948 additions and 134 deletions
|
|
@ -12,7 +12,7 @@ haiku to detect learnings worth persisting:
|
|||
Features:
|
||||
- Multi-turn context window (last 5 exchanges by default)
|
||||
- State tracking to avoid duplicate extraction
|
||||
- Writes to memory API/SQLite AND auto-memory markdown files
|
||||
- Writes to memory API/SQLite only
|
||||
- Throttled deep extraction: full window every ~5 turns, single-turn otherwise
|
||||
|
||||
Runs with async: true — does NOT block the user.
|
||||
|
|
@ -252,36 +252,6 @@ def _store_via_sqlite(content, category, tags, importance, expanded_keywords):
|
|||
conn.close()
|
||||
|
||||
|
||||
def _append_to_auto_memory(content: str, event_type: str) -> None:
|
||||
"""Append a learning to the auto-memory markdown file for the current project."""
|
||||
# Find the project memory directory based on CWD
|
||||
cwd = os.getcwd()
|
||||
# Claude Code stores project memory at ~/.claude/projects/<escaped-path>/memory/
|
||||
escaped = cwd.replace("/", "-")
|
||||
if escaped.startswith("-"):
|
||||
escaped = escaped[1:] # Remove leading dash
|
||||
memory_dir = Path.home() / ".claude" / "projects" / f"-{escaped}" / "memory"
|
||||
|
||||
if not memory_dir.exists():
|
||||
# Try without the leading dash
|
||||
memory_dir = Path.home() / ".claude" / "projects" / escaped / "memory"
|
||||
|
||||
if not memory_dir.exists():
|
||||
return
|
||||
|
||||
auto_learn_file = memory_dir / "auto-learned.md"
|
||||
now = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
|
||||
header = "# Auto-Learned Knowledge\n\nAutomatically extracted by the auto-learn hook. Review periodically and promote valuable entries to MEMORY.md.\n\n"
|
||||
|
||||
if not auto_learn_file.exists():
|
||||
auto_learn_file.write_text(header)
|
||||
|
||||
# Append the new learning
|
||||
with open(auto_learn_file, "a") as f:
|
||||
f.write(f"- [{now}] **{event_type}**: {content}\n")
|
||||
|
||||
|
||||
def _parse_llm_response(response_text: str) -> list[dict]:
|
||||
"""Parse LLM response text into events list."""
|
||||
response_text = response_text.strip()
|
||||
|
|
@ -485,12 +455,6 @@ def _store_events(events: list[dict], extracted_hashes: list[str]) -> list[str]:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Also append to auto-memory markdown
|
||||
try:
|
||||
_append_to_auto_memory(content, event_type)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
new_hashes.append(h)
|
||||
|
||||
return new_hashes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue