<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Claude Cowork Alternative on RockB</title><link>https://baeseokjae.github.io/tags/claude-cowork-alternative/</link><description>Recent content in Claude Cowork Alternative on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Wed, 30 Sep 2026 07:08:48 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/claude-cowork-alternative/index.xml" rel="self" type="application/rss+xml"/><item><title>CopilotKit OpenBot Review 2026: AI Coworkers That Each Get Their Own Browser</title><link>https://baeseokjae.github.io/posts/openbot-copilotkit-ai-coworkers-browser/</link><pubDate>Wed, 30 Sep 2026 07:08:48 +0000</pubDate><guid>https://baeseokjae.github.io/posts/openbot-copilotkit-ai-coworkers-browser/</guid><description>CopilotKit OpenBot review: an MIT, self-hosted platform where every AI coworker gets its own browser, its own logins, and a fail-closed policy gateway.</description><content:encoded><![CDATA[<p>CopilotKit OpenBot is an MIT-licensed, self-hosted platform where each AI coworker runs in its own container with its own Chromium browser, its own persistent logins, and where every action is decided against a fail-closed policy — and audited — before it can happen. As of 30 September 2026 it has 5,737 GitHub stars, 762 forks and 33 contributors.</p>
<p>This is a second, deliberately different look at the same product. Our <a href="/openbot-ai-coworker-computer-2026/">first OpenBot review</a> covered the &ldquo;a computer per coworker&rdquo; model in September. This one answers the questions that review left open: which of the four products called OpenBot you actually want, what one browser per coworker costs you operationally, and — the part most coverage gets wrong — which hardening in OpenBot is enforced by the software versus which is merely documented and left off.</p>
<h2 id="what-is-copilotkit-openbot">What Is CopilotKit OpenBot?</h2>
<p>OpenBot is the open-source, self-hosted half of CopilotKit&rsquo;s enterprise agent platform. The repository was created on 17 August 2026, announced publicly on 19 August, and is written mostly in TypeScript (roughly 83%) with about 12.8% Rust. CopilotKit raised a $27M Series A in May 2026 led by Glilot Capital, NFX and SignalFire, and OpenBot is the layer that lets a company run governed AI coworkers inside its own infrastructure instead of renting a hosted one.</p>
<p>The product&rsquo;s own description is unusually blunt about its maturity. The README carries two blockquotes worth reading before anything else:</p>
<blockquote>
<p><strong>A template, not a product.</strong> OpenBot is meant to be cloned and made your own. There is no hosted version to sign up for, and nothing here is published as a package to depend on: every workspace in this repository is private.</p></blockquote>
<blockquote>
<p><strong>Alpha, and under active development.</strong> OpenBot is early. Expect rough edges and bugs, and expect things to move.</p></blockquote>
<p>That framing matters because it separates OpenBot from almost every product it gets compared to. You are not buying a service; you are adopting a reference implementation of governed agent execution that you operate yourself, on your own hardware, with your own model key.</p>
<p>The honest one-line positioning: OpenBot does not compete with open-source agent frameworks on capability. It competes with hosted coworker products — Grok Bot, Claude Cowork — on governance, and it wants to host whatever framework you already have.</p>
<h2 id="three-products-called-openbot--which-one-you-want">Three Products Called OpenBot — Which One You Want</h2>
<p>Search for &ldquo;openbot&rdquo; and you land on at least four different things. This is not a minor annoyance; the name collision is why &ldquo;openbot ai coworkers&rdquo; has no Google autocomplete while &ldquo;openbot&rdquo; and &ldquo;openbot copilotkit&rdquo; do. Search intent is split, so define which one you mean before you evaluate anything.</p>
<table>
  <thead>
      <tr>
          <th>Product</th>
          <th>What it is</th>
          <th>Where it lives</th>
          <th>Who it is for</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CopilotKit OpenBot</td>
          <td>Self-hosted AI coworker platform; per-coworker browser container plus a fail-closed policy gateway and audit trail</td>
          <td>github.com/CopilotKit/OpenBot (MIT)</td>
          <td>Teams running governed agents inside their own infrastructure</td>
      </tr>
      <tr>
          <td>ob-f/OpenBot</td>
          <td>A $50 smartphone robot from Intel ISL; a phone becomes a wheeled robot with an Arduino board. Published as arXiv 2008.10631 in 2018</td>
          <td>github.com/ob-f/OpenBot</td>
          <td>Robotics hobbyists and researchers</td>
      </tr>
      <tr>
          <td>getopenbot.com</td>
          <td>A paid cloud &ldquo;coordinator&rdquo; for coding agents such as Claude Code, Codex and Cursor, priced around $60/month</td>
          <td>Hosted SaaS</td>
          <td>Individual developers running coding agents</td>
      </tr>
      <tr>
          <td>openbot.one</td>
          <td>Another hosted agent workspace using the same bare name</td>
          <td>Hosted SaaS</td>
          <td>Small teams wanting a managed agent workspace</td>
      </tr>
  </tbody>
