Generated banner for Engram Memory
Generated logo for Engram Memory

Engram Memory

WeaviateOfficialPublished by Weaviate

  • Skill
  • Tool integration
  • Data and AI
  • BSD-3-Clause
  • Low risk

From the author

Add persistent, long-term memory to LLM agents and chatbots using Engram, Weaviate's managed memory server. Use when an app needs to remember things across sessions — user preferences, profiles, past interactions, or lessons an agent learns over time (continual learning). Covers storing memories (string, conversation, pre-extracted input), semantic/keyword/hybrid search, scoping by user and topic, async run tracking, and chatbot/agent integration patterns.
— the description by Weaviate

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Engram is a managed memory server designed to give LLM agents the ability to remember information across sessions. By handling the extraction, consolidation, and storage of memories automatically, it allows developers to focus on integration rather than building custom memory infrastructure. The service supports semantic, keyword, and hybrid search, enabling agents to retrieve relevant user preferences, past interactions, or learned facts efficiently. It is designed for scenarios requiring continual learning and stateful interaction across multiple user sessions.

What it does

  • Provides persistent storage for agent memories and user profiles.
  • Supports semantic, keyword, and hybrid search for intelligent retrieval.
  • Handles automatic memory extraction, deduplication, and consolidation.
  • Enables scoping of memories by specific users or topics.
  • Offers asynchronous processing for memory storage operations.
  • Includes integration patterns for chatbots and agent-based architectures.

When to use it

  • When your AI application needs to maintain context across multiple user sessions.
  • When you need to store and retrieve user preferences or historical interaction data.
  • When building agents that require continual learning capabilities.
  • When you want to avoid building a custom memory management system from scratch.

Advantages

  • Reduces development time by offloading memory management to a managed service.
  • Ensures consistent memory retrieval through hybrid search capabilities.
  • Simplifies state management with built-in scoping by user and topic.
  • Provides a dedicated SDK for Python-based agent development.

Usage examples

“How do I store a user preference in Engram?”
Use the client.memories.add method to pass the information string and the associated user_id, which processes the memory asynchronously.
“How can I retrieve memories for a specific user?”
Use the client.memories.search method with a query string and the user_id, optionally configuring the retrieval type to hybrid or vector search.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/weaviate/engram-memory/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip engram-memory.zip -d .agents/skills

Documentation by Weaviate

Memory Management with Engram

This skill helps build applications with persistent memory using Engram, Weaviate's managed memory server for LLM agents. Engram automatically extracts, consolidates, and stores memories from raw text or conversations, then retrieves them with vector, keyword, or hybrid search.

Use this skill when the user wants their app to remember information across sessions. Do not hand-roll a memory system (a raw Weaviate collection with manual inserts) when Engram fits — it handles extraction, deduplication, and consolidation out of the box.

Engram Project

Engram is a managed service accessed via Weaviate Cloud. If the user does not have an Engram project, direct them to the cloud console to create one and generate an Engram API key. Create an Engram project via Weaviate Cloud.

Environment Variables

Required:

  • ENGRAM_API_KEY — Engram API key from Weaviate Cloud (format eng_...). This carries the project identity; WEAVIATE_URL / WEAVIATE_API_KEY are not needed for Engram itself.

Optional (only when combining Engram with an agent or chatbot for generation):

  • An LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY)

Installation

Engram ships as its own Python SDK, weaviate-engram — it does not come with weaviate-client. Install it (and import as engram):

uv add weaviate-engram   # or: pip install weaviate-engram

This skill targets weaviate-engram 1.0.x (current release 1.0.1; requires Python 3.11–3.14). 1.0 is the first stable API — upgrade if the project is on a 0.x release.

Engram also ships a Claude Code plugin that needs no application code (/plugin marketplace add weaviate/engram-plugins then /plugin install engram@weaviate-engram). See the reference's Off-the-shelf Integrations section before building a custom one.

