Skip to content

feat: structured fact store for cross-session memory persistence#156

Merged
luceinaltis merged 1 commit into
mainfrom
feat/structured-fact-store
Apr 14, 2026
Merged

feat: structured fact store for cross-session memory persistence#156
luceinaltis merged 1 commit into
mainfrom
feat/structured-fact-store

Conversation

@luceinaltis
Copy link
Copy Markdown
Owner

Summary

  • Add FactStore (DuckDB) that persists TradeThesis objects across sessions with automatic supersession handling (new thesis on same ticker → old one marked as superseded)
  • Wire write path: ConversationEngine._persist_facts() upserts thesis after each query — zero additional LLM calls since data is already structured in AnalysisResult.trade_thesis
  • Wire read path: StandardHandler injects open theses as prior_thesis evidence into analysis loop; QuickPathHandler appends thesis reminder to price checks; session-start briefing shows open theses with upcoming catalysts

Motivation

The existing 3-tier memory system (JSONL → Markdown → DuckDB FTS) discards structured data at write-time then attempts to reconstruct it from free-text at read-time. TradeThesis (ticker, entry_zone, target, stop, conviction, catalyst_date) exists as a perfect dataclass at runtime but was never persisted. This PR solves that with deterministic SQL lookup as the primary retrieval path, keeping BM25+vector search as fallback.

Validated by supermemory's ASMR research: structured fact extraction at write-time + deterministic lookup beats vector search for temporal/contradiction/update scenarios.

Files changed

File Change
qracer/memory/fact_models.py New — PersistedThesis, Finding, SessionDigest, ThesisStatus dataclasses
qracer/memory/fact_store.py New — DuckDB CRUD with supersession, catalyst date parsing, ticker-based lookup
qracer/conversation/engine.py Add fact_store param + _persist_facts() write path
qracer/conversation/handlers.py StandardHandler: inject prior theses; QuickPathHandler: thesis reminder
qracer/conversation/quickpath.py generate_briefing() shows open theses with upcoming catalysts
qracer/cli.py Initialize FactStore, wire to engine and briefing
qracer/memory/__init__.py Add new exports
tests/memory/test_fact_store.py New — 15 unit tests (CRUD, supersession, catalyst parsing)
tests/conversation/test_engine.py 3 new integration tests (persistence, supersession, graceful degradation)
.gitignore Fix !qracer/memory/ exception for post-flatten layout

Test plan

  • pytest tests/memory/test_fact_store.py — 15 passed
  • pytest tests/conversation/test_engine.py — 55 passed (40 existing + 15 new)
  • Full suite: 724 passed (3 pre-existing Windows codec failures, CI on Linux)
  • ruff check + ruff format clean
  • CI (code-quality + test-suite + docs-validation)

🤖 Generated with Claude Code

Add a FactStore layer that persists TradeThesis objects to DuckDB after
each analysis query, enabling deterministic ticker-based lookup across
sessions instead of relying solely on free-text search.

Write path: engine._persist_facts() extracts the thesis from
AnalysisResult and upserts it into fact_store.duckdb with automatic
supersession handling (new thesis on same ticker marks old as superseded).

Read path: StandardHandler injects open theses as "prior_thesis"
ToolResult evidence before the analysis loop. QuickPathHandler appends
a thesis reminder to price checks. Session-start briefing shows open
theses with upcoming catalysts.

Zero additional LLM calls — all data is already structured in the
existing TradeThesis dataclass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant