mirage

3.5kStars259Forks107Issues9Watchers

Replace DeepSeek Harness files and shell backend with the Mirage workspace, enabling dsh file tools and bash to directly operate on mounted resources like S3, Slack, and Redis.

Language
TypeScript
License
Apache-2.0
Branch
main
agent-sandboxagent-toolsai-agentsbashclaude-codedshdsh-pluginfuse

Install

$ dsh plugin --profile web add github:strukto-ai/mirage

Run the command above in your terminal to install this plugin via the dsh CLI. You can switch Profile in the top-right corner. New to dsh? Read the beginner tutorial

One-Line Pitch

Replace DeepSeek Harness's file and shell backends with a Mirage workspace, enabling dsh's file tools and bash tools to directly read/write data sources like S3, Slack, Redis, and Gmail that are connected via mounts, instead of the host machine's local disk.

Core Features

  • Take over dsh's file system interface (ctx.fs): read, write, edit, stat, listDir, etc. all go through the Mirage workspace, with all operations passing through the mount point mode and policy layer first
  • Take over dsh's shell interface (ctx.shell): bash tool is changed to execute Mirage's shell, with POSIX tools (grep, ls, cat, cp, etc.) uniformly available across multiple mount sources
  • Provide declarative mounting: list resources in profile's cordis.patch.yml (resource registration name, mode, config), with secrets like tokens parsed at mount time through !!js process.env.X
  • Support sandboxed Python (monty) and about 50 built-in backends (RAM, Disk, S3, Redis, Slack, Gmail, GDrive, Notion, Postgres, SSH, etc.), scripts can run under any mount path
  • Default mount a /tmp (RAM disk, exec mode), usable for temporary files and Python script outputs
  • Support persistent shell: passing sessionId to the shell plugin preserves cd, export, and function definitions across multiple invocations
  • When all runtimes in the workspace are confined to the vfs, automatically report workspace-write sandbox mode to dsh, allowing dsh's permission presets to combine with it