</table>
<p>If you arrived here from &ldquo;openbot ai coworkers&rdquo;, you want the first row. If you want a robot, you want the second. If you want someone else to run it for you, the third and fourth rows exist — but note that CopilotKit&rsquo;s own commercial answer for that is &ldquo;talk to an engineer&rdquo;, not a self-serve sign-up.</p>
<p>The disambiguating convention worth adopting: write &ldquo;CopilotKit OpenBot&rdquo; when you mean the AI coworker platform. The bare name is contested and will keep getting more contested.</p>
<h2 id="what-each-coworker-gets-its-own-browser-actually-means">What &ldquo;Each Coworker Gets Its Own Browser&rdquo; Actually Means</h2>
<p>The phrase is literal, and the mechanism is a single environment variable with a large blast radius.</p>
<p><code>COMPUTER_SUPERVISOR_URL</code> points the platform at a supervisor service that creates one container per Bot. Each container runs its own Chromium, holds its own persistent browser profile, mounts its own <code>/workspace</code> volume, and exposes its own file and shell tools. When that variable is absent, every Bot shares one <code>AGENT_COMPUTER_URL</code> instead — a materially weaker guarantee, and one of the most commonly missed configuration facts in third-party write-ups.</p>
<p>Why does a browser profile deserve a container of its own? Because a browser profile is an identity. Cookies, session tokens, saved logins and localStorage all live in that profile, and any agent driving that browser acts with whatever authority those sessions carry. A shared browser session means a research coworker can inherit the signed-in session of a publishing coworker, and your audit trail then records an action that a different role authorised.</p>
<p>Under the supervisor model, a bot asked to run <code>psql</code> against the product database finds nothing to connect to: PostgreSQL sits on a separate Docker network that only the API server and the migration job can reach. Computers bind to loopback and require a per-container token — <code>COMPUTER_TOKEN</code> is required or the computer refuses to start — so nothing on the host network can guess its way into a logged-in browser.</p>
<p>The supervisor itself holds the Docker socket, which is why the docs are explicit that you must not expose it outside the deployment network. Docker Compose binds it to <code>127.0.0.1:4500</code>, and a deployment running the server inside the compose network reaches it as <code>supervisor:4300</code> and needs no published port at all.</p>
<p>There are ceilings here too, and they matter for capacity planning. <code>COMPUTER_MAX_BROWSERS</code> defaults to 8 concurrent resident browsers, with the least recently used closed past that limit. <code>COMPUTER_BROWSER_IDLE_MS</code> closes an untouched browser after 30 minutes by default. If you run twelve coworkers and five of them need a browser simultaneously, they queue.</p>
<h2 id="the-gateway-decided-before-recorded-after">The Gateway: Decided Before, Recorded After</h2>
<p>OpenBot&rsquo;s central claim is that the gateway, not the browser, is the action boundary. The architecture documentation states the sequence as a fixed, ordered five-step path:</p>
<ol>
<li>Resolve the target from the server-held snapshot or request subject.</li>
<li>Evaluate the current action policy.</li>
<li>Write an audit row for the decision.</li>
<li>Call the computer only when the decision forwards.</li>
<li>Write a second audit row if a forwarded action fails.</li>
</ol>
<p>The ordering is the point. A bot cannot invent an element reference, because the reference it can act on comes from a snapshot the server took — so the model cannot fabricate a target and reach for it. And no forwarded action can complete without the row describing it already existing.</p>
<p>This is the distinction that gives the design its edge: most agent tooling records after the fact. The decision happens before, and the record includes refusals and failures, not only successes. The audit screen shows permitted, refused and failed rows together, which is what makes it usable for a review conversation rather than a debugging one.</p>
<p>A caution worth stating plainly: <strong>computer actions are the one family that carries no initiator, and correctly so</strong>. The architecture docs note that the computer tools are browser actions executed by the person&rsquo;s own session, so a headless run currently has no way to drive the computer at all. In other words, the governance story applies to the gateway-mediated families — files, shell, MCP, components — while today&rsquo;s browser control is inherently a person-present activity. Anyone claiming OpenBot gives you fully unattended browser automation governed under <code>initiator.kind</code> is overselling the current release.</p>
<h2 id="the-cel-policy-engine-fail-closed">The CEL Policy Engine, Fail-Closed</h2>
<p>Policies are written in CEL and live either in the <code>AGENT_COMPUTER_POLICY</code> environment variable or in the <code>/admin/boundaries</code> screen. The semantics are stated precisely in the README, and precision here is what earns a security reviewer&rsquo;s trust:</p>
<ul>
<li>Deny is evaluated before allow.</li>
<li>A missing policy permits nothing.</li>
<li>A broken rule refuses rather than opens.</li>
</ul>
<p>The rule fields you can inspect are concrete: <code>tool.name</code>, <code>intent</code>, <code>bot.id</code>, <code>actor.id</code>, <code>page.url</code> and <code>page.host</code>, <code>element.ref</code>/<code>role</code>/<code>name</code>/<code>type</code>, <code>key</code>, <code>command</code>, <code>file.path</code>/<code>name</code>/<code>extension</code>, <code>mcp.server</code>/<code>tool</code>/<code>effect</code>, and <code>initiator.kind</code>/<code>initiator.id</code>.</p>
<p>The shipped default is deliberately permissive — <code>deny: []</code>, <code>allow: ['true']</code> — which is the correct default for a template and the wrong default for a deployment. Nothing about the fail-closed semantics helps you until you write rules.</p>
<h3 id="why-initiatorkind-matters-more-than-actorid">Why initiator.kind Matters More Than actor.id</h3>
<p><code>actor.id</code> records on whose authority an action was taken. A routine asserts its owner&rsquo;s identity, so a scheduled 9am job shows up as <em>Andrew</em> — exactly as it would if Andrew typed the request himself. That column alone cannot tell you whether anybody was in the room.</p>
<p><code>initiator.kind</code> answers that. The four values are:</p>
<table>
  <thead>
      <tr>
          <th><code>initiator_kind</code></th>
          <th><code>initiator_id</code></th>
          <th>What it means</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>person</code></td>
          <td>none</td>
          <td>Somebody was in the room. The default.</td>
      </tr>
      <tr>
          <td><code>deployment</code></td>
          <td>none</td>
          <td>The deployment itself, at start-up or refusing a caller it could not identify.</td>
      </tr>
      <tr>
          <td><code>routine</code></td>
          <td>the routine&rsquo;s id</td>
          <td>A schedule fired it, as its owner, with nobody there.</td>
      </tr>
      <tr>
          <td><code>handoff</code></td>
          <td>the Bot that handed on</td>
          <td>Another Bot asked for this, on the person&rsquo;s behalf.</td>
      </tr>
  </tbody>
