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

# File System

> Read and write files during agent execution in the sandboxed environment

During execution, your agent runs inside an **isolated sandbox** with a dedicated filesystem. You can read and write files using Node.js `fs` module — no SDK wrapper needed.

## How It Works

Each agent execution gets its own temporary workspace. The only writable directory is `/work` — this is your working directory, HOME, and CWD. All other paths in the sandbox are read-only for security.

Everything in `/work` is **ephemeral** — destroyed after execution ends. If you need to persist data, send it to an external API before the execution finishes.

## Writing Files

```typescript theme={null}
import { createTool } from '@runflow-ai/sdk';
import { writeFile } from 'fs/promises';
import { z } from 'zod';

const generateReport = createTool({
  id: 'generate-report',
  description: 'Generate a CSV report',
  inputSchema: z.object({
    data: z.array(z.record(z.string())),
    filename: z.string(),
  }),
  execute: async (params) => {
    const headers = Object.keys(params.data[0]).join(',');
    const rows = params.data.map(row => Object.values(row).join(','));
    const csv = [headers, ...rows].join('\n');

    const path = `/work/${params.filename}`;
    await writeFile(path, csv, 'utf-8');

    return { success: true, path, rows: params.data.length };
  },
});
```

## Reading Files

```typescript theme={null}
import { createTool } from '@runflow-ai/sdk';
import { readFile } from 'fs/promises';
import { z } from 'zod';

const readConfig = createTool({
  id: 'read-config',
  description: 'Read a configuration file',
  inputSchema: z.object({
    filename: z.string(),
  }),
  execute: async (params) => {
    const content = await readFile(`/work/${params.filename}`, 'utf-8');
    return { content };
  },
});
```

## Persisting Files

Files in `/work` are deleted when execution ends. To keep them, send to an external service before finishing:

### Send to External API

```typescript theme={null}
import { httpPost } from '@runflow-ai/sdk/http';
import { readFile, writeFile } from 'fs/promises';

await writeFile('/work/output.pdf', pdfBuffer);

const fileContent = await readFile('/work/output.pdf');
await httpPost('https://api.example.com/upload', fileContent, {
  headers: { 'Content-Type': 'application/pdf' },
});
```

## Working with Binary Files

```typescript theme={null}
import { writeFile, readFile } from 'fs/promises';
import { httpGet } from '@runflow-ai/sdk/http';

// Download a file
const imageBuffer = await httpGet('https://example.com/image.png', {
  responseType: 'arraybuffer',
});
await writeFile('/work/image.png', Buffer.from(imageBuffer));

// Process it
const data = await readFile('/work/image.png');
// ... process the binary data
```

## Temporary Files in Workflows

Workflows can pass file paths between steps:

```typescript theme={null}
import { flow } from '@runflow-ai/sdk';
import { writeFile, readFile } from 'fs/promises';

const pipeline = flow('data-pipeline')
  .step('fetch', async ({ input }) => {
    const data = await fetchData(input.source);
    const path = '/work/raw-data.json';
    await writeFile(path, JSON.stringify(data));
    return { path };
  })
  .step('transform', async ({ results }) => {
    const raw = await readFile(results.fetch.path, 'utf-8');
    const data = JSON.parse(raw);
    const transformed = data.map(transform);
    const path = '/work/transformed.json';
    await writeFile(path, JSON.stringify(transformed));
    return { path, count: transformed.length };
  })
  .step('upload', async ({ results }) => {
    const content = await readFile(results.transform.path, 'utf-8');
    await httpPost('https://api.example.com/data', JSON.parse(content), {
      headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}` },
    });
    return { uploaded: true };
  })
  .build();
```

## Security and Limits

The sandbox is fully isolated — each execution runs in its own environment with strict constraints:

| Constraint         | Value                                              |
| ------------------ | -------------------------------------------------- |
| Writable directory | `/work` only                                       |
| Everything else    | Read-only (agent code, system binaries, libraries) |
| Process user       | Unprivileged (no root access)                      |
| Cleanup            | All files deleted after execution                  |

<Warning>
  **Never store secrets in files.** Use environment variables or the Credentials module instead. Files in `/work` are accessible to all code running in the same execution.
</Warning>

<Note>
  The `/work` directory has `0777` permissions — your agent code can create, read, and write files freely within it. Trying to write anywhere else will fail with a permission error.
</Note>

## Available System Tools

The sandbox includes common utilities you can use via `child_process`:

* **ffmpeg** / **ffprobe** — audio/video processing
* **python3** — Python scripts
* **node** — Node.js
* **imagemagick** (`convert`) — image manipulation

```typescript theme={null}
import { execSync } from 'child_process';

// Convert audio format
execSync('ffmpeg -i /work/input.ogg -ar 16000 /work/output.wav');

// Run a Python script
execSync('python3 /work/script.py');
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Tools" icon="wrench" href="/core-concepts/tools">
    Build tools that work with files
  </Card>

  <Card title="HTTP Utilities" icon="globe" href="/core-concepts/http-utilities">
    Download and upload files via HTTP
  </Card>

  <Card title="Knowledge (RAG)" icon="book" href="/core-concepts/knowledge-rag">
    Upload files to knowledge bases
  </Card>

  <Card title="Media Processing" icon="image" href="/core-concepts/media-processing">
    Process audio and images
  </Card>
</CardGroup>
