symfonic.memory.layers.episodic¶
episodic ¶
EpisodicLayer -- narrative events and timestamped history.
Wraps a VectorBackend for event storage and similarity-based retrieval. Implements BaseMemoryStore with link as a no-op (episodes are vector-stored).
EpisodicLayer ¶
EpisodicLayer(
vector_backend: VectorBackend,
embedding_provider: EmbeddingProvider,
telemetry_sink: EpisodicTelemetrySink | None = None,
)
Episodic memory layer for narrative events and history.
BaseMemoryStore contract
- retrieve: queries events by vector similarity
- write: stores an event with its embedding
- summarize: returns empty string (delegates to compaction engine)
- link: no-op (episodes are vector-stored, not graph-linked)
- delete: removes an event from the vector store
Source code in src/symfonic/memory/layers/episodic.py
count_events
async
¶
Return the total number of stored episodic events for the tenant.
Delegates to VectorBackend.count() when available, otherwise falls back to 0 to avoid a full scan with an arbitrary embedding.
Source code in src/symfonic/memory/layers/episodic.py
delete
async
¶
link
async
¶
list_events
async
¶
Return up to limit episodic events for the tenant.
Uses a neutral zero-vector query so that all stored events are
returned in approximate descending-similarity order (deterministic
for vectors with the same embedding, arbitrary but consistent
otherwise). Prefer this over query_events when you need a
full listing rather than a relevance-ranked subset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
TenantScope
|
Tenant scope for isolation. |
required |
limit
|
int
|
Maximum number of events to return (default 1000). |
1000
|
Returns:
| Type | Description |
|---|---|
list[MemoryEntry]
|
List of MemoryEntry instances (order: most-similar first). |
Notes
v7.13.6: when the embedder rejects embed("") (the OpenAI
embeddings endpoint returns 400 "input cannot be an empty
string") we transparently fall back to embed(" "). The
single-space probe is non-empty for OpenAI's validator while
remaining distance-agnostic enough to enumerate stored events
via similarity ranking. Sentence-transformers and other
backends that accept empty strings continue to use the
zero-string path. Fixes the v7.13.4 quick_nap phase-10 WARN
reported by Jarvio.
Source code in src/symfonic/memory/layers/episodic.py
query_events
async
¶
Query episodic events by vector similarity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
TenantScope
|
Tenant scope for isolation. |
required |
query
|
str
|
Natural language query to search against. Must be a non-empty, non-whitespace string. |
required |
top_k
|
int
|
Maximum number of results to return. |
5
|
Returns:
| Type | Description |
|---|---|
list[MemoryEntry]
|
List of MemoryEntry instances ranked by similarity. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/symfonic/memory/layers/episodic.py
render_for_prompt ¶
render_for_prompt(
entry: Any,
scrubber: Callable[[dict[str, Any]], dict[str, Any]]
| None = None,
) -> str
Render an episodic entry for MEMORY_CONTEXT.
v7.7: when metadata['speaker'] is present (per-message row
introduced in Phase B), emit [episodic] <speaker>: <content>
so the LLM sees a clean speaker-prefixed line. Pre-v7.7 rows
(no speaker) or rows tagged speaker == 'legacy' fall back
to :func:render_legacy for byte-identical pre-v7.7 output
and prompt-cache parity.
Source code in src/symfonic/memory/layers/episodic.py
retrieve
async
¶
Retrieve episodic events matching the query.
v7.22 T-7.22.8: emits :class:MultiScopeIgnoredWarning on first
encounter of a scope with inherits_from. Episodic events are
speaker-attributed timestamped records bound to a single
(tenant, sub_tenant) pair; merging inherited scopes would
corrupt per-tenant cost accounting and break GDPR scope
guarantees (v5.5.0 Gate 3).
Source code in src/symfonic/memory/layers/episodic.py
store_event
async
¶
store_event(
scope: TenantScope,
event: MemoryEntry,
*,
source_phase: str = "episodic.store_event",
) -> None
Store an event with its embedding in the vector backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
TenantScope
|
Tenant scope for isolation. |
required |
event
|
MemoryEntry
|
The memory entry representing the event. |
required |
source_phase
|
str
|
Tag identifying which caller triggered the
write (e.g. |
'episodic.store_event'
|
v6.2 T05 telemetry: when the caller populates event.metadata
with any of tokens_in / tokens_out / input_tokens /
output_tokens / cost_usd / model /
embedding_latency_ms, the values are lifted onto the
:class:EpisodicTelemetryRecord for the corpus. Keys missing
from metadata remain None and are stripped from the JSONL
line (v6.1 shape preserved byte-for-byte). The engine can source
the token / cost fields from LLMPreCallEvent /
LLMEndEvent and attach them to the metadata before calling
store_event; direct callers that do not consume LangChain
callbacks may pass the values in metadata themselves.
Source code in src/symfonic/memory/layers/episodic.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | |