docs(smfs): address review feedback on provider guides

- Mermaid: replace literal \\n with <br/> in node labels (didn't render)
- e2b: clarify FUSE chmod / sleep comments; use Path().read_text();
  move sudo Note above Pattern B
- daytona: extract shared SMFS_INSTALL snippet; upload agent.py in
  Pattern A (was missing); reword PATH tip; reconcile warning + note
- vercel: scope Pattern A to self-hosted Node (Vercel Functions can't
  hold a FUSE mount); switch Pattern B to @supermemory/bash for
  serverless deployments
- cloudflare: switch wrangler.toml -> wrangler.jsonc with proper
  Container Durable Object binding + migrations; define Container
  subclass and pass secrets via envVars; use containerFetch + getContainer;
  add /exec security warning; switch Flask dev server to gunicorn
This commit is contained in:
docs 2026-04-27 23:31:18 +00:00 • committed by Dhravya
parent ae8108adbe
commit 3eb7933a5c
4 changed files with 299 additions and 141 deletions

View file

@ -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<br/>(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<br/>(agent logic)"] -->|"containerFetch('/exec')"| Container
subgraph Container ["Cloudflare Container"]
Mount["/memory\n(SMFS mount)"]
Mount["/memory<br/>(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`
<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>
---
@ -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<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
@ -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.
<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
@ -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)
```
<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: 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<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
```
---
@ -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

View file

@ -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.
<Warning>
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).
</Warning>
## 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<br/>(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<br/>(your server)"] -->|"sandbox.process.exec()"| Sandbox
subgraph Sandbox ["Daytona Sandbox"]
Mount["/home/daytona/memory\n(SMFS mount)"]
Mount["/home/daytona/memory<br/>(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.
<Tabs>
<Tab title="Python">
```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"
)
```
</Tab>
<Tab title="TypeScript">
```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";
```
</Tab>
</Tabs>
---
## Pattern A: Agent inside the sandbox
### Agent code
@ -84,6 +119,7 @@ asyncio.run(main())
<Tab title="Python">
```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())
<Tab title="TypeScript">
```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)
<Note>
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.
</Note>

View file

@ -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<br/>(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<br/>(your server)"] -->|"sbx.commands.run()"| Sandbox
subgraph Sandbox ["E2B Sandbox"]
Mount["/home/user/memory\n(SMFS mount)"]
Mount["/home/user/memory<br/>(SMFS mount)"]
end
Mount -->|sync| SM["Supermemory"]
```
@ -99,6 +99,7 @@ asyncio.run(main())
<Tab title="Python">
```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.
<Note>
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.
</Note>
<Tabs>
<Tab title="Python">
```python run.py
@ -242,11 +251,6 @@ the agent never touches the filesystem directly.
</Tab>
</Tabs>
<Note>
The FUSE mount is owned by root. When writing files from outside the agent,
use `sudo bash -c 'echo "..." > /path/file'`.
</Note>
---
## Tips

View file

@ -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<br/>(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
<Note>
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.
</Note>
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.