Skip to content

OpenBot

An open-source platform from CopilotKit that gives each AI coworker a computer of its own — a container with Chromium, a workspace volume and its own logins — where a coworker is any endpoint speaking AG-UI, and every browser, file, MCP or shell action passes through one gateway that decides it against a CEL policy, writes an audit row and only then acts, or refuses and names the rule.

Screenshot of OpenBot
Editor screenshot, 30 Sep 2026OpenBot ↗

What it is

OpenBot is CopilotKit’s open-source platform for AI coworkers, each of which gets a computer of its own: a container holding a Chromium, a /workspace volume, its own logins and its own browser profile. A coworker is any endpoint that speaks AG-UI, so an agent written with LangGraph, CrewAI, Pydantic AI, Google ADK or by hand arrives the same way, and thirteen ship in the example package as YAML configuration rather than code. Everything a Bot does to a browser, a file, an MCP server or a component passes through one server-side gateway, which resolves the target, evaluates a CEL action policy, writes an audit row and only then calls the computer — and a refusal names the rule that caused it. The repository is a template rather than a product: there is no hosted version, nothing is published as a package, and the README says to clone it and replace the example tenant package with your own coworkers, channels and skills. It comes up on a laptop from Docker Compose, needs a CopilotKit Intelligence project and a single model key, and includes an optional Tauri desktop app.

Who built itAn organization account rather than a person. The repository lists 33 contributors across 451 commits in about six weeks: davidmckayv wrote 126 of them, kevin9327 91, Ayush7614 48 and zopeVaibhav 37, with a GitHub Actions bot on 12 and Renovate on 6. The history carries 242 co-author trailers, and the name most often in that list is David McKay, on 130 of them, with a further 13 written as davidmckayv — so the busiest account and the most frequently credited co-author carry the same name, and the next largest group of co-authors after him is a model.

How it is put together

The parts · 6

A Bot is a process on the far side of an AG-UI endpoint, and the product is everything around it: a supervisor that makes a container for it, a computer inside that container holding a real browser, and a server that stands between the two and the rest of the deployment. Three consequences shape the code. The first is that the gateway is the only way in, which is why the largest file in the server is the gateway and why target resolution, policy and audit are modules of their own rather than checks sprinkled through routes. The second is that a Bot is treated as a process that can be careless or hostile: it has a shell, so the database is put on a network the computer is not on; computers bind to loopback behind a token; the supervisor holds the Docker socket and offers four operations; the credential store encrypts at rest and never returns what it holds; and a secret is recorded as having been asked for and how long it was, not what it said. The third is that this is a template rather than a service — thirteen coworkers ship as YAML, no model ships at all, no workspace is published as a package, and the repository is written to be cloned and rewritten rather than operated by its authors for other people.

server/
The API and the decision point: 143 source files, led by a 105 KB CopilotKit runtime, a 67 KB app, a 66 KB entry point and a 55 KB configuration module, with the 49,664-byte computer gateway, a 37 KB audit module, policy, snapshot and supervisor clients, plus plugins, channels, routines, voice, auth and learning. Beside it sit 222 tests — one, plugin-store.integration.test.ts, is 319,249 bytes — and 97 Drizzle files holding migrations 0000 to 0047, whose names are sentences such as truncate_is_not_a_way_around_append_only.
app/
The interface: 272 files of React and Vite covering the composer, transcripts, the computer panel with its live screen, the skills and agents surfaces and eighteen admin route files, with 132 tests next to them, one of which is 114,325 bytes. The largest sources are the composer at 82,677 bytes, the chat transcript at 74,777 and the channel chat at 45,055 — this is where the product spends its surface area.
agent-computer/ and supervisor/
The computer and the thing that hands out one per Bot. agent-computer is 25 source files around a 60,722-byte entry point, with profiles at 27,263 bytes, the workspace at 15,459, the shell at 13,819, control handover at 11,892 and screenshots at 9,177, watched by 32 test files. The supervisor is seven files whose 27,198-byte Docker module creates, stops, resets and lists containers, with one memory limit, one identity and one name allocator.
The fourteen agent-* directories
The harness fleet: agent-bot as a proof of concept, agent-langgraph in TypeScript, agent-mastra, agent-langgraph-agui, and eleven Python Bots each with its own Dockerfile, requirements files and a tests directory holding a main test, a learning-delivery test and a model-spec test. They exist so that the README can say a Bot is any AG-UI endpoint and mean it — the largest, agent-crewai/tests/test_main.py, is 26,918 bytes.
desktop/
A Tauri desktop application: 70 files and 4,563 KB of Rust in src-tauri, with main.rs at 416,470 bytes, stack.rs at 318,549 and env.rs at 101,450, plus Windows code signing and signature-verification scripts in PowerShell, a macOS microphone note, a telemetry document and its validator, and a provider picker with its own 35,009-byte test.
shared/, charts/openbot/, docker/s6/ and scripts/
Everything that has to agree across languages and deployments. shared/model-providers.json is the single file naming each provider’s key variable, endpoint variable and default model, read by a TypeScript loader and a Python one. charts/openbot is 33 files of Helm with a 28,138-byte values file, a 19,951-byte validation template, a 13,888-byte network policy and CI values for AKS, EKS, GKE and self-hosted. docker/s6 is 27 files of s6-rc definitions that make one image carry the API, the computer, the migrations and optionally PostgreSQL, and scripts/start.sh at 21,826 bytes is what a clone actually runs.

