Appearance
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
| Variable | Required | Default | Description |
|---|---|---|---|
IRIS_FILESYSTEM_ENABLED | No | false | Set to true to enable all filesystem tools |
IRIS_WORKSPACE_ROOT | No | storage/app/iris/workspace | Absolute 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/workspaceWorkspace 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Workspace-relative path to the file (e.g. notes/todo.md) |
offset | integer | No | 1-based line number to start reading from. Bypasses the max-bytes threshold when provided. |
limit | integer | No | Maximum 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 Key | Default | Description |
|---|---|---|
iris.filesystem.max_read_lines | 500 | Maximum number of lines sent to the LLM |
iris.filesystem.max_read_chars | 30000 | Maximum 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Workspace-relative path to the target file |
content | string | Yes | Full 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Workspace-relative path to the file to edit |
old_string | string | Yes | Exact string to find and replace. Must appear in the file. Must differ from new_string. |
new_string | string | Yes | Replacement string. Must differ from old_string. |
replace_all | boolean | No | When 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
pattern | string | Yes | Regular expression to search for (PHP regex syntax, without delimiters) |
path | string | No | Workspace-relative subdirectory to restrict the search scope. Defaults to the workspace root. |
glob | string | No | Glob pattern matched against workspace-relative file paths to filter which files are searched (e.g. **/*.php) |
output_mode | string | No | files_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 Key | Default | Description |
|---|---|---|
iris.filesystem.max_grep_matches | 100 | Maximum 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
pattern | string | Yes | Glob pattern to match against workspace-relative paths (e.g. **/*.md, src/**/*.php) |
path | string | No | Workspace-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.mdresolves 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/passwdand 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_filedetects non-UTF-8 content by sampling the first 8 192 bytes and returns an error without reading further. - Max read size.
read_fileenforces a maximum file size of 1 MB (iris.filesystem.max_read_bytes, default1048576) when called withoutoffsetorlimit. Providing either parameter bypasses this threshold, allowing chunked reads of larger files. - LLM output caps. Even within the 1 MB limit,
read_filetruncates 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.grepcaps 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 — useoffsetandlimiton a follow-upread_filecall to page through large files. - Read-first gate for
edit_fileand overwritewrite_file. Both tools require the target file to have been read viaread_filein 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.