Skip to main content

camel-goodmem

GoodMem memory for CAMEL agents. Documents are chunked, embedded and searched server-side; this package wraps the official goodmem Python SDK and exposes it to CAMEL both as a toolkit and as a BaseRetriever.

Version 0.2.0. Verified against GoodMem server v1.0.320.

Upgrading from 0.1.0. 0.1.0 talked to GoodMem over hand-written HTTP and had defects that were invisible from its return values — a failed search reported success: true with no indication anything had gone wrong, and publicRead was sent on space updates although the server had removed the field and answers 400. See Changes in 0.2.0.

Install

pip install camel-goodmem
export GOODMEM_API_KEY="gm_your_key_here"
export GOODMEM_BASE_URL="https://your-goodmem-server"

Use

from camel.agents import ChatAgent
from camel_goodmem import GoodMemToolkit

toolkit = GoodMemToolkit(space_ids=["<space-uuid>"])
agent = ChatAgent("You remember things.", tools=toolkit.get_tools())

By default the model sees exactly two tools:

Tool What the model may pass
goodmem_search query, top_k
goodmem_remember text, metadata

Every operational setting — which spaces are readable, which reranker, whether a threshold applies, whether files can be uploaded — is fixed by you at construction time. The model cannot widen its own access, pick another space, or turn on indexing waits.

Opt in to more:

Constructor argument Adds
upload_dir=<path> goodmem_upload_file, confined to that directory
allow_admin_tools=True list_spaces, list_embedders, goodmem_get_space, create_space, update_space, list_memories, get_memory
allow_delete=True delete_memory, delete_space
allow_write=False removes goodmem_remember

Retrieval results

{
  "success": True,
  "query": "...",
  "results": [
    {
      "chunkId": "...", "text": "...", "memoryId": "...", "spaceId": "...",
      "score": 0.64,          # higher is better
      "rawScore": -0.64,      # exactly what the server sent
      "scoreKind": "vector",  # or "reranker" -- not the same scale
      "contentType": "text/plain",
      "metadata": {...},      # the memory's metadata, joined by UUID
    }
  ],
  "totalResults": 1,
  "partial": False,           # True when the server reported a problem
  "statuses": [],             # what it reported
  "resultSetId": "...",
}

partial means exactly one thing: the server reported a real problem during this retrieval. It is independent of whether hits came back. A degraded search still returns whatever hits arrived, with partial set; when nothing usable arrives the result is empty, partial is set, and a warning key plus a WARNING log line carry the server's own reason. A failed search is never presented as an empty one.

Scores

GoodMem produces two kinds of score, and they are not comparable:

  • vector scores are negative distances. score is the flipped value so higher is better, with rawScore kept beside it.
  • reranker scores are already higher-is-better, on a provider-dependent scale. Measured live on the same five documents: Voyage rerank-2.5 returned 0.27..0.93, Jina jina-reranker-v3 returned -0.14..0.43.

So there is no default threshold, and min_score applies only when reranker_id is set. If a threshold removes everything, the toolkit warns and names the range it actually saw rather than returning a silent empty list.

Metadata filters

Filters are expressions evaluated server-side, not SQL. Build them with the filters helper — in 0.1.0 the filter was a raw string the model supplied, which let it widen its own scope and broke on any value containing an apostrophe:

from camel_goodmem import GoodMemToolkit, filters

toolkit = GoodMemToolkit(
    space_ids=["..."],
    metadata_filter={"tenant": "acme", "active": True},
)

expression = filters.all_of(
    filters.equals("tenant", "acme"),
    filters.compare("year", ">=", 2026),
    filters.one_of("kind", ["note", "doc"]),
)

