symfonic.memory.layers.working¶
working ¶
WorkingLayer -- bounded active conversation context.
Session-scoped FIFO buffer backed by an optional GraphBackend for persistence. When a graph_store is provided, writes are mirrored to the graph so they survive across process restarts and can be counted by the metrics endpoint. The in-memory buffer is still used for fast within-session retrieval. Evicts oldest in-memory entries when the buffer exceeds max_entries.
WorkingLayer ¶
WorkingLayer(
max_entries: int = 10,
graph_store: object | None = None,
*,
stm_summary_mode: STMSummaryMode = "off",
working_graph_retention: int | None = None,
)
Working memory layer for active conversation context.
BaseMemoryStore contract
- retrieve: returns current context entries (from in-memory buffer)
- write: pushes an entry (FIFO eviction) and mirrors to graph if available
- summarize: returns empty string
- link: no-op
- delete: removes entry from graph when graph_store is set
- clear: clears in-memory deque AND all graph nodes for the tenant
When graph_store is provided, each write is also persisted as a MemoryNode with layer=WORKING so the metrics endpoint can count across restarts.
Graph retention cap: when working_graph_retention is set (an integer), the graph side is capped at that many nodes per tenant. On each push that exceeds the cap, the oldest nodes are pruned. The in-memory deque is governed separately by max_entries (FIFO eviction). The cap is intentionally opt-in (None = unbounded, the pre-F3 behaviour) so existing deployments that rely on the graph for long-term auditing are unaffected until they explicitly set a retention value.
Initialize the layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_entries
|
int
|
FIFO buffer ceiling per tenant. |
10
|
graph_store
|
object | None
|
Optional graph backend for dual-write persistence. |
None
|
stm_summary_mode
|
STMSummaryMode
|
v7.0.1 R3. Controls the default behaviour of
:meth: |
'off'
|
Source code in src/symfonic/memory/layers/working.py
clear
async
¶
Clear all working context for the tenant.
Empties the in-memory deque AND deletes all WORKING-layer graph nodes for this tenant when a graph_store is configured. Nodes belonging to other tenants are untouched (tenant isolation is enforced by the query_nodes filter).
Source code in src/symfonic/memory/layers/working.py
delete
async
¶
Remove a working memory node from the graph backend.
The in-memory deque uses FIFO eviction only; this method targets the graph-persistence side so that explicit deletes (e.g. triggered by retention-cap pruning or a user request) actually remove the node from the durable store.
When graph_store is not configured the call is a no-op (backward compatible with deployments that use in-memory-only working memory).
Source code in src/symfonic/memory/layers/working.py
get_context
async
¶
Get all current working context entries for the tenant.
Returns entries in chronological order (oldest first).
Source code in src/symfonic/memory/layers/working.py
link
async
¶
push
async
¶
Push an entry to the working memory buffer.
If the buffer is at capacity, the oldest entry is evicted (FIFO). If a graph_store is available, the entry is also persisted as a node. When working_graph_retention is set, the oldest graph nodes for this tenant are pruned after the write so the count stays at or below the configured cap.
v7.26.2 (Shape C1): the optional durability kwarg marks the
dual-written graph node so the sleep-consolidation pipeline can
skip promotion of transient/expired rows. None (default)
writes NO durability key -- byte-identical to pre-v7.26.2 (the
node reads as "durable" on the consolidation side). Resolution
order: explicit kwarg > entry.metadata['durability'] > unset.
An invalid value raises :class:ValueError at the call site
(fail-loud, never a silent gate bypass).
Source code in src/symfonic/memory/layers/working.py
rehydrate_from_messages
async
¶
rehydrate_from_messages(
scope: TenantScope,
messages: list[Any],
*,
max_turns: int | None = None,
) -> int
Replay the tail of a persisted transcript into the working deque.
v7.27.0 restart-resume (Q7, option-b): on session resume the
in-memory deque is empty (process restarted), but the durable
checkpointer still holds state['messages']. This method
reconstructs the RECENCY WINDOW only -- the last N user/assistant
turns -- as MemoryEntry rows tagged with the SAME
speaker/turn_id/source metadata shape the live
auto-turn writes use (engine.py), so downstream consumers cannot
tell a rehydrated row from a freshly-written one.
Scope of restore (LOCKED, plan §4.c): recency window ONLY. Tool / system messages are skipped (they are not conversational turns and would blow the cap with noise -- R4). Durable HMS state is NOT touched (it was never lost). No graph write is made -- this is a pure in-memory deque replay; persisting these rows again would double-count graph nodes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
TenantScope
|
Tenant scope selecting the target deque. |
required |
messages
|
list[Any]
|
The checkpointer's |
required |
max_turns
|
int | None
|
Cap on user+assistant rows replayed (counted as
individual rows, not pairs). Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The number of rows replayed into the deque. |
Source code in src/symfonic/memory/layers/working.py
render_for_prompt ¶
render_for_prompt(
entry: Any,
scrubber: Callable[[dict[str, Any]], dict[str, Any]]
| None = None,
) -> str
Render a working-memory entry for MEMORY_CONTEXT.
v7.7: same speaker-prefix shape as :class:EpisodicLayer --
per-message rows emit [working] <speaker>: <content>; pre-
v7.7 rows (no speaker, or speaker == 'legacy') delegate
to :func:render_legacy for byte-identical pre-v7.7 output.
v7.7.1: the engine's working-memory pre-hydration block (see
SymfonicAgent._hydrate_impl -- the recency-prefix loop
immediately preceding the polymorphic-dispatch render) calls
this method on each recent entry so the per-layer render
contract covers both code paths. Pre-7.7.1 the recency
prefix hand-built [working] <content> inline, bypassing
the protocol -- a stale docstring previously cited
engine.py:5184-5189 for the call site, which was both the
wrong line range AND a path that never invoked the renderer
(same bypass class as the v7.6.4 procedural-render-bypass).
Source code in src/symfonic/memory/layers/working.py
retrieve
async
¶
Retrieve current working context entries.
v7.22 T-7.22.9: emits :class:MultiScopeIgnoredWarning on first
encounter of a scope with inherits_from (plan §2.d wires
warning here ONLY -- push / get_context have no
scope-merge semantics to ignore, so emitting from them would be
a false-positive in adopter mental models). Working memory is a
per-(tenant, sub_tenant) FIFO buffer; merging inherited
scopes would either leak cross-tenant context or require a
merge policy with no defensible default.
Source code in src/symfonic/memory/layers/working.py
summarize
async
¶
Summarise working-memory entries according to stm_summary_mode.
v7.0 T09 shipped the extractive summariser but the in-layer
summarize() hard-coded mode="extractive" regardless of
config. v7.0.1 R3 wires the stm_summary_mode kwarg through
so direct-API callers see the same behaviour as engine-routed
callers:
"off"(default) — returns"". Preserves v6.1.x byte-for-byte behaviour."extractive"— delegates tosummarize_entrieswith the deterministic first-sentence / last-sentence heuristic. Zero-cost, no LLM call."llm"— the in-layersummarize()is synchronous- equivalent over the MemoryEntry list and does not own a model provider. Falls through to the extractive path so this call never stalls on an upstream outage; callers who want the true LLM micro-call must invoke :func:symfonic.memory.working.stm_summary.summarize_entriesdirectly with amodel_provider(the engine wires that path viaSymfonicAgent).
Empty input always returns the empty string so the v6.1.x observable "no meaningful summary" behaviour is preserved on empty buffers independent of mode.
Source code in src/symfonic/memory/layers/working.py
write
async
¶
Push an entry to the working buffer (FIFO eviction) and persist to graph.