Reference

  • Memory Management with Engram: The complete how-to guide for building Engram applications. Covers:

    • Concepts — memories, topics, groups, scopes, pipelines/runs.

    • Storing memories — string, conversation, and pre-extracted input; async run status and committed_operations.

    • Searching — vector, BM25, hybrid, and unranked fetch retrieval; topic filters, scope properties, and relevance-score cutoffs.

    • Managing memories — get and delete by id; deterministic cleanup.

    • Integration patterns — memory-backed chatbots and Engram-as-tools for the RouterAgent from the Basic Agent cookbook.

    • Off-the-shelf integrations — the Claude Code plugin.

    • REST API — equivalent endpoints for non-Python stacks.

    • Troubleshooting & Done Criteria — including the new-user APIError behaviour and async-pipeline gotchas.

Quick Start

The guide uses the async client by default. Minimal store-and-search:

import os
from engram import AsyncEngramClient, HybridRetrieval

client = AsyncEngramClient(api_key=os.environ["ENGRAM_API_KEY"])

# Store — fire-and-forget, processes asynchronously
run = await client.memories.add(
    "The user prefers dark mode and works primarily in Python.",
    user_id="alice",
)

# Search
results = await client.memories.search(
    query="What language does the user prefer?",
    user_id="alice",
    retrieval_config=HybridRetrieval(limit=5),
)

await client.aclose()  # or use `async with AsyncEngramClient(...) as client:`

Follow the reference guide for the full lifecycle, error handling, and integration patterns before building.

Error Handling

