DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

sweet-boby /

sweet-boby/dsh-session-isolation

Verified

DSH plugin for per-session working directories: keep parallel agent sessions from overwriting each other.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@33da6136

dsh-session-isolation

License: MIT tests: 25 passing DSH plugin

Per-session working directories for DSH — the Codex-style behavior, as an opt-in per project, switched from a page in the Web GUI, from a conversation, or from a marker file.

English | 中文


The problem

DSH gives every session the Workspace directory you picked, so two sessions in one project write into the same files. Run three agents on one folder and they overwrite each other's output: the second one "fixes" the file the first one just wrote.

What it does

While isolation is on for a directory, every new session created there works in its own session-<id> subfolder. That subfolder — not the project — becomes the session's workspace-write sandbox root, so parallel sessions cannot touch each other's files, while the project itself stays readable to all of them.

project/                          ← the Workspace you picked (readable to every session)
├── .dsh-isolate                  ← the switch (an empty file; delete it to turn isolation off)
├── src/ …                        ← the project's own files (read-only to sessions)
├── session-1a2b3c4d/             ← session A's sandbox root, private to it
└── session-9f8e7d6c/             ← session B's sandbox root, private to it
  • Each folder is created before the session exists, and its name carries the first eight characters of the session id (-2, -3, … if two ever collide).
  • The session still belongs to the project in the Workspace sidebar, and AGENTS.md discovery and project-skill lookup still work — both walk up from the session's cwd.
  • Sessions that already exist never move: isolation changes where the next session works, not where a running one does.
  • Turning the switch off stops new private folders; sessions created while it was on keep their folders and stay accounted to the project.

Nothing in DSH is modified. The plugin mounts through the ordinary Cordis composition, wraps two seams at load time, and removes both when it unloads.

Install

No build step and no runtime dependencies beyond DSH itself. The repository contains the Cordis entry, six lib/ modules, the browser half, the bundle patch, and the test suite. Verified against DSH 0.1.6-alpha.2; Node.js 22+ is required.

From GitHub

dsh plugin --profile <profile> add \
  https://github.com/sweet-boby/dsh-session-isolation.git

Then restart the host once. The plugin manager installs the package, selects its bundle, and applies cordis.patch.yml; the restart lets the browser module table discover the new dsh.client bundle.

From the Web GUI

Open Plugins → Add plugin and paste the GitHub URL:

https://github.com/sweet-boby/dsh-session-isolation

If that DSH build's installer only accepts a local bundle path, clone the repository first and paste the checkout's absolute path instead.

From a local checkout

git clone https://github.com/sweet-boby/dsh-session-isolation.git
dsh plugin --profile <profile> add \
  /absolute/path/to/dsh-session-isolation

Manual profile install

Add the dependency and select the bundle in ~/.dsh/profiles/<profile>/package.json:

{
  "dependencies": {
    "dsh-session-isolation": "github:sweet-boby/dsh-session-isolation"
  },
  "dsh": {
    "profile": {
      "bundles": ["…", "dsh-session-isolation"]
    }
  }
}

For local development, replace the dependency with file:/absolute/path/to/dsh-session-isolation, then install in the profile:

cd ~/.dsh/profiles/<profile>
pnpm install --ignore-scripts

Restart the host after either installation path.

Requirements

  • Node.js 22 or newer.
  • DSH with the sessionController, workspaceRegistry, and tools services composed — every base-backed profile has them. A deployment missing one leaves the plugin pending instead of half-applied.
  • The Web carrier (ctx.webServer) is optional: without it the plugin keeps the policy and the tool and simply serves no GUI page.

Turn isolation on

Isolation is a property of the directory, and its durable form is a .dsh-isolate marker file inside it, so the setting travels with the folder and a file manager can change it too.

  1. Web GUI — Settings → Built-in plugins → Workspace isolation (设置 → 内置插件 → 工作区隔离). One row per Workspace with its path, session count, and a single switch. The list also refreshes every five seconds, so a decision made anywhere else shows up here.
  2. In a conversation — ask the agent to isolate the project. It calls the session_isolation tool: status, list, enable, disable, with an optional absolute path that defaults to the calling session's workspace.
  3. From host code — await ctx.isolatedSessions.setIsolated(workspaceId, true) or ctx.isolatedSessions.setIsolatedPath(path, true); list() and isIsolated(id) read the current state.
  4. By hand — create an empty .dsh-isolate file in the project directory.

Verify it worked

Start a new session in that Workspace and ask it for its working directory (pwd, or just have it write a file). The answer is a session-<id> subfolder, and the project root appears in its file policy as a read-only location. Two such sessions get two different folders.

The GUI page

The browser half is a hand-written client bundle (lib/client.js) declared with dsh.client and exports["./client"], so the Web client module table serves it like any packaged plugin bundle. It registers one settings.plugins.tab entry through ctx.slots.inject, i.e. it waits for the Settings section to declare that slot before registering — plugin activation order is not stable, and registering into an undeclared slot fails the whole browser boot.

An external plugin's browser half cannot declare a Remote namespace, so the two halves talk over two named routes on the Web carrier:

Route Method Body Answer
/session-isolation/workspaces GET — { workspaces: [{ workspaceId, title, path, isolates, sessionCount }], isolatedCount }
/session-isolation/toggle POST { workspaceId, enabled } the refreshed list, or { error }

They hold no state of their own — every answer is projected from the marker files — and they are unauthenticated routes on the loopback-bound carrier, like the other plugin asset routes. A host without a Web carrier registers neither.

One restart is needed for the page to appear. The client module table scans dsh.client declarations at activation and caches its verdict per package, so a bundle that appeared after the host started is picked up on the next boot. Host-side edits reach a running host through configuration reload; a new client bundle does not. Until then, every other switch (tool, marker file, host service) already works.

How it works

Two seams, wrapped at load time and unwrapped on unload. Both delegate to the original for anything they do not own, and both fail loud — … this DSH build moved it — rather than silently isolating nothing.

  1. ctx.sessionController.create — the Remote entry point the GUI calls. DSH resolves a new session's cwd as workspace.path ?? request.cwd ?? default, and the controller rejects a request carrying both workspaceId and cwd, so the plugin points the named Workspace entity at a freshly allocated private directory for the duration of that one call. The durable record is untouched, the redirect is removed in a finally, and a failed creation deletes the folder it allocated. A mutation slot absorbs the entity's own writes during the redirect, so a session accounted in that call is never rolled back when the redirect is removed.
  2. The Workspace registry's session-path index — the host object every WorkspaceEntity reads (the registry builds one and shares it). A session directory inside an isolating Workspace resolves to that Workspace's root, so the stock attach validation, the durable membership prune, the one-time history bootstrap, and the "one owning Workspace per session" invariant all keep working with no source change.

The isolation state itself is never persisted by the plugin: it is recomputed from the marker files, so a restart, an external edit, or another tool all converge on the same answer. Scans are serialized, and a scan that lands while a per-call redirect is in flight merges instead of retracting a decision it cannot currently see.

Limits, and honest caveats

  • The project root is not writable by the session. That is the isolation. If a session must edit the project's own files, keep isolation off for that project, or move the shared material into the session folder.
  • Not a git-worktree substitute. The session folder lives inside the repository, so git works against the parent repository but a new branch is still shared. Branch-per-agent isolation needs a worktree, which this plugin does not create. For a normal coding repository, leave isolation off: the agent could not edit the repository's own files.
  • Reads are unchanged. Isolation constrains writes, exactly like DSH's workspace-write mode; a session can still read the project. A user can also approve danger-full-access for a turn, which bypasses the boundary — that is DSH's approval policy, not this plugin's.
  • Removing the plugin leaves isolated sessions ungrouped. The registry's membership rule is canonical-cwd equality, so without the plugin the private folders no longer match their project. Re-enable the plugin to restore the grouping; session logs and folders are never touched either way.
  • Nesting follows the nearest marked ancestor. Registering a Workspace over an isolating project's own path wins for that path; a session directory inside it belongs to the nearest marked ancestor.
  • Local file: installs are copies. pnpm copies a file: dependency into the profile rather than linking it, so after editing the plugin re-run pnpm install (or delete node_modules/dsh-session-isolation and reinstall) before the next host start.

Troubleshooting

Symptom Cause Fix
The settings page has no Workspace isolation tab The host started before the client bundle existed Restart the host once (see the restart note)
/session-isolation/workspaces answers 404 The plugin is not loaded, or the host has no Web carrier Check the plugin is a selected bundle and installed; check the host boot for a failed session-isolation fiber
New sessions still share the project directory The marker is not in the Workspace's own directory, or the session predates it Confirm .dsh-isolate sits in the Workspace path shown in the page; only sessions created after it are isolated
Sessions from an isolating project appear ungrouped The plugin is not active for that host Re-enable the plugin; the folders and logs are intact
The agent cannot write a file in the project root Working as designed Turn isolation off for that project, or have the session write inside its folder

Uninstall

  1. Turn isolation off for every project (each one removes its own .dsh-isolate), or leave the markers — they are inert without the plugin.
  2. Remove dsh-session-isolation from the profile's dsh.profile.bundles and dependencies, then run pnpm install --ignore-scripts in the profile.
  3. Restart the host.

Session folders and logs are never touched by an uninstall.

Development

node --test "tests/*.test.js"
Test file Covers
tests/isolation.test.js the marker policy, directory allocation, both seam wrappers, the state cache, the service API, and the HTTP routes against fakes
tests/integration.test.js the real dsh-storage → dsh-storage-domain → dsh-workspace stack plus a real dsh-tools runtime: session creation, concurrent sessions, membership, a restart over the same medium, and toggling
tests/tool.test.js the session_isolation tool's exported JSON Schema
tests/client.test.js the browser half's module-table handoff and its slot declaration wait

tests/integration.test.js imports the built lib/ trees from a DSH checkout by absolute path (the CHECKOUT constant at the top); adjust it when the checkout moves.

Layout

index.js              Cordis plugin: seam patches, service, tool, optional Web routes
lib/isolate.js        the marker policy and session-directory allocation
lib/state.js          the isolation cache: marker scans, redirect guard, serialization
lib/patch.js          the two seam wrappers + the ctx.isolatedSessions service
lib/tool.js           the session_isolation model tool
lib/web.js            the two HTTP routes behind the GUI page
lib/client.js         the browser half (Settings → Built-in plugins page)
cordis.patch.yml      the bundle patch that inserts the plugin row

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 33da6136e7d0

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout