OpenBot evaluates every agent action against policy and writes an audit row before execution. That governs what an agent may do inside one deployment. It does not establish who a counterparty is.
OpenBot governs what an agent may do. It does not settle who it is dealing with
Published August 24, 2026 · Last reviewed August 27, 2026
CopilotKit created the OpenBot repository on August 17, 2026. Ten days later it carries 3,142 stars and 384 forks.1 The pitch is narrow and unusually concrete for this category: self-hosted AI coworkers, each with its own container, its own workspace volume, and its own browser profile, where every action is decided before it happens and recorded after.1
What is interesting here is not the star count. It is which problem the design treats as load-bearing. Most agent runtimes ship capability first and add controls later, and the shift toward putting controls in the runtime instead of the prompt is one we traced through Microsoft’s Agent Governance Toolkit. OpenBot puts the control path in the middle of the architecture: one gateway, policy evaluated before the call, an audit row written before the call, and a refusal that names the rule that caused it.2 That is a real answer to a real question, and it is worth reading precisely, because the question it answers is narrower than the phrase “agent trust” suggests.
The distinction this article is about: authorizing an action inside a deployment you control is a different primitive from establishing who you are dealing with when the other party is outside it. OpenBot is good at the first. It does not claim the second, and reading it as if it did is the mistake worth avoiding.
What OpenBot actually ships
The shape is a supervisor, a gateway, and one policy engine. Each Bot gets its own container, its own /workspace volume, and its own browser profile, so two coworkers running at the same time are not sharing cookies, files, or a session.1 Read the condition on that, because it is easy to miss: per-Bot isolation applies when a supervisor is configured. Without one, every bot shares a single computer endpoint.2 That isolation is the part most self-hosted agent setups skip, and skipping it is how one agent’s authenticated session quietly becomes another’s.
The enforcement path is a single ordered sequence, and the order is the design. For a browser action the server resolves the target from a server-held snapshot rather than from whatever the model asserted, evaluates the current action policy, writes an audit row for the decision, calls the computer only when the decision forwards, and writes a second audit row if a forwarded action then fails.2 Resolving from a server-held snapshot matters more than it sounds. It means the model does not get to name the element it clicked after the fact.
Policy is written in CEL and can inspect tool.name, intent, bot.id, actor.id, page.url, page.host, element.*, key, file.* and mcp.*.1 Deny is evaluated before allow. The admin surface at /admin/audit lists what was permitted, what was refused, and what failed, and every refusal carries the rule that caused it.1 A refusal you can trace to a rule is a materially different artifact from a refusal you cannot.
Two further pieces are worth naming. MCP access is governed rather than assumed: custom servers must pass URL checks, and any tool not positively classified as a read is treated as a write.1 That is the same direction of travel as MCP’s enterprise-managed authorization, where which server an agent may reach becomes an identity-provider decision rather than a client-side one. And human takeover is a first-class event rather than an escape hatch. A Bot that hits a login wall or a 2FA prompt asks for help, control is handed over in the same panel, and the handover is recorded as computer.help_requested, computer.control_taken and computer.control_released.1 While a person is driving, Bot actions are refused rather than queued.2
The runtime is framework-independent. A Bot is any endpoint speaking AG-UI, the open protocol for agent-to-user interaction, so the control layer is not bound to one agent framework or one model vendor.1 It is also, by its own README, alpha and under active development, published under MIT.1 That is the honest reading of the traction: a well-shaped control path at an early version, not a finished product.
One caveat on the licence is worth carrying, because it is the sharpest thing the early coverage found. MIT describes the code. It does not describe the deployment: the write-ups report that a production install leans on a CopilotKit Intelligence project for durable thread storage, and that running the whole stack means Docker, PostgreSQL, a supervisor process, and a browser container per agent.3 Self-hosted and MIT is accurate about the repository and incomplete about what it costs to operate. That does not change anything in the argument below, which is about what the control path can and cannot establish, but it belongs in any evaluation that ends in a deployment decision.
Fail closed is a property of the engine, not of the shipped default
The most repeated claim about OpenBot in its first week was that it defaults to deny. That is close to right, and the precision is the useful part.
What fails closed is the policy engine. A missing or empty policy permits nothing, a broken deny rule denies, and a broken allow rule does not permit.2 Those are three separate guarantees covering three different failures. The first covers policy that never loaded. The second covers a restriction an operator wrote that no longer parses, where the restriction still restricts. The third covers the mirror case, where a permission that no longer parses stops permitting rather than silently widening. Plenty of policy systems get one or two of these right and fail open on the third.
What does not fail closed is the box you unpack. The compiled-in startup default is deny: [] with allow: ["true"], used whenever the policy environment variable is unset and no administrator policy has replaced it.4 Read plainly, a fresh install permits everything until somebody writes the rules. That is a defensible product decision for an alpha, and the code says so on purpose rather than by accident: the comment above that constant calls it “a separate decision,” deliberately an explicit allow rather than an unconfigured special case.4 A runtime that refuses every action on first boot is a runtime nobody finishes evaluating. It is also the one line an operator most needs to have read before pointing this at anything real.
The same shape shows up a second time, and this one is louder. The development configuration ships with authentication disabled behind OPENBOT_DEV_NO_AUTH, which two of the early write-ups flag as the thing most likely to follow somebody from a local trial into a server nobody meant to expose.3 Neither default is a bug. Both are the ordinary distance between a runtime that is safe to evaluate and a runtime that is safe to run.
The two properties are worth keeping apart because they protect against different things. Fail-closed semantics protect you from your own broken policy. They do not protect you from an absent one. Safety in the first week of a deployment comes from an administrator writing rules; the architecture guarantees those rules keep meaning what they said once they break.
One early review goes further and describes unresolved fail-open scenarios in the policy layer.3 That one does not survive contact with the source. The policy module is explicit that deny is evaluated first, that a broken deny expression still denies, and that an empty allow list permits nothing; the error handling exists precisely so a broken allow cannot widen access.5 The reviewer also discloses not having run the software. The distinction the article is drawing is worth keeping clean: the engine’s failure semantics are good, and the shipped defaults are permissive, and only the second of those is a reason to be careful on day one.
This is not a criticism specific to OpenBot, and the repository is unusually straight about it. It is the recurring gap between how a governance property gets described in a launch thread and how the software behaves at hour zero. Deny by default is a claim about the engine. Allow true is the state of your install. Both statements come from the same repository.
Every identity in the policy is a directory identity
Look at what the policy language can see: bot.id, actor.id, tool.name, intent, page.url, page.host, element.*, key, file.*, mcp.*.1 Every one of those is a fact about your own deployment or about the surface your bot is touching. None of them is a fact about a counterparty.
actor.id is the human who signed in, through Google, Microsoft, Okta, SAML, or an OpenID Connect provider, and /admin/people lists everybody who has signed in so an administrator can promote, demote, or remove them.1 That is a proper directory identity, and it is the right identity for the question OpenBot is asking: which of my people, running which of my bots, may take which action. It resolves inside the tenant because the tenant issued it.
page.host looks like it crosses the boundary, and it does not. A hostname is a network location. It tells your policy where the browser is pointed, not who is behind it. You can allow a bot to operate on a domain and still know nothing about whether the party operating that domain is who they present themselves as, whether they are willing to be contacted, or whether the account your bot is about to fill a form for belongs to a real counterparty. DNS answers where. It has never answered who.
That gap is not a flaw in OpenBot. It is a boundary in the category. We have written before about how workspace agents stay tenant-locked while identity has to be portable, and the shape repeats here. An identity issued by your identity provider is trustworthy precisely because your identity provider issued it, and it is inert the moment the question involves someone who does not authenticate against it. Two companies each running a well-governed OpenBot deployment have two internally coherent policy worlds and no shared vocabulary between them.
The moment an agent’s useful work involves a party outside the deployment, and for most commercially interesting agent work that happens immediately, the policy has run out of variables. Not because the engine is weak. Because the facts it would need to evaluate were never inside the deployment to begin with.
An audit row answers what happened, not who that was
/admin/audit is a genuinely good artifact. It records what was permitted, what was refused, and what failed, with the rule attached to every refusal.1 For an incident review, a compliance question, or an argument with your own security team about whether an agent did the thing, that is the record you want, and most agent deployments running today cannot produce it.
It is also a record with one reader: you. The audit row sits in your database, written by your gateway, about your policy. It is the deployment describing itself. That is sufficient for internal accountability and structurally insufficient for anything crossing a boundary, because the party who would need convincing is the one party with no way to check it. “Our logs show the action was authorized” is a claim, not evidence, to anybody who did not write the logs.
The standards landscape has been working on the other half of this for a while. An A2A Agent Card published at /.well-known/agent-card.json lets an agent be discovered and described by machines.6 A W3C DID Document gives an agent a resolvable identifier and a key to authenticate with.7 The ERC-8004 registries record agent identity, reputation, and validation results on chain, though the empirical picture of what those registrations amount to is thinner than the registration counts suggest, and the standard itself is still formally a draft while its contracts run on mainnet.8 Each of those produces something a stranger can check without access to your infrastructure. They are complementary to what OpenBot does rather than competitive with it: one governs the action inside, the others describe the party outside.
There is a third thing that neither an audit row nor a machine-readable card supplies. Authorization is a decision taken on the acting side. It records that your agent was permitted to send the message, open the ticket, or fill the form. It says nothing about whether the human on the receiving end agreed to be reached. Consent is a property of the other party, and no amount of rigor on your side generates it. A perfectly governed agent sending a perfectly audited message to somebody who never asked to hear from it has produced a flawless record of an unwelcome contact.
That is the honest way to read a fail-closed runtime. It answers what happened with unusual precision. Who that was, and whether they wanted the interaction, are questions asked at a different layer.
How this connects to Tobira
Tobira works on the layer OpenBot’s policy language cannot reach. A Tobira @handle is a human-readable address for an agent that represents a named person or company, published on a network rather than inside one deployment, so a counterparty can find it, talk to it, and learn who stands behind it without holding an account in your identity provider. The consent step is the part that matters here: contact details are exchanged only after both sides agree, which makes an introduction something the receiving party opted into rather than something the sending party authorized itself to do. Per Tobira’s founder update, June 2026, the network carried 648 agents, including 102 business agents. That is a different primitive from action authorization and it sits beside one rather than replacing it. A team can run a fail-closed runtime governing what its agents may do internally and still need a consent layer for the agentic web for the moment those agents reach somebody else’s human.
What to remember
Two lists, and the gap between them is the whole argument.
What OpenBot settles, inside a deployment you control: whether an action may proceed, decided before it happens against policy written in CEL; what the browser is actually pointed at, resolved from a server-held snapshot rather than from model output; who among your signed-in people ran which bot; what was permitted, refused or failed, with the rule attached to every refusal; and when a human took the wheel at a login or 2FA wall. That is more control surface than most agent deployments running today can produce, and it is shipped rather than described.
What it leaves open, the moment the work crosses your edge: who the party on the other side is, since every variable the policy can read is a fact your own deployment issued; whether an outsider has any reason to believe your audit row, since it was written by your gateway about your policy into your database; and whether the human your agent is about to reach agreed to be reached, which is a fact held by them and expressible only on a surface both sides can see.
Read the fail-closed claim precisely while you are at it. The engine fails closed and the shipped default does not, and an alpha runtime pointed at production on day one is running with allow: ["true"] until somebody writes the rules.
Governing what an agent may do and establishing who it is dealing with are different primitives, bought from different places. Which of the two is your stack currently answering?
FAQ
Does OpenBot deny agent actions by default?
The policy engine fails closed, and the shipped install does not. Those are two different statements and the source makes both. A missing or empty policy permits nothing, a broken deny rule denies, and a broken allow rule does not permit. The compiled-in startup default, however, is deny: [] with allow: ["true"], used whenever the policy environment variable is unset and no administrator policy has replaced it. So a fresh deployment is permissive until somebody writes rules, and once rules exist, breaking them tightens rather than loosens what agents may do. The development configuration separately ships with authentication disabled behind an environment flag, which is the other default worth checking before exposing an install.
What does an agent audit log actually prove?
It proves what your deployment decided and did, to you. OpenBot’s /admin/audit records what was permitted, what was refused, and what failed, with the rule attached to each refusal, which is the right artifact for an incident review or an internal compliance question. It is not evidence to a counterparty, because the log is written by your gateway about your policy and stored in your database. An outside party cannot verify it without trusting the system that produced it.
Is OpenBot ready for production use?
Its own README describes it as alpha and under active development, published under MIT. The architecture is unusually well shaped for this stage, with a single enforcement gateway, targets resolved from a server-held snapshot rather than from model output, and audited human takeover at login and 2FA walls. Three things temper that. The shipped action policy allows everything until an administrator writes rules, the development configuration disables authentication behind an environment flag, and early reviews report that a production install depends on a CopilotKit Intelligence project for durable thread storage, so MIT describes the code rather than the whole deployment. Treat the design as informative and the version label as accurate: this is an early release from CopilotKit, not a hardened platform, and the star count measures interest rather than maturity.
What is the difference between agent authorization and agent identity?
Authorization decides whether an action may proceed; identity establishes who a party is. OpenBot’s policy answers the first for actions inside one deployment, using facts that deployment owns: which bot, which signed-in human, which tool, which host. Identity answers the second across deployments, and needs a claim a stranger can check without access to your infrastructure, which is what A2A Agent Cards, W3C DID Documents, and the ERC-8004 registries each supply in their own way. A system can be excellent at one and silent on the other.
Can policy enforcement cover consent?
No, because consent is held by the other party. A policy rule can cap what your agent is allowed to send, to which host, at what rate, and it evaluates entirely on your side of the interaction. Whether the human receiving the message agreed to be contacted is a fact that lives with them, and it has to be expressed on a surface both sides can see. That is why authorization and consent end up as separate layers in the agent stack rather than one feature.
Sources
- CopilotKit, OpenBot repository. https://github.com/CopilotKit/OpenBot
- OpenBot architecture documentation. https://github.com/CopilotKit/OpenBot/blob/main/docs/architecture.md
- OpenBot policy engine and policy store,
server/src/computer/policy.tsandserver/src/computer/policy-store.ts. https://github.com/CopilotKit/OpenBot/blob/main/server/src/computer/policy.ts - MoClaw, “CopilotKit OpenBot gives each agent a computer.” https://moclaw.ai/blog/copilotkit-openbot
- explainx.ai, “OpenBot: open-source Grok bot for any harness,” August 2026. https://www.explainx.ai/blog/copilotkit-openbot-open-source-grok-bot-august-2026
- A2A protocol releases. https://github.com/a2aproject/A2A/releases
- W3C, Decentralized Identifiers (DIDs) v1.1, Candidate Recommendation. https://www.w3.org/TR/did-1.1/
- ERC-8004, Trustless Agents. https://eips.ethereum.org/EIPS/eip-8004
- Tobira founder update, June 2026, for the network figures cited in the Tobira section.
Footnotes
-
CopilotKit, OpenBot repository, https://github.com/CopilotKit/OpenBot. Repository created 17 August 2026; stars and forks read from the GitHub API on 27 August 2026, giving 3,142 stars and 384 forks. Star counts are a live figure and will have moved by the time you read this; it is cited as a measure of attention in the first ten days, not as a stable property of the project. Source of: the per-Bot container,
/workspacevolume and browser profile; the CEL policy fields; the/admin/auditand/admin/peoplesurfaces and the identity providers behind them; the MCP read-versus-write classification; the human takeover events; the AG-UI interface; and the MIT licence and alpha status. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 -
CopilotKit, OpenBot architecture documentation, https://github.com/CopilotKit/OpenBot/blob/main/docs/architecture.md. Source of the enforcement sequence: the target is resolved from a server-held snapshot rather than from model output, the policy is evaluated, the audit row is written, and only then is the computer called, with a second audit row if a forwarded action fails. Also the source of the isolation condition noted in the body: full per-Bot isolation applies when a supervisor is configured, and without one all bots share a single computer endpoint. ↩ ↩2 ↩3 ↩4 ↩5
-
Two early reviews are the basis for the caveats in the body. MoClaw, “CopilotKit OpenBot gives each agent a computer,” https://moclaw.ai/blog/copilotkit-openbot, reports the operational footprint (Docker, PostgreSQL, a supervisor process, one browser container per agent), flags the development configuration that disables authentication, and concludes the project is worth watching rather than deploying. explainx.ai, https://www.explainx.ai/blog/copilotkit-openbot-open-source-grok-bot-august-2026, raises the same authentication default, reports the CopilotKit Intelligence dependency for durable thread storage, and additionally describes unresolved fail-open scenarios in the policy layer. That last claim is the one contradicted by the source, as set out in the body; the same piece discloses that its author did not run the software. Both are read here as evaluation notes rather than as audits, and their star-count snapshots disagree with each other and with the repository, which is why no intermediate star figure is cited in this article. ↩ ↩2 ↩3
-
OpenBot,
server/src/computer/policy-store.ts, https://github.com/CopilotKit/OpenBot/blob/main/server/src/computer/policy-store.ts, which definesDEFAULT_ACTION_POLICYasmode: "enforce"withdeny: []andallow: ["true"]. The accompanying comment states that the engine is fail-closed, that an absent policy denies, and that this permissive startup default was chosen deliberately rather than falling out of an unconfigured code path. The default applies when the policy environment variable is unset, perserver/src/config.ts. Read from the repository at its current head on 27 August 2026; the same values were reported at launch, so no drift is visible over the first ten days. ↩ ↩2 -
OpenBot,
server/src/computer/policy.ts, https://github.com/CopilotKit/OpenBot/blob/main/server/src/computer/policy.ts. Source of the failure semantics described in the body: deny is evaluated before allow, a broken deny expression still denies, an empty allow list permits nothing, and the error handling exists so that a broken allow cannot permit. Also the source of the policy field list, including the note that an MCP tool not positively known to be a read is treated as a write. ↩ -
A2A Agent Cards are published at
/.well-known/agent-card.json. On versions, the release history at https://github.com/a2aproject/A2A/releases shows v1.0.0 on 12 March 2026 and v1.0.1 on 28 May 2026 as the latest release; there is no v1.1 or v1.2, a point worth stating because higher version numbers circulate. The specification landing page still labels 1.0.0 as the latest released version, so the releases page is the more precise reference for the patch. ↩ -
W3C Decentralized Identifiers. DID Core 1.0 remains the W3C Recommendation; DID v1.1 is a Candidate Recommendation Snapshot published 5 March 2026, https://www.w3.org/TR/did-1.1/, and DID Resolution 1.0 is a Candidate Recommendation published 6 August 2026. The point made in the body, that a DID Document supplies a resolvable identifier plus a key to authenticate with, holds across those versions; the version detail matters only for anyone implementing against the newer drafts. ↩
-
ERC-8004, Trustless Agents, https://eips.ethereum.org/EIPS/eip-8004, which specifies Identity, Reputation and Validation registries. Note the status mismatch that anyone citing this should carry: the EIP itself is still marked Draft on the canonical page, while the reference registries have been deployed on Ethereum mainnet since 29 January 2026. Live contracts and a finished standard are not the same thing. ↩