Choices, and what they beat

  • A computer per Bot rather than one shared computer over pointing every Bot at the same browser container

    With COMPUTER_SUPERVISOR_URL each Bot gets its own container, its own workspace volume and its own browser profile; without it every Bot shares one AGENT_COMPUTER_URL. The README treats the per-Bot case as the point of the feature, and the computer is where the difference between an agent that can use your tools and an agent you can let near them is enforced.

  • The gateway is the only path, so the record exists before the act does over letting the computer decide what it is allowed to do

    The computer itself does not decide policy: the server resolves the target from its own snapshot, evaluates the policy, writes an audit row, and calls the computer only when the decision forwards. The README states the consequence directly — there is no path that acts without the record existing first — and the computer’s own lower-level endpoints are documented as ones not to use to bypass the gateway.

  • One spec file for provider facts, read by two languages over a provider table kept separately in TypeScript and in Python

    A single JSON file carries which environment variable holds a provider’s key, which holds its endpoint and which model runs when nothing else does, plus a row per Bot. Both loaders validate the whole file at startup and stop the Bot with the path of the offending key rather than failing at its first model call, and each language has its own test pinning its provider list to that same file, so adding a provider to one side and forgetting the other fails a test.

  • A failed read must not look like an empty result over letting a pending query fall through to its empty state

    Three separate changes take the same position: isPending goes false on a failed fetch exactly as on a successful one, so the skills pages told people with a dozen skills that they had none, the connector pages said a connector does not exist, and the Boundaries screen showed a title over nothing. Each fix routes the failure to an error state with the server’s own reason where there is one, because an empty state shown before the read arrives is, in the repository’s own words, a claim the page has not yet earned.

  • Defer signing and notarization deliberately rather than ship a half-signed build over signing the macOS desktop build in the workflow that produces it

    The desktop workflow carries the comment that the bundle step signs and notarizes and is deliberately not run there, because it needs certificates the workflow does not hold; it uses the ad-hoc identity - instead, and signing is tracked as a named step of its own. The distinction matters to the issue that asked for a signed DMG: it is a deferred piece of work with a label, not something nobody noticed.

  • Cut text between characters rather than at a code unit over slicing a fixed number of UTF-16 code units

    Voice caps the context it joins, the captions it shows and the answers that come back, and every cap was a plain slice, which can land between the two halves of an emoji and hand the model or the transcript half a character. The caps now cut between characters, at most one unit shorter and never over the cap, matching the rule the routing text and the server already applied.

Read fromdocs/architecture.md (28,069 characters), README.md (28,799 characters), docs/configuration.md (54 KB), CHANGELOG.md (291,939 bytes), .github/workflows/ci.yml (30,219 bytes), .env.example (26,511 bytes), docker-compose.yml (22,056 bytes), scripts/start.sh (21,826 bytes), the two skill documents under .claude/skills/, and the complete 1,382-file tree with sizes.

Build log

