Skip to content

feat: MEMORY.md / BOOTSTRAP.md cross-session long-term memory (closes #160)#172

Open
luceinaltis wants to merge 2 commits into
mainfrom
feat/160-memory-bootstrap
Open

feat: MEMORY.md / BOOTSTRAP.md cross-session long-term memory (closes #160)#172
luceinaltis wants to merge 2 commits into
mainfrom
feat/160-memory-bootstrap

Conversation

@luceinaltis
Copy link
Copy Markdown
Owner

Summary

Closes #160.

docs/memory-system.md line 38 flagged MEMORY.md / BOOTSTRAP.md as 구현 예정 — Tier 2 summaries are auto-generated per-session but there was no durable bridge between them and the user-curated long-term context. This PR adds that layer.

  • MEMORY.md at ~/.qracer/MEMORY.md — a single Markdown file with a machine-managed auto region delimited by <!-- BEGIN:auto --> / <!-- END:auto -->. The auto region holds open theses (with entry/target/stop/catalyst) and upcoming catalysts, regenerated from FactStore after every thesis save. Everything outside the auto region is user-curated free text and preserved verbatim across refreshes.
  • BOOTSTRAP.md at ~/.qracer/BOOTSTRAP.md — optional raw text loaded once at ConversationEngine init and pushed onto the session history as a system turn so long-term preferences ("I'm a long-term value investor") reach the synthesizer without code changes.

What changed

  • qracer/memory/memory_file.py (new) — self-contained module:

    • MemoryDocument dataclass (auto_theses, auto_catalysts, user_content, last_updated) plus summary_line() for briefings.
    • render_memory(doc) / parse_memory(text) round-trip.
    • load_memory(path) / save_memory(doc, path) with atomic .tmp-swap writes and tolerant fallbacks for missing/unreadable/malformed files.
    • refresh_memory(doc, fact_store, catalyst_horizon_days=30) — pure function that regenerates the auto region from FactStore.get_open_theses() and get_upcoming_catalysts() without mutating user content.
    • refresh_memory_file(path, fact_store) — convenience wrapper.
    • load_bootstrap(path) — returns None for missing/empty files.
  • qracer/conversation/engine.py — two new optional kwargs (memory_path, bootstrap_path). _prime_long_term_memory() runs at the end of __init__: BOOTSTRAP.md content becomes a system turn; MEMORY.md with non-empty auto content becomes a rendered system turn. _persist_facts now calls _refresh_memory_file() after a successful thesis save so the auto region stays current. All load/save paths are try/except-guarded — a malformed MEMORY.md can never break the query loop. New memory_document property exposes the cached doc.

  • qracer/cli.pyrepl() wires memory_path = _user_dir() / "MEMORY.md" and bootstrap_path = _user_dir() / "BOOTSTRAP.md" into the engine, and surfaces ✓ MEMORY.md: N active theses, M upcoming catalysts on startup when a doc loads. _repl_loop accepts memory_path and handles three new commands:

    • memory show — print the file (or a hint if absent).
    • memory refreshrefresh_memory_file() and sync the engine's cached doc.
    • memory edit — launch $EDITOR (falls back to nano/vi), seeding the file with a canonical template on first use.

    BANNER + _HELP_TEXT updated accordingly.

  • docs/memory-system.md — replaces the 구현 예정 banner with the new format spec and CLI command list.

Scope mapping (issue #160)

Scope item Status
Define MEMORY.md format (structured Markdown for theses, watchpoints, user prefs) ✅ auto/user split via BEGIN/END markers; canonical template on first memory edit
Load MEMORY.md at session start and inject into ConversationContext ✅ injected as a system turn in _prime_long_term_memory
Auto-update MEMORY.md when FactStore theses change (open/close/supersede) _refresh_memory_file runs after every _persist_facts save — supersession flips happen inside FactStore.save_thesis, so the regenerated open-theses list reflects them on the next render
BOOTSTRAP.md as a lightweight system prompt extension loaded before first query load_bootstrap + system-turn injection
CLI commands: memory show, memory edit, memory refresh ✅ all three wired

Test plan

  • uv run pytest tests/memory/test_memory_file.py tests/conversation/test_engine.py81 passed (24 new test_memory_file cases + 7 new TestLongTermMemory engine cases).
  • uv run pytest full suite — 809 passed, 14 skipped.
  • uv run ruff check on changed files — clean.
  • uv run pyright qracer/memory/memory_file.py qracer/conversation/engine.py — 0 errors, 0 warnings.

New tests cover

memory_file

  • Render: empty doc renders placeholders; theses/catalysts become bullet lines; user content lives after the auto region.
  • Parse: round-trip preserves every field; empty input → default; missing auto region preserves user body; malformed *Last updated:* falls back to now(); bullets outside the ## Active Theses heading are ignored; user content between auto region and trailing sections is captured.
  • Refresh: auto region regenerated from FactStore; user content preserved; upcoming catalysts within horizon included; catalysts outside horizon excluded; last_updated bumped.
  • Persistence: save/load round-trip; save_memory creates parent dirs; missing file returns default; refresh_memory_file preserves user section on disk; .tmp file cleaned up after atomic swap.
  • BOOTSTRAP: missing returns None; empty file returns None; non-empty returns stripped content.
  • summary_line counts.

Engine integration

  • BOOTSTRAP.md content seeds a system message in history.
  • Missing BOOTSTRAP.md leaves history untouched.
  • Pre-existing MEMORY.md is parsed into memory_document.
  • MEMORY.md with theses becomes a system message in history.
  • Empty MEMORY.md is not injected.
  • After a thesis query, MEMORY.md on disk is refreshed and engine.memory_document reflects the new thesis.
  • Malformed MEMORY.md does not crash engine construction.

Manual verification

from pathlib import Path
from qracer.memory.fact_store import FactStore
from qracer.memory.memory_file import (
    MemoryDocument, refresh_memory_file, load_memory, save_memory
)
from qracer.models.base import TradeThesis

store = FactStore()
store.save_thesis(
    TradeThesis(
        ticker="AAPL", entry_zone=(175, 180), target_price=200, stop_loss=165,
        risk_reward_ratio=2.5, catalyst="AI revenue", catalyst_date="Q2 2026",
        conviction=8, summary="Long AAPL on AI.",
    ),
    session_id="demo",
)

path = Path("/tmp/MEMORY.md")
save_memory(MemoryDocument(user_content="## Mine\n\nKeep me."), path)
refresh_memory_file(path, store)
print(path.read_text())
# Auto region now lists AAPL; "Keep me." still present in user section.

claude added 2 commits April 15, 2026 06:20
Closes #160.

Introduces a durable layer above the auto-generated Tier 2 summaries:

- MEMORY.md at ~/.qracer/MEMORY.md with a machine-managed auto region
  (delimited by <!-- BEGIN:auto --> / <!-- END:auto -->) that is
  regenerated from FactStore after every thesis save; everything
  outside the auto region is user-curated and preserved verbatim.
- BOOTSTRAP.md at ~/.qracer/BOOTSTRAP.md — optional raw text loaded
  once at ConversationEngine init and injected as a system turn so
  preferences reach the synthesizer without code changes.

New module qracer/memory/memory_file.py exposes load/save/parse/render,
refresh_memory(doc, fact_store), refresh_memory_file(path, fact_store),
and load_bootstrap(path). ConversationEngine gains memory_path and
bootstrap_path kwargs plus a memory_document property. CLI wires both
files in `repl()` and adds `memory show / refresh / edit` commands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: MEMORY.md / BOOTSTRAP.md cross-session long-term memory

2 participants