Files
strukto-ai--mirage/examples/python/agents/openhands/README.md
T
wehub-resource-sync bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

76 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OpenHands + Mirage — agents that just use a shell
This example wires the OpenHands SDK to a Mirage `Workspace` and gives the agent **one** tool: `terminal`. No SaaS-specific tools, no MCP servers, no per-vendor schemas. Slack, S3, Gmail, GitHub, Linear — Mirage mounts each as a directory tree, and the agent treats them like a filesystem.
## Run
```bash
./python/.venv/bin/python examples/python/agents/openhands/sandbox_agent.py
```
The task: *"Find Slack messages containing 'hello' in #general."* The agent finishes in **2 commands**:
```
$ ls /slack/channels/
general__C04KEPWF6V7 random__C04JVGZM7UN test__C0AS76ABXMK
$ grep -i hello /slack/channels/general__C04KEPWF6V7/*.jsonl
.../2026-04-16.jsonl:[zechengzhang97] hello
.../2026-04-04.jsonl:[demo app] Hello from MIRAGE Slack provider!
```
That's it. The agent never learned about Slack's API. It used `ls` and `grep`.
## Why this matters: Mirage vs. the alternatives
The same task, three ways. Same answer; very different agent surface.
### With Mirage (this example)
- **Agent's tool list:** `terminal`. One tool, one schema.
- **Agent's vocabulary:** every shell command it already knows — `ls`, `cat`, `grep`, `head`, `wc`, `jq`, `find`, pipes, redirection.
- **What changed when we added Slack:** mount it at `/slack`. No new tools, no new prompts, no new agent code.
### With a Slack MCP server
- **Agent's tool list:** typically 612 Slack-specific tools — `slack_search_messages`, `slack_list_channels`, `slack_get_channel_history`, `slack_get_user_info`, `slack_post_message`, `slack_add_reaction`, …
- **Agent's vocabulary per tool:** every tool has its own JSON schema, parameter names, return shape. The model has to *learn the API*, then translate user intent into the right tool + the right params.
- **Composition:** want to filter messages with `jq` then count with `wc`? You can't — MCP tools are atomic; you get back what they return.
- **Adding Discord:** another MCP server with its own 612 tools. The agent's prompt now juggles two parallel APIs.
### With the Slack CLI
- **Agent's tool list:** `terminal` (good — same as Mirage), but...
- **Agent's vocabulary:** `slack search ...`, `slack chat send ...`, `slack auth login ...`. Vendor-specific subcommands, vendor-specific output formats, vendor-specific auth handling. The agent has to know the Slack CLI exists *and* how to invoke it.
- **Composition:** the CLI's stdout is its own format. Pipe it through `jq` if it happens to emit JSON, otherwise parse text.
- **Adding Discord:** install the Discord CLI. Now the agent needs to know two CLIs and pick correctly.
### Side-by-side
| | Mirage | Slack MCP | Slack CLI |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------------------------- |
| Tools the agent sees | 1 (`terminal`) | 612 per backend | 1 (`terminal`) |
| Vocabulary the agent must learn | shell + Mirage's filesystem layout | each tool's schema | each CLI's subcommand grammar |
| Composability (pipe / redirect / loop) | yes — real shell | no — atomic calls | partial — depends on CLI's stdout format |
| Adding a new backend | mount it; nothing else changes | new MCP server, new tool list, prompt churn | install new CLI; agent must learn it |
| Pushdown to native APIs (search, etc.) | automatic, in the builtin (Mirage rewrites `grep` over a Slack channel into one `search.messages` call) | only what the MCP exposes | none — text in, text out |
## What Mirage gives the agent
- **One stable tool surface** (`terminal`) regardless of how many backends are mounted.
- **Pipes and composability** because everything is a stream of bytes — `cat /s3/data/2026-04.parquet | grep error | jq '.user' | sort | uniq -c`.
- **Format-aware reads** — `cat` on `.parquet` / `.feather` / `.orc` returns a formatted table; `head -n 5` on `.jsonl` returns the first 5 messages; `grep` on a Slack channel directory pushes down to `search.messages` automatically.
- **One mental model** for the agent: *"the workspace is a filesystem; use shell."*
- **One mental model** for you: *"if I can mount it, the agent can use it."*
## Configure
The script loads `.env.development` from the repo root. Required:
| Var | What it's for |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LLM_API_KEY` | OpenHands `LLM` (defaults to Anthropic — set to your `ANTHROPIC_API_KEY`) |
| `AWS_S3_BUCKET`, `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | `/s3` mount |
| `SLACK_BOT_TOKEN` | `/slack` mount |
| `SLACK_USER_TOKEN` *(recommended)* | enables Slack's `search.messages` push-down so `grep` over `/slack/channels/<channel>/*.jsonl` runs in one API call instead of fanning out per day |