swaylq / dsh-genie

Listed

Wishes that outlive the session — turn a DeepSeek Harness agent's runtime plugins into permanently installed ones. No pnpm, no network, no build authorization.

mainToolSession View source

Installation

npx -y @deepseek-ai/dsh plugin --profile web add github:swaylq/dsh-genie

This installation command is an unverified starting point generated from the GitHub repository address.

README

Maintainer-authored documentation snapshot.

View on GitHub ↗
Commit d917d28Synced Aug 18, 2026

🪔 dsh-genie

Wishes that outlive the session.

DeepSeek Harness can already write its own plugins.
It just can't keep them. This is the other half.

English | 中文


The gap

DeepSeek Harness ships a self-referential toolset — cordis_inspect, cordis_define, cordis_run — that lets the agent write a plugin and hot-load it into the live process. You describe a feature, the agent builds it, and it works right now.

Then you restart, and it's gone. From the official README of that toolset:

Dynamic packages live only in the shared DSH process memory. […] They create no Plugin file, install no package, change no cordis.yml or personal/project configuration, do not survive restart, and cannot be promoted automatically. To keep an experiment, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow.

dsh-genie is that "regular development workflow", collapsed into one tool call.

The loop

you  ▸ every time I say "ship it", stage everything and commit with a generated message

     ▸ agent writes the plugin, cordis_run mounts it            ← ships with DSH
     ▸ you try it. it works.
     ▸ "keep it"
     ▸ genie_keep dyn-1 → dsh-wish-ship-it                      ← this package

     ▸ restart
     ▸ still there.

Two extra facts that make this pleasant:

  • No pnpm, no network, no build authorization. A granted wish is written straight into $DSH_HOME/genie/ and linked where the launcher already resolves modules. Compare that to the normal path for a git-installed plugin, which needs a prepare script from the author and an allowBuilds grant from you — arbitrary code execution at install time, outside every sandbox the agent runs in.
  • It's just files. Every wish is a readable directory with a package.json, a cordis.patch.yml, and an index.js. Read it, edit it, git init it, publish it. dsh-genie never rewrites a wish after granting it.

Install

dsh plugin --profile web add github:swaylq/dsh-genie

Then restart dsh. That's it — plain JavaScript, no build step, so there is no prepare script and nothing to authorize.

Already have your own @deepseek-ai/dsh-tool-cordis row? See Composition.

What you get

genie_keepTake a dynamic package the agent just proved out and make it permanent. Only appears when the dynamic-package runner is composed.
genie_wishSame, for code the agent wrote directly — no prototype step.
genie_listEvery wish on this machine, and whether it's live in this profile.
genie_revokeUnlist and unlink a wish. Keeps the source unless you ask otherwise.
/wishThe human view: what's granted, what's active, where the code lives.

The bundle also mounts @deepseek-ai/dsh-tool-cordis, DeepSeek Harness's own self-modification toolset. It ships inside every install but the stock web profile doesn't mount it — its runner already is, so switching it on costs no install.

How it works

Three facts about the launcher, none of them private API:

  1. A profile resolves bundles through two anchors — the dsh installation, then the profile directory. Node's ordinary parent walk from <profile>/package.json reaches $DSH_HOME/profiles/node_modules, the flat fallback directory the launcher maintains so in-box plugins resolve from any profile.
  2. That directory is append-only in practice. healProfilesModuleFallback keeps correct links, re-points moved ones, and never prunes what it doesn't own — so a link planted there survives every later boot.
  3. dsh plugin won't reclaim the layer. Its reconcile pass only removes a bundle whose name is (or was) a profile dependencies key. dsh-genie writes to dsh.profile.bundles and never to dependencies, so dsh plugin add anything-else leaves your wishes alone.

So granting a wish is: write the package → symlink it into the fallback → append one name to the profile's ordered bundle list. No installer, no registry, no build.

One wrinkle worth knowing, because it bites everyone once: Node resolves a package's imports from its real directory, not from the symlink used to find it. A wish in $DSH_HOME/genie/ would walk genie$DSH_HOME~ and never see the fallback, so dsh-genie plants $DSH_HOME/genie/node_modules$DSH_HOME/profiles/node_modules beside the wishes. That puts every in-box package back on the walk. For the same reason dsh-genie itself declares no runtime dependency at all — it loads @deepseek-ai/dsh-tools through the profile, the way the launcher resolves its own bundles, so it works from a link: checkout, a git install, an npm install, or a directory you dropped in by hand.

Trust stance

Read this before you install it, not after.

A granted wish is ordinary code that runs at every boot, with the harness's full context. That is the point of the package, and it is also the whole risk. dsh-genie does not add a capability your agent lacked — an agent with bash access could already write these files — but it does make it one tool call instead of a visible sequence of them, which is exactly the kind of convenience worth being explicit about.

What the design does about it:

  • Nothing runs in the session that wrote it. A grant writes files and edits one manifest. The code loads on the next restart, which only you can perform. That restart is the checkpoint, and every tool result says so.
  • Nothing is executed to be validated. Generated modules are parse-checked with node --check, which parses without evaluating. A wish that doesn't parse installs nothing.
  • Everything is listable and reversible. /wish and genie_list show every wish and its source path; genie_revoke removes it. Revoke only accepts dsh-wish-* names, so a mistyped argument cannot unlist dsh-base.
  • Wish names are one validated path segment. No scopes, no dots, no traversal.

What it does not do: sandbox the wish, review the code for you, or ask a second time. If you would not run a script an agent wrote without reading it, read the wish before you restart — the tool result hands you the exact path for that reason.

Upstream's stance on the toolset this builds on is the same one, and worth repeating: the vm sandbox behind cordis_run "isolates globals but is not a security boundary […] treat this toolset like bash access."

Composition

The bundle inserts two rows: genie-tool-cordis and genie. If something else in your profile already mounts @deepseek-ai/dsh-tool-cordis, both registrations claim the same tool names and the load fails loudly (as DSH intends). Disable ours from your profile's cordis.patch.yml:

- id: genie-tool-cordis
  disabled: true

Config on the genie row:

keydefaultmeaning
allowUpdatetruewhether mode: "update" may overwrite an existing wish

Caveats

  • A kept dynamic package changes realms. cordis_define bodies run in a vm sandbox where Node globals are absent or redirected to Cordis services. A kept wish runs in ordinary Node, so a body written against those facades may need adjusting. genie_keep tells the model to say so when the code touches them; it cannot rewrite the code for you.
  • Layer order is append. A wish patches on top of dsh-base, the mode bundle, and every plugin installed before it. A patch replaces a row's whole config rather than deep-merging, so a wish that overrides an existing row must restate every key that row needs.
  • DSH is a developer preview. Upstream says breaking changes are coming. This package leans on documented launcher behavior rather than internals, but "documented" and "stable" are not the same thing yet.
  • The client half is stored, not mounted. genie_keep saves a client.js beside the host half for reference; wiring a browser half into the web UI is still manual work.

Development

npm test          # 19 tests, real throwaway Harness homes, no network

lamp.js is the whole install mechanism and imports nothing from DSH — it is plain node:fs and testable on its own. index.js is the plugin surface.

License

MIT

Built on DeepSeek Harness (MIT) and Cordis. Not affiliated with DeepSeek.

Project files and signals

Shown items are public repository signals detected in the directory snapshot.

TestsDetected

Repository information

Language
JavaScript
License
MIT
Last updated
Aug 15, 2026, 6:12 AM

Install deliberately

Review source code, permissions, lifecycle hooks, dependencies and network access. Test untrusted plugins in an isolated environment.