</table>
<p>The audit screen filters on this, and its <strong>Nobody watching</strong> view is <code>routine</code> and <code>handoff</code> together — the exact question of what ran on somebody&rsquo;s authority while they were away. <code>deployment</code> is deliberately excluded, because a boundary held at start-up is not work done on anybody&rsquo;s behalf.</p>
<p>The value travels inside a signed run assertion, so a Bot cannot relabel its own run; an unknown kind is read as <code>person</code> rather than trusted. That is a real security property, not a field name.</p>
<p>This is what enables the most interesting rule class in the product: refuse an MCP write on a <code>routine</code>-started run while allowing the same call when <code>initiator.kind == &quot;person&quot;</code>. Most agent governance tooling cannot express that sentence.</p>
<h3 id="five-deny-rules-worth-copying">Five Deny Rules Worth Copying</h3>
<p>The policy engine is abstract until you read a rule. These map directly onto the documented field list:</p>
<ul>
<li>Refuse destructive shell: deny when <code>command</code> matches a recursive delete or a disk-format pattern, for every bot, no exceptions.</li>
<li>Keep every bot off finance hosts: deny when <code>page.host</code> ends in your finance domain, regardless of which bot is driving or who started the run.</li>
<li>Refuse MCP writes on unattended runs: deny when <code>mcp.effect == &quot;write&quot;</code> and <code>initiator.kind == &quot;routine&quot;</code>, so a schedule can read but never mutate.</li>
<li>Never type into a credential field: deny when <code>element.type == &quot;password&quot;</code> — the bot should stop and ask, which is the designed behaviour anyway.</li>
<li>Restrict file writes to the workspace: deny when <code>file.path</code> is outside <code>/workspace</code>, which stops a bot talked into writing to a host-mounted path.</li>
</ul>
<p>Because deny is evaluated before allow and a broken rule refuses, a typo in any of these fails toward blocking rather than toward opening. That is the right direction for the mistake.</p>
<h2 id="isolation-egress-and-secrets">Isolation, Egress and Secrets</h2>
<p>The isolation story is more careful than the marketing suggests, and the details are where it holds up.</p>
<p>Computers bind to loopback with a per-container token. PostgreSQL lives on a separate Docker <code>data</code> network reachable only by the server and the migration job. Browser navigation is limited to plain http and https, and cloud-metadata-looking addresses are refused regardless of any other configuration — closing the standard credential-theft route through <code>169.254.169.254</code>. Critically, the same target validation applies to custom AG-UI agent endpoints, not just page navigation, and an endpoint&rsquo;s auth header is stored write-only.</p>
<p>Per-bot egress needs a separate file. The <code>EGRESS_PROXY_&lt;BOT_ID&gt;</code> and <code>EGRESS_PROXY_DEFAULT</code> variables live in <code>egress.env</code> at the repository root, not in <code>.env</code>, and the file is optional and gitignored. The separation is deliberate: <code>.env</code> holds the deployment&rsquo;s secrets and neither the browser container nor the supervisor is given those. Without the file, every bot&rsquo;s browser goes out directly, which is the default.</p>
<p>Secrets are handled with more discipline than most tools manage. Credentials are stored through <code>/admin/credentials</code>, encrypted at rest with <code>KEY_ENCRYPTION_KEY</code>, never returned by any API, and redacted from audit events. The trail records that a secret was requested and its character count — not its value. A shell command on the computer inherits only PATH, locale, terminal names and the proxy variables by default; userinfo is stripped from proxy URLs, so a password in <code>HTTP_PROXY</code> does not appear in <code>env</code>.</p>
<p>One more mechanism worth knowing: if a tool is not positively classified as a read, it is treated as a write. That default is why an MCP catalogue can ship connectors for Atlassian, Box, Slack, Salesforce and ServiceNow without each one needing a bespoke policy.</p>
<h2 id="skills-are-instructions-grants-are-capabilities">Skills Are Instructions, Grants Are Capabilities</h2>
<p>The cleanest mental model in the documentation is this split, and getting it wrong is the most common way a deployment ends up with more access than intended.</p>
<p>A skill is a written instruction — a description of how to do a job. Listing &ldquo;find a document&rdquo; on the Knowledge bot loads nothing. Until an administrator connects Google Drive and grants those tools, the skill is text.</p>
<p>Capabilities are granted separately and through different paths: MCP grants are per bot, deployment skills are granted by admins, and personal skills only load on bots their author owns. A coworker&rsquo;s <em>role</em> never confers capability. UI components publish deployment-wide and can be withheld per bot.</p>
<p>The audit consequence is straightforward: if you read a bot&rsquo;s skill list and assume it describes what the bot can do, you have misread the model. The grant list is the capability surface. Read the grant list.</p>
<p>The v0.0.15 changelog adds a related guard worth noting — an administrator can no longer create a grant naming a skill that does not exist, because on a shared deployment that row would wait for whoever wrote a skill under that name next, and one person&rsquo;s instructions would end up answering everybody.</p>
<h2 id="take-the-wheel-human-handover-for-2fa-and-login-walls">Take the Wheel: Human Handover for 2FA and Login Walls</h2>
<p>Bots do not handle 2FA. That is the design, not a gap.</p>
<p>When a coworker hits a login wall or a 2FA prompt, it stops and asks for help. A person takes control of the same live browser panel, completes the step, and hands it back. The profile persists, so subsequent runs stay signed in. Since v0.0.5 a person can seize control at any moment rather than only when the bot asks.</p>
<p>The governance detail is what happens during the handover. It is recorded as three control events — <code>computer.help_requested</code>, <code>computer.control_taken</code>, <code>computer.control_released</code> — and while a person is driving, bot actions are refused rather than queued. Refused, not deferred: nothing the bot attempted during human control sits in a queue waiting to fire when the human lets go. Secret entry is treated as separate from chat content, and the trail records the request and the character count, never the value.</p>
<p>For a security reviewer, this is the section that answers &ldquo;what happens at 2am when the agent hits a wall?&rdquo; Somebody takes the wheel, from somewhere, and the fact that they did is in the log.</p>
<h2 id="hardening-what-ships-off-and-what-the-software-actually-enforces">Hardening: What Ships Off, and What the Software Actually Enforces</h2>
<p>This is the section no competing write-up has assembled in one place, and it is also where I found the most common factual error in circulation.</p>
<p>Several published takes list <code>OPENBOT_SINGLE_USER=true</code> as a &ldquo;footgun you must disable before exposing an install&rdquo;. The first half is true; the second half is not how it works. The configuration documentation states it plainly:</p>
<blockquote>
<p><strong>With no provider at all, <code>OPENBOT_SINGLE_USER=true</code> is required.</strong> A deployment that configures nothing to sign anybody in and does not say that was deliberate refuses to start, naming what to configure, because a public URL where every visitor is an administrator fails silently.</p></blockquote>
<p>And further:</p>
<blockquote>
<p><strong>But not on a public address.</strong> &hellip; If <code>OPENBOT_PUBLIC_URL</code>, <code>OPENBOT_APP_URL</code> or any <code>TRUSTED_ORIGINS</code> entry is an address the public internet routes to, the deployment refuses to start and names it. Loopback is silent. A private address is allowed and warned about once at boot, because a home server, a Tailnet, a VPN address and a <code>.local</code> name are what this flag is mostly used for, and anybody on that network is the administrator.</p></blockquote>
<p>So the accurate statement is stronger than &ldquo;turn it off&rdquo;: you cannot expose a single-user OpenBot on a public address — it will not boot. Anyone on a private network that can reach it <em>is</em> the administrator, and that is a deliberate, warned-about trade-off, not an oversight. The failure mode to avoid is running it on a Tailnet or corporate VPN where you have not thought about who is on that network.</p>
<p>The rest of the switches are honestly off by default, and each one is a real deployment decision:</p>
<table>
  <thead>
      <tr>
          <th>Switch</th>
          <th>Shipped state</th>
          <th>What enabling it buys you</th>
          <th>What the software enforces</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>OPENBOT_SINGLE_USER</code></td>
          <td><code>true</code> in <code>.env.example</code></td>
          <td>Nothing — it is the no-sign-in mode. Configure an OAuth provider instead (Google, Microsoft Entra, Okta)</td>
          <td>Refuses to start on a public address; warns once on a private one</td>
      </tr>
      <tr>
          <td><code>COMPUTER_SUPERVISOR_URL</code></td>
          <td>empty</td>
          <td>One container per Bot with its own browser profile instead of one shared computer</td>
          <td>Nothing — an empty value silently falls back to a shared <code>AGENT_COMPUTER_URL</code></td>
      </tr>
      <tr>
          <td><code>COMPUTER_RUNTIME=runsc</code></td>
          <td>empty</td>
          <td>Runs supervised computers under gVisor</td>
          <td>Nothing — opt-in only</td>
      </tr>
      <tr>
          <td><code>AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS</code></td>
          <td>commented out</td>
          <td>Lets a bot reach host services (rarely what you want)</td>
          <td>A <code>NODE_ENV=production</code> deployment refuses to start while it is set</td>
      </tr>
      <tr>
          <td><code>egress.env</code></td>
          <td>absent</td>
          <td>Per-bot egress proxy, so you can attribute one bot&rsquo;s traffic</td>
          <td>Nothing — absent means every browser goes out directly</td>
      </tr>
      <tr>
          <td><code>INITIAL_ADMIN_EMAILS</code>, <code>BETTER_AUTH_SECRET</code></td>
          <td>unset</td>
          <td>Real sign-in with an identity provider</td>
          <td>Required with any provider; incomplete combinations are refused at start-up</td>
      </tr>
      <tr>
          <td><code>KEY_ENCRYPTION_KEY</code></td>
          <td>example value</td>
          <td>Encrypted credential storage in production</td>
          <td><code>NODE_ENV=production</code> refuses the example key</td>
      </tr>
      <tr>
          <td>TLS termination</td>
          <td>not shipped</td>
          <td>Your deployment stops being plaintext</td>
          <td>Nothing — you front it</td>
      </tr>
  </tbody>
