---
title: Hands
description: Why Mirage gives agents one lightweight way to act across systems.
icon: hand
---

## Unified Hands

Mirage gives agents one unified way to act across systems.

Instead of learning a different action surface for every backend, agents can work through mounted paths, familiar Unix commands, and a lightweight stream-based execution model.

These commands are Mirage's hands. They stay simple on purpose: read, write, search, transform, and compose across systems through one interface.

## Why The Hands Stay Lightweight

Mirage is not trying to expose every backend as a giant in-process client library. It is trying to give agents a practical way to do work.

That is why the design stays lightweight:

- commands operate on paths and byte streams
- remote and local systems use the same shell-shaped interface
- composition happens through pipes, files, and command output
- cleanup and lifecycle stay explicit

This avoids pushing agents into resource-specific method trees or backend-specific action models for every system they touch.

## Why Async Fits The Hands

Mirage has to work well across local files, remote APIs, object storage, streamed command output, and cleanup steps that may continue after a command returns.

A synchronous, fully buffered model would either block too much or hide too much. Mirage instead treats command execution and I/O as naturally asynchronous, so the same hands can work consistently across systems.

This keeps the action surface simple while still supporting long reads, pipelines, and non-blocking cleanup.

## Hands Work In Bytes, Not Objects

The hands move bytes, not objects. That choice is deliberate, and it is what lets the same small set of commands act across every backend.

Consider the alternative. If every tool returned a backend-specific object shape, like `S3Object`, `SlackMessage`, or `LinearIssue`, then every new command has to learn every backend's type, and every new backend has to be taught to every command. (This is the M × N integration problem the [Architecture](/home/architecture#from-m-n-to-m--n) page covers in full.)

Bytes collapse it. Every command reads bytes and writes bytes. Every backend produces and consumes bytes. `grep` does not need to know whether its input came from local disk, an S3 object, or a generated stream. It just reads bytes. Add a new command and it works with every existing source. Add a new source and every existing command can already act on it.

Two other reasons this fits the hands:

- **In-distribution for LLMs.** Models have seen an enormous amount of shell and text-stream interaction in training. Byte-shaped hands feel native; bespoke object schemas are a sparser, newer signal.
- **Composable through pipes.** Bytes chain. A CSV in object storage, a Markdown file on GitHub, and the output of a transform command can all flow through the same pipeline. Custom response types cannot.

Mirage is built around that reality:

- commands consume and produce byte streams
- resources expose files and paths instead of custom response types
- higher-level structure stays optional at the edge

Byte-shaped hands are why the same small set of operations (read, write, search, transform, compose) works everywhere.

## Why This Fits Python and TypeScript

Both Python and TypeScript are increasingly used inside async frameworks, async servers, and agent applications that already expect non-blocking I/O. Mirage aligns with that direction instead of fighting it.

- **Python.** `await`-driven command execution and cleanup, compatible with the async stacks that dominate modern Python: [FastAPI](https://fastapi.tiangolo.com), [Starlette](https://www.starlette.io), and the [asyncio](https://docs.python.org/3/library/asyncio.html) runtime they build on.
- **TypeScript.** The same model maps cleanly onto promises, [Node.js streams](https://nodejs.org/api/stream.html), and modern frameworks like [Hono](https://hono.dev) or [Express](https://expressjs.com) that expect event-loop-based I/O.

The important part is not the syntax. Mirage keeps the same hands across Python and TypeScript.

## What This Buys Agents

- One interface for acting across local and remote systems.
- Better composability across resources.
- Streaming behavior that works for pipelines and large reads.
- Explicit lifecycle control for execution, caching, and cleanup.
- Finer-grained control over how agents read and write, which is better for security than scattered implicit backend mutations.
