Crux
GuidesWorkspaces

Workspaces

Give agents durable scratch space and generated output files without writing custom file tools.

Most agents need somewhere to work. Without a workspace, intermediate notes, drafts, generated files, uploaded material, and final deliverables usually end up in one of two bad places: stuffed back into the prompt, or hidden behind custom file tools that every app has to design from scratch.

workspace() gives the model a durable, path-addressed file tree with a compact manifest and safe file tools. The model sees what files exist, reads only the files it needs, writes drafts and outputs to known folders, and leaves your app with inspectable files instead of a long hidden transcript.

Use workspaces for notes, intermediate drafts, generated reports, CSVs, PDFs, images, and uploaded or mounted task files. Do not use them for learned user memory, shared coordination state, or searchable knowledge. Those are memory(), blackboards, and retrieval.

Basic Usage

import { prompt } from "@use-crux/core";
import { inMemoryStorage } from "@use-crux/core/storage";
import { workspace } from "@use-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 a compact workspace manifest and file tools:

listWorkspace
readWorkspaceFile
writeWorkspaceFile
editWorkspaceFile
renameWorkspaceFile
grepWorkspace

The manifest shows paths and metadata, not full file contents. The model calls read tools when it needs content, which keeps large work products out of the prompt until they are relevant.

Default Folders

Zero-config workspaces create two writable mounts:

/workspace   scratch notes, intermediate files, working state
/outputs     final generated deliverables
await ws.write("/workspace/notes.md", "# Research notes");

await ws.write("/outputs/report.md", "# Final report");

This convention is intentionally simple:

PathUse it for
/workspaceNotes, plans, intermediate files, extracted data, draft fragments
/outputsUser-facing deliverables, generated downloads, published artifacts
/sourcesOptional mounted reference files from uploads, retrievers, MCP, or app data

Outputs are regular files under /outputs. Mark generated deliverables with status and kind when the app needs an artifact view:

await ws.write("/outputs/report.md", "# Final report", {
  status: "draft",
  kind: "report",
});

const artifact = await ws.finalize("/outputs/report.md");

What Your App Can Do

Apps can use the same workspace directly:

await ws.list("/workspace/**/*.md");
await ws.read("/workspace/notes.md");
await ws.write("/outputs/report.md", "# Report");
await ws.edit("/outputs/report.md", { find: "draft", replace: "final" });
await ws.exists("/workspace/notes.md");
await ws.stat("/workspace/notes.md");
await ws.append("/outputs/report.md", "\nAppendix");
await ws.copy("/workspace/notes.md", "/outputs/notes.md");
await ws.grep("launch", { path: "/workspace/**/*.md", ignoreCase: true });
await ws.finalize("/outputs/report.md");

Every method accepts a { namespace } override for direct calls outside prompt resolution:

await ws.write("/workspace/notes.md", "# Notes", { namespace: "thread:123" });

Production Shape

For production, pass durable storage. records holds metadata and small inline text or JSON. assets holds binary and large payloads:

import { storage } from "@use-crux/core/storage";

workspace({
  id: "research",
  namespace,
  storage: storage({
    records,
    assets,
  }),
  content: {
    inlineTextBelowBytes: 64_000,
  },
});

Namespaces

Namespaces isolate files for tenants, users, threads, or runs. Static namespaces work directly. Dynamic namespaces resolve during prompt injection, and direct calls can pass an override:

const ws = workspace({
  id: "research",
  namespace: ({ input }) => `thread:${input.threadId}`,
  storage: storage({ records, assets }),
});

await ws.write("/workspace/notes.md", "# Notes", { namespace: "thread:123" });

const tools = ws.asTools({ namespace: "thread:123" });

The override is also useful when running workspace operations outside a prompt resolution.

Where To Go Next

For exact method signatures and option names, use the Workspace API reference.

On this page