diff --git a/apps/docs/docs.json b/apps/docs/docs.json index 82691bc1..381ac0ff 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -153,6 +153,7 @@ "smfs/install", "smfs/mount", "smfs/bash-tool", + "smfs/bash-tool-python", { "group": "Providers", "icon": "cloud", diff --git a/apps/docs/smfs/bash-tool-python.mdx b/apps/docs/smfs/bash-tool-python.mdx new file mode 100644 index 00000000..b7cbc4a6 --- /dev/null +++ b/apps/docs/smfs/bash-tool-python.mdx @@ -0,0 +1,246 @@ +--- +title: "Bash Tool (Python)" +sidebarTitle: "Bash Tool (Python)" +description: "supermemory-bash. The SMFS idea wrapped as a single agent tool, for Python agents and serverless runtimes." +icon: "terminal" +--- + +`supermemory-bash` is the SMFS idea wrapped as a single agent tool: `run_bash(command)`. The "filesystem" is your Supermemory container. Runs anywhere Python runs. AWS Lambda, Modal, Fly Machines, Cloud Run, your laptop. No mount, no FUSE, no local disk. + +Reach for the Bash Tool when your agent runs somewhere it can't mount a real filesystem. + +## Install + +```bash +pip install supermemory-bash +``` + +Or with uv: + +```bash +uv add supermemory-bash +``` + +## Quickstart + +```python +import os +from supermemory_bash import create_bash + +result = await create_bash( + api_key=os.environ["SUPERMEMORY_API_KEY"], + container_tag="user_42", +) +bash = result.bash +r = await bash.exec("ls /") +print(r.stdout) +``` + +`create_bash` returns a `CreateBashResult` with: + +- `bash`: a `Shell` instance with `.exec(cmd)` +- `volume`: the underlying `SupermemoryVolume` +- `tool_description`: a pre-written tool description (`str`) ready to hand to the model +- `configure_memory_paths(paths)`: async callable to scope which paths get extracted into Supermemory +- `refresh()`: async callable to re-prime the path index after external writes + +## Use it as a model tool + +### Anthropic SDK + +Pass `tool_description` straight into Claude's tool definition and run a normal agent loop. Each `tool_use` block calls `bash.exec` and the result goes back as a `tool_result`. + +```python +import asyncio +import os + +import anthropic +from supermemory_bash import create_bash + + +async def run_agent(user_message: str) -> str: + result = await create_bash( + api_key=os.environ["SUPERMEMORY_API_KEY"], + container_tag="user_42", + ) + bash = result.bash + + client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) + tools = [ + { + "name": "bash", + "description": result.tool_description, + "input_schema": { + "type": "object", + "properties": { + "cmd": {"type": "string", "description": "The bash command to run."} + }, + "required": ["cmd"], + }, + } + ] + + messages: list[dict] = [{"role": "user", "content": user_message}] + + for _ in range(10): + response = client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=4096, + tools=tools, + messages=messages, + ) + + if response.stop_reason == "end_turn": + for block in response.content: + if hasattr(block, "text"): + return block.text + return "" + + messages.append({"role": "assistant", "content": response.content}) + tool_results = [] + for block in response.content: + if block.type == "tool_use": + cmd = block.input.get("cmd", "") + r = await bash.exec(cmd) + output = r.stdout + if r.stderr: + output += f"\n[stderr]: {r.stderr}" + if r.exit_code != 0: + output += f"\n[exit_code]: {r.exit_code}" + tool_results.append( + { + "type": "tool_result", + "tool_use_id": block.id, + "content": output or "(no output)", + } + ) + messages.append({"role": "user", "content": tool_results}) + + return "(max steps reached)" + + +asyncio.run(run_agent("What's in my notes about the Q3 launch?")) +``` + +### OpenAI SDK + +Same idea with OpenAI's function-calling format. Define a single `bash` function, dispatch each `tool_calls` entry to `bash.exec`, and feed the output back as a `tool` message. + +```python +import asyncio +import json +import os + +from openai import OpenAI +from supermemory_bash import create_bash + + +async def run_agent(user_message: str) -> str: + result = await create_bash( + api_key=os.environ["SUPERMEMORY_API_KEY"], + container_tag="user_42", + ) + bash = result.bash + + client = OpenAI() + tools = [ + { + "type": "function", + "function": { + "name": "bash", + "description": result.tool_description, + "parameters": { + "type": "object", + "properties": {"cmd": {"type": "string"}}, + "required": ["cmd"], + }, + }, + } + ] + + messages: list[dict] = [{"role": "user", "content": user_message}] + + for _ in range(10): + response = client.chat.completions.create( + model="gpt-4o", + messages=messages, + tools=tools, + ) + message = response.choices[0].message + + if not message.tool_calls: + return message.content or "" + + messages.append(message.model_dump(exclude_none=True)) + for call in message.tool_calls: + args = json.loads(call.function.arguments or "{}") + r = await bash.exec(args.get("cmd", "")) + output = r.stdout + if r.stderr: + output += f"\n[stderr]: {r.stderr}" + if r.exit_code != 0: + output += f"\n[exit_code]: {r.exit_code}" + messages.append( + { + "role": "tool", + "tool_call_id": call.id, + "content": output or "(no output)", + } + ) + + return "(max steps reached)" + + +asyncio.run(run_agent("List my notes")) +``` + +### Claude Agent SDK + +The [Claude Agent SDK](https://docs.claude.com/en/api/agent-sdk/overview) ships with built-in `Bash`, `Read`, and `Write` tools. If your agent runs somewhere SMFS can be mounted (a long-lived process on macOS or Linux), point those built-ins at an SMFS mount and you don't need `supermemory-bash` at all — the agent just sees your container as a directory. + +See [Mount SMFS](/smfs/mount) for setup, or the [provider guides](/smfs/overview#use-smfs-with-your-sandbox-provider) for sandbox-specific instructions. + +## Memory + +The Bash Tool inherits SMFS memory semantics. By default, files named `user.md` or `memory.md` are extracted as memories. Configure additional memory paths after construction: + +```python +result = await create_bash(api_key=api_key, container_tag=container_tag) +await result.configure_memory_paths(["/notes/", "/journal.md"]) +``` + +Trailing `/` matches recursively. No slash matches an exact file. Pass `[]` to disable memory generation. + +The container also exposes a virtual `profile.md` at the root: a live digest of everything in the container. Read it once at the start of a session to give the model context without walking every file. + +```python +r = await result.bash.exec("cat /profile.md") +print(r.stdout) +``` + +## Commands the agent can run + +The Python tool exposes the same command surface as the TypeScript version: standard Unix builtins (`pwd`, `cd`, `ls`, `cat`, `stat`, `mkdir`, `rm`, `mv`, `cp`, `echo`), search and text utilities (`grep`, `find`, `head`, `tail`, `wc`, `sort`, `sed`, `awk`), plus the custom `sgrep [path]` for semantic search across the container. Pipes, redirects, conditionals, loops, and file tests all work. + +See the [TypeScript Bash Tool reference](/smfs/bash-tool#commands-the-agent-can-run) for the full list. + +## Configuration + +| Option | Default | Purpose | +| --- | --- | --- | +| `api_key` | required | Supermemory API key | +| `container_tag` | required | Container to expose as the filesystem | +| `base_url` | `None` | Override the API endpoint | +| `eager_load` | `True` | Warm the path index when the instance starts | +| `eager_content` | `True` | Also warm the content cache during eager load | +| `cwd` | `"/home/user"` | Initial working directory | +| `env` | `None` | Extra environment variables | +| `cache_ttl_ms` | `150_000` | Content cache TTL in ms. `None` = never expires (single-writer). `0` = no cache. | + +The container is what defines the filesystem; setting `cwd` or extra `env` from the host doesn't change the files the agent sees. + +## Limitations + +- `chmod`, `utimes`, and symlinks (`ln -s`, `readlink`) raise `ENOSYS`. +- `/dev/null` as a redirect target isn't supported. Write to `/tmp/discard.log` instead. +- Binary uploads aren't supported. Text is extracted server-side. diff --git a/apps/docs/smfs/overview.mdx b/apps/docs/smfs/overview.mdx index 32b9606f..ca34b11d 100644 --- a/apps/docs/smfs/overview.mdx +++ b/apps/docs/smfs/overview.mdx @@ -32,8 +32,8 @@ Pick by where your agent runs. For agents and tools with a real filesystem. Claude Code, Cursor, devcontainers, Docker, Codespaces. NFSv3 on macOS, FUSE on Linux. - - For agents running serverless or at the edge. Cloudflare Workers, AWS Lambda, Vercel. A virtual bash where the filesystem is your container. + + For agents running serverless or at the edge. Cloudflare Workers, AWS Lambda, Vercel, Modal. A virtual bash where the filesystem is your container. Available as [`@supermemory/bash`](/smfs/bash-tool) for TypeScript and [`supermemory-bash`](/smfs/bash-tool-python) for Python.