Files

8.2 KiB

description, kind
description kind
The local filesystem spill backend: how spilled tool output is saved to private session-scoped files and retrieved with read or grep. package-reference

@deepseek-ai/dsh-spill-local

English | 中文

Summary

dsh-spill-local saves a tool's oversized text to a private, session-scoped file on the host filesystem and returns that file's path as the locator, with retrieval guidance telling the model to read or grep it. Mount it whenever a composition needs spill storage on the same machine the agent runs on. Files are private to the current user, names are unpredictable, and each session's files group under a stable directory, so a shared root cannot leak output or be redirected by a planted symlink. Configuration selects the root and the startup-cleanup retention period; previews and spill decisions live in other packages.

Table of Contents


Use this package

Mount this backend in a composition that spills tool output to the local filesystem. It registers as the ctx.spillStore service that the dsh-spill-policy plugin and other callers use.

Minimal configuration

Loading the plugin with no config is safe: files land in a lazily-created private (0700) per-process directory under the OS temp directory. Set root when the files must live under a known location.

- name: '@deepseek-ai/dsh-spill-local'
  config:
    root: /absolute/path/to/spill
    cleanupPeriodDays: 30
Field Default Meaning
root private 0700 temp dir Root directory for spill files; set to keep them under a known location
cleanupPeriodDays 30 File age in days before the one-shot startup cleanup may delete it; 0 disables cleanup

The generated configuration catalog is the exhaustive source for every accepted field.

What you get back

Each saveText call writes the full text to a fresh file and returns three fields: locator (the file path), bytes (the exact UTF-8 byte count), and retrievalHint — "Use read with offset/limit, or grep this path to search within it." A consumer shows that hint to the model, which can then read or search the file with its ordinary file tools.

Where files land

Files are stored at <root>/session-<hash>/<random>-<safeName>, where session-<hash> is a short hash of the owning session id (so one session's files group together) and <random>-<safeName> pairs an unpredictable hex prefix with the caller's suggested name sanitized to one safe path segment. A relative root resolves from the process working directory.

Startup cleanup

One best-effort sweep starts after activation without delaying service availability. It scans the configured root and prior default dsh-spill-* roots under the OS temp directory, deletes regular files whose modification time is strictly older than the configured cutoff, prunes empty session directories, and removes only empty prior-default roots. A long-lived process does not sweep again until restart. Disposal waits for the sweep, and a concurrent write recreates a session directory if cleanup removes it.

The sweep resolves filesystem identities, never follows or deletes symlinks, and skips unrelated entries. On POSIX it admits only roots and session directories owned by the current user, not writable by group or others, and protected from replacement through their ancestor path; writable sticky temporary directories such as /tmp are permitted. Unsafe paths produce a warning and remain untouched. Filesystem and warning-sink failures are contained, so cleanup cannot fail activation or a concurrent spill write.

Failures and recovery

A real storage failure — permissions, no space left, an unwritable root — rejects the saveText call; the caller decides how to degrade. The shipped policy treats the rejection as best-effort and keeps the original inline result.


Understand the implementation

Implementation internals — click to expand

This section explains the design decisions behind the backend; the observable behavior is fully covered in Use this package.

Design philosophy

The backend owns storage details only, on one principle: a spilled tool result must be private and unredirectable. The root is private (0700), the session directory is a stable hash, the leaf name is unpredictable, and the write is exclusive and owner-only. The storage mechanics live in a Cordis-free module so they are unit-testable without a context.

Source map

File Role
src/index.ts Plugin entry: Config, the LocalSpillStore service, cleanup lifecycle, locator and retrieval-hint assembly
src/cleanup.ts One-shot age sweep, filesystem-identity checks, symlink and ownership safeguards
src/store.ts Cordis-free storage mechanics: private root, session directory, safe-name encoding, exclusive write
src/invariant.ts Invariant companion (no runtime invariant; contracts are enforced at the seam)

File naming and write

suggestedName is untrusted input, so encodeSegment escapes every character outside [A-Za-z0-9._-] (and ~ itself) into a ~XXXX form, making the mapping injective over all JS strings: separators, ../, NUL, and absolute paths can never escape one segment, and the whole-segment tokens ./.. are escaped too. The write is open(path, 'wx', 0o600) — it fails on any existing path, symlink or not, so a pre-planted target cannot redirect it. Two saves of the same suggested name get distinct random prefixes.


Further Exploration

Read these pages when the package-level contract is not enough.


Model Experience

Indirectly, through spill consumers, which render the saved file path and read/grep retrieval guidance to the model.

KV Cache effect

No direct invalidation; the named consumer owns any request-prefix changes.

Known Limitations and Deferred Work

These limits define when the local backend is a poor fit or needs operational care. They are current package constraints.

  • A long-lived deployment is not swept until restart — the one-shot sweep runs only after activation, so files that cross the age cutoff during a run are reclaimed on the next start.
  • Locators require a co-located filesystem consumer — a remote or virtual deployment needs another SpillStore backend whose locator and retrieval hint are meaningful there.

Dev Note

Working context for maintainers — click to expand

This Dev Note is working context for maintainers: open directions. It is explicitly non-authoritative.

Future: workspace-confinement interplay

The retrieval model assumes the model's read/grep tools can inspect the returned path even when the spill directory is outside the session working directory. A future workspace-confinement policy must either allow local spill paths explicitly or use a non-file spill backend.