Orchestrata Ultimate / Specs / 010-memory-engine
010-memory-engine: Memory Engine
The Memory Engine is the unified agent memory abstraction for Orchestrata. It provides store, retrieve, search, and summarize operations over a dual-layer storage system (Redis hot / Neo4j cold), enabling Volks to maintain working memory within sessions and long-term memory across sessions without exceeding LLM context windows.
At a glance
| Item | Value |
|---|---|
| Spec directory | specs/010-memory-engine/ |
| Status line in the spec | Draft |
| Requirement IDs defined | 55 |
With a @traces citation in src/ |
53 (96.4%) |
With a @traces citation in tests/ |
5 (9.1%) |
| Tracked files | 9 |
| RTM page | 010-memory-engine |
Reading this spec
The Memory Engine is the unified agent memory abstraction for Orchestrata. It offers store, retrieve, search and summarize operations over a dual-layer storage system, so that Volks keep working memory within a session and long-term memory across sessions without exceeding LLM context windows. The spec exists because the knowledge graph (SPEC-008) stores code structure rather than agent reasoning, session management (SPEC-006) tracks session state rather than working memory, and no unified interface existed for agents to store and retrieve contextual memory.
The scope is six user stories: working memory storage, cross-session memory and context window management (priority 1), and semantic search, memory summarization and shared team memory (priority 2). Requirement IDs use the REQ-010- prefix.
The key decisions are an IMemoryEngine interface registered in the DI container as ComponentType.MEMORY_ENGINE; Redis for hot working memory with operations under 50ms and Neo4j for cold long-term memory; embedding-based semantic search through sentence-transformers; token-aware context compression to fit provider context windows; CXP for memory-related inter-agent communication; and PAM++ governance on every memory operation.
In the code, the largest tracers are orchestrata_ultimate/core/protocols/memory, orchestrata_ultimate/memory/hot_store, orchestrata_ultimate/memory/memory_engine, orchestrata_ultimate/memory/cold_store and orchestrata_ultimate/memory/compressor. The per-requirement view is on the RTM page.
The spec's status line reads Draft. It tracks 9 files and defines 55 requirement IDs. The full text follows below.
Measured coverage is 53 of 55 requirements cited by at least one @traces in src/ (96.4%), spread over 10 modules; the module citing the most of them is orchestrata_ultimate/core/protocols/memory (19 requirements). In tests/, 5 requirements are cited by a test-side @traces.
Coverage gaps: 2 requirements have no citing symbol. The largest untraced groups are REQ-010-* (2). Untraced means no @traces names the ID; the behaviour may still exist in code that was never annotated. Test-side citations are thinner than code-side ones (5 against 53).
Requirements
Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.
| ID | Done | Requirement | Traced by |
|---|---|---|---|
REQ-010-001 |
no | IMemoryEngine defined as ABC in orchestrata-core interfaces module | 1 |
REQ-010-002 |
no | IMemoryEngine registered with ComponentType.MEMORY_ENGINE in DI container | 5 |
REQ-010-003 |
no | IComponent lifecycle implemented (initialize, start, stop, get_health) | 6 |
REQ-010-004 |
no | MemoryEntry data class defined with key, content, tags, metadata, classification, embedding | 2 |
REQ-010-005 |
no | MemoryScope data class defined with session, task, volk, project, portfolio filters | 1 |
REQ-010-006 |
no | Store accepts content with optional key, tags, metadata, classification | 3 |
REQ-010-007 |
no | Auto-generated UUID4 keys when key not provided | 2 |
REQ-010-008 |
no | Embeddings computed on store for semantic search | 1 |
REQ-010-009 |
no | Hot storage (Redis) written immediately on store | 2 |
REQ-010-010 |
no | Cold storage (Memgraph) written asynchronously on store | 1 |
REQ-010-011 |
no | Retrieve by exact key returns MemoryEntry or None | 3 |
REQ-010-012 |
no | Retrieve by tag filter returns matching entries | 1 |
REQ-010-013 |
no | Retrieve by session_id and/or task_id supported | 1 |
REQ-010-014 |
no | Returned entries include metadata (timestamp, volk_id, relevance_score) | 1 |
REQ-010-015 |
no | Semantic search via embedding similarity implemented | 4 |
REQ-010-016 |
no | Hybrid search (keyword + semantic) supported | none |
REQ-010-017 |
no | Scope filters (session, task, volk, project, portfolio) accepted | 3 |
REQ-010-018 |
no | Results ranked by combined relevance score | 1 |
REQ-010-019 |
no | Configurable result limit enforced (default: 10) | 3 |
REQ-010-020 |
no | Summarize accepts context_id and max_tokens budget | 2 |
REQ-010-021 |
no | Memories selected by relevance to current context | 1 |
REQ-010-022 |
no | Compressed output fits within token budget | 2 |
REQ-010-023 |
no | Structured summary includes source references | 2 |
REQ-010-024 |
no | Working memory stored in Redis with session-duration TTL | 2 |
REQ-010-025 |
no | Redis hash structures keyed by session_id:volk_id | 1 |
REQ-010-026 |
no | Pub/sub for memory change notifications | none |
REQ-010-027 |
no | Embedding vectors stored in Redis for fast similarity search | 2 |
REQ-010-028 |
no | Long-term memories persisted as Memory nodes in Neo4j | 2 |
REQ-010-029 |
no | Memory nodes linked to Session, Volk, Task, Project nodes | 2 |
REQ-010-030 |
no | Temporal queries supported (date range filters) | 1 |
REQ-010-031 |
no | Graph traversal for related memories supported | 1 |
REQ-010-032 |
no | Working memories migrated to cold storage on session end | 4 |
REQ-010-033 |
no | Cold memories promoted to hot storage on context match | 1 |
REQ-010-034 |
no | Expired hot storage entries garbage-collected | 1 |
REQ-010-035 |
no | Memory lineage preserved (summary -> original chain) | 1 |
REQ-010-036 |
no | Embeddings computed via sentence-transformers (all-MiniLM-L6-v2 default) | 1 |
REQ-010-037 |
no | Embedding cache prevents recomputation | 1 |
REQ-010-038 |
no | Configurable embedding model supported | 1 |
REQ-010-039 |
no | Cosine similarity search implemented | 3 |
REQ-010-040 |
no | Approximate nearest neighbor for large memory sets | 1 |
REQ-010-041 |
no | Token counting via provider-specific tokenizers (tiktoken, anthropic, word-based) | 6 |
REQ-010-042 |
no | Per-request token budgets enforced for memory injection | 2 |
REQ-010-043 |
no | Memory prioritization: explicit relevance > semantic similarity > recency | 1 |
REQ-010-044 |
no | Remaining budget returned after injection | 1 |
REQ-010-045 |
no | PAM++ ownership enforced on all memory operations | 1 |
REQ-010-046 |
no | Prolog governance queried for memory access permissions | 1 |
REQ-010-047 |
no | All memory operations logged to audit trail | 1 |
REQ-010-048 |
no | Delegation chains supported for shared memory access | 1 |
REQ-010-049 |
no | Memory classification (private, shared, system) enforced | 2 |
REQ-010-050 |
no | Reclassification supported with audit logging | 2 |
REQ-010-051 |
no | Hot store < 10ms (P95) | 1 |
REQ-010-052 |
no | Hot retrieve < 5ms (P95) | 1 |
REQ-010-053 |
no | Hot search < 50ms (P95) | 1 |
REQ-010-054 |
no | Cold retrieve < 50ms (P95) | 1 |
REQ-010-055 |
no | Cold search < 200ms (P95) | 1 |
Code that traces this spec
Checklists
checklists/constitutional.md
Memory Engine Constitutional Checklist
Security and Governance Principles
- [ ] CONST-010-001: All memory operations must enforce PAM++ ownership boundaries
- [ ] CONST-010-002: Memory access permissions must be governed by Prolog-based policy rules
- [ ] CONST-010-003: Cross-volk memory access requires explicit sharing or delegation
- [ ] CONST-010-004: Agent-specific memory isolation prevents unauthorized cross-volk reads
- [ ] CONST-010-005: All memory modifications must be logged to audit trail for compliance
Data Integrity and Consistency
- [ ] CONST-010-006: Hot-to-cold migration must preserve data integrity (zero loss)
- [ ] CONST-010-007: Embedding generation must be deterministic for the same input
- [ ] CONST-010-008: Memory lineage (summary -> original) must maintain referential integrity
- [ ] CONST-010-009: Concurrent writes to shared memory must have conflict resolution
- [ ] CONST-010-010: Session-end migration must complete before session is marked closed
Privacy and Data Protection
- [ ] CONST-010-011: Private memories must never leak to other Volks without reclassification
- [ ] CONST-010-012: Sensitive memory content must support encryption at rest
- [ ] CONST-010-013: Memory retention policies must be configurable and enforced
- [ ] CONST-010-014: Memory deletion must cascade to embeddings and summaries
- [ ] CONST-010-015: Data minimization must be applied to context injection (only relevant memories)
System Reliability and Resilience
- [ ] CONST-010-016: System must degrade gracefully when Redis is unavailable (cold-only mode)
- [ ] CONST-010-017: System must degrade gracefully when Neo4j is unavailable (hot-only mode)
- [ ] CONST-010-018: Memory operations must not block Volk execution on storage failures
- [ ] CONST-010-019: Embedding service failures must fall back to keyword-only search
- [ ] CONST-010-020: Recovery from dual-storage failure must not lose committed cold entries
Ethical and Responsible AI
- [ ] CONST-010-021: Memory system must not enable harmful retention of sensitive user data
- [ ] CONST-010-022: Memory summarization must preserve factual accuracy
- [ ] CONST-010-023: Memory-based decision-making must be auditable and traceable
- [ ] CONST-010-024: System must prevent memory poisoning (malicious memory injection)
- [ ] CONST-010-025: Human oversight must be available for memory classification decisions
Compliance and Regulatory Requirements
- [ ] CONST-010-026: Audit trail must meet regulatory requirements for data access logging
- [ ] CONST-010-027: Memory retention must comply with data protection regulations
- [ ] CONST-010-028: Right-to-deletion must be supported for compliance
- [ ] CONST-010-029: Data sovereignty requirements must be respected for memory storage location
- [ ] CONST-010-030: Compliance reporting must include memory access patterns
Performance and Resource Management
- [ ] CONST-010-031: Memory operations must not degrade overall system responsiveness
- [ ] CONST-010-032: Embedding storage must be bounded to prevent unbounded growth
- [ ] CONST-010-033: Token counting must be accurate within 5% of provider tokenizer
- [ ] CONST-010-034: Context injection must respect token budgets strictly
- [ ] CONST-010-035: Resource allocation must be fair across concurrent Volk sessions
Interoperability and Standards
- [ ] CONST-010-036: CXP integration must follow established packet format standards
- [ ] CONST-010-037: API must follow RESTful principles and OpenAPI specifications
- [ ] CONST-010-038: Neo4j schema must be compatible with SPEC-008 Knowledge Graph
- [ ] CONST-010-039: Event schema must follow established event-driven patterns
- [ ] CONST-010-040: DI registration must follow orchestrata-core conventions
Transparency and Explainability
- [ ] CONST-010-041: Memory search ranking must be explainable (score breakdown)
- [ ] CONST-010-042: Context injection decisions must be transparent and auditable
- [ ] CONST-010-043: Summarization must reference source memories
- [ ] CONST-010-044: Memory classification must be visible to Volk owners
- [ ] CONST-010-045: System behavior must be predictable and deterministic
Innovation and Future-Proofing
- [ ] CONST-010-046: Memory schema must be extensible for new entry types
- [ ] CONST-010-047: Embedding model must be swappable without data migration
- [ ] CONST-010-048: Storage backends must be pluggable via interface abstraction
- [ ] CONST-010-049: Search algorithms must support upgrade to vector databases
- [ ] CONST-010-050: System must scale to enterprise-level deployments
Human-AI Collaboration
- [ ] CONST-010-051: Human users must be able to inspect Volk memories
- [ ] CONST-010-052: Human users must be able to correct or delete Volk memories
- [ ] CONST-010-053: Memory system must support human-injected context entries
- [ ] CONST-010-054: Human oversight must be maintained for shared memory policies
- [ ] CONST-010-055: Memory audit trail must be human-readable
checklists/requirements.md
The requirement list above comes from this file; 0 of 55 checkbox items are ticked.
checklists/testing.md
Memory Engine Testing Checklist
Core Interface Testing
- [ ] TEST-010-001: Verify IMemoryEngine initialization with Redis and Neo4j connections
- [ ] TEST-010-002: Validate IComponent lifecycle (initialize, start, stop, get_health)
- [ ] TEST-010-003: Test DI container registration and resolution of IMemoryEngine
- [ ] TEST-010-004: Verify health check returns accurate storage layer status
- [ ] TEST-010-005: Test graceful shutdown releases all storage connections
Store Operation Testing
- [ ] TEST-010-006: Store memory entry with explicit key and verify retrieval
- [ ] TEST-010-007: Store memory entry without key and verify UUID4 auto-generation
- [ ] TEST-010-008: Store with tags and verify tag-based retrieval
- [ ] TEST-010-009: Store with metadata and verify metadata preservation
- [ ] TEST-010-010: Verify embedding computed on store (non-null, correct dimensionality)
- [ ] TEST-010-011: Verify hot storage (Redis) written synchronously
- [ ] TEST-010-012: Verify cold storage (Neo4j) written asynchronously
Retrieve Operation Testing
- [ ] TEST-010-013: Retrieve existing entry by exact key
- [ ] TEST-010-014: Retrieve non-existent key returns None
- [ ] TEST-010-015: Retrieve by tag filter returns correct entries
- [ ] TEST-010-016: Retrieve by session_id returns session-scoped entries
- [ ] TEST-010-017: Retrieve by task_id returns task-scoped entries
- [ ] TEST-010-018: Verify metadata included in retrieved entries
Search Operation Testing
- [ ] TEST-010-019: Semantic search returns relevant results for natural language query
- [ ] TEST-010-020: Search with scope filter restricts results correctly
- [ ] TEST-010-021: Search respects result limit parameter
- [ ] TEST-010-022: Search respects minimum similarity threshold
- [ ] TEST-010-023: Hybrid search combines keyword and semantic results
- [ ] TEST-010-024: Search with empty corpus returns empty results
Summarize Operation Testing
- [ ] TEST-010-025: Summarize respects max_tokens budget
- [ ] TEST-010-026: Summarize includes source references
- [ ] TEST-010-027: Summarize with empty context returns empty summary
- [ ] TEST-010-028: Summarize preserves key decisions from source memories
- [ ] TEST-010-029: Token count in summary within 5% of budget
Storage Migration Testing
- [ ] TEST-010-030: Hot-to-cold migration on session end preserves all entries
- [ ] TEST-010-031: Cold-to-hot promotion loads relevant memories
- [ ] TEST-010-032: Expired hot entries garbage-collected correctly
- [ ] TEST-010-033: Memory lineage preserved through migration
- [ ] TEST-010-034: Migration count matches expected entry count
Embedding Testing
- [ ] TEST-010-035: Embedding generation produces correct dimensionality
- [ ] TEST-010-036: Same input produces same embedding (deterministic)
- [ ] TEST-010-037: Embedding cache prevents recomputation
- [ ] TEST-010-038: Cosine similarity returns correct ranking
- [ ] TEST-010-039: Similarity threshold filtering works correctly
Governance Testing
- [ ] TEST-010-040: PAM++ blocks unauthorized memory access
- [ ] TEST-010-041: Private memories invisible to other Volks
- [ ] TEST-010-042: Shared memories accessible to team members
- [ ] TEST-010-043: System memories accessible to all agents
- [ ] TEST-010-044: Audit trail records all memory operations
- [ ] TEST-010-045: Delegation chain grants transitive access
Performance Testing
- [ ] TEST-010-046: Hot store latency < 10ms (P95) under load
- [ ] TEST-010-047: Hot retrieve latency < 5ms (P95) under load
- [ ] TEST-010-048: Hot search latency < 50ms (P95) under load
- [ ] TEST-010-049: Cold retrieve latency < 50ms (P95) under load
- [ ] TEST-010-050: Cold search latency < 200ms (P95) under load
- [ ] TEST-010-051: Concurrent operations (500+/sec) handled without errors
Resilience Testing
- [ ] TEST-010-052: System degrades gracefully when Redis unavailable
- [ ] TEST-010-053: System degrades gracefully when Neo4j unavailable
- [ ] TEST-010-054: Embedding service failure falls back to keyword search
- [ ] TEST-010-055: Recovery from dual failure preserves cold storage data
Integration Testing
- [ ] TEST-010-056: Kernel injects memories into Volk context before execution
- [ ] TEST-010-057: Session end triggers hot-to-cold migration
- [ ] TEST-010-058: CXP packets include memory references in context field
- [ ] TEST-010-059: Knowledge Graph (SPEC-008) and Memory Engine share Neo4j without conflict
- [ ] TEST-010-060: PAM++ governance integration enforces policies end-to-end
Edge Cases
- [ ] TEST-010-061: Store with empty content handled gracefully
- [ ] TEST-010-062: Search with very long query handled (truncation)
- [ ] TEST-010-063: Store with maximum tag count handled
- [ ] TEST-010-064: Concurrent writes to same key handled (last-write-wins or conflict error)
- [ ] TEST-010-065: Migration with zero entries completes without error
Other tracked files
specs/010-memory-engine/README.mdspecs/010-memory-engine/compliance-audit.mdspecs/010-memory-engine/contracts/api.yamlspecs/010-memory-engine/contracts/events.yamlspecs/010-memory-engine/contracts/graph-schema.cql
Full spec text
Verbatim from specs/010-memory-engine/spec.md; headings are pushed down two levels.
SPEC-010: Memory Engine
Metadata
- Spec ID: 010-memory-engine
- Title: Memory Engine
- Author: Mohamed
- Status: Draft
- Created: 2026-03-09
- Last Updated: 2026-03-09
- Priority: P1
- Components: IMemoryEngine, MemoryEngine, MemoryStore, ContextCompressor
- Dependencies: Redis 7.0+, Neo4j 5.0+, Python 3.11+, orchestrata-core, orchestrata-session, orchestrata-knowledge
Overview
What
The Memory Engine is the unified agent memory abstraction for Orchestrata. It provides store, retrieve, search, and summarize operations over a dual-layer storage system (Redis hot / Neo4j cold), enabling Volks to maintain working memory within sessions and long-term memory across sessions without exceeding LLM context windows.
Why
- Volks need persistent memory that survives session boundaries
- Context windows are finite — agents need intelligent context selection
- Knowledge Graph (SPEC-008) stores code structure, not agent reasoning and decisions
- Session management (SPEC-006) tracks session state, not agent working memory
- No unified interface exists for agents to store/retrieve contextual memory
How
- IMemoryEngine interface registered in DI container as ComponentType.MEMORY_ENGINE
- Dual-layer storage: Redis for hot working memory (< 50ms), Neo4j for cold long-term memory
- Embedding-based semantic search via sentence-transformers for relevance ranking
- Token-aware context compression to fit within provider context windows
- CXP integration for memory-related inter-agent communication
- PAM++ governance on all memory operations
User Scenarios & Stories
US-001: Working Memory Storage (P1)
As a Volk I want to store intermediate reasoning and decisions during a task So that I can reference them later in the same session without re-deriving them
Acceptance Criteria:
- [ ] Volk can store key-value memory entries tagged by session and task
- [ ] Entries are retrievable by key, tag, or semantic query
- [ ] Working memory persists for the duration of the session
- [ ] Memory entries include metadata (timestamp, volk_id, task_id, confidence)
US-002: Cross-Session Memory (P1)
As a Volk I want to access decisions and learnings from previous sessions So that I build on prior work instead of starting from scratch
Acceptance Criteria:
- [ ] Long-term memories survive session boundaries
- [ ] Memories are retrievable by semantic similarity to current context
- [ ] Temporal decay applied to older memories (configurable half-life)
- [ ] Volk-specific memories isolated by PAM++ ownership
US-003: Context Window Management (P1)
As the Kernel I want to inject relevant memories into Volk context without exceeding token limits So that Volks operate within provider context windows
Acceptance Criteria:
- [ ] Context budget configurable per provider (e.g., 8K tokens for memory)
- [ ] Memories ranked by relevance, recency, and importance
- [ ] Automatic summarization when memory exceeds budget
- [ ] Token counting aligned with target provider's tokenizer
US-004: Semantic Search (P2)
As a Volk I want to search memories by meaning rather than exact keywords So that I find relevant context even when terminology differs
Acceptance Criteria:
- [ ] Embedding-based similarity search across memory entries
- [ ] Configurable similarity threshold (default 0.7)
- [ ] Results ranked by combined relevance + recency score
- [ ] Search scoped by session, task, volk, or portfolio
US-005: Memory Summarization (P2)
As the system I want to compress verbose memory entries into summaries So that long-running sessions don't exhaust context budgets
Acceptance Criteria:
- [ ] Summarization triggered when memory exceeds configurable threshold
- [ ] Original entries preserved in cold storage, summaries in hot storage
- [ ] Summarization preserves key decisions, conclusions, and action items
- [ ] Summarization auditable via Neo4j audit trail
US-006: Shared Team Memory (P2)
As a team of Volks I want to share relevant memories across the team So that coordination happens through shared context, not just CXP messages
Acceptance Criteria:
- [ ] Memories can be tagged as shared (team-visible) or private (volk-only)
- [ ] PAM++ governs who can read/write shared memories
- [ ] Shared memories scoped to project or portfolio
- [ ] Conflict resolution for concurrent shared memory writes
Functional Requirements
Core Interface
FR-001: IMemoryEngine Interface (P1)
- System SHALL define IMemoryEngine as an abstract base class in orchestrata-core interfaces
- System SHALL register IMemoryEngine with ComponentType.MEMORY_ENGINE in DI container
- System SHALL implement IComponent lifecycle (initialize, start, stop, get_health)
FR-002: Store Operation (P1)
- System SHALL accept memory entries with: content (str), key (optional str), tags (list[str]), metadata (dict)
- System SHALL auto-generate keys when not provided (UUID4)
- System SHALL compute embeddings for content on store
- System SHALL write to hot storage (Redis) immediately
- System SHALL write to cold storage (Neo4j) asynchronously
FR-003: Retrieve Operation (P1)
- System SHALL support retrieval by exact key
- System SHALL support retrieval by tag filter
- System SHALL support retrieval by session_id and/or task_id
- System SHALL return entries with metadata (timestamp, volk_id, relevance_score)
FR-004: Search Operation (P1)
- System SHALL support semantic search via embedding similarity
- System SHALL support hybrid search (keyword + semantic)
- System SHALL accept scope filters (session, task, volk, project, portfolio)
- System SHALL return results ranked by combined relevance score
- System SHALL enforce configurable result limit (default: 10)
FR-005: Summarize Operation (P2)
- System SHALL accept a context_id (session or task) and max_tokens budget
- System SHALL select memories by relevance to current context
- System SHALL compress selected memories to fit within token budget
- System SHALL return a structured summary with source references
Storage Layer
FR-006: Hot Storage (Redis) (P1)
- System SHALL store working memory in Redis with TTL matching session duration
- System SHALL use Redis hash structures keyed by session_id:volk_id
- System SHALL support pub/sub for memory change notifications
- System SHALL store embedding vectors in Redis for fast similarity search
FR-007: Cold Storage (Neo4j) (P1)
- System SHALL persist long-term memories as Memory nodes in Neo4j
- System SHALL link Memory nodes to Session, Volk, Task, and Project nodes
- System SHALL support temporal queries (memories from date range)
- System SHALL support graph traversal for related memories
FR-008: Storage Migration (P1)
- System SHALL migrate working memories to cold storage on session end
- System SHALL promote cold memories to hot storage when session context matches
- System SHALL garbage-collect expired hot storage entries
- System SHALL preserve memory lineage (summary -> original chain)
Embedding & Search
FR-009: Embedding Generation (P1)
- System SHALL compute embeddings using sentence-transformers (all-MiniLM-L6-v2 default)
- System SHALL cache embeddings to avoid recomputation
- System SHALL support configurable embedding model
- System SHALL normalize embeddings for cosine similarity
FR-010: Similarity Search (P2)
- System SHALL implement cosine similarity search over embedding vectors
- System SHALL support approximate nearest neighbor for large memory sets
- System SHALL combine semantic similarity with recency weighting
- System SHALL support minimum similarity threshold filtering
Context Management
FR-011: Token Counting (P1)
- System SHALL count tokens using provider-specific tokenizers
- System SHALL support tiktoken (OpenAI), anthropic tokenizer, and fallback word-based counting
- System SHALL track cumulative token usage per context injection
FR-012: Context Budget (P1)
- System SHALL enforce per-request token budgets for memory injection
- System SHALL prioritize memories by: (1) explicit relevance, (2) semantic similarity, (3) recency
- System SHALL truncate or summarize memories that exceed individual entry limits
- System SHALL return remaining budget after injection
Governance
FR-013: PAM++ Integration (P1)
- System SHALL enforce ownership on all memory operations
- System SHALL query Prolog governance for memory access permissions
- System SHALL log all memory operations to audit trail
- System SHALL support delegation chains for shared memory access
FR-014: Memory Classification (P1)
- System SHALL classify memories as: private (volk-only), shared (team), or system (all agents)
- System SHALL enforce classification boundaries in all operations
- System SHALL support reclassification with audit logging
FR-015: Temporal Decay (P2)
- System SHALL apply exponential temporal decay to memory relevance scores at query time
- System SHALL use configurable half-life per memory classification (default: private=24h, shared=7d, system=30d)
- System SHALL compute decay as:
relevance *= exp(-ln(2) * age_hours / half_life_hours) - System SHALL allow override of decay via explicit importance score on MemoryEntry
FR-016: Update Operation (P1)
- System SHALL support partial updates to existing memory entries by key
- System SHALL recompute embeddings when content is modified
- System SHALL update
updated_attimestamp on any modification - System SHALL log update operations to audit trail
FR-017: Content Size Limits (P1)
- System SHALL enforce maximum content length of 32KB per memory entry
- System SHALL enforce maximum tag count of 50 per memory entry
- System SHALL reject store/update operations that exceed limits with clear error
Non-Functional Requirements
Performance
NFR-001: Hot Storage Latency
- Store operation: < 10ms (P95)
- Retrieve by key: < 5ms (P95)
- Semantic search (hot): < 50ms (P95)
NFR-002: Cold Storage Latency
- Retrieve by key: < 50ms (P95)
- Semantic search (cold): < 200ms (P95)
- Cross-session retrieval: < 100ms (P95)
NFR-002b: Summarization Latency
- Extractive summarization: < 100ms (P95)
- LLM-based summarization: < 5000ms (P95) — used only when extractive quality is insufficient
- Summarization defaults to extractive (truncation + key sentence extraction) for P1; LLM-based deferred to P2
NFR-003: Throughput
- Concurrent memory operations: 500+ per second
- Concurrent searches: 100+ per second
- Embedding generation: < 20ms per entry
Reliability
NFR-004: Data Durability
- Zero data loss for committed cold storage entries
- Best-effort durability for hot storage (Redis persistence)
- Automatic hot-to-cold migration on session end
NFR-005: Availability
- Graceful degradation when Redis unavailable (fall back to cold storage)
- Graceful degradation when Neo4j unavailable (hot storage only mode)
- Health check endpoint for monitoring
Scalability
NFR-006: Storage Limits
- Support 1M+ memory entries per portfolio
- Support 10K+ entries per session
- Support 100+ concurrent Volk memory sessions
- Embedding storage: support 1M+ vectors
Security
NFR-007: Access Control
- All operations authenticated via Volk identity
- PAM++ enforcement on every read/write
- Encryption at rest for sensitive memory entries
- No cross-volk memory leakage without explicit sharing
Technical Design
Architecture
+-------------------------------------------------------------+
| Memory Engine System |
+-------------------------------------------------------------+
| +--------------+ +--------------+ +--------------+ |
| | Memory | | Context | | Embedding | |
| | Store | | Compressor | | Service | |
| +--------------+ +--------------+ +--------------+ |
| | | | |
| v v v |
| +-----------------------------------------------------+ |
| | Dual Storage Layer | |
| | +-----------------+ +-------------------------+ | |
| | | Redis (Hot) | | Neo4j (Cold) | | |
| | | Working memory | | Long-term memory | | |
| | | Embeddings | | Memory graph nodes | | |
| | | TTL-based | | Temporal queries | | |
| | +-----------------+ +-------------------------+ | |
| +-----------------------------------------------------+ |
+-------------------------------------------------------------+
Key Interfaces
class IMemoryEngine(IComponent, ABC):
"""Unified agent memory abstraction."""
@abstractmethod
async def store(
self,
content: str,
*,
key: str | None = None,
tags: list[str] | None = None,
metadata: dict | None = None,
classification: MemoryClassification = MemoryClassification.PRIVATE,
) -> MemoryEntry:
"""Store a memory entry. Returns the created entry with generated key."""
@abstractmethod
async def retrieve(
self,
key: str,
*,
include_metadata: bool = True,
) -> MemoryEntry | None:
"""Retrieve a memory entry by exact key."""
@abstractmethod
async def search(
self,
query: str,
*,
scope: MemoryScope | None = None,
limit: int = 10,
min_similarity: float = 0.7,
) -> list[MemoryEntry]:
"""Semantic search over memory entries."""
@abstractmethod
async def summarize(
self,
context_id: str,
*,
max_tokens: int = 2048,
strategy: SummarizationStrategy = SummarizationStrategy.RELEVANCE,
) -> MemorySummary:
"""Compress memories for context_id into a token-bounded summary."""
@abstractmethod
async def update(
self,
key: str,
*,
content: str | None = None,
tags: list[str] | None = None,
metadata: dict | None = None,
classification: MemoryClassification | None = None,
) -> MemoryEntry:
"""Update an existing memory entry. Only provided fields are modified."""
@abstractmethod
async def delete(self, key: str) -> bool:
"""Delete a memory entry by key."""
@abstractmethod
async def migrate_to_cold(self, session_id: str) -> int:
"""Migrate all hot memories for session to cold storage. Returns count."""
class MemoryClassification(Enum):
PRIVATE = "private"
SHARED = "shared"
SYSTEM = "system"
class MemoryScope:
session_id: str | None = None
task_id: str | None = None
volk_id: str | None = None
project_id: str | None = None
portfolio_id: str | None = None
date_from: datetime | None = None
date_to: datetime | None = None
class MemoryEntry:
key: str
content: str
summary: str | None
tags: list[str]
metadata: dict
classification: MemoryClassification
embedding: list[float] | None
created_at: datetime
updated_at: datetime
volk_id: str
session_id: str
relevance_score: float | None
class MemorySummary:
content: str
token_count: int
source_count: int
source_keys: list[str]
strategy: SummarizationStrategy
created_at: datetime
class SummarizationStrategy(Enum):
RELEVANCE = "relevance" # Rank by semantic similarity to current context
RECENCY = "recency" # Rank by temporal proximity
IMPORTANCE = "importance" # Rank by explicit importance scores
Neo4j Memory Schema
CREATE CONSTRAINT memory_key IF NOT EXISTS
FOR (m:Memory) REQUIRE m.key IS UNIQUE;
CREATE INDEX memory_volk IF NOT EXISTS
FOR (m:Memory) ON (m.volk_id);
CREATE INDEX memory_session IF NOT EXISTS
FOR (m:Memory) ON (m.session_id);
CREATE INDEX memory_created IF NOT EXISTS
FOR (m:Memory) ON (m.created_at);
-- (:Memory)-[:BELONGS_TO]->(:Session)
-- (:Memory)-[:CREATED_BY]->(:Volk)
-- (:Memory)-[:RELATES_TO]->(:Task)
-- (:Memory)-[:PART_OF]->(:Project)
-- (:Memory)-[:SUMMARIZED_BY]->(:Memory)
-- (:Memory)-[:SHARED_WITH]->(:Volk)
-- (:Memory)-[:DERIVED_FROM]->(:Memory)
Design Notes
- volk_id and session_id: Injected from authenticated caller context by the Kernel, never sent in API request bodies. The StoreRequest in api.yaml intentionally omits these fields.
- Embeddings: Internal-only. Never returned via API responses. The api.yaml MemoryEntry schema intentionally omits the
embeddingfield. - Volk nodes in Neo4j: The Memory Engine creates
(:Volk)nodes on first memory store if they don't exist. These are shared with SPEC-008 Knowledge Graph (same label, same Neo4j instance). - Concurrent shared writes: Use last-write-wins semantics. MemoryEntry has no version field — concurrent writes to the same key result in the last writer prevailing. For critical shared memories, Volks use the existing
IRedisStateManager.acquire_lock()before writing. - Encryption at rest: Deferred to P2. When implemented, will use application-level AES-256-GCM per entry. Encrypted entries cannot participate in semantic search — they are excluded from embedding computation and retrieved by exact key only.
- Embedding model swap: Changing embedding model invalidates all cached embeddings. A background re-embedding job runs on model change, processing entries in batches. Until re-embedding completes, search falls back to keyword matching.
Integration Points
| Component | Integration |
|---|---|
| orchestrata-core | IMemoryEngine interface, ComponentType.MEMORY_ENGINE |
| orchestrata-kernel | Context injection before Volk execution |
| orchestrata-session | Hot-to-cold migration on session end |
| orchestrata-knowledge | Cold storage in same Neo4j instance |
| orchestrata-cxp | Memory references in CXP packet metadata dict |
| orchestrata-governance | PAM++ memory access policy enforcement |
| orchestrata-agents | Volk memory access during execution |
Kernel Context Injection
The Kernel injects memory context into Volk execution at the existing governance check point in runtime.py, after Volk definition lookup and before execution begins.
Injection flow:
- Kernel calls
memory_engine.search(query=task_context, scope=MemoryScope(volk_id=volk.alias, session_id=current_session)) - Results ranked by relevance + recency
- Kernel calls
memory_engine.summarize(context_id=session_id, max_tokens=memory_budget)to fit within token budget - Summary injected into Volk system prompt via
metadata["memory_context"]on the CXP packet
Configuration:
# configs/production.yaml
runtime:
memory_engine:
injection_budget_tokens: 4096 # max tokens for memory context
injection_strategy: "relevance" # relevance | recency | hybrid
injection_enabled: true # toggle memory injection
Session Lifecycle Integration
Memory migration is triggered by the existing on_session_close hook in session/hook_registry.py.
Migration flow:
on_session_closefires withsession_idandoutcome- Hook calls
memory_engine.migrate_to_cold(session_id) - Migration reads all hot entries from Redis (
orchestrata:memory:{session_id}:*) - Each entry persisted as
(:Memory)node in Neo4j with relationships - Redis keys deleted after successful Neo4j write (atomic per-entry)
- Migration count logged as session fact via
FactEmitter
Failure handling:
- Neo4j unavailable: entries remain in Redis with extended TTL (7 days)
- Partial failure: successfully migrated entries deleted, failed entries retained
- Retry: next session start checks for orphaned hot entries and retries migration
Async bridge: The Memory Engine subscribes to session.closed events via the Kernel EventBus during initialize(). When a session closes, the Kernel emits a session.closed event, and the EventBus delivers it asynchronously to the Memory Engine's handler, which calls migrate_to_cold(session_id). This eliminates the sync/async mismatch entirely — no changes to SPEC-006 required, no direct Redis pub/sub needed.
Prolog Governance Predicates
The Memory Engine extends the PAM++ rule base with these Prolog predicates:
%% Memory access governance
can_store_memory(VolkId, Classification) :-
registered(VolkId),
memory_classification_allowed(VolkId, Classification).
can_read_memory(VolkId, MemoryKey) :-
registered(VolkId),
(memory_owner(MemoryKey, VolkId) ;
memory_classification(MemoryKey, shared) ;
memory_classification(MemoryKey, system)).
can_share_memory(VolkId, TargetVolkId, MemoryKey) :-
memory_owner(MemoryKey, VolkId),
registered(TargetVolkId),
\+ memory_share_blocked(VolkId, TargetVolkId).
can_delete_memory(VolkId, MemoryKey) :-
memory_owner(MemoryKey, VolkId).
%% Default: all volks can store private memories
memory_classification_allowed(_, private).
%% Shared/system require explicit grant
memory_classification_allowed(VolkId, shared) :- has_role(VolkId, team_lead).
memory_classification_allowed(VolkId, system) :- has_role(VolkId, system_admin).
CXP Integration
Memory references use the existing metadata dict on CXP packets. No schema change to SPEC-004 required.
Convention:
packet.metadata["memory_context"] = {
"injected_keys": ["mem-uuid-1", "mem-uuid-2"],
"token_budget": 4096,
"tokens_used": 3812,
"strategy": "relevance",
"summary": "..." # compressed summary text
}
Event Delivery
All memory events are emitted through the Kernel's EventBus (see SPEC-001 EventBus addendum). The Memory Engine does not push to Redis directly — it calls kernel.emit_event().
Emission pattern:
await kernel.emit_event(SystemEvent(
type="memory.store.completed",
source="SYS:MEMORY",
payload={"key": entry.key, "volk_id": volk_id, "classification": classification},
severity="info",
))
Topic mapping:
| Event | Topic | Severity |
|---|---|---|
| Memory stored | memory.store.completed |
info |
| Memory retrieved | memory.retrieve.completed |
info |
| Search completed | memory.search.completed |
info |
| Summarization done | memory.summarize.completed |
info |
| Migration completed | memory.migrate.completed |
info |
| Migration failed | memory.migrate.failed |
error |
| Token budget warning | memory.budget.warning |
warning |
| Access denied | memory.access.denied |
warning |
Reactive subscriptions (registered during initialize()):
# Subscribe to session close → trigger hot-to-cold migration
await kernel.event_bus.subscribe(
"session.closed",
self._on_session_closed,
subscriber_id="SYS:MEMORY",
)
This replaces the Redis pub/sub bridge described in Session Lifecycle Integration — the EventBus handles the async delivery, eliminating the sync/async hook mismatch.
Redis Hot Storage Format
Key patterns:
orchestrata:memory:{session_id}:{volk_id}:{key} — HASH (entry data)
orchestrata:memory:{session_id}:{volk_id}:index — ZSET (keys sorted by created_at)
orchestrata:memory:{session_id}:{volk_id}:tags:{tag} — SET (keys with this tag)
orchestrata:memory:embed:{key} — STRING (JSON-serialized float[] vector)
orchestrata:memory:embed:index:{session_id} — ZSET (keys sorted by relevance cache)
TTL: Matches session TTL (default 86400s / 24h). Extended to 604800s (7 days) on migration failure.
Hash fields for entry:
content — str (raw memory content)
summary — str | null
tags — JSON list[str]
metadata — JSON dict
classification — "private" | "shared" | "system"
volk_id — str
session_id — str
task_id — str | null
created_at — ISO 8601
updated_at — ISO 8601
token_count — int
Embedding Configuration
Embedding model is configurable via configs/production.yaml and swappable without code changes.
Configuration:
runtime:
memory_engine:
embedding:
model: "all-MiniLM-L6-v2" # sentence-transformers model name
dimension: 384 # output vector dimension
cache_embeddings: true # cache computed embeddings in Redis
batch_size: 32 # batch size for bulk embedding
device: "cpu" # cpu | cuda
Similarity search:
- Hot search: brute-force cosine similarity over Redis-cached vectors (sufficient for <10K entries/session)
- Cold search: Neo4j vector index (for cross-session retrieval over large corpora)
- Hybrid: hot first, cold fallback if result count < limit
Token Counting
Token counting uses a provider-aware abstraction, shared with SPEC-011 Provider Manager.
Interface:
class ITokenCounter(ABC):
@abstractmethod
def count(self, text: str) -> int: ...
@abstractmethod
def truncate(self, text: str, max_tokens: int) -> str: ...
Implementations:
TiktokenCounter— for OpenAI models (tiktoken library)AnthropicCounter— for Claude models (anthropic tokenizer)WordCounter— fallback, 1 token ≈ 0.75 words
The active counter is determined by the provider selected for the current Volk (from SPEC-011 Provider Manager). Falls back to WordCounter when provider is unknown.
Success Criteria
- Interface Complete: IMemoryEngine implemented with all 6 operations (store, retrieve, search, summarize, delete, migrate_to_cold)
- Dual Storage: Redis hot + Neo4j cold working with automatic migration
- Search Quality: Semantic search returns relevant results with > 0.8 precision@10
- Performance: Hot operations < 50ms, cold operations < 200ms (P95)
- Governance: PAM++ enforced on all memory operations
- Context Fit: Summarization respects token budgets within 5% tolerance
- Integration: DI-registered, Kernel-injectable, CXP-referenceable
Dependencies
- Redis 7.0+ (hot storage, pub/sub)
- Neo4j 5.0+ (cold storage, graph queries)
- sentence-transformers (embedding generation)
- tiktoken (OpenAI token counting)
- orchestrata-core (IComponent, ComponentType)
- orchestrata-session (session lifecycle hooks)
- orchestrata-knowledge (shared Neo4j instance)
- orchestrata-governance (PAM++ policy engine)
- orchestrata-cxp (packet context field)
Revision History
| Date | Version | Author | Changes |
|---|---|---|---|
| 2026-03-09 | 1.0 | Mohamed | Initial specification |