docs(concepts+api-ref): add workspace spend cap — PATCH /workspaces/:id/budget
Pairs with molecule-core PR #611 (merged 2026-04-17). Closes #541. - concepts.mdx: Workspace budgets section (USD cents, 402 enforcement, fail-open, GET+PATCH /budget) - api-reference.mdx: Budget subsection with GET+PATCH /workspaces/:id/budget, full JSON shape, warn/info callouts; PATCH /workspaces/:id row notes budget_limit is not accepted there Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d566b84dcc
commit
fc1ced14b9
@ -72,7 +72,7 @@ Core workspace CRUD and lifecycle operations.
|
||||
| POST | `/workspaces` | AdminAuth | Create a new workspace. Accepts `name`, `runtime`, `template`, `parent_id`, `tier`, `workspace_dir`, and other fields. Runtime is auto-detected from template config if omitted (defaults to `langgraph`). |
|
||||
| GET | `/workspaces` | AdminAuth | List all workspaces with status, runtime, agent card, position, and hierarchy info. |
|
||||
| GET | `/workspaces/:id` | WorkspaceAuth | Get a single workspace by ID. |
|
||||
| PATCH | `/workspaces/:id` | WorkspaceAuth | Update workspace fields. A workspace bearer token is always required — unauthenticated calls return 401. Validates field constraints: `name` ≤ 255 chars, `role` ≤ 1,000 chars, `model` and `runtime` ≤ 100 chars each; `name` and `role` must not contain newlines (`\\n`, `\\r`) or YAML-special characters (`{}[]|>*&!`). Oversized or invalid field values return 400. `:id` must be a valid UUID. |
|
||||
| PATCH | `/workspaces/:id` | WorkspaceAuth | Update workspace fields. A workspace bearer token is always required — unauthenticated calls return 401. Validates field constraints: `name` ≤ 255 chars, `role` ≤ 1,000 chars, `model` and `runtime` ≤ 100 chars each; `name` and `role` must not contain newlines (`\\n`, `\\r`) or YAML-special characters (`{}[]|>*&!`). Oversized or invalid field values return 400. `:id` must be a valid UUID. Financial fields (`budget_limit`) are not accepted here — use `PATCH /workspaces/:id/budget` (AdminAuth). |
|
||||
| DELETE | `/workspaces/:id` | AdminAuth | Delete a workspace. Stops the container, revokes all auth tokens, and removes all associated data. |
|
||||
|
||||
### Lifecycle
|
||||
@ -83,6 +83,56 @@ Core workspace CRUD and lifecycle operations.
|
||||
| POST | `/workspaces/:id/pause` | WorkspaceAuth | Stop the container and set status to `paused`. Paused workspaces skip health sweep, liveness monitor, and auto-restart. |
|
||||
| POST | `/workspaces/:id/resume` | WorkspaceAuth | Re-provision a paused workspace. Status transitions to `provisioning`. |
|
||||
|
||||
### Budget
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/workspaces/:id/budget` | AdminAuth | Read a workspace's current spend and ceiling. Returns `budget_limit`, `monthly_spend`, and `budget_remaining` (all in USD cents). |
|
||||
| PATCH | `/workspaces/:id/budget` | AdminAuth | Set or clear a workspace's monthly spend ceiling. Body: `{ "budget_limit": N }` (positive integer, USD cents) or `{ "budget_limit": null }` to remove the cap. Negative values → 400. Returns same shape as GET. |
|
||||
|
||||
**Request / response shape:**
|
||||
|
||||
```json
|
||||
// PATCH request body
|
||||
{ "budget_limit": 500 } // $5.00/month ceiling
|
||||
{ "budget_limit": null } // no ceiling
|
||||
|
||||
// GET and PATCH success response (200)
|
||||
{
|
||||
"budget_limit": 500, // null when no ceiling
|
||||
"monthly_spend": 312, // accumulated spend this period, USD cents
|
||||
"budget_remaining": 188 // null when no ceiling; max(0, limit-spend) — can be negative
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
**`budget_limit` and `monthly_spend` are absent from `GET /workspaces/:id`**
|
||||
|
||||
Financial fields are stripped unconditionally from the workspace detail
|
||||
response — they do not appear for any caller, authenticated or not. Always
|
||||
use `GET /workspaces/:id/budget` (AdminAuth) to read spend data.
|
||||
|
||||
`budget_limit` is also **not** accepted on the general `PATCH /workspaces/:id`
|
||||
endpoint. Use the dedicated `/budget` route.
|
||||
</Callout>
|
||||
|
||||
<Callout type="info">
|
||||
**Enforcement and fail-open behaviour**
|
||||
|
||||
When `monthly_spend >= budget_limit`, `POST /workspaces/:id/a2a` returns:
|
||||
```
|
||||
HTTP 402 Payment Required
|
||||
{"error": "workspace budget limit exceeded"}
|
||||
```
|
||||
Channel sends (Slack, Telegram, Discord, Lark) are also budget-gated with
|
||||
the same 402 response. The workspace itself is **not paused** — it keeps
|
||||
running; only inbound A2A and channel traffic is blocked.
|
||||
|
||||
**Fail-open:** if the budget check encounters a DB error, traffic is allowed
|
||||
through rather than blocked. The spend ceiling is a soft guardrail, not a
|
||||
hard guarantee.
|
||||
</Callout>
|
||||
|
||||
---
|
||||
|
||||
## Registry
|
||||
|
||||
@ -18,6 +18,7 @@ workspace has:
|
||||
- An optional **parent** (forms the org tree)
|
||||
- An optional **workspace_dir** (a host path bind-mounted into the
|
||||
container — gives the agent direct access to your codebase)
|
||||
- An optional **budget_limit** (workspace-level spend cap — see [Workspace budgets](#workspace-budgets) below)
|
||||
|
||||
Workspaces talk to each other via **A2A** (agent-to-agent) messages, routed
|
||||
by the platform. Communication rules: same workspace, siblings, and
|
||||
@ -52,6 +53,75 @@ description: "Senior backend engineer focused on correctness, security, and perf
|
||||
The generator is non-fatal: a missing or unreadable `config.yaml` prints a
|
||||
startup warning but does not prevent the workspace from booting.
|
||||
|
||||
## Workspace budgets
|
||||
|
||||
A **budget limit** is a per-workspace monthly spend ceiling, expressed in
|
||||
**USD cents** (e.g. `500` = $5.00/month). It is set by a tenant admin — workspace
|
||||
agents cannot read or clear their own financial ceiling.
|
||||
|
||||
### What happens at the ceiling
|
||||
|
||||
When `monthly_spend >= budget_limit`, the platform blocks new A2A proxy calls
|
||||
to that workspace:
|
||||
|
||||
```
|
||||
HTTP 402 Payment Required
|
||||
{"error": "workspace budget limit exceeded"}
|
||||
```
|
||||
|
||||
The workspace itself keeps running — only inbound A2A messages and channel
|
||||
sends are gated. Raising `budget_limit` or resetting spend immediately
|
||||
restores traffic.
|
||||
|
||||
**Fail-open behaviour:** if the platform cannot reach the budget record
|
||||
(e.g. a transient DB error), traffic is **allowed through** rather than
|
||||
blocked. The spend ceiling is a soft guardrail, not a hard guarantee.
|
||||
|
||||
### Setting a limit (admin only)
|
||||
|
||||
```bash
|
||||
# Set ceiling to $5.00/month
|
||||
curl -X PATCH https://api.moleculesai.app/workspaces/<id>/budget \
|
||||
-H "Authorization: Bearer <admin-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"budget_limit": 500}'
|
||||
|
||||
# Remove ceiling entirely
|
||||
curl -X PATCH https://api.moleculesai.app/workspaces/<id>/budget \
|
||||
-H "Authorization: Bearer <admin-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"budget_limit": null}'
|
||||
```
|
||||
|
||||
Values must be a positive integer (USD cents) or `null`. Negative values are
|
||||
rejected with `400 Bad Request`.
|
||||
|
||||
### Monitoring spend
|
||||
|
||||
Use the dedicated budget endpoint — financial fields are **not** included in
|
||||
the standard `GET /workspaces/:id` response:
|
||||
|
||||
```bash
|
||||
curl https://api.moleculesai.app/workspaces/<id>/budget \
|
||||
-H "Authorization: Bearer <admin-token>"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"budget_limit": 500,
|
||||
"monthly_spend": 312,
|
||||
"budget_remaining": 188
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `budget_limit` | Ceiling in USD cents, or `null` if no limit is set |
|
||||
| `monthly_spend` | Accumulated spend this billing period in USD cents |
|
||||
| `budget_remaining` | `null` if no limit; otherwise `max(0, limit − spend)`. Can read negative if spend exceeded the ceiling before enforcement kicked in. |
|
||||
|
||||
See the [API Reference](/docs/api-reference#budget) for the full endpoint specification.
|
||||
|
||||
## External agents
|
||||
|
||||
An **external agent** is a workspace with `runtime: external` — it runs on
|
||||
|
||||
Loading…
Reference in New Issue
Block a user