Files
deepseek-harness/docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md
T
Tianyi Cui 30c1863755 fix(fs): address review — rename to dsh-fs-policy, fs/*-intent events, RFC currency, ENOTDIR
Rename per review naming decisions:
- package dsh-file-context → dsh-fs-policy (dir, package name, plugin name,
  tsconfig refs, importers, type-equiv manifest, generated catalog + module-graph)
- events fs/write-expectation → fs/write-intent, fs/edit-expectation → fs/edit-intent
  (fs/observed unchanged); type FsWriteExpectation → FsWriteIntent, "expectation"
  wording → "intent" throughout
- exported FileContextExec → FsPolicyExec

Make the implemented RFCs describe what shipped, not the superseded designs:
the 2026-06-17 capability-seam + tool-schemas RFCs no longer place policy on
ctx.fs or use full/partial-view authorization, and the fsspec RFC's ctx.fileContext
service prose is rewritten to the fs/* event-gate reality (freshness-based auth).
Sharpen docs/rfc/implemented/AGENTS.md: a rename is a fact to fix IN PLACE — the
"new RFC" escape hatch is for macro decision reversals only, not renames.

Code fixes from review:
- fsio.ts resolveLocalTarget/probe translate ENOTDIR (a parent path segment is a
  file) into the structured FsError taxonomy instead of leaking a raw Node error;
  resolve reports FS_NOT_FOUND, probe reports absent. Regression tests proven to
  fail on the unfixed code.
- tool-fs HMR test now asserts prompt sections (not just tool schemas) are
  withdrawn on disposal.
- fs/observed is a plain (unguarded) ctx.emit: correct the fs-policy comment,
  filesystem.md, and tool-fs module doc that wrongly claimed the tool "contains"
  a throwing listener; a throw surfaces as the tool's isError result.
- drop the false "loaded by the default product config" claim (no config wires
  the fs tools yet), the duplicate ctx.bash service-map row, the stale
  FileReadRequest catalog link-map entry, and the fs/fs README EOF blank line;
  correct the dsh-fs package.json description.
2026-07-02 03:12:38 +08:00

8.4 KiB

RFC: Filesystem tool schemas — model-facing read/write/edit shapes

Status: implemented

Problem

The filesystem capability-seam RFC defines the filesystem capability seam (ctx.fs), the package split (dsh-fs, dsh-fs-local, dsh-tool-fs, plus the dsh-fs-policy policy plugin), and the observed-file/stale-version policy for read-before-write/edit checks — which the split-fs-seam and event-gate RFCs moved off ctx.fs into the dsh-fs-policy plugin on the fs/* event gate. The remaining decision for the first filesystem tool delivery is the model-facing schema surface: what arguments the model sees for read, write, and edit.

The schema should be small enough to implement in the first dsh-tool-fs pass, but stable enough that future local/remote/sandboxed filesystem backends do not require model-facing churn. It should also avoid importing every option from reference systems. Claude Code and OpenCode expose similar core file tools but differ in naming style and extra flags; this RFC chooses the minimal shared surface for the prototype.

Proposal

@deepseek-ai/dsh-tool-fs exposes these three model-facing tools in the first filesystem suite:

Tool Our schema Claude Code OpenCode Notes Part of prototype
read read(file_path, offset?, limit?) Read(file_path, offset?, limit?, pages?) read(filePath, offset?, limit?) Files only; 1-indexed offset; no image/PDF/multimodal support in the first pass. YES
write write(file_path, content) Write(file_path, content) write(content, filePath) Creates or overwrites UTF-8 text. Under the default fs-policy, updates to existing files require a prior observation; new-file creates do not. YES
edit edit(file_path, old_string, new_string, replace_all?) Edit(file_path, old_string, new_string, replace_all?) edit(filePath, oldString, newString, replaceAll?) Literal string replacement; unique match required by default; under the default fs-policy requires a prior observation (any windowed read counts). YES

The schema uses snake_case field names (file_path, old_string, new_string, replace_all) to align with Claude Code and with existing DeepSeek Harness tool-schema examples. The consumer package translates these model-facing names into ctx.fs calls and fs/* event dispatches.

Tool schemas

read

read inspects a UTF-8 text file and returns line-numbered content.

Arguments:

  • file_path: string — required. Path to read, resolved by ctx.fs.
  • offset?: number — optional. 1-based first line to return. Defaults to the first line.
  • limit?: number — optional. Maximum number of lines to return. Defaults and caps are implementation details of dsh-tool-fs / ctx.fs.

Non-goals for the first pass:

  • No PDF pages argument.
  • No image or multimodal file reads.
  • No directory listing through read; if needed, listing becomes a separate future tool.

write

write creates or fully replaces a UTF-8 text file.

Arguments:

  • file_path: string — required. Path to write, resolved by ctx.fs.
  • content: string — required. Full UTF-8 text content to write.

Under the default fs-policy, updating an existing file with write requires a prior observation (a read/write/edit) of that file by the same execution context; the dsh-fs-policy plugin supplies the observed version as the stale guard on fs/write-intent. Creating a new file does not require a prior observation. With the policy plugin absent, write is an unconditional bare-provider create-or-overwrite.

The schema does not expose expected_hash, expected_version, or create_only as model-facing parameters. Stale-version checks are driven by backend-produced versions and the policy plugin's observed state, not by asking the model to copy version tokens through the schema.

edit

edit updates an existing UTF-8 text file by replacing literal text.

Arguments:

  • file_path: string — required. Path to edit, resolved by ctx.fs.
  • old_string: string — required. Literal text to replace. Empty strings are invalid in the first pass.
  • new_string: string — required. Literal replacement text; an empty string deletes the match.
  • replace_all?: boolean — optional. Defaults to false. When false, old_string must identify exactly one match.

edit requires a prior observation of the file in the same execution context (any windowed read counts — authorization is version freshness, not a full-view requirement), or a prior write/edit by that context. The dsh-fs-policy policy plugin derives the owner and supplies the recorded version as the stale guard; the provider's mutation lock enforces it.

The first pass rejects Codex-style patch grammars and multi-mode edit APIs. It uses one strict literal replacement mode so the model-facing contract stays simple and the backend can own exact-match, duplicate-match, line-ending, and stale-version semantics.

Result shape

The first implementation returns ContentBlock[] through the existing ToolDefinition.execute() contract. ctx.fs returns structured filesystem results and owns file-state recording/refreshing; tool-fs formats those results into the model projection.

Default native projections:

Tool Structured ctx.fs outcome consumed by tool-fs Default model projection
read returned lines, returned line count, total line count, target display path, file version, partial-view flag line-numbered text plus pagination footer
write create/update operation, target display path, new file version concise create/update success text
edit replacement count, replace-all flag, target display path, new file version concise edit success text

The structured outcome should not restate model arguments such as file_path, old_string, or content unless the backend has resolved them into new information such as displayPath, targetKey, or a new version. Token-conscious truncation is part of the model projection, not the backend's canonical result.

Deferred

The following are deliberately out of scope for the first filesystem schema pass:

  • Model-facing expected_hash, expected_version, or create_only parameters.
  • Directory listing, glob, grep, and search tools.
  • Binary-safe read/write operations.
  • PDF/image/multimodal read.
  • Code Mode projection values for filesystem tools.
  • A canonical edit diff format.

Tests

dsh-tool-fs schema tests should assert:

  • read requires file_path and accepts optional positive integer offset / limit.
  • write requires file_path and content.
  • edit requires file_path, old_string, and new_string, accepts optional boolean replace_all, rejects empty old_string, and defaults replace_all to false.
  • The registered JSON schemas use the snake_case field names in this RFC.
  • The tool descriptions accurately describe that, under the default fs-policy, existing-file write and edit require a prior observation (any windowed read counts) in the same execution context, while new-file write does not.
  • The tool-fs root plugin registers all three schemas.

Integration tests should execute read, write, and edit through ctx.tools.execute() against the real dsh-fs-local provider and verify that model arguments are translated into the expected ctx.fs calls and fs/* dispatches.

Risks

The first schema is intentionally smaller than Claude Code's. Dropping PDF pages, multimodal read, rich grep/list flags, and expected hash fields keeps the first implementation focused, but users may ask for those quickly. They should be added as separate RFCs or focused follow-ups rather than overloaded into the initial schema.

No explicit model-facing stale guard in v1. The schema does not ask the model to provide an expected hash/version. That is intentional: stale checks come from backend-produced versions and the dsh-fs-policy plugin's observed state, not from fragile model-copied tokens. Filesystem safety failures surface through structured FsError codes owned by dsh-fs, not through model-supplied version fields.

Naming becomes public surface. Once shipped, changing file_path to filePath or old_string to oldString would churn prompts, examples, and downstream clients. This RFC chooses snake_case up front and treats it as the stable model-facing contract.