Filesystem Tools
Moltis ships six native filesystem tools that agents use for structured,
typed file I/O: Read, Write, Edit, MultiEdit, Glob, and Grep.
Their schemas match Claude Code exactly so LLMs trained on those tools
work without adaptation. See GitHub #657
for background.
Prefer these over shelling out via the exec tool running cat / sed /
rg — the native tools give the model line-numbered reads, uniqueness-
enforced edits, typed error payloads, and structured audit logs.
Tools
Read
Read a file with line-numbered output.
{
"file_path": "/absolute/path/to/file.rs",
"offset": 1,
"limit": 2000,
"pages": "1-5"
}
file_path— absolute path (required). Relative paths are rejected.offset— 1-indexed line to start at. Default1.limit— max lines to return. Default2000.pages— (PDF only) page range to extract, e.g."1-5","3", or"10-20". Max 20 pages per request.
Returns one of the following typed payloads (the kind field is the
discriminator the LLM branches on):
kind: "text"— happy path. Includescontent(cat-n style line numbers),total_lines,rendered_lines,start_line,truncated.kind: "binary"— file detected as binary. Includesbytes. Withbinary_policy = "base64"the payload also carriesbase64.kind: "not_found"— file does not exist.kind: "permission_denied"— read not permitted by filesystem ACLs.kind: "too_large"— file exceedsmax_read_bytes.kind: "not_regular_file"— path is a directory, fifo, socket, etc.kind: "path_denied"— blocked by[tools.fs]allow/deny rules.kind: "image"— file detected as a supported image format (PNG, JPEG, GIF, WebP). Includeswidth,height,format, andbase64data. Images are resized and optimized for LLM consumption.kind: "pdf"— file detected as PDF. Includestext(extracted content),pages(total page count),rendered_pages, andtruncated. Supports optionalpagesparameter (e.g."1-5") to extract a page range.
CRLF files are rendered with \r stripped so the line-number column
aligns correctly; the file on disk is not modified.
Write
Atomically write a file. Parent directories must already exist. Refuses to follow symlinks.
{
"file_path": "/absolute/path/to/file.rs",
"content": "fn main() {}\n"
}
Returns { file_path, bytes_written, checkpoint_id }. checkpoint_id
is null unless [tools.fs].checkpoint_before_mutation is enabled.
Edit
Exact-match string replacement. Refuses to edit if old_string is not
unique in the file unless replace_all=true — the uniqueness requirement
is the main correctness win over shell sed.
{
"file_path": "/absolute/path/to/file.rs",
"old_string": "fn foo()",
"new_string": "fn bar()",
"replace_all": false
}
Returns { file_path, replacements, replace_all, recovered_via_crlf, checkpoint_id }.
recovered_via_crlf is true when the tool fell back to CRLF matching
for an LF-only old_string against a CRLF file.
MultiEdit
Apply multiple sequential edits to a single file atomically. Each edit sees the output of the previous. Either all succeed or the file is left untouched.
{
"file_path": "/absolute/path/to/file.rs",
"edits": [
{ "old_string": "alpha", "new_string": "ALPHA" },
{ "old_string": "beta", "new_string": "BETA" }
]
}
Glob
Find files matching a glob pattern, sorted by modification time (newest
first). Respects .gitignore by default.
{
"pattern": "**/*.rs",
"path": "/absolute/path/to/project"
}
path is required unless [tools.fs].workspace_root is configured. A
relative path is rejected.
Grep
Regex content search. Walks with the same ignore rules as Glob.
{
"pattern": "fn\\s+main",
"path": "/absolute/path/to/project",
"output_mode": "content",
"glob": "**/*.rs",
"-n": true,
"-C": 2
}
Parameters: pattern (regex, required), path, glob, type
(rust / py / ts / etc.), output_mode (files_with_matches /
content / count), -i (case-insensitive), -n (line numbers),
-A / -B / -C (context lines), multiline, head_limit, offset.
Configuration
All fs tools are configured under a single [tools.fs] section. Every
field is optional and conservative by default — the tools work out of
the box with no configuration.
[tools.fs]
# Default search root for Glob/Grep when `path` is omitted. Absolute.
# workspace_root = "/home/user/projects/my-app"
# Absolute path globs the fs tools may touch. Empty = no allowlist.
# Evaluated after canonicalization so symlinks can't escape.
allow_paths = []
# Deny globs. Deny wins over allow.
deny_paths = []
# Per-session Read history + loop detection. Prerequisite for
# must_read_before_write.
track_reads = false
# Refuse Write/Edit/MultiEdit on files the session hasn't Read.
# Requires track_reads = true.
must_read_before_write = false
# When true, Write/Edit/MultiEdit pause for explicit operator approval
# before mutating a file.
require_approval = true
# Read size cap. Files larger than this return a typed "too_large" payload.
max_read_bytes = 10485760 # 10 MB
# Binary file handling:
# "reject" — typed marker without content (default)
# "base64" — include base64-encoded bytes in the payload
binary_policy = "reject"
# Whether Glob/Grep respect .gitignore / .ignore / .git/info/exclude.
respect_gitignore = true
# When true, Write/Edit/MultiEdit snapshot the target file via the
# existing CheckpointManager before mutating, so the pre-edit state can
# be restored via the `checkpoint_restore` tool. Off by default because
# checkpoints grow with agent activity.
checkpoint_before_mutation = false
# Adaptive read sizing: when set, Read caps per-call output so a single
# call can't consume more than ~20% of the model's working set.
# Clamped to [50 KB, 512 KB]. When unset, Read uses a fixed 256 KB cap.
# context_window_tokens = 200000 # e.g. Claude Sonnet
require_approval reuses the existing approval queue and WebSocket
prompting path. If nobody approves the request, the mutation times out
instead of landing silently.
Policy Integration
The [tools.policy] allow/deny list gates access by tool name, not by
file path. You can make an agent read-only without touching fs-level
policy:
[tools.policy]
deny = ["Write", "Edit", "MultiEdit"]
The agent retains Read, Glob, and Grep — no need to deny the
shell tool wholesale and lose every other capability.
File-path allow/deny lives in [tools.fs]. Layer both for fine-grained
control. Example: a code-reviewer agent that can read the project tree
but can’t touch anything outside it:
[tools.policy]
allow = ["exec", "browser", "memory", "Read", "Glob", "Grep"]
[tools.fs]
workspace_root = "/home/user/project"
allow_paths = ["/home/user/project/**"]
deny_paths = ["/home/user/project/.env*", "/home/user/project/secrets/**"]
respect_gitignore = true
require_approval = true
Managed Files
Managed Files are a storage surface, not another native
filesystem tool. Read, Write, Edit, MultiEdit, Glob, and Grep can
operate on that surface only when their normal tool policy and path policy
allow it.
Use the host path reported as Host: files_dir=... when filesystem tools run
on the gateway host. In Docker, Podman, or Apple Container sandboxes, use
/home/sandbox/files; tools.exec.sandbox.managed_files_mount determines
whether that path is unavailable, read-only, or read-write. MOLTIS_FILES_DIR
provides the corresponding path to exec.
Files placed there are not automatically read, indexed as memory, or added to the prompt. The path is advertised so the agent can access a requested file deliberately.
Structured Audit
Every tool invocation is a structured event through moltis’s existing
tracing layer and the moltis_tool_executions_total /
moltis_tool_execution_errors_total metrics. Writes appear in traces as
structured key/value pairs — tool=Write file_path=... bytes=... outcome=ok
— dramatically easier to review than an opaque shell command string, and
the second big win (alongside model-quality improvements) that motivated
#657.
Related
- Checkpoints — pairs with
checkpoint_before_mutationfor opt-in pre-edit snapshots. - Hooks —
BeforeToolCallandToolResultPersistreceive structured payloads for each fs tool call, so policy hooks can inspect typed parameters instead of parsing shell strings. - Sandbox — fs tools route through the sandbox when the session is sandboxed. If no real sandbox backend is available, Moltis warns at startup and the tools operate on the gateway host.