Skip to content

Commit b4e1b0a

Browse files
rubenfiszelclaude
andcommitted
docs: update volumes, AI sandbox, and changelog with latest source changes
- Volumes: add setup section (volume storage workspace setting), permissions model, volume name/target validation, max 10 volumes, agent worker support, create from UI, exclusive lease TTL correction, correct CE limit (50 MB/file) - AI sandbox: update Claude Code template with token counting and prompt pattern - Changelog: correct CE limit to 50 MB per file (no file count limit) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 24189ce commit b4e1b0a

File tree

3 files changed

+74
-21
lines changed

3 files changed

+74
-21
lines changed

changelog/2026-02-26-volumes-sandbox-annotation-claude-sandbox/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ features:
1111
- 'AI sandbox: sandboxing + volumes pattern for running AI coding agents (Claude Code, Codex, OpenCode) with persistent state.'
1212
- 'Built-in Claude Code template using the Claude Agent SDK with volume-backed session persistence.'
1313
- 'Volumes UI in the Assets page for browsing, exploring, and deleting volumes.'
14-
- 'Community Edition volume limits: 50 files, 50 MB per file.'
14+
- 'Community Edition volume limit: 50 MB per file.'
1515
docs: /docs/core_concepts/ai_sandbox
1616
---

docs/core_concepts/56_volumes/index.mdx

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ import DocCard from '@site/src/components/DocCard';
88

99
Volumes provide persistent file storage that can be attached to [scripts](../../getting_started/0_scripts_quickstart/index.mdx) via code annotations. Files written to a volume during a job run are synced back to [object storage](../38_object_storage_in_windmill/index.mdx) and restored on the next run, enabling stateful workflows like caching, model storage, or agent memory.
1010

11-
Volumes are an [Enterprise](/pricing) feature. On the Community Edition, volumes are limited to 50 files and 50 MB per file.
11+
Volumes are an [Enterprise](/pricing) feature. On the Community Edition, volumes are limited to 50 MB per file.
1212

13-
## Prerequisites
13+
## Setup
1414

