Filesystem
Official Deep Agents calls this layer "backends." BeamWeaver names the public surface BeamWeaver.Filesystem because the agent-facing capability is a virtual filesystem: tools such as ls, read_file, write_file, edit_file, glob, and grep operate on absolute POSIX-style paths regardless of where the bytes are stored.
Use filesystem in the agent DSL or :filesystem with BeamWeaver.Agent.build/1. Runtime backend: remains accepted as a compatibility alias, but new BeamWeaver documentation should use filesystem:.
Quickstart
Thread-scoped scratch files are the default when a harness capability needs a filesystem, but you can configure one explicitly:
alias BeamWeaver.Agent
alias BeamWeaver.Filesystem
{:ok, agent} =
Agent.build(
model: "openai:gpt-5.4",
filesystem: Filesystem.State.new(),
tools: []
)
Module-defined agents use the same setting:
defmodule MyApp.Agent do
use BeamWeaver.Agent
model "openai:gpt-5.4"
filesystem BeamWeaver.Filesystem.State.new()
end
Built-in filesystems:
| Filesystem | Use |
|---|---|
BeamWeaver.Filesystem.State
| Thread-scoped files stored in graph state. |
BeamWeaver.Filesystem.Local
| Trusted local disk under a configured root. |
BeamWeaver.Filesystem.LocalShell
| Local disk plus host shell execution. Unsafe outside controlled development. |
BeamWeaver.Filesystem.Store
|
Durable files in a BeamWeaver.Memory.Store namespace.
|
BeamWeaver.Filesystem.Composite
| Route different virtual path prefixes to different filesystems. |
BeamWeaver.Filesystem.Sandbox
|
Adapt a BeamWeaver.Sandbox implementation into filesystem tools plus execute.
|
| Custom module |
Any struct implementing the BeamWeaver.Filesystem behaviour.
|
Tool Surface
Setting filesystem on an agent adds BeamWeaver.Agent.Middleware.Filesystem, which contributes these model-visible tools:
| Tool | Behavior |
|---|---|
ls
| List files and directories under an absolute virtual path. |
read_file
|
Read a file with optional offset and limit line pagination.
|
write_file
| Create a new file. Existing files are not overwritten. |
edit_file
|
Replace exact strings in UTF-8 files. Multiple matches require replace_all.
|
glob
| Find files matching a glob pattern. |
grep
| Search UTF-8 files for a literal string. |
execute
|
Added only when the configured filesystem implements BeamWeaver.Filesystem.Executable.
|
read_file returns UTF-8 text directly. Binary content is stored as base64 in BeamWeaver.Filesystem.FileData; the tool currently presents base64 data as an image content block. Use download_files/3 from custom code when you need raw bytes instead of model-visible content.
The middleware also offloads large tool results under /large_tool_results/<tool_call_id> and oversized user messages under /conversation_history/..., so the model can recover details later with read_file or grep.
State
BeamWeaver.Filesystem.State stores files in graph state under the :files key by default.
filesystem BeamWeaver.Filesystem.State.new()
Use it for scratch pads, intermediate artifacts, offloaded tool results, and thread-scoped memory. Persistence depends on your graph or agent checkpointer: files survive across turns in the same thread when state is checkpointed, but they are not shared across threads.
You can change the state key:
filesystem BeamWeaver.Filesystem.State.new(state_key: :workspace_files)
Local
BeamWeaver.Filesystem.Local reads and writes real files under a trusted local root:
filesystem BeamWeaver.Filesystem.Local.new(root: "/path/to/project")
root_dir: is also accepted:
filesystem BeamWeaver.Filesystem.Local.new(root_dir: "/path/to/project")
The agent still sees virtual absolute paths. For example, /lib/app.ex maps to /path/to/project/lib/app.ex. BeamWeaver normalizes paths under the configured root, rejects .., ~, Windows drive paths, and unsafe symlink escapes.
For most coding-assistant setups, wrap local project access in a composite filesystem and keep internal agent artifacts in state:
alias BeamWeaver.Filesystem
filesystem =
Filesystem.Composite.new(
default: Filesystem.State.new(),
routes: %{
"/workspace/" => Filesystem.Local.new(root: "/path/to/project")
}
)
This keeps /large_tool_results/ and /conversation_history/ out of your project directory while still giving the agent /workspace/... access.
Local Shell
BeamWeaver.Filesystem.LocalShell extends local filesystem access with the execute tool:
filesystem BeamWeaver.Filesystem.LocalShell.new(
root: "/path/to/project",
env: %{"MIX_ENV" => "test"},
timeout: 120,
max_output_bytes: 100_000
)
Commands run through sh -c in root with the current operating-system user's permissions. inherit_env: false clears inherited environment variables before applying env.
Store
BeamWeaver.Filesystem.Store stores files in a BeamWeaver.Memory.Store namespace. Use it for cross-thread durable files such as memories, instructions, or shared reference material.
alias BeamWeaver.Filesystem
alias BeamWeaver.Memory
store = Memory.ETS.new()
filesystem =
Filesystem.Store.new(
store: store,
namespace: ["users", "user-123", "files"]
)
{:ok, agent} =
BeamWeaver.Agent.build(
model: "openai:gpt-5.4",
store: store,
filesystem: filesystem
)
For multi-user applications, use a namespace factory so each user or tenant gets isolated storage:
filesystem =
BeamWeaver.Filesystem.Store.new(
namespace: fn runtime ->
user_id = get_in(runtime.context || %{}, [:user_id]) || "anonymous"
["users", user_id, "files"]
end
)
Namespace components cannot be empty and cannot contain * or ?.
Composite
BeamWeaver.Filesystem.Composite routes virtual path prefixes to different filesystems. Longer prefixes win.
alias BeamWeaver.Filesystem
alias BeamWeaver.Memory
store = Memory.ETS.new()
filesystem =
Filesystem.Composite.new(
default: Filesystem.State.new(),
routes: %{
"/workspace/" => Filesystem.Local.new(root: "/srv/project"),
"/memories/" => Filesystem.Store.new(store: store, namespace: ["memories"])
}
)
Behavior:
| Virtual path | Routed to |
|---|---|
/notes/plan.md
|
State default
|
/workspace/lib/app.ex
|
Local under /srv/project/lib/app.ex
|
/memories/preferences.md
|
Store key preferences.md in namespace ["memories"]
|
ls, glob, and grep preserve the original route prefixes in model-visible results. If the default filesystem implements BeamWeaver.Filesystem.Executable, the composite filesystem exposes execute through that default.
You can set artifacts_root: to move middleware-created offloading paths:
filesystem =
Filesystem.Composite.new(
default: Filesystem.State.new(),
artifacts_root: "/internal",
routes: %{"/workspace/" => Filesystem.Local.new(root: "/srv/project")}
)
Sandbox
BeamWeaver.Filesystem.Sandbox adapts a BeamWeaver.Sandbox implementation to the filesystem behaviour and executable extension:
sandbox = BeamWeaver.Sandbox.local(root: "/tmp/agent-work")
filesystem =
BeamWeaver.Filesystem.Sandbox.new(sandbox: sandbox)
Use a sandbox-backed filesystem when the agent needs shell execution but should not run directly on the host. BeamWeaver includes local and Docker sandbox building blocks; hard isolation depends on the sandbox implementation you choose and how you deploy it.
See Sandboxes for sandbox lifecycle, file transfer APIs, provider integration patterns, and security guidance.
Permissions
Use BeamWeaver.Filesystem.Permission to allow or deny model-visible filesystem-tool operations before they reach the backend:
alias BeamWeaver.Filesystem.Permission
filesystem_permissions [
Permission.new(
operations: [:write],
paths: ["/policies/**"],
mode: :deny
),
Permission.new(
operations: [:read, :write],
paths: ["/workspace/**"],
mode: :allow
)
]
Rules are evaluated in order and the first match wins. If no rule matches, the operation is allowed. Permissions apply to ls, read_file, write_file, edit_file, glob, and grep; list and search results are post-filtered so denied paths do not appear in model-visible output.
Permissions are not a shell sandbox. If execute is available, enforce command policy in the executable filesystem, sandbox, host environment, or BeamWeaver.Agent.Middleware.ShellTool.
See Filesystem Permissions for rule ordering, read-only memory, subagent overrides, composite routing, and sandbox caveats.
Custom Filesystems
Implement BeamWeaver.Filesystem when you want to project S3, Postgres, a remote API, or another storage system into the agent filesystem.
Required callbacks:
| Callback | Purpose |
|---|---|
ls/3
| Return immediate child entries for a virtual path. |
read/3
|
Return %BeamWeaver.Filesystem.ReadResult{} with %FileData{}.
|
write/4
| Create a new file. Return conflict errors instead of overwriting. |
edit/5
| Replace exact strings in a UTF-8 file. |
glob/3
|
Return matching %FileInfo{} entries.
|
grep/3
|
Return %GrepMatch{} entries.
|
upload_files/3
| Bulk upload path/content pairs. |
download_files/3
| Bulk download raw bytes or text. |
Normal "not found" and validation failures should return result structs with error: set, not raise exceptions.
defmodule MyApp.S3Filesystem do
@behaviour BeamWeaver.Filesystem
alias BeamWeaver.Filesystem
defstruct [:bucket, prefix: ""]
@impl true
def ls(_backend, _path, _opts) do
%Filesystem.LsResult{entries: []}
end
@impl true
def read(_backend, _path, _opts) do
%Filesystem.ReadResult{error: "file_not_found"}
end
@impl true
def write(_backend, path, _content, _opts) do
%Filesystem.WriteResult{path: path}
end
@impl true
def edit(_backend, path, _old, _new, _opts) do
%Filesystem.EditResult{path: path, occurrences: 0, error: "string not found"}
end
@impl true
def glob(_backend, _pattern, _opts) do
%Filesystem.GlobResult{matches: []}
end
@impl true
def grep(_backend, _pattern, _opts) do
%Filesystem.GrepResult{matches: []}
end
@impl true
def upload_files(_backend, files, _opts) do
Enum.map(files, fn {path, _content} -> %Filesystem.UploadResult{path: path} end)
end
@impl true
def download_files(_backend, paths, _opts) do
Enum.map(paths, fn path -> %Filesystem.DownloadResult{path: path, error: "file_not_found"} end)
end
end
To expose execute, also implement BeamWeaver.Filesystem.Executable on the same filesystem module:
defmodule MyApp.RemoteExecutorFilesystem do
@behaviour BeamWeaver.Filesystem
@behaviour BeamWeaver.Filesystem.Executable
# Filesystem callbacks omitted for brevity.
@impl BeamWeaver.Filesystem.Executable
def id(_backend), do: "remote-executor"
@impl BeamWeaver.Filesystem.Executable
def execute(_backend, command, _opts) do
%BeamWeaver.Filesystem.Executable.ExecuteResult{
exit_code: 0,
output: "ran: #{command}"
}
end
end
Policy Hooks
Path permissions are the first line of defense. For custom validation, auditing, rate limits, or content inspection, wrap a filesystem and delegate to the inner implementation. See Filesystem Permissions for a complete wrapper example and the interaction between path rules, composite filesystems, and executable backends.