supermemory/apps/docs/smfs/providers/cloudflare.mdx
Prasanna c01e3a3de0
docs: SMFS documentation — providers, Python bash tool, examples (#889)
Co-authored-by: Dhravya <63950637+Dhravya@users.noreply.github.com>
Co-authored-by: docs <docs@supermemory.ai>
2026-04-28 20:04:15 -07:00

337 lines
9.1 KiB
Text

---
title: "Cloudflare"
description: "Give your AI agent persistent memory inside a Cloudflare Container using SMFS"
---
Mount a Supermemory container inside a
[Cloudflare Container](https://developers.cloudflare.com/containers/) so your
agent can read and write memory using standard filesystem commands.
## How it works
There are two ways to wire SMFS into a Cloudflare Container — pick the one that
fits your architecture.
### Agent inside the container
The agent process runs inside the container with direct access to the SMFS
mount. The entrypoint sets up the mount and starts the agent.
```mermaid
graph LR
subgraph Cloudflare Container
Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["/memory<br/>(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
### Agent outside the container
The agent runs in a Cloudflare Worker and sends commands to the container over
HTTP. The container exposes a simple exec endpoint.
```mermaid
graph LR
Agent["Worker<br/>(agent logic)"] -->|"containerFetch('/exec')"| Container
subgraph Container ["Cloudflare Container"]
Mount["/memory<br/>(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
## Prerequisites
- A [Supermemory API key](https://supermemory.ai)
- An [Anthropic API key](https://console.anthropic.com)
- A [Cloudflare account](https://dash.cloudflare.com) with Containers enabled (Workers Paid plan)
- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/)
- The [`@cloudflare/containers`](https://www.npmjs.com/package/@cloudflare/containers) package: `npm install @cloudflare/containers`
<Note>
Cloudflare Containers are implemented as container-enabled Durable Objects.
You declare a `Container` subclass, bind it as a Durable Object, and
reference its image in the `containers` array. Worker secrets are **not**
automatically visible inside the container — you have to pass them through
`envVars` when starting the container (see below).
</Note>
---
## Pattern A: Agent inside the container
SMFS and the Claude Agent SDK are baked into the container image. On startup,
the entrypoint mounts memory and runs the agent.
### Dockerfile
```dockerfile Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y fuse3 curl bash && rm -rf /var/lib/apt/lists/*
RUN echo 'user_allow_other' >> /etc/fuse.conf
RUN curl -fsSL https://smfs.ai/install | bash -s -- 0.0.1-rc2
ENV PATH="/root/.local/bin:$PATH"
RUN pip install claude-agent-sdk
COPY agent.py /app/agent.py
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
### Entrypoint
```bash entrypoint.sh
#!/bin/bash
set -e
smfs login --key "$SUPERMEMORY_API_KEY"
smfs mount my_agent --ephemeral --path /memory --foreground &
sleep 3
exec python3 /app/agent.py
```
### Agent code
```python agent.py
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
MEMORY = "/memory"
async def main():
async for message in query(
prompt=f"You have a persistent memory filesystem at {MEMORY}. "
"Read profile.md to learn about the user, then create "
"session_notes.md summarizing what you found.",
options=ClaudeAgentOptions(
allowed_tools=["Bash", "Read", "Write"],
cwd=MEMORY,
),
):
print(message)
asyncio.run(main())
```
### Worker
The Worker defines the `Container` subclass and forwards Worker secrets into
the container via `envVars`:
```typescript worker.ts
import { Container, getContainer } from "@cloudflare/containers";
export class MyAgentContainer extends Container {
defaultPort = 8080;
// Forward Worker secrets into the container at start time.
// `this.env` is the Worker env object, populated from wrangler secrets.
envVars = {
SUPERMEMORY_API_KEY: this.env.SUPERMEMORY_API_KEY,
ANTHROPIC_API_KEY: this.env.ANTHROPIC_API_KEY,
};
}
export default {
async fetch(request: Request, env: Env) {
// The container runs the agent and exits; this Worker route just kicks
// it off (e.g. on a queue message or scheduled trigger).
const container = getContainer(env.MY_CONTAINER, "agent-singleton");
return container.fetch(request);
},
};
interface Env {
MY_CONTAINER: DurableObjectNamespace<MyAgentContainer>;
SUPERMEMORY_API_KEY: string;
ANTHROPIC_API_KEY: string;
}
```
### Config
```jsonc wrangler.jsonc
{
"name": "memory-agent",
"main": "worker.ts",
"compatibility_date": "2025-04-03",
"containers": [
{
"class_name": "MyAgentContainer",
"image": "./Dockerfile",
"max_instances": 5
}
],
"durable_objects": {
"bindings": [
{ "name": "MY_CONTAINER", "class_name": "MyAgentContainer" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["MyAgentContainer"] }
]
}
```
```bash
wrangler secret put SUPERMEMORY_API_KEY
wrangler secret put ANTHROPIC_API_KEY
wrangler deploy
```
---
## Pattern B: Agent outside the container
The agent logic lives in a Worker. The container just runs SMFS and exposes an
HTTP endpoint for executing commands against the mount.
<Warning>
The `/exec` endpoint below runs arbitrary shell commands inside the
container. **Only call it from your Worker** — never expose it publicly,
and never pass user input straight into `command` without validation.
Cloudflare Containers are addressable only through their Worker by default,
so this is safe as long as you don't add a public route that proxies to
`/exec`.
</Warning>
### Container (exec server)
The Dockerfile and entrypoint are nearly identical to Pattern A — the only
differences are the Python deps (`flask` instead of `claude-agent-sdk`) and
the file we exec at the end.
```dockerfile Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y fuse3 curl bash && rm -rf /var/lib/apt/lists/*
RUN echo 'user_allow_other' >> /etc/fuse.conf
RUN curl -fsSL https://smfs.ai/install | bash -s -- 0.0.1-rc2
ENV PATH="/root/.local/bin:$PATH"
RUN pip install flask gunicorn
COPY server.py /app/server.py
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```
The entrypoint differs from Pattern A only in the final `exec` line — we run
gunicorn against the Flask app instead of `python3 agent.py`:
```bash entrypoint.sh
#!/bin/bash
set -e
smfs login --key "$SUPERMEMORY_API_KEY"
smfs mount my_agent --ephemeral --path /memory --foreground &
sleep 3
exec gunicorn -b 0.0.0.0:8080 --chdir /app server:app
```
```python server.py
import subprocess
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/exec", methods=["POST"])
def exec_command():
cmd = request.json["command"]
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True, cwd="/memory", timeout=10
)
return jsonify(stdout=result.stdout, stderr=result.stderr, code=result.returncode)
```
<Note>
We use gunicorn instead of `app.run(...)` because Flask's built-in dev
server isn't meant for production traffic. If you'd rather just see it
work, you can replace the `exec` line with
`exec python3 /app/server.py` and add `app.run(host="0.0.0.0", port=8080)`
to `server.py` — but switch back to gunicorn before you ship.
</Note>
### Worker (agent logic)
```typescript worker.ts
import { Container, getContainer } from "@cloudflare/containers";
export class ExecContainer extends Container {
defaultPort = 8080;
envVars = {
SUPERMEMORY_API_KEY: this.env.SUPERMEMORY_API_KEY,
};
}
export default {
async fetch(_request: Request, env: Env) {
const container = getContainer(env.MY_CONTAINER, "agent-singleton");
const profile = await container
.fetch(new Request("http://container/exec", {
method: "POST",
body: JSON.stringify({ command: "cat /memory/profile.md" }),
headers: { "Content-Type": "application/json" },
}))
.then((r) => r.json<{ stdout: string }>());
return Response.json({ profile: profile.stdout });
},
};
interface Env {
MY_CONTAINER: DurableObjectNamespace<ExecContainer>;
SUPERMEMORY_API_KEY: string;
}
```
### Config
```jsonc wrangler.jsonc
{
"name": "memory-exec",
"main": "worker.ts",
"compatibility_date": "2025-04-03",
"containers": [
{
"class_name": "ExecContainer",
"image": "./Dockerfile",
"max_instances": 5
}
],
"durable_objects": {
"bindings": [
{ "name": "MY_CONTAINER", "class_name": "ExecContainer" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["ExecContainer"] }
]
}
```
```bash
wrangler secret put SUPERMEMORY_API_KEY
wrangler deploy
```
---
## Tips
- Use `--ephemeral` for container mounts — keeps the cache in memory only, but
writes still push to Supermemory
- Use `smfs grep 'query'` for semantic search across all files
- Worker secrets aren't automatically visible inside the container. Pass each
one through the `envVars` field on your `Container` subclass (see the Worker
snippets above)
- Use `containerFetch` from within a Container class method (e.g., lifecycle
hooks) to call the container's own HTTP server. From the Worker, use the
stub's `.fetch()` method instead