15-
Volumes require a [workspace object storage](../38_object_storage_in_windmill/index.mdx#workspace-object-storage) (S3, Azure Blob, GCS, or filesystem) to be configured. Volume data is stored under the `volumes/` prefix in your workspace storage.
15+
Volumes require two things to be configured in your workspace:
16+
17+
1. A [workspace object storage](../38_object_storage_in_windmill/index.mdx#workspace-object-storage) (S3, Azure Blob, GCS, or filesystem).
18+
2. A **volume storage** selection in the workspace settings under **Storage > Volume storage**. This can be set to the primary storage or any configured secondary storage. If volume storage is not selected, scripts with volume annotations will fail with an error.
19+
20+
Volume data is stored under the `volumes/<workspace_id>/<volume_name>/` prefix in the selected storage.
1621

1722
## Declaring volumes
1823

@@ -23,8 +28,10 @@ Volumes are declared with comment annotations at the top of your script. The syn
2328
```
2429

2530
Where:
26-
- `<name>` is the volume identifier (used as the storage key).
27-
- `<mount_path>` is the path where the volume will be available during execution. With [nsjail sandboxing](../../advanced/security_isolation/index.mdx), this can be an absolute path (`/tmp/data`) or a relative path. Without sandboxing, only relative paths are supported (e.g. `.claude`, `data/models`) — they are resolved relative to the job's working directory via a symlink.
31+
- `<name>` is the volume identifier. Must be 2–255 characters, start and end with an alphanumeric character, and contain only `a-z`, `A-Z`, `0-9`, `.`, `_`, or `-`.
32+
- `<mount_path>` is the path where the volume will be available during execution. With [nsjail sandboxing](../../advanced/security_isolation/index.mdx), this can be an absolute path (restricted to `/tmp/`, `/mnt/`, `/opt/`, `/home/`, `/data/` prefixes) or a relative path. Without sandboxing, only relative paths are supported (e.g. `.claude`, `data/models`) — they are resolved relative to the job's working directory via a symlink.
33+
34+
A script can mount up to 10 volumes. Duplicate volume names or mount paths within the same script are not allowed.
2835

2936
### TypeScript / JavaScript
3037

@@ -61,7 +68,7 @@ export async function main() {
6168

6269
### Multiple volumes
6370

64-
You can mount multiple volumes on the same script:
71+
You can mount multiple volumes on the same script (up to 10):
6572

6673
```python
6774
# sandbox
@@ -96,35 +103,61 @@ When called with `env="prod"` in workspace `acme`, mounts volumes `acme-cache` a
96103

97104
## How it works
98105

99-
1. **Before execution** - Windmill downloads the volume's files from object storage into a local directory, using a per-worker LRU cache (up to 10 GB) to skip unchanged files.
100-
2. **During execution** - The volume directory is bind-mounted (read-write) into the job's [nsjail](../../advanced/security_isolation/index.mdx) sandbox, or symlinked into the job directory when running without sandboxing.
101-
3. **After execution** - Changed and new files are synced back to object storage. Deleted files are removed from the remote. Volume metadata (size, file count) is updated.
106+
1. **Before execution** Windmill acquires an exclusive lease on the volume, then downloads the volume's files from object storage into a local directory using a per-worker LRU cache (up to 10 GB). Files are compared by size and MD5 hash to skip unchanged files.
107+
2. **During execution** The volume directory is bind-mounted (read-write) into the job's [nsjail](../../advanced/security_isolation/index.mdx) sandbox, or symlinked into the job directory when running without sandboxing.
108+
3. **After execution** — If the volume is writable, changed and new files are synced back to object storage. Deleted files are removed from the remote. Volume metadata (size, file count) is updated. The lease is released.
102109

103-
Symlinks inside a volume are preserved: they are serialized to a `_symlinks.json` metadata file in object storage and restored on download.
110+
Symlinks inside a volume are preserved: they are serialized to a metadata file in object storage and restored on download.
104111

105112
### Exclusive leasing
106113

107-
Only one job can use a given volume at a time. Windmill acquires an exclusive lease (30-second TTL, auto-renewed every 10 seconds) on each volume before downloading. If the volume is already leased by another worker, the job waits up to 2 minutes for the lease to be released.
114+
Only one job can use a given volume at a time. Windmill acquires an exclusive lease (60-second TTL, auto-renewed every 10 seconds) on each volume before downloading. If the volume is already leased by another worker, the job waits for the lease to be released.
115+
116+
### Agent workers
117+
118+
Volumes work with [agent workers](../28_agent_workers/index.mdx). Agent workers interact with volumes through an HTTP proxy API on the Windmill server — the same lease, download, sync-back, and permission logic applies.
119+
120+
## Permissions
121+
122+
Volumes support fine-grained permissions through Windmill's standard sharing model. You can share a volume with specific users or groups and set read-only or read-write access.
123+
124+
- **Owner** — the user who created the volume (or whose job first created it). Has full read-write access and can delete the volume.
125+
- **Read-only** — the job can use the volume but changes are not synced back to object storage after execution.
126+
- **Read-write** — the job can both read from and write to the volume.
127+
- **No permission** — if a volume has permissions set and the job's user has no matching entry, the job will fail.
128+
129+
Volumes with no permissions set (empty `extra_perms`) are accessible to all workspace users.
130+
131+
Admins always have full access to all volumes.
132+
133+
Permissions can be managed from the Volumes drawer in the [Assets](../52_assets/index.mdx) page using the share button.
108134

109135
## Managing volumes
110136

111-
Volumes are auto-created when a job with a volume annotation runs for the first time. You can view and manage volumes from the [Assets](../52_assets/index.mdx) page, where volumes appear as `volume://name`.
137+
### Creating volumes
138+
139+
Volumes can be created in two ways:
140+
141+
- **Automatically** — when a job with a volume annotation runs and the volume doesn't exist yet, it is created with the job's user as owner.
142+
- **Manually** — from the Volumes drawer in the [Assets](../52_assets/index.mdx) page using the "New volume" button.
112143

113-
Each volume entry tracks:
114-
- File count and total size
115-
- Created by (user who first triggered a job with this volume)
116-
- Last used timestamp
144+
### Exploring and deleting
117145

118-
Workspace admins can delete volumes from the UI. Deleting a volume removes its metadata from the database.
146+
From the Volumes drawer you can:
147+
148+
- View metadata: file count, total size, owner, last used timestamp.
149+
- Explore files in the volume using the S3 file browser.
150+
- Share the volume and manage permissions.
151+
- Delete the volume (owner, users with write permission, or admins). Deleting removes both the database metadata and all files from object storage. A volume that is currently leased by a running job cannot be deleted.
119152

120153
<!-- TODO: add screenshot of the Volumes drawer from the Assets page showing a list of volumes with their metadata -->
121154

122155
## Volumes and sandboxing
123156

124157
Volumes work with both sandboxed ([nsjail](../../advanced/security_isolation/index.mdx)) and non-sandboxed execution, but the mount path behavior differs:
125158

126-
- **With nsjail (sandbox)** - The volume directory is bind-mounted read-write at the declared mount path inside the sandbox. Both absolute (`/tmp/data`) and relative paths work. Relative paths are resolved relative to the nsjail working directory (`/tmp/bun` for Bun/TypeScript, `/tmp/deno` for Deno, `/tmp` for Python and others).
127-
- **Without nsjail** - Only relative mount paths are supported. Windmill creates a symlink from the job directory to the volume's local directory. An environment variable (`WM_VOLUME_<NAME>`) is also set pointing to the local directory.
159+
- **With nsjail (sandbox)** The volume directory is bind-mounted read-write at the declared mount path inside the sandbox. Both absolute (restricted to `/tmp/`, `/mnt/`, `/opt/`, `/home/`, `/data/`) and relative paths work. Relative paths are resolved relative to the nsjail working directory (`/tmp/bun` for Bun/TypeScript, `/tmp/deno` for Deno, `/tmp` for Python and others).
160+
- **Without nsjail** Only relative mount paths are supported. Windmill creates a symlink from the job directory to the volume's local directory.
128161

129162
You can combine volumes with the [`sandbox` annotation](../../advanced/security_isolation/index.mdx#sandbox-annotation) to enable per-script sandboxing, which is required if you need absolute mount paths:
130163

@@ -136,6 +169,12 @@ export async function main() {
136169
}
137170
```
138171

172+
<DocCard
173+
title="AI sandbox"
174+
description="Run AI coding agents with sandboxing and persistent volumes"
175+
href="/docs/core_concepts/ai_sandbox"
176+
/>
177+
139178
<DocCard
140179
title="Security and process isolation"
141180
description="Sandbox annotation and nsjail configuration"

docs/core_concepts/58_ai_sandbox/index.mdx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,19 @@ export async function main(anthropic: RT.Anthropic, agent_instructions?: AgentIn
7272
}
7373

7474
const isResume = !!sessionId;
75-
const prompt = "what day are we, what is the current gitlab price";
75+
76+
// You can hardcode the prompt or pass it as input.
77+
// AgentInstructions can contain a CLAUDE.md where to put the bulk of the instructions as well.
78+
const prompt = !isResume
79+
? "What is the fastest OSS workflow engine?"
80+
: "What did I ask you before?";
7681

7782
process.env.ANTHROPIC_API_KEY = anthropic.apiKey;
7883

7984
let response = "";
8085
let newSessionId: string | undefined;
86+
let tokenCount = 0;
87+
const seenIds = new Set<string>();
8188

8289
for await (const msg of query({
8390
prompt,
@@ -93,6 +100,13 @@ export async function main(anthropic: RT.Anthropic, agent_instructions?: AgentIn
93100
newSessionId = msg.session_id;
94101
}
95102
if (msg.type === "assistant") {
103+
const msgId = msg.message.id;
104+
if (!seenIds.has(msgId)) {
105+
seenIds.add(msgId);
106+
tokenCount += msg.message.usage.input_tokens + msg.message.usage.output_tokens;
107+
console.log(`${tokenCount} tokens`);
108+
}
109+
96110
response += msg.message.content
97111
.filter((b: any) => b.type === "text")
98112
.map((b: any) => b.text)

0 commit comments

Comments
 (0)