--- 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 asyncio import os from supermemory_bash import create_bash async def main() -> None: 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) asyncio.run(main()) ``` `create_bash` returns a `CreateBashResult` with: - `bash`: a `Shell` instance with `.exec(cmd)` - `tool_description`: a pre-written tool description ready to hand to the model - `configure_memory_paths(paths)`: scope which paths get extracted into Supermemory - `refresh()`: 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 = [{"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 = [{"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) bash = result.bash 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 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.