symfonic.capabilities.memory.factory¶
factory ¶
Memory as something a host can compose, from a store and a scope.
The public counterpart to what SymfonicAgent._retrieval_bundle does
privately. A generated project needs memory on an Agent and today the only
way to get it is to build a SymfonicAgent, which is the dependency the
kernel-native scaffold exists to remove.
One store, one scope, one capability. Capabilities are scope-bound at construction -- a memory capability recalls, records and erases for exactly one scope -- which is why a host keeps one agent per scope rather than passing a scope per turn. This factory is where that binding happens, and it is the only place it needs to.
The three grants travel together, and that is not a convenience.
memory-read without memory-write is a deployment that recalls and never
records; memory-write without memory-flush is worse -- the write stage
stages a memory as pending, and a pending memory is not retrievable until
flush, so a turn would answer, the store would fill, and nothing could ever be
recalled. TA8.71 found exactly that shipped. Asking for memory means asking for
all three.
Nothing here imports the engine, reads a private, or knows what a turn is.
MemoryBundleFactory ¶
MemoryBundleFactory(store: Any, *, limit: int = 5, recall_budget: RecallBudget | None = None, extractor: MemoryExtractorPort | None = None, consolidation: Any | None = None, conversation: Any | None = None, recent_turns: int = 0, activation: Any | None = None)
Builds the memory capability for one scope over a shared store.
The store is shared across scopes and the capability is not: a store isolates by scope on every read and write, and a capability is a scope. Handing the same store to two capabilities is how two tenants share persistence without sharing memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Any
|
an HMS satisfying the retrieval, write and lifecycle ports. One object for all three because staging, publishing and erasing are operations on one place -- and because a deployment that split them would have to answer what happens when only two are present, which the ports already refuse. |
required |
limit
|
int
|
how many memories a turn recalls. |
5
|
recall_budget
|
RecallBudget | None
|
explicit UTF-8 block/item ceilings; independent of working-turn retention. None preserves legacy character caps. |
None
|
extractor
|
MemoryExtractorPort | None
|
a :class: |
None
|
consolidation
|
Any | None
|
a
:class: |
None
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if |
Source code in src/symfonic/capabilities/memory/factory.py
for_scope ¶
The capability that recalls, records and erases for scope.
Source code in src/symfonic/capabilities/memory/factory.py
as_memory_scope ¶
Accept either scope type, so a host is not forced to pick one.
A platform scope and a memory scope are two spellings of one identity, and making a caller convert would put the translation in every composition root instead of here.
to_memory_scope() is tried first and its result is checked, because
on FrameworkTenantScope that method returns another framework scope
rather than a memory scope. Trusting it returned an object with no
segments, and the mismatch surfaced far away -- inside a retrieval,
as a missing attribute on a type nobody in that traceback had named. So
the fallback below reads the scope's own path, which is the one identity
both spellings agree on.
Source code in src/symfonic/capabilities/memory/factory.py
memory_capabilities ¶
memory_capabilities(store: Any, scope: MemoryScope | Any, *, limit: int = 5, recall_budget: RecallBudget | None = None, extractor: MemoryExtractorPort | None = None, consolidation: Any | None = None, conversation: Any | None = None, recent_turns: int = 0, activation: Any | None = None) -> list[Any]
The capability and the grants it needs, as one list to compose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Any
|
an HMS satisfying the retrieval, write and lifecycle ports. |
required |
scope
|
MemoryScope | Any
|
the scope this agent serves. Closed over by the capability, so one agent is one tenant. |
required |
limit
|
int
|
how many memories a turn recalls. |
5
|
recall_budget
|
RecallBudget | None
|
explicit UTF-8 rendered recall ceilings. None preserves legacy character caps; unrelated to the working conversation window. |
None
|
extractor
|
MemoryExtractorPort | None
|
a :class: |
None
|
consolidation
|
Any | None
|
a :class: |
None
|
activation
|
Any | None
|
a :class: |
None
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if |
Returned together because a capability may not grant itself an effect and a caller that forgot one would get a fold refusal naming a grant rather than a missing feature. The three are what memory is.
Prompting is NOT included, and composing memory alone does not put recall in front of the model. Memory is a resolution stage: it reaches the store and leaves an entry in the turn's snapshot. Prompting is the compilation stage that reads that snapshot and renders it. Fold memory by itself and both ports are called, the block is composed, the snapshot is populated -- and the model receives the bare instructions, because nothing consumed the entry. That exact defect has been found in this codebase twice.
It is not added here because a factory named for memory that quietly
composed prompting would decide a deployment's prompt on its behalf. A
composition root wanting recall in the prompt adds a PromptingCapability
beside this, and :mod:tests.platform.test_vertical_slice shows the pair.