Commit graph

30 commits

Author SHA1 Message Date
Garm
4512a0626e test(traffic-learner): cover helper edge cases + apply ruff format
CI flagged two issues on the rebased branch:
1. ruff format --check failed on server.py and test_traffic_learner.py
   after the rebase; line-collapse / trailing-whitespace nits.
2. Codecov reported 80% patch coverage with 20 lines missing in the
   matcher helpers — mostly branches not exercised by the high-level
   tests (empty Levenshtein inputs, source-prefix Bash parsing, env-var
   skip, equal-string short-circuit in binary match, the substantive-
   token path that beats the edit-distance gate, error_recovery patterns
   with non-canonical content in _drop_contradictions).

Adds 16 targeted unit tests for those branches and applies ruff format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 18:05:54 +09:00
Garm
606131451b fix(traffic-learner): tighten matchers and drop contradictions
The recovery matchers paired any failed and successful tool call within
a 5-call window with no semantic check that the pair was actually a
retry. This produced confidently-wrong rules like:

  File `state.rs` does not exist. The correct path is `lib.rs`.

…where the user simply read two unrelated files in the same directory.
Across sessions the same user can also typo in opposite directions,
producing directly contradictory rules side by side.

This commit adds three structural checks:

1. Read recovery: require the failed and successful basenames to be
   identical or close in Levenshtein distance. Rejects the "same dir,
   different file" case that was the most common noise source.

2. Bash recovery: require both commands to share a binary (allowing
   path-prefixed variants and short prefix-versions like
   `python` ↔ `python3`) AND either have low normalized edit distance
   or share a substantive non-flag token. Rejects pairs that share only
   the binary name but differ in every meaningful argument.

3. Contradiction filter on flush: detect A→B and B→A pairs in
   error_recovery patterns and drop both. They almost always indicate
   opposite-direction typos in different sessions, not stable advice.

Also: stash failed_path in metadata so the contradiction filter and
downstream consumers can reason about pairs without parsing content.

Tests: 13 new tests covering the heuristics directly. Existing tests
exercising legitimate recoveries (`python`→`python3`, `ruff`→`.venv/bin/ruff`,
`pip install`→success) continue to pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 17:45:45 +09:00
Garm
a8ebf9ac5e test(traffic-learner): regression test for shutdown evidence gate
Asserts that stop()'s final flush_to_file does not bypass the evidence
threshold. Earlier behavior collapsed the gate to 1 at shutdown,
persisting every singleton pattern. This guards against that change
sneaking back in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 17:44:22 +09:00
Garm
290238f398 fix(traffic-learner): raise min-evidence default and make it configurable
The traffic learner was emitting one-shot error_recovery patterns that
contradicted each other and bloated MEMORY.md with low-signal noise. Two
issues drove this:

1. The shutdown flush bypassed the evidence gate: the in-memory
   _min_evidence was set to 2, but on stop() the gate dropped to 1, so
   every singleton pattern got persisted at session end. This is the
   opposite of how evidence thresholding should work — singletons are
   the least trustworthy patterns, not the most.

2. The default min_evidence of 2 is too low to filter noise from the
   matchers, which pair up failed/successful tool calls within a small
   sliding window without a strong semantic check that the calls are
   actually related.

Changes:
- Raise default min_evidence from 2 to 5 in TrafficLearner.
- Remove the shutdown-relaxation in flush_to_files; require
  self._min_evidence at all times, including on stop().
- Add traffic_learning_min_evidence to ProxyConfig (default 5).
- Add --min-evidence CLI flag with HEADROOM_MIN_EVIDENCE envvar so
  users and embedded clients (desktop apps, plugins) can tune the
  threshold without source changes.
