fs data types: inputs and outputs
Every operation in the fs module is driven by plain, serde-derived
structs that double as the JSON schema for the MCP tool arguments. This page is
the reference for those types and for the hashline output format they share.
It is organized in three parts:
- The hashline format — the common output convention you must understand first.
- Request types — what you pass in to
read,write, andedit. - Response types — what you get back.
All the request types derive Deserialize
and JsonSchema, so they serialize directly to/from the tool-call JSON that an
agent sends and receives.
The hashline output format
Reading, writing, editing, and rolling back all communicate through a shared text convention called hashline. Understanding it is the key to using the module well, so here is what you need to know.
Every operation that reports a file’s state returns two fields: a file_hash
and a header. They are produced from the whole file’s content:
file_hash— a 4-hex-character fingerprint of the file’s normalized text (uppercase, e.g.1A2B). It is content-derived: any read of byte-identical content mints the same tag, so two reads of an unchanged file agree.header— the display form,¶path#TAG. The¶prefix is the hashline file marker and#separates the path from the hash tag, e.g.¶src/main.rs#3C4D.
The hash is the anchor for safe editing. When you later call edit, you pass
the tag back; the engine refuses to apply your edit if the live file no longer
hashes to it (and attempts a 3-way merge recovery if it changed since you read
it). This is what prevents an agent from silently editing a file that moved
underneath it.
Numbered lines. When read displays file content it prefixes each line with
its 1-based line number using the | separator: N| text. So line 1 of a file
containing fn main() {} renders as 1| fn main() {}. These line numbers are
what you reference in edit operations (replace 5..7:, insert after 15:, …).
The hashline tags are also recorded in the session rollback history every time a
file is read or written, which is what makes rollback possible.
Request types
Target — one read request
Target describes a single read. The only required field is path; the rest
select how much of the file you want, and they compose:
| Field | Type | Meaning |
|---|---|---|
path |
String |
Path to read, relative to the project root (or absolute, if within the guards). |
line |
Option<usize> |
A 1-based line number. When set, the reader resolves the syntactic block containing that line (function, struct, impl, …) and returns it. |
symbol |
Option<String> |
A symbol name (function, class, variable) to look up within the file, or across a directory tree. |
line_range |
Option<String> |
An exact 1-based inclusive range or comma-separated list of ranges, e.g. "50-100" or "10-20,200-220". A plain line slice — no AST resolution. |
Precedence and interaction rules, straight from the implementation:
symbolis checked first and wins over everything: when set, the reader searches for the definition and ignoreslineandline_range.line_rangeis checked beforeline— if both are set, the exact slice wins over the AST block resolution.- When
pathis a directory, asymbolmust be provided; the reader then walks the tree recursively and searches every file with a supported tree-sitter grammar. Reading a directory without a symbol is an error. pathwithoutline/symbol/line_rangereads the whole file.
Reading less than a whole file is strongly encouraged. Block and symbol reads elide large blocks, exact-range reads truncate over-long columns, and both report how to continue, so the model reads only the lines it needs instead of whole files.
FsRead — the read input
pub struct FsRead {
pub targets: Vec<Target>,
}
The wrapper for a read batch. Fs::read accepts the targets directly and builds
this internally; the free function read(metadata, FsRead) takes it explicitly.
Errors are not fatal: a missing file or a bad line_range is reported as a
warning inside the matching ReadResult, and the rest of the batch still runs.
TargetFile and FsWrite — the write input
pub struct TargetFile {
pub text: String,
pub path: String,
}
pub struct FsWrite {
pub targets: Vec<TargetFile>,
}
One TargetFile is a complete overwrite: the file at path is replaced by
text in its entirety. There is no partial write — use edit for
that. The text is normalized before writing (see write) and may
carry legacy hashline display prefixes copied from output; N: (colon) line
prefixes and [path#hash] headers are stripped automatically when the whole
content is consistently prefixed. The current read format — ¶path#TAG
headers and N| pipe prefixes — is not auto-stripped, so send the raw
file text without line-number prefixes.
EditTarget and FsEdit — the replace-engine edit input
pub struct EditTarget {
pub path: String, // path to edit, relative to the project root
pub file_hash: String, // 4-hex tag from a read/search, e.g. "1A2B"
pub ops: String, // hashline edit operations, one per line
}
pub struct FsEdit {
pub targets: Vec<EditTarget>,
}
file_hash must match the file’s current content (copy it verbatim from the
¶path#TAG header of a read). ops is a small edit DSL described on the
edit page; briefly:
replace 5..7:
+fn hello() {
+ println!("hi");
+}
delete 10..12
insert after 15:
+// new comment
Order carries intention: targets are applied in the order listed, and a batch
aborts at the first failure (see EditBatchError).
AstEditOp and FsAstEdit — the AST-engine edit input
pub struct AstEditOp {
pub pat: String, // structural pattern to match
pub out: String, // replacement template
}
pub struct FsAstEdit {
pub ops: Vec<AstEditOp>, // rewrite ops, applied in order per file
pub paths: Vec<String>, // files, directories, or globs to rewrite
pub max_files: Option<usize>, // optional cap (default: 1000)
}
Patterns are ast-grep style and structurally validated. $NAME matches exactly
one node; $$$NAME matches zero or more nodes (an argument list, for example).
The same metavariable must capture identical text in every occurrence for a
match to succeed. Full details and examples are on the
AST engine page.
FsMetadata — the shared configuration
pub struct FsMetadata {
pub root: PathBuf,
pub allowlist: Option<Vec<PathBuf>>,
pub blocklist: Option<Vec<PathBuf>>,
}
The snapshot of the guards that the free functions receive. The Fs wrapper
builds this from its own state on every call — the write scope comes from
allowlist/blocklist, and the read scope substitutes
read_allowlist/read_blocklist when present. You normally never construct it
yourself. Each operation validates paths against it through the internal
fs_guard method (which delegates to PathGuard, see the
permission model).
FsRollbackInput and FsRollback — the rollback input
pub struct FsRollbackInput {
pub path: String,
pub hash: String,
}
pub struct FsRollback; // marker type, currently unused
FsRollbackInput is the JSON shape an agent sends for the fs_rollback tool;
Fs::rollback receives path and hash as separate arguments instead. An
empty hash means “restore the version immediately before the current file
content”.
Response types
Every response type implements Serialize except EditBatchError (an error
type, Display-formatted only), so the results serialize back to JSON for the
agent. serde also skips empty Option fields where noted.
ReadResult
pub struct ReadResult {
pub path: String,
pub file_hash: String,
pub header: String, // "¶path#TAG"
pub content: String, // numbered lines, possibly elided/truncated
pub warnings: Option<String>,
}
One entry per successfully rendered target. content is what the model reads:
hashline header, then N| text lines, then any content notices (elision
markers, truncation notices, “how to continue” hints). warnings is None on
success and a human-readable explanation on partial or failed reads — a missing
file, an unresolvable block, a symbol not found, or an invalid line_range.
ReadResult does not carry a first_changed_line; see EditResult for that.
WriteResult
pub struct WriteResult {
pub path: String,
pub file_hash: String,
pub header: String, // "¶path#TAG"
pub warnings: Option<String>,
}
Same shape as ReadResult minus content. On success, file_hash/header
describe the content that was just written, so a follow-up edit can anchor on
them directly. warnings reports non-fatal events such as auto-stripped
hashline prefixes, chmod +x on a shebang file, an empty file, a machine-generated
file that was refused, or a failed write.
EditResult
pub struct EditResult {
pub path: String,
pub file_hash: String,
pub header: String, // "¶path#TAG"
pub first_changed_line: Option<u32>, // 1-based, first line the edit touched
pub warnings: Vec<String>,
pub diff: Option<String>, // unified diff when the file changed
}
Produced by both edit engines. first_changed_line is None when nothing
changed; diff is a 3-context-line unified diff between the before and after
text, serialized only when present. warnings collects per-file issues such as
an unparseable pattern or “no matches found”.
EditBatchError
pub struct EditBatchError {
pub failed_path: String, // the target that failed
pub cause: String, // the underlying error
pub applied: Vec<EditResult>, // committed results before the failure
pub skipped: Vec<String>, // paths never attempted, after the failure
}
The error type of the replace engine’s batch. As covered on the
module overview, when target N fails the batch stops: earlier results
stay committed (applied), and later targets are skipped as a consequence
(skipped), never reported as independent failures. It implements Display
with a readable multi-line message listing the fresh hash tags of the applied
results so the agent can continue editing those files without re-reading.
RollbackResult
pub struct RollbackResult {
pub path: String,
pub file_hash: String, // hash of the restored version now on disk
pub header: String, // "¶path#TAG"
pub replaced_hash: String, // hash of the previous disk state
pub warning: Option<String>,
}
Returned by a successful rollback. replaced_hash is the hash
of whatever was on disk before the restore; pass it back to a subsequent
rollback to undo the restore. warning notes anomalies that did not prevent the
restore (for example, the file was modified externally since the snapshot).