Moltis
Features

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. Default 1.
  • limit — max lines to return. Default 2000.
  • 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. Includes content (cat-n style line numbers), total_lines, rendered_lines, start_line, truncated.
  • kind: "binary" — file detected as binary. Includes bytes. With binary_policy = "base64" the payload also carries base64.
  • kind: "not_found" — file does not exist.
  • kind: "permission_denied" — read not permitted by filesystem ACLs.
  • kind: "too_large" — file exceeds max_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). Includes width, height, format, and base64 data. Images are resized and optimized for LLM consumption.
  • kind: "pdf" — file detected as PDF. Includes text (extracted content), pages (total page count), rendered_pages, and truncated. Supports optional pages parameter (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.

  • Checkpoints — pairs with checkpoint_before_mutation for opt-in pre-edit snapshots.
  • HooksBeforeToolCall and ToolResultPersist receive 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.