@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
360 lines (265 loc) • 15.8 kB
Markdown
# Workspaces
**Added in:** `@mastra/core@1.1.0`
A Mastra workspace gives agents a persistent environment for storing files and executing commands. Agents use workspace tools to read and write files, run shell commands, and search indexed content.
A workspace supports the following features:
- **[Filesystem](https://mastra.ai/docs/workspace/filesystem)**: File storage (read, write, list, delete, copy, move, grep)
- **[Sandbox](https://mastra.ai/docs/workspace/sandbox)**: Command execution (shell commands) and background processes
- **[LSP inspection](https://mastra.ai/docs/workspace/lsp)**: Hover, definition, and implementation queries through language servers
- **[Search](https://mastra.ai/docs/workspace/search)**: BM25, vector, or hybrid search over indexed content
- **[Skills](https://mastra.ai/docs/workspace/skills)**: Reusable instructions for agents
## When to use workspaces
Use a workspace when your agent needs access to the local filesystem, shell commands, semantic code inspection, indexed search, or reusable skill instructions.
## How it works
When you assign a workspace to an agent, Mastra includes the corresponding tools in the agent's toolset. The agent can then use these tools to interact with files and execute commands.
You can create a workspace with any combination of the supported features. The agent receives only the tools relevant to what's configured.
## Usage
### Creating a workspace
Create a workspace by instantiating the `Workspace` class with your desired features:
```typescript
import { Workspace, LocalFilesystem, LocalSandbox } from '@mastra/core/workspace'
const workspace = new Workspace({
filesystem: new LocalFilesystem({
basePath: './workspace',
}),
sandbox: new LocalSandbox({
workingDirectory: './workspace',
}),
skills: ['skills'],
})
```
The `skills` array specifies paths to directories containing skill definitions, see [Skills](https://mastra.ai/docs/workspace/skills).
### Global workspace
Set a workspace on the Mastra instance. All agents inherit this workspace unless they define their own:
```typescript
import { Mastra } from '@mastra/core'
import { Workspace, LocalFilesystem } from '@mastra/core/workspace'
const workspace = new Workspace({
filesystem: new LocalFilesystem({ basePath: './workspace' }),
})
const mastra = new Mastra({
workspace,
})
```
### Agent-level workspace
Assign a workspace directly to an agent to override the global workspace:
```typescript
import { Agent } from '@mastra/core/agent'
import { Workspace, LocalFilesystem } from '@mastra/core/workspace'
const workspace = new Workspace({
filesystem: new LocalFilesystem({ basePath: './agent-workspace' }),
})
export const myAgent = new Agent({
id: 'my-agent',
model: 'openai/gpt-5.4',
workspace,
})
```
## Configuration patterns
Workspaces support several configuration patterns depending on what capabilities your agent needs. The two main building blocks are `filesystem` (file tools) and `sandbox` (command execution), with `mounts` as the way to bridge cloud storage into sandboxes.
### Filesystem + sandbox (local)
For local development, pair a `LocalFilesystem` and `LocalSandbox` pointed at the same directory. Since both operate on the local machine, files written through the filesystem are immediately available to commands in the sandbox:
```typescript
const workspace = new Workspace({
filesystem: new LocalFilesystem({ basePath: './workspace' }),
sandbox: new LocalSandbox({ workingDirectory: './workspace' }),
})
```
The agent receives both file tools and `execute_command`. This is the simplest full-featured setup.
### Mounts + sandbox (cloud storage)
When you need cloud storage accessible inside a sandbox, use `mounts`. This FUSE-mounts the cloud filesystem into the sandbox so commands can read and write files at the mount path:
```typescript
const workspace = new Workspace({
mounts: {
'/data': new S3Filesystem({
bucket: 'my-bucket',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}),
'/skills': new GCSFilesystem({
bucket: 'agent-skills',
}),
},
sandbox: new E2BSandbox({ id: 'dev-sandbox' }),
})
```
Under the hood, `mounts` creates a [CompositeFilesystem](https://mastra.ai/docs/workspace/filesystem) that routes file tool operations to the correct provider based on path prefix. Commands in the sandbox access the mounted paths directly (e.g., `ls /data`).
You can mount multiple providers at different paths. Each mount path must be unique and non-overlapping.
> **Note:** `filesystem` and `mounts` are mutually exclusive — you can't use both in the same workspace. Use `filesystem` for a single provider without a sandbox, or `mounts` when you need to combine cloud storage with a sandbox.
### Filesystem only
Use a single `filesystem` when agents only need to read and write files. No command execution is available.
```typescript
const workspace = new Workspace({
filesystem: new S3Filesystem({
bucket: 'my-bucket',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}),
})
```
The agent receives file tools (`read_file`, `write_file`, `list_directory`, `grep`, etc.) that operate directly against the storage provider.
### Sandbox only
Use a single `sandbox` when agents only need to execute commands. No file tools are added.
```typescript
const workspace = new Workspace({
sandbox: new E2BSandbox({ id: 'dev-sandbox' }),
})
```
The agent receives the `execute_command` tool.
### Dynamic filesystem (per-request)
Pass a resolver function to `filesystem` to return a different filesystem per request. This is useful for multi-tenant applications or multi-role agents where each request needs a different storage root or different permissions.
```typescript
const workspace = new Workspace({
filesystem: ({ requestContext }) => {
const role = requestContext.get('agent-role') || 'guest'
return new LocalFilesystem({
basePath: `/workspaces/${role}`,
readOnly: role !== 'admin',
})
},
})
```
One workspace instance serves all requests. The resolver runs at tool execution time, so each request gets its own filesystem. See [dynamic filesystem](https://mastra.ai/docs/workspace/filesystem) for details.
### Which pattern should I use?
| Scenario | Pattern |
| --------------------------------------------------------- | ----------------------------------------------------- |
| Local development with files and commands | `filesystem` + `sandbox` (both local, same directory) |
| Cloud storage accessible inside a cloud sandbox | `mounts` + `sandbox` |
| Multiple cloud providers in one sandbox | `mounts` + `sandbox` (one mount per provider) |
| Agent reads/writes files, no command execution needed | `filesystem` only |
| Agent runs commands, no file tools needed | `sandbox` only |
| Multi-role or multi-tenant agent with per-request storage | `filesystem` with resolver function |
## Tool configuration
Configure tool behavior through the `tools` option on the workspace. This controls which tools are enabled and how they behave.
```typescript
import { Workspace, LocalFilesystem, LocalSandbox, WORKSPACE_TOOLS } from '@mastra/core/workspace'
const workspace = new Workspace({
filesystem: new LocalFilesystem({ basePath: './workspace' }),
sandbox: new LocalSandbox({ workingDirectory: './workspace' }),
tools: {
// Global defaults
enabled: true,
requireApproval: false,
// Per-tool overrides
[WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: {
requireApproval: true,
requireReadBeforeWrite: true,
},
[WORKSPACE_TOOLS.FILESYSTEM.DELETE]: {
enabled: false,
},
[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
requireApproval: true,
},
},
})
```
### Tool options
| Option | Type | Description |
| ------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enabled` | `boolean \| (context) => boolean` | Whether the tool is available (default: `true`). When a function, evaluated at tool-listing time. |
| `requireApproval` | `boolean \| (context) => boolean` | Whether the tool requires user approval before execution (default: `false`). When a function, evaluated at execution time with access to `args`. |
| `requireReadBeforeWrite` | `boolean \| (context) => boolean` | For write tools: require reading the file first (default: `false`). When a function, evaluated at execution time with access to `args`. |
| `name` | `string` | Custom name for the tool. Replaces the default `mastra_workspace_*` name. |
| `maxOutputTokens` | `number` | Maximum tokens for tool output (default: `2000`). Output exceeding this limit is truncated using tiktoken. |
### Dynamic tool configuration
Tool options that accept functions receive a context object and return a boolean. This enables context-aware tool behavior.
```typescript
const workspace = new Workspace({
filesystem: new LocalFilesystem({ basePath: './workspace' }),
tools: {
// Dynamic enabled: disable command execution unless explicitly allowed
[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
enabled: async ({ requestContext }) => {
return requestContext['allowExecution'] === 'true'
},
},
// Dynamic requireApproval: only require approval for protected paths
[WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: {
requireApproval: async ({ args }) => {
return (args.path as string).startsWith('/protected')
},
requireReadBeforeWrite: true,
},
},
})
```
Functions for `enabled` receive `{ requestContext, workspace }`. Functions for `requireApproval` and `requireReadBeforeWrite` also receive `args` since they are evaluated when the tool is called.
### Tool name remapping
Rename workspace tools to match the conventions your agent expects. The config key remains the original `WORKSPACE_TOOLS` constant — only the exposed name changes.
```typescript
import { Workspace, LocalFilesystem, LocalSandbox, WORKSPACE_TOOLS } from '@mastra/core/workspace'
const workspace = new Workspace({
filesystem: new LocalFilesystem({ basePath: './workspace' }),
sandbox: new LocalSandbox({ workingDirectory: './workspace' }),
lsp: true,
tools: {
[WORKSPACE_TOOLS.FILESYSTEM.READ_FILE]: { name: 'view' },
[WORKSPACE_TOOLS.FILESYSTEM.GREP]: { name: 'search_content' },
[WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES]: { name: 'find_files' },
[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: { name: 'execute_command' },
[WORKSPACE_TOOLS.LSP.LSP_INSPECT]: { name: 'lsp_inspect' },
},
})
```
The agent sees `view`, `search_content`, `find_files`, `execute_command`, and `lsp_inspect` instead of the default `mastra_workspace_*` names. Tool names must be unique — duplicate names or conflicts with other default names throw an error.
## LSP inspection
Enable `lsp` on a workspace to add semantic code inspection through language servers. This adds the `mastra_workspace_lsp_inspect` tool by default, which can return hover information, definition locations, and implementations for a symbol at a specific cursor position.
See [LSP inspection](https://mastra.ai/docs/workspace/lsp) for configuration, examples, and tool name remapping.
### Output truncation
Workspace tools automatically truncate large outputs to avoid exceeding LLM context limits. Two layers of truncation apply:
1. **Line-based tail**: Command output is limited to the last 200 lines by default (configurable per-command via the `tail` parameter)
2. **Token-based limit**: Tool output is capped at 2000 tokens by default
Set `maxOutputTokens` per tool to adjust the token limit:
```typescript
const workspace = new Workspace({
// ...
tools: {
[WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
maxOutputTokens: 5000,
},
},
})
```
ANSI escape codes (colors, cursor sequences) are automatically stripped from command output before it reaches the model.
### Read-before-write
When `requireReadBeforeWrite` is enabled on write tools, agents must read a file before writing to it. This prevents overwriting files the agent hasn't seen:
- **New files**: Can be written without reading (they don't exist yet)
- **Existing files**: Must be read first
- **Externally modified files**: If a file changed since the agent read it, the write fails
File write safety is enforced at two layers:
1. **Tool layer**: Before a write tool runs, the read tracker checks whether the file was modified since it was last read. If it was, the tool throws a `FileReadRequiredError`.
2. **Filesystem layer**: At write time, `writeFile()` compares the file's current modification time against the expected value (passed via `expectedMtime` in write options). If they don't match, it throws a `StaleFileError`. This catches external modifications (for example, an editor saving the file) that happen between the tool-level check and the actual write.
When `requireReadBeforeWrite` is enabled, workspace tools pass the recorded modification time through automatically. You can also use `expectedMtime` directly when calling `filesystem.writeFile()` outside of tools:
```typescript
const stat = await filesystem.stat('/docs/file.md')
// ... later ...
await filesystem.writeFile('/docs/file.md', newContent, {
expectedMtime: stat.modifiedAt,
})
```
## Initialization
Calling `init()` is optional in most cases—some providers initialize on first operation. Call `init()` manually when using a workspace outside of Mastra (standalone scripts, tests) or when you need to pre-provision resources before the first agent interaction.
```typescript
import { Workspace, LocalFilesystem, LocalSandbox } from '@mastra/core/workspace'
const workspace = new Workspace({
filesystem: new LocalFilesystem({ basePath: './workspace' }),
sandbox: new LocalSandbox({ workingDirectory: './workspace' }),
})
// Optional: pre-create directories and sandbox before first use
await workspace.init()
```
### What `init()` does
Initialization runs setup logic for each configured provider:
- `LocalFilesystem`: Creates the base directory if it doesn't exist
- `LocalSandbox`: Creates the working directory
- `Search` (if configured): Indexes files from `autoIndexPaths`, see [Search and Indexing](https://mastra.ai/docs/workspace/search)
External providers may perform additional setup like establishing connections or authenticating.
## Related
- [Filesystem](https://mastra.ai/docs/workspace/filesystem)
- [Sandbox](https://mastra.ai/docs/workspace/sandbox)
- [LSP inspection](https://mastra.ai/docs/workspace/lsp)
- [Skills](https://mastra.ai/docs/workspace/skills)
- [Search and indexing](https://mastra.ai/docs/workspace/search)
- [Workspace class reference](https://mastra.ai/reference/workspace/workspace-class)