DSH plugin filesystem seam: FsTarget, FsVersion and guards
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.
| Part | Responsibility |
|---|---|
dsh-fs | Owns ctx.fs and guarded atomic text operations |
dsh-fs-local | Local disk backend |
dsh-fs-observation-policy | Records observed present/absent state and adds freshness rules via events |
dsh-tool-fs | Performs model-facing read/write/edit and renders windows |
- Without the policy plugin:
writeunconditionally creates or overwrites, andeditunconditionally replaces literal text. - The policy intervenes through events: it adjudicates
fs/*waterfalls rather than offering policy methods. - Removing it does not break tools: tools call
ctx.fsand dispatch events. - Load it by default: deployments with
dsh-tool-fsshould also load the observation policy. - 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.
interface FsTarget {
targetKey: FsTargetKey // opaque, never parse
displayPath: string // displayable
}
- Cross-capability coordinates: share execution-world paths with
processPath(target),processPathFromHostPath(hostPath),fileUrl(target)andcontains(parent, child)instead of assembling strings. - Version tokens:
FsVersioncomes from the backend; the policy stores and compares it, consumers only display related metadata. - Why: a remote backend's "path" may be a URI or file id, so treating identity as a path locks you to one backend.
- Verify: run the same tool calls against another backend —
displayPathchanges 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.
statmetadata:version,type(file/directory/other) and optionalsize; absent targets returnundefined.- Reject early with type: refuse directories and special files before reading.
- Choose with size: text consumers pick
readTextorstreamTextwithout probing by failure. readBytesrequires a cap:readBytes(target, signal, maxBytes)fails withFS_TOO_LARGEon overflow rather than truncating or buffering without bound.lstatdoes not follow links:resolvedeliberately follows symlinks for stable identity, so trust-boundary consumers canlstatfirst and rejectsymlink.listDirreturns metadata only: direct children in stable name order; permission or IO failures fail the whole listing withFS_PERMISSION_DENIEDorFS_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.
type FsWriteIntent =
| { kind: 'createIfAbsent' }
| { kind: 'replaceIfVersion'; version: FsVersion }
createIfAbsentrejects an existing target withFS_NOT_OBSERVED, even one that appeared after the initial probe.replaceIfVersionreplaces only when the target exists and the version matches, otherwiseFS_STALE_VERSION.- Omitted guard: unconditional create-or-overwrite, not a hidden third intent.
- Write outcome:
FsWriteOutcomecarries the operation, the new version,before(null for a create) andafter, both LF-normalized as a diff basis. editTextis a provider-level mutation: not read plus write;oldStringmust be non-empty andreplaceAllcontrols uniqueness.- 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.
fs/write-intentandfs/edit-intent: single-slot waterfalls, dispatched with a default thunk that returns undefined (bare provider); a listener decides fully without callingnext().- First come, first served: slots are occupied in registration order; the policy plugin taking them is a deployment convention, not an invariant.
fs/observed: carriesFsObservation(present with a version, or absent) through plainctx.emit.- 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. - 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.
FS_NOT_OBSERVED—createIfAbsentfound an existing target; switch to replace or conditional create.FS_STALE_VERSION— stale version or missing target; resolve again and retry.FS_TOO_LARGE— exceededmaxBytesonreadBytes; stream or raise the cap.FS_PERMISSION_DENIED/FS_IO_ERROR— backend problems, not policy refusals.- Edit reports a version error instead of a mismatch — correct, guards check the version first.
- 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
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.
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.
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.
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.
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
- DeepSeek Harness docs — Filesystem (FsTarget, write guards, fs policy events, errors and IO timeouts)· deepseek-harness.github.io
- dsh-fs provider source (types.ts and index.ts)· deepseek-ai (GitHub)
- dsh-fs-observation-policy (observed state and freshness rules)· deepseek-ai (GitHub)