6 stages
  1. 01

    Six weeks, fifteen releases and a 292 KB changelog

    The repository was created on 2026-08-17, and its oldest commit is dated two days earlier than that. In the six weeks since, 451 commits landed — 178 in August and 273 in September — released as fifteen versions, from v0.0.1 on the day it was created to v0.0.15 on 2026-09-22, with v0.0.2, v0.0.3 and v0.0.4 all cut on the same day, 2026-08-22. The tree holds 1,382 files. CHANGELOG.md is 291,939 bytes and .env.example is 26,511, so the running account of what changed is larger than most of the code it describes. By the end of September it had 5,761 stars, 765 forks, 23 watchers and 18 open issues, under MIT and written in TypeScript, and the README carries a badge for third place on a daily trending ranking. A repository that grows this fast is usually a pile of scripts; this one arrives with a Helm chart, a Tauri desktop application, an s6 service tree that lets one container carry the app, the API, the browser and optionally PostgreSQL, and 222 test files in the server directory alone.

  2. 02

    One monorepo, fourteen agent harnesses and a browser in a box

    The layout says what the product is. app/ is a React and Vite interface of 272 files, server/ is a Hono API of 143 source files with 222 test files and 97 Drizzle files beside it, and agent-computer/ is the piece the whole idea rests on: 25 source files that own a Chromium, a /workspace volume, browser profiles, screenshots, ARIA snapshots, a shell and the file tools. supervisor/ is seven files that hold the Docker socket and do four things — ensure, stop, reset and list — one container per Bot, bound to loopback because its token is a shared secret rather than a network boundary. Around those sit fourteen harness directories: eleven Python Bots (ADK, AG2, Agno, the Claude SDK, CrewAI, LangGraph AG-UI, Langroid, LlamaIndex, the Microsoft Agent Framework, Pydantic AI and Strands), a LangGraph Bot in TypeScript, a Mastra Bot, and a hand-written proof of concept. Each Python one carries a Dockerfile, a requirements file and three tests, one of which pins it to a shared model specification. None of the fleet is privileged, because a Bot is only an AG-UI endpoint: the governance rides the protocol rather than the framework.

  3. 03

    The gateway decides, records, and only then acts

    The product claim is one sentence, and the code is arranged around it: every action a Bot takes against a browser, a file, an MCP server or a component comes back to the server first. server/src/computer/gateway.ts is 49,664 bytes and runs a five-step loop — resolve the target from a server-held snapshot, evaluate the current policy, write an audit row for the decision, call the computer only when the decision forwards, and write a second row if a forwarded action fails. The policy is CEL, deny is evaluated before allow, and the engine fails closed: a missing or empty policy permits nothing, a broken deny rule denies, and a broken allow rule does not permit. A malformed configured policy stops server startup, and the shipped default is written out explicitly as deny: [] with allow: ["true"] rather than left to be inferred. A rule can inspect tool.name, intent, page.host, element.*, key, command, file.*, mcp.* and initiator.* — what started the run, which is the only field that can tell an unattended routine from somebody typing. The network is part of the design as well: a Bot has a shell, and a shell reaches whatever its container reaches, so PostgreSQL sits on a Docker network of its own, computers bind to 127.0.0.1 behind a per-container token, and a host that supports it can run them under gVisor with COMPUTER_RUNTIME=runsc.

  4. 04

    Six weeks of bug reports, and the same mistake three times

    The pull requests read like a catalogue of ways an interface can lie to the person using it. In #665 the skills page, the admin skills page and the skill editor all branch on isPending, which goes false on a failed fetch just as on a successful one — so somebody with a dozen skills was told “You don’t have any skills yet.”, while the page’s own comment already called an empty state before the read arrives a claim the page has not yet earned. #664 is the same bug on three connector pages, and #656 is the same again, a regression from #60, leaving the Boundaries screen with a title over nothing while it stays open. The rest is equally specific: #657 refuses an administrator’s grant of a skill nobody has written, because the upsert stored the row anyway; #653 deletes the temporary file left behind when a Bot’s browser-control state fails to save; #651 resets a live screen’s reconnect counter only when a frame arrives; #652 cuts voice text between characters rather than at UTF-16 code units. Three more are portability: the start script ends on macOS with a bad substitution, because ${name^^} is bash 4 syntax; Docker Compose v5 needs COMPOSE_PROGRESS=quiet where a flag would be rejected by older versions; and a health check that shelled out to python3 breaks on Git Bash, where that name is a Microsoft Store alias that exits 49.

  5. 05

    Thirty-three contributors, and model names in the trailers

    All 451 commits carry a linked account, and 33 people have contributed. The largest share is davidmckayv with 126, then kevin9327 with 91, Ayush7614 with 48 and zopeVaibhav with 37; a workflow bot accounts for 12 and Renovate for 6. What makes the history unusual is the trailers: 242 co-author trailers, and after David McKay, credited on 130 of them, the next largest group is a model. Claude Opus 5 appears on 41, Claude Opus 5.5 on 8, Claude Fable 5.1 on 5, Claude Opus 5 in a million-token context on 3, Claude Opus 4.8 on 3 and Claude Fable 5 once, with one mention of Warp and two of something called pi. Review is automated too: on eight of kevin9327’s pull requests the only comment is a Codex connector reporting that it has reached its usage limits for code reviews. Outside contributors are not waved through — on #649, which makes one JSON file the single place that names a provider’s key variable, endpoint variable and default model for both TypeScript and Python, a commenter says he checked it on main at a given commit rather than taking it on trust, and then points at the two tests that pin each language’s provider list to that file. Two further pull requests, #670 and #671, arrive from an automated security scanner, and the maintainer answers both by reproducing the failures, pushing a follow-up commit and adding a test that fails against the old code.

  6. 06

    The architecture document says what does not work yet

    There are ten documents under docs/, led by a 54 KB configuration reference and a 28,069-character architecture document, and the second is worth reading for what it admits rather than what it promises. It records that channels.allowed_groups is declared and stored but takes no part in the access decision, because users.groups is never populated by any sign-in path — so a group rule has nothing to evaluate and should be treated as a declaration waiting on the identity provider rather than a control that is running. It calls the Activity tab a window rather than a record, since it lives in the browser and is gone on reload while the audit trail is server-side and survives restarts. It explains that a saved file contributes its path and size and never its contents, matching a write route that declines to echo them because a Bot may be saving something it was told in confidence. It notes that computer actions carry no initiator, and correctly so, because they are executed by the person’s own browser session. The desktop side shows the same habit from the other direction: a commenter points out that the macOS signing workflow holds no certificates on purpose, leaves the ad-hoc identity - in place, and tracks signing and notarization as a named, deliberately deferred step rather than an oversight.

Adjacent records

All records →