Skip to content

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 06 Nov 00:44
· 143 commits to main since this release
a03111e

Before upgrading. Forty things change for an existing deployment:

  • Migrations 0066_recorder_extension, 0067_recorder_activity and
    0068_recorder_organization_session_expiry add six tables for the OpenBot Recorder and each
    person's own Learning space (recorder_connect_codes, recorder_connections,
    recorder_trajectory_bindings, learning_person_spaces, learning_skill_sharing,
    learning_open_skills), then nullable columns on the first two. Nothing existing changes, and
    they stay empty until somebody uses Teach a Bot, connects a browser or changes Settings →
    Learning.
  • "Teach a browser workflow" is gone, with its routes under /api/demonstrations, its instant
    Skill draft, "Save and use with this Bot" and "Run on a schedule". Skills saved from it stay, as
    ordinary Skills, and its recordings stay in the database, unread. Record in Teach a Bot or in a
    Bot's browser replaces it (below).
  • Routines is now always on, in every edition. At start, OpenBot adds the Routines app if it is
    missing and gives its tools to every Bot, including Bots made later. It can no longer be removed,
    and a Bot's Routines tools can no longer be taken away (both answer 403). An MCP allowlist never
    refuses it.
  • With BOT_PROVIDER=anthropic and no BOT_MODEL, Bots move from claude-sonnet-4-5, which
    Anthropic retires on 2026-11-30, to claude-sonnet-5-5, and choosing Anthropic in Desktop setup
    writes claude-sonnet-5-5. A BOT_MODEL you set is left alone.
  • A Bot that deletes, sends, shares or posts, on a schedule too, now waits for approval every time,
    in both editions. An Always allow saved for one of these, including one given on a private-share
    card for a send, no longer applies. Through an app, removing something and a scheduled send now
    ask every time, and paying an invoice or order, running payroll, and upgrading, downgrading or
    changing a paid plan are handed to the person.
  • A hosted computer's browser now runs headed by default (full Chromium on the computer's own
    virtual display), which uses a little more memory. COMPUTER_BROWSER_MODE=headless (Helm:
    computers.browserMode: headless) keeps headless.
  • A computer now closes browsers nobody is using when its memory reaches 85% of its limit (in the
    openbot image that share counts the database and the API server too), and a Bot's background
    tabs after ten minutes untouched. COMPUTER_MEMORY_HIGH_PERCENT=0 and COMPUTER_TAB_IDLE_MS=0
    (Helm: computers.memoryHighPercent, computers.tabIdleMs) keep the old behaviour. Helm's
    computers.browserIdleMs and computers.maxBrowsers now take effect on sandbox computers, and
    wherever a value rendered in exponent form (an hour as 3.6e+06) or was 0.
  • Bots now share one computer per person. Migration 0069_isolated_bots adds
    agent_profiles.isolated, off for every existing Bot. The supervisor stops the old per-Bot
    containers when it starts and leaves their volumes for you to remove. A private Bot's logins move
    to its owner (with the supervisor, its files too); people sign in again once to public Bots, and
    on Helm sandbox to every Bot. Helm's computer memory defaults rise to a 2Gi request and an 8Gi
    limit.
  • Computers the supervisor or Helm sandbox runs are replaced or restarted the first time each is
    used after upgrading, to take a token of their own (and, under the supervisor, the permissions
    package installs need). Files and sign-ins are kept.
  • A single-user OpenBot now refuses a request addressed to a host name no setting names and, run on
    your own machine with no address beyond it configured, any other machine. Set
    OPENBOT_PUBLIC_URL, OPENBOT_APP_URL or TRUSTED_ORIGINS to the address other machines use, or
    configure sign-in. A page on another origin that calls OpenBot from a browser must be listed in
    TRUSTED_ORIGINS.
  • A Bot's endpoint no longer receives Authorization or x-* headers from the person's request. A
    remote Bot that read one must be given it as one of the Bot's own headers.
  • In the openbot image, sudo apt-get and the other package commands are refused. A Bot uses
    sudo openbot-apt install <package>, as its tool description now says.
  • Migration 0054_memory_on_intelligence adds two columns to personal_memories. On a deployment
    entitled to Intelligence memory, the memory sweep then copies existing reviewed, switched-on
    memories into Intelligence, a few people at a time each minute; it is idempotent and safe to
    interrupt. Memories awaiting review, paused or forgotten stay local until that changes.
  • With nothing saved under Admin → Automatic Learning and no
    CPK_INTELLIGENCE_LEARNING_CONTAINER_ID, the server assigns the project's openbot Learning
    container once it exists (below). A saved setting, including off, is never changed;
    OPENBOT_LEARNING_AUTO_ASSIGN=false (Helm: config.learning.autoAssign: false) prevents it.
  • Migration 0055_default_opt_outs adds default_opt_outs, where a person's removal of a default
    memory source or check-in is recorded. Each app a person connected with their own account
    then becomes a memory source for each of their Bots that can read it (below). Only removals from
    now on are recorded: a source somebody deleted before the upgrade comes back on that Bot's next
    turn for them (one turned off and kept stays off). OPENBOT_MEMORY_SOURCE_DEFAULTS=false (Helm:
    config.memory.sourceDefaults: false) prevents it.
  • Migrations 0071_proactive_check_ins, 0072_check_in_read_labels and 0075_individual_check_ins
    add the check-in tables, mcp_tools.declared_read, and proactive_settings.subject and
    source_link, and let a Bot hold more than one check-in. Background research already set up keeps
    running as a check-in on everything the person connected (below). It now reads only apps they
    connected with their own account, is skipped when the Bot can read none of them, and what it finds
    reaches them through Needs you and one message an hour instead of a message per suggestion. No
    check-in is switched on by itself unless OPENBOT_PROACTIVE_DEFAULTS=true.
  • Migration 0070_memory_dedup_per_bot rebuilds one unique index on personal_memories to include
    the Bot that formed the memory (below). It keeps every row. While it rolls out, a server still on
    0.2.0 fails to save a newly formed Bot memory until it is replaced; nothing else is affected.
  • OpenBot Desktop now sends OpenBot's product usage events by default, under the telemetry notice it
    already shows (below). Launching it with DO_NOT_TRACK=1 or COPILOTKIT_TELEMETRY_DISABLED=1
    turns them off with the rest of its telemetry.
  • Migration 0060_app_access_requests adds one table, app_access_requests, which keeps people's
    requests that an admin switch on an app, grant it to a Bot or connect a Shared app's team account.
    It starts empty.
  • Migration 0073_entra_scope adds a nullable mcp_servers.entra_scope. Servers without it behave
    as before.
  • Migration 0074_single_user_identity adds the brokered_identities table and lets
    pending_authorizations.action be null. A single-user deployment on Composio connects each of
    its apps once more after upgrading.
    At its next start it gets its own user id at the broker
    (below), and its connections recorded under dev-local-user are removed from OpenBot, because no
    broker can move a connection to another user id. Nothing is ended at Composio, since other
    OpenBots on the same key may be using those accounts. Deployments that sign people in keep their
    ids.
  • Behind an OPENAI_BASE_URL that is not OpenAI or Azure, the built-in Bots now reach most
    openai/... models through Chat Completions instead of the Responses API (below). A gateway that
    serves only the Responses API needs COPILOTKIT_OPENAI_API=responses.
  • Interfaces a Bot drew before the upgrade (A2UI 0.9) no longer draw when a conversation is
    reopened. The messages around them stay.
  • With both COMPOSIO_API_KEY and ARCADE_API_KEY set and no OPENBOT_BROKER_PROVIDER, the server
    now refuses to start. Set OPENBOT_BROKER_PROVIDER=composio, or unset the Arcade key. Migration
    0058_arcade_provider_backfill also marks Personal any Composio app that holds accounts but had
    no account mode, which the server used to refuse to call.
  • With both CPK_INTELLIGENCE_API_KEY and INTELLIGENCE_API_KEY set, the server now uses
    CPK_INTELLIGENCE_API_KEY. If that one is left over from an older project, remove it.
  • A source checkout with no COMPOSIO_API_KEY and no Arcade key now has scripts/start.sh create a
    Composio project and write its key to .env, with .env.openbot-installation-id naming the
    installation to CopilotKit. Keep that file across restarts and never copy it to another
    installation.
  • OpenBot Desktop for Mac installed from v0.2.0 or earlier cannot update itself. Install a signed
    .dmg from this release by hand once; the app it installs offers later releases itself. Building
    the Desktop app from source now needs Rust 1.90 or newer.
  • Migrations 0061_suggestion_superseded to 0065_owner_shared_audience only add to existing
    tables: a superseded status for proactive_suggestions, an empty action_policy.labels
    (existing boundary rules show as "Custom rule" until named), an index on work_items, and the
    nullable mcp_servers.owner_grants, plugin_grants.granted_as and
    shared_use_approvals.approved_as.
  • On one shared computer (Helm computers.mode: shared, or no supervisor), each person's files for
    each Bot now live in computers/<person id>~<Bot id> on the workspace volume (below). Files saved
    before the upgrade stay at the root, where no Bot sees them; move them into a folder to keep them.
  • Migration 0056_sign_in_code_step widens the check on sign_in_requests.status to allow
    code_required. Upgrade a server and its computers together: an older computer has no
    /sign-in/code.
  • A "Never submit a form" boundary rule added before this release still reads key == "Enter", so
    Shift+Enter, Control+Enter and Space get past it. Add the preset again, or change key == "Enter"
    to intent == "activate" in your copy (and in AGENT_COMPUTER_POLICY if you took the line from
    .env.example).
  • The built-in safety rules now hand a refund to the person. A Bot that issued refunds on its own,
    for example with SHOPIFY_CREATE_REFUND, hands each one back to you instead.
  • With OPENBOT_SINGLE_USER=true, an address with no scheme (such as example.com:3010) in
    OPENBOT_PUBLIC_URL, OPENBOT_APP_URL or TRUSTED_ORIGINS now stops the server starting instead
    of only warning. Write it with its scheme so it is judged by its host.
  • A Bot on the Claude Agent SDK harness starts a new Claude process for every turn instead of
    keeping one per conversation, so each turn starts a little later (about a second or two), and a
    conversation past about 100,000 tokens now leaves out its oldest messages.
  • Picking the Bot for an untagged message from home now calls claude-haiku-5-5 (Anthropic) or
    gpt-6-luna (OpenAI) on the deployment's key, and falls back to the Bots' model when the provider
    refuses it. ROUTER_MODEL names another. Behind OPENAI_BASE_URL or ANTHROPIC_BASE_URL, or
    with a model allowlist that leaves it out, routing stays on the Bots' model.
  • Admin → Enterprise controls is gone: /admin/enterprise opens the Admin overview, and its
    switches are on Permissions, Providers, Identity providers, Computers, Plugins, Automatic learning
    and Audit (below).
  • OpenBot Desktop no longer connects to a hosted or self-hosted Intelligence. An installation set
    up that way opens at the first screen and sets up Intelligence on this computer, keeping its
    install, database and Bots; its conversations stay in the old Intelligence.
  • On a Podman machine an earlier OpenBot Desktop made, local Intelligence will not start until you
    stop the machine, run podman machine set --rootful and start it again. OpenBot's data stays.
  • The Helm chart now installs its own Intelligence. A release on managed Intelligence or one of its
    own needs --set intelligence.enabled=false; until then the chart refuses the upgrade,
    --reuse-values included. routines.enabled now defaults to true, and a changed value under
    secrets restarts the server.
  • Migration 0077_channel_opening adds a nullable channels.opening.

