cf-memory
by hansakoch
README.md
# CF Memory Plugin
**Contributor:** [Hans Al Koch](https://github.com/hansakoch) (HAK)
**Canonical source:** [github.com/hansakoch/cf-memory-plugin](https://github.com/hansakoch/cf-memory-plugin) · [cloudflare-memory.pages.dev](https://cloudflare-memory.pages.dev)
Listings that scrape this repository are not the source.
Persistent memory for AI agents, backed by
[Cloudflare Agent Memory](https://developers.cloudflare.com/agent-memory/).
This plugin is a thin client. Cloudflare stores, classifies, and recalls
memories. You do not run a vector DB, embeddings pipeline, or Worker.
**Private beta.** Expect 2-4 weeks for access after signing up. Paid Workers is required. Paid Workers alone is not enough —
you still need Agent Memory entitlement.
| Need | Link |
|---|---|
| Join the beta (2-4 week wait) | [Waitlist form](https://forms.gle/RAXbK6gN9Yy89ECw8) |
| Product docs | [developers.cloudflare.com/agent-memory](https://developers.cloudflare.com/agent-memory/) |
| HTTP API | [HTTP API](https://developers.cloudflare.com/agent-memory/api/http-api/) |
| Pricing | [Agent Memory pricing](https://developers.cloudflare.com/agent-memory/platform/pricing/) |
| Limits | [Platform limits](https://developers.cloudflare.com/agent-memory/platform/limits/) |
| Create a token | [API Tokens](https://dash.cloudflare.com/profile/api-tokens) |
| Create a token (docs) | [Create API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) |
| Workers Paid | [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/) |
| Design notes | [Introducing Agent Memory](https://blog.cloudflare.com/introducing-agent-memory/) |
---
## One-Minute Setup
```bash
# 1. Install
pip install git+https://github.com/hansakoch/cf-memory-plugin.git
# 2. Set credentials (get these from Cloudflare dashboard)
export MCP_CLOUDFLARE_API_KEY="cf-api-token-with-agent-memory"
export CF_ACCOUNT_ID="your-32-char-account-id"
# 3. Verify everything works
cf-memory doctor
```
`CF_ACCOUNT_ID` is required. There is no default account. Find it in the
[Cloudflare dashboard](https://dash.cloudflare.com/) sidebar.
Create the token at
[dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens)
with **Agent Memory** permission only. Do not reuse a Global API Key.
### Verify it works
```bash
$ cf-memory doctor
cf-memory doctor — full diagnostic
1. Credentials
✓ MCP_CLOUDFLARE_API_KEY is set (cfut_W5...6d4e0)
✓ CF_ACCOUNT_ID is set (0870b0bd...)
Namespace: hermes
Profile: default
2. Connectivity
✓ API reachable (0.8s)
3. Namespace
✓ Namespace 'hermes' exists
4. Profile & Memories
✓ Profile 'default' has 20 memories
5. Write/Read Test
✓ remember: 1.4s [fact] cf-memory doctor connectivity test
✓ recall: 3.2s — cf-memory doctor connectivity test
✓ cleanup: test memory deleted
6. Latency Summary
list_namespaces: 0.8s
remember: 1.4s
recall: 3.2s
──────────────────────────────────────────────────
✓ All checks passed — cf-memory is healthy
```
If anything fails, the output tells you exactly what to fix.
See [Troubleshooting](#troubleshooting) for details.
---
## What's Stored
Memories live in **namespaces** (isolated buckets) and **profiles** (per-user
or per-session views within a namespace).
```
Account
└── Namespace: "my-app" ← one per project
├── Profile: "default" ← general memories
│ ├── Entry: "User prefers concise answers"
│ ├── Entry: "Project uses PostgreSQL 16"
│ └── Entry: "Deploy target is Fly.io"
├── Profile: "user:alice" ← per-user context
│ └── Entry: "Alice is in UTC+9"
└── Profile: "session:xyz" ← per-session scratch space
└── Entry: "Currently refactoring auth module"
```
| Concept | Default | Override |
|---|---|---|
| Namespace | `hermes` | `CF_MEMORY_NAMESPACE` env or `--namespace` flag |
| Profile | `default` | `CF_MEMORY_PROFILE` env or `--profile` flag |
Each entry has:
- **content** — the raw text you stored
- **summary** — Cloudflare-generated one-liner
- **type** — classified category (fact, instruction, event, etc.)
- **timestamps** — `createdAt`, `updatedAt`
Use one namespace per app and one profile per user/tenant. Don't store
secrets, passwords, or customer PII you aren't allowed to store.
---
## What this is
| You call | Cloudflare does | Default MCP |
|---|---|---|
| `remember` | Store one fact / instruction / event | yes |
| `recall` | Search + synthesize an answer (~5s) | yes |
| `ingest` | Extract memories from a conversation (writes land 3–8s later) | yes (omit with `--slim`) |
| `summary` | Markdown profile of what is stored | yes (omit with `--slim`) |
| `list` / `get` / `delete` | Inspect or remove entries | yes (omit with `--slim`) |
---
## MCP surface (token cost)
Harnesses (Grok CLI, Grok Bot, Claude, Codex, Cursor) inject **every** MCP
tool schema into **every** turn, even if unused. Tool count and description
size are the cost.
`cf-memory serve` and `python -m cloudflare_memory` advertise the **full** tool
surface by default. Use `cf-memory serve --slim` for remember + recall only:
| Tool | Schema | Returns | Default | `--slim` |
|---|---|---|---|---|
| `remember` | `content: str` | compact `{"id","type"}` | yes | yes |
| `recall` | `query: str` | short synthesized answer only | yes | yes |
| admin tools | list/get/delete/ingest/summary/namespaces | varies | yes | no |
`thinking_level` and `response_length` are hardcoded to `low` / `short`. They
are not MCP parameters.
Hermes does **not** use this MCP server. It uses the native memory provider
(background ingest, non-blocking prefetch, **zero MCP tools**).
## HTTP latency (client / CLI)
Each client call hits Cloudflare. Here's what to expect:
| Call | Latency | What happens |
|---|---|---|
| `remember` | ~2s (1.3–3.8s) | Classify + store one memory |
| `recall` | ~5s | Semantic search + LLM synthesis of answer |
| `ingest` | ~3s + async | Accept messages, return immediately. Cloudflare extracts memories in the background (3–8s) |
| `summary` | ~1s | Fetch the generated markdown profile |
| `list` | ~0.4s | Fast pagination, no content |
| `get` | ~1.4s | Single entry with content |
| `delete` | ~1s | Remove by ID |
**Cost:** Agent Memory is **free during private beta** with 30-day notice
before billing. You pay only for Workers Paid ($5/mo minimum). See
[Agent Memory pricing](https://developers.cloudflare.com/agent-memory/platform/pricing/)
for post-beta rates.
**Tip for Hermes users:** Hermes prefetches memory in the background, so
`recall`'s ~5s latency doesn't block your turn.
---
## A2A (Agent-to-Agent)
```bash
# Start A2A server
cf-memory a2a --port 9120
# Agent card available at
curl http://localhost:9120/.well-known/agent.json
```
The A2A server exposes `remember` and `recall` as agent skills.
Any A2A-compatible agent can discover and call these tools.
## MCP Config Examples
Same MCP block everywhere. Only the config file path changes.
`args: ["serve"]` is the full default (all tools). Use `["serve", "--slim"]`
for remember + recall only.
### Generic MCP block
```json
{
"mcpServers": {
"cf-memory": {
"command": "cf-memory",
"args": ["serve"],
"env": {
"MCP_CLOUDFLARE_API_KEY": "your-token",
"CF_ACCOUNT_ID": "your-account-id"
}
}
}
}
```
### Grok CLI
Add to `~/.config/grok/mcp.json`:
```json
{
"mcpServers": {
"cf-memory": {
"command": "cf-memory",
"args": ["serve"],
"env": {
"MCP_CLOUDFLARE_API_KEY": "your-token",
"CF_ACCOUNT_ID": "your-account-id"
}
}
}
}
```
### Grok Bot
Set via environment variables before launch:
```bash
export MCP_CLOUDFLARE_API_KEY="your-token"
export CF_ACCOUNT_ID="your-account-id"
```
Or configure in your Grok Bot deployment's MCP server list using the generic
block above.
### Hermes
Hermes is the only native provider — **zero MCP tools**, so it does not pay
the MCP schema tax. It prefetches in the background so recall does not add
~5s to every turn, and it ingests turns without blocking.
**Single profile:**
```bash
pip install git+https://github.com/hansakoch/cf-memory-plugin.git
# Add to ~/.hermes/.env
echo 'MCP_CLOUDFLARE_API_KEY=cfut_your_token' >> ~/.hermes/.env
echo 'CF_ACCOUNT_ID=your_account_id' >> ~/.hermes/.env
hermes config set memory.provider cloudflare-memory
hermes cloudflare-memory test
```
**Multi-profile (hub + specialists):**
Each profile has its own `.env` — the root `.env` does NOT propagate automatically.
```bash
# For EACH profile that needs memory access:
echo 'MCP_CLOUDFLARE_API_KEY=cfut_your_token' >> ~/.hermes/profiles/<name>/.env
echo 'CF_ACCOUNT_ID=your_account_id' >> ~/.hermes/profiles/<name>/.env
hermes --profile <name> config set memory.provider cloudflare-memory
```
**Migrating old sessions:** See [docs/hermes-migration-guide.md](docs/hermes-migration-guide.md) for bulk ingest of existing session data.
### Claude Code
```bash
claude mcp add cf-memory -- cf-memory serve
```
Or add to `.claude/mcp.json`:
```json
{
"mcpServers": {
"cf-memory": {
"command": "cf-memory",
"args": ["serve"],
"env": {
"MCP_CLOUDFLARE_API_KEY": "your-token",
"CF_ACCOUNT_ID": "your-account-id"
}
}
}
}
```
### Codex
Add to `~/.codex/config.toml`:
```toml
[mcp_servers.cf-memory]
command = "cf-memory"
args = ["serve"]
[mcp_servers.cf-memory.env]
MCP_CLOUDFLARE_API_KEY = "your-token"
CF_ACCOUNT_ID = "your-account-id"
```
### Cursor
Add to `.cursor/mcp.json`:
```json
{
"mcpServers": {
"cf-memory": {
"command": "cf-memory",
"args": ["serve"],
"env": {
"MCP_CLOUDFLARE_API_KEY": "your-token",
"CF_ACCOUNT_ID": "your-account-id"
}
}
}
}
```
### Other clients
| Agent | Where to put it | Docs |
|---|---|---|
| [OpenClaw](https://github.com/openclaw) | host MCP config | OpenClaw |
| [TRAE](https://www.trae.ai) | `.trae/mcp.json` | TRAE |
| [OpenCode](https://opencode.ai) | `~/.opencode/config.json` | OpenCode |
| [pi](https://pi.dev) | host MCP config | pi |
| Any MCP client | stdio `cf-memory serve` | [MCP spec](https://modelcontextprotocol.io) |
| [LangChain](https://www.langchain.com) / [LangGraph](https://www.langchain.com/langgraph) | Python `CloudflareMemoryClient` | LangChain |
| A2A peers | `cf-memory a2a --port 9120` | Agent card at `/.well-known/agent.json` |
---
## Use it from Python
```python
import asyncio
from cloudflare_memory import CloudflareMemoryClient
async def main():
async with CloudflareMemoryClient(
account_id="your-account-id",
api_token="your-token",
namespace="my-app",
profile="default",
) as client:
await client.remember("User prefers concise answers.")
result = await client.recall("How should I answer?")
print(result.answer)
asyncio.run(main())
```
---
## CLI
```bash
cf-memory doctor # full diagnostic (run this first!)
cf-memory test # quick connectivity check
cf-memory serve # MCP (stdio): full tool surface
cf-memory serve --slim # MCP: remember + recall only
cf-memory list # list memories (CLI-only)
cf-memory get MEMORY_ID # get one memory (CLI-only)
cf-memory delete MEMORY_ID # delete one memory (CLI-only)
cf-memory summary # markdown summary (CLI-only)
cf-memory ingest messages.json # extract from a conversation (CLI-only)
cf-memory namespaces # list namespaces (CLI-only)
cf-memory create-ns NAME # create a namespace (CLI-only)
cf-memory delete-ns NAME # delete a namespace (CLI-only)
cf-memory a2a --port 9120 # A2A on localhost
cf-memory card # print agent card
hermes cloudflare-memory status
hermes cloudflare-memory namespaces
```
**Run `cf-memory doctor` after setup** — it checks credentials, connectivity,
namespace, profile, memory count, and latency in one pass. If anything is
broken, it tells you exactly what to fix.
`ingest` and `summary` stay on the CLI because registering them as MCP tools
blows context on every turn.
---
## Cost
| Item | Today | Source |
|---|---|---|
| Agent Memory | **$0 during private beta.** 30-day notice before billing. | [Pricing](https://developers.cloudflare.com/agent-memory/platform/pricing/) |
| Workers Paid (required to apply) | **$5/month** minimum | [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/) |
| This plugin | Free (MIT). Your own HTTP calls only. | — |
Recommended: Workers Paid on the account that will hold memory. Do not put
production memory on a free account. Do not assume every paid Workers account
has Agent Memory — we verified paid ≠ entitlement.
After beta, treat Agent Memory as a separate bill. Cloudflare has not published
GA rates yet.
---
## Dependencies
Runtime:
| Package | Why |
|---|---|
| [httpx](https://www.python-httpx.org/) | HTTPS client to `api.cloudflare.com` |
| [mcp](https://github.com/modelcontextprotocol/python-sdk) | MCP server (`cf-memory serve`) |
Optional:
| Extra | Packages | When |
|---|---|---|
| `pip install 'cf-memory-plugin[a2a]'` | starlette, uvicorn | A2A peer server |
| `pip install 'cf-memory-plugin[dev]'` | pytest, pytest-asyncio, respx | Tests |
No Cloudflare Worker, Wrangler, D1, Vectorize, or Workers AI binding is
required for this plugin. Those are Cloudflare products this client does **not**
use.
---
## Security
Follow Cloudflare's token rules:
[API token best practices](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/).
- Store `MCP_CLOUDFLARE_API_KEY` in the environment or a secret store. Never
commit it.
- Scope the token to **Agent Memory** on one account.
- Set `CF_ACCOUNT_ID` yourself. This plugin will not fall back to another
account.
- `cf-memory a2a` binds to `127.0.0.1` by default. Do not expose it to the
public internet without auth.
- `ingest` and `sync_turn` send conversation text to Cloudflare. Do not ingest
secrets, passwords, or customer PII you are not allowed to store.
- Use one namespace per app and one profile per user/tenant.
- Rotate the token from the
[API Tokens](https://dash.cloudflare.com/profile/api-tokens) page if it leaks.
See [SECURITY.md](SECURITY.md).
---
## Troubleshooting
### `cf-memory test` fails with "Agent Memory not enabled"
Your account has Workers Paid but not the Agent Memory entitlement. This is a
private beta — you need to join the
[waitlist](https://forms.gle/RAXbK6gN9Yy89ECw8) and wait 2-4 weeks.
### `401 Unauthorized` or `403 Forbidden`
Your API token is missing the **Agent Memory** permission, or it's scoped to
the wrong account. Create a new token at
[dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens)
with only **Agent Memory** selected.
### `CF_ACCOUNT_ID` not set
```
Error: CF_ACCOUNT_ID is required
```
Find your 32-character account ID in the
[Cloudflare dashboard](https://dash.cloudflare.com/) sidebar. Export it:
```bash
export CF_ACCOUNT_ID="abc123..."
```
### `cf-memory: command not found`
The package isn't on your `PATH`. Try:
```bash
pip install --force-reinstall git+https://github.com/hansakoch/cf-memory-plugin.git
```
Or run it as a module: `python -m cloudflare_memory` (full MCP) or
`python -m cloudflare_memory serve --slim`.
### MCP client can't connect
- Ensure `cf-memory` is on `PATH` in the environment where the client runs.
Some editors (Cursor, VS Code) use a different shell profile.
- Check the env block in your MCP config — typos in `MCP_CLOUDFLARE_API_KEY`
or `CF_ACCOUNT_ID` are the most common issue.
- Test standalone first: `cf-memory test`.
### `recall` is slow (~5s)
This is expected. `recall` does a semantic search then an LLM synthesis pass
on Cloudflare's side. Hermes users get prefetching that hides this latency.
For other clients, consider caching or calling `recall` asynchronously.
### Ingested memories don't appear immediately
`ingest` returns immediately but Cloudflare extracts memories asynchronously.
Wait 3–8 seconds, then call `list` or `recall` to verify.
### Namespace already exists / doesn't exist
```
Error: namespace 'my-app' already exists
```
This is informational. Existing namespaces are fine — the plugin reuses them.
If a namespace doesn't exist, create it:
```bash
# Via CLI (not an MCP tool — namespaces are not on the default server)
cf-memory create-ns my-app
# Or set it and it will be created on first write
export CF_MEMORY_NAMESPACE="my-app"
```
### Rate limits
Cloudflare enforces API rate limits. If you see `429 Too Many Requests`, back
off and retry. The plugin automatically retries transient errors (429, 5xx) up
to 3 times with exponential backoff.
### MCP server crash loop (every 5 minutes)
If your agent logs show the MCP server failing and restarting every 5 minutes,
the most common cause is **missing dependencies** in a sibling MCP server (not
cf-memory itself). For example, a Node.js MCP server that was never `npm install`ed.
**Diagnosis:**
```bash
# Check your agent's MCP stderr log
cat ~/.hermes/profiles/<profile>/logs/mcp-stderr.log | tail -20
# Look for: ERR_MODULE_NOT_FOUND, Cannot find package, etc.
```
**Fix:** Install the missing dependencies in the failing MCP server's directory,
then restart your agent gateway. The cf-memory MCP server itself has no external
dependencies beyond Python packages.
**Prevention:** Run `cf-memory doctor` after any config change — it validates
the full stack in one pass.
### `cf-memory doctor` — run this first
If anything feels broken, run the doctor:
```bash
cf-memory doctor
```
It checks:
1. Credentials (`MCP_CLOUDFLARE_API_KEY`, `CF_ACCOUNT_ID`)
2. API connectivity and auth
3. Namespace existence
4. Profile and memory count
5. Write/read round-trip with latency
6. Actionable error messages for every failure
Exit code 0 = healthy. Exit code 1 = something is wrong (the output tells you what).
---
## Deploy the landing page (optional)
The plugin itself is not a Worker. The public page at
[cloudflare-memory.pages.dev](https://cloudflare-memory.pages.dev) is a static
[Cloudflare Pages](https://developers.cloudflare.com/pages/) site.
```bash
# token needs Pages:Edit on the same account
export CLOUDFLARE_API_TOKEN=...
export CLOUDFLARE_ACCOUNT_ID=...
npx wrangler pages deploy ./public --project-name cloudflare-memory --commit-dirty=true
```
[Pages docs](https://developers.cloudflare.com/pages/get-started/guide/) ·
[Wrangler](https://developers.cloudflare.com/workers/wrangler/)
---
## Credits
- **Contributor:** [Hans Al Koch](https://github.com/hansakoch) (HAK)
- Canonical source: [github.com/hansakoch/cf-memory-plugin](https://github.com/hansakoch/cf-memory-plugin). Listings that scrape this repository are not the source.
- Memory backend: [Cloudflare Agent Memory](https://developers.cloudflare.com/agent-memory/)
([blog](https://blog.cloudflare.com/introducing-agent-memory/),
[Discord](https://discord.cloudflare.com),
[Community](https://community.cloudflare.com))
- Protocol: [Model Context Protocol](https://modelcontextprotocol.io)
- Hermes provider contract: [Nous Research Hermes Agent](https://hermes-agent.nousresearch.com)
- HTTP client: [httpx](https://www.python-httpx.org/)
- Design inspiration: [Open Brain (OB1)](https://github.com/NateBJones-Projects/OB1) — "One database, one AI gateway, one chat channel — any AI plugs in."
This repo is not affiliated with Cloudflare or Nous Research.
## License
[MIT](LICENSE)
## MCP Compatibility
### mcp 1.26.0+
If you get `ModuleNotFoundError: No module named 'mcp.server.mcpserver'`, the fix is already applied in this repo. The server uses `FastMCP` instead of `MCPServer`.
### Older mcp versions
If you have mcp < 1.0, you may need to update:
```bash
pip install --upgrade mcp
```
## Multi-Agent Setup (All Profiles)
### Automatic Configuration
When installing cf-memory-plugin, configure ALL Hermes profiles:
```bash
# For each profile in ~/.hermes/profiles/:
for profile in $(ls ~/.hermes/profiles/); do
# Set memory provider
hermes --profile $profile config set memory.provider cloudflare-memory
# Add MCP server
cat >> ~/.hermes/profiles/$profile/config.yaml << 'MCP'
mcp:
servers:
cf-memory:
command: cf-memory
args: ["serve"]
env:
MCP_CLOUDFLARE_API_KEY: ${MCP_CLOUDFLARE_API_KEY}
CF_ACCOUNT_ID: ${CF_ACCOUNT_ID}
MCP
# Add vision support
current=$(hermes --profile $profile config get toolsets 2>/dev/null)
if ! echo "$current" | grep -q "computer_use"; then
hermes --profile $profile config set toolsets "$current,computer_use"
fi
# Remove old memory toolset (prevents using local memory)
current=$(hermes --profile $profile config get toolsets 2>/dev/null)
if echo "$current" | grep -q "memory"; then
new=$(echo "$current" | sed 's/,memory//g' | sed 's/memory,//g')
hermes --profile $profile config set toolsets "$new"
fi
done
```
### SOUL.md Migration
Store all SOUL.md files in CF memory for cross-agent access:
```bash
python3 << 'PYEOF'
import asyncio, os, glob
from cloudflare_memory import CloudflareMemoryClient
async def main():
async with CloudflareMemoryClient(
account_id=os.environ["CF_ACCOUNT_ID"],
api_token=os.environ["MCP_CLOUDFLARE_API_KEY"],
namespace="hermes",
profile="default",
) as client:
for soul_path in glob.glob(os.path.expanduser("~/.hermes/profiles/*/SOUL.md")):
profile_name = soul_path.split("/")[-2]
with open(soul_path, "r") as f:
content = f.read()
await client.remember(f"[SOUL.md:{profile_name}] {content[:2000]}")
print(f"Stored SOUL.md for {profile_name}")
asyncio.run(main())
PYEOF
```
### Multi-Device Access
CF memory is accessible from any device with:
- API token (MCP_CLOUDFLARE_API_KEY)
- Account ID (CF_ACCOUNT_ID)
Configure each device's agents with the same credentials.
### Export Data
```bash
python3 << 'PYEOF'
import asyncio, os, json
from cloudflare_memory import CloudflareMemoryClient
async def main():
async with CloudflareMemoryClient(
account_id=os.environ["CF_ACCOUNT_ID"],
api_token=os.environ["MCP_CLOUDFLARE_API_KEY"],
namespace="hermes",
profile="default",
) as client:
entries = await client.list_memories(per_page=100)
export = [{"id": e.id, "type": e.type, "summary": e.summary} for e in entries]
with open("cf-memory-export.json", "w") as f:
json.dump(export, f, indent=2)
print(f"Exported {len(entries)} memories")
asyncio.run(main())
PYEOF
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues