Replies: 5 comments
Update: community precedent found — re-scoping this RFCSince posting, two community plugins were shared in #1320 (by @Momojie-S):
The plugin's ADR-0002 makes the architectural call this RFC was circling around: agent-scoped registration already delivers per-workspace isolation without a new scope layer — tools die with the agent, so "switch workspace = switch agent = switch toolset", and nothing leaks across projects. The documented trade-off is that sessions in the same workspace do not share one MCP process, and agent rebuilds reconnect. So the RFC should be re-scoped: per-workspace isolation is largely solved at the agent layer. What is still missing is the workspace-level shared runtime:
Revised questions for maintainers:
The trust question from the original post can stay out of scope for a first version. |
|
This RFC identifies a significant architectural gap in the current model: workspace identity is currently implicit through session context, but runtime-owned resources such as MCP servers, LSP processes, terminals, and environment overlays require an explicit lifecycle boundary. The proposed workspace runtime abstraction appears to be a strong direction because it provides a single ownership model for configuration, process isolation, resource sharing, and cleanup. This avoids duplicating workspace-awareness logic across individual plugins. One area worth defining further is workspace identity and lifecycle management. In real-world environments, a workspace may represent a directory, Git worktree, monorepo package, or another logical project boundary. Establishing a canonical identity model early would help prevent resource collisions and unexpected sharing. It may also be worth considering lazy runtime initialization and reference-counted disposal so workspace services are only created when required and safely released when no sessions remain attached. Overall, this seems like a foundational improvement that could provide a consistent platform for future workspace-aware capabilities beyond MCP. |
Another process-level config source: the new
|
|
Implementation update: I built a pure out-of-tree prototype for this RFC and published it here: https://github.com/TTTPOB/dsh-workspace-overlay It targets DSH The documented v1 limits remain explicit: synchronous |
|
Follow-up from implementing the out-of-tree prototype: the low-level scope graph is flexible enough, but the current high-level preset mount API owns a fixed placement and the only re-link authority. That is the seam the prototype had to reconstruct. Evidence from current
|
Uh oh!
There was an error while loading. Please reload this page.
Summary
DeepSeek Harness currently has a process/profile-wide Cordis composition and per-agent scoped registrations, but it does not have a workspace-scoped configuration and runtime plane.
This becomes visible with MCP:
@deepseek-ai/dsh-mcp-clientrow added through a profile patch creates one long-lived MCP client/server instance;cwdandenvare fixed when the plugin instance is loaded;ctx.toolslayer;session.header.cwd.As a result, a project cannot safely declare an MCP server that is automatically available only to sessions in that project.
The same missing scope also affects other project-bound services such as environment overlays, language servers, code indexes, persistent terminals, and local development services.
A concrete motivating case is a potential
dsh-envrcplugin. Safely applying.envrcin DSH Web requires an environment owned by a workspace rather than mutation of the Host'sprocess.env. The resulting environment should be reusable by Bash, persistent terminals, workspace MCP servers, LSP processes, and other subprocess consumers without leaking into unrelated sessions.Expected behavior
A project-local configuration should be able to express something like:
{ "mcp": { "project_database": { "transport": "stdio", "command": "project-database-mcp", "cwd": "." } } }with the following semantics:
Project-local configuration should merge over Host-global defaults, and a project should be able to override or disable an inherited global MCP entry.
Current behavior
DSH profiles are composed at Host startup from:
cordis.patch.yml;$DSH_HOME/cordis.patch.yml;--patchoverlays.These layers configure the Host Cordis tree, not a tree selected from each session's workspace.
@deepseek-ai/dsh-mcp-clientreflects that model:cwdandenvare static plugin configuration;serverNamereservations are currently unique across the application root.A root/profile MCP row therefore represents a Host-level MCP server.
DSH does have scoped tool registrations and Agent Presets. Those are useful foundations, but they do not provide workspace-local configuration:
session.header.cwd;cwdandenvremain static configuration rather than values derived from the owning workspace;AGENTS.md,AGENTS.local.md,CLAUDE.md, andCLAUDE.local.md) affects model instructions only, not Cordis services or MCP configuration.In other words, DSH has an authoritative session cwd and scoped registries, but it lacks the layer that turns a workspace identity into scoped service instances and configuration.
Why this is more than an MCP convenience issue
1. Tool visibility
If project A adds an MCP row through the profile, sessions in project B receive the same root tool registrations.
Tool naming or prompting cannot provide strong isolation. The project-B agent should not receive project-A tool schemas in the first place.
2. Process working directory
A stdio MCP server receives the plugin's configured
cwd, not the cwd of the session making the tool call.A project-sensitive server may therefore read or modify the wrong repository when used from another session.
3. Environment and secrets
A project MCP may need a virtual environment, Nix/direnv environment, database URL, or project-specific credential.
Injecting that environment into a root MCP instance makes it shared by every session that can call the instance. Mutating the Host's
process.envwould be worse: DSH Web can host concurrent sessions from unrelated workspaces.4. Stateful servers
Many MCP servers keep indexes, caches, database handles, or in-memory state. One process cannot simultaneously have two different project roots and OS environments unless the server protocol explicitly models them.
5. Other workspace services
The same scope mismatch appears when implementing:
Adding independent ad hoc fixes for each consumer would duplicate workspace identity, lifetime, reload, and disposal logic.
6. A concrete blocked consumer: safe
.envrcintegrationThis issue became clear while investigating a possible
dsh-envrcplugin.A simple implementation could run:
and assign the result to the Host's
process.env. Some OpenCode plugins take this approach, but it is unsafe for DSH Web:The last session to mutate
process.envwould affect unrelated sessions and any subsequently spawned Host service. Removed variables would also require explicit unset semantics, and project secrets would be exposed to more subprocesses than intended.Injecting the environment only into one-shot Bash is safer, but incomplete. Persistent Bash/PTY, workspace MCP servers, LSP processes, and other subprocess consumers use separate creation paths. Each consumer could grow an independent direnv adapter, but that would duplicate:
.envrctrust state;A workspace runtime would provide a natural owner for this behavior:
The environment would be copied into each workspace-owned child process at spawn time. The DSH Host itself would remain environment-neutral, and global services would not accidentally inherit project variables.
This does not imply automatically trusting
.envrc. An.envrcis executable project code. A safe implementation should never rundirenv allowautomatically, should bind trust to a canonical file/path and content revision, should avoid logging exported values, and should make fail-open versus fail-closed behavior explicit.The important architectural requirement is therefore not built-in direnv support. It is a reusable workspace-owned environment capability that a
dsh-envrcplugin can provide and Bash, PTY, MCP, LSP, and other workspace process providers can consume.Prior art: OpenCode server mode
OpenCode solves this even when one app server serves multiple GUI workspaces.
The GUI creates a directory-bound SDK client and sends the directory with each request. The server resolves the request to an
InstanceContextand caches instance state by normalized directory:Global and project
opencode.json/opencode.jsoncfiles are merged per instance. MCP clients, LSP state, plugins, and tool assembly read the current instance state.This does not require one server process per GUI project. One server multiplexes multiple directory instances while keeping their service state separate.
The important idea is not OpenCode's exact config format. It is the existence of a directory/workspace runtime between the global server and individual sessions.
Suggested design direction
This section is intentionally brief; the primary request is for a supported workspace scope, not a specific implementation.
1. Introduce a canonical workspace runtime
A Host service could resolve and retain a runtime by canonical workspace identity:
The runtime could be keyed by a canonical project/worktree root rather than by raw path spelling.
Sessions in the same workspace could share it. The final lease release would dispose workspace-owned plugin fibers and processes.
2. Allow workspace runtimes to contribute environment overlays
A workspace runtime should be able to expose an environment snapshot independently of Host
process.env:The exact API is only illustrative. The important properties are:
A
dsh-envrcplugin could implement this capability by evaluating an already trusted.envrcthroughdirenv export json, caching the result per workspace, and publishing a new snapshot when direnv's watched inputs change.Process providers could consume the same snapshot at their actual spawn boundaries:
This keeps environment policy separate from each consumer while preserving consumer-specific lifecycle behavior. Long-lived MCP, LSP, and terminal processes would normally need restart or explicit refresh after an environment revision changes; changing a cached map cannot modify an already running process's OS environment.
3. Add workspace scope to the existing scope chain
A useful lookup order could be:
Registrations made through the workspace context would be visible only to agents attached to that workspace.
This would let the existing layered
ctx.toolsdesign provide correct visibility without checking cwd inside every tool execution.The exact order of workspace and preset scopes should be decided explicitly, especially for shadowing and restrictions.
4. Start with a declarative project configuration
Automatically loading arbitrary Cordis modules from a repository is equivalent to executing project code and needs a trust decision.
A safer first version could support a restricted declarative file, for example:
with a narrow initial schema such as:
{ "mcp": { "project_database": { "transport": "stdio", "command": "project-database-mcp", "args": [], "cwd": ".", "enabled": true } } }Possible precedence:
A workspace should be able to disable an inherited global entry with
enabled: false.Full workspace Cordis composition could be considered separately, with explicit trust and approval semantics.
5. Make MCP instances workspace-owned
For a local MCP entry:
cwdagainst the canonical workspace root;process.env;serverNamein unrelated workspace scopes;A global MCP entry should remain possible and retain its existing Host-wide semantics.
6. Bind sessions and subagents explicitly
A session should record or deterministically derive the workspace runtime it joined.
Resume should reattach to the same workspace identity. Subagents should inherit the parent's workspace by default, while an explicit cross-workspace delegation would require a separate operation and permission decision.
7. Treat workspace config as a trust boundary
Project configuration can start executable programs and supply environment references. It should not become trusted merely because a user opened a directory.
At minimum, the design should define:
Acceptance examples
Project-only MCP
Expected:
Same MCP name in two projects
Expected:
project;Global default overridden by a project
Expected:
Concurrent Web sessions
Run sessions in A and B concurrently and call their MCP tools.
Expected:
Workspace environment isolation
Expected:
process.envis not mutated;.envrcfiles are not automatically allowed.Questions for maintainers
@deepseek-ai/dsh-mcp-clientbe adapted to a workspace context, or should a manager/provider own workspace MCP instances above it?dsh-envrccan supply one snapshot to Bash, PTY, MCP, LSP, and other workspace process providers?All reactions