Technical Implementation

  • Language: TypeScript (DSH integration package); the underlying Mirage has a separate Python implementation, this plugin only interacts with the TypeScript mirror
  • Key Dependencies: @struktoai/mirage-node (provides Workspace, resource registry, shell executor), @deepseek-ai/cordis (plugin orchestration), @deepseek-ai/dsh-fs and @deepseek-ai/dsh-shell (DSH's file and shell capability gaps, 0.1.0-rc.6)
  • Architecture Pattern: Loaded via dsh's bundle mechanism (dsh.bundle.patch in package.json points to cordis.patch.yml); the patch disables dsh's built-in five plugins (fs-sandbox, bash-sandbox, pwsh-sandbox, tool-pwsh, tool-fs-search) and inserts mirage's trio (MirageService holds the workspace, MirageFileSystem and MirageShellExecutor each declare inject = ['mirage'] to get it), ultimately letting ctx.fs and ctx.shell share the same workspace
  • Entry Files: typescript/packages/dsh/cordis.patch.yml (DSH load entry), typescript/packages/dsh/src/plugin/{service,fs,shell}.ts (each plugin's default export)

Use Cases

You want the Agent in dsh to operate not on the host machine's original file system, but on existing objects in external data sources like S3, Slack, Notion, Redis; or you want to give an Agent session a clean "virtual workspace" isolated from the host machine, where the Agent can read/write, run Python scripts, and produce files synchronously. Also suitable for directly embedding existing Python+TypeScript Mirage workspaces into the DSH system.

Prerequisites and Compatibility

DependencyMinimum VersionNotes
Node.js>=20.10.0Declared by package's engines field
DSH0.1.0-rc.6peerDependencies pins this version, no wider range declared
PlatformmacOS / Linux / WindowsPackage itself declares no os/cpu restrictions; underlying may need platform support if using FUSE real mounts
Native ModulesNoneDSH integration package itself contains no native dependencies, runtime pulls mirage-node's dependencies on demand

Installation

dsh plugin --profile web add github:strukto-ai/mirage

Configuration Options

ConfigTypeDescriptionDefault
MirageService.mountsObjectDeclarative mounting: each entry is a resource (registration name) / mode (read/write/exec) / config (resource config); if not passed, must pass a live workspace, mutually exclusiveNot set
MirageService.workspaceWorkspaceMirage workspace with caller-owned lifecycle; once passed, plugin won't construct its ownNot set
MirageService.runtimesString or Object ArrayRuntime list (monty, pyodide, quickjs, etc.), can use either this or workspaceOptions.runtimesNot set
MirageShellExecutor.workdirStringDefault working directory for commands; when binding sessionId, used as the initial directory for that session/
MirageShellExecutor.sessionIdStringBind to specified workspace session, all commands execute within that session, cd/export/function definitions persist across invocationsNot bound (each invocation is an independent subshell)
MirageShellExecutor.defaultTimeoutMsMillisecondsDefault timeout for single command120000
MirageShellExecutor.maxTimeoutMsMillisecondsUpper limit for caller-requested timeout600000
MirageShellExecutor.stdoutMaxBytesBytesstdout capture limit200000
MirageShellExecutor.stderrMaxBytesBytesstderr capture limit64000
MirageShellExecutor.spillDirString"Spill" directory for background command output overflow (should be a workspace path like /tmp), Agent can subsequently read full output via this pathNot set (overflows are marked truncated instead of written to disk)
MirageFileSystem.cwdStringVirtual base directory for relative path resolution/
MirageFileSystem.diffBasisMaxBytesBytes"Pre-text" limit for before/after fields in write receipts10485760 (10 MiB)

FAQ

Q: What content does dsh see by default after installation?

A: A /tmp is automatically mounted as a RAM temp disk with exec (script execution) permission. Beyond that there are no other real data sources; all read/write happens in this session's memory and disappears when dsh is closed. When you need real data sources, just override the mirage line in your profile.

Q: How to connect real data sources like Slack, Redis, S3?

A: In your profile's cordis.patch.yml, change the mirage config to a mounts block, specifying resource (registration name like slack/redis/s3), mode (read/write/exec), and config (fields required by the resource). Use !!js process.env.SLACK_BOT_TOKEN to parse secrets at load time, avoiding plaintext on disk.

Q: After enabling, why are dsh's built-in PowerShell and ripgrep search gone?

A: These tools spawn host processes and have no place in the workspace, so this bundle explicitly disables pwsh-sandbox, tool-pwsh, and tool-fs-search. Search capability is covered by grep in the bash tool, spanning all mount sources.

Q: How to keep directory and variables persistent across multiple bash invocations?

A: Pass the sessionId field to MirageShellExecutor (e.g., 'agent-1'), then cd, export, and function definitions persist across invocations; if not passed, each invocation is an independent subshell with no persistent state. Same sessionId means connecting to the same workspace session, different ones are completely isolated.

Q: Is the workspace after installation read-only or writable?

A: Depends on each mount's mode, with read < write < exec three levels progressively opening up. Default /tmp is exec level, can execute bash scripts. To let dsh's file tools write to a mount (e.g., write results back to Redis), declare it as mode: write or mode: exec.

Q: How are large-output commands handled?

A: Default stdout buffers 200KB, stderr buffers 64KB, overflow is truncated and marked truncated. To preserve full output, point MirageShellExecutor.spillDir to a workspace path (like /tmp), overflow parts will be written to files, with paths returned in readOutput(), Agent can read back via the same VFS.

Q: Is it an official DSH plugin?

A: Not part of the DSH repository. It inserts its three Cordis plugins into the dsh system via dsh's bundle mechanism (cordis.patch.yml), replacing dsh's ctx.fs and ctx.shell two capability gaps. The underlying uses DSH 0.1.0-rc.6's exposed fs/shell plugin interfaces.

Q: How to mount my own backend?

A: On the caller side, registerResourceFactory('my-store', (config) => new MyStore(config)), then in the mount block write resource: my-store. Registration logic must complete before workspace construction, so put it in another plugin loaded by the profile (or import directly before MirageService starts in code). Built-in names cannot be overridden.

Learning Curve

Advanced — the default /tmp works out of the box to run commands, but to give the Agent real access to Slack/Redis etc., you need to write mount blocks in profile's cordis.patch.yml, figure out the three mode levels and !!js expression semantics; enabling persistent shell, spill directory, Python sandbox, etc. all require some configuration.

Known Issues and Limitations

  • Bundle disables dsh's built-in PowerShell tools and full-text search tools based on ripgrep by default, because they run host processes and there's no place for them in the workspace; use grep in bash tool for search instead
  • Write operations to the same target path are serialized (locked by path), when concurrent writes occur only one wins, other requests will observe the new version and be rejected by the stale version guard as outdated
  • When the workspace includes a runtime that "bypasses VFS" (like host local Python runtime), vfsOnly returns false, the shell plugin reports sandboxMode = undefined (i.e., "no sandbox") to dsh instead of declaring workspace-write; in this case dsh's permission presets won't combine with this shell
  • When spillDir is not set, background command output exceeding stdout buffer is only marked truncated, not written to file; to be "fully readable" you must explicitly give a workspace path
  • Bash tool is isolated between invocations by default (state not persistent), to preserve cwd/export/functions you must pass sessionId; different shell instances on the same sessionId share the same workspace session
  • Mount mode strength is ordered as read < write < exec, scripts cannot execute on weakly mode'd mounts; to let Agent run Python scripts, explicitly declare /tmp (or corresponding path) as mode: exec
  • The "stale version" guard for write operations is based on backend's fingerprint or modification time + size, backends that can't represent both simultaneously report as unversioned, writes don't do version comparison