Attachments
In one sentence:
ctx.attachmentsis a persistent binary attachment seam: it validates and atomically commits immutable image bytes, returning a serializableImageAttachmentRef.attachment-localis its local filesystem implementation, andui-attachmentis the pure React attachment atom.
Unsent browser drafts do not live here; bytes only enter persistent storage when the user submits a prompt or a provider adapter submits structured model output.
1. What it is
The attachment capability solves one thing: persist image bytes so that session logs only keep a content-addressed reference + validated metadata, not browser paths, object URLs, provider URLs, or base64. Both core packages are product packages.
| Package | ctx key | Role |
|---|---|---|
attachment | ctx.attachments | Immutable attachment references, image limits, storage service |
attachment-local | (registered on ctx.attachments) | Content-addressed private storage under DSH_HOME |
ui-attachment | none (zero cordis) | Pure React attachment atom: draft image rail, history gallery, large-image lightbox |
2. Reference and write API (attachment)
ctx.attachments validates and atomically commits immutable image bytes, returning a serializable ImageAttachmentRef; consumers never persist browser paths, object URLs, provider URLs, or base64 into session events.
validateImage: runs the same admission policy but does not persistsaveImages(rc.7): ordered batch commit — validates every member before writing any; admission failures start no writes, storage failures return no partial references (already-published content-addressed objects may remain until a future retention policy collects them)saveImage: commits every accepted image before each model-visible session event is publishedreadImage: validates the content-addressed object against recorded metadata; the caller can cancel, the implementation observes the cancellation and preserves it as a cancellation (not translated into a storage failure)
Unsent composer images remain temporary browser-owned drafts.
3. Local storage (attachment-local)
attachment-local is the private local implementation of attachment, mounted by default (base composition).
- Objects live at
<DSH_HOME>/attachments/v1/objects/<sha256-prefix>/<sha256>, addressed by an opaquesha256:id - Each process syncs home-directory ancestors step by step to the filesystem root, proving the home directory is durable only once, avoiding mistaking "a directory another process created but has not yet synced" for a safe boundary
- Writes use a private staging directory, owner-only files, synced temporary files, atomic exclusive hard-link publication, and directory syncing on the publish path (POSIX; Windows relies on filesystem metadata journaling), ensuring the reported reference survives crashes
- Both write admission and reads fully decode the raster before accepting format and dimensions
DSH_HOMEfollows the shared path policy: explicit config →$DSH_HOME→~/.dsh. Session logs contain only references and validated metadata, never this host pathreadImageforwards an optional cancellation into the filesystem read and preserves the cancellation rather than wrapping it asATTACHMENT_READ_FAILED
4. Browser UI atom (ui-attachment)
A pure React attachment atom, zero cordis: all strings resolve through the owning plugin's own locale namespace and read no application state; ui-conversation is its current consumer.
AttachmentRail: draft image rail, a single row of 64px thumbnails that scrolls horizontally, hidden scrollbar, overflow paged with round-edge arrows (prefers-reduced-motion: reducegives instant scrolling)MessageImage/ImageGallery: a single history image (longest side capped at 240px), with an explicit retry on load failure; clicking opensImageLightboxImageLightbox: document-level modal preview, closed by Escape / background click / close control, focus returned to the opener on unmount
Model visibility: none — it renders a pure React atom in the browser and never enters any model request.
5. Configuration
attachment-local has no explicit config options; the storage root is resolved from DSH_HOME. The mount line, as in the default composition:
- id: attachment-local
name: '@deepseek-ai/dsh-attachment-local'
6. Known limitations
- The first version only accepts PNG, JPEG, WebP, and GIF
- Retention and garbage collection are deferred (recovered/derived sessions may share immutable objects)
- Generic files, audio, video, and persisting unsent drafts need their own lifecycle and provider contracts
7. Verification
# Check whether attachment-local is mounted
dsh web --dump-config | grep -iE "attachment"
# Look at the attachment object directory (owner-only, bucketed by sha256 prefix)
ls -la ~/.dsh/attachments/v1/objects/
Next steps
- Storage: another kind of non-session persistent data (KV backends); attachments do not go through here
- Session system: session logs only store
sha256:references and validated metadata - Built-in tools: images enter provider requests as
ImageBlock