How should you design memory APIs for agent frameworks?

Short answer: Expose a thin Engram-shaped surface—add, search, manage, check runs—with scope required on every call and no second private memory model.

Make incomplete scopes hard to forget. Split deterministic hooks from optional agent tool recall. Keep limits and thresholds public so frameworks do not over-fetch. Map thread ids and role stores to properties and groups. Log group, scopes, run id, and wait behavior so handoffs are debuggable.

Agent frameworks succeed or fail at the seams. Memory is one of those seams. If the API forces every specialist to invent its own save format, scopes leak and races return. If the API hides too much, agents cannot choose when to recall or wait. Designing memory APIs for agent frameworks means exposing a small set of durable operations that match how orchestrators and tools actually run. Weaviate Engram already provides that shape: add, search, manage, and check runs, with groups and scopes as first-class arguments. This chapter turns those primitives into framework design rules so memory stays infrastructure instead of folklore inside each agent prompt.

What Should a Framework Memory API Make Impossible to Forget?

Scope must travel with every write and every read. Engram rejects incomplete scope for topics that require user_id or custom properties. A good framework wrapper should not offer a “save this string” helper that omits those fields. It should require the case id, user id, or session id that the topic demands.

Group must be explicit when a product has more than one use case. Personalization and continual learning are different stores. Orchestrator plans and specialist craft are different stores. Defaulting everything to one silent group recreates the homogeneous pile that role partitioning worked hard to avoid.

Async truth must be visible. memories.add returns a run_id and a non-final status. Framework APIs that pretend the call is a synchronous database insert teach developers the wrong model. Expose the run handle. Let callers choose fire-and-forget or runs.wait.

How Should Frameworks Split Deterministic Hooks from Agent Tool Calls?

Not every recall should be a model decision. Engram’s application guidance covers both styles. Deterministic hooks can search before each user turn and inject high-scoring memories. Tool calls can expose memories.search when the agent needs mid-reasoning recall. Frameworks should support both, and document which path is default.

Writes follow the same split. Conversation turns can auto-add on every exchange. Discrete events can add string payloads. Agents that must decide what counts as memorable can submit pre-extracted facts. The API should not collapse those three input types into one vague “remember” button with hidden behavior.

Deterministic save-and-recall removes a common failure mode where the model forgets to remember. Agent-controlled search preserves flexibility for deep tool loops. A framework that only offers one of the two will feel either rigid or unreliable.

Which Retrieval Knobs Belong in the Public Surface?

Search needs a query, a scope, a group, and a retrieval config. Hybrid retrieval is the usual default. Vector and BM25 remain available for conceptual versus exact-term needs. Fetch retrieval belongs in the API for bounded topics such as a single profile or conversation summary that should be loaded by identity, not ranked by similarity.

Topic filters belong in the public surface too. Specialists should search only the topics they are allowed to use. Per-topic property overrides matter when one request spans a user fact topic and a conversation summary topic. Hiding those controls inside the framework forces every agent to over-fetch.

Limits are part of safety. A framework that always injects fifty memories will burn the context window and recreate full-history costs. Small limits with score thresholds keep memory additive instead of dominant.

How Can a Framework Wrap Engram Without Inventing a Second Memory Model?

The wrapper should be thin. Map framework concepts onto Engram concepts instead of translating into a private schema. A “thread id” becomes properties.conversation_id. A “tenant” becomes another property or a separate project. A “role store” becomes a group name. When the wrapper invents parallel abstractions, operators lose the ability to debug in the Engram console and docs.

Error surfaces should stay honest. Missing required scope should fail loudly. Topic-not-found on fetch should be catchable. Run failures should expose the error string from the run status. Silent retries that drop scope are how isolation breaks.

Concurrency policy should be declared at the API edge. Background saves do not wait. Handoff saves may wait. Bounded status updates should document that later writes converge on one object per scope. The framework is the right place to encode those defaults so every agent does not re-negotiate them.

What Does a Clean Framework Integration Look Like in Code?

Consider an interlibrary-loan desk framework coordinating request ill-request-903. The harness injects deterministic recall for the patron, exposes search as a tool for the specialist, and always passes scope.

from engram import EngramClient, HybridRetrieval

client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
patron_id = "patron-lee-27"
request_id = "ill-request-903"

def remember_turn(messages):
    # Framework auto-save: fire-and-forget for ordinary dialogue.
    return client.memories.add(
        messages,
        user_id=patron_id,
        group="personalization",
        properties={"request_id": request_id},
    )

def recall_for_turn(user_text):
    # Deterministic hook before the model runs.
    return client.memories.search(
        query=user_text,
        user_id=patron_id,
        group="personalization",
        properties={"request_id": request_id},
        retrieval_config=HybridRetrieval(limit=5),
    )

def tool_search_memory(query: str):
    # Agent tool: same Engram search, explicit scopes preserved.
    hits = client.memories.search(
        query=query,
        user_id=patron_id,
        group="personalization",
        properties={"request_id": request_id},
        retrieval_config=HybridRetrieval(limit=5),
    )
    return [{"content": m.content, "topic": m.topic} for m in hits]

def post_fulfillment_lesson(text: str):
    # Critical path: wait so the next agent can rely on the lesson.
    run = client.memories.add(
        text,
        group="continual_learning",
        properties={"request_id": request_id},
    )
    return client.runs.wait(run.run_id)

Version the wrapper’s defaults the way you version any public API. Changing the default group, the default wait policy, or the default retrieval limit is a breaking behavioral change for every agent that depended on the old silence. Publish those defaults beside the function signatures.

The framework never invents a second store. It decides when to wait, which group to use, and whether recall is a hook or a tool. Engram remains the memory system of record.

Keep observability in the same package. Log group, scope keys, run id, and whether the caller waited. When a handoff fails, those fields explain more than a prompt dump ever will.

Train new agent authors against the wrapper, not against raw Engram calls. One shared surface keeps scope keys and group names consistent when the cast of agents grows.

APIs make memory callable. Pipelines still need a clean handoff shape when one agent’s output becomes the next agent’s input. Our next chapter, How does memory handoff work between agents in a pipeline?, focuses on those staged transfers without collapsing back into one shared transcript.