WeaveScope

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.