Files
deepseek-harness/packages/fs/tool-fs
Tianyi Cui ecb8aa5b8e Add a gated Known Limitations and Deferred Work section to every package README
Every packages/*/* README now carries a canonical '## Known Limitations and
Deferred Work' section: condensed, evidence-backed bullets for consumer-visible
gaps (unimplemented features, platform caveats, MVP cuts) and consciously
postponed work (TODO/FIXME/XXX markers, RFC deferrals still open). The ten
pre-existing ad-hoc variants ('What is NOT here (TODO)', 'Deferred',
'Limitations (MVP)', 'Known limitations (tracked TODOs)', ...) are normalized
into the canonical heading.

A new doc-sync gate, scripts/verify-readme-limitations.ts, enforces the shape:
exactly one limitations-like heading per package README, byte-equal to the
canonical h2, with at least one bullet; near-miss headings fail so variants
cannot creep back. Packages with genuinely nothing to declare (dsh-brand,
dsh-timeout, dsh-subagent-mock, dsh-app-boot) are whitelisted in the script and
must NOT carry the section; whitelist entries are validated against the scanned
package set so a rename fails loud.

Wired into the doc-sync chain (package.json) and the run-gates doc-sync leaf
set; the standing rule lands in packages/AGENTS.md and the adding-a-package
cookbook; decision record in
docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md
(RFC index regenerated).

Also fixes two stale '(deferred)' markers claiming dsh-compact-basic is
unimplemented (the dsh-compact seam README's package table and the seam's
module doc comment).
2026-07-12 01:46:34 +08:00
..

@deepseek-ai/dsh-tool-fs

The model-facing filesystem toolsread, write, edit — and their executor. This is the consumer layer of the filesystem stack: it owns tool names, JSON schemas, argument validation, prompt sections, read windowing, and result formatting. It reads/writes/edits through the ctx.fs provider seam (@deepseek-ai/dsh-fs) directly — it injects fs (plus tools/systemPrompt), not a policy service. The freshness/observation policy is contributed by a separate plugin (@deepseek-ai/dsh-fs-policy) through the fs/* event gate; the tool is not method-coupled to it.

// Default deployment: a ctx.fs provider, the policy plugin, then the tools.
await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) // @deepseek-ai/dsh-fs-local
await ctx.plugin(FsPolicy)                             // @deepseek-ai/dsh-fs-policy (policy gate)
await ctx.plugin(ToolFs)                                  // this package — registers read/write/edit

@deepseek-ai/dsh-fs-policy is optional: omit it and the tools run against the bare provider (unconditional write/overwrite/edit, no observed-state). A deployment that loads these tools is expected to also load it, so the behavior is read-before-write/edit.

Config

All keys are optional; the defaults are the shipped read caps.

Key Default Meaning
readLimit 2000 Default and maximum lines returned by one read call (the tool schema advertises it as the limit default).
readMaxLineLength 2000 Characters kept per line before truncation (the suffix names the cap).
readMaxBytes 51200 Byte cap on one read call's selected lines; overflow ends the window with a "capped" footer.
readStreamMinSize 10485760 Files at or above this size (or with unknown size) stream instead of loading whole into memory.

Tools (schemas per the filesystem tool schemas RFC)

Tool Arguments Behavior
read file_path, offset?, limit? Line-numbered UTF-8 content with a pagination footer. offset is 1-based; limit defaults to and caps at the configured readLimit (2000).
write file_path, content Create or fully replace a file. With the policy plugin: overwriting an existing file requires a prior read at the unchanged version; creating a new file does not. Without it: unconditional.
edit file_path, non-empty old_string, new_string, replace_all? Literal replacement; unique match required unless replace_all is true. With the policy plugin: requires a prior read (any window) and the file unchanged since. Without it: unconditional.

Field names are snake_case to match Claude Code and existing harness tool schemas.

The tool is the executor; policy is an event gate

The tools do not inject a policy service or inspect any cache. Each tool resolves the path via ctx.fs.resolve(path, { cwd }) — passing the calling agent's session cwd (exec.agent.session.header.cwd) so a relative path resolves against the session's workspace, matching dsh-tool-bash (see the per-session cwd RFC) — then:

  • read — one ctx.fs.stat (type + size routing + version), then readText/streamText, then builds the line window, then emits fs/observed with a plain ctx.emit. (1 stat.)
  • writectx.waterfall('fs/write-intent', target, exec, () => undefined) for the optional guard, then ctx.fs.writeText(target, content, intent), then fs/observed. (0 stat.)
  • editctx.waterfall('fs/edit-intent', target, exec, () => undefined) for the optional guard, then ctx.fs.editText(target, edit, intent), then fs/observed. (0 stat.)

The tool passes exec (the tool-execution context) as the opaque actor on every dispatch. The default thunks return undefined (the unconstrained bare provider). When @deepseek-ai/dsh-fs-policy is loaded it occupies the single decision slot — returning createIfAbsent/replaceIfVersion/{ version } or throwing FS_NOT_OBSERVED — and records on fs/observed. Backend errors (FsError) and a thrown FS_NOT_OBSERVED flow through ToolRegistry.execute() and become isError tool results with their { name, code } attached.

fs/observed is fire-and-forget

fs/observed fires AFTER the read/write/edit already succeeded, via a plain ctx.emit. A listener is contractually a synchronous, side-effect-only recorder (@deepseek-ai/dsh-fs-policy's is a WeakMap.set); the tool does not guard the emit, so a listener that throws would surface as the tool's isError result — async or fallible observation does not belong on this event.

The read rendering (line windowing + output formatting) lives in src/read-render.ts (Cordis-free, independently unit-tested); src/read.ts/write.ts/edit.ts are the tool executors and src/index.ts composes them.

Known Limitations and Deferred Work

  • No directory-listing, glob, grep, or search tools ship — a deferral of the tool-schemas RFC (and ctx.fs.listDir has no tool consumer yet); models fall back to bash.
  • read handles UTF-8 text files only — binary-safe reads and PDF/image/multimodal content are deferred; a directory target is FS_NOT_REGULAR_FILE.
  • No timeout surfaceread/write/edit take no timeout argument and declare no timeout-policy budget; cancellation rides exec.signal only (the deliberate fs-family stance).