swaylq
dsh-genie
Wishes that outlive the session — turn a DeepSeek Harness agent's runtime plugins into permanently installed ones. No pnpm, no network, no build authorization.
- Stars
- 1
- Language
- JavaScript
- Created
- Aug 15, 2026
- Updated
- Aug 15, 2026
Introduction
🪔 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.ymlor 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 apreparescript from the author and anallowBuildsgrant 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, acordis.patch.yml, and anindex.js. Read it, edit it,git initit, 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_keep | Take a dynamic package the agent just proved out and make it permanent. Only appears when the dynamic-package runner is composed. |
genie_wish | Same, for code the agent wrote directly — no prototype step. |
genie_list | Every wish on this machine, and whether it's live in this profile. |
genie_revoke | Unlist and unlink a wish. Keeps the source unless you ask otherwise. |
/wish | The 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:
- A profile resolves bundles through two anchors — the dsh installation, then the profile
directory. Node's ordinary parent walk from
<profile>/package.jsonreaches$DSH_HOME/profiles/node_modules, the flat fallback directory the launcher maintains so in-box plugins resolve from any profile. - That directory is append-only in practice.
healProfilesModuleFallbackkeeps correct links, re-points moved ones, and never prunes what it doesn't own — so a link planted there survives every later boot. dsh pluginwon't reclaim the layer. Its reconcile pass only removes a bundle whose name is (or was) a profiledependencieskey. dsh-genie writes todsh.profile.bundlesand never todependencies, sodsh plugin add anything-elseleaves 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.
/wishandgenie_listshow every wish and its source path;genie_revokeremoves it. Revoke only acceptsdsh-wish-*names, so a mistyped argument cannot unlistdsh-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:
| key | default | meaning |
|---|---|---|
allowUpdate | true | whether mode: "update" may overwrite an existing wish |
Caveats
- A kept dynamic package changes realms.
cordis_definebodies 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_keeptells 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 wholeconfigrather 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_keepsaves aclient.jsbeside 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
Built on DeepSeek Harness (MIT) and Cordis. Not affiliated with DeepSeek.