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.