# Agent Shell

<!-- -->

A virtual bash environment with a persistent filesystem backed by Tigris object storage, written in TypeScript and designed for AI agents.

AI agents produce artifacts — reports, data, configs, logs. These need to go somewhere durable, shareable, and globally accessible. Agent Shell gives agents a familiar bash interface (`cat`, `grep`, `sed`, `jq`, `awk`, pipes, redirects) where every file operation is backed by a Tigris bucket.

* **Isolated** — writes stay in-memory until you explicitly flush. No partial state leaks to storage.
* **Durable** — flush persists files to Tigris, globally distributed.
* **Checkpointable** — take snapshots of your storage at any point. Roll back if needed.
* **Forkable** — create copy-on-write forks of a bucket for safe experimentation.
* **Shareable** — generate presigned URLs for any stored file.

Built on [just-bash](https://github.com/vercel-labs/just-bash) for the shell engine and [@tigrisdata/storage](https://www.npmjs.com/package/@tigrisdata/storage) for the storage layer.

## Getting Started[​](#getting-started "Direct link to Getting Started")

### Installation[​](#installation "Direct link to Installation")

* NPM
* Yarn

```
npm install @tigrisdata/agent-shell
```

```
yarn add @tigrisdata/agent-shell
```

### Programmatic Usage[​](#programmatic-usage "Direct link to Programmatic Usage")

```
import { TigrisShell } from "@tigrisdata/agent-shell";



const shell = new TigrisShell({

  accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

  secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

  bucket: process.env.TIGRIS_STORAGE_BUCKET, // optional — auto-mounts at cwd (default: /workspace)

});



await shell.exec('echo "Hello from agent-shell" > greeting.txt');

await shell.exec("cat greeting.txt | tr a-z A-Z");

// stdout: "HELLO FROM AGENT-SHELL"



// Persist changes to Tigris

await shell.flush();
```

### Interactive Shell[​](#interactive-shell "Direct link to Interactive Shell")

Launch a shell directly — no install needed:

```
npx @tigrisdata/agent-shell
```

Authenticate with access keys:

```
$ configure --key tid_... --secret tsec_...

Mounted 2 bucket(s) at /. Run 'df' to list them.



/ $ ls

my-bucket  shared-data

/ $ cd my-bucket

/my-bucket $ echo "hello" > greeting.txt

/my-bucket $ cat greeting.txt

hello

/my-bucket $ flush

Flushed 2 mount(s)
```

Or login with your Tigris account:

```
$ login

Open this URL in your browser:

  https://auth.storage.tigrisdata.io/activate?user_code=XKCD-1234



Waiting for authorization... done!

Logged in as you@example.com



Mounted 2 bucket(s) at /. Run 'df' to list them.
```

You can also pass credentials as flags:

```
npx @tigrisdata/agent-shell --key tid_... --secret tsec_... --bucket my-bucket
```

## Storage Model[​](#storage-model "Direct link to Storage Model")

Agent Shell uses an in-memory write-back cache that provides isolation:

```
Agent writes file  →  cached in memory (isolated)

Agent reads file   →  cache hit or fetch from Tigris

Agent calls flush  →  all changes persisted atomically
```

This gives you:

* **Isolation** — nothing touches storage until you say so
* **Atomic commits** — if the agent fails midway, no partial state is written
* **Fast execution** — most operations never hit the network

```
const shell = new TigrisShell({

  accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

  secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

  bucket: process.env.TIGRIS_STORAGE_BUCKET,

});



try {

  await shell.exec('echo "processing..." > status.txt');

  await shell.exec("echo '{\"score\": 0.95}' > results.json");

  await shell.exec("cat results.json | jq .score"); // "0.95\n"



  // Only persist on success

  await shell.flush();

} catch (e) {

  // Nothing was written to Tigris — storage is clean

}
```

## Configuration[​](#configuration "Direct link to Configuration")

### TigrisConfig[​](#tigrisconfig "Direct link to TigrisConfig")

| Option            | Type     | Required | Description                           |
| ----------------- | -------- | -------- | ------------------------------------- |
| `bucket`          | `string` | No       | Bucket name — auto-mounted at cwd     |
| `accessKeyId`     | `string` | No\*     | Tigris access key ID                  |
| `secretAccessKey` | `string` | No\*     | Tigris secret access key              |
| `sessionToken`    | `string` | No\*     | Session token (from OAuth)            |
| `organizationId`  | `string` | No\*     | Organization ID (required with token) |
| `endpoint`        | `string` | No       | Tigris endpoint URL                   |

note

At least one auth mode is required: (accessKeyId + secretAccessKey) or (sessionToken + organizationId).

### ShellOptions[​](#shelloptions "Direct link to ShellOptions")

| Option | Type                     | Default      | Description                                     |
| ------ | ------------------------ | ------------ | ----------------------------------------------- |
| `cwd`  | `string`                 | `/workspace` | Starting working directory and auto-mount point |
| `env`  | `Record<string, string>` | `{}`         | Initial environment variables for the shell     |

```
const shell = new TigrisShell(

  {

    accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

    secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

    bucket: process.env.TIGRIS_STORAGE_BUCKET,

  },

  { cwd: "/my-project", env: { NODE_ENV: "production" } },

);
```

## API Reference[​](#api-reference "Direct link to API Reference")

### TigrisShell[​](#tigrisshell "Direct link to TigrisShell")

| Method / Property           | Type                                             | Description                        |
| --------------------------- | ------------------------------------------------ | ---------------------------------- |
| `constructor`               | `(config: TigrisConfig, options?: ShellOptions)` | Create a new shell instance        |
| `exec(command)`             | `Promise<BashExecResult>`                        | Execute a bash command             |
| `mount(bucket, mountPoint)` | `void`                                           | Mount a bucket at a path           |
| `unmount(mountPoint)`       | `void`                                           | Unmount a path                     |
| `listMounts()`              | `Array<{ bucket, mountPoint }>`                  | List current mounts                |
| `flush(mountPoint?)`        | `Promise<void>`                                  | Flush all mounts or a specific one |
| `engine`                    | `Bash`                                           | Underlying just-bash instance      |

### Sub-exports[​](#sub-exports "Direct link to Sub-exports")

Agent Shell exposes additional modules for advanced use cases:

```
// Filesystem adapter for custom storage layouts

import { TigrisAdapter } from "@tigrisdata/agent-shell/fs";



// Individual Tigris command factories

import {

  createTigrisCommands,

  createPresignCommand,

  createSnapshotCommand,

  createForkCommand,

} from "@tigrisdata/agent-shell/commands";
```

## Built-in Tigris Commands[​](#built-in-tigris-commands "Direct link to Built-in Tigris Commands")

Beyond standard bash commands, Agent Shell includes Tigris-specific commands.

### presign[​](#presign "Direct link to presign")

Generate presigned URLs for sharing or uploading:

```
presign /path/to/file.txt                    # GET URL, 1 hour expiry

presign /path/to/file.txt --expires 7200     # GET URL, 2 hour expiry

presign /path/to/file.txt --put              # PUT URL for uploads
```

### snapshot[​](#snapshot "Direct link to snapshot")

Checkpoint your storage. Create or list point-in-time bucket snapshots:

```
snapshot my-bucket                           # Create a snapshot

snapshot my-bucket --name "checkpoint-1"     # Create a named snapshot

snapshot my-bucket --list                    # List all snapshots
```

### fork[​](#fork "Direct link to fork")

Branch your storage. Create a copy-on-write fork for safe experimentation:

```
fork source-bucket my-fork                   # Fork a bucket

fork source-bucket my-fork --snapshot 1713200000   # Fork from a specific snapshot
```

### forks[​](#forks "Direct link to forks")

List forks of a bucket:

```
forks my-bucket
```

## Multi-Bucket[​](#multi-bucket "Direct link to Multi-Bucket")

Mount multiple buckets at different paths:

```
import { TigrisShell } from "@tigrisdata/agent-shell";



const shell = new TigrisShell({

  accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

  secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

  bucket: "agent-workspace", // auto-mounted at cwd (default: /workspace)

});



shell.mount("shared-datasets", "/datasets");



await shell.exec("cat /datasets/training/labels.csv | head -10");

await shell.exec("cp /datasets/training/labels.csv ./local-copy.csv");

await shell.exec('echo "processed" > results.txt');



// Flush all mounts, or a specific one

await shell.flush(); // all

await shell.flush("/datasets"); // just /datasets



// List and unmount

shell.listMounts();

shell.unmount("/datasets");
```

Or in the interactive shell:

```
$ mount shared-datasets /datasets

Mounted shared-datasets at /datasets



$ cp /datasets/data.csv ./local.csv

$ df

Bucket                    Mounted on

agent-workspace           /agent-workspace

shared-datasets           /datasets



$ umount /datasets

Unmounted /datasets
```

## Examples[​](#examples "Direct link to Examples")

### Processing Files[​](#processing-files "Direct link to Processing Files")

```
import { TigrisShell } from "@tigrisdata/agent-shell";



const shell = new TigrisShell({

  bucket: process.env.TIGRIS_STORAGE_BUCKET,

  accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

  secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

});



// Read a file already in the bucket

const result = await shell.exec("cat data.csv");

console.log(result.stdout);



// Process with standard Unix tools

const sorted = await shell.exec("cat data.csv | tail -n +2 | sort -t, -k2 -rn");

console.log(sorted.stdout);



// Write results and persist

await shell.exec("cat data.csv | wc -l > stats.txt");

await shell.flush();
```

### Snapshots, Forks, and Presigned URLs[​](#snapshots-forks-and-presigned-urls "Direct link to Snapshots, Forks, and Presigned URLs")

```
import { TigrisShell } from "@tigrisdata/agent-shell";



const shell = new TigrisShell({

  bucket: process.env.TIGRIS_STORAGE_BUCKET,

  accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

  secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

});



// Write a file and persist

await shell.exec('echo "quarterly report" > report.txt');

await shell.flush();



// Generate a shareable URL

const getUrl = await shell.exec("presign report.txt");

console.log(getUrl.stdout.trim());



// Checkpoint storage before changes

await shell.exec("snapshot my-bucket --name before-migration");



// Branch storage for safe experimentation

await shell.exec("fork my-bucket my-bucket-experiment");
```