Common errors (see the reference's Troubleshooting section for the full list):

  • ENGRAM_API_KEY not set → set the environment variable; ensure it is an Engram key (eng_...), not a Weaviate cluster key.

  • APIError (422, user "..." not found) on search → nothing has ever been added for that user_id; catch it and treat as "no memories" (every chatbot's first message from a new user hits this).

  • AuthenticationError subclasses APIError → a bare except APIError around a search silently turns a bad API key into "no memories". Catch AuthenticationError first and re-raise it.

  • Search returns nothing right after add → storage is asynchronous; await client.runs.wait(run.run_id) before searching, or check the run status for failed. On a user's first write the tenant may still be initializing even after the run reports completed — retry the search briefly.

Files

reference/engram_memory.md

Memory Management with Engram

Add persistent, long-term memory to AI applications using Engram, Weaviate's managed memory server for LLM agents. Engram automatically extracts, consolidates, and stores memories from raw text or conversations, and retrieves them with vector, keyword, or hybrid search.

Use this cookbook when the user wants their app to remember things across sessions: user preferences, profiles, past interactions, or lessons learned by an agent (continual learning).

Read if integrating with an agent or chatbot:

  • Basic Agent Cookbook — for the RouterAgent and tool design patterns used below.

  • Query Agent Chatbot — for the chatbot integration pattern.

Docs to reference if needed:

Core Rules

  • Use uv for Python project/dependency management, and let it manage the virtual environment (uv venv).

  • Do not manually author pyproject.toml or uv.lock; let uv generate/update them.

  • Use this install set: uv add weaviate-engram python-dotenv (see Installation — Engram is a separate package, not part of weaviate-client)

  • If combining with an agent (recommended pairing), also follow the install set from Basic Agent: uv add dspy

  • Customise this cookbook to the users specification, ask them for details if not given.

  • Engram is a managed service accessed via Weaviate Cloud. If the user does not have an Engram project, direct them to Weaviate Cloud to create one and generate an Engram API key.

  • Do not build a hand-rolled memory system (e.g. a raw Weaviate collection with manual inserts) when the user asks for memory — Engram handles extraction, deduplication, and consolidation out of the box. The manual approach in Agentic RAG is only for users who explicitly want full control or self-hosting.

Env Rules

Mandatory:

  • ENGRAM_API_KEY — Engram API key from Weaviate Cloud (format eng_...). This carries the project identity; WEAVIATE_URL/WEAVIATE_API_KEY are not needed for Engram itself.

Optional (only when combining with an agent/chatbot):

  • An LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY)

Engram Overview

Key concepts (use these terms with the user):

ConceptMeaning
MemoryA discrete piece of stored information, automatically embedded for semantic search. Fields: id, project_id, content, topic, group, user_id, properties, tags, created_at, updated_at, score (search only).
TopicA category of memory with a natural-language description that guides LLM extraction (e.g. food_preferences: "What food the user likes to eat"). Only information matching a topic is stored.
GroupA named bundle of topics + pipeline, mapping 1:1 to a use case. Every project has a default group; all calls use it unless group= is passed.
ScopeIsolation boundary: project-wide (shared, from the API key), user-scoped (user_id, strict isolation), or custom properties (e.g. conversation_id).
Pipeline / RunStorage is asynchronous. add returns a run_id immediately; the pipeline extracts facts, transforms them against existing memories (dedupe, merge, rewrite), and commits.

Topics and groups are configured per project in the Weaviate Cloud console (starter templates exist for personalization and continual learning). Code interacts with them by name — do not assume you can create topics from the SDK; ask the user what topics their project defines, or assume the template default (e.g. UserKnowledge).

A read-only client.groups API (get, list, returning each group's topics with their descriptions and scoping) is merged upstream but not in 1.0.1 — do not call it until a release includes it. Until then, ask the user rather than introspecting.

Installation

Engram ships as its own Python SDK, weaviate-engram. It does not come with weaviate-client (or weaviate-agents) — installing those does not give you Engram, and Engram does not require them.

uv add weaviate-engram

# or with pip
pip install weaviate-engram

Note the package name and import name differ: install weaviate-engram, import engram:

from engram import AsyncEngramClient

This guide targets weaviate-engram 1.0.x (current release 1.0.1), which requires Python 3.11–3.14. 1.0 is the first stable API — if the user is on a 0.x release, upgrade before following this guide, and pin the dependency (weaviate-engram>=1.0.1,<2) so a future major cannot silently change the surface.

Client Setup

Use the async client (AsyncEngramClient) — memory calls sit in request paths and event loops (FastAPI, chat backends, agents), so they should not block. A synchronous EngramClient with the identical surface (drop the await) exists for scripts and sync-only frameworks.

import os
from dotenv import load_dotenv
from engram import AsyncEngramClient

load_dotenv()
client = AsyncEngramClient(api_key=os.environ["ENGRAM_API_KEY"])

All client.memories.* and client.runs.* methods below are coroutines — call them with await from async code. See Async Client for general async patterns (lifecycle, FastAPI integration) that apply here too.

Close the client when you are done with it. Both clients hold an HTTP connection pool: await client.aclose() on the async client, client.close() on the sync one. Both are also context managers, which is the safer form for scripts and tests:

async with AsyncEngramClient(api_key=os.environ["ENGRAM_API_KEY"]) as client:
    ...

In a long-lived server, construct one client at startup and close it on shutdown (FastAPI lifespan) — never one per request.

EngramClient/AsyncEngramClient also accept base_url (default https://api.engram.weaviate.io) and timeout (default 30.0 seconds).

Error Handling

All SDK exceptions inherit from EngramError and import from either engram or engram.errors:

ExceptionRaised when
APIErrorAny non-2xx response. Carries .status_code and .body.
AuthenticationError401 — missing, invalid, or wrong-type API key. Subclasses APIError.
ValidationErrorInvalid client configuration, raised before any request. In 1.0.1 the only trigger is a non-positive timeout; an empty/malformed api_key is not caught here and surfaces as AuthenticationError on the first call.
ConnectionErrorNetwork failure reaching the Engram API.
EngramTimeoutErrorruns.wait() did not reach a terminal status in time.

Two behaviours to handle deliberately.

Searching a user_id that has no memories yet raises APIError (HTTP 422, "user not found") instead of returning an empty result. A user whose memories were all deleted returns an empty result, not an error. Any code that recalls memories before the user's first add — which includes every chatbot's first message from a new user — must catch this and treat it as "no memories" (the integration patterns below do). The same error also appears transiently just after a user's first add while the tenant initializes; see Run Status.

AuthenticationError subclasses APIError, so a bare except APIError swallows a bad API key — and a misconfigured app then looks exactly like one with an empty memory store, silently and permanently. Always let authentication errors through:

from engram.errors import APIError, AuthenticationError

try:
    memories = await client.memories.search(query=q, user_id=user_id)
except AuthenticationError:
    raise                  # configuration error — never silently degrade
except APIError:
    memories = []          # this user genuinely has no memories yet

Storing Memories

client.memories.add(...) is fire-and-forget: it returns a run immediately and processes in the background. All three input types share optional parameters user_id, properties, and group (defaults to "default").

String input — raw text, Engram extracts the facts:

run = await client.memories.add(
    "The user prefers dark mode and works primarily in Python.",
    user_id="alice",
)
print(run.run_id, run.status)  # status: "running"

Conversation input — chat transcripts (OpenAI Chat Completions message format; roles user, assistant, system, tool, developer are supported):

run = await client.memories.add(
    [
        {"role": "user", "content": "I just moved to Berlin."},
        {"role": "assistant", "content": "Welcome to Berlin!"},
        {"role": "user", "content": "I prefer specialty coffee."},
    ],
    user_id="alice",
)

Pre-extracted input — you did the extraction yourself; items skip LLM extraction and go straight to transform + commit. Each item targets a topic by name:

from engram import PreExtractedInput, PreExtractedItem

run = await client.memories.add(
    PreExtractedInput(items=[
        PreExtractedItem(content="User prefers dark mode", topic="UserKnowledge"),
        PreExtractedItem(content="User works in Python", topic="UserKnowledge"),
    ]),
    user_id="alice",
)

Typed inputs — the plain str and list[dict] forms above are shorthands. The explicit models add fields the shorthands cannot express, most usefully timestamps, which matter when backfilling history so Engram reconciles facts in the order they actually happened rather than the order you uploaded them:

from datetime import datetime
from engram import StringInput, ConversationInput, MessageInput

run = await client.memories.add(
    StringInput(
        content="The user switched from Postgres to DuckDB.",
        created_at=datetime(2026, 3, 14, 9, 30),
    ),
    user_id="alice",
)

run = await client.memories.add(
    ConversationInput(
        messages=[MessageInput(role="user", content="I prefer specialty coffee.")],
        metadata={"source": "support-ticket"},
    ),
    user_id="alice",
)

MessageInput also carries tool_call_id, name, and tool_calls for agent transcripts that include tool calls.

The REST API accepts one further parameter the Python SDK does not expose: root, the pipeline root name, for projects with advanced multi-pipeline configurations. Call the REST API directly if a project needs it.

Run Status

Storage is async, so a search immediately after add may not see new memories. Block until processing finishes when tests or demos need read-after-write:

status = await client.runs.wait(run.run_id)
print(status.status)  # "completed"

# committed_operations groups results into created / updated / deleted lists,
# each entry carrying memory_id and committed_at. Prefer the three shorthand
# properties: committed_operations is None on a failed run, so reaching through
# it (status.committed_operations.created) raises AttributeError exactly when
# something went wrong — the shorthands return [] instead.
created_ids = [op.memory_id for op in status.memories_created]
#   also available: status.memories_updated, status.memories_deleted

Run statuses: running, in_buffer (paused at a buffer step waiting for a count/time trigger), completed, failed. Only completed and failed are terminal. runs.wait keeps polling anything else, so waiting on a run that parks at in_buffer always ends in EngramTimeoutError — never block a test on a buffered pipeline; use runs.get and accept in_buffer as a valid resting state. Default pipelines typically commit within ~5–15 seconds (conversation inputs trend slower) — a run stuck in running for minutes warrants checking for failed. runs.wait polls every interval=0.5 seconds with a default timeout=30.0 seconds and raises EngramTimeoutError past it; pass a larger timeout= when waiting in tests. In production chat loops, do not wait — fire and forget.

A completed run does not guarantee the memory is searchable yet. On a user's first add, the backing tenant may still be initializing, and a search issued right after the run completes can raise APIError or come back empty. Tests, demos, and seed scripts that need read-after-write should retry briefly:

import asyncio
from engram.errors import APIError, AuthenticationError

async def search_when_ready(**kwargs):
    """Search, tolerating a cold tenant right after a user's first add."""
    for attempt in range(5):
        try:
            return await client.memories.search(**kwargs)
        except AuthenticationError:
            raise
        except APIError:
            if attempt == 4:
                raise
            await asyncio.sleep(3)

This only affects the first write for a given scope — steady-state searches do not need it.

There is no single delete-all operation for a user. For deterministic cleanup (test teardown, "forget me" features), either collect memory_ids from committed_operations at write time, or list all of a user's memories with retrieval_config=FetchRetrieval(limit=N) (unranked listing — a query string is still required but unused) and delete by id.

Searching Memories

results = await client.memories.search(
    query="What programming language does the user prefer?",
    user_id="alice",
)
for memory in results:
    print(memory.content, memory.topic, memory.score)

Two things the examples don't show: search returns a SearchResults object (a Sequence of memories — iteration, len(), and indexing work), not a plain list, so annotate helpers accordingly. And results are filled up to limit even when weakly relevant: score is higher-is-better, but its range depends on retrieval type — hybrid is normalised roughly 0–1, while vector and BM25 scores run far lower. Apply a relevance cutoff before injecting memories into a prompt; ~0.5 is a reasonable starting point for hybrid (it would drop everything for vector/BM25 — tune per retrieval type and app).

Retrieval types — pass retrieval_config; hybrid is the default general-purpose choice:

from engram import VectorRetrieval, BM25Retrieval, HybridRetrieval, FetchRetrieval

results = await client.memories.search(
    query="user preferences",
    user_id="alice",
    retrieval_config=HybridRetrieval(limit=10),
    # or VectorRetrieval / BM25Retrieval / FetchRetrieval (unranked listing)
)

When the server-side default limit is fine, retrieval_config also accepts the bare strings "vector", "bm25", "hybrid", and "fetch" — retrieval_config="hybrid" is equivalent to HybridRetrieval(). Omitting retrieval_config entirely lets the server pick.

Filter by topic:

results = await client.memories.search(
    query="user preferences",
    topics=["UserKnowledge"],
    user_id="alice",
    retrieval_config=HybridRetrieval(limit=10),
)

Custom scope properties — narrow by e.g. conversation. At search time properties are optional filters (omitting one searches across all values); at add time every property the target topic is scoped by must be provided:

from engram import Topic

results = await client.memories.search(
    query="what did we decide?",
    user_id="alice",
    properties={"conversation_id": "abc-123"},
    topics=[
        "user_facts",                                                  # plain string: not conversation-scoped, ignores the filter
        Topic(name="conversation_summary"),                            # no properties: inherits the global filter
        Topic(name="messages", properties={"conversation_id": None}),  # None: clears the global filter for this topic only
    ],
)

Omitting topics searches every topic in the group.

Managing Memories

Get or delete an individual memory by ID (IDs come from search results or committed_operations):

memory = await client.memories.get(memory_id, user_id="alice", group="default")

await client.memories.delete(memory_id, user_id="alice", group="default")

Integration Pattern: Chatbot Memory

The standard pattern for chatbots: search memories before generating, add the exchange after responding.

from engram.errors import APIError, AuthenticationError

async def chat_turn(user_id: str, user_message: str, generate) -> str:
    # 1. Recall relevant memories for this message. A user_id that has never
    #    had an add (first ever message) raises APIError, not an empty result.
    #    AuthenticationError subclasses APIError, so re-raise it — otherwise a
    #    bad API key degrades into "this user has no memories" forever.
    try:
        memories = await client.memories.search(
            query=user_message,
            user_id=user_id,
            retrieval_config=HybridRetrieval(limit=5),
        )
    except AuthenticationError:
        raise
    except APIError:
        memories = []
    memory_context = "\n".join(f"- {m.content}" for m in memories if m.score >= 0.5)

    # 2. Generate with memories in context (any LLM framework)
    response = generate(user_message, memory_context)

    # 3. Store the exchange — the add call returns as soon as the run is
    #    accepted; processing happens server-side. Do not wait on the run.
    await client.memories.add(
        [
            {"role": "user", "content": user_message},
            {"role": "assistant", "content": response},
        ],
        user_id=user_id,
    )
    return response

generate is the user's generation function — e.g. the generate from Basic RAG with memory_context prepended to the context.

Two production notes. Conversation input extracts memories from the dialogue itself too (e.g. "the user asked for dinner ideas") — if recall gets polluted with meta-chatter over time, tighten the topic descriptions. And chat_turn discards the returned run — for a "forget me" feature, either record run.run_id/committed_operations out-of-band, or list-and-delete with FetchRetrieval (see Run Status).

Integration Pattern: Agent Memory Tools

Expose Engram as tools on the RouterAgent from Basic Agent, so the agent decides when to recall or remember:

from engram.errors import APIError, AuthenticationError

async def search_memories(query: str) -> str:
    """Search the user's long-term memory for facts, preferences, and past interactions.
    Use when answering may depend on something the user said in a previous session."""
    try:
        results = await client.memories.search(
            query=query,
            user_id=CURRENT_USER_ID,
            retrieval_config=HybridRetrieval(limit=5),
        )
    except AuthenticationError:
        raise  # configuration error — never report it to the model as "no memories"
    except APIError:  # user has no memories yet
        return "No relevant memories found."
    return "\n".join(m.content for m in results) or "No relevant memories found."


async def store_memory(fact: str) -> str:
    """Save an important fact about the user to long-term memory.
    Use when the user states a lasting preference, correction, or piece of personal context."""
    run = await client.memories.add(fact, user_id=CURRENT_USER_ID)
    return f"Memory stored (run {run.run_id})."


router = RouterAgent(model="<model_name>", tools=[search_memories, store_memory])

CURRENT_USER_ID should come from the app's session/auth context — set it per request, never hardcode it in multi-user apps.

The RouterAgent from Basic Agent calls tools synchronously (tool_result = tool_function(**tool_inputs)). With async tools, make get_response an async def and await coroutine tools:

import inspect

tool_result = tool_function(**tool_inputs)
if inspect.isawaitable(tool_result):
    tool_result = await tool_result

If the agent framework is strictly synchronous and cannot be adapted, use the sync EngramClient inside the tools instead.

For continual learning (project-wide agent memory, e.g. "filter on the genres property instead of doing a near-text query"), use a project-wide topic and omit user_id — lessons learned from one user's sessions then benefit all users. This is configured in the project's topics (the continual learning template).

Off-the-shelf Integrations

Before writing a custom integration, check whether the packaged one already fits — it needs no application code.

Claude Code plugin — gives Claude Code long-term, cross-session memory backed by Engram. It recalls relevant memories before each answer and stores each completed turn through hooks; the agent has no tools to call, and memory is best-effort and never blocks a session. Pick the Coding Assistant topic template when creating the project, then:

export ENGRAM_API_KEY=...   # in ~/.zshrc or ~/.bashrc
# inside a Claude Code session
/plugin marketplace add weaviate/engram-plugins
/plugin install engram@weaviate-engram

See weaviate/engram-plugins for optional customization.

The plugin reads ENGRAM_BASE_URL to target a staging or self-hosted endpoint; the SDK takes the same override as EngramClient(base_url=...).

REST API (non-Python stacks)

Same operations over HTTP, with Authorization: Bearer $ENGRAM_API_KEY:

# Store
curl -X POST https://api.engram.weaviate.io/v1/memories \
  -H "Authorization: Bearer $ENGRAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": {"string": {"content": ["The user prefers dark mode."]}}, "user_id": "alice"}'

# Search
curl -X POST https://api.engram.weaviate.io/v1/memories/search \
  -H "Authorization: Bearer $ENGRAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "What editor does the user prefer?", "user_id": "alice", "retrieval_config": {"retrieval_type": "hybrid", "limit": 5}}'

# Run status
curl https://api.engram.weaviate.io/v1/runs/{run-id} \
  -H "Authorization: Bearer $ENGRAM_API_KEY"

There is no official TypeScript/JavaScript SDK, so REST is the integration path for TypeScript/Next.js backends (see Frontend Interface) — call it from server-side routes only, never expose ENGRAM_API_KEY to the browser. The full endpoint list is at https://docs.weaviate.io/engram/api/rest.

User-specific Customisations

If not specified ask the user about these points before implementing:

Memory use case

  • Personalization (per-user preferences/profile) → user-scoped topics, always pass user_id.

  • Continual learning (agent improves from experience, shared across users) → project-wide topics, no user_id.

  • Both → separate topics (or separate groups) in the same project; scoping is per topic.

Topics

What should the app remember? Each distinct category should be a topic with a clear natural-language description — descriptions directly control what the extraction LLM stores. For a single running summary or profile per user, use a bounded topic (max one memory per scope, updated in place) rather than accumulating facts.

Input type

  • Chat app → conversation input per exchange.

  • Event/log style data ("User viewed page X") → string input.

  • The user already extracts facts with their own LLM logic → pre-extracted input.

Retrieval

Hybrid is the right default. Vector for purely conceptual recall, BM25 for exact terms/IDs. Ask what limit fits their context budget (5–10 is typical).

Troubleshooting

  • APIError (422, user "..." not found) on search: nothing has ever been added for that user_id — catch it and treat as an empty result, as in the integration patterns. A user whose memories were all deleted returns an empty result, not this error.

  • A bad API key behaves like an empty memory store: AuthenticationError subclasses APIError, so a bare except APIError swallows it. Catch AuthenticationError first and re-raise.

  • Searches return nothing right after add: processing is asynchronous — await client.runs.wait(run.run_id) before searching, or check the run status for failed. If this is the first write for that user, the tenant can still be initializing even after the run reports completed; retry the search a few times with a short sleep (see Run Status).

  • Memory counts don't match inputs: extraction may split one input into several memories or merge facts into existing ones — never assert exact counts in tests; assert on search results instead.

  • Run stuck at in_buffer: the group's pipeline has a buffer step waiting for a count/time trigger; this is expected behaviour, not an error. Because in_buffer is not terminal, runs.wait will raise EngramTimeoutError on it — use runs.get instead when a pipeline buffers.

  • Facts not being stored: only information matching a configured topic description is extracted — check the project's topics in the Weaviate Cloud console and tighten/broaden their descriptions.

  • Scope errors on add: every scope property required by the target topic (including user_id for user-scoped topics) must be provided.

  • AuthenticationError (401, .status_code == 401): ensure ENGRAM_API_KEY is set and is an Engram key (eng_...), not a Weaviate cluster API key. An empty or malformed key is not validated client-side — it fails on the first call. Note 403 raises plain APIError, not AuthenticationError.

  • EngramTimeoutError from runs.wait: the default timeout is 30 s and conversation inputs can exceed it. The exception carries .run_id and .timeout. Pass a larger timeout=, or stop waiting and let the pipeline finish in the background. Check the run is not simply parked at in_buffer before raising the timeout.

  • Unclosed-connection warnings at exit: close the client (await client.aclose() / client.close()) or use it as a context manager.

  • ImportError on StringInput, MessageInput, or AuthenticationError: the project is on a 0.x weaviate-engram; upgrade to 1.0.1 or newer.

  • For any other issues, refer to the official docs at https://docs.weaviate.io/engram and use web search extensively for troubleshooting.

Done Criteria

  • Create test scripts to check store → wait → search → get → delete work end-to-end against the user's Engram project. Use a throwaway user_id (e.g. f"test-{uuid.uuid4().hex[:8]}"), and clean up by deleting the memory_ids from status.memories_created, or by listing the user's memories with FetchRetrieval and deleting by id. Wrap the first search in the retry helper from Run Status so a cold tenant does not fail the test. Tear down tests after completion, or create a proper test suite with pytest (requires install).

  • Verify persistence the way a user would: seed a fact, await client.runs.wait(...), then search for it from a fresh process/session.

  • Memory writes in the app are fire-and-forget; nothing in the request path blocks on runs.wait.

  • Recall paths re-raise AuthenticationError instead of folding it into "no memories".

  • The client is closed on shutdown (aclose/close, or a context manager), and is not constructed per request.

  • User has completed specification of the app.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an API key, Needs Python

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Weaviate
Publisher
Weaviate (Official)
Source
weaviate/agent-skills · skills/engram_memory · commit b19751c
Licence
BSD 3-Clause License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Weaviate.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download engram-memory.zip