The helper applies the escaping the server accepts (' → \', \ → \\; SQL-style '' doubling is rejected with HTTP 400), refuses control characters, restricts field names, and casts each value to the type GoodMem stored. A boolean compared as TEXT is accepted with HTTP 200 and matches nothing, so filters never stringifies a bool.

Uploads

Uploads are off unless you set upload_dir. When set, every path is resolved — symlinks included — and refused if it lands outside that directory, so a model-supplied path cannot read arbitrary files from the host.

toolkit = GoodMemToolkit(space_ids=["..."], upload_dir="/srv/agent-uploads")

Retriever

from camel_goodmem import GoodMemRetriever, GoodMemToolkit

retriever = GoodMemRetriever(GoodMemToolkit(space_ids=["..."]))
retriever.process("Text to remember.")
rows = retriever.query("what did I store?", top_k=5)

query() returns CAMEL's retriever shape — similarity score, content path, metadata, extra_info, text — with GoodMem specifics under extra_info (goodmem_chunk_id, goodmem_memory_id, goodmem_space_id, goodmem_score_kind, goodmem_raw_score, goodmem_partial, and goodmem_statuses when degraded).

Bringing your own client

from goodmem import Goodmem
from camel_goodmem import GoodMemToolkit

toolkit = GoodMemToolkit(client=Goodmem(base_url=..., api_key=...))

An injected client keeps its own server, credentials and TLS settings, and is never closed by the toolkit.

Changes in 0.2.0

Every item below was reproduced against the published 0.1.0 wheel, live against GoodMem v1.0.320.

Was Now
Hand-written requests client Official goodmem SDK
A search with a broken reranker returned success: true and no status; the server had sent three partial + statuses, and the hits are still returned
publicRead sent on space update — live 400 Unrecognized field "publicRead" Not offered; the SDK's own request model has no such field
metadata_filter was a raw string from the model, so it could widen its own scope; an apostrophe in a value was a 400 Developer-set metadata_filter, built and escaped by filters
file_path was a model argument with no restriction; it read /etc/hostname and uploaded it Confined to upload_dir; absolute, .. and symlink escapes refused
Empty search took 11.6 s — wait_for_indexing defaulted on and was model-controllable 0.33 s; the read path never polls
13 retrieval arguments; delete_space and update_space always in the toolset goodmem_search(query, top_k); admin and destructive tools opt-in
A PDF's content came back as raw bytes, which no tool result can carry Text as text, anything else base64 — always JSON-serialisable
A failed content fetch set contentError and left success: true A failure raises
Chunks and memories were two arrays joined by position Joined by UUID, de-duplicated by chunk id
Threshold documented "(0-1)"; raw negative scores score/rawScore/scoreKind, reranker-only threshold that warns
list_spaces returned the first page; the server's nextToken was never read Paginated, bounded by max_list_items
400 Client Error: Bad Request The server's own message and status on GoodMemError
Reusing a space name silently accepted a different embedder Reuse requires a matching embedder; a mismatch names both
No request carried a timeout (0 of 12) On the client, configurable
No retriever — GoodMem could not be used with CAMEL's RAG paths GoodMemRetriever(BaseRetriever)
71 tests that mocked the HTTP session wholesale; no CI 69 offline + 29 live; CI on 3.10–3.13

Tests

Suite Count Needs
tests/test_goodmem_toolkit.py 69 nothing — the real SDK over a mock transport, fed NDJSON captured from a live server
tests/test_goodmem_live.py 29 GOODMEM_API_KEY + GOODMEM_BASE_URL; skips entirely without them
pip install -e ".[dev]"

# offline
pytest tests/test_goodmem_toolkit.py

# live (pin the embedder if the server's first one is unhealthy)
GOODMEM_API_KEY=... GOODMEM_BASE_URL=... \
  GOODMEM_TEST_EMBEDDER_ID=... \
  pytest tests/test_goodmem_live.py

# what CI runs
ruff check camel_goodmem tests
ruff format --check camel_goodmem tests
mypy camel_goodmem

The live suite creates one space per run and asserts, against a fresh server inventory, that it is gone afterwards.

Deliberately not done

  • No AgentMemory implementation. CAMEL's AgentMemory is chat history with a context-window policy; GoodMem is a document store with server-side embedding. Implementing it would fake one side of the contract.
  • Turning off TLS verification is possible via verify_ssl for self-signed development servers. It defaults to on, no example here turns it off, and CI fails if shipped Python does.

License

Apache-2.0.

Release files for camel-goodmem 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for camel-goodmem 0.2.0
File Size Uploaded
camel_goodmem-0.2.0.tar.gz 28.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for camel-goodmem 0.2.0
File Interpreter ABI Platform
camel_goodmem-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 57.5 kB

Release files / camel_goodmem-0.2.0.tar.gz

Download URL camel_goodmem-0.2.0.tar.gz
Size 28.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e83053d834a880346d6f59b8f75b47495aa4513ec141499b7231f87cd6de7531
BLAKE2b-256 checksum
How to use checksums
6af9f4d8b3328643765104151d4c3992b71b3fdefa81fee5c7ea61160a4420c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / camel_goodmem-0.2.0-py3-none-any.whl

Download URL camel_goodmem-0.2.0-py3-none-any.whl
Size 28.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d1682c62d4a06328f742a4a6ca945c0da63101fb43acc3e398b4a4ec6ed651e7
BLAKE2b-256 checksum
How to use checksums
2daaa3d554377e12ede6c73590f62d6d6a0b4ed26123c450bca0c6c3d1e662b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page