mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 07:51:52 +00:00
docs(memory): document src/services/memory/index.js and fix save race condition
- Added JSDoc and inline comments to `src/services/memory/index.js` to clarify module flow and internal invariants. - Fixed a race condition in `performSave` where `currentSavePromise` was cleared before the pending save was chained, potentially allowing concurrent writes. - Created `docs/memory-service-index-overview.md` with high-level architecture. - Verified changes with `npm test src/services/memory/index.js` and `npm run lint`. - Updated `TEST_LOG` and `task-logs` to reflect the fix. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
802711f00c
commit
c7b5a88d4b
14 files changed
+271
-9
No files matched your search
@@ -0,0 +1,95 @@
|
||||
# Memory Service Overview (`src/services/memory/index.js`)
|
||||
|
||||
This document provides a high-level overview of the `src/services/memory/index.js` module, which orchestrates memory ingestion, retrieval, pruning, and persistence for the TORCH agent system.
|
||||
|
||||
## What this module does
|
||||
|
||||
`src/services/memory/index.js` acts as the **central controller** for the agent memory system. It coordinates:
|
||||
|
||||
1. **Ingestion**: Validating raw events, generating summaries, and persisting them.
|
||||
2. **Retrieval**: Fetching relevant memories based on semantic similarity (or simple filtering) and updating usage stats.
|
||||
3. **Pruning**: Identifying and removing stale, unpinned memories to keep the store manageable.
|
||||
4. **Persistence**: Managing an in-memory store backed by a JSON file (`.scheduler-memory/memory-store.json`), with serialized writes to prevent corruption.
|
||||
5. **Caching**: Providing a short-lived cache for retrieval queries to improve performance within a single run.
|
||||
|
||||
It serves as the main API surface for agents and the scheduler to interact with long-term memory.
|
||||
|
||||
## Key Flows
|
||||
|
||||
### 1. Ingestion Flow (`ingestEvents`)
|
||||
|
||||
1. **Validate**: Checks input events against schema.
|
||||
2. **Ingest**: Calls `ingestMemoryWindow` (in `ingestor.js`) to process events.
|
||||
3. **Persist**: Writes new records to the in-memory store and triggers a save.
|
||||
4. **Invalidate**: Clears the retrieval cache (`cache.clear()`) because new data might change query results.
|
||||
5. **Telemetry**: Emits metrics and telemetry events.
|
||||
|
||||
### 2. Retrieval Flow (`getRelevantMemories`)
|
||||
|
||||
1. **Check Cache**: Returns cached results if the exact query params match.
|
||||
2. **List/Filter**: Fetches all memories or a filtered subset from the store.
|
||||
3. **Rank**: Calls `filterAndRankMemories` (in `retriever.js`) to score relevance (e.g., using embeddings or keyword matching).
|
||||
4. **Update Usage**: Updates `last_seen` timestamp for retrieved memories (async, fire-and-forget).
|
||||
5. **Cache**: Stores the ranked result in the retrieval cache.
|
||||
6. **Return**: Returns the top-k memories.
|
||||
|
||||
### 3. Pruning Flow (`runPruneCycle`)
|
||||
|
||||
1. **Identify Candidates**: Calls `listPruneCandidates` (in `pruner.js`) or scans the store for unpinned memories older than `retentionMs`.
|
||||
2. **Check Mode**:
|
||||
* `off`: Does nothing.
|
||||
* `dry-run`: Returns candidates without deleting.
|
||||
* `on` (default): Deletes candidates from the store.
|
||||
3. **Persist**: Saves the updated store if changes occurred.
|
||||
4. **Invalidate**: Clears the retrieval cache.
|
||||
|
||||
## Public API
|
||||
|
||||
| Function | Description |
|
||||
| :--- | :--- |
|
||||
| `ingestEvents(events, options)` | Ingests raw agent events, creates memory records, and persists them. |
|
||||
| `getRelevantMemories(params)` | Retrieves relevant memories for a query, updating usage stats. |
|
||||
| `runPruneCycle(options)` | Prunes stale, unpinned memories based on retention policy. |
|
||||
| `listMemories(filters, options)` | Lists memories with optional filtering (tags, agent_id, type) and pagination. |
|
||||
| `inspectMemory(id, options)` | Retrieves a single memory record by ID. |
|
||||
| `pinMemory(id, options)` | Pins a memory to prevent it from being pruned. |
|
||||
| `unpinMemory(id, options)` | Unpins a memory, allowing it to be pruned if it becomes stale. |
|
||||
| `markMemoryMerged(id, dest, options)` | Marks a memory as merged into another, effectively archiving it. |
|
||||
| `triggerPruneDryRun(options)` | Simulates a prune cycle to preview what would be deleted. |
|
||||
| `memoryStats(options)` | Returns aggregate statistics (counts, rates, estimated size) for observability. |
|
||||
|
||||
## Usage Example
|
||||
|
||||
```javascript
|
||||
import { ingestEvents, getRelevantMemories } from './src/services/memory/index.js';
|
||||
|
||||
// 1. Ingest a new observation
|
||||
await ingestEvents([{
|
||||
agent_id: 'test-agent',
|
||||
content: 'The api/v1/users endpoint requires Bearer auth.',
|
||||
timestamp: Date.now(),
|
||||
tags: ['api', 'auth']
|
||||
}]);
|
||||
|
||||
// 2. Retrieve relevant context later
|
||||
const memories = await getRelevantMemories({
|
||||
agent_id: 'test-agent',
|
||||
query: 'how to authenticate user api',
|
||||
k: 5
|
||||
});
|
||||
|
||||
console.log(memories);
|
||||
```
|
||||
|
||||
## Key Invariants & Assumptions
|
||||
|
||||
* **Process-Local Truth**: The `memoryStore` Map is the source of truth for the running process. Multi-process coordination (locking) is handled at the scheduler level, not here.
|
||||
* **Serialized Writes**: `saveMemoryStore` uses a promise queue (`currentSavePromise`, `pendingSavePromise`) to ensure writes to `memory-store.json` do not overlap or race, preventing data corruption.
|
||||
* **Cache Invalidation**: Any mutation (ingest, prune, pin, merge) **must** clear the retrieval cache (`cache.clear()`) to ensure consistency.
|
||||
* **Fire-and-Forget Usage Updates**: `updateMemoryUsage` is often called without awaiting its save operation to minimize latency on the retrieval path.
|
||||
|
||||
## When to Change
|
||||
|
||||
* **Schema Updates**: If the memory record structure changes (e.g., new fields), update `ingestor.js` and `schema.js`, and ensure `index.js` passes the new fields correctly.
|
||||
* **Persistence Strategy**: If moving from a JSON file to a real database (SQLite, Postgres), this file would need significant refactoring to replace the in-memory `Map` and `fs` calls with DB queries.
|
||||
* **Concurrency**: If multiple agents run in parallel processes writing to the *same* memory file without a coordinator, the file-based locking/serialization here is insufficient. You would need a real DB or a dedicated memory server process.
|
||||
Reference in new issue
Block a user