Teach a Bot: do a task once in your own browser, and your Bots learn it

The OpenBot Recorder is a Chrome and Edge extension that records a task while a person does it and
sends it, masked in the browser first, straight to Intelligence as a trajectory in that person's
own Learning space. Every Bot they use then gets the Skills learned from it (next section). OpenBot
hands out a pass for each recording and is told only that one is running: its name, when it started
and how many steps it has. Teach a Bot in the sidebar is where a person starts, in every
edition, and it carries a red dot named for the recording while any of their Recorders records.

  • On OpenBot Desktop, and in the personal edition, Teach a Bot opens your installed Google
    Chrome (or Microsoft Edge where Chrome is missing) as a window on your desktop, in an OpenBot
    profile of its own, with the Recorder in it and already connected as you. The page takes you
    through three steps: what it does, the task's name and narration, then Open browser and
    record
    . Stop from the Recorder's mark at the top of a recorded page, which asks whether to save
    or discard, or with Stop on the page, which says what was saved. Without Chrome or Edge (a Flatpak
    build does not count), Desktop shows a browser inside the page instead and says why.
  • On hosted OpenBot, Teach a Bot is the computer's Chromium shown in the page, with tabs, an
    address bar, back, forward, reload and Record. It runs on a computer of its own (a supervisor's
    or a Sandbox), only you can see or use it, and it reaches the open web, OpenBot and the
    Intelligence gateway, never anything else inside the deployment. Where every Bot shares one
    computer, Teach a Bot is hidden.
  • In a Bot's own browser, with nothing to install: open the Bot's screen full size, take control
    and press Record. Only you are recorded: handing control back, or the Bot's request ending, stops
    the recording, and the Bot acts again only after it. Each recording gets its own Recorder
    connection, ended at Stop. Record works where each person has a computer of their own (the
    supervisor, which OpenBot Desktop and laptops use, or a Sandbox) and in the personal edition.
    Where every Bot shares one computer (Helm shared, or AGENT_COMPUTER_URL with no supervisor),
    it is refused and the panel says why: another Bot's shell could read the Recorder's connection
    there.

