Delegation
For developers who connect a client to OpenBot or extend its server. The examples need the access stated on this page. Start with the developer manual.
Delegation means that a Bot gives a task to a Bot. The task can go to another Bot, or to the same Bot. The work runs in its own task Chat, with its own context, at the same time as other work. The Bot that gave the work does not wait. It goes on, or it ends its answer. When the work ends, the result comes back to its Chat as a notice. So a Bot always stays free to answer people, and much work can run in parallel. Each piece of work has limits: a time limit, and if you like, a limit of model steps and a budget. At a limit the work pauses, and a decider says whether it goes on. This page shows the Bot tools, how the work runs and ends, its limits, and how to watch it and stop it.
The Bot tools
Section titled “The Bot tools”OpenBot gives every Bot these tools:
| Tool | What it does |
|---|---|
openbot_list_bots |
Lists the Bots it can give work to, with their roles. |
openbot_bot_details |
Reads the role and instructions of one Bot. |
openbot_delegate |
Gives a task to a Bot, or to itself. It returns at once. |
openbot_cancel_delegation |
Stops work that its Chat gave away. |
openbot_create_bot |
Makes a new Bot that it owns. See Bots that own Bots. |
Give work
Section titled “Give work”openbot_delegate takes these fields:
| Field | Meaning |
|---|---|
task |
Required. What to do, with everything that the work needs. The other Bot does not see the Chat that gives the work. |
limits |
Required. The limits of the work: timeMs, and if you like steps and budgetMicros. They have the same form as the limits of the work that you read. See Limits. |
bot |
Optional. The ID of the Bot that does the work. Without it, the Bot gives the work to itself. |
reason |
Why the Bot gives this work. It is required for a Bot that it does not own. A decider reads it. |
delegation |
Optional. The ID of earlier work of this Chat. The new task goes on with it. See Add to earlier work. |
The tool returns at once:
{ "delegation": "dlg_9f1e…", "status": "queued", "note": "The work runs on its own. Its result comes back to this Chat as a message. Go on with other work, or end your answer now."}Who can get the work:
- The Bot itself. Leave out
bot. A new helper with the same role and no history does the work. This needs no decision. - A Bot that it owns. This needs no decision.
- Any other Bot. This is the capability
bots.delegate, and it needs areason. The first time, the decider above the Bot must agree. See Requests. While the decider decides, the status isauthorizing. When the work is allowed, it starts. When it is denied, the status becomesfailed, and the Chat gets a notice with the reason.
There is no limit on how deep work goes, or on how much work runs at the same time. A Bot can give work to itself, and that work can give more work. Bot A can give work to Bot B, and B can give work back to A. Nothing waits, so nothing gets stuck. The limits of each piece of work, and budgets and rate limits, stop work that runs too long or costs too much.
Add to earlier work
Section titled “Add to earlier work”To give more work to the same context, send the ID of earlier work as
delegation. The work must come from the same Chat.
- When that work still runs, the new
taskgoes into its task Chat as a message. The Bot there reads it at its next step. The ID stays the same, and the work keeps its limits. - When that work has ended, OpenBot makes a new delegation in the same task Chat, with the new limits. So the Bot there still has all of its history.
Where the work runs
Section titled “Where the work runs”Each delegation runs in a task Chat of the Bot that does the work. The task Chat
has kind: "task" and a parent with the Chat and the delegation that gave the
work. Its first input is a notice with the task, such as
[OpenBot] Assistant gives you this task: …. See Chats.
The work is for the same person as the work that gave it. So the permissions of that person decide what the Bot may do, and requests go to the same deciders. A task Chat belongs to the owner of the Chat that gave the work. In a Channel, that is the actor of the Channel. The owner can read task Chats like their other Chats.
A person can also send a message to a task Chat with
POST /v1/chats/:chatId/messages. The Bot there reads it as a message from the
person.
When the work ends
Section titled “When the work ends”The work of a task Chat ends when one of its Turns ends and the Chat has no
open work. Open work is the work that the task
Chat gave away, its requests that wait for a decision, its reminders, and input
in its inbox. While some of it is open, the work stays running. The results
come back as notices, they start new Turns, and the work goes on.
The end of the last Turn gives the result:
| The Turn ends as | The work becomes | result |
|---|---|---|
Completed |
completed |
The last answer of the Bot. |
Failed |
failed |
The error. |
Interrupted |
cancelled |
A note that the work stopped. |
Then the Chat that gave the work gets a notice, such as:
[OpenBot] The work dlg_9f1e… that you gave to Programmer is done.
Result:Added the endpoint and two tests.The notice goes through the inbox of that Chat. When the Chat is idle, it starts a new Turn, and the Bot goes on with the result. When the Bot gave work to many Bots, each result comes back on its own, as soon as it is ready.
A person can start a new round in an ended task Chat. The message command creates one new delegation and one new Turn in the same database transaction. A retry with the same command key returns the same Turn. The old work keeps its result. The new round keeps the original caller and token limits, plus the limits of all later input. OpenBot checks current rights before each new outside call. When this round ends, the parent Chat gets its result too.
A late notice adds to the history of ended work. It does not start another round on its own.
Limits
Section titled “Limits”Each piece of work has limits. When it reaches one, it pauses, and the decider above the Bot that does the work says whether it goes on.
| Limit | Required | What counts |
|---|---|---|
time |
Yes | The running time of the work, from its start. Time in a pause does not count. |
steps |
No | The model steps of the task Chat. A step is one call to the model. |
budget |
No | The cost of the model calls of the task Chat, and of all the work that it gave away, layer by layer. |
A Bot gives the limits in limits of openbot_delegate, for example
{ "timeMs": 1800000, "steps": 40 }. They have the same form as the limits of
the work that you read: timeMs from 1 second to 30 days,
and steps and budgetMicros from 1. Work from a
trigger, and work from before limits, have no limits of
their own. They get the time limit delegationTimeLimitMs of the
organization, and no other limits. A model without a
price costs 0, so it never uses up a budget. See Usage.
OpenBot checks the steps and the budget before each model step. It checks the time at the end of the time, also while no step runs. Then:
- The work becomes
paused. Its Turn cannot start a new outside call or commit a new Chat step. A call already sent may still return. OpenBot saves its result and confirmed usage, but waits for a decision before using it. - The work that it gave away pauses too, layer by layer. Each piece of it gets
a
pausewithdelegation, the ID of the work at the limit. It does not ask for a decision itself. It waits for the decision about the work above it. - OpenBot checks the capability
delegations.continuefor the Bot of the paused work, like each other request. The target is the ID of the work, andargumentshas thelimit. Usually this makes a request for the decider above that Bot: its owner Bot, or the person at the top.
While the work is paused, new input waits in its saved queue. No new model step starts. A pause from an owner is shown with that owner’s ID. It does not need a second request or a copied pause record. The parent Chat lists the work as paused.
The decider answers the request:
- Allow: the work goes on. Its limits start again from now, so it gets the
same time, steps and budget once more. The
reasonof the decision goes to the task Chat as a message, so the decider can give the Bot more instructions. The work below goes on too. The next step gets this message before its input is saved. A step already sent keeps its saved input and result. - Deny: the work stops and becomes
cancelled, with the work below it. The parent Chat gets a notice that the work stopped. The reason stays on the request. The same happens when nobody answers in time. Known call results stay in history, but the Turn is interrupted and the work has no success result. - Ask: the decider asks a question. This is a deny with the question, so the work stops too, and the parent Chat gets the question with the result.
A decision with the scope chat or lifetime is kept as a Grant. Then the same
work goes on at the same limit without a new request.
Watch the work
Section titled “Watch the work”GET /v1/chats/:chatId/delegations lists the work that one Chat gave away.
GET /v1/delegations/:delegationId reads one piece of work. You see only the
work of your own Chats. Other work returns 404 with DelegationNotFound.
The view has executionId, executionStatus and failure. Business status
and execution state are separate. Read events from
GET /v1/executions/:executionId/observe?afterSequence=0. A cancel first sets
executionStatus to cancelling. The work ends only after its resources and
child work are released. An unknown outside result waits for a decision.
{ "id": "dlg_9f1e…", "executionId": "019c3a6a-7b00-7000-8000-000000000001", "executionStatus": "running", "failure": null, "parent": { "chat": "chat_7d21…", "turn": "t-12", "toolCallId": "call-3" }, "bot": "bot_5c0e…", "chat": "chat_c81d…", "status": "running", "task": "Add the endpoint for the trip list, with tests.", "reason": "The Programmer writes the code of this project.", "result": null, "limits": { "timeMs": 1800000, "steps": 40 }, "usage": { "timeMs": 5000, "steps": 3, "costMicros": 0 }, "createdAt": 1792486800000, "updatedAt": 1792486805000}chat is the task Chat. It is null until the work starts. Work from a
trigger has trigger and a parent with no turn and
no toolCallId.
limits holds the limits: timeMs, and steps and budgetMicros when the
work has them. A budget is in micro-USD: one US dollar is 1,000,000 micro-USD.
usage is what the work used since it started. Its costMicros is the cost
when OpenBot last checked it. Work from before limits has neither field.
Paused work has pause:
| Field | Meaning |
|---|---|
limit |
time, steps or budget. |
request |
The request that decides whether the work goes on. null for the work below. |
delegation |
Only for the work below: the ID of the work above it whose limit was reached. |
at |
When the work paused. |
| Status | Meaning |
|---|---|
authorizing |
The work waits for a request to be answered. |
queued |
The work may start. OpenBot starts it now. |
running |
The Bot works on it. |
paused |
The work reached a limit and waits for a decision. See Limits. |
completed |
The work is done. result holds the last answer. |
failed |
The work failed or was not allowed. result says why. |
cancelled |
The work was stopped. |
Stop the work
Section titled “Stop the work”There are four ways to stop work:
- The Bot calls
openbot_cancel_delegationwith thedelegationID and an optionalreason. Its Chat gets no notice, because the Bot stopped the work itself. - You call
POST /v1/delegations/:delegationId/cancelwith{ "commandKey": "stop-work-1" }. The answer is202with an execution receipt. Keep the key for a retry. The parent Chat gets a notice after release. - You interrupt the Chat that gave the work with
{ "delegations": true }. - You delete the Chat that gave the work.
A cancel stops the running Turn of the task Chat. It also stops the work that
the task Chat gave away, layer by layer, and its requests and reminders. Each
piece of work becomes cancelled.
A normal interrupt stops only the running Turn. The work that the Chat gave away goes on.
Errors
Section titled “Errors”| Status | Error | When |
|---|---|---|
404 |
DelegationNotFound |
There is no work with this ID in your Chats. |
409 |
DelegationConflict |
The work changed at the same time. Read it again. |
503 |
DelegationStorageUnavailable |
OpenBot cannot reach its database. |
See all fields in the Delegations reference.