Skip to content

Filesystem Tools

IMPORTANT

This feature is in beta and represents an initial implementation. The API and behavior may change or be removed in future releases.

Iris can read, write, search, and list files within a confined workspace directory. The five filesystem tools give Iris structured access to a single workspace root per deployment — isolated from the rest of the host filesystem by design.

WARNING

Filesystem tools are disabled by default. Enable them only on trusted deployments where you understand the access implications.

Setup

Environment Variables

VariableRequiredDefaultDescription
IRIS_FILESYSTEM_ENABLEDNofalseSet to true to enable all filesystem tools
IRIS_WORKSPACE_ROOTNostorage/app/iris/workspaceAbsolute path to the workspace root directory

Add the following to your .env file:

env
IRIS_FILESYSTEM_ENABLED=true

# Optional: override the default workspace location
IRIS_WORKSPACE_ROOT=/var/data/iris/workspace

Workspace Root Behavior

Default path (storage/app/iris/workspace): If IRIS_WORKSPACE_ROOT is not set, Iris uses storage/app/iris/workspace relative to the application root. This directory is created automatically on the first tool call if it does not already exist.

User-configured path: If IRIS_WORKSPACE_ROOT is set to a directory that does not exist, Iris will not create it. Instead, every tool call will return an error until the directory is created manually. This avoids silently writing to an unintended location.


Tools

read_file

Class: App\Tools\Filesystem\ReadFileTool

Read the contents of a file within the workspace. Returns the file content with 1-based line numbers in cat -n style ( 1\tline content). Registers the path as "read" in the session, which is required before write_file can overwrite an existing file or edit_file can modify a file.

ParameterTypeRequiredDescription
pathstringYesWorkspace-relative path to the file (e.g. notes/todo.md)
offsetintegerNo1-based line number to start reading from. Bypasses the max-bytes threshold when provided.
limitintegerNoMaximum number of lines to return (default: 2000). Bypasses the max-bytes threshold when provided.

Returns: File content with line numbers, or an error view if the path is rejected, the file does not exist, or binary content is detected.

Output Truncation

Large files are automatically truncated to protect the LLM's context window. Two limits apply — whichever is hit first wins:

Config KeyDefaultDescription
iris.filesystem.max_read_lines500Maximum number of lines sent to the LLM
iris.filesystem.max_read_chars30000Maximum number of characters sent to the LLM

When truncation occurs, a notice is appended after the file content:

Showing lines 1-500 of 3,000. Use the offset and limit parameters to read specific sections.
Full output saved to /path/to/storage/app/private/tool-output/<uuid>. Use ReadFileTool with offset/limit to access specific sections.

The full file content is always written to the tool output storage path (iris.tool_output.storage_path, default storage/app/private/tool-output/). The absolute path to that file is included in the truncation notice so the agent can reference or re-read it directly using read_file with offset and limit.

NOTE

Truncation is an LLM context optimization only. The full content is always retained in the database and in the saved output file. Your conversation history is unaffected — the agent can read any section of a large file by calling read_file again with offset and limit parameters.


write_file

Class: App\Tools\Filesystem\WriteFileTool

Write content to a file within the workspace. Creates the file and any intermediate directories if they do not exist. When writing to an existing file, the file must have been read in the current session via read_file first (read-first gate).

ParameterTypeRequiredDescription
pathstringYesWorkspace-relative path to the target file
contentstringYesFull file content to write (replaces any existing content)

Returns: Confirmation with workspace-relative path and byte count written, or an error view if the path is rejected or the read-first gate blocks the write.


edit_file

Class: App\Tools\Filesystem\EditFileTool

Perform an exact-string replacement on a file within the workspace. The file must have been read in the current session via read_file first (read-first gate).

ParameterTypeRequiredDescription
pathstringYesWorkspace-relative path to the file to edit
old_stringstringYesExact string to find and replace. Must appear in the file. Must differ from new_string.
new_stringstringYesReplacement string. Must differ from old_string.
replace_allbooleanNoWhen false (default), old_string must be unique — multiple matches return an error. When true, all occurrences are replaced.

Returns: Confirmation of the edit with replacement count, or an error view if the path is rejected, the read-first gate blocks the edit, the strings are identical, or old_string is ambiguous without replace_all.


grep

Class: App\Tools\Filesystem\GrepTool

Search file contents using a regular expression pattern within the workspace. Supports three output modes and optional scoping by subdirectory or file type.

