# mirage

> 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.

## Metadata

- Author: [@strukto-ai](https://github.com/strukto-ai)
- Repo: <https://github.com/strukto-ai/mirage.git>
- GitHub: [strukto-ai/mirage](https://github.com/strukto-ai/mirage)
- Stars: 3,497
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Homepage: <https://www.strukto.ai/mirage>
- Topics: `agent-sandbox`, `agent-tools`, `ai-agents`, `bash`, `claude-code`, `dsh`, `dsh-plugin`, `fuse`, `llm-agents`, `openai-agents`, `python`, `typescript`, `vfs`, `virtual-filesystem`
- Forks: 259
- Open Issues: 107
- Last push: 2026-08-17T08:46:40.000Z
- Added: 2026-08-17T00:00:00.000Z

## Install

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

## Wiki

## 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
| Dependency | Minimum Version | Notes |
|---|---|---|
| Node.js | >=20.10.0 | Declared by package's `engines` field |
| DSH | 0.1.0-rc.6 | peerDependencies pins this version, no wider range declared |
| Platform | macOS / Linux / Windows | Package itself declares no `os`/`cpu` restrictions; underlying may need platform support if using FUSE real mounts |
| Native Modules | None | DSH integration package itself contains no native dependencies, runtime pulls mirage-node's dependencies on demand |

## Installation
```bash
dsh plugin --profile web add github:strukto-ai/mirage
```

## Configuration Options
| Config | Type | Description | Default |
|---|---|---|---|
| MirageService.mounts | Object | Declarative mounting: each entry is a `resource` (registration name) / `mode` (read/write/exec) / `config` (resource config); if not passed, must pass a live workspace, mutually exclusive | Not set |
| MirageService.workspace | Workspace | Mirage workspace with caller-owned lifecycle; once passed, plugin won't construct its own | Not set |
| MirageService.runtimes | String or Object Array | Runtime list (monty, pyodide, quickjs, etc.), can use either this or workspaceOptions.runtimes | Not set |
| MirageShellExecutor.workdir | String | Default working directory for commands; when binding sessionId, used as the initial directory for that session | `/` |
| MirageShellExecutor.sessionId | String | Bind to specified workspace session, all commands execute within that session, cd/export/function definitions persist across invocations | Not bound (each invocation is an independent subshell) |
| MirageShellExecutor.defaultTimeoutMs | Milliseconds | Default timeout for single command | 120000 |
| MirageShellExecutor.maxTimeoutMs | Milliseconds | Upper limit for caller-requested timeout | 600000 |
| MirageShellExecutor.stdoutMaxBytes | Bytes | stdout capture limit | 200000 |
| MirageShellExecutor.stderrMaxBytes | Bytes | stderr capture limit | 64000 |
| MirageShellExecutor.spillDir | String | "Spill" directory for background command output overflow (should be a workspace path like `/tmp`), Agent can subsequently read full output via this path | Not set (overflows are marked truncated instead of written to disk) |
| MirageFileSystem.cwd | String | Virtual base directory for relative path resolution | `/` |
| MirageFileSystem.diffBasisMaxBytes | Bytes | "Pre-text" limit for before/after fields in write receipts | 10485760 (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

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [mirage](https://deepseek-plugin.org/plugins/strukto-ai/mirage)
Wiki generated by AI (model: `MiniMax-M2.5`)
