Version: 0.1.0-draft · Status: draft v0.1.0 � subject to change · Date: 2026-05-26
memorywire is the wire-format specification for agent memory operations. It defines the operations, memory types, and data shapes exchanged between memory clients, the memory router, and backend memory stores. The reference implementation lives in
src/memorywire/. Schemas are written against JSON Schema Draft 2020-12.Context for the design choices in this document:
docs/kickoff/FINDINGS-CONTEXT.md� why this protocol existsdocs/kickoff/ARCHITECTURE.md� storage shape, RRF semantics, FSM formatdocs/kickoff/memorywire-SPEC-v0.md� original draft this document refines
memorywire is to agent memory what MCP is to agent tool-use: a vendor-neutral wire format so any memory client can talk to any backend, and any agent can carry its memory across runtimes.
memorywire defines:
- Five operations �
remember,recall,forget,merge,expire� with stable JSON schemas. - Four memory-type tags drawn from the human-memory taxonomy:
semantic,episodic,procedural,emotional. - A
MemoryStoreinterface that backends MUST implement. - A
Recallresult shape that clients SHOULD assume. - An optional
governancechannel for HITL approval workflows.
memorywire does NOT define:
- Which embedding model to use.
- Which vector store technology to use.
- Specific FSM semantics for procedural memory (uses pytransitions-compatible JSON; spec is open).
- Authentication or transport security (delegated to the surrounding application).
Each memory record carries one type tag from this fixed vocabulary:
| Type | Definition | Example |
|---|---|---|
semantic |
General facts, concepts, declarative knowledge | "Alice is allergic to peanuts" |
episodic |
Specific past events with time/place context | "On 2026-05-20 Alice told me she was nervous about her flight" |
procedural |
How-to knowledge, FSM-encoded procedures | The state machine for "book a flight" |
emotional |
Affective associations with experiences | "Alice expressed anxiety when discussing flights" |
These map 1:1 to the human-memory taxonomy used in cognitive-science literature and in the D. Biswas agentic AI module 7.
Backends MAY store all four types in one table with a type column, or shard by type. Clients MUST NOT assume a particular storage strategy.
All timestamp fields (stored_at, created_at, updated_at, expires_at, reviewed_at) are Unix epoch milliseconds as integers, per ARCHITECTURE.md §3.
The collapsed snippets below show the shape of each operation; the JSON Schema files are the source of truth for validation.
Write a new memory.
{
"$id": "https://memorywire.dev/schemas/v0/remember",
"type": "object",
"required": ["agent_id", "type", "content"],
"properties": {
"agent_id": { "type": "string", "minLength": 1, "maxLength": 256 },
"user_id": { "type": "string", "maxLength": 256 },
"type": { "enum": ["semantic", "episodic", "procedural", "emotional"] },
"content": { "type": "string", "minLength": 1 },
"metadata": { "type": "object", "additionalProperties": true },
"confidence": { "type": "number", "minimum": 0, "maximum": 1, "default": 1.0 },
"source": { "type": "string" },
"expires_at": { "type": "integer", "description": "Unix epoch ms; optional TTL" },
"approval_required": { "type": "boolean", "default": false }
}
}Full schema: src/memorywire/schemas/operations/remember.json
Response shape:
{
"id": "01HZK...",
"stored_at": 1716700000000,
"stores": ["sqlite-vec", "mem0"],
"pending_approval": false,
"approval_url": null
}If approval_required is true (or governance policy forces it), the response sets pending_approval: true and approval_url points to the governance UI. The memory is staged but not committed until approval � stores is the empty array while pending.
Read memories matching a query.
{
"$id": "https://memorywire.dev/schemas/v0/recall",
"type": "object",
"required": ["agent_id", "query"],
"properties": {
"agent_id": { "type": "string" },
"user_id": { "type": "string" },
"query": { "type": "string", "minLength": 1 },
"k": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 5 },
"types": { "type": "array", "items": { "enum": ["semantic", "episodic", "procedural", "emotional"] } },
"hops": { "type": "integer", "minimum": 0, "maximum": 3, "default": 0 },
"fusion": { "enum": ["rrf", "max", "weighted"], "default": "rrf" },
"filter": { "type": "object", "additionalProperties": true },
"fresher_than_days": { "type": "integer", "minimum": 0 }
}
}Full schema: src/memorywire/schemas/operations/recall.json
Response shape:
{
"results": [
{
"id": "01HZK...",
"type": "semantic",
"content": "Alice is allergic to peanuts",
"score": 0.94,
"metadata": { },
"created_at": 1716700000000,
"supporting": [],
"source_store": "sqlite-vec"
}
],
"fusion_used": "rrf",
"stores_queried": ["sqlite-vec", "mem0"],
"latency_ms": 247
}supporting lists related memory ids that contributed to the score via graph-hop boost (empty when hops=0). score magnitude depends on the fusion algorithm � see §5.
Delete memories matching a filter.
{
"$id": "https://memorywire.dev/schemas/v0/forget",
"type": "object",
"required": ["agent_id"],
"properties": {
"agent_id": { "type": "string" },
"user_id": { "type": "string" },
"ids": { "type": "array", "items": { "type": "string" } },
"filter": { "type": "object", "additionalProperties": true },
"hard_delete": { "type": "boolean", "default": false },
"reason": { "type": "string" }
}
}Full schema: src/memorywire/schemas/operations/forget.json
Soft delete (hard_delete: false, default) marks deleted_at but retains the row for audit. Hard delete removes the row. The audit log keeps the operation either way.
Editor's note: the kickoff draft does not specify a response shape for
forget. v0.1 standardizes it as:{ "forgotten_ids": ["01HZK..."], "hard_delete": false, "stores": [ { "store": "sqlite-vec", "count": 1 }, { "store": "mem0", "count": 1, "error": null } ], "pending_approval": false, "approval_url": null }
forgotten_idsis the set of memory ids actually removed (callers may pass afilterinstead of explicitids, so they need the resolved list back). The per-storestoresaggregate captures partial failures via the optionalerrorfield.pending_approval/approval_urlmirror therememberresponse and are set when governance intercepts the operation. Servers MUST reject requests where bothidsandfilterare absent (no-scope mass-delete protection).
Collapse two or more entities into one canonical entity. Used for deduplication.
{
"$id": "https://memorywire.dev/schemas/v0/merge",
"type": "object",
"required": ["agent_id", "canonical", "duplicates"],
"properties": {
"agent_id": { "type": "string" },
"canonical": { "type": "string", "description": "the surviving entity id or name" },
"duplicates": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
"strategy": { "enum": ["keep_canonical", "merge_content", "keep_highest_confidence"], "default": "keep_canonical" }
}
}Full schema: src/memorywire/schemas/operations/merge.json
Editor's note: the kickoff draft defines no response shape for
mergeand no per-strategy semantics. v0.1 sketches the response below as guidance � the schema for it is not authored at v0 (deferred to v0.2 once adapters return concrete per-store aggregates):{ "canonical": "01HZK...", "merged_count": 2, "strategy_used": "merge_content", "stores": ["sqlite-vec", "mem0"] }Per-strategy semantics:
keep_canonical� canonical row is preserved verbatim; duplicates are dropped after pointer migration.merge_content� backends MUST union metadata keys (last-write-wins on conflict, ordered bycreated_atascending) and MAY concatenatecontentwith a newline separator. The merged row'sconfidenceismax(confidence)over all merged rows.keep_highest_confidence� whichever row has the highestconfidencebecomes the new canonical content; ties are broken by oldercreated_at.
Apply a TTL policy to a memory subset.
{
"$id": "https://memorywire.dev/schemas/v0/expire",
"type": "object",
"required": ["agent_id"],
"properties": {
"agent_id": { "type": "string" },
"policy": {
"type": "object",
"properties": {
"older_than_days": { "type": "integer", "minimum": 1 },
"type": { "enum": ["semantic", "episodic", "procedural", "emotional"] },
"confidence_below": { "type": "number", "minimum": 0, "maximum": 1 },
"no_recall_in_days": { "type": "integer", "minimum": 1 }
}
},
"action": { "enum": ["forget", "archive", "demote"], "default": "forget" }
}
}Full schema: src/memorywire/schemas/operations/expire.json
Editor's note: the kickoff draft defines no response shape for
expire, nor behaviour for backends lacking last-recalled-at tracking. v0.1 sketches the response below as guidance � the schema for it is not authored at v0 (deferred to v0.2):{ "matched_count": 412, "action_taken": "forget", "stores": ["sqlite-vec", "mem0"] }Multiple policy keys are ANDed together. Backends that do not track last-recalled-at MUST return an error (rather than silently skipping the policy) when
no_recall_in_daysis set.action=demoteapplies a server-side score multiplier (default 0.25) to matched rows on subsequent recall without modifying the row.
Backends adopt memorywire by implementing a MemoryStore Protocol. Reference Python signature:
from typing import Protocol, Any
from memorywire.models import (
RememberRequest, RememberResponse,
RecallRequest, RecallResponse,
ForgetRequest, ForgetResponse,
MergeRequest, MergeResponse,
ExpireRequest, ExpireResponse,
)
class MemoryStore(Protocol):
async def remember(self, req: RememberRequest) -> RememberResponse: ...
async def recall(self, req: RecallRequest) -> RecallResponse: ...
async def forget(self, req: ForgetRequest) -> ForgetResponse: ...
async def merge(self, req: MergeRequest) -> MergeResponse: ...
async def expire(self, req: ExpireRequest) -> ExpireResponse: ...
async def health(self) -> dict[str, Any]: ...
@property
def capabilities(self) -> set[str]: ...capabilities is a set of strings declaring what the backend supports � e.g. {"semantic", "episodic", "fts", "vector", "graph", "procedural", "recall_tracking"}. The router uses this to skip stores that do not support a given operation.
The router is itself a MemoryStore, composed of N child stores. Per operation:
remember� fans out to all child stores by default;policy: "primary_only"writes only to the first.recall� fans out to all stores in parallel, fuses results perfusion.forget/merge/expire� fans out to all stores; aggregates per-store responses.
Fusion algorithms:
-
rrf(default) � Reciprocal Rank Fusion, k=60:score(item) = Σ (1 / (60 + rank_i(item))) for each store i -
max� take the highest score per item across stores. -
weighted� per-store weights from router config; sum weighted scores. Magnitudes are not normalized.
Graph-hop boost (when hops > 0):
final_score(item) = rrf_score(item) * (1 + 0.1 * (1 / (1 + min_hop_distance)))
When a client is configured with a GovernanceClient, remember / forget / merge operations with approval_required: true (or matching a policy rule) are routed through governance before commit.
Governance request:
{
"$id": "https://memorywire.dev/schemas/v0/governance/review",
"type": "object",
"required": ["operation", "request", "agent_id"],
"properties": {
"operation": { "enum": ["remember", "forget", "merge"] },
"agent_id": { "type": "string" },
"request": { "$ref": "https://memorywire.dev/schemas/v0/remember" },
"diff": {
"type": "object",
"description": "Structured diff against current memory state",
"properties": {
"added": { "type": "array" },
"removed": { "type": "array" },
"modified": { "type": "array" }
}
},
"reasoning": { "type": "string" }
}
}Governance response:
{
"approved": true,
"reviewer": "user@example.com",
"reviewed_at": 1716700000000,
"reason": "Looks correct; aligned with policy"
}The Pro-tier governance UI may layer an approval-learning loop on top: track which patterns the reviewer always approves / always rejects and auto-allow after N consistent decisions.
Editor's note: the
request.$refabove is shown ashttps://memorywire.dev/schemas/v0/rememberfor brevity. In practice therequestschema MUST be selected conditionally on theoperationenum value (one ofremember/forget/merge). The reference schema will be expressed withoneOf+if/thenin v0.2; v0 implementations may validaterequestagainst the matching operation schema and ignore the literal$refvalue.
Procedures are stored as JSON-serialized FSMs compatible with pytransitions:
{
"name": "book-flight",
"initial": "searching",
"states": ["searching", "comparing", "selecting", "paying", "confirmed", "cancelled"],
"transitions": [
{ "trigger": "found_options", "source": "searching", "dest": "comparing" },
{ "trigger": "picked", "source": "comparing", "dest": "selecting" },
{ "trigger": "paid", "source": "selecting", "dest": "paying" },
{ "trigger": "receipt", "source": "paying", "dest": "confirmed" },
{ "trigger": "cancel", "source": "*", "dest": "cancelled" }
],
"current": "searching",
"metadata": {
"agent_id": "travel-agent-v2",
"version": "1.0.0",
"created_at": 1716700000000
}
}This shape is intentionally minimal so backends can extend with their own state-machine semantics. Future versions may standardize action handlers.
Editor's note: the
"source": "*"value in thecanceltransition is the pytransitions wildcard idiom for "any source state". Adapters that re-validate transitions against the declaredstateslist MUST special-case"*". memorywire v0 schemas accept any non-empty string forsourceto preserve this convention.
In remember requests, the FSM JSON is currently carried as a string in content (because content is required to be a string in v0). Promoting procedural memory to a structured content field is tracked for v0.2 � see §11.
import asyncio
from memorywire import Memory, MemoryType
async def main():
mem = Memory(
agent_id="customer-support-bot",
stores=["sqlite-vec://./mem.db", "mem0://default"],
)
# Write a semantic fact
r = await mem.remember(
"Customer 12345 prefers email over phone",
type=MemoryType.SEMANTIC,
user_id="customer-12345",
confidence=0.95,
)
print(r.id, r.stores) # 01HZK..., ['sqlite-vec', 'mem0']
# Write an episodic memory
await mem.remember(
"On 2026-05-26 customer 12345 reported a billing issue",
type=MemoryType.EPISODIC,
user_id="customer-12345",
)
# Recall both
hits = await mem.recall(
"what do I know about customer 12345?",
k=10,
types=[MemoryType.SEMANTIC, MemoryType.EPISODIC],
fusion="rrf",
)
for hit in hits:
print(hit.score, hit.type, hit.content)
# Expire old episodic
await mem.expire(policy={"older_than_days": 90, "type": "episodic"})
asyncio.run(main())- memorywire v0 is draft. Breaking changes allowed until v0.5.
- v0.5 → v1.0: stabilization. Operations and schemas frozen.
- v1.0 → publish as IETF Internet-Draft for cross-language adoption.
Backwards compatibility rules:
- New optional fields � allowed at any time.
- New required fields � minor version bump, deprecation period.
- Removed fields � major version bump only.
- New memory types � minor version bump.
Deliberately not yet decided:
- Embedding metadata � should
remember()accept a pre-computed embedding, or always compute server-side? - Cross-agent sharing � should memorywire define a
shareoperation for inter-agent memory transfer? Likely v0.2. - Streaming
recall�recall_stream()for very largek? Defer to v1. - Privacy-intent flags � the "Layer 3 gap" from Module 11. Should
remember()carryprivacy_intent: {"retain": "30d", "share_with": ["agent-x"], "user_consent_id": "..."}? Likely v0.2. - Federated memory � multi-tenant scoping primitives. Defer to v1.
- Structured
contentfor procedural memory � promote FSM payloads from a string blob incontentto a typed field. Tracked for v0.2.
Standards we model after:
- JSON Schema 2020-12
- Model Context Protocol � for the cross-vendor MCP-shaped pattern
- OpenTelemetry semconv � for stable identifier patterns
- GenAI Semantic Conventions � for cross-tool tracing
Memory research informing the design:
- LongMemEval benchmark
- LoCoMo benchmark
- BEAM benchmark
- "Remember Me, Refine Me" � procedural memory
- "Governed Memory" arXiv 2603.17787 � Co-memorize HITL
- Mem0 architecture writeup
- Letta (MemGPT) hierarchical memory paper
Three examples per operation live under docs/spec/examples/. Each file is a single JSON object with description, _schema, request, response, and optional notes. Examples are validated against the operation schemas by scripts/verify_spec.py.
examples/remember/01_semantic_fact.json� a canonical semantic fact about a known user, written to a single store with high confidence.examples/remember/02_episodic_with_ttl.json� edge case: an episodic event withexpires_atTTL, nouser_id, and an emptymetadataobject.examples/remember/03_procedural_fsm_with_approval.json� a procedural FSM written withapproval_required: true, staged behind the governance channel.
examples/recall/01_simple_topk.json� natural-language query, defaultk=5, default RRF fusion, no type filter.examples/recall/02_multi_type_graph_hop.json� multi-type filter withhops=1graph boost andfresher_than_days=30recency window.examples/recall/03_weighted_cross_store.json�fusion: "weighted"across three heterogeneous stores with a customfilter.
examples/forget/01_by_ids.json� soft delete a small batch of memories by explicit id list.examples/forget/02_by_filter.json� filter-only deletion scoped byuser_idandtype.examples/forget/03_hard_delete_with_reason.json� GDPR-style hard delete with auditreason.
examples/merge/01_two_duplicates.json� two near-duplicates collapsed into one canonical entity with defaultkeep_canonicalstrategy.examples/merge/02_three_way.json� three-way merge usingkeep_highest_confidenceand entity-name addressing.examples/merge/03_merge_content.json�strategy: "merge_content"unions metadata and concatenates content across rows.
examples/expire/01_ttl_by_age.json� TTL by age: expireepisodicolder than 90 days with defaultaction: "forget".examples/expire/02_no_recall_archive.json� cold-storage policy:no_recall_in_days: 180withaction: "archive".examples/expire/03_low_confidence_demote.json� demote (not delete) stale low-confidence semantic facts.