Workspace Files
Upload, manage, and version shared files and folders across agents with namespace-scoped workspace storage
Workspace Files
This guide covers practical usage of workspace files -- shared mutable documents with full revision history. For conceptual background (ACL, concurrency model, architecture), see Concepts: Workspace.
Workspace file content requires S3-compatible storage (included in the default Docker Compose stack). Folder operations work without S3.
Creating Folders
Organize workspace items into a tree structure with folders.
CLI:
chorus workspace mkdir --namespace ring:dev --name "project-docs"
SDK:
const folder = await client.workspace.createFolder({
namespace: "ring:dev",
name: "project-docs",
});
console.log(`Created folder: ${folder.id}`);
curl:
curl -X POST http://localhost:3000/workspace/folders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "ring:dev",
"name": "project-docs"
}'
To create a nested folder, pass parent_id:
chorus workspace mkdir --namespace ring:dev --name "drafts" --parent workspace_item:abc123
Uploading Files
Upload binary files via multipart form data.
CLI:
chorus workspace upload ./report.pdf --namespace ring:dev
SDK:
const file = new File([buffer], "report.pdf", { type: "application/pdf" });
const item = await client.workspace.uploadFile(file, {
namespace: "ring:dev",
});
console.log(`Uploaded: ${item.id} (${item.current_size_bytes} bytes)`);
curl:
curl -X POST http://localhost:3000/workspace/files/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@report.pdf" \
-F "namespace=ring:dev"
Optional fields: parent_id (place inside a folder), metadata (JSON), revision_metadata (JSON attached to the first revision).
Listing Items
Browse workspace contents with namespace and optional parent filtering.
CLI:
# List root items in a namespace
chorus workspace list --namespace ring:dev
# List items inside a specific folder
chorus workspace list --namespace ring:dev --parent workspace_item:abc123
SDK:
const items = await client.workspace.list({
namespace: "ring:dev",
parent_id: "workspace_item:abc123",
});
for (const item of items) {
console.log(`${item.kind === "folder" ? "DIR" : "FILE"} ${item.name}`);
}
curl:
curl "http://localhost:3000/workspace/items?namespace=ring:dev" \
-H "Authorization: Bearer YOUR_API_KEY"
Results are cursor-paginated. Pass cursor from the response to fetch the next page.
Downloading Files
Retrieve the current file content or a specific historical revision.
CLI:
# Download current content
chorus workspace download workspace_item:abc123
# Save to a specific path
chorus workspace download workspace_item:abc123 --output report.pdf
SDK:
// Current content
const blob = await client.workspace.downloadCurrent("workspace_item:abc123");
// Historical revision
const oldBlob = await client.workspace.downloadRevision("workspace_revision:xyz789");
curl:
# Current content
curl -o report.pdf http://localhost:3000/workspace/files/workspace_item:abc123/content \
-H "Authorization: Bearer YOUR_API_KEY"
# Historical revision
curl -o old-report.pdf http://localhost:3000/workspace/revisions/workspace_revision:xyz789/content \
-H "Authorization: Bearer YOUR_API_KEY"
Updating File Content
Write a new revision to an existing file. Requires the current revision ID for optimistic concurrency.
SDK (binary):
const item = await client.workspace.get("workspace_item:abc123");
const newFile = new File([updatedBuffer], "report.pdf", { type: "application/pdf" });
const updated = await client.workspace.updateFileContent(
"workspace_item:abc123",
newFile,
item.current_revision_id,
);
console.log(`New revision: ${updated.current_revision_number}`);
curl:
curl -X PUT http://localhost:3000/workspace/files/workspace_item:abc123/content \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@updated-report.pdf" \
-F "expected_revision_id=workspace_revision:xyz789"
If another agent updated the file since you read it, the server returns 409 WORKSPACE_CONFLICT with the current item state. Re-read the item, resolve conflicts, and retry with the new current_revision_id.
Revision History
View the full revision chain for a file.
CLI:
chorus workspace history workspace_item:abc123
SDK:
const revisions = await client.workspace.history({
item_id: "workspace_item:abc123",
});
for (const rev of revisions) {
console.log(`#${rev.revision_number} - ${rev.size_bytes} bytes - ${rev.created_at}`);
}
curl:
curl "http://localhost:3000/workspace/items/workspace_item:abc123/history" \
-H "Authorization: Bearer YOUR_API_KEY"
To download a specific past revision, use the revision ID with the download endpoint:
chorus workspace download workspace_revision:xyz789
Moving and Renaming
Move an item to a different parent folder or rename it in place.
CLI:
# Move to a new parent folder
chorus workspace move workspace_item:abc123 --parent workspace_item:folder456
# Move to root (no parent)
chorus workspace move workspace_item:abc123 --parent root
SDK:
const item = await client.workspace.get("workspace_item:abc123");
await client.workspace.moveItem({
id: "workspace_item:abc123",
parent_id: "workspace_item:folder456",
expected_version: item.version,
});
curl:
curl -X POST http://localhost:3000/workspace/items/workspace_item:abc123/move \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"parent_id": "workspace_item:folder456",
"expected_version": 1
}'
The server enforces cycle detection -- you cannot move a folder into its own subtree.
Deleting Items
Soft-delete an item and its entire subtree.
CLI:
chorus workspace rm workspace_item:abc123
SDK:
const item = await client.workspace.get("workspace_item:abc123");
await client.workspace.deleteItem({
id: "workspace_item:abc123",
expected_version: item.version,
});
curl:
curl -X DELETE http://localhost:3000/workspace/items/workspace_item:abc123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expected_version": 1}'
Deleted items retain their stable IDs and revision history. They are excluded from list queries but can still be fetched by ID directly.
Text Mode
The SDK and RPC provide convenience methods for text content that handle encoding and revision tracking automatically. Useful for config files, notes, markdown, and other text documents.
SDK:
// Create a text file
const item = await client.workspace.createText({
namespace: "ring:dev",
name: "meeting-notes.md",
content: "# Sprint Planning\n\n- Review backlog\n- Assign tasks",
});
// Read text content
const { content, item: current } = await client.workspace.readText({
id: item.id,
});
console.log(content);
// Update text content (requires current revision ID)
await client.workspace.updateText({
id: item.id,
content: "# Sprint Planning\n\n- Review backlog\n- Assign tasks\n- Set deadlines",
expected_revision_id: current.current_revision_id,
});
RPC:
# Create
curl -X POST http://localhost:3000/rpc \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "workspace/create_text",
"params": {
"namespace": "ring:dev",
"name": "notes.md",
"content": "# Notes"
},
"id": 1
}'
# Read
curl -X POST http://localhost:3000/rpc \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "workspace/read_text",
"params": { "id": "workspace_item:abc123" },
"id": 2
}'
Access Control
Workspace items use the same namespace ACL as memory and artifacts:
agent:{id}-- private to the owning identityring:{name}-- shared with all ring members- Admin identities -- full access to all namespaces
ACL is enforced on every operation. An agent in ring:dev can read, write, and delete any workspace item in the ring:dev namespace.
Quotas
Workspace file storage shares the quota system with artifacts. The ARTIFACT_QUOTA_DEFAULT_MB environment variable controls the combined storage limit per namespace for both workspace files and artifacts.
When a namespace exceeds its quota, uploads return 409 WORKSPACE_QUOTA_EXCEEDED. Admin identities bypass quota enforcement.
MCP Tools
Both the embedded MCP server (/mcp endpoint) and the standalone chorus-mcp package expose workspace tools:
| Tool | Description |
|---|---|
workspace_list | List items in a namespace |
workspace_get | Get an item by ID |
workspace_create_folder | Create a folder |
workspace_create_text | Create a text file |
workspace_update_text | Update text file content |
workspace_read_text | Read text file content |
workspace_upload_file | Upload a binary file |
workspace_update_file | Write a new file revision |
workspace_download | Download file content |
workspace_history | List file revisions |
workspace_move | Move or rename an item |
workspace_delete | Soft-delete an item |
Error Handling
See Concepts: Workspace -- Error Codes for the full error code table. The most common errors during workspace operations:
WORKSPACE_CONFLICT(409) -- version mismatch. Re-read the item and retry with the current version.WORKSPACE_STORAGE_UNAVAILABLE(503) -- S3 not configured. Folder operations still work.WORKSPACE_NOT_FILE(400) -- attempted a file content operation on a folder.WORKSPACE_NAMESPACE_DENIED(403) -- the caller does not have access to this namespace.
For all error codes and HTTP responses, see the Errors Guide.