</table>
<p>Two rows deserve emphasis. The second one — a missing <code>COMPUTER_SUPERVISOR_URL</code> quietly collapsing the product&rsquo;s headline isolation into a shared browser — is the single highest-impact misconfiguration available, and nothing in the software stops you. The fifth — an absent <code>egress.env</code> meaning every bot&rsquo;s browser exits your network directly — is the one most likely to fail an egress audit.</p>
<p>Also worth knowing: <code>NODE_ENV</code> does <strong>not</strong> decide whether sign-in is required. The docs are explicit that <code>NODE_ENV=production</code> refuses the example encryption key but has nothing to do with authentication. Assuming production mode implies authentication is a mistake this product&rsquo;s own docs call out.</p>
<h2 id="what-it-costs-to-run">What It Costs to Run</h2>
<p>OpenBot&rsquo;s licence costs nothing. The operational bill is the real price, and it is worth pricing per unit rather than in adjectives.</p>
<p>Things that must stay alive: PostgreSQL with pgvector, the API server on port 3001, the app on 3010, the supervisor, and one browser container per active coworker. <code>scripts/start.sh</code> starts PostgreSQL, <code>agent-computer</code>, <code>agent-bot</code>, <code>agent-langgraph</code> and the supervisor through Docker Compose, then starts the server and app on the host. The compose file also defines optional SPIRE services for attested identity, which <code>start.sh</code> does not start.</p>
<p>The unit that surprises people is Chromium. Four coworkers on shift means four resident Chromium instances, capped at eight by default with a 30-minute idle close. The machine must be reachable whenever anyone opens a channel, because coworkers are a chat surface, not a batch job.</p>
<p>Routines have their own ceilings, and the docs give the reason: a routine may fire at most every 15 minutes, because a model can be talked into anything a sentence can describe — including &ldquo;every minute&rdquo; — and the floor is what a sentence cannot talk it out of. A person may have at most 20 routines switched on at once, for the same reason: a conversation is an easy place to accumulate standing work without noticing. A failing routine posts exactly one message about it — the first failure after a success, not every failure — and ten consecutive failures switch the routine off with a final message.</p>
<p><code>AGENT_STALL_TIMEOUT_MS</code> bounds a bot&rsquo;s stream: the documented behaviour is that a bot&rsquo;s turn is ended when the stream produces nothing. Note the primary source states it as &ldquo;unset (off)&rdquo; in the configuration table while the <code>.env.example</code> ships <code>60000</code>; treat the <code>.env</code> value as your deployment&rsquo;s actual setting rather than assuming a default.</p>
<p>And then there is the dependency that the MIT licence does not cover.</p>
<h2 id="what-mit-does-not-cover">What MIT Does Not Cover</h2>
<p>The repository is MIT and no enterprise tier gates the audit log, SSO or SAML/OIDC. But the surrounding requirements are not MIT code:</p>
<ul>
<li><strong>CopilotKit Intelligence</strong> is an external, separately licensed service that provides durable threads, memory and the realtime gateway. Every independent reviewer lands on the same point, and Zentor states it outright: the code being MIT does not make the whole deployment dependency-free. There is a free plan and a self-hosted option, and there is no degraded mode if it is unavailable.</li>
<li><strong>Docker</strong> is required as shipped. There is no supported path that skips it.</li>
<li><strong>Bun 1.3+</strong> is required to run the app and API server.</li>
<li><strong>A model key you supply.</strong> No model ships in the box.</li>
<li><strong>An operator.</strong> Somebody has to run five services and maintain a fork.</li>
</ul>
<p>That list is the honest answer to &ldquo;is OpenBot free?&rdquo; — the software is free, the stack is not free of dependencies.</p>
<h2 id="what-changed-since-v0013--and-why-week-old-reviews-are-stale">What Changed Since v0.0.13 — and Why Week-Old Reviews Are Stale</h2>
<p>The release cadence is the fastest-moving thing about this project: v0.0.12 (15 Sep), v0.0.13 (18 Sep), v0.0.14 (21 Sep), v0.0.15 (22 Sep). The most-cited third-party review was written against v0.0.12 on 18 September; three releases have shipped since.</p>
<p>The changelog is written for the operator, not the commit author. Its own stated rule is that a line belongs there when a deployment behaves differently afterwards, and does not when only the code moved. That makes it the right source to cite instead of secondhand summaries.</p>
<p>From the recent releases, the changes that affect a deployment:</p>
<ul>
<li><strong>v0.0.15 — a model provider&rsquo;s sign-in can stand in for an API key.</strong> <code>OPENBOT_MODEL_OAUTH_FILE</code> mounts a <code>POST /api/model-provider/v1/chat/completions</code> route that answers ordinary Chat Completions requests using a Google or xAI OAuth grant, refreshing the access token 120 seconds before expiry and again on a provider 401. The caller authenticates with a separate local bearer compared in constant time, never with the provider&rsquo;s token, so a refresh token never leaves the server process. The credential file is refused unless it is a regular file under 64KB, owned and readable by nobody else, and a Google grant must name a quota project. Leave the variable unset — true for most deployments — and the route does not exist. The same release also fixed a provider 403 being misread as an expired sign-in.</li>
<li><strong>v0.0.14 — one provider spec file.</strong> <code>shared/model-providers.json</code> now holds the provider facts and each bot&rsquo;s default provider and model, read by the TypeScript bots through <code>shared/model-providers.ts</code> and the Python bots through <code>shared/model_providers.py</code>, with <code>BOT_PROVIDER</code> and <code>BOT_MODEL</code> still winning. The practical change: a wrong row now fails every bot at start-up instead of at its first model call, and an unrecognised <code>BOT_PROVIDER</code> is refused instead of quietly defaulting.</li>
<li><strong>v0.0.13 — desktop setup and cross-platform keyboard fixes.</strong> Fresh desktop setup installs its runtime before sign-in, OpenBot chooses its own local ports by binding them and writes them to the deployment <code>.env</code>, and driving a bot&rsquo;s browser no longer triggers the app&rsquo;s own keyboard shortcuts or scrolls the surrounding page.</li>
</ul>
<p>The pattern is a product that has been hardening its edges quickly. That is a reason to watch it. It is also a reason not to quote a review written a week ago.</p>
<h2 id="what-is-still-missing-or-unproven">What Is Still Missing or Unproven</h2>
<p>Honesty here is what separates a review from a press release.</p>
<ul>
<li><strong>Alpha by its own description</strong>, with &ldquo;expect rough edges and bugs&rdquo; in the README.</li>
<li><strong>No npm package and no hosted version</strong>; every workspace in the repository is private, so there is nothing to depend on, only something to fork.</li>
<li><strong>No SLA.</strong> Support is community-only, and production adoption is unverified.</li>
<li><strong>No degraded mode</strong> when CopilotKit Intelligence is unavailable.</li>
<li><strong>Third-party maturity scoring is unflattering at the enterprise end.</strong> Independent scoring puts overall quality at 6/10 (docs 7, activity 9, community 7, code 7) and labels it not production-ready; audience-fit reads hobby 3/5, startup 3/5, SMB 2/5, enterprise 2/5. That is a direct counterweight to the enterprise marketing, from a source that is not a competitor.</li>
<li><strong><code>channels.allowed_groups</code> is declared but inert.</strong> This is the detail worth surfacing because it is stated in the project&rsquo;s own docs: the tenant package requires an <code>allowed_groups</code> field per channel, it is validated and stored, and <strong>nothing reads it</strong>. Group-based rules have nothing to evaluate because <code>users.groups</code> is populated by no sign-in path. Channel access is decided by <code>channel_memberships</code> instead, and every channel route refuses a caller without a row. The docs describe the group field as a declaration waiting for a future feature. Report it as a stated limitation, not a vulnerability — but do not deploy believing a group-based control is protecting anything.</li>
<li><strong>Local models are not officially tested.</strong> Nothing local ships in the box.</li>
</ul>
<h2 id="openbot-vs-grok-bot-claude-cowork-browser-use-and-playwright-mcp">OpenBot vs Grok Bot, Claude Cowork, Browser Use and Playwright MCP</h2>
<table>
  <thead>
      <tr>
          <th></th>
          <th>CopilotKit OpenBot</th>
          <th>Grok Bot / Claude Cowork</th>
          <th>Browser Use / Playwright MCP</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Licence</td>
          <td>MIT, self-hosted</td>
          <td>Proprietary, hosted</td>
          <td>Open source, library</td>
      </tr>
      <tr>
          <td>Where it runs</td>
          <td>Your infrastructure, Docker Compose</td>
          <td>Vendor cloud</td>
          <td>Wherever you call it</td>
      </tr>
      <tr>
          <td>Per-agent browser</td>
          <td>Yes — one container and profile per coworker</td>
          <td>Shared cloud session</td>
          <td>One browser per run, no isolation guarantee</td>
      </tr>
      <tr>
          <td>Policy decided before action</td>
          <td>Yes — fail-closed CEL at the gateway</td>
          <td>Confirmation prompts</td>
          <td>No</td>
      </tr>
      <tr>
          <td>Audit of refusals and failures</td>
          <td>Yes, <code>/admin/audit</code></td>
          <td>Not exposed to you</td>
          <td>No</td>
      </tr>
      <tr>
          <td>Human takeover</td>
          <td>Audited, with control events; seize control any time</td>
          <td>Within the vendor UI</td>
          <td>Manual, outside any trail</td>
      </tr>
      <tr>
          <td>Scheduled unattended runs</td>
          <td>Routines, with <code>initiator.kind</code> separating them from people</td>
          <td>Vendor-defined</td>
          <td>You build it</td>
      </tr>
      <tr>
          <td>SSO / SAML / OIDC</td>
          <td>Yes, ungated by licence tier</td>
          <td>Vendor-managed</td>
          <td>No</td>
      </tr>
      <tr>
          <td>Maturity</td>
          <td>Alpha, v0.0.15, ~6 weeks old</td>
          <td>Generally available</td>
          <td>Mature libraries</td>
      </tr>
  </tbody>
