> ## Documentation Index
> Fetch the complete documentation index at: https://docs.archil.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bash tool for agents

> Give an AI agent a persistent file system and a bash tool, using disk.exec and the AI SDK.

This guide shows how to wire up an AI agent with a persistent workspace disk and a bash tool backed by [serverless execution](/compute/serverless-execution). The agent can `ls`, `cat`, `grep`, write files, run scripts — anything bash can do — against data that lives in your S3 bucket.

The whole thing is about 30 lines.

## 1. Create a workspace disk

<CodeGroup>
  ```typescript TypeScript theme={null}
  import * as archil from "disk";

  const { disk } = await archil.createDisk({
    name: "agent-workspace",
    mounts: [{ type: "s3", bucketName: "agent-bucket" }],
  });
  ```

  ```python Python theme={null}
  import archil
  from archil import S3Mount

  disk = archil.create_disk(
      name="agent-workspace",
      mounts=[S3Mount(bucket_name="agent-bucket")],
  ).disk
  ```
</CodeGroup>

The disk is immediately usable — the file system grows and shrinks with your bucket and survives every agent run.

## 2. Wrap `disk.exec` as a tool

Steps 2 and 3 below use [the Vercel AI SDK](https://sdk.vercel.ai/) (TypeScript). The pattern is identical from Python — wrap `Disk.exec` as a tool in whatever agent framework you use (for example, tool use with the [Anthropic Python SDK](/sdks/python)). Only the framework glue changes; the `Disk.exec` call is the same.

Using the Vercel AI SDK, an `exec` tool is a handful of lines:

```typescript theme={null}
import { generateText, tool } from "ai";
import { z } from "zod";

const bash = tool({
  description: "Run a shell command inside the workdir.",
  inputSchema: z.object({
    command: z.string(),
  }),
  execute: async ({ command }) => {
    const { stdout, stderr, exitCode } = await disk.exec(command);
    return `exit ${exitCode}\n${stdout}${stderr ? `\n${stderr}` : ""}`;
  },
});
```

A few things worth calling out about the tool shape:

* **Keep the description minimal.** The agent doesn't need to know it's on Archil, or that this is a "workspace disk" — those are implementation details that cost reasoning tokens. `"Run a shell command inside the workdir."` is enough.
* **Return a single string, not an object.** Prefix with `exit N` and let the model read the output. Splitting stdout and stderr into fields forces the model to think about which field to read first; most of the time it doesn't matter.
* **Don't over-describe the argument.** `z.string()` without a `.describe()` is fine — the model knows what a shell command is.

Under the hood, every call spins up a container with the file system as the working directory, runs the command, and returns the result.

## 3. Run the agent

```typescript theme={null}
const result = await generateText({
  model: "openai/gpt-5.2",
  prompt:
    "List everything on the disk, then find every line in app.log that mentions ERROR.",
  tools: { bash },
  maxSteps: 10,
});

console.log(result.text);
```

The agent will:

1. Call `bash({ command: "ls" })` to discover what's there.
2. Call `bash({ command: "grep ERROR app.log" })` to find the errors.
3. Summarize the result.

Because the disk persists, running the same agent tomorrow sees the same files plus anything it wrote. You can also mount the same disk from a local laptop (`archil mount ...`) to inspect what the agent has been doing.

## Patterns

### Persistent agent memory

Write to a known path and the next agent run sees it:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await disk.exec("echo 'learned fact' >> memory.jsonl");
  ```

  ```python Python theme={null}
  disk.exec("echo 'learned fact' >> memory.jsonl")
  ```
</CodeGroup>

### Fan-out across the bucket

A map-reduce across every file in a directory:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const files = (await disk.exec("ls logs")).stdout.trim().split("\n");

  const results = await Promise.all(
    files.map((f) => disk.exec(`wc -l logs/${f}`)),
  );
  ```

  ```python Python theme={null}
  import asyncio
  from archil import Archil

  async def main():
      async with Archil() as client:
          d = await client.disks.get.aio("dsk-abc123")
          files = (await d.exec.aio("ls logs")).stdout.strip().split("\n")
          results = await asyncio.gather(*(d.exec.aio(f"wc -l logs/{f}") for f in files))

  asyncio.run(main())
  ```
</CodeGroup>

Each exec gets its own container — this scales horizontally without touching your local compute. If the agent's goal is to *search* for a pattern rather than run an arbitrary command per file, reach for [`disk.grep`](/compute/search-files) instead — it fans the same work out in a single call and returns structured matches.

### Running a local agent, too

If the agent runs on your laptop but wants the same workspace, mount the disk alongside:

```bash theme={null}
archil mount aws-us-east-1/agent-workspace /mnt/data
```

Reads and writes from the local mount are read-after-write consistent with anything the `exec`-backed bash tool does.

## Next steps

* [Serverless execution concepts](/compute/serverless-execution)
* [TypeScript SDK reference](/sdks/typescript)
* [Python SDK reference](/sdks/python)
* [Multi-agent systems guide](/guides/ai/multi-agent-systems)
