# Agent 04: Memory System Functional Testing -- Bug Fix Report

## Scope
Comprehensive review and fix of all 16 Python modules in `memory/` (~10K lines).
Addressed bugs from `docs/BUG-AUDIT-v6.61.0.md` plus newly discovered issues.

## Bugs Fixed

### BUG-MEM-001 (Audit) -- Episode ID date parsing produces garbage paths
- **File**: `memory/engine.py` method `get_episode()`
- **Root cause**: Fixed-offset parsing with `parts[1]`/`parts[2]`/`parts[3]` assumed a two-character prefix like `ep-`. Variable-length prefixes (e.g., `episode-YYYY-MM-DD-xxx`) shifted the offsets, producing wrong date directories.
- **Fix**: Replaced fixed-offset parsing with regex `re.search(r'(\d{4})-(\d{2})-(\d{2})', episode_id)` that extracts the date from anywhere in the ID string. Falls back to full directory scan if no date pattern is found.

### BUG-MEM-002 (Mission) -- Semantic search returns stale results after consolidation
- **File**: `memory/retrieval.py` methods `retrieve_by_similarity()`, `build_indices()`
- **Root cause**: After consolidation modifies `patterns.json`, the in-memory vector index still holds old embeddings. Searches return outdated results.
- **Fix**: Added `_indices_built_at` timestamp tracking. `build_indices()` records the build time. `retrieve_by_similarity()` compares patterns.json mtime against the build timestamp and falls back to keyword search when stale. Added `mark_indices_stale()` method for explicit invalidation.

### BUG-MEM-003 (Mission) -- Consolidation pipeline has no locking
- **File**: `memory/consolidation.py` method `consolidate()`
- **Root cause**: The consolidation pipeline performs multiple read-modify-write operations on patterns.json without any exclusive locking. Concurrent consolidation runs (e.g., from parallel agents) corrupt data.
- **Fix**: Added file-based exclusive lock (`fcntl.flock`) via `.consolidation.lock`. The `consolidate()` method acquires the lock before delegating to the new `_consolidate_locked()` method. Lock is always released in a finally block, and the lock file is cleaned up.

### BUG-MEM-004 (Mission) -- Memory engine doesn't validate schema versions
- **File**: `memory/engine.py` class `MemoryEngine`
- **Root cause**: No version validation when loading memory data files. Incompatible schema versions could silently produce wrong results or corrupt data.
- **Fix**: Added `SUPPORTED_SCHEMA_VERSIONS` set and `CURRENT_SCHEMA_VERSION` constant to `MemoryEngine`. Added `_validate_schema_version()` method that checks version fields in loaded data. Called during `initialize()` for index.json and timeline.json. Logs warnings for unsupported versions, auto-assigns version to legacy data without one. Changed hardcoded `"1.0"` to `self.CURRENT_SCHEMA_VERSION` in new file creation.

### BUG-MEM-005 (Mission) -- Token counter overflows for large sessions
- **File**: `memory/token_economics.py` methods `record_discovery()`, `record_read()`
- **Root cause**: Token counters grew unbounded in very long sessions. While Python ints don't overflow, downstream JSON serializers and dashboard charts can choke on extremely large numbers.
- **Fix**: Added `_MAX_TOKEN_COUNTER = 10_000_000_000` class constant. Both `record_discovery()` and `record_read()` now cap their accumulated values at this limit using `min()`.

### BUG-MEM-006 (Mission) -- Embedding model fallback dimension mismatch warning
- **File**: `memory/embeddings.py` method `embed()`
- **Root cause**: When the primary embedding provider fails at runtime and falls back to a provider with a different dimension (e.g., OpenAI 1536 -> local 384), callers holding references to VectorIndex objects created with the original dimension get dimension mismatch errors. No warning was issued.
- **Fix**: Added dimension change detection after runtime fallback. Logs an explicit warning when the dimension changes, informing callers that existing vector indices may need to be rebuilt.

### BUG-MEM-007 (Mission) -- Vector index not rebuilt after consolidation
- **File**: `memory/consolidation.py` class `ConsolidationResult`
- **Root cause**: After consolidation creates or merges patterns, vector indices are not notified and continue serving stale data.
- **Fix**: Added `vector_index_stale` boolean flag to `ConsolidationResult`. The flag is set to `True` when patterns are created, merged, or anti-patterns are created. Callers can check this flag and rebuild indices accordingly.

### BUG-MEM-013 (Audit) -- Missing encoding on vector index JSON sidecar write
- **File**: `memory/vector_index.py` method `save()`
- **Root cause**: JSON sidecar files were written without specifying encoding. On systems with non-UTF-8 default locale, non-ASCII metadata caused encoding errors.
- **Fix**: Replaced direct file write with atomic write pattern (tempfile + `os.replace`). Added `encoding="utf-8"` and `ensure_ascii=False` to the JSON dump.

