This is the procedure for making an OpenBot Desktop installer and proving that it works. Follow it in order. Every step says what you should see. If you see something else, stop and use When something goes wrong.
For what the installer does once it runs, read Desktop install. For the full release reference, read Releasing.
You need:
- write access to
CopilotKit/OpenBot, to start workflows; - the GitHub CLI (
gh), signed in, for the command-line steps; - an Apple silicon Mac;
- an x64 Windows 11 PC, or an x64 cloud VM with nested virtualization. A Windows on ARM VM on an Apple silicon Mac cannot run WSL 2, so it cannot run OpenBot;
- a CopilotKit account, to sign in during setup;
- an AI provider key, such as an OpenAI or Anthropic key.
Signed builds need an approval. These people can approve:
| Environment | Approvers |
|---|---|
macos-signing (Mac Developer ID signing) |
jpr5, davidmckayv |
windows-signing (Windows signing) |
davidmckayv, tylerslaton, jpr5, mme, contextablemark |
Internal builds are not signed and need no approval.
An installer is made from one commit. It contains:
| Part | Where it comes from | Where it is recorded |
|---|---|---|
| The app | The commit | build-version.json: version, sourceSha |
| The bundled Intelligence CLI | desktop/intelligence-cli.json (cli) |
build-version.json: cli (name, exact version, integrity, lockfileSha256) |
| The bundled Node runtime | desktop/intelligence-cli.json (node), checked by SHA-256 |
build-version.json: node |
| The deployment and its images (internal builds and candidates only) | The images built from the same commit, tagged sha-<commit>, pinned by digest |
Compiled into the app; written to ~/OpenBot/.openbot-deployment and ~/OpenBot/container-images.json on install |
There are three kinds of build:
| Kind | Made by | Pinned to its commit | Signed | Where it goes |
|---|---|---|---|---|
| Internal build | Internal build workflow | Yes | No | Workflow artifacts, kept 30 days |
| Release candidate or beta | Create release PR, then merge | Yes | After approval | GitHub prerelease |
| Stable release | Create release PR, then merge | No: it installs the latest release | After approval | GitHub release, marked Latest once Mac and Windows signing have both attached their signed installers |
A release is made only by merging its release PR. Do not push a v* tag by hand: that builds and
publishes nothing. It only starts Mac signing, which then has no release to attach to.
Every release carries each installer under a fixed name as well as under its versioned name. These addresses always give the installers of the release marked Latest:
| Platform | Address |
|---|---|
| Mac, Apple silicon | https://github.com/CopilotKit/OpenBot/releases/latest/download/OpenBot-mac-arm64.dmg |
| Mac, Intel | https://github.com/CopilotKit/OpenBot/releases/latest/download/OpenBot-mac-x64.dmg |
| Windows, x64 | https://github.com/CopilotKit/OpenBot/releases/latest/download/OpenBot-windows-x64-setup.exe |
A candidate or beta is never Latest. Link to its installers by version:
https://github.com/CopilotKit/OpenBot/releases/download/<tag>/OpenBot-mac-arm64.dmg. See
Download links.
Use an internal build to test a branch. Nothing is published.
Condition: the Internal build workflow must be on main, and the branch you build must
contain the build workflows (internal-build.yml, images.yml, desktop-build.yml). If the branch
is older than those workflows, merge main into it first.
-
Start the build. Either:
-
on GitHub, open Actions → Internal build → Run workflow, choose the branch, and select Run workflow; or
-
from a terminal:
gh workflow run internal-build.yml -R CopilotKit/OpenBot --ref <branch>
-
-
Find the run and watch it:
gh run list -R CopilotKit/OpenBot --workflow internal-build.yml --limit 1 gh run watch <run-id> -R CopilotKit/OpenBot --exit-status
Expected: every job succeeds. The image jobs come first; recent runs took 6 to 15 minutes. The Mac and Windows installer jobs follow.
-
Open the run page. The summary names the commit and the manifest version (
internal-<first 12 characters of the commit>), and links the two installers. -
Download the installers:
gh run download <run-id> -R CopilotKit/OpenBot -n openbot-desktop-macos -D build-mac gh run download <run-id> -R CopilotKit/OpenBot -n openbot-desktop-windows -D build-windows
Expected: each folder has an installer (
.dmgor-setup.exe) and abuild-version.json. -
Check the build. Do Check a build before you give the installer to anybody.
To try a branch on your own Apple silicon Mac without waiting for the workflow, make the same build locally:
git commit ... # it builds HEAD, never uncommitted work
desktop/scripts/local-build.shThe script:
- pushes the branch to origin (never
main, never forced), because a pinned app downloads its deployment fromhttps://github.com/CopilotKit/OpenBot/archive/<commit>.tar.gz; - starts a registry container,
openbot-local-registry, on127.0.0.1:5050; - builds
server,supervisor,agent-computerandagent-claude-sdkfrom the commit for linux/arm64 and pushes them there; - writes
container-images.jsonwith versionlocal-<first 12 characters of the commit>: the images it built by their local digest, every other image at the latest release's digest; - builds
OpenBot.apppinned to that manifest, with the bundled CLI, ad-hoc signed.
Expected: it ends by printing each stage's time, the manifest, and the app's path under
~/.openbot-local-build/apps/. To install it, quit OpenBot and copy the app into /Applications.
Set OPENBOT_LOCAL_IMAGES to build other images (OPENBOT_LOCAL_IMAGES="server"), or to an empty
string to take every image from the release. The other settings are at the top of the script.
A local build is for Docker only. Podman cannot pull from a registry on the Mac's own 127.0.0.1.
Like any build without the update key, it never checks for a newer version, so it shows no update bar.
Use a candidate to test what will become a release. The details are in Release candidates and betas and Finishing a candidate.
-
Write the release policy.
release-policy.jsonmust describe the new release: the versions it upgrades from, its database transition, downtime and recovery. Publishing refuses a policy that names another version. Within one release, Create release PR moves only itsversion(v0.3.0,v0.3.0-rc.1, …). -
Make sure
CHANGELOG.mdhas the release notes under## Unreleased. -
Cut the first candidate from
main:gh workflow run release.yml -R CopilotKit/OpenBot --ref main -f kind=rc -f bump=minor
Expected: a branch
release/X.Yand a release PR into it. -
Review and merge the release PR. Merging publishes the candidate. Expected: a GitHub prerelease
vX.Y.Z-rc.1withcontainer-images.json, the source archive, the chart, the release manifest, and the Mac and Windows installers. -
Approve Desktop macOS signing and Desktop Windows signing when they ask. These need a
macos-signingand awindows-signingapprover. For a stable release, Desktop Windows signing asks a second time, formacos-signing, once its installer is signed: that job signs the installer for the in-app updater with the updater key. Expected: the signed Apple silicon.dmgreplaces the ad-hoc one on the release, a signed Intel.dmgis added, and the signed-setup.exereplaces the unsigned one. Each is attached under its versioned name and its fixed name. For a candidate all three are pinned to the candidate's images, like the unsigned ones. A stable release also getslatest.json, listing both Mac archives and the Windows installer, and becomes Latest when the second of the two signing runs finishes. Until then the fixed addresses keep giving the previous release's signed installers. -
Test the candidate with Install and test and Check a build.
-
For a fix, merge it into
release/X.Y, then run Create release PR from that branch withkind=rcfor the next candidate. -
To finish, run Create release PR from
release/X.Ywithkind=final, then merge. A final may not differ from its last candidate except in the version, the changelog and the policy's version. Expected: a merge-back PR intomainopens. The release becomes Latest once you approve both signing runs for it and they finish (step 5).
A stable release from main (kind=stable) follows steps 1 to 5 without a release branch. Its
installers are not pinned: they install the latest release. When it becomes Latest, the fixed
addresses in What an installer contains give its installers.
Start from a clean machine, or the result proves nothing about a first install.
-
Reset the machine. See Reset a test machine.
-
Install:
- Mac: open the
.dmgand drag OpenBot to Applications. An unsigned (ad-hoc) build is blocked on the first open: open System Settings → Privacy & Security and select Open Anyway. A signed build opens normally. - Windows: run the
-setup.exe. An unsigned build shows SmartScreen: select More info, then Run anyway.
- Mac: open the
-
Go through setup: Install on the first screen, then Account → AI → Start. Install runs while you sign in.
- Sign in to CopilotKit in the browser and accept the evaluation agreement.
- Install downloads the images and shows each one with its speed. On Windows, the app may set up WSL and ask for one restart; open OpenBot again after the restart.
- Choose the AI provider and enter the key.
Expected: the window becomes OpenBot.
-
Ask a Bot a question. Expected: an answer.
-
Record the time from the first click to the open OpenBot, and anything that went wrong.
Web search and app connections need a Composio key in ~/OpenBot/.env
(%USERPROFILE%\OpenBot\.env on Windows) as COMPOSIO_API_KEY=.... Without one, they are
unavailable. That is expected for now.
These checks prove that an installer is the build you meant.
-
The build record. In the downloaded folder:
jq '{version, sourceSha, cli: .cli.version, node: .node.version, lockfile: .cli.lockfileSha256}' build-version.jsonExpected:
sourceShais the commit you built.cliis the CLI version you meant, andnodeis the version indesktop/intelligence-cli.json. -
The version in the app.
-
Mac:
defaults read /Applications/OpenBot.app/Contents/Info OpenBotBuildVersion defaults read /Applications/OpenBot.app/Contents/Info OpenBotSourceRevision
Expected: the same
versionandsourceShaasbuild-version.json. -
Windows: right-click
openbot-desktop.exein%LOCALAPPDATA%\OpenBot, then select Properties → Details. Expected: Product version is thatversion.
-
-
The deployment it installed (internal builds and candidates). After Install:
jq . ~/OpenBot/.openbot-deployment jq -r '.version, .commit, (.images | to_entries[] | .value.reference)' ~/OpenBot/container-images.json
Expected:
.openbot-deploymentshows the manifest version and"pinned": true;container-images.jsonnames the commit you built;- every image reference ends in
@sha256:....
-
The running containers.
docker ps --filter label=com.docker.compose.project=openbot-desktop --format '{{.Names}}\t{{.Image}}'Expected: the OpenBot containers are up, and their images are the ones in
container-images.json. -
The Mac signature (signed builds only):
codesign --verify --deep --strict --verbose=2 /Applications/OpenBot.app spctl -a -t exec -vv /Applications/OpenBot.appExpected: both pass, and
spctlsaysNotarized Developer ID.
The reset scripts return a machine to never-installed: the next launch goes through sign-in, the agreement and Install again.
-
Mac:
desktop/scripts/reset-test-machine.sh --dry-run # shows what it would do desktop/scripts/reset-test-machine.sh --remove-app -
Windows (PowerShell, as the person who uses OpenBot, not as administrator):
.\desktop\scripts\reset-test-machine.ps1 -DryRun .\desktop\scripts\reset-test-machine.ps1 -RemoveApp
They remove:
- OpenBot's containers, volumes and networks, and the Podman machine OpenBot made;
- the Intelligence stack OpenBot created, and the CopilotKit CLI sign-in;
- the evaluation agreement record,
~/OpenBot, and the app's own data; - with
--remove-app/-RemoveApp, the app itself.
They keep:
- Docker Desktop, Podman and WSL;
- other Intelligence stacks and your browsers' data;
- images, unless you add
--images/-Images.
Uninstalling the app alone is not a reset. The uninstaller and dragging to Trash both keep
~/OpenBot and the containers, on purpose, so that an update keeps the person's data.
Use this checklist to show that somebody else can repeat a build and get the same result. Write the results in the PR or the issue for the build.
-
Choose the branch and its commit.
-
Make an internal build (section 1). Record the run URL.
-
Check the build record (section 4, step 1). Record the commit, the CLI version, the Node version and the
lockfileSha256. -
On the Mac, reset, install and test (sections 5 and 3), then do the checks in section 4, steps 2 to 4.
-
On the Windows PC, do the same. Run the reset script with
-DryRunfirst. -
Write down the result:
Item Value Run URL Commit CLI version / Node version Mac: install time, Bot answered, checks passed Windows: install time, Bot answered, checks passed Problems -
Give this runbook, not your notes, to a second person, and ask them to do steps 2 to 6 on another machine. The process is proved when their result matches yours without your help.
| What you see | What to do |
|---|---|
Internal build is not in the Actions list, or gh workflow run says the workflow is not found |
The workflow is not on main yet. Merge the build workflows first. |
| An image job fails | Open the failed job's log and read the first error. A Dockerfile error is a code problem on the branch. Do not run it again until you know the cause. |
The build fails with the pinned deployment manifest ... has commit ..., but this build compiles ... |
The installer is being built from a different commit than its images. Build both from the same commit. |
| A signing run waits | It needs an approver from the table in Before you start. |
| A stable release is published but is not Latest | It becomes Latest once both Mac and Windows signing have attached their signed installers. The attach step of the run that finished says which installers it still needs. Approve the other run. If a run failed, run it again from the release tag with release-tag set and make-latest on. |
Mac or Windows signing of a candidate fails with container-images.json ... was built from ... |
The signing run is not on the candidate's tag. Run it from the tag the release was published from. |
| A fixed download address gives the unsigned build | That platform's signing run was not approved, or failed. Approve it, or run it again from the release tag with release-tag set; its attach step replaces both names. |
| A fixed download address gives an older release | latest/download follows the release marked Latest. A candidate or beta is never Latest, a stable release becomes Latest only once both signing runs have attached their installers, and a patch released while a newer release is out does not become Latest. |
| Windows setup stops with a virtualization message | The machine cannot run WSL 2. Use an x64 PC or an x64 VM with nested virtualization. |
| Setup returns to Install after sign-in | The saved sign-in no longer works. Sign in again. |
| A step shows Retry | Select it once. If it fails again, open Technical details and copy them into the issue. |
| Web search does not work | Add a Composio key to ~/OpenBot/.env. See Install and test. |
copilotkit local delete says the CLI is busy |
The reset script tries again for up to 90 seconds. If it still fails, quit OpenBot and run the reset again. |