</table>
<p>The clean framing, and the one the product&rsquo;s own reviewers keep arriving at: Browser Use and Playwright MCP give an agent <em>a</em> browser. OpenBot gives each agent its own browser container with persistent logins, puts a policy gateway and audit log between agent and browser, brings files, shell and MCP tools under the same gate, and wraps it in a multi-user chat product with SSO. It is a platform, not a library — and it is designed to host the libraries.</p>
<p>Against hosted competitors, the trade is explicit. You get governance, data residency and an audit trail you control. You pay in operational burden, an alpha maturity level, and an external dependency on CopilotKit Intelligence. If your security review killed a hosted coworker pilot over where the data went, that trade is the reason to look here.</p>
<h2 id="how-to-evaluate-it-in-an-hour">How to Evaluate It in an Hour</h2>
<p>You do not need to run it for a month to form a view. The evaluation that matters is whether the audit page is something your compliance lead would sign off on.</p>
<ol>
<li>Clone the repository and pin to v0.0.15 rather than tracking <code>main</code>.</li>
<li>Run the setup path with Bun 1.3+ and Docker. Loopback only — do not point <code>OPENBOT_PUBLIC_URL</code> at anything public yet.</li>
<li>Set <code>COMPUTER_SUPERVISOR_URL</code> so each bot gets its own container. Without it you are evaluating a different product.</li>
<li>Ask the General Assistant for the top story on Hacker News. This exercises the browser path and produces audit rows you can read.</li>
<li>Write three deny rules in <code>/admin/boundaries</code> — a shell command, a host, and a password field. Then ask the assistant to fill out <code>httpbin.org/forms/post</code> and watch a rule fire.</li>
<li>Open <code>/admin/audit</code> and read it. Permitted, refused and failed rows all appear together, and the <strong>Nobody watching</strong> filter isolates anything that ran on someone&rsquo;s authority while they were away.</li>
</ol>
<p>Step 6 is the whole evaluation. The design claim is that the record exists before the action; the audit page either demonstrates that or it does not.</p>
<h2 id="who-should-run-openbot--and-who-should-not">Who Should Run OpenBot — and Who Should Not</h2>
<p><strong>A good fit:</strong></p>
<ul>
<li>Platform engineers who want a working reference implementation of policy-gated tool calls, rather than a design document.</li>
<li>Teams whose agents already speak AG-UI and who want a governance layer that rides the protocol instead of the framework — so changing from LangGraph to Mastra does not cost you your policy layer.</li>
<li>Organisations where a security review already rejected a hosted coworker product over data residency.</li>
<li>Teams willing to maintain a fork and run five services.</li>
</ul>
<p><strong>Not a good fit:</strong></p>
<ul>
<li>A solo developer who wants a personal agent. Hosted products are better at this and always will be.</li>
<li>Anyone allergic to maintaining a fork, or who needs something to depend on. The repository&rsquo;s own README says there is nothing here to depend on.</li>
<li>Pure browser automation. Use Browser Use; OpenBot is the building around that library, not a replacement for it.</li>
<li>Teams that want a fully unattended browser agent today. Per the architecture docs, computer actions are person-session actions right now.</li>
</ul>
<h2 id="verdict">Verdict</h2>
<p>CopilotKit OpenBot is the most serious attempt I have seen at making agent governance the product rather than a checkbox. The ordered gateway sequence — resolve, decide, record, act, record on failure — is a genuinely different design from the log-after-the-fact model that most tooling ships, and <code>initiator.kind</code> is a field almost nothing else in this category can express: the difference between a person typing and a schedule firing with nobody in the room.</p>
<p>The honesty cuts both ways. It is six weeks old and calls itself a template rather than a product. The headline isolation depends on one environment variable you can silently omit. Its memory layer is a separately licensed external service with no degraded mode. Independent scoring rates it 2/5 for SMB and enterprise fit, and the group-based channel control the tenant package requires decides nothing today.</p>
<p>So: worth a pinned clone and an afternoon on a spare machine, with <code>COMPUTER_SUPERVISOR_URL</code> set and an OAuth provider configured instead of leaving single-user mode on. Worth adopting into production for governed, unattended agent work only once you have an operator, a fork maintenance plan, and a policy file you have actually tested against a live refusal.</p>
<p>The gateway is the product. Computer use is not the novel claim — isolation plus a fail-closed policy is. Judge it on the audit page, not the star count.</p>
<h2 id="faq">FAQ</h2>
<h3 id="is-copilotkit-openbot-free-for-commercial-use">Is CopilotKit OpenBot free for commercial use?</h3>
<p>Yes. The repository is MIT-licensed, and no enterprise tier gates the audit log, SSO or SAML/OIDC support. But the MIT licence covers the code, not the whole stack: durable threads and memory go through CopilotKit Intelligence, which is a separate, separately licensed service with a free plan and a self-hosted option. You also supply Docker, Bun 1.3+, a model key, and an operator.</p>
<h3 id="does-openbot-require-copilotkit-intelligence">Does OpenBot require CopilotKit Intelligence?</h3>
<p>For durable threads and memory, yes — there is no degraded mode if it is unavailable. It sits outside the repository, is licensed separately, and offers both a free plan and a self-hostable option. It also provides the realtime gateway. Plan for it as a production dependency rather than an optional add-on.</p>
<h3 id="do-i-have-to-turn-off-openbot_single_user-to-deploy-openbot-safely">Do I have to turn off OPENBOT_SINGLE_USER to deploy OpenBot safely?</h3>
<p>Not quite — and this is where most coverage gets it wrong. <code>OPENBOT_SINGLE_USER=true</code> is required when no identity provider is configured, because a deployment that says nothing about how people sign in refuses to start. What protects you is that OpenBot refuses to start at all on a public address with that flag set. On a private address it starts with a single boot-time warning, and anyone who can reach that network is the administrator. The real fix is configuring Google, Microsoft Entra or Okta sign-in with <code>INITIAL_ADMIN_EMAILS</code>, not flipping the flag.</p>
<h3 id="can-openbot-coworkers-handle-2fa-or-a-login-wall">Can OpenBot coworkers handle 2FA or a login wall?</h3>
<p>No, by design. A bot that hits a login wall or a 2FA prompt asks for help; a person takes the wheel in the same panel, signs in, and hands it back. The browser profile persists, so later runs stay signed in. Handovers are recorded as <code>computer.help_requested</code>, <code>computer.control_taken</code> and <code>computer.control_released</code>, and while a person is driving, bot actions are refused rather than queued. Since v0.0.5 a person can seize control at any time without waiting to be asked.</p>
<h3 id="how-is-openbot-different-from-browser-use-or-playwright-mcp">How is OpenBot different from Browser Use or Playwright MCP?</h3>
<p>Those give an agent a browser. OpenBot gives each agent its own browser container with a persistent profile and its own logins, and puts a fail-closed CEL policy gateway and an audit trail between the agent and the browser — extending the same gate over file, shell, MCP and component actions. It also wraps that in a multi-user chat product with channels, SSO and an audited human takeover. The short version: they are libraries, OpenBot is the building around them, and it is designed to host them.</p>
]]></content:encoded></item></channel></rss>