What a recording holds is said before the first one, in the Recorder, in Settings → Browsers and in
Teach a Bot: every tab you use until you stop (each shows that it is recorded, with a button that
leaves that site out of this and every later recording), the pages and what is on them, your clicks
and typing, the request and answer of a step that saves something, the conversation with an AI
assistant on the page that speaks AG-UI (OpenBot's own chat included, your messages too), and your
narration once you turn it on, turned into text on your own computer. Passwords, one-time codes,
secret fields, card numbers, email addresses, keys and sign-in tokens are masked in the browser
before anything leaves it; names, phone numbers and amounts are not.

Settings → Browsers connects the Recorder in the browser you are using and lists every browser
connected as you, a Bot's browser during a recording included, each with a Disconnect that signs
its Recorder out at once. A connection also ends when your sign-in ends (signing out, signing out
everywhere, an admin removing you or requiring SSO, your organization's session ending) or after it
goes unused as long as a sign-in lasts, and a recording kept for a person who never connects again
is deleted unsent after 7 days. Connecting to another OpenBot, or as another person, always asks
first. The Recorder is not yet in the Chrome Web Store or Edge Add-ons; until it is, OpenBot Desktop
does not add it to your everyday Chrome or Edge.

A personal edition for one person on one computer

OPENBOT_EDITION=personal runs OpenBot for one person on their own machine, without the screens
that govern other people. Unset, or team, is OpenBot as before. scripts/use-personal-edition.sh
writes its settings into .env, and scripts/start.sh then starts one computer, no Bot containers
and one Personal Assistant, which runs inside the server. The server refuses to start, naming the
variable to change, unless OPENBOT_SINGLE_USER=true, no sign-in provider or
OPENBOT_ORGANIZATION_AUTH_URL is set, and AGENT_COMPUTER_URL names the one computer with no
COMPUTER_SUPERVISOR_URL or COMPUTER_SANDBOX_NAMESPACE.

  • Ordinary changes run without asking. Ask before making changes is off until the person turns
    it on, at onboarding or in Settings → Approvals, and a choice already saved is kept. Paying and
    buying are handed to them, and deleting, sending, sharing and posting ask, as in the team edition
    (below).
  • Web search works from the first start. The personal edition adds Parallel Search, Parallel's
    free web search with no key, once, so every Bot, including ones made later, can search the web and
    read pages; queries and the addresses of pages a Bot reads go to Parallel. Turned off, it stays
    off (personal_default_apps in enterprise_settings remembers it). Startup does not wait for it:
    offline, the server asks Parallel for its tools again in the background and on each start until it
    answers.
  • Connections is the one place for apps. One press connects an app and gives every Bot every
    tool it lists, writes included; an app cannot be taken from one Bot. A Bot that needs an app not
    yet connected offers Connect in the conversation, which turns it on and opens its sign-in.
  • Fixed, with no screen to change them: local computer access, Slack and Teams, Team Bots,
    Isolated Bots, SSO, action recording, model and MCP allowlists and Skill sharing are off, and the
    computer is never stopped for being idle. Values a team deployment left in the database are not
    read.
  • The menu is Bots, Connections and Settings. Settings holds saved logins, keys, Skills,
    Computer and Activity. Admin becomes OpenBot for Business, a short list of what the team edition
    adds, and the API behind team-only screens answers 404 (people, identity providers, enterprise
    controls except /api/admin/enterprise/me, the admin learning and check-in settings, SCIM, SSO).
  • Teach a Bot works here too. scripts/start.sh starts your installed Chrome or Edge with the
    Recorder in it, as OpenBot Desktop does (scripts/stop.sh stops it), and starts the one computer
    as yours (COMPUTER_PERSON_ID), so Record works in your Bots' browsers.

See docs/personal-edition.md.

OpenBot Desktop runs CopilotKit Intelligence on your own computer, set up from the app alone

OpenBot Desktop now sets up OpenBot for one person with CopilotKit Intelligence on the same
computer: Account (sign in to CopilotKit and accept the Intelligence evaluation licence), Install,
AI and Start. Install starts on the first click and shows each image's size and speed. The installer
carries a CopilotKit CLI preview (copilotkit 4.33.1-pre.256cc68, pinned in
desktop/intelligence-cli.json) that installs Intelligence 0.16.0-rc.5, a release candidate and the
same release the Helm chart installs, and its Node, checked against pinned digests. The CLI runs the
Intelligence; sign-in shows OpenBot's own page when the CLI offers it. OpenBot opens once
Intelligence is ready with a valid licence, and the app warns five days before the licence expires.

  • Only this Intelligence. No hosted or self-hosted option, organization sign-in or Bot picker.
  • OpenBot runs in containers, from the published openbot-server image, not as Bun processes
    the app installed. New installations are Compose project openbot-desktop, apart from a source
    checkout's; an earlier one keeps its project and data.
  • A new Podman machine is rootful, as Intelligence needs. On Windows, setup turns on WSL 2 with
    one prompt and a restart, and says when the machine cannot run it.
  • Settings → This computer, in the Desktop app only, shows each part's memory, with Restart,
    Stop and Copy diagnostics. Stop and Quit stop Intelligence too, if the app created it.
  • Apps need your own COMPOSIO_API_KEY in ~/OpenBot/.env.

Deleting, sending, sharing and posting always ask first

In both editions a Bot now asks before it deletes or removes something, sends to other people (a
scheduled send too), shares or posts, whatever Ask before making changes, a rule or Auto-review
says. The card offers Allow once or Deny: Always allow is not offered, and the server refuses one.
Paying and buying are still handed to you to do yourself, and now also cover an app tool named for a
transfer of money, a payout, withdrawal, charge or order, paying an invoice or order, a payroll run,
an app subscription, or upgrading, downgrading or changing a plan, and buttons such as Transfer,
Place your order, Send $20, Upgrade, Change plan, and Subscribe for a paid plan. Moving a
repository or ownership is not a payment.

The four are recognised only by what the action is called: an app tool's first verb
(GMAIL_SEND_EMAIL, GOOGLEDRIVE_DELETE_FILE), so marking a comment as spam, liking a post,
cancelling a scheduled send or a request named for the web's POST method
(STRIPE_POST_V1_PLANS_OVERVIEW) is neither a send nor a post; or a browser button whose label
starts with the action (Delete, Send, Share, Post) or is exactly Remove, Comment or Reply. Removing
somebody's access asks as sharing. An app call with an email address in to, cc or any argument
named for attendees, invitees or guests is a send, so a meeting invitation asks through every
calendar app, and so are Cmd+Enter or Ctrl+Enter, and Enter in a box for a message, reply or
comment. Drafts, edits, labels, events with no guests, a Bot's memory, and files and commands on its
own computer stay ordinary. An action whose tool or button doesn't name it (an unlabelled icon,
trashing mail by relabelling it) isn't caught: Auto-review, Ask before making changes or a rule
covers those.

You are asked once: a connector send asks on the send itself, not on a private-share card first,
and a Bot OpenBot runs is told not to put its own approval card in front. See docs/approvals.md.

Each person's Bots share one computer, and a Bot can have its own

Every Bot used to have a computer of its own: on a laptop or Desktop a container per Bot running all
the time, and for a public Bot one set of logins shared by everyone who used it. Now all the Bots a
person uses run on that person's one computer, each with its own browser profile and folder, so a
login belongs to the person and the Bot. Tick Isolated when making a Bot, or in its Setup, to
give it a computer of its own; everyone who uses that Bot shares it and whatever is signed in there,
which its Setup says. Admin → Permissions → Isolated Bots decides who may: on for everyone
by default, or limited to roles and groups. A Bot whose owner loses the switch runs on the owner's
computer from its next run, and its Setup says why. A deployment with one shared computer (Helm
shared, or no supervisor) cannot isolate a Bot, and the checkbox says so.

The Bots on one person's computer are not kept from each other: a command any of them runs, a public
Bot somebody else made included, can reach anything on that computer. Admin → Computers says so; a
Bot that must be kept apart is made Isolated. Removing a person or deleting an isolated Bot leaves
its computer until an administrator resets it there, which signs every Bot on it out.

With the supervisor on a shared network (Compose), a computer whose container name is longer than
DNS allows (63 characters, which a longer COMPUTER_NAMESPACE reaches) is now reached by its short
container id. Before, it started healthy and the server could not reach it.

OpenBot answers only its own pages, and a single-user install only its own machine

  • Other websites. The CopilotKit runtime answered CORS for every origin, so a page on any
    website could use OpenBot through your browser. It now answers only OpenBot's own origins (the
    public and app URLs, TRUSTED_ORIGINS, and on a single-user install the app's port on loopback
    and on each configured host) and the Recorder. A request that changes something, and every
    WebSocket, from another site's page is refused with 403 in every auth mode. Requests with no
    Origin and no Sec-Fetch-Site (webhooks, the worker, Bots calling back, the mobile app, CLIs)
    are unaffected, and /api/auth/* keeps Better Auth's own check.
  • Single-user installs (OPENBOT_SINGLE_USER=true, OpenBot Desktop among them) admit the
    administrator only for a request addressed to localhost, a loopback address or a configured
    URL's host, which stops DNS rebinding; in a container, or once an address beyond this machine is
    configured, any IP address too. Run on the person's own machine with no such address, only a
    caller on that machine is admitted, and app/serve.ts and the Vite dev server answer only that
    machine. OpenBot's images set OPENBOT_IN_CONTAINER=true, so containers keep admitting callers
    through the container engine.
  • Computer tokens. Each person's computer and each isolated Bot's, under the supervisor or Helm
    sandbox, now holds its own token derived from COMPUTER_TOKEN, so a Bot that reads its
    computer's token can open that computer and no other. The one shared computer is unchanged.
  • A Bot's endpoint no longer receives Authorization or any x-* header from the request of
    the person running it. The Bot's own key and the deployment's agent token are still sent.

Bots offer check-ins and tell you when something needs you

Background research is now check-ins, and a Bot asks before it starts one. After it reads something
that keeps changing (a document, a project page, your inbox), it can offer a check-in on that one
thing: a card in the conversation with Turn on, Not now and how often (once a day unless you pick
twice a day, every four hours or every hour). Nothing runs until you accept. A Bot can watch several
things, each listed by name on its Memory page.

A check-in only reads, only in apps the person connected with their own account (never a Shared
app's team account or the deployment's own key), and only what is new since the last check-in that
read everything. When something needs the person it raises an item saying why and where it came
from; when nothing does, nothing is sent. The person hears at most once an hour, with every Bot's
items in one message: in Needs you with a badge and a browser notification, by phone push, and on
Slack, Teams or SMS where they already talk to a Bot. Work on it opens a task in a new conversation
with that Bot, which asks before every send or change. Dismiss keeps an item from coming back from
any of their Bots for 30 days.

Settings → Notifications holds a Check-ins switch for all of a person's Bots and "Something needs
you", where these messages go. Admin → Check-ins turns them off for everyone, sets the daily limit
(24 a day per Bot for each person) and shows counts per Bot. OPENBOT_PROACTIVE_DEFAULTS=true
switches on an hourly check-in on everything a Bot can read the first time a person talks to it
alone.

Each app on a Bot says whether you can use it, and you can ask an admin for the rest

A Bot's page now says, for every work app the Bot holds, what that app is for the person looking:
ready, not connected yet, needing reconnecting, waiting on the team account or on an admin's
approval of who the Bot reaches, not switched on or not set up, or not given to this Bot. On
somebody else's Team Bot it also says whose account the app runs on. Each state comes with the one
step that fixes it: Connect or Reconnect your own account, or Request access where only an admin
can help.

In a conversation, a Bot that needs an app it cannot use puts the same card in front of you instead
of a sentence, including, through a new show_app_access tool every Bot holds, an app that is
switched off or not granted to it. The card reads the app's state when it is drawn, so it never
offers a step already taken. Connect on it opens the vendor's consent in a new tab, so the
conversation stays where it was, and it says connected only once the server lists the account. Each
app's card is drawn once per turn.

Admins see waiting requests at the top of Plugins, a count on each app's row and a Requests section
on the app's page. Approve switches the app on and grants the Bot the app's read-only actions in one
press (or says what has to happen first), and Dismiss closes the request. A person may have 20
requests waiting at once.

An account the vendor has lost now reads as lost once a call finds out: a Composio account the
vendor no longer holds is removed so it can be connected again, and a Google Drive or Notion
connection whose refresh token is refused is withdrawn, with Reconnect on the card. Without a
COMPOSIO_API_KEY, Connections no longer lists Composio apps nobody could connect.

Every Bot gives parts of its work to subagents

A Bot, built in or an AG-UI agent somebody brings, can hand a piece of its work to a subagent: a
fresh run of the same Bot, as the same person, with the same tools, approval rules and standing
instructions, that sees none of the conversation and hands back only its answer. Bots are told to
use subagents only when a request falls into several independent parts that each need a lot of
reading (up to five for one answer), to do a single question or search themselves, and to keep
decisions and the answer. A subagent never waits on the person: an action that needs approval is
handed back, and the Bot asks in the conversation. A subtask is a step in the Bot's work line beside
its searches ("Searched the web, read 5 pages and ran 2 subtasks") and opens to its task, its
answer and its steps; one that stopped before an approval says Handed back. OpenBot runs the tool
itself, so a remote agent needs no change.

People can pick the model a built-in Bot answers on, per conversation

A brain icon in the composer opens a searchable list of models, read from the provider, or from the
gateway in OPENAI_BASE_URL, filtered to chat models and to the model allowlist. It is offered in a
conversation with one built-in Bot, and on a new conversation before its first message. The pick is
kept on the server per person and conversation (migration 0076_model_selection adds a nullable
column), every run there uses it, routines too, and the allowlist and usage records judge the
picked model. Off by default in the team edition: an admin turns on "Let people choose a model"
under Admin → Providers. Off, every run uses the configured model and saved picks are kept. Always
on in the personal edition. Bots on their own framework, and built-in Bots on a ChatGPT or Claude
subscription, keep their own model.

The composer is now two rows: the message box, then attach and the model on the left, dictation and
send on the right. It draws no focus ring, and opening a conversation puts the caret in it.

OpenBot Desktop installers are signed and at fixed links, and the Mac app updates itself

Each release carries OpenBot Desktop for Mac (Apple silicon and Intel), signed with a Developer ID
and notarized so macOS opens it without Open Anyway, and for Windows x64, code-signed. The newest
stable release's are at https://downloads.copilotkit.ai/latest/ and
https://github.com/CopilotKit/OpenBot/releases/latest/download/, as OpenBot-mac-arm64.dmg,
OpenBot-mac-x64.dmg and OpenBot-windows-x64-setup.exe. A release becomes Latest only once its
signed installers are on it, so these never give an unsigned build or a candidate.

The Mac app checks GitHub's Latest release when it opens and offers Update and restart. Nothing
downloads until you press it, and an update not signed with OpenBot's update key is refused.
Windows has no updater. A release candidate is a GitHub prerelease, linked by its version; its app
has no updater and always runs its own release, laid over the folder's installation, data kept.

An installation moves to a newer release only when you ask, with a backup first

A normal start changes nothing: each installation keeps its release until you move it. In the
Desktop setup window, Runtime updates takes a release tag, checks the move is allowed and
downloads the release while OpenBot keeps running; once you accept the downtime it stops OpenBot,
backs up the database, swaps the files and starts the new release, and puts the previous one back if
that fails (with Restore previous release when the database changed). In a source checkout, bun scripts/update.ts local plan and apply do the same for Docker Compose or host processes, and
scripts/start.sh will not start a checkout whose update was interrupted. On Kubernetes, bun scripts/update.ts kubernetes plan, apply and gitops move a Helm release to the new chart and
image digests, keeping its values, pausing scheduled work and running your backup command first.
Each checks the release's published release-manifest.json against a policy you keep, so they start
from a release that published one: this one onwards, not v0.2.0. The server answers /release with
its version, commit and repository to a caller on its own machine, and the chart adds
routines.suspend. See docs/desktop-updates.md, docs/local-updates.md,
docs/kubernetes-upgrades.md and, for a fork, docs/upstream-upgrades.md.

Arcade can be the deployment's app provider instead of Composio

Set ARCADE_API_KEY and an admin can enable Arcade's apps from /admin/plugins/arcade, give their
actions to Bots, and let each person connect their own account, as with Composio. A deployment uses
one provider at a time: with both keys set, OPENBOT_BROKER_PROVIDER (composio or arcade) says
which, and switching is refused while the other still holds connections. An app's page says how many
permissions its actions ask for, and warns when Arcade has not said which only read, so every call
will ask for approval. Each Arcade provider needs an OAuth app of your own. On every deployment, an
action whose name no model accepts is listed on its app's page instead of failing the app, and a
call to Composio or Arcade from a screen gives up after 10 seconds. Migrations
0057_arcade_provider, 0058_arcade_provider_backfill and 0059_skipped_tools add
pending_authorizations, mcp_tools.vendor_name and mcp_servers.skipped_tools. See
Arcade.

After a consent, Arcade sends the person to its project's user verifier, which must be this
deployment's /api/plugins/arcade/verify. A single-user install points it there through Arcade's
API before each Connect and reads it back, so several on one key take turns. A deployment that signs
people in treats the project as shared and never changes it: until it names this deployment, Connect
is refused before any consent screen, with the exact address to set in Arcade's dashboard, which the
server also names at start. See Arcade.

An Arcade app has Connect, under Connections and on a Bot's app card, which asks Arcade for
everything the app's actions need in one consent; the action list still approves one action at a
time. Every Arcade connection is recorded in OpenBot, and a Bot's call needs it, as on Composio. A
consent that exists only at Arcade is recorded, with no new consent, when the person opens the app
under Connections or presses Connect. A call Arcade refuses shows the Reconnect card, and Reconnect
asks for the whole app again with no Disconnect first. The connection is cleared only when Arcade
holds nothing for the app, so actions already approved keep working. See
Arcade.

First run takes three clicks at most, and Settings → Setup says what is missing

  • Onboarding is an animated Welcome with the Ask before making changes switch (as in Settings →
    Approvals); then, on a single-user OpenBot (OPENBOT_SINGLE_USER=true, Desktop included), Talk
    to us, whose Book a call opens CopilotKit's booking page; then the person's name, unless sign-in
    gave one. The step is saved as they go, so a reload or browser Back keeps it.
    /api/capabilities gains localEvaluation, true in single-user mode.
  • A conversation offers notifications once, after the person's first message.
  • New Settings → Setup, for admins and single-user installs, reads the new GET /api/setup: whether
    Intelligence, the model key, the Bot computer, more apps, and Slack and Teams work, with the page
    or variables that close each gap. The Settings sidebar counts what an admin can fix. Check again
    asks again, at most every 10 seconds; no computer is woken to answer.

A Bot's owner can give it their own apps

The owner of a Bot can let it use an app they connected themselves, without an admin: from the app
card ("Let Research Bot use your Gmail?"), under Your apps on the Bot's page, or on the New Bot
sheet. Calls under such a grant use the asking person's own account, even on a published Team Bot.
By default this covers apps each person connects for themselves, and Routines; an admin decides per
app, can let owners give a Shared app's team account, and can take away the "Give apps to their own
Bots" capability. Once an app a Bot stopped for is ready, its card sends "Gmail is ready now. Please
carry on." and the Bot resumes; an admin sees the fix itself, such as "Turn on and give it to
Sales".

Each person's recordings teach every Bot they use, and they choose whose Bots learn too

Every recording joins the person's own Intelligence Learning container, user- and 32 hex digits
derived from their OpenBot user id. OpenBot makes it when they first connect a browser, with the
credential that makes the openbot container: a service token on a CopilotKit local evaluation
install, or a CopilotKit CLI login against managed Intelligence (Intelligence's runtime key
cannot). Without one, a missing space cannot be made, and the Recorder says Learning will not see
the recording. A run now gets the Bot's own learned Skills, then the person's own, then any another
person opened; on a name clash the Bot's own wins, then the person's. A missing or unreachable space
never fails a run, and Automatic Learning off, or the Bot excluded, still means no learned Skills.

Settings → Learning chooses who else's Bots use a person's Skills: only their own (the
default), every Bot in the deployment, or Skills they open one by one. Opening any needs Admin →
Automatic learning → Skill sharing
, off by default; while it is off nothing is shared, and each
person's choice is kept for when it is turned on.

For recordings to reach Intelligence it needs Product Trajectories (in its licence, and
FF_LEARNING_TRAJECTORIES), people's browsers must reach INTELLIGENCE_GATEWAY_WS_URL, and the
address the Recorder reaches OpenBot at (OPENBOT_PUBLIC_URL, else BETTER_AUTH_URL or the app's
own) must be https, or http on 127.0.0.1 or [::1]. OPENBOT_RECORDER_EXTRA_EXTENSION_IDS
trusts other Recorder builds (a fork, another signing key): each 32 letters from a to p, comma
separated, and any other value stops the server at boot. OPENBOT_BROWSER_URL runs people's own
browsers on a separate computer, as OpenBot Desktop and the personal edition do. A computer puts
the Recorder from OPENBOT_RECORDER_DIR (its image sets it) in the browsers it opens, and without
it refuses to open a person's browser. A tenant package can no longer name a Bot person- and 32
hex digits, the shape of a person's browser. Audited as recorder.connected, recorder.revoked,
recorder.connect_refused, recorder.bearer_refused, recorder.trajectory_refused,
recorder.sign_out_failed, learning.sharing_changed, learning.skill_opened and
learning.skill_closed.

A Bot's screen is a browser you can work in

A Bot's screen, opened full size, now has tabs, an address bar, back, forward and reload above the
page. Take control and the bar is yours, from the keyboard too; while the Bot has control it shows
where the Bot is, read only. Taking control now records who took it, so on a shared Bot only that
person can use the bar or record.

  • What you type stays yours. After you hand back, the Bot reads a field you filled as "filled
    in by the person", never its value, in its page snapshot, its page text and its screenshots.
  • A Bot's request for help shows in the conversation, with its reason and Take control, and
    folds into one line once answered. After you hand back, the Bot is told what you did and says what
    it does next; if it still stops without a word, the card says so and offers Carry on.
  • Clear browser data, beside reload in a Bot's browser and in your own, asks "Clear this
    browser?" and deletes its profile: cookies, saved passwords, history and site storage, so it
    starts signed out of everything. OpenBot's own Passwords stay. Only the person holding a Bot's
    control can clear its browser. Audited as computer.browser_cleared.
  • Leaving a conversation while you hold a Bot's control no longer leaves it blank: the Bot's reply
    after you hand back is there when you return.

Enterprise controls moved onto the Admin pages each part governs

What people may do is on a new Permissions page; the model switch, the model allowlist and model
usage are on a new Providers page; Require SSO and SCIM are on Identity providers; the network
policy and stopping idle computers are on Computers; the MCP server allowlist is on Plugins; Skill
sharing is on Automatic learning; Action Recording and the OpenTelemetry export are on Audit. An old
link to Enterprise controls opens the Admin overview. The model allowlist is now picked from the
provider's own list, searchable, with the configured model marked; a model the list lacks, such as a
remote Bot's, is added by its provider/model name.

A message sent from home opens its conversation at once

Pressing send on the home page opens the conversation straight away, showing the message, while a
Bot is picked and the conversation is made behind it. The real conversation replaces it if you are
still there, and Back opens it if you left. A start that fails returns you to home with the message
in the box and the reason under it. Picking the Bot for an untagged message now runs on a lighter
model (see Before upgrading), and a message that names its Bot with @ no longer waits for the
audit record of that choice.

Built-in Bots know today's date, and a run always ends with an answer

Built-in Bots are told today's date (UTC), so "this quarter" or "last week" no longer starts a guess
or a date command that waits for approval. The last step a built-in Bot's run is allowed is sent
with no tools, so a Bot that spent its steps searching answers from what it found instead of
stopping with nothing said. Every Bot is told to draw a table or chart once, with its result.

The Helm chart installs CopilotKit Intelligence with OpenBot

By default (intelligence.enabled: true), one helm install with secrets.licenseToken and
secrets.modelApiKey is the whole product: Intelligence 0.16.0-rc.5, a release candidate,
self-hosted on its own PostgreSQL, Redis and object store. Its bootstrap creates OpenBot's project
and openbot Learning container, with a runtime key the chart generates, and nobody signs in.
Automatic Learning, on secrets.modelApiKey with openai/gpt-5.5, and trajectories are on. The
server carries the browser's socket to Intelligence's gateway (INTELLIGENCE_GATEWAY_PROXY=on), so
one kubectl port-forward reaches everything.

  • With the bundled database the chart generates the database password, secrets.keyEncryptionKey
    and secrets.computerToken, reads them back on upgrade and keeps them on uninstall. helm template (Argo CD) cannot read them back, so set them there.
  • Every workload that reads the database waits for it and its migrations, so --wait works on a
    first install.
  • The Ingress, and a LoadBalancer server Service, carry the tag
    aws-apn-id-058264219587=pc:avjdxr516uh0mgsqob730tzgy, which credits CopilotKit through AWS
    Partner Revenue Measurement. No setting turns it off.

A Bot asked by another Bot answers in its own name

The asked Bot is told who it is and which Bot asked, and gets your conversation as quoted context.
Its answer appears in your conversation word for word under its own name, not retold by the Bot
that asked. Bots are matched the way people say them ("the Knowledge Bot"). An answer for a busy
conversation waits without using up its retries, and one never delivered is audited as
agent.handoff_failed.

Fewer "Verify you are human" pages

Sites behind Cloudflare challenged a Bot's browser on sight, and opening one could restart the
computer and leave the person a blank page to solve.

  • A hosted computer runs headed Chromium on its own virtual display by default, behind the same live
    screen. Headless Chromium is what bot checks catch first, and a computer still run headless no
    longer calls itself HeadlessChrome.
  • The computer no longer restarts on such a page. Images and Desktop run Bun 1.4.3, which fixes the
    connection crash behind it, and the network filter connects to one checked address at a time.
  • When the computer or its browser restarts while a Bot waits for a person, the page the request was
    raised on is opened again, through the usual network and page rules, so the person who takes
    control has the check to solve.
  • Web Bot Auth, off unless switched on: with WEB_BOT_AUTH=on (Helm: config.webBotAuth: "on") and
    an https public URL, every request a Bot's browser sends is signed, with a key derived from
    KEY_ENCRYPTION_KEY unless WEB_BOT_AUTH_KEY gives one, published at
    /.well-known/http-message-signatures-directory. Register the deployment once with Cloudflare
    (docs/configuration.md) for sites that let signed agents through. Every site a Bot visits then
    sees the deployment's URL. on without an https public URL stops the server at start.
  • On Helm, computers.egressProxy sends every computer's traffic through a proxy, such as a
    residential or ISP one, with its URL in a Secret. Off by default; elsewhere EGRESS_PROXY_DEFAULT
    does the same, as before.

A computer closes browsers nobody is using when its memory runs high

A computer's only limits were a count of browsers and half an hour untouched, so a few heavy pages
could take its container past its memory limit, and the kernel then killed processes whichever Bot
was busy (on Kubernetes, every browser at once). A computer now reads its memory as the kubelet
does, before it starts a browser and once a minute. At 85% of its limit it closes browsers one at a
time, least recently used first, until it is under 75%. Never closed: the one being started, the
most recently used, any used in the last minute, a person's own browser, and a Bot's whose wheel a
person holds or has been asked to take. A Bot whose browser was closed starts again on its next
step, still signed in. In the openbot image the share counts the database and the API server,
which this protects; a computer outside a container closes nothing for memory. Each close is logged
as computer-browser-closed-for-memory. COMPUTER_MEMORY_HIGH_PERCENT (Helm:
computers.memoryHighPercent) sets the share, and 0 switches it off.

A Bot's background tabs, such as a sign-in popup's opener, are closed after ten minutes unused. The
page the Bot is on, a person's own browser and a Bot whose wheel a person holds keep theirs.
COMPUTER_TAB_IDLE_MS (Helm: computers.tabIdleMs) sets the time, and 0 keeps them.

Through Helm, computers.browserIdleMs and computers.maxBrowsers now reach the computer as
written (an hour rendered as 3.6e+06 and was ignored, and 0 was dropped), and all four settings
now reach computers.mode: sandbox computers too.

A Bot can install packages on its computer, and gets no other root

On a computer the supervisor runs (laptop, Desktop, Compose), package installs failed: the container
dropped the permissions apt-get needs even as root. It now keeps those few (changing file owners
and switching user) and no others. In the openbot image (one container, and every Helm computer
pod), the Bot's user could run apt-get, apt, dpkg, apt-key and apt-cache through sudo,
and an apt option runs any command as root, where the deployment's secrets are. sudo now runs only
openbot-apt: sudo openbot-apt update and sudo openbot-apt install <package>, package names
only (-y is accepted and changes nothing). /app, the API's code and the page it serves, is no
longer the Bot's user's. A Bot's tool description says which to use, by whether its shell is root.

The Bot's screen opens only when there is something to watch

  • Reading, writing or listing a file and running a command no longer open the Bot's screen beside
    the conversation; only work on the screen does. An action refused by a policy, a switch or a
    network rule closes the screen it opened, unless an earlier action in the run opened it; one that
    was allowed and failed keeps it.
  • In a conversation, a Bot is no longer offered computer tools that the Cloud browser use, Cloud
    computer use or Cloud network access switches (Admin → Permissions) would refuse, so it
    stops reaching for one and spending the turn saying it was refused.
  • Opening the Computer panel shows the screen at once. It could stay on "Waiting for the Bot's
    screen" while the Bot browsed.
  • The full-size screen has a close button, including while you have control; closing leaves control
    where it is. While you hold it, the small view says so on an amber band, and that the Bot waits
    until you hand it back.

The Computer panel's Activity lists every command and file step

Activity was written by the page's own tool handlers and kept only until the tab closed. It is now
read from the conversation, so it lists every command the Bot runs and every file it reads, saves or
lists, with what it printed, once each: run at once, after you allowed it, or not at all. A
conversation opened again shows the same list. A step you declined, on an approval request or the
Bot's own approval card, reads "Declined" with "You declined it."; one you did yourself shows as
done by you; any other step not done says why in your words rather than the sentence the Bot is
told, and its line in the conversation says the same. A step shows a time only if the page saw it
run. Resetting a computer from Admin clears the Activity of every Bot that ran on it, and on that
browser a reload or another tab no longer brings it back.

Following a link is judged as opening its address

A Bot's click on a link to another page now opens that address, judged exactly as
computer_navigate is: a read, with the same allowlist, policy, approval and audit. As a click it
was a change, so the built-in payments requirement handed a Bot back to the person for following a
link to /purchase-orders/... while typing the address went through. Buttons, same-page # links,
javascript: and mailto: links, links in an iframe and links with no address are still clicked. A
rule on the link (element.name) still applies, and one on where pages open (page.url) now does
too. Snapshots carry each top-page link's absolute address as url. Stop cancels a followed link,
even while it waits for approval, and a link from a page that has since moved on is refused as
stale.

Memory lives in CopilotKit Intelligence

A deployment entitled to Intelligence memory now keeps its people's memories there: Intelligence
holds the content and ranks recall, and memories another app on the same project formed for a
person appear in their Memory page and reach their Bots. OpenBot still records each memory's
provenance, review state and which Bot or source it came from, and applies those rules to
everything Intelligence returns, so a Bot-formed memory still reaches only the Bot that formed it.
Without Intelligence memory nothing changes: OpenBot's own tables remain the store.

Each app you connect becomes a memory source for your Bots

Connected-app memory sources used to wait for somebody to set them up, one Bot and one app at a
time. Now, when a Bot takes a turn for a person, each app the person connected with their own
account that the Bot can read becomes a memory source for that Bot, except on a Team Bot somebody
else published, where their accounts need their consent. Sources read as the person, their facts
reach that Bot only, and imported facts wait for the person's review, as with a source made by
hand. Shared apps and apps that need no account are not added. Turning a source off keeps it off,
and removing one is remembered, through a Bot reset too. OPENBOT_MEMORY_SOURCE_DEFAULTS=false
(Helm: config.memory.sourceDefaults: false) stops new ones for the whole deployment; nothing
already saved changes.

Automatic Learning assigns its openbot container, and creates it where it can

Automatic Learning was on by default but collected nothing until somebody assigned a container.
Now, with nothing saved under Admin → Automatic Learning and no
CPK_INTELLIGENCE_LEARNING_CONTAINER_ID, the server assigns its Intelligence project's openbot
container as soon as it exists (it checks at start and every ten minutes until then), records that
in the audit log, and Admin says so. A saved setting, including off, is never changed.
OPENBOT_LEARNING_AUTO_ASSIGN=false (Helm: config.learning.autoAssign: false) turns this off.

The server first creates it with the runtime key, which Intelligence after 0.15.1 allows. On
0.15.1 and earlier, OpenBot creates it with a credential that can:

  • bun scripts/setup-learning.ts, which scripts/start.sh now runs on every start without a
    prompt, uses a CopilotKit CLI login, even with no project selected, once the login's organization
    and project match the runtime key's. On Windows it starts the CLI through cmd.exe.
  • The server creates it at start, and from a "Create the openbot container" button under Admin →
    Automatic Learning, with a CLI login on its machine or INTELLIGENCE_SERVICE_TOKEN with
    INTELLIGENCE_PROJECT_ID (a service token counts only on a CopilotKit local evaluation install).
    It refuses to start with only one of those two.
  • The Helm chart runs a setup Job on each install and upgrade when config.learning.setup names a
    CLI-login Secret or a service-token Secret.

Without one, Admin and the server log say what to run.

OpenBot can send product usage events

OpenBot can now send usage events to CopilotKit through the telemetry channel the CopilotKit
runtime already uses: when a run a person, routine, subagent or Bot-to-Bot hop started begins and
ends, and how (a stop reads cancelled, a headless turn's deadline timeout); use of connections,
approvals, Bot and routine setup, the computer and Learning sharing, read from the audit log; and
when an answer is seen on screen or copied, which the browser reports to
POST /api/telemetry/product-events. Runs the runtime makes for itself (naming a conversation,
suggestions) are not counted. A person appears only as a pseudonym keyed with
KEY_ENCRYPTION_KEY; no names, prompts, tool arguments, results or URLs are sent. Nothing is
stored and there is no migration.

A server sends these only with OPENBOT_PRODUCT_TELEMETRY=1. OpenBot Desktop sends them by default,
under its existing telemetry notice. OPENBOT_PRODUCT_TELEMETRY=0, DO_NOT_TRACK=1 or
COPILOTKIT_TELEMETRY_DISABLED=1 turns them off. The CopilotKit runtime's own events now carry
app_id=openbot, and this deployment's ID only while product telemetry is on.

Naming a conversation and offering suggestions run the Bot with none of its tools

The CopilotKit runtime names a new conversation by running its Bot again on a title prompt, and
answers its suggestions route by running the Bot on what the caller sent. Both runs held every tool
the Bot had, out of the person's sight; with subagents, a naming run was seen starting one that
acted with all the Bot's permissions and kept going after Stop. Both now run with none of the Bot's
tools, and a remote Bot's naming run is sent no signed run, so a callback from it is refused. A
person's own runs, the subagents they start, headless turns and hops keep every tool.

Capabilities and Connections sit beside Bots in the sidebar

The sidebar's Skills entry becomes two. Capabilities (/capabilities) holds Skills and Memory
as tabs, and Memory leaves Settings. Connections (/connections) is one searchable list of the
apps your Bots use as you: apps you connect, apps on the team account (marked "Team account") and
your GitHub and Slack links, those in use first, each with its own page. GitHub's page holds your
link and the repositories whose events can start a responsibility. The reset for what you let
shared Bots use moves to Settings → Approvals, as Shared Bots. /skills,
/settings/connected-accounts (and any app beneath it) and /settings/memory redirect to the new
places, keeping their query, so a consent begun before upgrading still lands.

Settings, Admin, Connections, Capabilities and each Bot's pages now share one column width, 768px
(it was 630px). The audit log stays wider.

A Bot can send, draft and reply in Gmail

OpenBot left out every Composio action that takes a file, including those where the file is
optional, so Gmail's send, draft, reply and update-draft never appeared. They are now offered
without the optional file; a call that passes one anyway is refused with a message saying to try
again without it. An action that needs a file, such as a Google Drive upload, is still left out.
For an app that is already on, press Refresh tools on its page in Admin, then grant the new actions
to a Bot.

Generated interfaces use CopilotKit's standard catalog on A2UI 1.0

With generative UI on, a Bot builds an interface from CopilotKit's standard catalog on A2UI 1.0
instead of the 18 A2UI 0.9 basic components: 65 components for layout, text, inputs, tables,
charts, media and composites such as entity cards and image grids, including the A2UI basic
components. They take OpenBot's colours, radii and font, in light and dark. Four are left out: Map
needs a map library OpenBot does not ship, and Favicon, LinkCard and Citation would send every
linked domain to Google's favicon service. None is listed in the components gallery or can be
turned off per Bot. Granted gallery components are unchanged, and OPENBOT_GENERATIVE_UI=false
still switches it all off, on the server too. Code and markdown blocks in a generated interface
are drawn like a reply's: highlighted in light and dark, with the same copy and download buttons,
tables and document links.

OpenBot runs on CopilotKit 1.83

OpenBot now runs on CopilotKit 1.83.0 (@copilotkit/runtime and @copilotkit/react-core, from
1.77.0) and AG-UI 1.0.2 (@ag-ui/core and @ag-ui/client, from 1.0.1).

Behind an OPENAI_BASE_URL that is not OpenAI or Azure, the built-in Bots now call the endpoint's
Chat Completions API (/chat/completions), which every OpenAI-compatible endpoint serves, instead
of the Responses API. A model that takes tools only through the Responses API (gpt-5.6 and later,
the default gpt-5.6-terra among them, and the -pro and -codex models, also under a gateway's
openai/ name) still gets the Responses API: OpenBot sets COPILOTKIT_OPENAI_API=responses for
it at start and logs openai-api. A value you set always wins: responses or chat picks one API
for every model, and any other value is an error when a built-in Bot runs. On Chat Completions,
Responses-only features such as reasoning summaries are dropped without an error. Deployments on
OpenAI or Azure are unchanged.

Company systems behind Microsoft Entra, reached as the signed-in person

An admin can add an MCP server by URL with an Entra scope (entraScope), and each call then goes
out with a token Entra issues for the person asking, through the on-behalf-of exchange: from the
Entra token their app sent, or otherwise from their stored Microsoft sign-in, which routines and
check-ins use. Apps listed by client id in MICROSOFT_OAUTH_ALLOWED_CLIENTS can call OpenBot's API
with a person's Entra token for MICROSOFT_OAUTH_API_SCOPE; a token issued to any other app is
refused. Unset, no app can. A value that is not a client id stops the server at start. See
Company systems behind Microsoft Entra.

Each single-user OpenBot has its own Composio and Arcade identity

Every single-user OpenBot is dev-local-user, and that was also the user id it filed connections
under at the broker. So two single-user OpenBots on one COMPOSIO_API_KEY were one Composio user:
each listed and used the other's accounts, a disconnect in one ended the account in both, and a call
that named no account could run in whichever was connected last. Each now has its own id at its
broker, dev-local-user: and a random part, given at its first start with that broker and kept in
its database, so restarts and replicas agree. Threads and memory stay with dev-local-user. See
configuration.md.

Shopify and other Composio apps on a server-to-server grant can be connected

Composio publishes some apps, Shopify among them, with only OAUTH2 and S2S_OAUTH2, so they were
hidden from the picker and refused at enable. Set COMPOSIO_<SLUG>_CLIENT_ID and
COMPOSIO_<SLUG>_CLIENT_SECRET (for Shopify, COMPOSIO_SHOPIFY_CLIENT_ID and
COMPOSIO_SHOPIFY_CLIENT_SECRET) to the OAuth client you registered with the vendor, and the app
can be enabled and connected. Because the credential is the deployment's, such an app connects
only as a Shared app: an admin connects it and grants it to Bots, and a personal Connect is
refused. See rotating the client.
Without them, nothing changes.

A Shopify store connected with a key is now checked with SHOPIFY_GET_SHOP_DETAILS. The check used
SHOPIFY_COUNT_BLOGS, which needs a scope a store app with only order and product scopes lacks, so
a working key was reported as rejected.

A Bot sees the images its tools return

An image a tool returned, such as a Gmail attachment fetched through Composio, reached the model as
a storage link it could not open. The server now downloads it and puts the picture in front of the
model beside the tool's answer: https only, only from Composio's file storage hosts, never from an
address that is not public, at most 8 MB each and the newest four in a run. A picture that cannot
be downloaded, such as one whose link has expired, is replaced by a sentence saying it is missing.
Each picture is sent again on every later step, so expect more input tokens.

A built-in Bot can hold hundreds of tools

A built-in Bot whose run would send more than 50 tools no longer sends all of them on every step.
Its own tools stay in front of the model, and an app's tools wait behind one search tool: the model
searches in a few words and calls what it found from its next step. Every call still goes through
the grant, the approval rules and the audit trail. A question that needs an app's tool makes one
more model call than before. A run at or under 50 tools is sent exactly what it was.

A run that would still send more than 128 tools at once, counting room for a search's answer, is
refused before the model is called wherever search is on or the model is reached through Chat
Completions, which refuses more itself. The person is told why, and the audit log records it under
Blocked as "Too many tools for its model".

Public-web research works with Exa and Firecrawl, not only Parallel

Four catalogue entries give a Bot web search and page reading without its browser: Exa Search
(free, rate-limited), Exa Search (API key), Firecrawl (free) and Firecrawl (API key),
beside the two Parallel entries. In the team edition none is granted to a Bot until an admin does
it. See docs/web-research.md.

  • Research guidance follows whichever provider a Bot holds, and a Bot holding more than one
    uses the one the person asks for.
  • A Bot holding a research provider searches without being asked when an answer depends on
    facts that change, such as current figures or recent events, and stops after a search or two.
  • The conversation does not name the provider. Research calls show as "Search the web" and "Read
    web pages".
  • Skills declare roles. The shipped research-public-web skill now names @research/search and
    @research/fetch, which match every granted provider's search and fetch tools. A role grants
    nothing.
  • Exa and Firecrawl list their reads, so every other tool they offer is treated as a write.
    Exa's deployment key is sent in its own x-api-key header.

A run of tool calls folds into one line

A Bot's consecutive tool calls, research and browser steps now fold into one line that says what was
done, such as "Searched the web and read 2 pages" or "Used 5 tools", with an icon for each kind of
step. It names the step in progress while the Bot works and counts steps that were blocked or
failed. Opened, it lists each step, and each step opens to what it was given and what came back.
A lone call keeps its own line, and approvals, connection requests and anything drawn as an
interface stay outside the fold.

A source checkout sets up Composio from your CopilotKit login

With no Composio key, scripts/start.sh uses your CopilotKit CLI login to create or reuse one
Composio project for the installation and writes COMPOSIO_API_KEY to .env; bun run setup:composio runs that step alone. If it fails, OpenBot starts with apps unavailable. A key you
set always wins, and a deployment on Arcade is left alone. OpenBot Desktop, a server, a container
or a Helm release still needs the key set.

The server image runs the routines worker too, and Compose can run OpenBot from it

The published openbot-server image now also carries the routines worker (bun ../worker/src/index.ts), so the API, the app and the worker can run from one image.
docker-compose.yml has server and worker services built that way behind the openbot profile
(docker compose --profile openbot up -d), publishing the app on APP_PORT and SERVER_PORT on
loopback only; scripts/start.sh and a plain docker compose up do not start them.
INTELLIGENCE_BROWSER_GATEWAY_WS_URL gives the browser a different gateway address from the
server's where it needs one. The image also carries the computer's egress rules, which the server
imports; without them it exited at start when run as the API, in 0.2.0 too.

Bots answer with components when an answer has structure, and answers are wider

Built-in Bots, and remote Bots through the standing role OpenBot sends them, are now told to use a
component when an answer has structure: a chart for figures, a checklist or table for steps, a
record card for one thing's details, a card to choose from for a decision. Short replies stay plain
text. Code in replies, tool results and group chat is highlighted in light and dark. A coding Bot's
Claude Code steps get cards of their own (a command with its output, an edit as a diff, its to-do
list), and a step that failed or was refused no longer shows as done. The answer column is 768px
wide, up from 672px; skins keep their own widths.

A new Bot starts from its name, and OpenBot knows yours

New Bot opens a sheet on the Bot itself: a name, optional instructions and who can see it. Create
Bot makes one that runs here; Connect a custom agent goes on to the frameworks, and choosing one
goes to its address, which Continue checks before anything is created. The To: list of a new
conversation has New Bot at its top, which makes a Bot and adds it without losing the message.
Settings has a Profile form for your name (PATCH /api/me/profile), the home screen greets you by
it, and Bots that run here are told it on each run. The frameworks list Claude Agent SDK, Google
ADK, Google Antigravity and LangGraph first.

The Desktop app opens the Docker Desktop you already use instead of installing Podman

On a Mac or Windows PC with Docker Desktop installed but quit, setup installed Podman beside it. It
now opens Docker Desktop and uses it when it has run on that computer before; one never opened is
left alone and the computer gets Podman as before. Each installation keeps the engine it was set up
on, because its images and database are there, and Start brings that engine up, so an installation
reopened after a restart no longer stops with "podman is installed but not answering". Linux is
unchanged.

Bots on other frameworks answer from the whole conversation, in their role

OpenBot sends a Bot the whole conversation on every run, behind its role and a summary of the tools
it holds. Several harnesses dropped part of that. Each now gives the model the conversation as
OpenBot sent it, with the current role and tools summary, so a restart or another replica no
longer changes what the Bot knows, and a changed grant reaches it on the next run. Only that role
and summary are its instructions: any other system message, such as a skill you pick, reaches it
labelled as part of the conversation.

  • Claude Agent SDK. After its container restarted, or the conversation sat idle for 30
    minutes, it answered only the newest message, and it never received its role or tools summary.
    A long conversation now sends its newest messages, up to about 100,000 tokens, and tells the Bot
    that older ones are left out. Built-in Bots on a Claude subscription run through this harness and
    get the same limit.
  • Agno. It was sent only the newest message, so it never remembered an earlier turn, and it
    never received its role or tools summary. A long conversation is cut the same way, at 100,000
    tokens or half the model's window. A tool result that reaches a restarted harness or another
    replica is answered from the conversation instead of failing the run.
  • Google ADK. After about 20 idle minutes it saw only the newest message. It kept only the
    first system message OpenBot sends, so it never had its tools summary, nor its role when personal
    memory came first. A tool result that reaches another replica or a restarted harness now carries
    on from the call, so the tool runs once; that replica used to call it again (a message sent
    twice) or end the run with nothing said.
  • LangGraph. It dropped the first system message, usually its role, so it often answered as a
    plain assistant, and a running harness kept an old tools summary. After you approve something it
    stopped to ask about, it now carries on from the conversation; it used to stop without answering,
    or answer with none of the conversation.
  • Mastra. It now receives the context the app sends with a run, such as a component catalogue,
    after its role, and a skill's instructions and your personal memory, which it never saw.
  • Strands. It never received its role, its tools summary or a skill's instructions, so it
    answered as a plain assistant. Its role and summary are now its instructions on every run, so a
    new role or grant applies on the next message without a restart.

The Agno, ADK and LangGraph harnesses also no longer keep a copy of every finished conversation in
memory for the life of the process.

Google Antigravity can be a Bot

OpenBot Desktop's setup and the New Bot sheet offer Google Antigravity, from the new
openbot-agent-antigravity image. It works only with Google's models: it reads GOOGLE_API_KEY (or
GEMINI_API_KEY) and GOOGLE_GENERATIVE_AI_BASE_URL, stays on Google whatever BOT_PROVIDER says,
and takes BOT_MODEL for the Gemini model (default gemini-3.6-flash). Checking the address of the
deployment's own Bot, or of the framework picked in Desktop setup, now sends the deployment's token
as a run does and stops at the first event, so a working Bot is no longer reported as refused or
broken. It answers from the whole conversation OpenBot sends, in its role and with the summary of
its tools, after a restart too; a change to either starts its session again, and a long conversation
is sent from its newest messages with a note that earlier ones are left out.

A Bot's turn rides through a network blip without losing its conversation

Headless turns (routines, responsibilities, background research, wake-ups, group turns) and
Bot-to-Bot handoffs renew a lock on the conversation. Any failed renewal used to stop a headless
turn, which is retried from the start, so a blip could make a Bot send an email twice; a handoff
ignored failures and kept writing, even after the person started typing. Now both stop only when
Intelligence says the lock is another run's (409), or when no renewal has landed for nearly the
lock's 120 seconds. A handoff to a paused or disallowed Bot is refused before it takes the lock.

The Helm chart pins by digest, mounts a tenant package, and takes a Composio key

  • image.digest (sha256: and 64 hex characters, from a release's container-images.json) runs
    every workload as repository:tag@digest.
  • config.extraVolumes and config.extraVolumeMounts mount volumes on the server and the routines
    and computer-culler CronJobs, so a tenant package from a ConfigMap needs no custom image.
  • secrets.composioApiKey, or composio-api-key in externalSecrets.data or
    secrets.existingSecret, reaches the server alone as COMPOSIO_API_KEY.

Unset, the chart renders as before.

Each Bot on a shared computer has its own files

A computer many Bots share kept one folder of files, so a note one Bot saved was a file every other
Bot could read. Each person's use of each Bot now gets its own folder, where its commands start. It
is on for Helm's shared computer and the Compose agent-computer;
COMPUTER_SEPARATE_WORKSPACES=on turns it on for the all-in-one image. A command can still read
other folders: keep people apart with computers.mode: sandbox.

Every chart says where its figures came from

A chart a Bot draws ends with what its figures were read from and when they are from ("Sources:
World Bank · As of 2025"), or "From the Bot's own knowledge. Not checked against a source." Bots are
told to look up figures that change over time before charting them. Slack and Teams replies carry
the line.

OpenBot has a new look, and plainer words

Pages sit in a panel inset from a darker frame shared with the sidebar, in a lilac palette with a
plum dark mode, and share one spacing, row and empty-state design. Each Bot is an orb in its own
colors, and a conversation opens with its Bot's orb, name and role. The app says Bot everywhere
("agent" only for an outside program you connect), admin, and conversation rather than
channel, with shorter text that says what to do next. Text written for the model is unchanged.

Boundaries are named rules, the audit log is a table, and a Bot's page is shorter

  • Admin → Boundaries lists named rules ("Bots may never…"). A new one starts from a template or a
    blank rule, and each opens to a page with what it has stopped lately. Adding a credential is a
    page too, not a dialog.
  • Admin → Audit is one filterable table, newest first, with the full date and time, blocked rows
    red and actions that did not happen amber. Search and the Outcome, Bot and Started by filters
    narrow the events loaded.
  • A Bot's page shows at most three things running or scheduled; the full lists move to its Activity
    page. A conversation's gear panel gains Pin and Delete, and Home's @ menu opens at the caret.

Each person can pick a skin: OpenDuet, OpenDots or OpenMuse

Settings → Preferences → Skin changes how OpenBot looks for you, not what it does: OpenDuet (one
phone-width column), OpenDots (a rail, a sidebar of Bots, the Bot's computer beside the chat) or
OpenMuse (one Bot at the centre). Each draws an open-source template's layout over OpenBot's own
features and keeps the name from brand.yaml. The choice is saved on your account. Template
features with no OpenBot server, such as OpenDots' Spaces, are left out.

A sign-in's verification code is asked for after the password

The private sign-in form asked for a username, a password and "a verification code, if the site
asks for one" all at once, so a person had to guess whether a code was coming and type it before
the site had sent it. The form now asks for the username and password alone. If the site, once it
has the password, shows a one-time-code field, the request waits in a new code_required state and
the form asks for that code on its own. A wrong code asks again, a code nobody enters expires with
the request, and the person can still take over the browser or cancel. The Bot is told only how it
ended, never the code, and a saved login never stores one. The step is audited as
computer.sign_in_code_requested, and a failed code as computer.sign_in_failed with
step: "code".

The safety rules judge what an action does, not words that happen to be in it

The built-in safety rules (credentials, security settings, paying or moving money) hand an action
to the person whatever the other rules say. They now read it more accurately:

  • What a Bot types is no longer read, only the field, the page and the button. A CRM note about a
    customer's purchase orders, or a source field reading "Business Wire", was stopped as moving
    money in the middle of a form. A card number field, a checkout page or a new password field still
    goes to you, whatever is typed.
  • A news wire (business, news or press wire) no longer counts as wiring money. Wiring money still
    does.
  • A field the Bot left empty no longer counts. A note on a Shopify order was handed to you because
    the action has a billing_address field nobody filled in.
  • A refund now counts as moving money. SHOPIFY_CREATE_REFUND and other refund actions used to
    pass straight through.

A page visit an approval rule covers asks you

With an approval rule that asks before a Bot opens, reads or scrolls a page, the Bot was told the
action "was not started from a conversation that can ask you", did nothing, and said it could not
get an approval in this chat. The visit now waits on the approval card in the conversation, as a
click or a command does: Allow opens the page and the Bot carries on, Deny tells the Bot it was
declined. Page visits no rule asks about still go straight to the computer.

A Bot can choose from a dropdown

A Bot could not set a native <select> in its browser, such as a CRM's Industry or Stage. Typing
into one waited until it timed out and was reported as a field that had gone from the page, so the
Bot took a fresh snapshot and tried the same thing again. Typing into a dropdown now chooses the
option whose label (or failing that, value) matches the text, ignoring case, and the page sees the
same change a person's choice makes. Text that matches no option is refused with a message saying
so. A page snapshot lists each dropdown's options (up to 50) and the one it shows.

A Bot carries on after a browser step fails

When a click, typed entry, key press, command or file write the Bot made on its computer failed or
was refused, the Bot's turn ended on the spot and it said nothing more. The failure now reaches the
Bot as a result, so it can take a fresh snapshot, try another way, or tell you what stopped it.

Boundary rules catch two spellings that used to get past them

  • Enter or Space with a modifier is an activation. computer_key names keys the way Playwright
    does, and the browser submits a form on Shift+Enter as on Enter, but only the bare Enter,
    NumpadEnter and Space counted as intent == "activate". Now Shift+Enter, Control+Enter,
    Shift+Space, \n and \r do too. The shipped "Never submit a form" rule (the Boundaries preset
    and the AGENT_COMPUTER_POLICY example in .env.example) now refuses a keypress on
    intent == "activate" instead of key == "Enter".
  • page.host drops the root dot a host can end with, so https://intranet.example.com./ no
    longer slips past page.host == "intranet.example.com". The port stays.

A Bot's question waits above the composer, with the answers it expects

When a Bot stops to ask you something, the conversation it asked in now shows the question and why
in a drawer above the composer, however far you scroll, and what you send there is your answer. A
Bot can put one decision or up to six in a question, each with the answers it expects (which time
zone to use, say); the drawer takes them one at a time, an option or your own words, and a reply
from the composer answers the whole. Dismiss puts a question down unanswered, with nothing sent to
the Bot (also POST /api/approvals/questions/:id/dismiss).

Each browser on a computer is held to its own network policy, and Google loads behind it

The computer's network filter now asks every browser whose it is. Before, browsers' connections
arrived anonymous and were judged by every policy on that computer, strictest first, so a Bot
allowed to reach a service was refused because another browser on the same computer was not. A
shell command's connections are judged as before. Google's pages also load in a browser behind the
filter: they never finished loading (Chromium restarts the request when Google asks for client
hints, and the restart was lost), and every browser behind the filter now launches with that
restart switched off.

OpenBot Desktop installs and starts more reliably

  • A new Desktop install of v0.2.0 stopped at start with Cannot find module '../../../agent-computer/src/egress', because Desktop did not lay out agent-computer. It does
    now, and lays out again, keeping settings and data, an installation an earlier Desktop left
    without it.
  • Opened from Finder or the Dock, the app could stop at Start with bun: command not found in its
    log. Every process Desktop starts now has the directory of the Bun it runs first on its PATH.
  • The window keeps answering while Desktop downloads software: bringing it forward, or the setup
    page loading again, no longer shows a spinning cursor until the download ends.
  • A new installation made after the old one's folder was deleted no longer silently reuses what that
    one left in the container engine: each Bot's workspace files and browser profile, with its website
    sign-ins (only the database was checked). Start lists those volumes and stopped containers and
    offers Start fresh, which deletes exactly those and then starts; to keep them, restore the old
    folder. A container from it that is still running blocks the deletion. An installation with its
    saved key keeps its data as before.

The approval card for an app's call appears in the conversation

When OpenBot stops a Bot to ask about an app's call (sending an invitation, deleting a file), the
approval card now appears in the conversation where the call is, as soon as the run stops. It used
to appear only on the Bots page, while the conversation showed the call still running.

A skill in a new conversation's first message reaches the Bot

A / skill picked in the first message of a new conversation showed its chip, but its instructions
were left out, for every kind of Bot. The first message now carries them, as later ones did.

Conversations go by your first message until named, and are named on Anthropic too

Until a conversation is named, the sidebar and every skin show your first message as plain text,
not the Bot's reply with its Markdown, and search matches it. Names are stored as plain text. With
Bots on Anthropic, naming sent the Anthropic key to OpenAI and no conversation was ever named; it
now asks Anthropic.

A routine asked for in a conversation posts in that conversation

It used to post into the one conversation you share with the Bot, or have the Bot ask which. The
Routines page names conversations by their title.

Admin → Automatic learning reviews and publishes proposed skills

The page lists Learning's proposed skills, each with its SKILL.md, files and what it was learned
from, to approve and publish or reject, both audited. Learn now starts a run. It works on an
Intelligence with no dashboard, and says when Learning is off in Intelligence.

An allowed site on a non-default port opens

With a network allowlist, a Bot opening an allowed site on a port other than the scheme's default,
such as http://erp.internal:8080, was refused whatever the rule said: the server compared the host
with its port attached against the allowlist's domains. It now compares host and port apart, as the
computer's own filter does.

The computer image is smaller

openbot-agent-computer no longer carries Playwright's headless shell, a second Chromium it never
starts, or the package cache left from installing the browser: about 100 MB less to download and
about 300 MB less unpacked. The openbot image drops the same. The browser is unchanged.

A computer run on a laptop can stay off the local network

A computer run straight on a machine rather than in a container listened on every interface, leaving
its token-protected API reachable from the local network. COMPUTER_BIND=127.0.0.1 now keeps it to
the machine. Unset, nothing changes; a computer driving a native Chrome (OpenBot Desktop's) always
listens on loopback.

Each Bot keeps a fact it formed, even when another Bot formed it first

A memory a Bot forms from what it read was deduplicated per person, but each Bot recalls only the
memories it formed itself. So when a second Bot formed a fact the first already held, it was told
the fact was remembered and could then never recall it. Each Bot now keeps its own copy, and
resetting one Bot leaves the other's alone. Forgetting stays the person's: a fact forgotten from
any Bot is not formed again by another.

A licence token from a store reaches the server

The Helm chart passed COPILOTKIT_LICENSE_TOKEN to the server only when secrets.licenseToken was
written as a literal. A token synced through externalSecrets reached the Secret and never the
pod, so the deployment ran unlicensed with nothing saying why. It is now passed whenever
externalSecrets.data names license-token, and as an optional reference with
secrets.existingSecret, where the chart cannot see the Secret's keys.

The server says at start-up when Intelligence or the model key will refuse it

Both checks ask once, log what they find and never delay start-up:

  • A self-hosted Intelligence without its own licence refuses memory and other licensed features
    while conversations carry on. The server logs intelligence-entitlement-not-ready with the code
    and where the licence goes, or intelligence-entitlement-unreadable when Intelligence cannot be
    asked.
  • A key without access to the tenant package's default_model passed every start-up check and
    failed at the first message. The server asks OpenAI or Anthropic and logs model-unavailable with
    the model and the fix, or model-credential-rejected for a refused key. It does not ask another
    endpoint set by OPENAI_BASE_URL or ANTHROPIC_BASE_URL, and says nothing while no key is
    configured.

A responsibility hears about large pull requests

GitHub events for a responsibility were refused above 32 KB, and most pull_request deliveries are
larger, since the pull request repeats its repository under head and base. They were answered
503, which GitHub does not deliver again on its own, so the responsibility never ran.
/api/events/github/:bindingId now takes up to 256 KB, like the event triggers, and stores an event
over 32 KB as its short top-level fields (action, number) and an excerpt from the start of the
rest. A body over 256 KB is answered 413.

Routines describe a repeated minute once, and concurrent edits keep the limit

  • A schedule like 0,30,0 9 * * * fires at 09:00 and 09:30, but the routines page and the model
    were told "Every day at 09:00, 09:00 and 09:30". A repeated minute is now said once.
  • An edit now reads the routine under the same per-person lock that counts and writes it, so an
    enable queued behind another cannot take a person past 20 switched-on routines, and concurrent
    schedule and time zone edits work out the next run from the schedule actually saved.

A title that arrives after its conversation was deleted is dropped

It no longer renames the deleted conversation or announces a new title.

A paused hand-off closes its run before it gives up

When a person pauses a Bot while another Bot's hand-off to it is running, OpenBot now waits for that
run to close on Intelligence before giving up on the hand-off. It used to let go at once, and the
run stayed open there with no end.

Connect works for an app Composio already holds your account for

Connect could be refused because Composio already held an account for the app that OpenBot had no
record of, with advice to disconnect an app the page showed as not connected. Connect now records
the account Composio holds and shows the app as connected. A Shared app's team account is still
recorded only by an admin.

A Bot holding apps keeps its browser, and each step is drawn once and named for the app

  • The browser works beside apps. A Bot that held any app or research tool could not open a
    page, click or type in a conversation: every browser step came back unavailable. It now works as
    it does for a Bot that holds none.
  • Each tool call is drawn once. The LangGraph Bot named its tool calls' message differently from
    the conversation store, so every call was kept twice, drawn twice (also after a reload) and sent
    back to the model on the next turn. Each is now kept once, and drawn once even in a conversation
    that already holds a copy.
  • Steps name the app, not the broker. "Used Composio Airtable" and "Airtable list bases" now
    read "Used Airtable" and "List bases", and app cards say "Gmail", not "Composio Gmail".

Approving a Bot for a Shared app either completes or can be tried again

Deciding a Shared app request now commits the decision and the access it grants together. If they
cannot be saved, the request stays pending, where it used to read as approved with no access
granted. Approving a Bot whose published audience and assigned groups name the same group no longer
fails. Concurrent approvals of the same Bot and app wait for each other instead of deadlocking.

A server that cannot read an app's OAuth client no longer replaces it

When a server could not decrypt an app's stored OAuth client, Connect registered a new client,
replacing the one every replica that could still read it was using. It now fails that Connect with
a retryable error and records mcp.oauth_client_read_failed on the audit trail, with nothing of
the credential. A missing, revoked or malformed client is still registered afresh.

A message naming different Bots' apps stays on the default Bot when routing is unsure

When the router could not decide, a message naming apps held by different Bots went to whichever
Bot came first in the roster. It now stays on the default Bot. One Bot holding every named app, or
a confident routing decision, still wins.

A Bot's Slack link code works only in a direct message, and the audit trail names who used it

The link <code> message from a Bot's Reaching you now links on Slack only when sent to the app in
a direct message. Sent in a channel, it is cancelled and the sender is told to make a new one, as a
code from Connections already was. Teams codes still link wherever they are sent. The
delivery.linked audit row now names the Slack or Teams account that sent the code, not only the
workspace; SMS and push rows still carry no phone number or device token.

A freshly built Microsoft Agent Framework Bot starts

agent-framework-ag-ui 1.7.0, released after v0.2.0, refuses to build an AG-UI endpoint that does
not declare how it authorizes, so a Microsoft Agent Framework Bot built with it failed at startup.
It is now pinned below 1.7.

The audit trail can be exported as CSV

Admin → Audit has Export CSV, which downloads the events the open view matches, up to the newest
5,000 (GET /api/admin/audit-events?format=csv; a cut export says so and sets
X-Audit-Export-Truncated). A cell starting with =, +, - or @ gets a leading ', so a
spreadsheet reads it as text. Each export is recorded as audit.exported. The enterprise
models/usage?days= and actions?limit= routes now answer 400 to a value that is not a whole
number instead of using their defaults.

OpenBot reads the Intelligence key the CopilotKit CLI writes

npx copilotkit project select writes the key as CPK_INTELLIGENCE_API_KEY, which OpenBot did not
read. The server, scripts/setup-learning.ts and the Learning setup job now read it first and fall
back to INTELLIGENCE_API_KEY; scripts/start.sh accepts either, and OpenBot Desktop writes both.

A local Intelligence install signs in through its own dashboard

Connecting an Intelligence install made with copilotkit local failed with invalid_redirect_uri.
bun scripts/setup-learning.ts now asks the newest CopilotKit CLI on the machine (installed or in
npm's npx cache) for its dashboard and signs in there. Any other Intelligence signs in at its API
address as before.

A new conversation starts with no Bot chosen

To: starts empty with the list open, unless the address names a Bot (?agent=), and Tab goes from
To: straight to the message box. When a Bot's computer stops during a hand-over, the notice says so
in plain words: if you had control it asks you to check the page and hand it back, and otherwise it
says the Bot will ask again if it still needs you.

A Bot's steps act for that Bot after you leave its conversation

A Bot keeps working after you leave its conversation, but its later steps were decided by whatever
was on screen: a component it drew could show "Not shown", and in another Bot's conversation its
command or page could go to that Bot's computer. Each step now acts for the Bot whose run it is,
with that Bot's own components and tools, wherever you are in the app.

The private sign-in button shows while a Bot waits for you

When a Bot asked you to sign in to a site in an open conversation, its card said "Waiting for you to
sign in" with no Open private sign-in button. The card now shows the button while the Bot waits.

A Bot's proposed Bot or skill says when the name is taken

A card proposing a Bot or skill whose name already exists now says so ("Create Researcher, again",
or "Replace" with whose skill it is); it used to check a list that had not loaded. A Bot's own
list_skills, read_skill and list_skill_tools no longer answer "No skills exist here yet" while
skills exist.

The all-in-one image waits for its database before migrating

With its embedded database, the openbot image could start migrating while PostgreSQL was still
starting, fail with the database system is starting up, and stay up with no server answering. It
now waits up to 60 seconds for the database, and says what it waited for if it never answers.

A Desktop download fails only when it stops arriving

OpenBot Desktop gave up on any download that took over 30 seconds in total, which on a connection
under about 1 MB/s failed the Bun download every time ("error decoding response body"). It now gives
up only after 30 seconds with nothing arriving.

Each source checkout starts its own routine worker

scripts/start.sh skipped starting the routine worker whenever any OpenBot worker ran on the
machine, so a second checkout never ran its routines. On macOS and Linux it now counts only a worker
from its own checkout, and stops and says so when it cannot tell.

A conversation opens at the bottom and stays still while a Bot writes

A conversation opens at its newest messages, and older ones load as you scroll up without moving
the view. Charts, tables and cards grow in place as they stream instead of flashing "not available"
first. A reply is revealed at a steady pace, followed until you scroll up or select text. A rail
beside it has a mark per turn to preview and jump to.

A Bot is told to search with its search tool, not its browser

A Bot holding a search tool is told to use it, and to keep its browser for signing in, clicking
through a site, forms or a page the person named. Without one it is told never to open a search
engine, to answer from what it knows saying it has not checked, and to show a card for a search app
the person can ask an admin for.

A computer that is starting says so

The Computer panel said "Can't show the screen right now" while a Bot's computer was asleep or
starting. It now says starting, or off until the Bot next uses it, and shows a problem only when the
computer can't be reached or has gone two and a half minutes without a picture; looking no longer
wakes it. An action a cold start turns away reads "not done yet" and the Bot tries again. A hosted
computer's cold start answers within 45 seconds, under a load balancer's usual 60-second timeout.

A conversation shows who it is waiting on

While a Bot asks another, the conversation ends with that Bot's avatar and "Asking Risk Analyst…",
and a routine firing into it reads "Market Scout is running a routine…"; the sidebar keeps it lit,
through a reload. A Bot at an address this deployment will not dial fails at once, not after five
minutes of retries.

A Bot's browser step is no longer refused while it is being saved

Before a click or a typed entry runs, the server checks it against the copy saved in the
conversation. That copy is saved a moment behind, a piece at a time, and a check that read it half
saved refused the step with "That unanswered tool call is not saved in this conversation" or
"Supply a valid request", stopping a form partway. The check now waits up to five seconds for the
step to be saved whole, as it already did for a step not saved at all.

More credentials in a command are redacted

A credential passed as the word after a flag was kept in clear: docker login -p secret,
sshpass -p secret, redis-cli -a secret, -asecret and --pass secret. So were a URL with a
password and no user (redis://:secret@host) and curl's --proxy-user user:secret. All are now
redacted, in the command kept for Action Recording and in the copy exported to a telemetry
collector. Each rule stops where its command does, so the port in sshpass -p secret ssh -p 2222,
or in a docker run -p 8080:80 on the line after a docker login, is kept.

For mysql and mysqldump, the rule that scrubs secrets from a Bot's recorded and exported
commands also hid -P, the port, so mysql -h db -P3306 -phunter2 app was kept with the password
in plain text. It now hides the password and leaves the port.

Outside content that looks marked as untrusted is marked anyway

A connector's answer or error was passed to the model without the untrusted-data notice when its
text already started and ended like one. That text could carry a notice in its own words, or close
the tag and continue outside it. Content is now left as it is only when it is exactly the envelope
OpenBot itself writes for that source; anything else is wrapped.

A Bot that runs on the deployment's own agent works when that agent is on a private address

A Bot created in the app with no address of its own runs on the deployment's agent,
MANAGED_AGENT_AG_UI_URL. Where that address is private, as on a local deployment that keeps it on
loopback, every message to the Bot failed with "This deployment will not dial". The deployment's own
agent addresses (MANAGED_AGENT_AG_UI_URL, and PICKED_HARNESS_URL when PICKED_HARNESS_IMAGE is
set) are now allowed at exactly those URLs, when a Bot is saved, in Test connection and on every
run. Another path on the same host and port, and a redirect onto those addresses, are still refused.

An email trigger reads the mail as the sender wrote it

A base64 or quoted-printable part was always read as UTF-8, so an iso-8859-1 mail reached the Bot
with every accented letter turned into �. Each part is now decoded in the charset its Content-Type
names, and one the runtime does not know still reads as UTF-8. An HTML-only mail kept its character
references once the tags were stripped (R&amp;D, we&#8217;re). Numeric references, &amp;,
&lt;, &gt;, &quot;, &apos; and &nbsp; are now decoded once, so &amp;lt; reads as &lt;.

Long text is cut between characters, not inside an emoji

Where OpenBot shortens text, a cut that landed inside an emoji left a broken character before the
"…". It now drops the half character instead, in a text message too long for one SMS, in what an
approval shows a Bot is about to type, write or send (and each field on the OpenTag card), in the
preview of a private share, and in a tool's link fields and a vendor's refusal the Bot reads.

No sign-in refuses an address it cannot place

With OPENBOT_SINGLE_USER=true, an address with no scheme, such as example.com:3010, in
OPENBOT_PUBLIC_URL, OPENBOT_APP_URL or TRUSTED_ORIGINS counted as a private network address
and only warned, though its host could not be read. It now stops the server starting, as an address
the public internet reaches does.