A workspace is the unit of isolation in AI Workbench — a named tenant that owns its own catalogs, vector-store descriptors, documents, saved queries, and async-ingest jobs.
Workspaces are runtime records, not config. They're created via
POST /api/v1/workspaces, fetched via GET /api/v1/workspaces/{uid},
and deleted via DELETE. Earlier drafts of this document described
a YAML-based workspace model; that's gone — workspaces are now rows in
the wb_workspaces table behind whichever control-plane backend the
runtime is using.
A single runtime process needs to serve multiple logical tenants without mixing their data. Rather than one container per tenant, we run one process with N workspaces and scope every operation by workspace UID.
uidis an RFC 4122 v4 UUID (lowercase, hyphenated).- The uid is a path segment:
/api/v1/workspaces/{uid}/…. nameis a human-readable label; it's not unique and has no semantic weight.
POST /api/v1/workspaces → create (returns uid)
GET /api/v1/workspaces → list
GET /api/v1/workspaces/{uid} → fetch
PUT /api/v1/workspaces/{uid} → patch
DELETE /api/v1/workspaces/{uid} → cascade delete
DELETE cascades to:
- Every catalog under the workspace.
- Every vector-store descriptor under the workspace, after dropping its underlying collection through the workspace's driver.
- Every document under any of those catalogs.
- Every saved query under any of those catalogs.
- Every async-ingest job record scoped to the workspace.
- Every workspace API key issued from the workspace.
- A request carrying workspace UID
Acan never read or mutate resources in workspaceB. Nested routes callControlPlaneStore.listCatalogs(workspace)/…getCatalog(workspace, uid)etc. and the store asserts the workspace exists before returning anything. - Logs carry
requestId. Structured OTel attributes (workspaceId, catalogUid, jobId) are on the cross-cutting observability workstream — seeroadmap.md.
Every workspace declares a kind — the backend it targets:
| Kind | Meaning |
|---|---|
astra |
DataStax Astra, via the Data API |
hcd |
Hyper-Converged Database (Astra's self-hosted cousin) — routing deferred |
openrag |
The OpenRAG project — routing deferred |
mock |
In-memory, for CI and offline development |
The kind describes this workspace's backend — distinct from
whichever backend the runtime's own control plane uses (configured via
workbench.yaml). mock stays a
first-class option so tests and local dev don't need any external
service.
kind is immutable after creation. PUT /api/v1/workspaces/{uid}
rejects a kind field with 400. Changing a workspace's kind would
orphan any vector-store collections already provisioned on the
original backend — there's no safe way to transparently migrate them,
so the runtime doesn't try. Delete and recreate the workspace if the
backend needs to change.
nameis a human-readable label. It is not unique — two workspaces can share a name (the UID is the identity). UIs should display the name but disambiguate by uid when needed.endpointis the data-plane URL for this workspace's backend. Forastra/hcdworkspaces it's the Astra Data API endpoint the vector-store driver dials (https://<db>-<region>.apps.astra.datastax.com). Each Astra DB has its own endpoint — put one workspace per DB to route correctly.endpointaccepts either a literal URL or a SecretRef (env:ASTRA_DB_API_ENDPOINT,file:/path). The driver detects refs by prefix-matching a registeredSecretProvider; literal URLs are used as-is. That lets the same workspace record work in dev (value baked into.env) and prod (value injected via a K8s Secret mounted as an env var) without code changes.mock/openragworkspaces don't dial anything and may leaveendpoint: null.
Credentials are never stored by value. A workspace may hold a
credentialsRef map whose values are SecretRef pointers:
{
"name": "prod",
"kind": "astra",
"credentialsRef": {
"token": "env:ASTRA_DB_APPLICATION_TOKEN"
},
"keyspace": "default_keyspace"
}Every value in the map must match the <provider>:<path> shape —
env:VAR_NAME or file:/abs/path. Posting a raw token returns
400. The runtime resolves refs through its SecretResolver at the
moment the workspace's backend needs to be contacted.
A workspace owns:
- Vector-store descriptors — the
wb_vector_store_by_workspacerows. Each declares dimensions, similarity, embedding config, lexical config, reranking config. These are descriptors, not the vector data itself — the underlying Data API Collection is provisioned transactionally by the workspace's vector-store driver when the descriptor is created. - Catalogs — named document collections, each optionally
vectorStore-bound to one of the workspace's descriptors.
Multiple catalogs may share one vector store. This was a deliberate relaxation from an earlier draft's strict 1:1 constraint. The store enforces:
- A catalog's
vectorStorefield (if non-null) must reference a vector store in the same workspace. DELETEa vector store is blocked with409 conflictwhile any catalog references it. Clear or move the catalog binding first, then delete the vector store.
The relationship:
workspace ──► catalog ──► vector-store descriptor (N:1)
│
└──► documents
When running with the default memory control plane, you can
pre-populate workspaces via seedWorkspaces in
workbench.yaml. Seeds
are only loaded into the memory backend; file and astra backends
already persist data and ignore the block.
- The runtime starts.
- It builds a
ControlPlaneStoreper the configured backend. - If memory + seeds are configured, seeds are loaded into the store.
- The HTTP server accepts
/api/v1/*requests; all workspace state comes from / lives in the store.
/readyz returns { status: "ready", workspaces: <N> } — N is the
current count of workspaces, not a list. Listing is at GET /api/v1/workspaces.
Create a mock workspace, add a catalog, list:
WS_BODY='{"name":"demo","kind":"mock"}'
WS_UID=$(curl -s -X POST http://localhost:8080/api/v1/workspaces \
-H "content-type: application/json" -d "$WS_BODY" | jq -r .uid)
CAT_BODY='{"name":"support"}'
curl -s -X POST http://localhost:8080/api/v1/workspaces/$WS_UID/catalogs \
-H "content-type: application/json" -d "$CAT_BODY"
curl -s http://localhost:8080/api/v1/workspaces/$WS_UID/catalogsDelete the workspace — the catalog goes with it:
curl -X DELETE http://localhost:8080/api/v1/workspaces/$WS_UID