Skip to content
This repository was archived by the owner on Jun 16, 2026. It is now read-only.

Commit c906048

Browse files
Merge pull request #5 from NimbleBrainInc/docs/workspace-lifecycle-alignment
Align workspace + MCP docs with lifecycle refactor (PR #67)
2 parents e8bf011 + 6fb43ef commit c906048

6 files changed

Lines changed: 41 additions & 21 deletions

File tree

src/content/docs/api/authentication.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ The server always responds to `OPTIONS` preflight requests with:
270270

271271
```
272272
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
273-
Access-Control-Allow-Headers: Content-Type, Authorization, Mcp-Session-Id, Last-Event-ID, Mcp-Protocol-Version
273+
Access-Control-Allow-Headers: Content-Type, Authorization, Mcp-Session-Id, Last-Event-ID, Mcp-Protocol-Version, X-Workspace-Id
274274
Access-Control-Expose-Headers: Mcp-Session-Id, Mcp-Protocol-Version
275275
```
276276

src/content/docs/api/bootstrap.mdx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@ import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
77

88
The Bootstrap API returns everything a client needs to initialize: the authenticated user's identity, their workspaces, and the active workspace's shell configuration. The web UI calls this on page load.
99

10+
Bootstrap is the discovery endpoint — the one place the server picks a default workspace for the caller. Data endpoints (`/mcp`, `/v1/chat`, `/v1/tools/call`, `/v1/shell`, `/v1/events`) treat `X-Workspace-Id` as authoritative and reject requests that omit it. Same header name, different strictness by endpoint class.
11+
1012
## GET /v1/bootstrap
1113

1214
Returns the current user, their workspaces with roles, and the shell configuration for the active workspace.
1315

16+
**Request headers:**
17+
18+
| Header | Required | Description |
19+
|---|---|---|
20+
| `Authorization` | Yes | `Bearer <token>` — see [Authentication](/api/authentication) |
21+
| `X-Workspace-Id` | No | **Soft hint.** If present and the user is a member of this workspace, it becomes `activeWorkspace` in the response. Otherwise ignored and the first membership is returned. Clients use this to restore a user's last-selected workspace from local storage. |
22+
1423
**Response:**
1524

1625
```json
@@ -50,15 +59,16 @@ Returns the current user, their workspaces with roles, and the shell configurati
5059
|-------|------|-------------|
5160
| `user` | `object` | Authenticated user identity (id, name, email, role) |
5261
| `workspaces` | `array` | Workspaces the user belongs to, with their role in each |
53-
| `activeWorkspace` | `string` | ID of the currently active workspace |
62+
| `activeWorkspace` | `string` | ID of the currently active workspace. Always populated — the server enforces the invariant that every authenticated user has at least one workspace (provisioned at login, re-created on next login if externally deleted). |
5463
| `shell` | `object` | Shell configuration — sidebar placements, route endpoints |
5564
| `config` | `object` | Runtime configuration — model, iteration limits, token limits |
5665

5766
**Error responses:**
5867

59-
| Status | Condition |
60-
|--------|-----------|
61-
| 401 | Not authenticated |
68+
| Status | Error code | Condition |
69+
|--------|-----------|-----------|
70+
| 401 || Not authenticated |
71+
| 500 | `workspace_invariant_violation` | Authenticated user reached bootstrap with zero workspaces. Provisioning should have run at login; this indicates a broken identity-layer path rather than client misuse. |
6272

6373
**Example:**
6474

src/content/docs/api/mcp-endpoint.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Every MCP request is scoped to a workspace. External clients identify the worksp
7676

7777
Workspace IDs have the form `ws_<alphanumeric>` and can be copied from **Settings → Profile → MCP Connection** in the web UI.
7878

79-
If the user belongs to multiple workspaces and no header is set, the server returns `400` with the message "Multiple workspaces available. Set X-Workspace-Id header to specify which workspace to use." Set the header on every client config — it removes ambiguity if the user is later added to more workspaces.
79+
If the header is missing, the server returns `400` with the message "Workspace required. Set the X-Workspace-Id header. The workspace ID is available from GET /v1/bootstrap or Settings → Profile → MCP Connection." — regardless of how many workspaces the user belongs to. The server does not pick a default on data endpoints.
8080

8181
## What's exposed
8282

src/content/docs/api/overview.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Every endpoint requires authentication unless noted otherwise.
7878
| `POST` | `/v1/chat/stream` | Yes | Streaming chat (SSE) |
7979
| `GET` | `/v1/apps/:name/resources/:path` | Yes | Fetch a UI resource |
8080
| `POST` | `/v1/tools/call` | Yes | Direct tool invocation |
81-
| `GET` | `/v1/shell` | Yes | Shell configuration (placements, endpoints) |
81+
| `GET` | `/v1/shell` | Yes | Shell configuration (placements, endpoints) — requires `X-Workspace-Id` |
8282
| `GET` | `/v1/files/:fileId` | Yes | Serve an uploaded file |
8383
| `GET` | `/v1/events` | Yes | Workspace SSE event stream |
8484
| `GET` | `/v1/auth/provider` | No | Auth adapter info (local, oidc, or dev) |

src/content/docs/apps/placements.mdx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,22 +135,28 @@ The `size` field provides a hint to the slot renderer:
135135

136136
The `PlacementRegistry` is an in-memory store that tracks all active placements. It is updated when bundles are installed or uninstalled.
137137

138-
**Registration** — When a bundle starts, its placements are registered. If the bundle has explicit `placements`, they are used directly. If it only has `primaryView`, `registerLegacy()` converts it to a single `"main"` placement.
138+
Every entry is either **ambient** (no `wsId` — platform-provided views like Home, Conversations, Files, Settings; always present inside any workspace) or **workspace-scoped** (installed bundles, visible only to members of that workspace). The registry exposes a single read method, `forWorkspace(wsId)`, which returns ambient + scoped entries merged and sorted. There is deliberately no "return everything" accessor — in a multi-tenant host, no legitimate caller wants placements unrelated to a workspace.
139139

140-
**Querying**`forSlot(slot)` returns all entries matching the slot (including sub-slots), sorted by priority ascending.
140+
**Registration**When a bundle starts, its placements are registered against the installing workspace's `wsId`. If the bundle has explicit `placements`, they are used directly. If it only has `primaryView`, `registerLegacy()` converts it to a single `"main"` placement.
141141

142-
**Unregistration** — When a bundle is uninstalled, all its placements are removed.
142+
**Querying**`forWorkspace(wsId)` returns ambient entries plus entries scoped to `wsId`, sorted by slot then priority (lower first).
143+
144+
**Unregistration** — When a bundle is uninstalled, its placements for that workspace are removed. Other workspaces' entries for the same bundle are untouched.
143145

144146
```typescript
145-
// Register explicit placements
146-
registry.register("my-app", [
147-
{ slot: "sidebar", resourceUri: "ui://nav", priority: 50, label: "My App" },
148-
{ slot: "main", resourceUri: "ui://dashboard", route: "my-app" },
149-
]);
150-
151-
// Query sidebar items (includes sidebar.* sub-slots)
152-
const items = registry.forSlot("sidebar");
153-
// → sorted by priority, includes sidebar, sidebar.conversations, etc.
147+
// Register explicit placements, scoped to a workspace
148+
registry.register(
149+
"my-app",
150+
[
151+
{ slot: "sidebar", resourceUri: "ui://nav", priority: 50, label: "My App" },
152+
{ slot: "main", resourceUri: "ui://dashboard", route: "my-app" },
153+
],
154+
"ws_engineering",
155+
);
156+
157+
// Read the merged ambient + workspace-scoped list
158+
const items = registry.forWorkspace("ws_engineering");
159+
// → sorted by slot then priority
154160
```
155161

156162
## Example: sidebar + main view

src/content/docs/using/workspaces.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,15 @@ The agent installs the bundle and registers its tools in the workspace's tool re
9191

9292
## Switching workspaces
9393

94-
In the web UI, switch workspaces from the workspace selector in the sidebar. The API uses the `X-Workspace-Id` header or the `workspaceId` field in chat requests to target a specific workspace.
94+
In the web UI, switch workspaces from the workspace selector in the sidebar. Every API request to a workspace-scoped endpoint (`/mcp`, `/v1/chat`, `/v1/tools/call`, `/v1/shell`, `/v1/events`) must include an `X-Workspace-Id` header. Chat requests continuing an existing conversation inherit the workspace from `conversationId` — no alternative addressing scheme.
95+
96+
<Aside type="note">
97+
NimbleBrain guarantees every authenticated user has at least one workspace. One is provisioned on first login and re-created on the next login if manually deleted. Client code can rely on this invariant — `activeWorkspace` from `/v1/bootstrap` is always a valid workspace ID.
98+
</Aside>
9599

96100
## Bootstrap
97101

98-
When the web UI loads, it calls `GET /v1/bootstrap` to get the current user's identity, workspace list with roles, and the active workspace's configuration. This single call provides everything needed to render the shell.
102+
Clients that don't yet know a workspace ID call `GET /v1/bootstrap` — the one endpoint where the server picks a default. It returns the authenticated user's identity, the full workspace list with roles, and the active workspace's shell configuration. Clients may send `X-Workspace-Id` on bootstrap as a soft hint (e.g., restoring a user's last selection from local storage); the server honors it when it matches a membership and falls back to the first workspace otherwise.
99103

100104
## What's next
101105

0 commit comments

Comments
 (0)