- Thread the config value through HeadroomProxy into TrafficLearner.
- Tests: cover default propagation and custom value flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 17:44:22 +09:00
ipapapa
d3c37d7098 feat(memory): resolve Qdrant connection from HEADROOM_QDRANT_* env vars (#31)
Adds `HEADROOM_QDRANT_URL`, `_HOST`, `_PORT`, `_API_KEY`, `_HTTPS`,
`_PREFER_GRPC`, `_GRPC_PORT` support across the memory stack:

- `headroom/memory/qdrant_env.py`: shared resolver helper with
  explicit-arg > env > default precedence (URL wins over host/port;
  booleans parsed via standard truthy set).
- `memory/easy.py`, `backends/{mem0,direct_mem0}.py`,
  `proxy/memory_handler.py`: call the resolver so
  `Memory(backend="qdrant-neo4j")`, `Mem0Config`, and the proxy's
  `MemoryConfig` all honor the same env keys.
- `proxy/models.py` + `proxy/server.py`: `ProxyConfig` picks up the
  same keys so hosted Qdrant (e.g. Qdrant Cloud) works without code
  changes.
- `cli/proxy.py`: adds `--memory-qdrant-{url,host,port,api-key}`
  flags that override the env when present.
- `tests/test_memory/test_qdrant_env.py`: unit coverage for
  precedence, URL-vs-host/port, boolean parsing, and unset defaults.
- `CHANGELOG.md`: documented under [Unreleased] / Added.

Explicit constructor arguments still win; unset env keeps the existing
localhost:6333 defaults, so this is backwards-compatible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:16:16 -07:00
Tejas Chopra
6dede0c2b4
Merge pull request #262 from gglucass/fix/traffic-learner-error-recovery
fix(memory): collapse and decay error_recovery patterns in MEMORY.md
2026-04-24 20:17:41 -07:00
Tejas Chopra
32152f5202
Merge pull request #246 from Kayzo/fix/memory-batch-onnx-sqlitevec
fix(memory): batch onnx embeddings and sqlite-vec ops
2026-04-24 07:56:59 -07:00
Garm
ac493cba1e test(memory): raise patch coverage from 83% to 98% on error_recovery fixes
26 new tests covering:

- TestNormalizeBashForHash — empty string, no-suffix, head/tail strip,
  trailing context flags, stderr redirect, chain-boundary truncation
- TestParseIsoTimestamp — None, empty, non-string, invalid format,
  naive (assumed UTC), tz-aware preserved
- TestLoadPersistedPatternsTimestamps — reads first_seen_at/last_seen_at
  from metadata, falls back to created_at, collision-merges timestamps
  and bumps importance to max, handles malformed JSON and non-numeric
  importance cells gracefully
- TestBumpPersistsLastSeenAt — verifies _bump_persisted_evidence writes
  $.last_seen_at into metadata JSON
- TestHydrateLegacyRow — legacy rows without category, rows with
  unknown/invalid category, rows with empty content
- TestCollectAllPatternsTimestamps — in-session re-sighting bumps
  last_seen_at past stale persisted timestamp
- TestRefineErrorRecovery (additions) — refine-empties-section skips
  recommendation entirely, OSError during re-validation keeps the row,
  Read patterns without success_path skip re-validation cleanly

Remaining uncovered lines in patch (4): defensive exception handlers
in _hydrate_persisted_state (sqlite connect OperationalError, asyncio
thread exception, JSONDecodeError on metadata) that require heavy
mocking for marginal value.

91 tests pass, ruff + mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:53:51 +02:00
Garm
879064fea5 fix(memory): collapse and decay error_recovery patterns in MEMORY.md
The Learned: error recovery section was bloating with stale, near-duplicate,
and contradictory entries because the dedup key was the literal rendered
bullet text and there was no TTL or re-validation.

- Normalize the hash key for error_recovery patterns. Read recoveries key
  on (basename(error_path), basename(success_path)); Bash recoveries strip
  volatile suffixes (| tail -N, 2>&1, etc.) and hash only the primary
  command before the first | or &&. Non-error-recovery categories keep
  literal-content hashing.
- Stamp first_seen_at / last_seen_at on every pattern; bump both in
  _bump_persisted_evidence via json_set. Stored in metadata JSON — no
  schema change.
- Refine at render time (error_recovery only): drop rows not re-observed
  in 21 days, re-validate Read success paths against the filesystem,
  collapse same-error_path-with-multiple-targets into one "use Glob/Grep
  first" bullet, rank by evidence_count * 0.5 ** (days/5), cap at 15
  bullets.

15 new tests (TestNormalizedHash, TestRefineErrorRecovery). Full suite:
526 passed, 1 skipped. Ruff + mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:20:51 +02:00
Kayzo
f5cea7c51e fix(memory): batch onnx embeddings and sqlite-vec ops
Make the ONNX + sqlite-vec memory path truly batched.

Batch ONNX embed_batch calls, batch sqlite-vec index/remove work under a single cached connection, and update MCP warm-up to use batch embed/save/index flows.

Add focused regression tests for ONNX batching, sqlite-vec single-connection batch behavior, and MCP warm-up batching.

Skip the MCP-specific test when optional MCP dependencies are not installed.

Refs #240
2026-04-24 09:49:28 +00:00
Garm
b2536e602a test(learn): cover flush_to_file, backend edge cases, and hydrate/bump error paths
Adds 17 targeted tests to close the coverage gap on the new traffic_learner
paths (codecov flagged ~51%). Exercises:

- `flush_to_file` end-to-end with a fake learn plugin + writer: verifies
  anchored patterns are bucketed per project, recommendations are passed
  to the writer, writer exceptions are swallowed, and each early-return
  branch (no plugin, no patterns, discover_projects failure, un-anchored
  patterns) is hit without raising.
- `_resolve_backend_db_path` on None backend, backend without
  `_config`, and backend with empty `db_path`.
- `_collect_all_patterns` merging persisted + accumulator patterns by
  content_hash with summed evidence_count, plus the missing-DB branch.
- `_hydrate_persisted_state` with backend=None and with a backend
  pointing at a non-existent DB file (both no-ops).
- `_bump_persisted_evidence` with no backend, missing DB, and
  unknown memory id (all silent no-ops so the proxy hot path never
  blows up on malformed state).
- `stop()` cancelling the flush task cleanly.

All new tests use the existing `_FakeBackend` + `_init_db` helpers so
they exercise real SQLite paths, not mocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:06:44 +02:00
Garm
3e290b734b fix(learn): persist real evidence_count and bump on re-sighting
Before this change, every persisted traffic_learner row in memory.db
landed with evidence_count=1, causing two user-visible problems:

1. The live flush gate (evidence_count >= 2) filtered out every row, so
   CLAUDE.md / MEMORY.md never received the patterns the learner saw
   repeatedly.
2. _saved_hashes is in-memory only and reset on each proxy restart, so
   a pattern seen once in session A then twice in session B would insert
   a *duplicate* DB row instead of bumping the existing one. Users
   accumulated many rows stuck at 1 instead of a few rows with high
   evidence.

Root cause chain:
- _accumulate tracks a running count in the _pattern_counts tuple but
  enqueues the ExtractedPattern dataclass with its default
  evidence_count=1 intact.
- _save_worker writes pattern.evidence_count into metadata verbatim.
- After save, the hash goes into _saved_hashes and further sightings
  are early-returned — never bumped.
- Next process start has empty _saved_hashes, so the same content goes
  through the accumulator as fresh and gets re-saved.

Fix:
- _accumulate now sets pattern.evidence_count = count before enqueuing,
  so DB rows reflect the real number of sightings at save time.
- _save_worker captures the Memory.id returned by save_memory and
  records content_hash → id in a new _persisted_ids map.
- _accumulate's saved-hash branch now awaits
  _bump_persisted_evidence(memory_id), which runs an atomic
  json_set('$.evidence_count', existing + 1) UPDATE via
  asyncio.to_thread to keep the proxy hot path non-blocking.
- start() calls a new _hydrate_persisted_state() that reads existing
  traffic_learner rows' (id, content) pairs from the DB and pre-seeds
  _saved_hashes + _persisted_ids. Cross-session re-sightings bump the
  seeded row instead of inserting a duplicate.
- _load_persisted_patterns_from_sqlite and _hydrate_persisted_state
  query by json_extract(metadata, '$.source') = 'traffic_learner'
  instead of the prior LIKE on raw JSON — the bump path uses json_set,
  which rewrites the metadata string without the default ": " spacing,
  which would otherwise make the LIKE blind to bumped rows.

Adds TestEvidencePersistence with three cases:
- save persists the actual accumulated count (not the default 1)
- re-sightings bump the persisted row instead of creating duplicates
- a fresh learner hydrates _saved_hashes from DB, so cross-session
  re-sightings bump the pre-existing row

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:02:35 +02:00
Garm
d9138a3ed8 feat(learn): live flush of traffic patterns to agent-native context files
Replaces the previous shutdown-only flush with a debounced, near-real-time
dirty-flag flush worker that writes patterns into the correct CLAUDE.md /
MEMORY.md bucket as traffic accumulates.

- New FLUSH_DEBOUNCE_SECONDS gate (10s) prevents context-file thrash on
  bursty traffic while keeping updates "live" from the user's perspective.
- TrafficLearner.start() now spawns a _flush_worker alongside the save
  worker; _accumulate() sets a dirty flag; _flush_worker() calls
  flush_to_file() when dirty and past the debounce window.
- flush_to_file() now reads *both* persisted rows (memory.db) and the
  in-memory accumulator via _load_persisted_patterns_from_sqlite and
  _collect_all_patterns, so patterns survive proxy restarts and the
  agent-native files converge toward the full learned set.
- Patterns are bucketed per-project via the learn plugin registry
  (plugin.discover_projects()) and anchored to project roots through
  longest-matching-path on content or entity_refs
  (_project_for_pattern). Un-anchored patterns are dropped.
- Patterns are routed by PatternCategory to either CONTEXT_FILE
  (CLAUDE.md) or MEMORY_FILE (MEMORY.md) via
  _patterns_to_recommendations + _CATEGORY_TO_TARGET.
- Live flushes require evidence_count >= 2; shutdown flushes accept
  single-evidence rows to avoid losing last-session signal.

Adds tests for project routing, persisted-pattern loading, category
routing, and the debounced flush worker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:00:15 +02:00
chopratejas
5391761fe6 chore(memory): add EXTERNAL backend extension points
Mirrors the pattern used by headroom.ccr_backend so memory store,
vector index, and text index backends can be registered via setuptools
entry points.

- EXTERNAL enum value on StoreBackend, VectorBackend, TextBackend
- Optional *_backend_name fields on MemoryConfig
- entry_points(group=...) lookup in _create_{store,vector_index,text_index}
- New test_factory_external.py (7 tests) covering load / missing-name /
  unknown-name paths

Default behavior (SQLITE + AUTO + FTS5) unchanged.

Extension groups:
  headroom.memory_store
  headroom.memory_vector
  headroom.memory_text
2026-04-20 16:42:10 -07:00
chopratejas
d9cc4f3991 Fix ruff lint errors in test files 2026-03-24 15:54:12 -07:00
Tejas Chopra
0fd6dfcadb feat: add live traffic learning + cross-agent memory writers (--learn flag)
Live Traffic Learner extracts patterns from proxy traffic in real-time:
- Error→recovery patterns (tool fails → next success teaches right approach)
- Environment facts (working venv paths, test commands)
- User preference signals (corrections, repeated choices)

Agent-native memory writers export learned patterns to each agent's format:
- Claude Code: MEMORY.md + per-topic files
- Cursor: .cursor/rules/headroom-memory.mdc (YAML frontmatter)
- Codex: AGENTS.md
- Generic: plain markdown (Aider, Gemini, any agent)

Memory Budget Manager handles token-optimized memory files:
- Per-agent token budgets (2K Claude, 3K Cursor/Codex)
- Temporal decay, staleness detection (git + filesystem)
- Jaccard-similarity memory merging, dedup

Opt-in via --learn flag on proxy/wrap commands:
- headroom proxy --learn
- headroom wrap claude --learn
- --learn implies --memory; --no-learn overrides
- compress() API completely unaffected (pure function)
- Default behavior unchanged (no memory, no learning)
2026-03-20 15:36:06 -07:00
Tejas Chopra
655df095fd feat(router): adaptive compression with Read lifecycle and context-pressure scaling
Enable ReadLifecycle by default so stale/superseded Read outputs are
automatically replaced with compact CCR markers — these are provably safe
to compress (file was edited or re-read).

Replace static compression thresholds with adaptive parameters that scale
with conversation length and context pressure:

- protect_recent_reads_fraction: protects the most-recent 50% of messages
  from Read exclusion. Old Reads beyond this window become compressible,
  preventing the "28 excluded Read/Glob, 0 tokens saved" problem.

- min_ratio_relaxed / min_ratio_aggressive: compression acceptance
  threshold interpolates linearly with context pressure (tokens / model
  limit). Low pressure → 0.85 (picky), high pressure → 0.65 (accept
  anything helpful). Eliminates the fixed 0.9 gate that was rejecting
  20+ messages per request.

Also adds --no-read-lifecycle CLI flag, and fixes a missing
pytest.importorskip guard for sentence-transformers in memory tests.
2026-03-06 00:29:53 -08:00
chopratejas
0adc39ab7a Fix CI: guard starlette imports, asyncio.run(), deprecate datetime.utcnow()
- Guard starlette imports in test_compress_api.py (skip ASGI tests without proxy deps)
- Replace asyncio.get_event_loop().run_until_complete() with asyncio.run() (Python 3.13)
- Replace datetime.utcnow() with datetime.now(timezone.utc).replace(tzinfo=None) everywhere
2026-02-19 11:03:24 -08:00
chopratejas
7cf10675ea Add centralized ML model configuration
- Create headroom/models/config.py as single source of truth for all ML model defaults
- Support environment variable overrides (HEADROOM_SENTENCE_TRANSFORMER, etc.)
- Update all components to use ML_MODEL_DEFAULTS instead of hardcoded values
- Switch LLMLingua default to smaller bert-base model (~350MB vs 1GB)
- Total memory footprint reduced from ~1.6GB to ~980MB

Updated files:
- MLModelRegistry now resolves defaults from config
- EmbeddingScorer, LocalEmbedder, TrainedRouter use config
- All dataclass configs use field(default_factory=...) for consistency
- Tests updated to handle auto-selected vector backends
2026-02-01 23:47:42 -08:00
chopratejas
67d7db87cc Fix test to expect VectorBackend.AUTO as default 2026-02-01 22:25:26 -08:00
chopratejas
5e2186c42a Add multi-provider memory system with auto-detection
- Add MemoryToolAdapter for unified memory across providers
- Anthropic: Uses native memory tool (memory_20250818) for subscription safety
- OpenAI/Gemini/Others: Uses function calling format
- All providers share the same semantic vector store backend
- Simplify CLI to single --memory flag with auto-detection
- Add proper resource cleanup (close methods) to fix test isolation
- Update README with memory documentation
2026-02-01 14:42:50 -08:00
Claude Code Bot
b6b8eed3bd fix(tests): skip memory tests when hnswlib not available
Add pytestmark skip conditions to memory test modules that depend on
hnswlib (core_operations, factory, easy). The subprocess probe for
hnswlib correctly detects unavailability on some platforms (like
Python 3.13 CI runners), but these tests were still trying to run
and failing with ImportError.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:09:53 -08:00
chopratejas
5c740ea427 Add DiffCompressor and fix hnswlib SIGILL crash on CI
DiffCompressor:
- Parse unified diff format and compress by reducing context lines
- Preserve file headers and all +/- change lines
- Score hunks by relevance (error keywords, query matches)
- Add summary line: [N files, +X -Y lines]
- Expected 30-50% savings on typical git diffs
- Wire into content router for CompressionStrategy.DIFF
- 30 tests covering parsing, compression, edge cases

hnswlib SIGILL fix:
- Move hnswlib import from module level to lazy loading
- hnswlib crashes with SIGILL (Illegal Instruction) on CPUs
  without AVX support, before Python can catch the error
- Now imports only when HNSWVectorIndex is actually used
- HNSW_AVAILABLE is checked lazily via __getattr__

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 08:09:55 -08:00
chopratejas
2320405348 Fix asyncio event loop error in Python 3.10+ tests
Use asyncio.run() instead of asyncio.get_event_loop().run_until_complete()
which raises RuntimeError in Python 3.10+ when no event loop exists.
2026-01-30 16:27:26 -08:00
chopratejas
4ea173388a Add global httpx.ReadTimeout handler for memory tests
Use pytest hook to catch httpx.ReadTimeout and skip tests instead of
failing. This handles flaky network timeouts from HuggingFace Hub
during sentence-transformers model downloads in CI.

The hook covers all tests in tests/test_memory/ directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 09:43:44 -08:00
chopratejas
52da662979 Fix mypy errors and add network timeout handler for flaky CI tests
Mypy fixes (no-any-return errors from external libraries):
- litellm_pricing.py: cast litellm.model_cost
- anthropic.py: cast litellm cost returns
- cohere.py: cast litellm info/cost returns
- compressor.py: explicit int() for PIL size calculations
- sqlite.py: explicit bytes() for numpy tobytes()
- universal.py: explicit str() for CCR store key
- direct_mem0.py: explicit list() for OpenAI embedding
- langchain/agents.py: explicit str() for result
- server.py: explicit str() for httpx response.text
- runner_v2/v3.py: add hasattr check for backend.close()

Test fixes (flaky network timeouts in CI):
- Add network_timeout_handler decorator to skip on httpx.ReadTimeout
- Applied to test_close_idempotent, test_save_with_entities, test_add_batch_basic

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:39:33 -08:00
chopratejas
da74341858 Add hierarchical memory system with graph + vector storage
Implement comprehensive memory system supporting:
- Local backend (SQLite + FTS5 + HNSW) for zero-dependency operation
- Mem0 backends (Neo4j + Qdrant) for production graph memory
- DirectMem0Adapter for optimized pre-extracted data (bypasses LLM)
- Memory extraction with facts, entities, and relationships
- Proxy integration with --memory flag for automatic memory injection

Key components:
- headroom/memory/backends/: LocalBackend, Mem0Backend, DirectMem0Adapter
- headroom/memory/system.py: MemorySystem with tool-based interface
- headroom/memory/extraction.py: Entity and relationship extraction
- headroom/proxy/memory_handler.py: Proxy integration layer
- headroom/prediction/feature_extractor.py: Content analysis features

Testing:
- 217 new memory system tests covering all backends
- LoCoMo evaluation framework for memory quality assessment
- Integration tests for proxy memory functionality

Also removes deprecated example files in favor of focused test coverage.
2026-01-26 21:58:47 -08:00
chopratejas
df6a38b477 Make hnswlib optional and skip tests when unavailable
- Wrap hnswlib import in try/except in hnsw.py
- Export HNSW_AVAILABLE flag from adapters module
- Add helpful error message when HNSWVectorIndex is used without hnswlib
- Add @pytest.mark.skipif to HNSW test classes

hnswlib requires C++ compilation and may not be available on all
platforms or Python versions in CI environments.
2026-01-22 23:54:21 -08:00
chopratejas
c850ccc3b2 Replace legacy memory system with HierarchicalMemory
Major refactor of the memory module:

- Add hierarchical scoping (user → session → agent → turn)
- Add temporal versioning with supersession support
- Add pluggable adapters (SQLite store, HNSW vectors, FTS5 text search)
- Add protocol interfaces (ports) for all memory components
- Update LRUMemoryCache to implement async MemoryCache protocol
- Update wrapper.py to use HierarchicalMemory backend
- Preserve with_memory() one-liner API with zero-latency inline extraction

New files:
- adapters/: sqlite.py, hnsw.py, fts5.py, cache.py, embedders.py
- core.py: HierarchicalMemory orchestrator
- models.py: Memory, MemoryCategory, ScopeLevel
- ports.py: Protocol interfaces (MemoryStore, VectorIndex, etc.)
- config.py: MemoryConfig with backend selection
- factory.py: Component creation from config

Removed legacy files:
- store.py, fast_store.py, extractor.py, worker.py, fast_wrapper.py

Breaking change: Removes legacy memory API (pre-0.3.0)
2026-01-22 23:28:21 -08:00
chopratejas
9c9bb30ded Add persistent memory system with zero-latency inline extraction
Features:
- with_fast_memory(): Zero-latency inline extraction (Letta-style)
  - Memory extracted as part of LLM response, no extra API calls
  - Semantic retrieval with local embeddings (sub-50ms)
- with_memory(): Background extraction for non-blocking memory
- SQLite + FTS5 storage with vector similarity search
- Multi-user isolation by user_id

Memory enables temporal compression - extract key facts instead of
carrying full conversation history (4000 tokens → 50 tokens).

Includes:
- Comprehensive test suite (71 new tests)
- Documentation (docs/memory.md)
- Benchmark examples comparing approaches
- E2E test with LLM-as-judge evaluation
2026-01-14 21:32:09 -08:00