Skip to main content
Loom provides three core file operation tools that enable agents to interact with the codebase. All operations are scoped to the project directory and include comprehensive error handling.

file_read

Reads a file and returns its contents formatted with line numbers.
file_path
string
required
Path to the file relative to project root
offset
integer
Line number to start reading from (1-based). Use this to read specific sections of large files.
limit
integer
Maximum number of lines to return. Combine with offset for pagination.

Returns

Success response includes:
  • File path and total line count in header
  • Line-numbered content (6-character padded line numbers)
  • Count of lines shown
lib/my_app/router.ex (45 lines total, showing 45)
     1	defmodule MyApp.Router do
     2	  use Plug.Router
     3	
     4	  plug :match
     5	  plug :dispatch
...

Error Cases

enoent
File not found at the specified path
eisdir
Path points to a directory, not a file
path_validation
Path attempts to access outside project directory

Usage Examples

{:ok, result} = Loom.Tools.FileRead.run(
  %{file_path: "lib/my_app/router.ex"},
  %{project_path: "/home/user/project"}
)
Use offset and limit for large files to avoid reading unnecessary content. The header shows total lines, helping you determine if pagination is needed.

file_write

Writes content to a file, creating parent directories if needed. Overwrites existing files.
file_path
string
required
Path to the file relative to project root
content
string
required
The content to write to the file

Returns

Success response includes:
  • Number of bytes written
  • Full path to the written file
Wrote 1234 bytes to /home/user/project/lib/my_app/new_module.ex

Behavior

Parent directories are created automatically:
# This creates lib/my_app/controllers/ if it doesn't exist
FileWrite.run(
  %{
    file_path: "lib/my_app/controllers/user_controller.ex",
    content: "defmodule MyApp.UserController do\nend"
  },
  context
)
file_write always overwrites existing files without confirmation.For editing existing files, use file_edit instead to make targeted changes.

Usage Examples

{:ok, result} = Loom.Tools.FileWrite.run(
  %{
    file_path: "lib/my_app/hello.ex",
    content: """
    defmodule MyApp.Hello do
      def world, do: "Hello, world!"
    end
    """
  },
  %{project_path: "/home/user/project"}
)
Always use file_read before file_write when modifying existing files, or use file_edit for precise changes.

file_edit

Performs exact string replacement in a file. Designed to prevent ambiguous edits.
file_path
string
required
Path to the file relative to project root
old_string
string
required
The exact text to find and replace. Must match exactly including whitespace and indentation.
new_string
string
required
The text to replace it with. Must be different from old_string.
replace_all
boolean
default:"false"
Replace all occurrences. When false, old_string must appear exactly once to prevent ambiguous edits.

Returns

Success response includes:
  • Number of occurrences replaced
  • File path
Replaced 1 occurrence(s) in lib/my_app/router.ex

Validation Rules

1

String Not Found

If old_string doesn’t exist in the file:
{:error, "old_string not found in file. Make sure the text matches exactly..."}
2

Multiple Matches Without replace_all

If old_string appears multiple times and replace_all is false:
{:error, "old_string appears 3 times. Use replace_all: true to replace all, or provide a larger unique string."}
3

Successful Edit

When validation passes, the replacement is applied and the file is written.

Usage Examples

{:ok, result} = Loom.Tools.FileEdit.run(
  %{
    file_path: "lib/my_app/router.ex",
    old_string: "plug :match",
    new_string: "plug :match\n  plug :auth"
  },
  %{project_path: "/home/user/project"}
)

Best Practices

Exact Matching

Whitespace matters. Copy the exact indentation and line endings from file_read output.

Include Context

When the string might appear multiple times, include surrounding lines to make it unique.

Use replace_all Wisely

Only use replace_all: true when you’re certain you want to replace every occurrence (e.g., renaming a variable).

Read First

Always use file_read to see the current file state before editing.

Implementation Details

All file operations are implemented in lib/loom/tools/: Each tool uses Loom.Tool.safe_path!/2 for path validation.

Build docs developers (and LLMs) love