Workspaces
Give agents durable scratch space and generated output files without writing custom file tools.
workspace() is the Crux primitive for files an agent can inspect and create while it works.
Use it when the model needs a durable working area: notes, intermediate drafts, generated reports, CSVs, PDFs, images, or app-uploaded files. Do not use it for learned user memory, shared coordination state, or searchable knowledge. Those are memory(), blackboard(), and retriever().
Basic Usage
import { prompt } from '@crux/core'
import { inMemoryStorage } from '@crux/core/storage'
import { workspace } from '@crux/core/workspace'
const ws = workspace({
id: 'research',
namespace: `thread:${threadId}`,
storage: inMemoryStorage(),
})
const analyst = prompt({
id: 'analyst',
use: [ws],
system: 'Research the request and write final deliverables to /outputs.',
})use: [ws] injects two things:
1. A compact workspace manifest in the system prompt.
2. File tools: listWorkspace, readWorkspaceFile, writeWorkspaceFile, editWorkspaceFile.The manifest shows paths and metadata, not file contents. The model calls read tools when it needs contents.
Default Folders
Zero-config workspaces create two writable mounts:
/workspace scratch notes, intermediate files, working state
/outputs final generated deliverablesawait ws.write('/workspace/notes.md', '# Research notes')
await ws.write('/outputs/report.md', '# Final report')There is no separate public artifact() API in V1. Outputs are regular files under /outputs.
Listing Files
list() and listWorkspace support directory paths and simple globs:
await ws.list('/workspace')
await ws.list('/workspace/**/*.md')
await ws.list('/outputs/*.pdf')Supported in V1:
*within one path segment.**across path segments.- extension patterns such as
/**/*.md. limit.
Reading Files
read() returns a discriminated union:
const file = await ws.read('/workspace/notes.md')
if (file.kind === 'text') {
console.log(file.content)
}Binary files return metadata and a URI instead of raw bytes:
const file = await ws.read('/outputs/report.pdf')
if (file.kind === 'binary') {
console.log(file.uri, file.mimeType, file.size)
}Models see paths, metadata, previews when available, and blob URIs. Apps and devtools can fetch full bytes through the blob store.
Storage Rules
Workspaces use two storage layers:
import { storage } from '@crux/core/storage'
workspace({
storage: storage({
data, // metadata + small inline text/json
blobs, // binary and large payloads
}),
content: {
inlineTextBelowBytes: 64_000,
},
})Rules:
- Small text and JSON can live inline in
DataStore. - Large text goes to
BlobStore. - Binary always goes to
BlobStore. - Writing binary or oversized content without
blobsthrows clearly.
For bundled storage options and custom S3/R2/GCS-style blob stores, see Storage and BlobStore.
Source Files
/sources is explicit because source ownership differs per app.
const ws = workspace({
id: 'research',
namespace,
storage: storage({ data, blobs }),
mounts: [
{
path: '/sources',
access: 'read',
description: 'Uploaded reference material for this task.',
},
{ path: '/workspace', access: 'readwrite' },
{ path: '/outputs', access: 'readwrite' },
],
})Use /sources for uploaded files, mounted MCP resources, app-owned documents, or material produced by ingestion.
Multiple Workspaces
One unprefixed workspace can be injected directly:
prompt({
id: 'writer',
use: [ws],
system: 'Write the report.',
})Multiple workspaces need prefixes so tool names do not collide:
const research = workspace({
id: 'research',
namespace,
storage: storage({ data, blobs }),
tools: { prefix: 'research' },
})
const drafts = workspace({
id: 'drafts',
namespace,
storage: storage({ data, blobs }),
tools: { prefix: 'drafts' },
})
prompt({
id: 'writer',
use: [research, drafts],
system: 'Use research files to produce drafts.',
})That produces names such as listResearchWorkspace and writeDraftsWorkspaceFile.
Delete Is Opt-In
Delete is intentionally not injected by default:
const ws = workspace({
id: 'research',
namespace,
storage: storage({ data, blobs }),
tools: {
delete: true,
},
})Approvals
Use tool middleware for policy and approval.
import { approvalMiddleware } from '@crux/core'
import { workspaceToolNames } from '@crux/core/workspace'
const names = workspaceToolNames({ prefix: 'research' })
const approvals = approvalMiddleware({
id: 'workspace-writes',
match: [names.writeFile, names.editFile],
onRequest: async ({ input }) => {
await notifyUser(input)
},
})Prefixes change the final tool names, so match the generated names.
Convex
Convex apps use cruxConvexStore() for workspace metadata and convexWorkspaceBlobStore() for binary/large payloads. See the Convex guide for the complete setup and runtime caveats.
Devtools
Devtools include a Workspaces view that reconstructs a filesystem-like tree from workspace operations:
research
├─ /workspace
│ └─ notes.md
└─ /outputs
└─ report.pdfOTel receives privacy-safe operation metadata only. Raw file contents and full paths are not emitted to OTel.