From 0704f4282498353f42169e894b0e2b94395a0580 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 7 Jul 2026 15:06:51 +0200 Subject: [PATCH 1/2] Add Mirage integration tile --- integrations/mirage.md | 156 +++++++++++++++++++++++++++++++++++++++++ logos/mirage.svg | 13 ++++ 2 files changed, 169 insertions(+) create mode 100644 integrations/mirage.md create mode 100644 logos/mirage.svg diff --git a/integrations/mirage.md b/integrations/mirage.md new file mode 100644 index 00000000..c5077ede --- /dev/null +++ b/integrations/mirage.md @@ -0,0 +1,156 @@ +--- +layout: integration +name: Mirage +description: Give a Haystack Agent a bash shell over Mirage's unified virtual filesystem, mounting S3, Google Drive, Postgres and 50+ other backends as one filesystem +authors: + - name: deepset + socials: + github: deepset-ai + twitter: haystack_ai + linkedin: https://www.linkedin.com/company/deepset-ai/ +pypi: https://pypi.org/project/mirage-haystack +repo: https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/mirage +type: Tool Integration +report_issue: https://github.com/deepset-ai/haystack-core-integrations/issues +logo: /logos/mirage.svg +version: Haystack 2.0 +toc: true +--- +### **Table of Contents** +- [Overview](#overview) +- [Installation](#installation) +- [Usage](#usage) +- [Security model](#security-model) +- [License](#license) + +## Overview + +[Mirage](https://github.com/strukto-ai/mirage) is a unified virtual filesystem for AI agents: it +mounts heterogeneous backends — object storage (S3, GCS, R2), databases (Postgres, MongoDB, Redis), +and SaaS apps (Google Drive, Gmail, Slack, GitHub, Notion) — as a single filesystem, so every service +speaks the same familiar Unix semantics. An agent can `ls`, `cat`, `grep` and pipe across mounts +exactly as it would on local disk, without learning a new API for each backend. + +The `mirage-haystack` integration wraps Mirage as a Haystack +[`Tool`](https://docs.haystack.deepset.ai/docs/tool) that an +[`Agent`](https://docs.haystack.deepset.ai/docs/agent) can invoke to run bash commands across the +mounted filesystem. Instead of pre-loading data into a pipeline, you hand the agent one well-described +tool and let it explore the mounts itself to answer a question. + +## Installation + +```bash +pip install mirage-haystack +``` + +## Usage + +### Components + +This integration introduces the following: + +- `MirageMount`: A declarative, serializable description of a single backend mounted into the + workspace — its mount `path` (e.g. `/s3`), its Mirage `resource` name (e.g. `"s3"`, `"gdrive"`, + `"postgres"`), its `config`, and whether it is `read_only`. Credentials can be passed as Haystack + `Secret`s. Call `MirageMount.available_resources()` to list every backend name you can mount. +- `MirageWorkspace`: Holds a list of `MirageMount`s and lazily builds the live `mirage.Workspace` on + first use. It serializes cleanly (resolving `Secret`s only at build time) and can also be run + directly via `run()` / `run_async()`. +- `MirageShellTool`: A Haystack `Tool` that exposes the workspace's `execute` surface to an `Agent` + through a single `command` parameter. Output is normalized to text and truncated before it reaches + the model. It carries the security guards (`allowed_commands`, `denied_paths`) described below. + +### Use with a Haystack Agent + +Mount a directory (or any Mirage backend) read-only and give an `Agent` a `MirageShellTool` it can +drive with ordinary bash — the agent explores the files itself to answer: + +```python +from haystack.components.agents import Agent +from haystack.components.generators.chat import OpenAIChatGenerator +from haystack.dataclasses import ChatMessage + +from haystack_integrations.tools.mirage import MirageMount, MirageShellTool, MirageWorkspace + +workspace = MirageWorkspace( + mounts=[ + MirageMount(path="/data", resource="ram"), + MirageMount(path="/s3", resource="s3", config={"bucket": "my-bucket"}, read_only=True), + ] +) +tool = MirageShellTool(workspace, allowed_commands=["ls", "cat", "grep", "head", "wc", "cp"]) + +agent = Agent( + chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"), + tools=[tool], + system_prompt=( + "A virtual filesystem is available through the `mirage_shell` tool. Use bash commands " + "(ls, cat, grep, wc, ...) to inspect the mounts before answering. Base your answer only " + "on what the files actually show." + ), +) +agent.warm_up() + +result = agent.run( + messages=[ChatMessage.from_user("How many lines in /s3/log.txt mention 'alert'?")] +) +print(result["messages"][-1].text) + +tool.close() +``` + +Every backend is mounted the same way — swap the `MirageMount` for the one you need. Values that are +credentials should be wrapped in a `Secret`: + +```python +from haystack.utils import Secret +from haystack_integrations.tools.mirage import MirageMount + +MirageMount(path="/data", resource="ram") # in-memory scratch +MirageMount(path="/local", resource="disk", config={"root": "/srv/data"}) # local disk +MirageMount(path="/s3", resource="s3", config={"bucket": "my-bucket"}, read_only=True) +MirageMount( + path="/drive", + resource="gdrive", + config={"client_id": "...", "refresh_token": Secret.from_env_var("GDRIVE_REFRESH_TOKEN")}, + read_only=True, +) +``` + +### Run the workspace directly + +You don't need an `Agent` to use a workspace — call `run()` to execute a command yourself. This is +handy for testing mounts or composing across backends in plain Python: + +```python +from haystack_integrations.tools.mirage import MirageMount, MirageWorkspace + +ws = MirageWorkspace( + mounts=[ + MirageMount(path="/data", resource="ram"), + MirageMount(path="/s3", resource="s3", config={"bucket": "my-bucket"}, read_only=True), + ] +) +print(ws.run("grep -r alert /s3/logs | wc -l")) +ws.close() +``` + +## Security model + +Mirage never shells out to the host: every command runs inside Mirage's own virtual-filesystem +interpreter. Three controls shape what an Agent can do: + +- **Per-mount read-only mode** (`MirageMount(..., read_only=True)`) is the authoritative write + boundary. Mirage refuses any write to a read-only mount regardless of the command used, so this + is how you prevent modification or deletion. Mount anything the Agent should not change as read-only. +- **The command allowlist** (`allowed_commands`) restricts *which* commands may run. It is enforced + against every command Mirage would execute, including commands nested inside `$(...)`, backticks, + `<(...)` and subshells, so `ls "$(rm x)"` is rejected unless `rm` is also allowed. Treat it as a + best-effort filter to steer the Agent, not a sandbox: allowing a command that itself runs other + commands (`eval`, `bash`, `sh`, `source`, `xargs`, `timeout`) effectively allows anything, so do not + list those for untrusted/hosted use. +- **`denied_paths`** rejects any command whose text references one of the given path substrings. + +## License + +`mirage-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license. diff --git a/logos/mirage.svg b/logos/mirage.svg new file mode 100644 index 00000000..8722c1c4 --- /dev/null +++ b/logos/mirage.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + From 46555f5518b492a076954f6be8fb2214ac5886b1 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 7 Jul 2026 15:13:10 +0200 Subject: [PATCH 2/2] change logo --- integrations/mirage.md | 2 +- logos/mirage.png | Bin 0 -> 11565 bytes logos/mirage.svg | 13 ------------- 3 files changed, 1 insertion(+), 14 deletions(-) create mode 100644 logos/mirage.png delete mode 100644 logos/mirage.svg diff --git a/integrations/mirage.md b/integrations/mirage.md index c5077ede..9c37d3f0 100644 --- a/integrations/mirage.md +++ b/integrations/mirage.md @@ -12,7 +12,7 @@ pypi: https://pypi.org/project/mirage-haystack repo: https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/mirage type: Tool Integration report_issue: https://github.com/deepset-ai/haystack-core-integrations/issues -logo: /logos/mirage.svg +logo: /logos/mirage.png version: Haystack 2.0 toc: true --- diff --git a/logos/mirage.png b/logos/mirage.png new file mode 100644 index 0000000000000000000000000000000000000000..3676c0177d6f8735827c70595eae668315915892 GIT binary patch literal 11565 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 LO#hWGIp}`?Y&?=u literal 0 HcmV?d00001 diff --git a/logos/mirage.svg b/logos/mirage.svg deleted file mode 100644 index 8722c1c4..00000000 --- a/logos/mirage.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - -