Bots
Bot permissions and budget
What a bot may do and spend. Permission classes and presets, hand-off, admin locks, approvals and their scope, and the per-run and monthly spend caps, applied the same on every door.
A bot's rules live in two places that are merged for every run: the permissions map in its YAML, and a policy stored on the bot's row. The engine loads the stored policy once per run, so a run by id, a schedule, a trigger, a chat message, MCP, the CLI and Studio all stop at the same holds and caps. A shared or templated bot cannot unlock itself, because locks come from the row, never from the YAML.
Classes and presets
Every tool call has a class: send, write, pay, delete or read. A preset says what happens to a class (or to one tool name):
| Preset | Effect |
|---|---|
allow | The call runs. |
ask | The run pauses and waits for the owner. |
deny | The call does not run. |
hand_off | A deny that does not retry. The call never runs, and the result tells the owner it is theirs to do. Only One Agent understands it; any other runtime gets deny. |
The stored policy covers send, write, pay and delete. In YAML, permissions can also name read and individual tools:
- type: harness
id: bot
agent: one-agent
permissions:
pay: hand_off
send: ask
budget_usd_run: 0.5How the order of checks works is on Policy and review. Two rules to remember: an operation named buy, purchase, fetch_paid, send, transfer, pay, send_payment, withdraw or shop is on the money floor and always ask, even with send: allow, and posture: read_only denies every mutating class whatever the presets say.
Set the stored policy
PUT /v1/workflows/{id}/permissions replaces the fields you send and leaves the others. It is owner-only.
curl -s -X PUT https://api.plungeai.com/v1/workflows/<bot id>/permissions \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"classes":{"send":"ask","pay":"deny"},"budget_usd_run":0.25,"budget_usd_month":10}'| Field | Meaning |
|---|---|
classes | A preset per class: send, write, pay, delete. A class you leave out falls back to the bot's YAML, then to the default. |
locks | Classes an admin locked. A locked class lifts the bot's own allow to ask; it never lifts a deny. |
budget_usd_run | Cap on one run, in USD. A positive number sets it and null removes it; 0 is stored but means no cap. |
budget_usd_month | Cap on a month, in USD. A positive number sets it and null removes it; 0 is stored but means no cap. |
GET /v1/workflows/{id}/permissions returns the stored policy. The same settings are in Studio under the bot's Permissions and Budget fields, on MCP, and on the CLI:
ocean-cli workflows permissions set <bot id> --class pay=ask --lock pay --budget-run 0.5 --budget-month 20- Locks need a team admin over the workflow: the project owner or an admin collaborator, or a company owner or admin. Anyone else gets
403 lock_admin_required, and nothing in that body is written. - An invalid field is
422 invalid_paramsand nothing is written.
How the YAML and the row combine
| Part | Rule |
|---|---|
| Class presets | The two are merged; the YAML's own entry wins per class. |
| Locks | The row's locks replace whatever the YAML carried. |
budget_usd_run | The lower of the YAML's value and the row's wins. |
Approvals
A run that hits an ask pauses with status needs_approval and a continuation that carries the pending action, its price and its class. Answer it with POST /v1/executions/{id}/continue:
curl -s -X POST https://api.plungeai.com/v1/executions/<id>/continue \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"approve":true,"allow_scope":"run"}'allow_scope | The approval reaches |
|---|---|
once (default) | This call only. |
run | The same tool for the rest of the run. |
always | Not written by this call: it behaves as once for the run, and the next gated call asks again. The rule is the class preset stored with PUT .../permissions. Studio's "Allow always for this bot" stores the preset first and then approves; over REST, do the PUT yourself. |
{"answer": "..."} replies to a question, or denies or redirects an approval. An unattended run that hits an ask waits for the owner, up to seven days.
Spend caps
- Per run. When the running token cost passes
budget_usd_run, the run stops at the next turn withstop_reason: budget, and the result says[run stopped at $... per-run budget cap after N turns ...]. The engine applies it on every door. - Per month.
budget_usd_monthis enforced by the scheduler and gates scheduled runs only. A chat message or a web widget visitor is bounded by the per-run cap, which is why a bot behind a public surface needs one.