CrewAI
Back CrewAI's Knowledge with a GoodMem space, or give agents thirteen GoodMem tools for storing documents, semantic search, and space management.
Overview
GoodMem is a self-hostable RAG service that handles embedding, chunking, storage, and retrieval
server-side. The CrewAI integration plugs it in two ways: as a storage backend for CrewAI's own
Knowledge, via GoodMemKnowledgeStorage, and as thirteen crewai.tools.BaseTool classes that
agents can call directly to store documents, run semantic search, and manage memory spaces.
Installation
pip install goodmem-crewai
# or
uv add goodmem-crewaiThe pip package is goodmem-crewai; the import name is goodmem_crewai. Python 3.11–3.13 and
CrewAI 1.15.9+ are supported, and the runtime dependencies are crewai and the official
goodmem SDK (0.1.34+).
Configuration
| Variable | Description |
|---|---|
GOODMEM_BASE_URL | Base URL of your GoodMem server, e.g. https://your-goodmem-server.example.com. |
GOODMEM_API_KEY | API key issued by your GoodMem instance (starts with gm_). |
Every tool and GoodMemKnowledgeStorage also accept base_url and api_key constructor
arguments, which take precedence over the environment variables, plus verify_ssl (pass False
for a self-signed development certificate, or a CA bundle path), timeout (seconds, default 60),
and client — a pre-configured goodmem.Goodmem client to inject if you want to own the
connection pool and TLS configuration yourself. Environment variables never redirect an injected
client.
Quick start
Use GoodMem as the storage behind CrewAI's Knowledge so agents retrieve from a GoodMem space
automatically:
from crewai import Agent, Crew, Knowledge, Task
from goodmem_crewai import GoodMemKnowledgeStorage
storage = GoodMemKnowledgeStorage(space_id="<space-id>", reranker_id="<reranker-id>")
knowledge = Knowledge(collection_name="handbook", sources=[], storage=storage)
storage.save(["Refunds over $500 need a manager's approval."])
agent = Agent(
role="Support Lead",
goal="Answer policy questions from the handbook.",
backstory="You cite the passage you relied on.",
knowledge=knowledge,
)space_id is required; optional fields include space_ids (additional spaces to search),
reranker_id, filter (a GoodMem filter expression), fetch_k, wait_for_indexing
(default True), and indexing_timeout (default 120 seconds). A write that fails partway
raises GoodMemIngestionError, whose created_memory_ids records what was accepted.
Local development. If you are running GoodMem locally with a self-signed certificate, pass
verify_ssl=False to the storage or tool constructor. Keep TLS verification enabled for any
deployed server.
Agent tools
GoodMemSearchTool takes only a query from the model. Which spaces it searches, how many results
it returns, whether it reranks, whether an LLM answers, and any metadata filter are set by you, so
a model cannot redirect the search mid-run:
from crewai import Agent
from goodmem_crewai import GoodMemSearchTool
search = GoodMemSearchTool(
space_ids=["<space-id>"],
k=5,
reranker_id="<reranker-id>", # optional
llm_id="<llm-id>", # optional: an answer from an LLM
metadata_filter={"category": "policy"}, # optional, escaped for you
)
agent = Agent(
role="Researcher",
goal="Answer from the knowledge base.",
backstory="You cite sources.",
tools=[search],
)metadata_filter values are compared as their own JSON type: a str as text, a bool as a
boolean, and an int/float as a number. Any other value (None, a list, a dict) is refused
with ValueError; use filter to pass a GoodMem filter expression directly.
The full toolset:
| Tool | Purpose |
|---|---|
GoodMemSearchTool | Semantic search; the model passes only a query |
GoodMemCreateMemoryTool | Store text; waits for indexing by default |
GoodMemUploadFileTool | Store a file from a configured upload_dir (opt-in) |
GoodMemGetMemoryTool | Fetch a memory with readable content |
GoodMemListMemoriesTool / GoodMemDeleteMemoryTool | Manage memories |
GoodMemListSpacesTool / GoodMemGetSpaceTool | Find spaces |
GoodMemCreateSpaceTool / GoodMemUpdateSpaceTool / GoodMemDeleteSpaceTool | Manage spaces |
GoodMemListEmbeddersTool / GoodMemListRerankersTool | Discover model IDs |
Space, memory, and file tools carry the authority of the configured API key — give them only to
crews that need it. Every id, whether a model passes it or you configure it, must be a UUID;
anything else is refused before a request is made (a tool returns a ToolFailure with reason
INVALID_INPUT; GoodMemKnowledgeStorage and wait_for_memories raise ValueError).
An answer from an LLM (opt-in)
Set llm_id on GoodMemSearchTool to the id of an LLM registered in GoodMem and the server runs
it over the passages it retrieved. The tool's output then carries the answer as abstract_reply
next to results, which are returned as usual. It is a field on the tool, not a tool argument, so
the model cannot turn it on or pick a different LLM. If the LLM fails, the passages are still
returned with partial: true and the server's statuses, and nothing is raised.
GoodMemKnowledgeStorage has no llm_id: CrewAI puts only each result's content into the
agent's prompt and runs a knowledge search for every task, so an answer generated there would be
paid for each time and then discarded. Give the agent GoodMemSearchTool with llm_id instead.
Partial results
Tool results carry partial and statuses. If part of a search failed — a reranker was
unavailable, one space was unreachable — you get the usable passages and the fact that they are
incomplete; a search that produced nothing usable returns empty results with partial: true and
the statuses, so the model can tell a failed search from a miss. GoodMemKnowledgeStorage returns
a bare list, so in that case it emits a warning and a log line instead.
Waiting for indexing
Searching is not a way to wait for a write. GoodMemCreateMemoryTool waits for its own memory by
default; use wait_for_memories(ids) to wait on specific IDs (it also accepts a connection —
a GoodMemConnection — and a timeout, default 120 seconds).
Scores
CrewAI's SearchResult.score is higher-is-better. GoodMem's vector score is a negative inner
product — the best match is the lowest number — so it is negated to fit that convention; a
reranker score already runs the right way and is passed through. The untouched server value is
kept as metadata["raw_score"], and metadata["score_kind"] ("vector" or "reranker") names
the scale.
Neither scale is 0–1, so score_threshold is applied only when a reranker produced the scores;
without one it is ignored with a warning. Reranker scales are model-dependent, so calibrate the
threshold for the reranker you use — if a threshold drops every result, the storage warns and
names the observed score range rather than returning a silent empty list.
Learn more
The full README, examples, and API reference are available on the PyPI package page.