Merge pull request #32 from Molecule-AI/docs/workspace-budget-limit-541

docs(concepts+api-ref): add workspace spend cap — PATCH /workspaces/:id/budget
This commit is contained in:
molecule-ai[bot] 2026-04-20 08:49:07 -07:00 committed by GitHub
commit ff5d83ecd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 121 additions and 1 deletions

View File

@ -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

View File

@ -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