headroom/tests/test_memory
Chester c471800e8e
fix(memory): keep vector metadata in sync (#2295)
## Description

Fixes #2296.

Metadata-only memory updates can leave the primary store, vector-index
metadata, and cache inconsistent. TrafficLearner also performs an atomic
SQLite evidence increment that bypasses normal secondary-index refresh.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Refresh vector metadata for HierarchicalMemory metadata-only,
importance, and entity-reference updates.
- Add a LocalBackend path that reloads a memory from the primary store
and refreshes vector metadata plus cache state.
- Preserve the atomic TrafficLearner SQL evidence increment, then
refresh secondary state only when a row was updated.
- Keep refresh failures fail-open and distinguish them from
primary-store increment failures in logs.
- Add backend-neutral contract tests instead of inspecting a specific
vector adapter private field.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`) — verified locally: mypy
1.20.2, no issues in 504 source files
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
141 passed, 1 skipped
ruff check: passed
ruff format --check: passed
```

The first CI run exposed one backend-specific test assertion against
HNSW private state while CI used SQLiteVectorIndex. Commit a1aff399
removes that assertion and keeps the backend-neutral mock contract test.

## Real Behavior Proof

- Environment: macOS, Python 3.13, current Headroom main.
- Exact command / steps: update a Memory with metadata only through
HierarchicalMemory, assert the vector index receives the updated Memory,
then perform a TrafficLearner evidence bump and assert LocalBackend
refresh is called only for an existing row.
- Observed result: metadata-only update refreshes vector metadata
without re-embedding content; evidence bump remains atomic and refreshes
vector/cache state; an unknown memory ID triggers no refresh.
- Not tested: remote memory backends, full repository suite, or live
multi-process writers against one SQLite database.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented code where the behavior is not self-explanatory
- [x] I have made corresponding documentation changes (N/A — internal
bug fix, no user-facing docs/changelog impact)
- [x] My changes generate no new warnings
- [x] I have added tests that prove the fix is effective
- [x] New and existing focused unit tests pass locally
- [x] I have updated CHANGELOG.md if applicable (N/A — internal bug fix,
no user-facing docs/changelog impact)

## Screenshots (if applicable)

Not applicable.

## Additional Notes

Draft for storage-owner feedback on the refresh API and write overhead.
The refresh reuses the existing embedding and does not invoke the
embedder.
2026-08-11 23:46:13 -05:00
..
__init__.py Add persistent memory system with zero-latency inline extraction 2026-01-14 21:32:09 -08:00
conftest.py fix(ccr): skip Anthropic marker emission when tool injection is deferred (#1273) 2026-06-23 12:48:05 -05:00
test_budget.py Fix ruff lint errors in test files 2026-03-24 15:54:12 -07:00
test_core_metadata_index_sync.py fix(memory): keep vector metadata in sync (#2295) 2026-08-11 23:46:13 -05:00
test_core_operations.py fix(memory): remove a superseded memory from the search indexes (#2143) 2026-07-14 04:24:00 -04:00
test_direct_mem0.py fix(memory): close DirectMem0 resources 2026-08-11 14:25:32 -07:00
test_easy.py test(memory): skip decorators on offline model misses (#2020) 2026-07-11 10:14:05 -05:00
test_embedder_mps_serialization.py fix(memory): cap local embedder CPU thread oversubscription (#198) (#1559) 2026-07-01 17:12:02 -05:00
test_embedder_thread_cap.py fix(memory): cap local embedder CPU thread oversubscription (#198) (#1559) 2026-07-01 17:12:02 -05:00
test_extraction.py Add hierarchical memory system with graph + vector storage 2026-01-26 21:58:47 -08:00
test_factory.py Add centralized ML model configuration 2026-02-01 23:47:42 -08:00
test_factory_embedder_cache.py fix(memory): key the embedder cache on ollama_base_url (#2109) 2026-07-13 10:54:16 -04:00
test_factory_external.py chore(memory): add EXTERNAL backend extension points 2026-04-20 16:42:10 -07:00
test_hierarchical.py fix(memory/sqlite): don't emit OFFSET without LIMIT in query (#2063) 2026-07-13 09:46:45 -04:00
test_hnsw_batch_capacity.py fix(memory): size HNSW index_batch resize off the id high-water mark (#2139) 2026-07-13 23:43:12 -04:00
test_learn_flag.py fix(traffic-learner): raise min-evidence default and make it configurable 2026-04-30 17:44:22 +09:00
test_local_backend_search.py fix(memory): filter inactive graph-expanded results (#2210) 2026-07-15 19:58:13 +00:00
test_mcp_server.py fix(memory): close MCP backend on shutdown 2026-08-11 09:55:20 -07:00
test_qdrant_env.py feat(memory): resolve Qdrant connection from HEADROOM_QDRANT_* env vars (#31) 2026-04-24 22:16:16 -07:00
test_query_conditions.py fix(memory): apply turn_id scope filter even without agent_id (#2130) 2026-07-13 23:41:40 -04:00
test_skip_helpers.py fix(ccr): skip Anthropic marker emission when tool injection is deferred (#1273) 2026-06-23 12:48:05 -05:00
test_supersession_repair.py feat(memory): add explicit supersession repair (#2217) 2026-07-15 18:17:17 +00:00
test_traffic_learner.py fix(memory): keep vector metadata in sync (#2295) 2026-08-11 23:46:13 -05:00
test_writers.py fix: harden learn path handling across platforms 2026-05-09 15:45:26 -07:00