Crux
GuidesWorkspaces

Storage and Limits

Choose workspace records and assets, configure namespaces, TTL, quotas, and inline thresholds.

Workspaces use records for file metadata and small inline content, and assets for binary or oversized content.

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

const ws = workspace({
  id: "research",
  namespace,
  storage: storage({
    records, // metadata plus small inline text/json
    assets, // binary and large payloads
  }),
  content: {
    inlineTextBelowBytes: 64_000,
  },
});

Rules:

  • Small text and JSON can live inline in RecordStore.
  • Large text goes to AssetStore.
  • Binary always goes to AssetStore.
  • Writing binary or oversized content without assets throws clearly.

For bundled storage options and custom S3/R2/GCS-style asset stores, see Storage and AssetStore.

Asset Stores

Use AssetStore for binary and oversized workspace files. Asset-backed text and JSON read back as text or json; binary files return a URI.

Custom stores implement:

interface AssetStore {
  put(asset: Asset, options?: AssetPutOptions): Promise<StoredAsset>;
  get(ref: AssetRef): Promise<StoredAsset>;
  delete(ref: AssetRef): Promise<void>;
}

Convex apps use convexRecordStore({ component, ctx }) for workspace metadata and convexAssetStore() for binary or large payloads:

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

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

See the Convex guide for complete setup and runtime caveats.

Namespaces

Namespaces isolate files for tenants, users, threads, or runs. Static namespaces work directly:

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

Dynamic namespaces resolve during prompt injection:

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

Pass an override when calling the workspace directly outside prompt resolution:

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

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

Retention And Quotas

Operator controls live on the workspace config:

const ws = workspace({
  id: "research",
  namespace,
  storage: storage({ records, assets }),
  retention: { ttlMs: 1000 * 60 * 60 * 24 },
  limits: {
    maxFileBytes: 1_000_000,
    maxNamespaceBytes: 25_000_000,
  },
});

retention.ttlMs is passed to RecordStore.put(..., { ttlMs }) only when the store supports TTL. maxFileBytes rejects one oversized write, and maxNamespaceBytes rejects writes that would push the namespace total over its cap.

The namespace quota counts live files only, not historical snapshots. Bound history storage with versioning.maxVersions:

const ws = workspace({
  id: "research",
  namespace,
  storage,
  versioning: { maxVersions: 20 },
});

On this page