Skip to content

Latest commit

 

History

History
527 lines (408 loc) · 22.7 KB

File metadata and controls

527 lines (408 loc) · 22.7 KB

memorywire � Specification v0

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:


1. Goals

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:

  1. Five operations � remember, recall, forget, merge, expire � with stable JSON schemas.
  2. Four memory-type tags drawn from the human-memory taxonomy: semantic, episodic, procedural, emotional.
  3. A MemoryStore interface that backends MUST implement.
  4. A Recall result shape that clients SHOULD assume.
  5. An optional governance channel 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).

2. Memory Types

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.


3. Operations

The collapsed snippets below show the shape of each operation; the JSON Schema files are the source of truth for validation.

3.1 remember

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.

3.2 recall

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.

3.3 forget

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_ids is the set of memory ids actually removed (callers may pass a filter instead of explicit ids, so they need the resolved list back). The per-store stores aggregate captures partial failures via the optional error field. pending_approval / approval_url mirror the remember response and are set when governance intercepts the operation. Servers MUST reject requests where both ids and filter are absent (no-scope mass-delete protection).

3.4 merge

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 merge and 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 by created_at ascending) and MAY concatenate content with a newline separator. The merged row's confidence is max(confidence) over all merged rows.
  • keep_highest_confidence â€â€� whichever row has the highest confidence becomes the new canonical content; ties are broken by older created_at.

3.5 expire

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_days is set. action=demote applies a server-side score multiplier (default 0.25) to matched rows on subsequent recall without modifying the row.


4. The MemoryStore interface

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.


5. Memory Router semantics

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 per fusion.
  • 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)))

6. Governance channel (optional)

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.$ref above is shown as https://memorywire.dev/schemas/v0/remember for brevity. In practice the request schema MUST be selected conditionally on the operation enum value (one of remember / forget / merge). The reference schema will be expressed with oneOf + if/then in v0.2; v0 implementations may validate request against the matching operation schema and ignore the literal $ref value.


7. Procedural memory format

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 the cancel transition is the pytransitions wildcard idiom for "any source state". Adapters that re-validate transitions against the declared states list MUST special-case "*". memorywire v0 schemas accept any non-empty string for source to 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.


8. Worked end-to-end example

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())

9. Spec versioning & evolution

  • 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.

10. Open questions for v0

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 share operation for inter-agent memory transfer? Likely v0.2.
  • Streaming recall â€â€� recall_stream() for very large k? Defer to v1.
  • Privacy-intent flags â€â€� the "Layer 3 gap" from Module 11. Should remember() carry privacy_intent: {"retain": "30d", "share_with": ["agent-x"], "user_consent_id": "..."}? Likely v0.2.
  • Federated memory â€â€� multi-tenant scoping primitives. Defer to v1.
  • Structured content for procedural memory â€â€� promote FSM payloads from a string blob in content to a typed field. Tracked for v0.2.

11. References

Standards we model after:

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

12. Worked examples

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.

remember

recall

forget

merge

expire