Code Mode
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.
In Code Mode, the Bot writes a short JavaScript program instead of calling App tools one at a time. The program calls the tools, works with their results, and gives back only what the Bot needs. This saves tokens, because large results stay in the program and do not go into the context of the model. The program can also call many tools at once.
Code Mode gives the Bot no new powers. Each App call in a program goes through the same capability check as a normal App tool call. Each call is also a normal App operation.
| Word | Meaning |
|---|---|
| Program | The JavaScript code that the Bot writes. It is the body of an async function. |
| Binding | One App capability that a program can call, such as apps.work_calendar.list_events. |
| Sandbox | A closed place where the program runs. It has no network, no files, no timers and no Node.js. |
| Code run | One program and the record of all its App calls. Read it with GET /v1/code-runs/:id. |
| Replay | A new run of the same program after a decision. Calls that already ran give their saved result. |
Turn on Code Mode
Section titled “Turn on Code Mode”See Set up Code Mode.
How the Bot uses it
Section titled “How the Bot uses it”The Bot gets two more tools. They are next to the normal App tools, and do not replace them.
| Tool | What it does |
|---|---|
openbot_code_api |
Shows the TypeScript declarations of the bindings. The Bot can search them by words or by connection. |
openbot_run_code |
Runs a program and gives back its result, its log lines, and a list of its App calls. |
Each binding has two parts: the connection and the capability. A connection
called “Work calendar” with a tool list-events becomes
apps.work_calendar.list_events. apps.call("work_calendar.list_events", args)
does the same. Resources and prompts become read bindings too. A program could
look like this:
const [today, tomorrow] = await Promise.all([ apps.work_calendar.list_events({ day: "2026-10-23" }), apps.work_calendar.list_events({ day: "2026-10-24" }),])const busy = [today, tomorrow].flatMap( (result) => result.structuredContent.events,)console.log("events", busy.length)return busy.filter((event) => event.attendees > 10).map((event) => event.title)A tool call gives the program the result that the App sent. The program gets an
Error when a call does not work:
| Error name | Why |
|---|---|
AppCallDenied |
A decider did not allow the call. |
AppCallFailed |
The App call failed. details.reason says why. |
AppInputRequired |
The App needs more input. Answer the App operation in details.operationId outside the code. |
VaultDenied |
The call has a Vault reference that the Bot may not use here. OpenBot sent nothing. |
The program can catch these errors and go on.
When a call needs a decision
Section titled “When a call needs a decision”Some App calls need a decision, for example a tool that writes. Then the program
stops at that call. The Turn does not wait: the tool call of the program gets a
pending result at once, and the Bot goes on. The request
has the App call in app, and the program in code:
{ "capability": { "action": "apps.call", "target": "conn-7/send_email" }, "toolCallId": "call-9", "app": { "id": "…", "callId": "call-9:1", "arguments": { "to": "team@example.com" } }, "code": { "run": "8f1c…", "index": 1, "code": "const notes = await …" }}After the decision, OpenBot runs the program again from the top, in the background. Each call that already ran gives its saved result, and OpenBot does not send it to the App again. The program then goes on past the call that waited. When it ends, its result comes back to the Chat as a notice, and the Bot goes on with it.
This works only when the program makes the same calls in the same order each
time. So the program sees a fixed time in Date.now(), and Math.random()
gives the same numbers in each run. If a program makes other calls than before,
OpenBot stops it with ReplayMismatch. This can happen when a connection
changed while the program waited. Nothing is sent by mistake.
Use secrets
Section titled “Use secrets”A program can use secrets from a Vault without seeing
them. The Bot gets vault next to apps. vault.github.token is a reference
such as ⟦vault:github/token⟧, not the secret. OpenBot puts the secret in only
when it sends an App tool call that the item allows. A call with a reference
always needs a decision. The App result comes back with ⟦hidden:github/token⟧
in place of the secret.
Read code runs
Section titled “Read code runs”GET /v1/chats/:chatId/code-runs lists the programs of a Chat.
GET /v1/code-runs/:id reads one. Both use the current read permission of the
source Chat. API keys and session cookies use the same check.
A code run has its business id and an executionId. Its status comes from
the shared execution state. Observe or cancel it through the execution API.
result holds the saved program output. A failure carries a safe category and
error ID; the full cause stays sealed on the server.
calls lists each call in program order. Each entry has index, binding,
app and rejection. app is the App execution view, with its own business
ID, execution ID and saved result. Before that child exists, app is null. A
call rejected before submission has a safe rejection name and message.
Code, arguments and call results stay sealed in the database. Waiting for an approval releases the current Turn’s wait through the shared decision log. The program keeps running as its own execution, and sends a notice when done.
After an upgrade, old programs keep their IDs, saved results, and App call links. You can read them in the same history API. A program with no saved caller or checkpoint ends as interrupted. A write with no known result stays unknown. Old programs do not run again when you read them.
Failure reasons
Section titled “Failure reasons”reason |
Meaning |
|---|---|
Error |
The program threw an error that it did not catch. |
Timeout |
The program used all of its time. |
Memory |
The program used all of its memory. |
OutputTooLarge |
The program returned more data than outputBytes allows. |
ReplayMismatch |
The program made other calls than the last time it ran. |
Cancelled |
The program was stopped. |
Unavailable |
OpenBot could not run or save the program, or Code Mode is off. |