> For the complete documentation index, see [llms.txt](https://weavescope.gitbook.io/beam_weaver/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://weavescope.gitbook.io/beam_weaver/capabilities/filesystem.md).

# 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:`.

{% hint style="info" %}
**Name Mapping**

Python Deep Agents `StateBackend`, `FilesystemBackend`, `StoreBackend`, and `CompositeBackend` map to BeamWeaver `BeamWeaver.Filesystem.State`, `BeamWeaver.Filesystem.Local`, `BeamWeaver.Filesystem.Store`, and `BeamWeaver.Filesystem.Composite`.
{% endhint %}

## Quickstart

Thread-scoped scratch files are the default when a harness capability needs a filesystem, but you can configure one explicitly:

```elixir
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:

```elixir
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.

```elixir
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:

```elixir
filesystem BeamWeaver.Filesystem.State.new(state_key: :workspace_files)
```

## Local

`BeamWeaver.Filesystem.Local` reads and writes real files under a trusted local root:

```elixir
filesystem BeamWeaver.Filesystem.Local.new(root: "/path/to/project")
```

`root_dir:` is also accepted:

```elixir
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.

{% hint style="warning" %}
**Local Files Are Real Files**

`BeamWeaver.Filesystem.Local` gives the agent direct read/write access under the configured root. Use it for trusted local development, CI workspaces, or mounted volumes. Do not expose it to arbitrary end users unless the surrounding application enforces a real security boundary.
{% endhint %}

For most coding-assistant setups, wrap local project access in a composite filesystem and keep internal agent artifacts in state:

```elixir
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:

```elixir
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`.

{% hint style="danger" %}
**Not A Sandbox**

`LocalShell` can run arbitrary host commands. Filesystem permissions only apply to filesystem tools, not to shell command behavior. Use a sandbox filesystem or your own command policy for untrusted input, production services, or multi-tenant systems.
{% endhint %}

## 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.

```elixir
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:

```elixir
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.

```elixir
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:

```elixir
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:

```elixir
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](/beam_weaver/capabilities/sandboxes.md) 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:

```elixir
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](/beam_weaver/capabilities/permissions.md) 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.

```elixir
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:

```elixir
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](/beam_weaver/capabilities/permissions.md#policy-hooks) for a complete wrapper example and the interaction between path rules, composite filesystems, and executable backends.

## Related Guides

* [Composed Agent Capabilities](/beam_weaver/capabilities/agent_harness.md)
* [Context Engineering](/beam_weaver/core-components/context_engineering.md)
* [Filesystem Permissions](/beam_weaver/capabilities/permissions.md)
* [Skills](/beam_weaver/capabilities/skills.md)
* [Sandboxes](/beam_weaver/capabilities/sandboxes.md)
* [Subagents](/beam_weaver/capabilities/subagents.md)
* [Prebuilt Middleware](/beam_weaver/core-components/prebuilt_middleware.md)
* [Tools](/beam_weaver/core-components/tools.md)
* [Human-In-The-Loop](/beam_weaver/core-components/human_in_the_loop.md)
* [Memory](/beam_weaver/capabilities/memory.md)
* [Long-Term Memory](/beam_weaver/core-components/long_term_memory.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://weavescope.gitbook.io/beam_weaver/capabilities/filesystem.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
