DSH plugin filesystem seam: FsTarget, FsVersion and guards

Plugin DevelopmentPublished 2026-10-17Author: DeepSeek Plugin Market
DeepSeek HarnessDSH pluginFsTargetFsErrorCodefilesystemseam
How DSH plugin filesystem access works: opaque FsTarget and FsVersion, optional write guards, two fs waterfalls and stable error codes.

The DSH plugin filesystem seam has a counterintuitive rule: the target identity is opaque, so consumers should not treat it as a path. resolve() turns a user path into an FsTarget; displayPath is for display, but targetKey is a branded id — the local backend uses a realpath-like string while remote backends may use a URI or file id. Writes and edits guard against another opaque token, FsVersion, which the policy layer records and compares but nobody interprets. This design is what lets a backend swap leave tool schemas and policy untouched.

DSH plugin filesystem seam: four parts

Bottom line: definition, backend, observation policy and tool consumer each own a layer; swapping the backend changes neither policy nor tool schema.

PartResponsibility
dsh-fsOwns ctx.fs and guarded atomic text operations
dsh-fs-localLocal disk backend
dsh-fs-observation-policyRecords observed present/absent state and adds freshness rules via events
dsh-tool-fsPerforms model-facing read/write/edit and renders windows
  1. Without the policy plugin: write unconditionally creates or overwrites, and edit unconditionally replaces literal text.
  2. The policy intervenes through events: it adjudicates fs/* waterfalls rather than offering policy methods.
  3. Removing it does not break tools: tools call ctx.fs and dispatch events.
  4. Load it by default: deployments with dsh-tool-fs should also load the observation policy.
  5. Outside the mainline: filesystem access is optional and the agent loop does not depend on it.

DSH plugin FsTarget and FsVersion: two opaque identifiers

Bottom line: a path appears once at resolve time; afterwards everything uses FsTarget, and the version token is only for guards.

ts
interface FsTarget {
  targetKey: FsTargetKey    // opaque, never parse
  displayPath: string       // displayable
}
  1. Cross-capability coordinates: share execution-world paths with processPath(target), processPathFromHostPath(hostPath), fileUrl(target) and contains(parent, child) instead of assembling strings.
  2. Version tokens: FsVersion comes from the backend; the policy stores and compares it, consumers only display related metadata.
  3. Why: a remote backend's "path" may be a URI or file id, so treating identity as a path locks you to one backend.
  4. Verify: run the same tool calls against another backend — displayPath changes while the calling convention does not.

DSH plugin read paths: stat, lstat, listDir and bounded readBytes

Bottom line: stat returns metadata only; use lstat before resolve at trust boundaries; byte reads require a cap.

  1. stat metadata: version, type (file/directory/other) and optional size; absent targets return undefined.
  2. Reject early with type: refuse directories and special files before reading.
  3. Choose with size: text consumers pick readText or streamText without probing by failure.
  4. readBytes requires a cap: readBytes(target, signal, maxBytes) fails with FS_TOO_LARGE on overflow rather than truncating or buffering without bound.
  5. lstat does not follow links: resolve deliberately follows symlinks for stable identity, so trust-boundary consumers can lstat first and reject symlink.
  6. listDir returns metadata only: direct children in stable name order; permission or IO failures fail the whole listing with FS_PERMISSION_DENIED or FS_IO_ERROR.

DSH plugin write and edit guards: FsWriteIntent and atomic replacement

Bottom line: guards are optional and omission means unconditional; guarded edits validate the version before matching.

ts
type FsWriteIntent =
  | { kind: 'createIfAbsent' }
  | { kind: 'replaceIfVersion'; version: FsVersion }
  1. createIfAbsent rejects an existing target with FS_NOT_OBSERVED, even one that appeared after the initial probe.
  2. replaceIfVersion replaces only when the target exists and the version matches, otherwise FS_STALE_VERSION.
  3. Omitted guard: unconditional create-or-overwrite, not a hidden third intent.
  4. Write outcome: FsWriteOutcome carries the operation, the new version, before (null for a create) and after, both LF-normalized as a diff basis.
  5. editText is a provider-level mutation: not read plus write; oldString must be non-empty and replaceAll controls uniqueness.
  6. Atomicity: matching, line endings, stale checks and replacement share one mutation critical section.

DSH plugin policy events: two waterfalls and one record event

Bottom line: write and edit intents are single-slot decision waterfalls; observations are fire-and-forget and listeners must be synchronous.

  1. fs/write-intent and fs/edit-intent: single-slot waterfalls, dispatched with a default thunk that returns undefined (bare provider); a listener decides fully without calling next().
  2. First come, first served: slots are occupied in registration order; the policy plugin taking them is a deployment convention, not an invariant.
  3. fs/observed: carries FsObservation (present with a version, or absent) through plain ctx.emit.
  4. Listener discipline: synchronous and side-effect only — the tool does not catch throws from that emit, and a throw could replace the pending error or turn a successful mutation into isError.
  5. Owner derivation: the policy narrows the opaque actor in the event instead of importing tool, agent or session packages.

DSH plugin FsErrorCode and filesystem troubleshooting

Bottom line: read the error code first, then decide whether a guard or a backend refused.

  1. FS_NOT_OBSERVED — createIfAbsent found an existing target; switch to replace or conditional create.
  2. FS_STALE_VERSION — stale version or missing target; resolve again and retry.
  3. FS_TOO_LARGE — exceeded maxBytes on readBytes; stream or raise the cap.
  4. FS_PERMISSION_DENIED / FS_IO_ERROR — backend problems, not policy refusals.
  5. Edit reports a version error instead of a mismatch — correct, guards check the version first.
  6. Repair status, stated honestly: the four-part split, opaque identifiers, guard semantics and event discipline follow the official Filesystem page; confirm the full error-code set and signatures against its Cordis API and your version.

Sources: Filesystem, dsh-fs source, dsh-fs-observation-policy.

FAQ

Which packages make up the DSH plugin filesystem seam, and what does each do?

The DSH plugin filesystem has four parts: dsh-fs owns ctx.fs and guarded atomic text operations, dsh-fs-local implements the local backend, the observation policy records present or absent state, and dsh-tool-fs performs model-facing read/write/edit calls.

Why must a DSH plugin never treat FsTarget targetKey as a local absolute path?

A DSH plugin FsTarget targetKey is a branded opaque identifier: the local backend uses a realpath-like string while remote backends may use URIs or file ids. Consumers may display displayPath but parsing targetKey breaks on a backend swap.

Is the DSH plugin observation policy optional when dsh-tool-fs is loaded?

DSH plugin deployments loading dsh-tool-fs should also load the observation policy so the default becomes read-before-write or read-before-edit. Without it, write unconditionally creates or overwrites and edit unconditionally replaces literal text.

Can the DSH plugin createIfAbsent guard stop a file that appeared after probing?

Yes: a DSH plugin createIfAbsent guard rejects an existing target with FS_NOT_OBSERVED, even if the target appeared after the provider's initial probe. A publish operation must never replace existing content.

Why does DSH plugin editText report a version error instead of a match failure?

A guarded DSH plugin editText validates the expected version before matching literal text, so stale content reports FS_STALE_VERSION rather than a mismatch. Matching, line-ending handling, stale checks and atomic replacement share one critical section.

Related Terms

FsTarget
FsTarget is a user path resolved by a backend into a stable identity, carrying an opaque targetKey and a display-path string. Every later operation takes it instead of re-taking a path.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/filesystem
FsVersion
FsVersion is an opaque freshness token owned by the backend and guarded against by writes and edits. The policy layer records it for stale checks and consumers must not interpret it.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/filesystem
FsWriteIntent
FsWriteIntent is a guarded write intent with exactly two arms, createIfAbsent and replaceIfVersion; omitting the guard field means unconditional create-or-overwrite rather than a third arm.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/filesystem
FsObservation
FsObservation is one authoritative observation of a target: present carries the version used by guarded replacement, while absent authorizes only a guarded create and never an edit.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/filesystem

Sources