ParameterTypeRequiredDescription
patternstringYesRegular expression to search for (PHP regex syntax, without delimiters)
pathstringNoWorkspace-relative subdirectory to restrict the search scope. Defaults to the workspace root.
globstringNoGlob pattern matched against workspace-relative file paths to filter which files are searched (e.g. **/*.php)
output_modestringNofiles_with_matches (default) — returns matching file paths only; content — returns path:line: text prefixed matching lines; count — returns match counts per file

Returns: Matching files, lines, or counts in the chosen format. Returns a human-readable no-matches message when zero files match.

Output Truncation

Search results are capped at a configurable match limit to protect the LLM's context window:

Config KeyDefaultDescription
iris.filesystem.max_grep_matches100Maximum number of matches sent to the LLM

When the cap is hit, a notice is appended after the results:

Showing 100 of 500 matches. Use a more specific pattern or path to narrow results.
Full output saved to /path/to/storage/app/private/tool-output/<uuid>. Use ReadFileTool with offset/limit to access specific sections.

The full match output is always written to the tool output storage path (iris.tool_output.storage_path). The absolute path to that file is included in the truncation notice.

NOTE

Truncation is an LLM context optimization only. The full output is always retained in the database and in the saved output file. Your conversation history is unaffected — narrow the search with a more specific pattern or path to get targeted results within the cap.


glob

Class: App\Tools\Filesystem\GlobTool

List files matching a glob pattern within the workspace. Use to discover files by name, extension, or directory structure.

ParameterTypeRequiredDescription
patternstringYesGlob pattern to match against workspace-relative paths (e.g. **/*.md, src/**/*.php)
pathstringNoWorkspace-relative subdirectory to restrict the search scope. Defaults to the workspace root.

Returns: Lexicographically sorted list of matching workspace-relative paths, or an error view if the pattern or path is rejected.


Containment Model

All five tools share a single workspace root per deployment, enforced by App\Services\Filesystem\WorkspacePathResolver. Iris resolves and validates every path before any filesystem operation occurs:

  • Tilde expansion: Iris treats ~ as the workspace root, not the system user's home directory. A path like ~/notes.md resolves to {workspace_root}/notes.md. A lone ~ resolves to the workspace root itself.
  • Absolute paths: Iris rejects any path beginning with / unless it falls within the workspace root. All other paths must be workspace-relative.
  • Traversal sequences: Iris blocks any .. segments that would resolve outside the workspace root — including paths like ../../etc/passwd and patterns like ../**/*.
  • Symlinks: If a symlink target resolves to a location outside the workspace root, Iris rejects the path.
  • Realpath resolution: For existing paths, Iris uses PHP's realpath() to resolve the final absolute path and checks it against the workspace root. For not-yet-existing paths (e.g. a new file to write), Iris normalises .. segments manually before the containment check.

Iris logs every rejection via Log::warning with the attempted input, rejection reason, and authenticated user ID (if any). Logs never include the absolute workspace root.


Denylist

The denylist (iris.filesystem.blocked_paths) lets you prevent Iris from accessing specific files or directories within the workspace, even when the path passes the containment check.

Patterns in the denylist are matched against the post-resolution absolute realpath of the requested file using PHP's fnmatch. For example, a pattern of *.env would block any file whose realpath ends in .env.

The default denylist is empty. Add patterns in config/iris-custom.php:

php
// config/iris-custom.php
return [
    'filesystem' => [
        'blocked_paths' => [
            '*.env',
            '*.key',
            'secrets/*',
        ],
    ],
];

TIP

Denylist patterns are matched against the full resolved absolute path, so patterns like secrets/* should be written relative to what fnmatch will receive — the absolute path. Use * to match within a directory at any level (e.g. */secrets/*).


Limitations

  • Text files only. The tools do not support binary files, images, or PDFs. read_file detects non-UTF-8 content by sampling the first 8 192 bytes and returns an error without reading further.
  • Max read size. read_file enforces a maximum file size of 1 MB (iris.filesystem.max_read_bytes, default 1048576) when called without offset or limit. Providing either parameter bypasses this threshold, allowing chunked reads of larger files.
  • LLM output caps. Even within the 1 MB limit, read_file truncates output sent to the LLM at 500 lines (iris.filesystem.max_read_lines) or 30,000 characters (iris.filesystem.max_read_chars), whichever comes first. grep caps results at 100 matches (iris.filesystem.max_grep_matches). Full output is always saved to the tool output storage path and the path is included in the truncation notice — use offset and limit on a follow-up read_file call to page through large files.
  • Read-first gate for edit_file and overwrite write_file. Both tools require the target file to have been read via read_file in the same session before they will modify it. Writing to a new (non-existent) file bypasses this gate.
  • Single root per deployment. There is one workspace root for the entire deployment. Per-user scoping or multiple independent workspaces are not supported.