Crux
CookbookBasics

Workspace files

Give an agent durable scratch files, final outputs, and approval-gated writes.

Use a workspace when an agent needs to create files without stuffing every intermediate result into the prompt.

This recipe gives the model:

  • /workspace for notes and intermediate files.
  • /outputs for final deliverables.
  • file tools through use: [files].
  • approval middleware for writes.
import { approvalMiddleware, prompt } from "@use-crux/core";
import { inMemoryStorage, storage } from "@use-crux/core/storage";
import { workspace, workspaceToolNames } from "@use-crux/core/workspace";

const files = workspace({
  id: "case-files",
  namespace: "case:demo",
  storage: inMemoryStorage(),
  tools: {
    prefix: "case",
  },
});

const names = workspaceToolNames({ prefix: "case" });

const writeApproval = approvalMiddleware({
  id: "approve-file-writes",
  match: [names.writeFile, names.editFile],
  onRequest: async ({ input }) => {
    console.log("File write requested:", input);
  },
});

export const writer = prompt({
  id: "case-writer",
  use: [files],
  middleware: [writeApproval],
  system: `
You are a careful case writer.

Use /workspace for notes.
Write the final user-facing deliverable to /outputs.
Read existing files before editing them.
  `,
});

The injected tools are prefixed:

listCaseWorkspace
readCaseWorkspaceFile
writeCaseWorkspaceFile
editCaseWorkspaceFile

Delete is not injected unless you explicitly enable it:

const files = workspace({
  id: "case-files",
  namespace: "case:demo",
  storage: storage({ records, assets }),
  tools: {
    prefix: "case",
    delete: true,
  },
});

Production Storage

For production, replace the in-memory stores with durable adapters.

import { convexRecordStore, convexAssetStore } from "@use-crux/convex";
import { storage } from "@use-crux/core/storage";
import { workspace } from "@use-crux/core/workspace";

const files = workspace({
  id: "thread-files",
  namespace: threadId,
  storage: storage({
    records: convexRecordStore({ component: components.crux, ctx }),
    assets: convexAssetStore({ ctx }),
  }),
});

records keeps file metadata and small inline text. assets keeps binary and large files. See Storage and AssetStore for custom S3/R2/GCS-style asset stores.

For source material that already lives in a retriever, MCP server, project index, connected drive, or app document service, use a source-backed mount instead of copying everything into the workspace. Provider mounts are read-only by default and can opt into writes through custom source hooks. See Federated workspace sources.

On this page