diff --git a/apps/docs/smfs/providers/cloudflare.mdx b/apps/docs/smfs/providers/cloudflare.mdx
index 127a719c..ec0e581a 100644
--- a/apps/docs/smfs/providers/cloudflare.mdx
+++ b/apps/docs/smfs/providers/cloudflare.mdx
@@ -20,7 +20,7 @@ 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\n(SMFS mount)"]
+ Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["/memory
(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
@@ -32,9 +32,9 @@ HTTP. The container exposes a simple exec endpoint.
```mermaid
graph LR
- Agent["Worker\n(agent logic)"] -->|"fetch('/exec')"| Container
+ Agent["Worker
(agent logic)"] -->|"containerFetch('/exec')"| Container
subgraph Container ["Cloudflare Container"]
- Mount["/memory\n(SMFS mount)"]
+ Mount["/memory
(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
@@ -43,8 +43,17 @@ graph LR
- 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
+- 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`
+
+
+ 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).
+
---
@@ -108,16 +117,63 @@ async def main():
asyncio.run(main())
```
-### Deploy
+### Worker
-```toml wrangler.toml
-name = "memory-agent"
-main = "worker.ts"
+The Worker defines the `Container` subclass and forwards Worker secrets into
+the container via `envVars`:
-[[containers]]
-class_name = "MY_CONTAINER"
-image = "./Dockerfile"
-max_instances = 5
+```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;
+ 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
@@ -133,8 +189,21 @@ wrangler deploy
The agent logic lives in a Worker. The container just runs SMFS and exposes an
HTTP endpoint for executing commands against the mount.
+
+ 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`.
+
+
### 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
@@ -143,7 +212,7 @@ 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
+RUN pip install flask gunicorn
COPY server.py /app/server.py
COPY entrypoint.sh /entrypoint.sh
@@ -152,6 +221,9 @@ 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
@@ -160,7 +232,7 @@ smfs login --key "$SUPERMEMORY_API_KEY"
smfs mount my_agent --ephemeral --path /memory --foreground &
sleep 3
-exec python3 /app/server.py
+exec gunicorn -b 0.0.0.0:8080 --chdir /app server:app
```
```python server.py
@@ -176,28 +248,78 @@ def exec_command():
cmd, shell=True, capture_output=True, text=True, cwd="/memory", timeout=10
)
return jsonify(stdout=result.stdout, stderr=result.stderr, code=result.returncode)
-
-app.run(host="0.0.0.0", port=8080)
```
+
+ 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.
+
+
### 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: any) {
- const container = await env.MY_CONTAINER.start();
+ async fetch(_request: Request, env: Env) {
+ const container = getContainer(env.MY_CONTAINER, "agent-singleton");
const profile = await container
- .fetch("/exec", {
+ .fetch(new Request("http://container/exec", {
method: "POST",
body: JSON.stringify({ command: "cat /memory/profile.md" }),
headers: { "Content-Type": "application/json" },
- })
- .then((r: Response) => r.json());
+ }))
+ .then((r) => r.json<{ stdout: string }>());
return Response.json({ profile: profile.stdout });
},
};
+
+interface Env {
+ MY_CONTAINER: DurableObjectNamespace;
+ 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
```
---
@@ -207,3 +329,9 @@ export default {
- 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
diff --git a/apps/docs/smfs/providers/daytona.mdx b/apps/docs/smfs/providers/daytona.mdx
index a81dc77e..7026783b 100644
--- a/apps/docs/smfs/providers/daytona.mdx
+++ b/apps/docs/smfs/providers/daytona.mdx
@@ -7,11 +7,12 @@ Mount a Supermemory container inside a [Daytona](https://daytona.io) sandbox so
your agent can read and write memory using standard filesystem commands.
- Daytona sandboxes currently cannot reach `api.supermemory.ai` due to network
- restrictions from their datacenter IPs. The SMFS binary installs and the FUSE
- mount starts, but it cannot sync data. We're working with Daytona to resolve
- this. In the meantime, use [E2B](/smfs/providers/e2b) or a
- [local mount](/smfs/providers/vercel) instead.
+ Daytona sandboxes currently cannot reach `api.supermemory.ai` from their
+ datacenter IPs. The SMFS binary still installs (we download it directly from
+ GitHub Releases), the FUSE mount still starts, and `pip install
+ claude-agent-sdk` still works — but the runtime sync to Supermemory fails. We're
+ working with Daytona to resolve this. In the meantime, use
+ [E2B](/smfs/providers/e2b) or a [self-hosted mount](/smfs/providers/vercel).
## How it works
@@ -26,7 +27,7 @@ The agent process runs inside the sandbox and accesses the SMFS mount directly.
```mermaid
graph LR
subgraph Daytona Sandbox
- Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["/home/daytona/memory\n(SMFS mount)"]
+ Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["/home/daytona/memory
(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
@@ -38,9 +39,9 @@ sandbox remotely.
```mermaid
graph LR
- Agent["Claude Agent\n(your server)"] -->|"sandbox.process.exec()"| Sandbox
+ Agent["Claude Agent
(your server)"] -->|"sandbox.process.exec()"| Sandbox
subgraph Sandbox ["Daytona Sandbox"]
- Mount["/home/daytona/memory\n(SMFS mount)"]
+ Mount["/home/daytona/memory
(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
@@ -53,6 +54,40 @@ graph LR
---
+## Install SMFS in a Daytona sandbox
+
+Both patterns below run the same setup snippet inside the sandbox before
+mounting. Daytona can't reach `smfs.ai`, so we download the binary directly
+from GitHub Releases and add `~/.local/bin` to PATH.
+
+
+
+ ```python
+ SMFS_INSTALL = (
+ "mkdir -p $HOME/.local/bin && "
+ "curl -sL https://github.com/supermemoryai/smfs/releases/download/"
+ "v0.0.1-rc2/smfs-linux-x64 -o $HOME/.local/bin/smfs && "
+ "chmod +x $HOME/.local/bin/smfs && "
+ "echo 'user_allow_other' | sudo tee -a /etc/fuse.conf > /dev/null && "
+ "pip install claude-agent-sdk"
+ )
+ ```
+
+
+ ```typescript
+ const SMFS_INSTALL =
+ "mkdir -p $HOME/.local/bin && " +
+ "curl -sL https://github.com/supermemoryai/smfs/releases/download/" +
+ "v0.0.1-rc2/smfs-linux-x64 -o $HOME/.local/bin/smfs && " +
+ "chmod +x $HOME/.local/bin/smfs && " +
+ "echo 'user_allow_other' | sudo tee -a /etc/fuse.conf > /dev/null && " +
+ "pip install claude-agent-sdk";
+ ```
+
+
+
+---
+
## Pattern A: Agent inside the sandbox
### Agent code
@@ -84,6 +119,7 @@ asyncio.run(main())
```python run.py
import os
+ from pathlib import Path
from daytona_sdk import Daytona, DaytonaConfig
daytona = Daytona(DaytonaConfig(
@@ -96,19 +132,8 @@ asyncio.run(main())
},
)
- # Install SMFS (from GitHub releases — smfs.ai is unreachable from Daytona)
- sandbox.process.exec(
- "mkdir -p $HOME/.local/bin && "
- "curl -sL https://github.com/supermemoryai/smfs/releases/download/"
- "v0.0.1-rc2/smfs-linux-x64 -o $HOME/.local/bin/smfs && "
- "chmod +x $HOME/.local/bin/smfs"
- )
-
- # Fix FUSE config and install agent SDK
- sandbox.process.exec(
- "echo 'user_allow_other' | sudo tee -a /etc/fuse.conf > /dev/null"
- )
- sandbox.process.exec("pip install claude-agent-sdk")
+ # See "Install SMFS in a Daytona sandbox" above
+ sandbox.process.exec(SMFS_INSTALL)
# Mount memory
sandbox.process.exec("$HOME/.local/bin/smfs login --key $SUPERMEMORY_API_KEY")
@@ -117,7 +142,8 @@ asyncio.run(main())
" --path /home/daytona/memory --foreground &' && sleep 3"
)
- # Run the agent
+ # Upload and run the agent
+ sandbox.fs.upload_file(Path("agent.py").read_bytes(), "agent.py")
result = sandbox.process.exec("python3 agent.py")
print(result.result)
@@ -127,6 +153,7 @@ asyncio.run(main())
```typescript run.ts
import { Daytona } from "@daytonaio/sdk";
+ import { readFileSync } from "fs";
const daytona = new Daytona({
apiKey: process.env.DAYTONA_API_KEY!,
@@ -138,19 +165,8 @@ asyncio.run(main())
},
});
- // Install SMFS (from GitHub releases — smfs.ai is unreachable from Daytona)
- await sandbox.process.exec(
- "mkdir -p $HOME/.local/bin && " +
- "curl -sL https://github.com/supermemoryai/smfs/releases/download/" +
- "v0.0.1-rc2/smfs-linux-x64 -o $HOME/.local/bin/smfs && " +
- "chmod +x $HOME/.local/bin/smfs"
- );
-
- // Fix FUSE config and install agent SDK
- await sandbox.process.exec(
- "echo 'user_allow_other' | sudo tee -a /etc/fuse.conf > /dev/null"
- );
- await sandbox.process.exec("pip install claude-agent-sdk");
+ // See "Install SMFS in a Daytona sandbox" above
+ await sandbox.process.exec(SMFS_INSTALL);
// Mount memory
await sandbox.process.exec(
@@ -162,6 +178,7 @@ asyncio.run(main())
);
// Upload and run the agent
+ await sandbox.fs.uploadFile(readFileSync("agent.py"), "agent.py");
const result = await sandbox.process.exec("python3 agent.py");
console.log(result.result);
@@ -192,16 +209,8 @@ remotely via `sandbox.process.exec()`.
},
)
- # Install and mount SMFS
- sandbox.process.exec(
- "mkdir -p $HOME/.local/bin && "
- "curl -sL https://github.com/supermemoryai/smfs/releases/download/"
- "v0.0.1-rc2/smfs-linux-x64 -o $HOME/.local/bin/smfs && "
- "chmod +x $HOME/.local/bin/smfs"
- )
- sandbox.process.exec(
- "echo 'user_allow_other' | sudo tee -a /etc/fuse.conf > /dev/null"
- )
+ # See "Install SMFS in a Daytona sandbox" above
+ sandbox.process.exec(SMFS_INSTALL)
sandbox.process.exec("$HOME/.local/bin/smfs login --key $SUPERMEMORY_API_KEY")
sandbox.process.exec(
"bash -c '$HOME/.local/bin/smfs mount my_agent --ephemeral"
@@ -235,16 +244,8 @@ remotely via `sandbox.process.exec()`.
},
});
- // Install and mount SMFS
- await sandbox.process.exec(
- "mkdir -p $HOME/.local/bin && " +
- "curl -sL https://github.com/supermemoryai/smfs/releases/download/" +
- "v0.0.1-rc2/smfs-linux-x64 -o $HOME/.local/bin/smfs && " +
- "chmod +x $HOME/.local/bin/smfs"
- );
- await sandbox.process.exec(
- "echo 'user_allow_other' | sudo tee -a /etc/fuse.conf > /dev/null"
- );
+ // See "Install SMFS in a Daytona sandbox" above
+ await sandbox.process.exec(SMFS_INSTALL);
await sandbox.process.exec(
"$HOME/.local/bin/smfs login --key $SUPERMEMORY_API_KEY"
);
@@ -275,12 +276,7 @@ remotely via `sandbox.process.exec()`.
- FUSE is available in Daytona sandboxes but `user_allow_other` needs to be
added to `/etc/fuse.conf`
-- The binary installs to `~/.local/bin/` which isn't on PATH by default in
- Daytona's zsh — use the full path or `export PATH=$HOME/.local/bin:$PATH`
+- We invoke SMFS as `$HOME/.local/bin/smfs` in the examples because Daytona's
+ default zsh PATH doesn't include `~/.local/bin`. Alternatively, prepend it
+ once with `export PATH=$HOME/.local/bin:$PATH`
- Use `pip install claude-agent-sdk` to install the agent SDK (PyPI is reachable)
-
-
- Daytona sandboxes can't reach `smfs.ai`, so the install downloads the binary
- directly from GitHub releases. The SMFS binary and Claude Agent SDK both
- install successfully — only the Supermemory API connection is blocked.
-
diff --git a/apps/docs/smfs/providers/e2b.mdx b/apps/docs/smfs/providers/e2b.mdx
index 8bf0a7d5..e014b8e1 100644
--- a/apps/docs/smfs/providers/e2b.mdx
+++ b/apps/docs/smfs/providers/e2b.mdx
@@ -19,7 +19,7 @@ Your orchestrating code just boots the sandbox and kicks off the agent.
```mermaid
graph LR
subgraph E2B Sandbox
- Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["/home/user/memory\n(SMFS mount)"]
+ Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["/home/user/memory
(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
@@ -32,9 +32,9 @@ infra.
```mermaid
graph LR
- Agent["Claude Agent\n(your server)"] -->|"sbx.commands.run()"| Sandbox
+ Agent["Claude Agent
(your server)"] -->|"sbx.commands.run()"| Sandbox
subgraph Sandbox ["E2B Sandbox"]
- Mount["/home/user/memory\n(SMFS mount)"]
+ Mount["/home/user/memory
(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
@@ -99,6 +99,7 @@ asyncio.run(main())
```python run.py
import os
+ from pathlib import Path
from e2b_code_interpreter import Sandbox
sbx = Sandbox.create(
@@ -110,10 +111,11 @@ asyncio.run(main())
},
)
- # One-time FUSE fix (device exists but is root-only by default)
+ # /dev/fuse exists in E2B but is root-only by default. chmod once per sandbox.
sbx.commands.run("sudo chmod 666 /dev/fuse")
- # Mount memory
+ # Mount memory. We background the foreground daemon so this command returns,
+ # then sleep briefly to let the FUSE mount come up before the agent reads it.
sbx.commands.run("smfs login --key $SUPERMEMORY_API_KEY")
sbx.commands.run(
"bash -c 'smfs mount my_agent --ephemeral"
@@ -121,7 +123,7 @@ asyncio.run(main())
)
# Upload and run the agent
- sbx.files.write("/home/user/agent.py", open("agent.py").read())
+ sbx.files.write("/home/user/agent.py", Path("agent.py").read_text())
result = sbx.commands.run("python3 /home/user/agent.py", timeout=120)
print(result.stdout)
@@ -142,10 +144,11 @@ asyncio.run(main())
},
});
- // One-time FUSE fix (device exists but is root-only by default)
+ // /dev/fuse exists in E2B but is root-only by default. chmod once per sandbox.
await sbx.commands.run("sudo chmod 666 /dev/fuse");
- // Mount memory
+ // Mount memory. We background the foreground daemon so this command returns,
+ // then sleep briefly to let the FUSE mount come up before the agent reads it.
await sbx.commands.run("smfs login --key $SUPERMEMORY_API_KEY");
await sbx.commands.run(
"bash -c 'smfs mount my_agent --ephemeral --path /home/user/memory --foreground &' && sleep 3"
@@ -171,6 +174,12 @@ The agent runs in your server process and executes commands inside the sandbox
remotely via `sbx.commands.run()`. The SMFS mount lives inside the sandbox —
the agent never touches the filesystem directly.
+
+ The FUSE mount is owned by root inside the sandbox. When writing to it from
+ outside the agent, wrap the command in `sudo bash -c '…'` so the redirect
+ runs with the right permissions. You'll see this in the write examples below.
+
+
```python run.py
@@ -242,11 +251,6 @@ the agent never touches the filesystem directly.
-
- The FUSE mount is owned by root. When writing files from outside the agent,
- use `sudo bash -c 'echo "..." > /path/file'`.
-
-
---
## Tips
diff --git a/apps/docs/smfs/providers/vercel.mdx b/apps/docs/smfs/providers/vercel.mdx
index 0158fad5..d3e13daa 100644
--- a/apps/docs/smfs/providers/vercel.mdx
+++ b/apps/docs/smfs/providers/vercel.mdx
@@ -3,48 +3,59 @@ title: "Vercel AI SDK"
description: "Give your AI agent persistent memory using SMFS with the Vercel AI SDK"
---
-Mount a Supermemory container on your server and let a Claude agent read and
-write memory using standard filesystem commands.
+This guide is about the [Vercel AI SDK](https://ai-sdk.dev) — the TypeScript
+agent framework — not Vercel hosting. The choice of pattern depends on where
+your code actually runs:
+
+- **Self-hosted Node** (your own VM, ECS, Fly.io, Railway, a Vercel Sandbox,
+ etc.): you can mount SMFS as a real filesystem on the server.
+- **Vercel Functions / serverless / edge**: there's no long-lived process to
+ hold a FUSE mount, so use the [Bash Tool](/smfs/bash-tool)
+ (`@supermemory/bash`) instead. The container becomes the filesystem; no mount
+ needed.
## How it works
-There are two ways to wire SMFS into a Vercel-based agent — pick the one that
-fits your architecture.
-
-### Claude Agent SDK (agent has full filesystem access)
+### Self-hosted Node (real mount)
The agent runs as a separate process with direct access to the SMFS mount.
-Best when you want the agent to have full bash, read, and write capabilities.
+Best when you want full bash, read, and write capabilities and your server is
+long-lived.
```mermaid
graph LR
subgraph Your Server
- Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["./memory\n(SMFS mount)"]
+ Agent["Claude Agent"] -->|"cat, ls, echo"| Mount["./memory
(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
-### Vercel AI SDK (agent uses a tool)
+### Vercel Functions / serverless (Bash Tool)
-The agent runs inside `generateText` and accesses memory through a bash tool
-you define. Best when you're building an API route and want to keep everything
-in one TypeScript process.
+The agent runs inside `generateText` and accesses memory through `@supermemory/bash`,
+which proxies bash commands to your Supermemory container over HTTP. No mount,
+no FUSE, no long-lived process required.
```mermaid
graph LR
- subgraph Your Server
- AI["generateText()"] -->|"bash tool"| Mount["./memory\n(SMFS mount)"]
+ subgraph Vercel Function
+ AI["generateText()"] -->|"bash tool"| Bash["@supermemory/bash"]
end
- Mount -->|sync| SM["Supermemory"]
+ Bash -->|HTTPS| SM["Supermemory"]
```
## Prerequisites
- A [Supermemory API key](https://supermemory.ai)
- An [Anthropic API key](https://console.anthropic.com)
-- SMFS installed: `curl -fsSL https://smfs.ai/install | bash`
+- For Pattern A only: SMFS installed on your server (`curl -fsSL https://smfs.ai/install | bash`)
-## Mount memory
+---
+
+## Pattern A: Claude Agent SDK on self-hosted Node
+
+Use this when the Vercel AI SDK is just the orchestrator and your real workload
+is a Claude agent running on a long-lived server you control.
Start the mount once when your server boots — not per-request:
@@ -53,12 +64,14 @@ smfs login --key $SUPERMEMORY_API_KEY
smfs mount my_agent --path ./memory
```
----
-
-## Pattern A: Claude Agent SDK
+
+ This won't work on Vercel Functions or any serverless runtime: there's no
+ process between requests to hold the mount, and FUSE isn't available. For
+ those targets, jump to Pattern B.
+
Write a standalone agent script. Nothing server-specific — just Python that
-reads and writes files.
+reads and writes files:
```python agent.py
import asyncio
@@ -87,39 +100,37 @@ python3 agent.py
---
-## Pattern B: Vercel AI SDK
+## Pattern B: Vercel AI SDK + Bash Tool (serverless-friendly)
-Expose the memory filesystem as a bash tool inside an API route. The agent
-calls the tool to run commands against the mount.
+`@supermemory/bash` exposes your Supermemory container as a single agent tool
+— `run_bash(command)` — without mounting anything. It runs anywhere TypeScript
+runs, including Vercel Functions, edge runtimes, and Lambda.
+
+```bash
+npm install @supermemory/bash ai @ai-sdk/anthropic zod
+```
```typescript api/agent.ts
import { generateText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
+import { createBash } from "@supermemory/bash";
import { z } from "zod";
-import { execSync } from "child_process";
-
-const MEMORY = "./memory";
export async function POST(req: Request) {
const { prompt } = await req.json();
+ const { bash, toolDescription } = await createBash({
+ apiKey: process.env.SUPERMEMORY_API_KEY!,
+ containerTag: "my_agent",
+ });
+
const result = await generateText({
- model: anthropic("claude-sonnet-4-20250514"),
+ model: anthropic("claude-sonnet-4-5"),
tools: {
bash: tool({
- description: `Run a bash command. Memory filesystem is at ${MEMORY}.`,
- parameters: z.object({ command: z.string() }),
- execute: async ({ command }) => {
- try {
- return execSync(command, {
- cwd: MEMORY,
- encoding: "utf-8",
- timeout: 10_000,
- });
- } catch (e: any) {
- return e.stderr || e.message;
- }
- },
+ description: toolDescription,
+ inputSchema: z.object({ cmd: z.string() }),
+ execute: async ({ cmd }) => bash.exec(cmd),
}),
},
maxSteps: 10,
@@ -130,10 +141,29 @@ export async function POST(req: Request) {
}
```
+A few things worth calling out:
+
+- **`maxSteps: 10`** lets the agent chain multiple bash calls per request
+ (read `profile.md`, then `cat` a few notes, then write a summary). Bump it
+ if your agent needs deeper chains; lower it to cap cost per request.
+- **`toolDescription`** is a pre-written description of the available bash
+ surface (semantic `sgrep`, `cat`, `ls`, redirects, etc.). Hand it straight
+ to the model — don't roll your own.
+- **No timeout/abort plumbing.** `bash.exec` already runs against the
+ container over HTTPS, so it returns when the command returns. No event-loop
+ blocking and no FUSE.
+
+See the [Bash Tool reference](/smfs/bash-tool) for the full command surface,
+memory path configuration, and other framework integrations.
+
---
## Tips
-- Mount SMFS once when your server starts, not per-request
-- Use `smfs grep 'query'` for semantic search across all files
-- Use `--ephemeral` if you don't need a local cache on the server
+- **Pattern A**: mount SMFS once when your server starts, not per-request.
+ Use `--ephemeral` if you don't need a local cache on the server.
+- **Pattern B**: configure memory paths once at startup with
+ `configureMemoryPaths(["/notes/", "/journal.md"])` to control which files
+ get distilled into Supermemory memories.
+- Both: use `smfs grep 'query'` (Pattern A) or `sgrep 'query'` inside the
+ bash tool (Pattern B) for semantic search across all files.