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
assetsthrows 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 },
});Snapshot Storage And Lifetime
ws.snapshot.create() materializes the selected local tree instead of retaining
references to live files or their history. Small text and JSON payloads are copied
into snapshot records. Binary and oversized payloads are copied into independently
owned AssetStore objects. A final artifact whose published content differs from
its working content owns both payloads.
The returned WorkspaceSnapshotRef.sizeBytes reports these materialized payload
bytes; it excludes record metadata. This makes snapshots reliable after live files,
history, or live asset objects change, but each snapshot can consume storage close
to the full captured tree size.
Snapshots are not counted by limits.maxNamespaceBytes and are not bounded by
versioning.maxVersions. They have no TTL, automatic pruning, or snapshot quota in
this release. Keep the complete JSON-safe ref and release storage explicitly:
const checkpoint = await ws.snapshot.create({ path: "/outputs" });
try {
await runRiskyWorkflow();
} finally {
await ws.snapshot.delete(checkpoint);
}Deletion is idempotent for a valid ref owned by the Workspace. A process crash can still leave incomplete metadata or orphaned payload objects on generic stores; snapshots provide same-process consistency and observed-failure rollback, not backup, disaster recovery, or distributed transactions.