### NEW BUG -- Non-atomic npz file write in vector index
- **File**: `memory/vector_index.py` method `save()`
- **Root cause**: `np.savez()` writes directly to the target path. A crash during write could leave a corrupt npz file, breaking index loading.
- **Fix**: Write to a temp file first, then atomically rename using `os.replace()`.

### NEW BUG -- TOCTOU race in increment_pattern_usage
- **File**: `memory/engine.py` method `increment_pattern_usage()`
- **Root cause**: Used `read_json()` + `write_json()` as separate operations to update a pattern's usage count. Another concurrent write could overwrite the changes between the read and write.
- **Fix**: Replaced with `load_pattern()` + `_dict_to_pattern()` + `save_pattern()` which performs the full upsert under an exclusive file lock via the storage layer.

### NEW BUG -- Timeline TOCTOU race in engine
- **File**: `memory/engine.py` method `_update_timeline_with_episode()`
- **Root cause**: Used `read_json()` + `write_json()` (separate lock acquisitions) to update timeline.json. Concurrent episode storage could lose timeline entries.
- **Fix**: Delegated to `self.storage.update_timeline(action_entry)` which performs the full read-modify-write under a single exclusive lock.

## Bugs Verified as Already Fixed

The following bugs from the audit were already fixed in the current codebase:

| Bug ID | Description | How verified |
|--------|-------------|-------------|
| BUG-MEM-004 (Audit) | `cluster_by_similarity` uses `list.index()` on duplicates | Code at line 300 uses `member_indices` tracking instead |
| BUG-MEM-005 (Audit) | Anti-pattern dedup misses current-run duplicates | Code at lines 228-230 adds to `existing_patterns` within loop |
| BUG-MEM-006 (Audit) | Non-atomic `index.json` write in layers | `memory/layers/` directory does not exist; storage.py uses `_atomic_write` |
| BUG-MEM-007 (Audit) | Non-atomic `timeline.json` write in layers | Same as above |
| BUG-MEM-009 (Audit) | `apply_decay` float comparison causes unnecessary rewrites | Code at line 1245 uses `abs(...) > 0.001` tolerance |
| BUG-MEM-011 (Audit) | `_to_utc_isoformat` edge case with custom tzinfo | Code uses `dt.utcoffset()` comparison, not deprecated `utctimetuple()` |
| BUG-MEM-012 (Audit) | Redundant filesystem scan in token economics | `_full_load_baseline` caching works correctly |
| BUG-MEM-014 (Audit) | `AttributeError` on dict-typed actions in `_episode_to_text` | Code handles both dict and object types with `isinstance` checks |

## Validation

All 15 Python files in `memory/` pass `ast.parse()` syntax validation:
- `__init__.py`, `consolidation.py`, `cross_project.py`, `embeddings.py`, `engine.py`
- `knowledge_graph.py`, `namespace.py`, `rag_injector.py`, `retrieval.py`, `schemas.py`
- `storage.py`, `test_importance.py`, `token_economics.py`, `unified_access.py`, `vector_index.py`

## Edge Cases Analyzed

1. **Empty data**: All methods handle empty lists/dicts gracefully with early returns.
2. **Unicode**: JSON sidecar writes now use `encoding="utf-8"` and `ensure_ascii=False`.
3. **Very large episodes**: Token counters capped at 10 billion to prevent JSON serialization issues.
4. **Concurrent access**: Consolidation pipeline now has exclusive lock; pattern updates use storage-level locking; timeline updates use storage-level locking.
5. **Schema version drift**: Engine now validates schema versions on load and warns about incompatible versions.
6. **ID format variations**: Episode lookup now uses regex to extract dates from any position in the ID string.
7. **File corruption during crash**: Vector index npz and JSON sidecar files now use atomic write (temp file + rename).

## Files Modified

| File | Changes |
|------|---------|
| `memory/engine.py` | Fixed episode ID parsing, added schema version validation, fixed TOCTOU races in pattern usage and timeline updates |
| `memory/retrieval.py` | Added index staleness detection, build timestamp tracking, `mark_indices_stale()` |
| `memory/consolidation.py` | Added exclusive file lock for consolidation pipeline, `vector_index_stale` flag |
| `memory/token_economics.py` | Added token counter overflow cap |
| `memory/embeddings.py` | Added dimension change warning on runtime fallback |
| `memory/vector_index.py` | Atomic writes for both npz and JSON sidecar files, encoding fix |
