Memory Store
pkgs/store/memory is the provider-neutral long-term memory boundary used by Agent runtimes. Provider adapters live in the flowcraft, mem0, and volc subpackages.
Contract
Observation.Scope and Query.Scope are opaque product-owned namespaces. Callers decide which product context shares a memory namespace, but they do not provide provider-native fields such as runtime, user, agent, run, project, or worker IDs. Each adapter privately maps the opaque scope to its backend protocol.
result, err := store.Observe(ctx, memory.Observation{
Scope: memory.Scope("player:42"),
Turns: turns,
})
recalled, err := store.Recall(ctx, memory.Query{
Scope: memory.Scope("player:42"),
Text: "What does the player prefer?",
Limit: 10,
})Update and Delete take only the opaque fact ID previously returned by the store, plus an optional opaque revision. Wait takes only the opaque operation ID. Callers must not resubmit a scope for identity-based operations; adapters encode any routing state they need in those opaque locators.
Asynchronous Observe calls return an operation. Stores implementing OperationWaiter wait using the caller's context.Context; constructors do not start background goroutines. Flowcraft can recover durable operation locators after the adapter is reconstructed with the same injected stores.
Provider construction
Provider packages accept in-memory runtime dependencies only. They do not decode YAML, expand environment variables, open configuration files, or choose product identity.
Flowcraft is constructed with one flowcraft.Config. The config can inject a ModelLoader, retrieval index, temporal store, evidence store, async queue, and side-effect outbox. Injected dependencies remain caller-owned. If a dependency is omitted, the adapter uses Flowcraft's in-memory implementation.
store, err := flowcraft.New(ctx, flowcraft.Config{
Loader: loader,
Extraction: flowcraft.ExtractionConfig{Model: "extractor"},
Embedding: flowcraft.EmbeddingConfig{Model: "embedding"},
RetrievalIndex: index,
TemporalStore: temporal,
})Mem0 is constructed with one mem0.Config. FlavorPlatform uses Authorization: Token; FlavorSelfHosted uses X-API-Key when a key is supplied. The adapter maps an operation scope to Mem0's native user_id internally. Update and delete use the returned memory ID directly.
Volcengine AgentKit/Viking MEM0 is constructed with one volc.Config. It accepts either an explicit Mem0 data-plane key or a credential resolver. The adapter resolves credentials and delegates fact operations to the Mem0 adapter; a data-plane endpoint is mandatory.
Composition and YAML
cmd/internal/stores is the composition root. It owns serializable YAML DTOs, environment expansion, workspace/index construction, model-loader injection, credential resolution, and lifecycle management. A Flowcraft dir belongs to this layer: the command creates the Flowcraft workspace and BBH retrieval index, then injects their interfaces into the adapter.
stores:
agent-memory:
kind: memory
flowcraft:
dir: ${GIZCLAW_MEMORY_DIR}
extraction_model: memory-extractor
embedding_model: text-embedding
extraction_mode: single_pass
graph_enabled: true
async:
enabled: trueMem0 Platform:
stores:
agent-memory:
kind: memory
mem0:
endpoint: https://api.mem0.ai
api_key: ${MEM0_API_KEY}
flavor: platformVolcengine AgentKit/Viking MEM0:
stores:
agent-memory:
kind: memory
volc_memory:
mem0:
endpoint: ${VOLC_MEM0_ENDPOINT}
memory_project_id: ${VOLC_MEMORY_PROJECT_ID}
region: cn-beijing
access_key_id: ${VOLC_ACCESS_KEY_ID}
access_key_secret: ${VOLC_ACCESS_KEY_SECRET}A logical memory store selects exactly one provider. Unknown YAML fields are rejected. Scope and backend-native routing fields are not valid server configuration.
Ownership and errors
Provider adapters do not close injected dependencies. The composition root that constructs a workspace, index, HTTP client, or credential dependency owns it and closes resources in reverse construction order.
The stable sentinel errors are ErrInvalidInput, ErrNotFound, ErrUnsupported, ErrConflict, and ErrUnavailable. Providers preserve errors.Is behavior. If a provider cannot preserve a filter, attribute patch, or conditional-write semantic, it returns ErrUnsupported rather than discarding the condition. Errors must not expose API keys, access-key credentials, or credential-bearing response bodies.