# Changelog Source: https://docs.claw.boo/appendices/changelog What changed in each release of the clawboo npm package, newest first, and what is on the main branch waiting for the next release. Release history of the `clawboo` npm package, newest first. Each entry starts with what changed for you. The full notes for every release are in [`apps/cli/CHANGELOG.md`](https://github.com/clawboo/clawboo/blob/main/apps/cli/CHANGELOG.md), and [Release process](/internals/release-process) explains how a release is made. > [!NOTE] > > These docs follow the main branch, which is ahead of the latest npm release, 0.5.0. What is listed under **Unreleased** is not in 0.5.0 yet. Run `clawboo --version` to see your version, and `npx clawboo@latest` to run the latest release. ## At a glance | Version | Released | Highlights | | - | - | - | | Unreleased | Not yet | Rooms, steering a running task, a rebuilt workspace, a Kanban-first Board, Ollama with no key, a proxy fix | | 0.5.0 | 2026-10-21 | The Board as a calendar, task costs that count every run, a quieter team chat | | 0.4.0 | 2026-09-28 | Connectors, a memory graph, building on the graph, Routines, a Needs you column | | 0.3.1 | 2026-07-20 | Team-chat reliability, live status badges, a provider manager per runtime | | 0.3.0 | 2026-07-15 | Native-first: run on one provider key with no Gateway, teams that mix runtimes | | 0.2.0 | 2026-06-20 | Clawboo runs its own agents and coordinates four other runtimes | | 0.1.9 | 2026-06-01 | A lighter welcome screen, team colors | | 0.1.8 | 2026-05-29 | Light and dark themes, a design-system pass | | 0.1.7 | 2026-05-20 | Device pairing in the setup wizard, Windows fixes | | 0.1.6 | 2026-05-20 | Setup checks what is really installed | | 0.1.5 | 2026-05-20 | Approve a new device from the dashboard | | 0.1.4 | 2026-05-20 | OpenClaw protocol 4, Windows install support | | 0.1.3 | 2026-05-19 | The CLI only opens a port that really serves Clawboo | | 0.1.2 | 2026-05-19 | Fixes `Cannot GET /` | | 0.1.1 | 2026-05-19 | First real release | | 0.1.0 | 2026-02-28 | Name placeholder | Every 0.1.x release is deprecated on npm. That line ran on OpenClaw only and does not work reliably with current OpenClaw. Install a supported release with `npm install -g clawboo@latest`, or run `npx clawboo@latest`. Keep the `@latest`: a bare `npx clawboo` can reuse an old copy from npm's cache. **Unreleased** On main, not yet on npm The next release gives a team chat rooms, lets you steer a task while an agent works on it, and rebuilds the workspace around a team. The Board opens on a calmer Kanban view. Clawboo Native runs on a local Ollama with no key, and a security gap for installs behind a reverse proxy is closed. **Security** * **The access token covers the MCP servers behind a proxy.** On 0.5.0 and older, any request from `127.0.0.1` reached `/api/mcp/` without the access cookie, so a reverse proxy on the same machine exposed the MCP servers and let anyone ask `/api/mcp/config` for a signed attach URL. Now a request without the cookie reaches an MCP server only with an attach token, only on a direct connection to `127.0.0.1`, and never through a proxy. `/api/mcp/config` always needs the cookie. Until you upgrade, deny all of `/api/mcp/` at the proxy: see [Security model](/operating/security). * **Re-copy attach URLs you set up by hand.** If you attached an agent by URL on an install with an access token, copy the URL again from `GET /api/mcp/config` (sending the access cookie). The old URL carries no token and gets a `401`. * **A token the access check cannot use stops the server.** A `STUDIO_ACCESS_TOKEN` with a character other than letters, digits, `.`, `_`, `~` and `-`, such as `openssl rand -base64` output, used to turn the check off. Now the server logs `SECURITY: refusing to start` and exits; make a new token with `openssl rand -hex 24`. Files that hold keys or tokens, including `clawboo backup` files, are now readable only by you. * **Claude Code agents leave your own Claude Code setup out.** They no longer load your `~/.claude` settings, plugins, hooks, MCP servers, `CLAUDE.md` and rules, or Claude Code's auto memory. A repository's own `CLAUDE.md` and `.claude/settings.json` still apply. Auth you set in `~/.claude/settings.json`, such as an `env` block or an `apiKeyHelper`, no longer reaches agents: set those variables in the environment you start Clawboo with. See [Claude Code](/runtimes/claude-code#your-own-claude-code-setup-stays-out). * **Connector access set to ask every time always asks.** A remembered **Always** used to skip the prompt even under `approvalPolicy: always`. Now the call asks, and the card offers no way to remember it. * **Risky hand-offs are never remembered.** An `allow_always` answer sent through the API used to stop that leader's risky hand-offs from asking again, with no end date and no way to undo it in the dashboard. Now it allows that one hand-off. * **Attach URLs name real teams, and their secret can be replaced.** `GET /api/mcp/config` refuses a `scopeTeamId` that names no team, and a Clawboo agent signed for a team it is not on. `POST /api/mcp/attach-secret/rotate` replaces the secret behind every attach URL without a restart. See [MCP servers](/operating/mcp-servers#the-attach-token). * **TeamChat takes no author from tool arguments over HTTP.** A session without a signed room-and-author binding gets no TeamChat tools. * **`create_subtask` checks the team.** A parent task from another team is refused. * **Images in an agent's text no longer load on their own.** In chat, a task's report and its comments, an image used to load from its address as soon as it was shown, so an agent steered by something it read could send data out in that address. It now shows as a link named after its alt text that opens only when you click it. An address that is not a web or same-site one is only named. * **API routes match letter case exactly**, and with no access token set, the server logs a one-time `SECURITY:` warning when a request arrives through a reverse proxy. **Rooms and chat** * **Rooms.** Next to the team's own chat, now called **General**, a team can have rooms, each with some of its members. A room has its own conversation, task cards and graph. Boo Zero leads every room, and the team's rules, brief, memory and board stay team-wide. Create one with **New room**, and pin, rename or delete it from its menu. Deleting a room archives it. See [Team chat](/using/team-chat). * **Chat with Claude Code, Codex and Hermes agents.** The chat on their page now runs on the Clawboo server, with no OpenClaw Gateway, and each agent keeps its conversation from one message to the next until you send `/reset`. These chats and Clawboo Native's start in the agent's team folder, where a Clawboo Native agent can now use its file tools and, with **Let this Boo ask to run commands** on, ask to run a command. See [Agents](/using/agents#chat-with-an-agent). * **Clear chat.** Every chat's menu can clear it. The messages are set aside rather than deleted, the agents start that conversation fresh (OpenClaw agents are sent `/reset`), and the chat stops showing task cards from before the clear. Those stay on the Board and in the **Tasks** card. **Steering** * **Steer a running task.** Comment on a task from a team chat's **Tasks** card, or from **Tasks & Routines** on an agent's page, and the agent reads the comment during the run. Clawboo Native, Claude Code, Codex and OpenClaw take it at the run's next step; Hermes pauses the run and resumes the same session with your comment. The comment always lands in the agent's mailbox as well, so a task that is not running yet still gets it. See [Steering a running task](/runtimes/index#steering-a-running-task). * **Codex runs in `codex app-server`**, which is what lets it be steered. An older Codex without it falls back to `codex exec`, where a comment waits for the next run. * **A reply reaches the agent, and says where it went.** A reply on a task now goes to the agent it belongs to whatever the task's status, including a task moved back to To do. A reply to a task waiting on you, or waiting in To do, starts the agent on it again, so answering a blocker no longer needs a second click on **Retry**; replies within 15 seconds start one run. A reply to a task in review waits for the agent to read after the check. A finished task cannot run again, so the reply is not sent, and the note under it offers **Send as a follow-up task**, a new task for the same agent. The Board's task drawer now has a reply box, and a note under each reply you send, there and in the chat's **Tasks** card, says where it went. The **Tasks** card's **Send and retry** button is gone, since **Send** does both. See [Steer a running task](/using/board#steer-a-running-task). **The workspace** * **A sidebar you can resize and hide.** Drag the agent column's edge to resize it, or hide it with `Cmd/Ctrl + \`. The nav reads **Home**, **Org chart** (the new name for Atlas), **Board**, **Connectors**, **Memory**, **Marketplace** and **Settings**, and `Cmd/Ctrl + 1` to `4` open Org chart, Board, Connectors and Memory. **Fleet** moves to **Settings** > **Insights**. * **Cards beside every chat.** An agent's page shows **Graph**, **Tasks & Routines**, **Browser**, **Activity** and **Details** next to its chat. A team chat shows **Browser**, the team's or room's graph, **Tasks** and **Activity**. * **Home.** Each team has a Home page, the place its dashboards and its agents' artifacts will live. * **The Org chart turns.** With the browser panel open, the radial Org chart turns around Boo Zero to bring the team you pick next to the panel. **Ollama and providers** * **Ollama with no key.** Picking **Ollama** during setup now counts as a connected provider everywhere: agents deployed without a model pick run on it, Boo Zero is created on it, and team chat and one-to-one chat both work. Turn it on or off in **Settings** > **Runtimes** > **Clawboo Native** > **Manage**. * **Your pulled models.** The model lists show the chat models your Ollama has pulled, mark the ones that cannot use tools, and show `ollama pull llama3.2` with **Check again** when none is pulled. * **Your default provider wins.** Agents deployed without a model pick run on the provider you chose as your default, instead of the first one with a key. * **OpenClaw on Ollama.** When Ollama is your provider, **Set up OpenClaw** configures OpenClaw on it instead of asking for a key, and stops with the `ollama pull` command when the model is missing. Setup also connects to the Gateway port in your OpenClaw config. **The Board** * **Kanban first.** The Board opens on **Kanban** (the view called All before, key **K**), every task by status with Needs you first. **Today**, **Week** and **Month** stay as the calendar views, and a view you already picked is kept. On Kanban, cards say how long ago they were added or finished ("4m ago", "Yesterday", "3d ago"); the calendar views keep clock times. * **One header and one toolbar.** The header holds the title, the team filter, **Star** and **New task** (key **C**). **Refresh** and the subtitle are gone; a spinner shows only when a background read is slow. The toolbar holds **Find** (**/** or **Cmd/Ctrl+K**), **Filter** (agents, tasks with no agent, what is running now), **Display** (routine runs, the Cancelled column, cost and time on cards), an agents-working chip and the view menu. "Since you last looked" is a chip that shows only when something changed. * **Calmer cards.** Cards and lanes are neutral, with color only in each column's status mark and amber for Needs you. Cards drop the runtime mark, the corner clip and the hover lift, and counts no longer roll. * **New task in one sentence.** Write what should get done and press Enter. Clawboo suggests who should do it by matching your words to each agent's name and the role in its SOUL.md, else the agent you last gave a task to, then the team lead, and says why. Picking an agent also picks its team. **More options** opens the full form with Team, Agent and Details. * **Needs you cards lead with one action.** Each card says why it waits and offers **Retry**, **Assign**, **Review** or **Open**, with the other choices, including **Dismiss**, in a **More** menu. * **The task drawer reads like a document.** The agent's report once, as formatted text, then the brief, the properties, verification, the workspace, run history, comments and activity. Each section folds from its heading, empty ones are not shown, and **J** and **K** step through the column. See [Board](/using/board). **Budgets** * **Pick a budget by name.** In **Settings** > **Insights** > **Governance**, choose agent, team or mission, then the agent, team or board task from a list you can search. You no longer look up an id first, and each budget shows its name. The form no longer offers tenant, which nothing charges; a tenant budget you already have still shows. **Fixes** * Creating and deleting agents works without an OpenClaw Gateway, and a new agent made on the Org chart always joins a team. * An OpenClaw agent deleted while its Gateway is down stays deleted. * Approving this device during OpenClaw setup shows what went wrong, with **Retry**, instead of sticking on "Approved. Reconnecting…". * Updating from the dashboard keeps working after the first update, a failed update shows npm's own reason, and Clawboo runs `openclaw doctor --fix --non-interactive` once when a newer OpenClaw needs it before the Gateway will start. * The Org chart's Radial layout spaces each team's Boos apart, also with "reduce motion" turned on. * While one of a routine's runs is still going, the Board's calendar marks the right next run. * Loading the dashboard no longer counts against the API's request limit. * A Boo no longer loses its body when another copy of it is hidden. * An agent can no longer put a card back after you moved it: its own status changes are refused, with a message telling it not to try again, until the task runs again. * The Kanban lanes run unbroken to the bottom, and dropdown menus are as wide as their longest option. * Team chat runs, and task runs that only read (routines made in the dashboard included), start in a folder of their team, `~/.clawboo/workspaces/`, instead of the folder that holds Clawboo's database and keys. The folder only sets where Claude Code, Codex and Hermes start: they keep full access to your files. Clawboo Native agents get their file tools there, kept inside the folder. An agent's **Workspace** tab shows the folder, and deleting a team deletes it with its files. See [What agents can do](/concepts/permissions). * A model your own Ollama serves is recorded at \$0 and reads **free**, a Claude Code or Codex run whose final cost report crosses a hard cap keeps its result (the next run is refused instead), every run that ended without a cost report is estimated, and **Tokens Used** lists every runtime. See [Budgets and spend](/using/budgets-and-spend). * A paused hard cap now refuses new team-chat and one-to-one chat turns before they start, with a notice in the chat saying which budget is paused; a refused hand-off waits in **Needs you** with the reason. Saving a budget again keeps its mode unless you pick another, and the Governance form shows the mode of a budget you already have. See [What a hard cap stops](/using/budgets-and-spend#what-a-hard-cap-stops). **0.5.0** 2026-10-21 The Board becomes a calendar and a task board at once, moving a card by hand does what it says, and a task's cost counts every run. See [Board](/using/board). **The Board** * **Days as rows.** Status columns run across the top and days run down the side. Week is the default; Today, Month and All are a click away, or **D**, **W**, **M** and **A**. Open work sits on the day it was added, and work that reaches Done or Cancelled moves to the day it finished. Open work from before the range gathers in an Earlier row, and empty days fold into a thin line you can open. * **Needs you lives in today.** Approvals and stuck tasks sit in today's row with **Retry**, **Assign** and **Dismiss**, and tasks that broke the same way share one card. * **Routines on their days.** Each routine's upcoming runs show in To do on the days they are scheduled, from the new `GET /api/schedules/occurrences`. A **Routines** switch hides them. * **Since you last looked, and Find.** A line under the title says what changed while you were away, and changed cards carry a dot until you look. **/** finds any task or routine. * **Calmer cards.** One detail row: the agent, its runtime, one status mark or the cost, and when the card was added or finished. Unassigned work no longer claims to run on OpenClaw. **Moving cards by hand** * A drop goes to the column under the pointer and nowhere else. * In progress takes no card by hand, because moving it there started nothing. A card left there shows in Needs you as **Not started**. * Finishing a card asks which day it finished when it sits on another day, and the board `PATCH` takes a `completedAt`. Cancelling always asks first, and cancels the plan steps that were waiting on it. * Moving a card an agent is working on stops that run, its late result can no longer override you, and whoever delegated the task is told. **Also in this release** * **Task costs that count every run.** A task's cost is the sum of every run, failed ones included. A run with no reported charge is priced from its token usage, or from its prompt and reply length when there is none, and marked as an estimate. * **A quieter team chat.** A teammate's reply reaches the chat only when you addressed it or it is the lead's summary, and **Stop** also stops a run that was about to start. * **Routing by role.** On the lead's turn the roster lists each teammate's runtime and a one-line role from its SOUL.md, so Boo Zero routes work by what a teammate does. **0.4.0** 2026-09-28 Agents reach outside apps through connectors, team memory becomes a graph that learns, the Scheduler becomes Routines, and the Board gains a Needs you column. **Connectors** * **A Connectors view in the sidebar.** Curated connectors Clawboo runs itself, a longer list from the official MCP registry, and Gmail, Slack, Jira and more through Composio. Turning one on is one button, and sharing it with an agent is a line on the graph that is also the permission Clawboo enforces. See [Connectors](/using/connectors). **The graph** * **Build on the graph.** Every Boo carries one visible port. Pull a thread onto another Boo to connect them, or let go on empty canvas to pick a skill, a connector or a new agent, already wired. Routes, skills and shares all come off the same way, and a share has an Undo. See [Org chart and team graphs](/using/org-chart). **Memory** * **Team memory is a graph that learns.** Memory is a sidebar view where facts cluster and link by similarity. Agents report whether a recalled fact helped, so a fact two teammates found useful comes first, and one that misled someone leaves automatic recall. Every recalled line carries an id the agent can cite. See [Memory](/using/memory). **Work** * **Routines.** The Scheduler is now Routines, and a routine targets a team or one agent. A team routine posts its instructions into the team chat for the lead; an agent routine files a task for that agent. See [Routines](/using/routines). * **Needs you, and tasks for one agent.** The Board gains a Needs you column for approvals and stuck tasks, with **Retry**, **Assign** and **Dismiss**. **New task** asks for a team, then an agent, and only that agent works on it. **Platform** * **The catalog leaves the package.** The agent and team catalog is downloaded instead of bundled, and grew to nineteen packs, each naming its source repository, commit and license. * **OpenClaw 2026.9.** The Gateway pin moves from `~2026.5` to `~2026.9`, with the fixes that line needed. * **Served under a path.** `CLAWBOO_BASE_PATH` mounts the whole app under a URL prefix behind a reverse proxy. * **In-place database upgrades.** An upgrade adds new columns to an existing `clawboo.db` instead of failing on first use. * **Coordination that stays awake.** Task changes reach running agents through a per-agent mailbox, runs prove they are alive with regular heartbeats, and a team-chat run that ends without an answer says why. **Removed and limits** * `POST /api/onboarding/seed-native-team` is removed. Deploy a team from the Marketplace, or with `POST /api/teams`. * The API has request rate limits, with a much lower one on the costliest routes, such as installing a runtime, driving the Gateway, signing in a CLI and approving a device. See [Limits and defaults](/reference/limits-and-defaults). * The Tasks MCP server caps task creation: 24 live subtasks per parent, 2 levels of nesting, and 30 top-level tasks per rolling 5 minutes. * A task dependency that would form a cycle is refused. **0.3.1** 2026-07-20 A patch for running Clawboo Native teams day to day: team-chat replies stay put, status badges update live, and each runtime manages its own providers. **Team chat** * **Replies stay in the transcript.** Every streamed team-chat reply is saved or cleanly cleared, so a delegating agent's message cannot vanish on the next update or on reload. A Clawboo Native one-to-one reply that fails mid-stream is kept. * **Live Working and Idle badges.** The status marks in the sidebar and in team chat update in real time for Clawboo Native and server-run team work, not only for OpenClaw. **Providers and models** * **A provider manager per runtime.** **Manage** on Clawboo Native, OpenClaw and Hermes in the Runtimes panel lists the providers you connected, each with connect, disconnect and reconnect, plus a default-model picker for Clawboo Native. A Clawboo Native runtime with no key reads **Disconnected**, with a shortcut to set it up. * **Reconnect with any provider.** Reconnecting Clawboo Native works with any provider you already set up, not only Anthropic. * **Provider first, then model.** Creating a team asks for a provider (only the connected ones), then its model, and shows the exact default model that will run. **Polish** * The **Add runtimes** step of setup shows your connected providers read-only, with **Back** to the step where you add keys. * The sidebar mascot's tooltip reads "Boo Zero", matching what clicking it opens. **0.3.0** 2026-07-15 Native-first: the first run needs one provider key and nothing else. No OpenClaw install, no Gateway and no other CLI. OpenClaw, Claude Code, Codex and Hermes become runtimes you add to a setup that already works. **Setup and providers** * **Setup starts with a key.** Connect a provider, optionally add runtimes, then deploy a real team from the Marketplace. See [Quickstart](/getting-started/quickstart). * **A Providers hub in Settings** covers ten providers. Pasting a key fetches that provider's live model list, so the model picker offers what your key can reach. * **A first-run tour**: a one-time, skippable spotlight over the sidebar. See [Tour the dashboard](/getting-started/dashboard-tour). **Teams and chat** * **A runtime and a model for each member.** Creating a team gives every member, the lead included, its own runtime and, where the runtime uses one, its own model. A choice that is not available falls back to Clawboo Native instead of blocking the deploy. See [Mix runtimes on one team](/guides/multi-runtime-team). * **Clawboo Native teams chat and delegate.** A Clawboo Native team sends from team chat and streams back, and its lead hands work to teammates the way an OpenClaw lead does. See [Work with your team in chat](/using/team-chat). * **Orchestration runs on the server**, per team, so delegated work carries on after you close the tab. * Failed and abandoned delegations show on the Board instead of leaving the delegating agent waiting. **The graph** * Every Boo carries its runtime's mark and shows its model when expanded, and an agent's MCP servers appear as tiles beside its skills. See [Org chart and team graphs](/using/org-chart). **Resilience and security** * **A degraded Gateway no longer blocks the dashboard** when you have other agents. A banner offers the recovery that applies: restart the Gateway, approve this device, or retry. * Hermes installs on Python 3.11 or newer instead of failing on an older system Python, and an existing `codex login` is recognized. * Binding to a non-loopback address refuses to start without an access token. **0.2.0** 2026-06-20 Clawboo becomes an orchestrator for several agent runtimes instead of a control panel for OpenClaw. Clawboo Native agents are built in: paste a provider key and start. OpenClaw, Claude Code, Codex and Hermes join as teammates in one chat, sharing one board and one memory. * **Five runtimes** behind one adapter: Clawboo Native, OpenClaw, Claude Code, Codex and Hermes. See [Choose a runtime](/runtimes/index). * **A durable task board** that agents claim work from, replacing delegation parsed out of chat text. See [The board](/concepts/the-board). * **Four MCP servers**, Tasks, Memory, Tools and TeamChat, that give any attached agent the board, team memory, tools and team chat. See [MCP tools reference](/reference/mcp-tools). * **Verification**: fixed checks plus an independent critic agent review some task results before they count as done. See [How work is checked](/concepts/verification). * **Guardrails**: USD budgets, tool-loop breakers, limits on delegation depth and fan-out, and an audit log. See [Budgets and guardrails](/concepts/governance). * **Observability**: an append-only event log, traces and an error taxonomy. See [Observability](/concepts/observability). * **Its own home** in `~/.clawboo/`: the database, settings, an encrypted key vault, and a folder per runtime and agent. * **A team-task scheduler**, which later became Routines. * **A release security audit**, and a loopback-only bind by default. **Removed**: the delegation engine that parsed chat text, the experimental Labs UI, and the per-feature flags. **Upgrading**: in 0.2.0 a database schema change meant resetting the database, and a 0.1.x database could not be upgraded in place. Since 0.4.0 an upgrade adds missing columns in place. See [Data and state](/operating/data-and-state). **0.1.9** 2026-06-01 * **A calm welcome screen.** A soft animated sky replaces the WebGL welcome animation on the setup splash and the home screen, which also makes the install smaller and spares the GPU on first load. * **Light theme first.** Fresh installs start in the light theme. Switch to dark or system afterwards. * **Team colors.** Each team picks one of 8 color palettes, and the create-team preview matches the deployed team. * Token usage is tracked by team and by agent. * On Windows, the setup's detect step no longer hangs when a program lookup is slow. **0.1.8** 2026-05-29 * **Light and dark themes**, with a toggle that remembers your choice. See [Choose a light or dark theme](/using/theming). * A design pass over surfaces, type and motion, and the radial layout for the all-teams graph (then called Atlas, now the Org chart). * Real provider logos and a consistent model dropdown in setup, plus fixes for setup hangs. **0.1.7** 2026-05-20 * **Device pairing in the setup wizard.** On a fresh macOS install with OpenClaw 2026.5, the approval card appears inside the wizard, with no page refresh needed. * **Windows fixes.** Launching OpenClaw and npm commands on Windows no longer fails or pops up a console window, and longer timeouts cover a slow first start. * **Retry** at the Gateway start step joins a launch already in progress instead of starting a second Gateway. **0.1.6** 2026-05-20 * Setup checks what is really installed on disk instead of trusting a flag saved in the browser, so you can run the install wizard again after removing OpenClaw. **0.1.5** 2026-05-20 * **Approve this device from the dashboard.** OpenClaw 2026.5 stopped pairing new devices on first connect. An **Approve this device** button approves it without a trip to the terminal, then retries the connection. See [OpenClaw](/runtimes/openclaw). **0.1.4** 2026-05-20 * **OpenClaw protocol 4.** Clawboo connects to OpenClaw 2026.5.18 and later (protocol 4) as well as older releases (protocol 3), and installs OpenClaw as `openclaw@^2026.5`. * **Windows install support.** The install and detect steps find Windows commands such as `npm.cmd`, so **Install** no longer fails with `spawn npm ENOENT`. The CI bundle smoke test gained a Windows leg. **0.1.3** 2026-05-19 * The CLI checks that a port really serves Clawboo before opening it, so it no longer opens your browser on an OpenClaw Gateway port or another app's port. A clean-install test joins CI and the publish gate. **0.1.2** 2026-05-19 * Fixes `Cannot GET /` in 0.1.1: the bundled server serves the dashboard at `/`. **0.1.1** 2026-05-19 The first real release. * A Marketplace with **Skills**, **Agents** and **Teams** tabs: 304 agents and 82 team templates. * The all-teams graph (then called Atlas) and team chat, with Boo Zero leading every team. * Delegation between agents, with multi-step plans and parallel work. * A setup wizard that detects, installs, configures and starts OpenClaw, then deploys a team. * A dashboard port that starts at 18790 and moves up as far as 18809 when a port is taken. This release ran on OpenClaw only. 0.2.0 replaced its chat-text delegation with board-driven orchestration. **0.1.0** 2026-02-28 * A placeholder published to reserve the `clawboo` name on npm. ## Related * [Known issues and limitations](/appendices/known-issues): current limits, and fixes on main that are not released yet. * [Install, update and remove Clawboo](/getting-started/installation): install the latest release or update in place. * [Release process](/internals/release-process): how changesets become a release. * [Glossary](/appendices/glossary): what the product terms mean, and their old names. # Contributing to Clawboo Source: https://docs.claw.boo/appendices/contributing Set up the repo, run the dev stack, pass the local gate, add a changeset, and open a pull request that keeps the architecture rules. Use this page when you want to change Clawboo and open a pull request. It walks the contributor workflow and links into the rest of the docs. The repo's [`CONTRIBUTING.md`](https://github.com/clawboo/clawboo/blob/main/CONTRIBUTING.md) is the authority on process: if the two disagree, `CONTRIBUTING.md` and the root `package.json` win. Why the code is shaped the way it is lives in the [contributor overview](/internals/index). ## Before you begin * **Node.js 22.12 or later** and **pnpm 9**. The root `package.json` declares `engines.node >=22.12.0` and `engines.pnpm >=9.0.0`, and pins `pnpm@9.15.0` as its package manager. * **No external runtime.** Clawboo Native runs agents inside the server, with a provider key or a local Ollama. Install OpenClaw, Claude Code, Codex or Hermes only when you work on that runtime. ## Make a change **1. Clone and install** ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} git clone https://github.com/clawboo/clawboo.git cd clawboo corepack enable pnpm install ``` `corepack enable` makes `pnpm` the pinned 9.15.0, which keeps lockfile diffs clean. `pnpm install` links the whole workspace: `packages/*`, `packages/adapters/*`, `apps/*` and `docs` (see [Monorepo and build](/internals/monorepo-and-build)). **2. Run the dev stack** ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} pnpm dev ``` The dashboard's dev orchestrator picks a free API port first, then starts the Express API and the Vite dev server with that port in their environment, so the UI's proxy and the API always agree. It prints a banner like this: ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} [clawboo-dev] API port: 18790 · UI port: 5173 ``` * The **Vite SPA** serves on port 5173. * The **API** takes the first free port from 18790 to 18809. Pin it with `CLAWBOO_API_PORT` (see [Environment variables](/reference/environment-variables)). * `pnpm dev` also starts the docs preview (`mint dev --port 3111`), which needs the Mintlify CLI. Clawboo keeps its state in `~/.clawboo/`, created on first run. Set `CLAWBOO_HOME` to point a dev instance at a throwaway directory. There are no feature flags: every subsystem is on. Budgets track spend and warn, and nothing pauses a run until someone sets a hard cap. **3. Work on a branch** Clawboo follows [GitHub Flow](https://docs.github.com/en/get-started/using-git/github-flow): branch from `main`, push, open a pull request, and squash-merge once CI is green. Name the branch with a prefix: | Prefix | For | | - | - | | `feat/` | A new feature or surface | | `fix/` | A bug fix | | `chore/` | Tooling, deps, housekeeping | | `docs/` | Documentation only | | `test/` | Tests only | | `refactor/` | Non-behavioral restructuring | **4. Run the local gate** These are the checks CI runs, as root scripts: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} pnpm format:check pnpm lint pnpm typecheck pnpm test pnpm e2e pnpm catalog:verify pnpm verify:connectors pnpm assemble && pnpm test:clean-install ``` * **`pnpm format:check`** runs Prettier; fix failures with `pnpm format`. CI's Lint job runs it before `pnpm lint`. * **`pnpm lint`** runs ESLint across the workspace, the docs frontmatter and heading check, and `scripts/`. * **`pnpm test`** runs every package's Vitest suites, including `apps/web`'s node project (`*.test.ts`) and jsdom project (`*.test.tsx`, with React Testing Library, msw, jest-dom and jest-axe). CI runs the same suites as `pnpm test:coverage`, which adds a coverage table and no threshold. * **`pnpm e2e`** runs Playwright in a sandboxed `$HOME`. It needs `pnpm build` first and a Chromium download (`pnpm exec playwright install chromium`). * **`pnpm catalog:verify`** checks every marketplace content rule, then that `catalog/dist` and the compiled seed match a rebuild. **`pnpm verify:connectors`** checks the committed connector catalog. Both are offline. * **`pnpm assemble && pnpm test:clean-install`** bundles the CLI, packs and installs the tarball in a temp directory, and boots that install, through a headless-Chromium check and one real agent run. It needs network access for its `npm install`, the same Chromium download, and no other Clawboo dashboard on ports 18790 to 18809 (stop `pnpm dev` first). `pnpm prepublish:check` runs the catalog and connector gates, then `pnpm assemble` and `pnpm test:clean-install`. [Testing](/internals/testing) lists every CI job, including the cross-platform tests and the website build that have no single local command. **5. Add a changeset** Clawboo uses [Changesets](https://github.com/changesets/changesets) for versions and changelogs: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} pnpm changeset ``` Choose the `clawboo` package and a bump (`patch`, `minor` or `major`), write a short summary, and commit the new file under `.changeset/`. Every `@clawboo/*` library is private and only `clawboo` publishes, but the dashboard (`apps/web`) ships inside it. So add a changeset for any change a user of the npm package would notice, dashboard changes included. Docs-only, CI-only and test-only changes need none. [Release process](/internals/release-process) has the details. **6. Open the pull request** Push the branch and open a pull request; the template walks the checklist. Keep one concern per pull request, add a test for what you add (Vitest for logic, the jsdom project for components, Playwright for new end-to-end behavior), and get CI green before you ask for review. Pull requests are squash-merged into `main`. Keep the `pnpm-lock.yaml` diff small. If it grows by thousands of lines, you are on a different pnpm: run `corepack enable`, then `pnpm install`, and commit only the intended change. Dependabot opens grouped dependency updates weekly, so you rarely need to touch the lockfile by hand. ## Your first contribution Issues labeled [`good first issue`](https://github.com/clawboo/clawboo/labels/good%20first%20issue) are scoped small and name the files to touch. Comment on the one you want and a maintainer assigns it to you. Good areas that rarely need core changes: a marketplace team template, a docs page, a provider or runtime icon, or a test for an uncovered component. If you are unsure whether an idea fits, open a [Discussion](https://github.com/clawboo/clawboo/discussions) before you write code. A half-finished pull request with a question is welcome. ## Documentation changes The `docs/` directory is this site: hand-edited Mintlify pages with no build step. Merging to `main` redeploys [docs.claw.boo](https://docs.claw.boo) as it is, so the files in your diff are exactly what ships. > [!WARNING] > > Two mistakes break a page. Quote any frontmatter `title` or `description` that contains `: ` or > starts with `@` or a backtick, or Mintlify serves the page as a 404. Never put a bare `%` in a > heading, or the page fails to build its anchors. `pnpm check:docs` catches both, and it also runs inside `pnpm lint` and CI's Lint job. For a full check, use the Mintlify CLI: `npx mint validate` runs a strict build, and `npx mint broken-links --check-anchors` checks links and anchors. Mintlify keeps `/` and quote characters in heading anchors, so link to the page rather than to a heading that contains them. [`docs/README.md`](https://github.com/clawboo/clawboo/blob/main/docs/README.md) covers the layout, the hand-maintained `docs.json` navigation and local preview. ## Contributing a marketplace pack Agent and team content lives in `catalog/`, a plain content folder outside the workspace. Edit `catalog/packs///**`, run `pnpm catalog:build` to regenerate `catalog/dist` and the compiled seed, run `pnpm catalog:verify`, and commit the regenerated `catalog/dist/**` with your change. A pull request that touches only `catalog/` runs the short `catalog-ci.yml` instead of the full matrix, except for `catalog/packs/clawboo/**` (the seed compiled into the npm package), which gets the full matrix. Packs are reviewed as writing and merged by hand. `CONTRIBUTING.md` has the review bar: a description is one or two sentences, `IDENTITY.md` is the full instruction body, a pack with `provenance.repo` ships a `NOTICE.md`, a team has at least two members, and new categories are declared. The format is in the [marketplace catalog reference](/reference/marketplace-catalog). ## Code guidelines Review and, where possible, lint enforce these: * **TypeScript strict.** No `any`, no `@ts-ignore`, no leaking `unknown`. * **No `console.log`.** Log through [`@clawboo/logger`](/reference/packages/index#logger) (pino). * **Lucide icons only**, never emoji in the UI. * **Theme tokens, never raw hex.** Use the CSS variables and Tailwind tokens; brand marks are the only exception. See the [design system](/internals/design-system). * **Keep new database columns addable.** There is no migration ladder; [Schema changes](/internals/monorepo-and-build#schema-changes) lists the rules a new column must meet. * **Version every persisted client store.** A zustand `persist` store sets both `version` and `migrate`, and the migrate is a pure exported function with its own test. * **Pollers pause when the tab is hidden.** A new interval refresh uses `useVisiblePolling` from `apps/web/src/lib/useVisiblePolling.ts`, not a bare `setInterval`. * **Schema fixtures are append-only.** Never edit `packages/pack-format/src/__fixtures__/v1/*.json`; a changed shape gets a fixture under the new version's folder. Each version has a `minimal.json` and a `kitchen-sink.json` that sets every optional field. * **Pure where it claims to be.** Policy and projection functions stay free of side effects and unit-testable. * **No secrets in logs, responses or storage.** Show that a credential is present (the env var name and true or false), never its value. See [Security](/operating/security). ## Architecture rules A change must keep the [architecture invariants](/internals/architecture-invariants). The ones a first contribution is most likely to meet: * **The agent registry says who exists; the OpenClaw Gateway says how OpenClaw agents run.** Read agents through an [`AgentSource`](/internals/agent-source), not straight off the Gateway client. * **Packages never import apps.** Dependencies flow one way: `apps/web` imports `packages/**`, and the adapters import `@clawboo/executor`. Lint enforces it; see [Monorepo and build](/internals/monorepo-and-build). * **Every runtime is a [`RuntimeAdapter`](/internals/runtime-adapter).** A new runtime implements the trait and emits the normalized `RuntimeEvent` stream instead of adding special cases to the orchestrator. * **The board is the record for task and coordination state.** Prefer board changes over parsing prose; see [Board internals](/internals/board-internals). * **Schema changes are additive.** An upgrade only adds missing columns. It never removes a column or changes one's type or constraints, so those are not in-place upgrades. The [contributor overview](/internals/index#caution-surfaces) lists the caution surfaces: files that hold a rule whose breakage shows up far from the edit. Read the linked page before you change one. ## Releases Releases are automated and maintainer-only. When a changeset lands on `main`, `publish.yml` opens a pull request titled `chore: version packages`. Merging it bumps the version, updates the changelog and publishes the `clawboo` package to npm; nothing else publishes, and nobody runs `npm publish` by hand. Before it publishes, the workflow reruns the build, lint, typecheck, tests, the catalog gate and the clean-install gate. It does not rerun every pull request check, and it needs the network. See [Release process](/internals/release-process). ## License By contributing, you agree that your contributions are licensed under the [MIT License](https://github.com/clawboo/clawboo/blob/main/LICENSE). See [License and notices](/appendices/license-and-notices). ## Related * [`CONTRIBUTING.md`](https://github.com/clawboo/clawboo/blob/main/CONTRIBUTING.md): the process document this page follows. * [Contributor overview](/internals/index): the repo map, the architecture and the caution surfaces. * [Testing](/internals/testing): the test layers and every CI job. * [Release process](/internals/release-process): changesets and the publish workflow. # Frequently asked questions Source: https://docs.claw.boo/appendices/faq Short answers about Clawboo: what you need, cost, privacy, where data lives, what agents can touch, remote access, updates and resets. Short answers to the questions people ask most, each with a link to the page that covers it in full. If you are looking at an error message, go to [Troubleshooting](/help/troubleshooting). ## Getting started ### Do I need OpenClaw to use Clawboo? No. Clawboo has a built-in runtime, Clawboo Native, that needs nothing else installed. In the setup wizard you connect one AI provider: an API key from Anthropic, OpenAI, OpenRouter or seven others, or a local Ollama, which needs no key. You can also sign in with your ChatGPT subscription, which runs your agents on Codex. OpenClaw is one of the runtimes you can add later. See the [Quickstart](/getting-started/quickstart) and [OpenClaw](/runtimes/openclaw). ### What is the difference between a provider and a runtime? A provider supplies the AI model and bills you for it: Anthropic, OpenAI, OpenRouter, or Ollama on your own computer. A runtime is the program that runs an agent on that model, with its own tools and habits: Clawboo Native, OpenClaw, Claude Code, Codex or Hermes. One key can serve several runtimes. An Anthropic key, for example, works for Clawboo Native, Claude Code and Hermes. [Choose a runtime](/runtimes/index) compares them. ### Which runtimes can my agents use? | Runtime | What you install | What it needs to run | | - | - | - | | Clawboo Native | Nothing; it is built in | An API key from one of ten providers, or a local Ollama | | OpenClaw | OpenClaw and its Gateway; **Set up OpenClaw** installs both | A provider key, a ChatGPT sign-in or a local Ollama, and a one-time device approval | | Claude Code | The `claude` program, plus the Claude Agent SDK next to a global Clawboo install | An Anthropic API key | | Codex | The `codex` program | **Sign in with ChatGPT** | | Hermes | Hermes, which needs Python 3.11 to 3.13 | An OpenRouter, Anthropic or OpenAI key, or a ChatGPT sign-in | **Settings > Runtimes** installs and connects each one, except the Agent SDK, which you install with `npm install -g clawboo @anthropic-ai/claude-agent-sdk`. One team can mix runtimes. See [Providers and runtimes](/using/providers-and-runtimes). ### Can I use my ChatGPT or Claude subscription instead of an API key? A ChatGPT subscription, yes. **Sign in with ChatGPT** runs your agents on Codex under your ChatGPT plan, with no API key. Hermes and OpenClaw can sign in with ChatGPT too. A Claude subscription, no. Claude Code agents in Clawboo need an Anthropic API key, and a `claude` program signed in with your Claude plan does not count. See [Run Clawboo without an API key](/guides/no-api-key). ### Can I run a local model? Yes, with Ollama and Clawboo Native, and no key. Install Ollama and pull a chat model that can use tools, for example `ollama pull llama3.2`. Then choose **Ollama** in the setup wizard, or click **Use** on the Ollama row under **Settings > Runtimes > Clawboo Native > Manage**, then **Make default** on that row. Clawboo lists the models you have pulled and marks the ones that cannot use tools. If Ollama runs on another machine or port, set `OLLAMA_BASE_URL` before you start Clawboo. See [Run Clawboo without an API key](/guides/no-api-key). On the 0.5.0 release, choosing Ollama during setup is not saved, so a new team tries Anthropic instead. [Known issues](/appendices/known-issues) lists this with the other fixes that arrive in the next release. ### Does Clawboo work on Windows? Yes. Clawboo runs on macOS, Linux and Windows, with Node.js 22.12 or newer. On Windows, Clawboo Native agents cannot run shell commands, and OpenClaw's ChatGPT sign-in runs in a terminal you open yourself. The first start can also take longer while antivirus software scans the new files. See [Install](/getting-started/installation). ### Does Clawboo cost money? Clawboo is free and open source under the MIT license, and there is no Clawboo account. You pay your AI provider for what your agents use. Ollama on your own computer costs nothing, and **Sign in with ChatGPT** uses the ChatGPT plan you already pay for. A team uses more tokens than a single assistant, because every hand-off is another model run. [Budgets and spend](/using/budgets-and-spend) shows how to watch spend and set a limit. ### Why does a local model show a cost? A model your own Ollama serves costs nothing, so Clawboo records its runs at $0, shows the task as **free** and charges no budget. If one shows a cost, Clawboo did not count it as local. Ollama's cloud models (a `-cloud` tag) run on ollama.com, so they are estimated at a default rate, $3 per million input tokens and \$15 per million output tokens, and marked **est.** An OpenClaw agent counts as local only when its model in `openclaw.json` is an `ollama/` one. [Budgets and spend](/using/budgets-and-spend) lists what each runtime reports. ### What should I ask my team first? A job with clear parts and a clear result, such as "Write a welcome email series for new customers of our budgeting app: three short emails with subject lines, and a note on when to send each one." Boo Zero, your team leader, usually splits a job like that into tasks for teammates, then posts one summary when they finish. A quick question gets a direct answer. See [Work with your first team](/getting-started/first-team). ## Privacy and safety ### Is my data sent anywhere? Prompts go only to the providers and apps you connect. There is no Clawboo account, and Clawboo itself sends no telemetry. Your teams, tasks, chats and memory stay in a database on your computer. Clawboo makes a few requests of its own. The server downloads the Marketplace catalog from Clawboo's GitHub repository, asks the npm registry whether a newer Clawboo exists, and fetches OpenRouter's public model list for the model pickers, without a key. In your browser, the dashboard loads its font from Google Fonts and the repository's star count from GitHub. A connector you turn on talks to its own service. When you save a key, Clawboo checks it with one request to the provider's list of models. That request stores nothing, and if the check fails you can still save the key. See [Production defaults](/operating/production-defaults) and the [Security model](/operating/security). ### Where is my data stored? In `~/.clawboo` on your computer, or the folder you set in `CLAWBOO_HOME`. It holds the database with your teams, tasks, chats and memory, your settings, the encrypted key vault, the server log and a folder for each team's files. Clawboo also writes to OpenClaw's `~/.openclaw` folder in a few cases: * Keys you save under **Settings > Providers** are copied there in plain text, so OpenClaw can use them. * **Set up OpenClaw**, and the OpenClaw settings you change in the dashboard, write OpenClaw's own config. * Each time it connects to your OpenClaw Gateway, Clawboo registers its tool servers there. * If you run Clawboo's MCP servers from the command line, they use a separate database in `~/.openclaw/clawboo/` by default. [Data and state](/operating/data-and-state) lists every file, with backup and reset steps. ### Can agents read my files or run commands? It depends on the runtime: * **Claude Code, Codex and Hermes** run with their own approval prompts and sandboxes turned off. They can read and change any file your user account can, and run commands, without asking you. * **Clawboo Native** agents read and write files only inside the folder their run works in: their team's folder in team chat, in one-to-one chat and on research and review tasks, or the task's own git worktree. Commands stay off until you switch on **Let this Boo ask to run commands** on the agent's page, under **Details > Permissions**, and then every command asks you first. * **OpenClaw** agents work in their own OpenClaw workspace, under OpenClaw's own sandbox settings. Their commands follow the **Command Execution** setting under **Details > Permissions** on their page: **Run Freely**, **Ask for Unknown** (where agents you create in Clawboo start) or **Always Ask**. A git worktree keeps a task's file changes apart from your other files, but it is not a sandbox. For work you do not trust, run Clawboo as a separate user or in a virtual machine. See [What agents can do](/concepts/permissions). ### Can I open Clawboo from another computer, or share it with a colleague? Clawboo is built for one person on one computer, with no user accounts or roles. Out of the box it listens only on that computer. To open it from somewhere else, follow [Reach Clawboo from another machine](/guides/self-host-securely). An SSH tunnel is the simplest way. Anything wider needs an access token (`STUDIO_ACCESS_TOKEN`), and anyone who has the token can do everything you can. Use only letters, digits, `.`, `_`, `~` and `-` in the token: with any other character the server refuses to start. It also refuses to start on a network address with no token. > [!WARNING] > > On clawboo 0.5.0 and older, a reverse proxy on the same machine leaves every `/api/mcp/` path open, even with an access token set. Deny all of `/api/mcp/` at the proxy, or upgrade once a release newer than 0.5.0 is out. The [Security model](/operating/security) has the rules for nginx, Caddy and Cloudflare Tunnel. Run `clawboo --version` to see your version. ## Working with your team ### Do I have to keep the dashboard open while agents work? No, but the Clawboo server has to keep running. The server runs your team's work, so you can close the browser tab. `clawboo` starts the server in the background, and it keeps running after you close the terminal. Routines and background clean-up run in the server too: while it is stopped, routines wait, and a routine that came due fires once when the server runs again. See [How routines run](/concepts/scheduling). ### Where do I find what my team made? Boo Zero posts a summary in the chat when the teammates finish. Each teammate's full result is saved on its task: open **Comments & activity** on the task card in the chat, or open the task on the **Board**. Agents return their results as text. Claude Code, Codex, Hermes and Clawboo Native can also write files. In team chat they start in their team's folder (`~/.clawboo/workspaces/`), which the agent's **Workspace** tab shows. Claude Code, Codex and Hermes can write anywhere you can, so name the folder you want when you ask for files elsewhere. See [Work with your first team](/getting-started/first-team). ### My agent is not doing what I asked. Where do I start? With the checklist in [Troubleshooting](/help/troubleshooting#when-an-agent-is-not-doing-what-you-asked). The usual causes, most common first: the agent runs on another runtime or model than you think, it has no working provider, its model cannot use tools, or it never read your file. After those come an app that is not shared with the agent, an approval waiting in **Needs you**, a budget that paused the work and a blocked task. ### Why can't my agent use the app I connected? Connecting an app under **Connectors** makes it available, but gives no agent access. Give it to each agent that needs it on a graph, such as the Org chart or a team graph: drag the connector onto the agent's Boo. The agent then has write access, and risky calls ask you first. Every agent already has the two browser connectors, Playwright and Chrome DevTools. OpenClaw agents cannot use connectors you share with them; they reach apps through Composio, and every such call asks you first. See [Connectors](/using/connectors). ### How do agents on different runtimes work together? Through the team's chat and board. When the leader hands off work, Clawboo turns the hand-off into a task on the board and sends it to the teammate, whatever runtime it uses. The teammate's result lands on that task, and Clawboo sends it back to the leader. Every runtime can use the team's shared memory, except that OpenClaw agents see only global memory. See [How a team works](/intro/how-it-works). ### What is the difference between General and a room? General is a team's main chat, with every member. A room is a separate chat with only the members you pick, for example for a side project, with its own conversation and task cards. Boo Zero leads General and every room, and all of a team's chats share its rules, brief, memory and board. See [Rooms](/using/team-chat#rooms). ### What is the difference between the Org chart and a team graph? The Org chart shows all your teams at once, with Boo Zero, your team leader, above them. Open it from the sidebar, or with `Cmd+1` (`Ctrl+1` on Windows and Linux). A team graph is the **Team graph** card beside a team's chat, which draws only that team; in a room, the card is the **Room graph**. See [Org chart](/using/org-chart). ### Can agents work while I'm away? Yes. A routine sends work on a schedule to a team or to one agent, for example every Monday at 9am. The Clawboo server must be running when it is due. When you come back, **Needs you** on the **Board** lists what waits for you: approvals, and failed or stopped tasks. Budgets only warn unless you choose a hard cap, and even a hard cap stops some kinds of runs only after they finish. See [Routines](/using/routines) and [Budgets and spend](/using/budgets-and-spend). ### My agent says the work is done. Did Clawboo check it? Usually not. Clawboo checks work only on a task run that worked in its own copy of a git repository (a worktree) and changed files, and only the API starts those runs. Then it runs the task's test command and, for bigger or handed-off changes, has a second agent review the change. A failed check sends the task back to the agent once, then to **Needs you**. Nothing you start in the dashboard is checked: team chat, tasks on the **Board**, one-to-one chats, routines made in the dashboard and all OpenClaw work. Review those results yourself. See [How work is checked](/concepts/verification). ### How do I keep spending under control? No budget exists until you add one in **Settings > Insights > Governance**. A new budget is **warn only** by default: Clawboo counts spend against the limit and turns the budget amber at 80%, but never pauses anything. Choose **hard cap** to pause work at the limit; what that stops depends on where the work runs. To halt a team right away, press **Stop** in its chat. See [Budgets and spend](/using/budgets-and-spend). ## Running and maintaining Clawboo ### How do I stop or restart the server? ```bash theme={"theme":"github-dark"} clawboo stop clawboo restart ``` `clawboo stop` is safe to run when nothing is running. `clawboo restart` starts a fresh server on the same port and opens your browser. With npx, run `npx clawboo@latest stop`. On a server protected by `STUDIO_ACCESS_TOKEN`, both commands refuse and print a command that stops the server by its port. See the [CLI reference](/reference/cli). ### How do I update Clawboo? When a newer version is out, an **Update available** chip appears at the bottom left of the dashboard. On a global install, click it to update and restart. If you use npx, it copies `npx clawboo@latest` for you to run. From a terminal: ```bash theme={"theme":"github-dark"} npm install -g clawboo@latest clawboo restart ``` **Check for Updates** in **Settings > System** updates OpenClaw, not Clawboo. See [Update Clawboo](/getting-started/installation#update-clawboo). ### How do I reset Clawboo? An upgrade needs no reset: a newer Clawboo adds what it needs to your database when it opens it. Reset only to wipe data, because a reset deletes data you cannot get back. Stop the server first, or it keeps serving the old database. If the server uses an access token, `clawboo stop` cannot stop it: stop it with Ctrl+C in its terminal, with your service manager, or with the command that `clawboo stop` prints, and delete files only after it has stopped. To reset only the data, delete the database and its two side files. The `&&` deletes them only if `clawboo stop` succeeds: ```bash theme={"theme":"github-dark"} clawboo stop && rm -f ~/.clawboo/clawboo.db ~/.clawboo/clawboo.db-wal ~/.clawboo/clawboo.db-shm clawboo ``` Your provider keys and `settings.json` stay. Everything in the database starts over: teams and agents, the board, chats, memory, routines, budgets and most dashboard settings, including your default model. To reset everything, including the saved keys, delete the whole `~/.clawboo` folder instead. That also deletes task worktrees with any uncommitted work in them, and `clawboo backup` does not copy those, so commit or copy that work first. If you set `CLAWBOO_HOME`, use that folder in place of `~/.clawboo`. Keys you saved under **Settings > Providers** also live in `~/.openclaw/.env`, which a reset does not touch. To keep a copy of the database first, run `clawboo backup`. See [Hard reset](/operating/data-and-state#hard-reset). ## Related * [Troubleshooting](/help/troubleshooting): fixes, by the message you see. * [Known issues](/appendices/known-issues): limitations we know about. * [Glossary](/appendices/glossary): what each term means. * [Key concepts](/concepts/index): how the pieces fit together. # Glossary Source: https://docs.claw.boo/appendices/glossary Plain definitions of the words in Clawboo and its docs, from A to Z, and the names the app uses today next to older names you may still see. Plain definitions of the words you meet in Clawboo and in these docs, from A to Z. Each entry links to the page that explains it. If you saw a name somewhere else, such as Atlas or Ghost Graph, look it up in [Names in the app](#names-in-the-app). Terms for contributors, with their code names, are under [For developers](#for-developers). ## Terms ### A **Access token**: an optional secret that guards the dashboard and Clawboo's API. Without one, anything that can reach the server can use it. With one, each browser presents the token once before it gets in. Set one before you open Clawboo to other computers. See [Security model](/operating/security). **Agent**: an AI worker on one of your teams. Each agent runs on one runtime and one model, follows its own instruction files, and appears in the dashboard as a Boo. See [Agents](/using/agents). **Agent files**: the instruction files an agent keeps, edited under **Details** > **Agent Files** on its page. SOUL.md holds its personality and instructions, and AGENTS.md lists the teammates it hands work to. What each file does depends on the runtime. See [Agents](/using/agents). **Approval**: a request that waits for your yes before it goes ahead: a shell command from a Clawboo Native or OpenClaw agent, a connector call that needs a yes, or a risky hand-off in team chat. Approvals appear in **Needs you** on the Board and in the chat where the agent works. See [Approvals](/using/approvals). ### B **Board**: every task in one place, by status and by day. Its first column, **Needs you**, holds what waits on you. See [Board](/using/board). **Boo**: how the dashboard draws an agent: a small mascot with a badge for the agent's runtime. Buttons such as **Create Boo** use the word too. See [Agents](/using/agents). **Boo Zero**: your team leader. One agent leads the chats of every team: it answers messages that @mention no one, and hands work to teammates. Clawboo creates it on Clawboo Native once you have a Clawboo Native agent on a team and a connected provider; otherwise it uses your OpenClaw Gateway's default agent. See [Boo Zero](/using/boo-zero). **Brief**: a team's short description, edited together with its rules. Boo Zero reads it only when you start a message in Boo Zero's own chat with @ and the team's name, and then only its first 900 characters. Boo Zero's page also has a **Global Brief**, which Clawboo saves but does not use yet. See [Teams](/using/teams) and [Boo Zero](/using/boo-zero). **Budget**: a dollar limit on one agent, team or mission. In **warn only** mode, the default, Clawboo counts spend and warns you as the budget fills. In **hard cap** mode, which you opt into, it also pauses work at 100%: new runs are refused, though a run already going may finish first. No budget exists until you create one. See [Budgets and spend](/using/budgets-and-spend). ### C **Capabilities**: the skills, tools and connectors your agents can use. **Settings** > **Capabilities** lists them all, with what each one needs. See [Capabilities](/using/capabilities). **Circuit breaker**: an automatic stop for a task-runner run that is going nowhere, such as one that repeats the same failing call. See [Budgets and guardrails](/concepts/governance). **Claude Code**: Anthropic's coding agent, one of the runtimes Clawboo can run agents on. It needs an Anthropic API key. See [Claude Code](/runtimes/claude-code). **Clawboo Native**: Clawboo's built-in runtime, with nothing to install. It runs agents on an API key from one of ten providers (Anthropic, OpenAI, OpenRouter, Google, xAI, Groq, Mistral, Together, Cerebras or Moonshot), or on a local Ollama with no key. See [Clawboo Native](/runtimes/native). **Clear chat**: empties one chat (General or a room) and starts that conversation fresh for its agents. Clawboo sets the messages aside rather than deleting them, and the chat's tasks stay on the Board. See [Team chat](/using/team-chat). **Codex**: OpenAI's coding agent, one of the runtimes Clawboo can run agents on. You connect it with **Sign in with ChatGPT**, not an API key. See [Codex](/runtimes/codex). **Connector**: a link to an outside app or tool, such as GitHub, Gmail, a folder or a web browser, that agents use through Clawboo. Connecting one gives no agent access until you share it with that agent on the graph. The two browser connectors are the exception: every new agent gets them. See [Connectors](/using/connectors). **Cost estimate**: a cost Clawboo works out itself when a runtime reports none, marked **est.** on task cards. Models Clawboo has no price for are estimated at a default rate. A model your own Ollama serves is not estimated: it costs nothing, so its runs read **free**. See [Budgets and spend](/using/budgets-and-spend). ### D **Delegation**: an agent handing part of its work to a teammate, also called a hand-off. Each hand-off becomes a task on the Board. See [How a team works](/intro/how-it-works). **Device pairing**: OpenClaw makes you approve each new device before it can connect to the OpenClaw Gateway. Clawboo shows an **Approve this device** card that does it for you. See [OpenClaw](/runtimes/openclaw). ### F **Fleet**: **Settings** > **Insights** > **Fleet**: all your agents at a glance, with recent task and check results, spend, and health per runtime. See [Tour the dashboard](/getting-started/dashboard-tour). ### G **General**: a team's main chat, with every member, listed as **General (Main Room)**. You can clear it, but not rename or delete it. See [Team chat](/using/team-chat). **Governance**: **Settings** > **Insights** > **Governance**: budgets, limits, the approval queue and the audit log. See [Budgets and guardrails](/concepts/governance). ### H **Hermes**: an open-source agent, published as the `hermes-agent` Python package, and one of the runtimes Clawboo can run agents on. It keeps its own memory and skills between runs. See [Hermes](/runtimes/hermes). **Home**: a team's own page, opened from the team's icon on the team rail. It is a placeholder for the team's dashboards and its agents' work; to work with the team, open **General**. See [Tour the dashboard](/getting-started/dashboard-tour). ### L **Leader**: a badge Clawboo gives the team member whose name or role reads as a lead when you deploy a team template. Boo Zero still leads the team. The Leader leads the team's chats only when your install has no Boo Zero. See [Teams](/using/teams). ### M **Marketplace**: ready-made agents, teams and skills that you deploy with a click. Clawboo downloads the full catalog from GitHub; offline, it shows a built-in set of 5 teams and 15 agents. See [Marketplace](/using/marketplace). **MCP**: the Model Context Protocol, an open standard for giving AI agents tools from another program. Agents reach Clawboo's board, memory and tools through Clawboo's own MCP servers, and most connectors are MCP servers too. See [MCP servers](/operating/mcp-servers). **Meet your team**: the introduction a team's chat shows on your first visit: the team's agents, then a few words from you about who you are. The agent that answers you in any of the team's chats receives your introduction with your message. See [Team chat](/using/team-chat). **Memory**: the facts your agents save and share, shown in the sidebar's **Memory** view as a graph and a list. An agent on a team reads its team's facts and the global ones. See [Memory](/using/memory). **Mission**: a task together with every task handed off under it. A budget can cover one mission. See [Budgets and guardrails](/concepts/governance). **Model**: the AI model an agent runs on, such as a Claude model, a GPT model or a local Ollama model. You pick a default, and you can change the model per agent. See [Connect providers and runtimes](/using/providers-and-runtimes). ### N **Needs you**: the Board's first column. It holds approvals and every task that waits on a person: one that failed, timed out, was stopped, is blocked, has no agent, needs review, or never started. See [Board](/using/board). ### O **Ollama**: a free program that runs AI models on your own computer. Clawboo Native can run agents on it with no API key. See [Connect providers and runtimes](/using/providers-and-runtimes). **OpenClaw**: an open-source agent runtime that runs its own Gateway on your computer. Clawboo can run agents on it next to its other runtimes. See [OpenClaw](/runtimes/openclaw). **OpenClaw Gateway**: the program OpenClaw runs on your computer. Clawboo connects to it to create, run and chat with OpenClaw agents. See [OpenClaw](/runtimes/openclaw). **Org chart**: every agent on every team on one live chart, around Boo Zero. Open it from the sidebar or with `Cmd/Ctrl + 1`. See [Org chart and team graphs](/using/org-chart). ### P **Peer chat**: the shared channel where teammates on different runtimes post updates to each other while they work. You do not see it in the dashboard; your chats with a team are General and its rooms. See [How agents talk to each other](/concepts/peer-chat). **Permissions**: the **Permissions** section on an agent's page, under **Details**. On Clawboo Native it holds the switch that lets the agent ask to run shell commands. On OpenClaw it sets when the agent asks before running a command, and lists the commands it may run without asking. See [Command permissions](/using/command-permissions). **Personality**: the presets and sliders on an agent's page that set its tone. Clawboo writes the result into the agent's SOUL.md. See [Agents](/using/agents). **Provider**: the service that runs an AI model, such as Anthropic, OpenAI, OpenRouter or your own Ollama. You connect one with an API key or a sign-in. Clawboo is free; the provider bills any usage. See [Connect providers and runtimes](/using/providers-and-runtimes). ### R **Room**: a smaller chat inside a team, with some of its members. It has its own conversation, task cards and graph, while the team's rules, brief, memory and board stay team-wide. Boo Zero leads every room. Deleting a room archives it. See [Team chat](/using/team-chat). **Route**: a line on the Org chart from one agent to another, meaning "this agent hands work to that one". It is a line in the first agent's AGENTS.md, and only OpenClaw agents follow it. See [Org chart and team graphs](/using/org-chart). **Routine**: work that runs on a schedule. A team routine posts its instructions in the team's chat for Boo Zero; an agent routine gives one agent a task. See [Routines](/using/routines). **Runtime**: the engine that runs an agent: Clawboo Native (built in), OpenClaw, Claude Code, Codex or Hermes. A team can mix runtimes. See [Choose a runtime](/runtimes/index). ### S **Settings**: one window with three groups. **Workspace** holds Providers, Runtimes, Memory, Capabilities and Routines; **Insights** holds Fleet, Tokens Used, Observability and Governance; **System** holds System and System Health. Open it from the sidebar or with `Cmd/Ctrl + ,`. See [Tour the dashboard](/getting-started/dashboard-tour). **Share**: to give an agent a connector on the graph. Pull a thread off its Boo and pick the connector, or drop a connector tile onto the Boo. Dragging a tile from another agent's ring asks for **Read**, **Write** or **Admin** access. **Stop sharing** takes it back from one agent. See [Connectors](/using/connectors). **Sign in with ChatGPT**: connects Codex to your ChatGPT plan instead of an API key. Hermes and OpenClaw can use the plan too, each with its own sign-in. Clawboo Native cannot. See [Connect providers and runtimes](/using/providers-and-runtimes). **Skill**: a label for something an agent is good at, picked from the Marketplace's **Skills** tab or on the graph. It shows on the agent's ring and in the Capabilities panel, but gives the agent no new tool and does not change its instructions. Hermes agents also write skill files of their own, which Hermes reads. See [Capabilities](/using/capabilities). **Standing permission**: a command an OpenClaw agent may run without asking again, recorded when you answer **Always** to its command approval. You see and remove them in the agent's **Permissions** section. See [Command permissions](/using/command-permissions). **Steer**: to reply on a task while an agent is working on it, so the agent reads your reply during the run. A reply reaches the task's agent at any other status too: a waiting task starts again, and a finished one offers **Send as a follow-up task**. Reply from the Board's task drawer, a team chat's **Tasks** card or the agent's **Tasks & Routines** card. See [Steer a running task](/using/board#steer-a-running-task). **System Health**: **Settings** > **System** > **System Health**: Clawboo's startup checks, with a way to run them again. See [System Health](/using/system-health). ### T **Task**: one piece of work on the Board, usually with an agent, a status and comments. See [Board](/using/board). **Task runner**: one of the two ways Clawboo runs agents. It runs agent routines on Clawboo Native, Claude Code, Codex and Hermes, and runs started through the API. Only the task runner gives a run that names a git repository its own worktree, checks finished work, and stops runs that go nowhere. Most work runs in team chat instead. See [How a team works](/intro/how-it-works). **Tasks card**: the card beside a team chat that sorts the chat's tasks into **Needs you**, **In progress**, **To do**, **Done** and **Cancelled**. Open a task there to read its comments, steer it, or open it on the Board. An agent's page has a **Tasks & Routines** card instead. See [Team chat](/using/team-chat). **Team**: a group of agents that work together, with its own chats, rules, brief and memory. Deploy one from the Marketplace or build your own. See [Teams](/using/teams). **Team chat**: a team's conversations: **General** and any rooms. When you send a message that @mentions no one, Boo Zero answers it or hands the work to a teammate. See [Team chat](/using/team-chat). **Team graph**: the card beside a team's chat that draws the team, with each agent's live status. In a room it is the **Room graph**. See [Org chart and team graphs](/using/org-chart). **Team rules**: short rules that every member of a team follows on every turn in that team's chats. Edit them with the team's brief, or add one with `/rule` in a chat. See [Teams](/using/teams). **Tokens Used**: **Settings** > **Insights** > **Tokens Used**: recorded token use and cost by team and agent, for every runtime. See [Budgets and spend](/using/budgets-and-spend). **Trace**: the record of one task-runner run, with its steps, tool calls and errors, shown in the **Observability** panel. See [Observability](/concepts/observability). ### V **Vault**: Clawboo's encrypted store for the keys you connect, kept in your Clawboo folder. Keys you save under **Providers** are also copied, unencrypted, into OpenClaw's files. See [Where keys are stored](/using/providers-and-runtimes#where-keys-are-stored). **Verification**: Clawboo's check on finished work. It runs the project's test command, and for bigger changes a second agent reviews the work. It runs only for task-runner runs that worked in their own git worktree and changed files, so most work is never checked. See [How work is checked](/concepts/verification). ### W **Worktree**: a separate working copy of a git repository, on its own branch, where one task's file changes stay apart from other work. It is not a sandbox: the agent can still reach the rest of your computer. See [Worktrees and handoff](/concepts/worktrees-and-handoff). ## Names in the app The dashboard's labels are the names these docs use. The right-hand column lists older or other names you may meet in earlier releases, older pages, or elsewhere. | In the app | What it is | Older or other names | | - | - | - | | **Org chart** | Every team on one chart, around Boo Zero | Atlas (the sidebar name up to 0.5.0), Ghost Graph | | **Team graph**, **Room graph** | The graph card beside a team's chat, or beside a room | Ghost Graph | | **General** | A team's main chat, shown as **General (Main Room)** | Group Chat (the sidebar name up to 0.5.0), group chat | | A team chat with its cards (**Browser**, **Team graph**, **Tasks**, **Activity**) | A team's working view | team space | | **Meet your team** | The one-time introduction before a team's chat opens | Know Your Team gate | | **Routines** | Work that runs on a schedule, for a team or for one agent | Scheduler (before 0.4.0) | | **Needs you** | The Board's first column | Needs approval (before 0.4.0) | | **Tokens Used** | Recorded token use and cost, for every runtime | Cost dashboard; the welcome screen's **Cost** button opens it | | **warn only**, **hard cap** | The two budget modes | kill-switch (for hard cap) | | **Clawboo Native** | The built-in runtime | native runtime, native harness, `clawboo-native` (its id) | | **OpenClaw Gateway**, **Gateway** | OpenClaw's own server on your computer | gateway | | **Boo** | An agent, as the dashboard draws it | agent | | **Boo Zero** | Your team leader | universal leader, team lead | | **Leader** | The badge on a team's lead member | team lead | | **Memory** | Team memory as a graph and a list | Memory browser | | **Capabilities**, **Governance**, **Observability**, **System Health** | Panels in Settings | Capabilities dashboard, Governance dashboard, Observability dashboard | | **Fleet** | **Settings** > **Insights** > **Fleet** | Fleet in the sidebar (up to 0.5.0) | | **System** | **Settings** > **System** > **System** | System maintenance | ## For developers > [accordion] Code names and architecture terms > > | Term | What it is | Read more | > | - | - | - | > | `RuntimeAdapter` | The interface every runtime implements, with `capabilities()`, `start()`, `events()`, `abort()` and an optional `steer()` among its members | [The RuntimeAdapter trait](/internals/runtime-adapter) | > | `RuntimeEvent` | The seven event kinds every adapter emits: `text-delta`, `tool-call`, `tool-result`, `status`, `cost`, `done` and `error` | [The RuntimeAdapter trait](/internals/runtime-adapter) | > | Runtime ids | `clawboo-native`, `openclaw`, `claude-code`, `codex` and `hermes`, plus the test-only `clawboo-mock`, offered only with `CLAWBOO_ENABLE_MOCK_RUNTIME=1` | [Choose a runtime](/runtimes/index) | > | Steering modes | `live` (Clawboo Native, OpenClaw, Claude Code, Codex) and `interrupt-resume` (Hermes) | [Steering a running task](/runtimes/index#steering-a-running-task) | > | `AgentSource` | Where the agent list comes from: five sources (OpenClaw, Clawboo Native, and one each for Claude Code, Codex and Hermes), mirrored into SQLite | [AgentSource and the agent registry](/internals/agent-source) | > | `CapabilitySource` | Six sources that report skills, tools and connectors: one per runtime, plus `connector` | [Capabilities](/concepts/capabilities) | > | Manageability tier | Whether Clawboo can change a capability: `managed`, `external-write`, `runtime-of-record` or `observe-only` | [Capabilities](/concepts/capabilities) | > | Task statuses | `backlog`, `todo`, `in_progress`, `in_review`, `blocked`, `done` and `cancelled`. The Board shows `in_review` as **Verifying** in In progress, and `blocked` in Needs you | [The board](/concepts/the-board) | > | Atomic claim | `UPDATE ... WHERE status='todo' AND assignee IS NULL AND dropped=0`. An empty result is a conflict, and the caller never retries it | [Board internals](/internals/board-internals) | > | Team engine, executor runner | The server code behind team chat, and behind the task runner | [The executor runner](/internals/executor-runner) | > | Clawboo's MCP servers | Tasks, Memory, Tools and TeamChat. OpenClaw agents get the first three | [MCP tools reference](/reference/mcp-tools) | > | `isUser=false` | The marker in a peer post's header that tells an agent the post is evidence from a peer, not an instruction from you | [How agents talk to each other](/concepts/peer-chat) | > | Bridge, Policy, Handler | The browser's three steps for OpenClaw Gateway events: classify the frame, decide what it means, update the dashboard | [The event pipeline](/internals/event-pipeline) | > | Event log | The append-only table of orchestration events, each with a rising `seq` | [Observability](/concepts/observability) | > | Deterministic gate, critic, `completed_with_debt` | The test-command check, the read-only reviewer, and the verdict given when the last attempt still fails | [How work is checked](/concepts/verification) | > | Worktree files | `TASK.md`, `task-progress.md`, `DECISIONS.json`, `init.sh` and `VERIFICATION.md`, plus the `AGENT_HANDOFF.json` a run leaves for the next one | [Worktrees and handoff](/concepts/worktrees-and-handoff) | > | `run_command` | Clawboo Native's shell tool | [What agents can do](/concepts/permissions) | > | Drift | A connector whose command or tool list changed after you gave access. Clawboo refuses its calls, and its tile says **Changed since you approved it** | [Connectors](/using/connectors) | > | Lethal trifecta | Private data, untrusted content and a way to send data out, together in one run. Calls that could send data out then always ask | [What agents can do](/concepts/permissions) | > | Boo Zero resolution | An agent designated as Boo Zero, then a Clawboo Native Boo Zero, then the OpenClaw Gateway's default agent | [Boo Zero](/using/boo-zero) | ## Related * [Tour the dashboard](/getting-started/dashboard-tour): where each of these things lives on screen. * [Key concepts](/concepts/index): the ideas behind the terms, in reading order. * [Known issues](/appendices/known-issues): what does not work yet, and what to do about it. * [FAQ](/appendices/faq): short answers to common questions. # Known issues and limitations Source: https://docs.claw.boo/appendices/known-issues What doesn't work yet in Clawboo, or works with a catch: what you see, what it affects, what to do, and whether a fix is waiting for a release. This page lists what does not work yet in Clawboo, or works with a catch. Each entry says what you see, what it affects, what to do about it, and its status: * **Open**: no fix exists yet. * **Fixed on main**: the fix is in the source code but not in an npm release yet. It ships with the next release. The latest npm release is 0.5.0. Run `clawboo --version` to see which one you have. For error messages and their fixes, see [Troubleshooting](/help/troubleshooting). ## Security and access ### A reverse proxy on the same machine exposes the MCP servers (0.5.0 and older) > [!WARNING] > > **What you see:** nothing. Behind nginx, Caddy or a Cloudflare Tunnel connector on the same machine, requests to `/api/mcp/` succeed without the access cookie, even with `STUDIO_ACCESS_TOKEN` set. > > **What it affects:** a same-host proxy delivers every request from `127.0.0.1`, and these releases let any request from `127.0.0.1` reach every `/api/mcp/` path without the cookie. Anyone who can reach your proxy can use Clawboo's MCP servers (your boards, memory, team chat and tools). They can also ask `/api/mcp/config` for an attach URL signed as any of your agents, with that agent's connector grants. > > **What to do:** deny all of `/api/mcp/` at the proxy, not only the four MCP server routes, because `/api/mcp/config` is the one that signs attach URLs. [Security model](/operating/security) has the nginx, Caddy and Cloudflare Tunnel rules. Or upgrade once the next release is out. > > **Status:** fixed on main. From the next release, a request without the cookie reaches an MCP server only with an attach token, only on a direct connection to `127.0.0.1`, and never through a proxy. `/api/mcp/config` always needs the cookie. ### An access token with an unsupported character turns the access check off **What you see:** the dashboard opens without asking for your token, and the server log says `STUDIO_ACCESS_TOKEN contains an unsupported character` and that the access gate is disabled. **What it affects:** Clawboo accepts only letters, digits, `.`, `_`, `~` and `-` in the token. Any other character switches the token check off. With Clawboo on `127.0.0.1` behind a reverse proxy on the same machine, the dashboard and every `/api/` route are then open to anyone who can reach the proxy. A bind to other machines (`HOST=0.0.0.0`) refuses to start instead. **What to do:** make a token from the allowed characters, for example with `openssl rand -hex 24`. Restart Clawboo and check that the dashboard asks for the token. See [Reach Clawboo from another machine](/guides/self-host-securely). **Status:** fixed on main. From the next release, such a token stops the server at startup on every bind, with a `SECURITY: refusing to start` error that names the character. `CLAWBOO_ALLOW_INSECURE=1` does not skip it. ### Keys saved under Providers are also stored unencrypted for OpenClaw **What you see:** your provider keys appear in plain text in `~/.openclaw/.env`, even if you never installed OpenClaw, and in each OpenClaw agent's `auth-profiles.json`. **What it affects:** Clawboo copies every key you save on **Settings** > **Providers** into OpenClaw's files so an OpenClaw Gateway can use it. Anything that runs as your user account can read those files, including the coding agents. Deleting `~/.clawboo` removes the encrypted vault but not these copies. **What to do:** if you do not use OpenClaw, connect keys on the runtime's row in **Settings** > **Runtimes** instead: keys entered there stay in the encrypted vault. To remove a copied key, click **Disconnect** on its row under **Settings** > **Providers**. See [Where keys are stored](/using/providers-and-runtimes#where-keys-are-stored). **Status:** open. The copy is how the OpenClaw Gateway gets your keys. ### Disconnecting a runtime deletes keys other runtimes use **What you see:** after you click **Disconnect** on a runtime in **Settings** > **Runtimes**, another runtime can read **Needs key**. **What it affects:** a runtime's **Disconnect** deletes every key that runtime reads from the vault, and the vault keeps one copy of each key for all runtimes. Disconnecting Hermes, for example, also deletes the Anthropic and OpenAI keys that Clawboo Native or Claude Code run on. The confirmation mentions only one key. **What to do:** to remove a single key, use **Disconnect** on its row under **Settings** > **Providers**. To undo a runtime's disconnect, connect the keys again. See [Disconnect a key or a runtime](/using/providers-and-runtimes#disconnect-a-key-or-a-runtime). **Status:** open. ## What agents can reach ### Claude Code, Codex and Hermes run with their own sandboxes off **What you see:** coding agents run commands, edit and delete files, and reach the network without asking you. **What it affects:** these runtimes start with their own permission prompts and sandboxes turned off, so they can work unattended. They run as your user account and can read and change any file it can. Without a worktree, as in team chat, in an agent's own chat and in agent Routines you create in the app, they start in their team's folder (`~/.clawboo/workspaces/`). That folder only sets where they start: they can still reach Clawboo's database and saved keys. A git worktree keeps one task's file changes apart from another's; it is not a sandbox. **What to do:** give coding agents only work you would let them do unattended. For work you do not fully trust, run Clawboo under a separate user account or in a virtual machine. See [What agents can do](/concepts/permissions). **Status:** open, by design. ### Claude Code agents follow your own Claude Code settings **What you see:** a Claude Code agent behaves as if it had your personal Claude Code setup, for example following instructions from your `CLAUDE.md`. **What it affects:** Clawboo does not limit which settings files Claude Code loads. Every Claude Code agent reads what your own `claude` command reads: your user settings and memory in `~/.claude`, plus any `.claude` settings and `CLAUDE.md` in the folder it works in. All Claude Code agents share them. **What to do:** keep anything you do not want agents to follow out of `~/.claude`, or run Clawboo under a separate user account. See [Claude Code](/runtimes/claude-code). **Status:** fixed on main. From the next release, Claude Code agents load only the Claude Code files of the folder they work in, such as a repository's `CLAUDE.md`. Your `~/.claude` settings, plugins, MCP servers and memory files, and Claude Code's auto memory, are left out. ### Clawboo Native shell commands are one approved command at a time **What you see:** an agent with shell commands turned on asks you before every command, and never offers **Always**. **What it affects:** * Each command is one program and its arguments, with no pipes, redirects or chained commands. * Commands are offered in team chat, one-to-one chat and task runs alike, but never on Windows. * One run can ask about at most 10 commands. A card waits 10 minutes for your answer, and the run waits with it. * Once you decline a command, or let a card expire, the agent cannot ask again in that run. **What to do:** turn shell commands on only for agents that need them, and answer cards promptly. See [What agents can do](/concepts/permissions) and [Approvals](/using/approvals). **Status:** open, by design. ## Costs and budgets ### Models without a known price are estimated at $3 and $15 per million tokens **What you see:** a run on a model Clawboo has no price for, such as gpt-5.4 or one of Ollama's cloud models, shows a cost such as `~$0.02 est.` on its task card. **What it affects:** Clawboo's price table lists Claude models and two GPT-4o models. Any other model is priced at a default rate of $3 per million input tokens and $15 per million output tokens. The run is marked **est.**, and the estimate counts toward your budgets. A model your own Ollama serves is not affected: it is recorded at \$0 and reads **free**. **What to do:** treat **est.** figures as rough. For flat-rate plans, set budgets with the placeholder rate in mind; your provider's bill is the real number. **Status:** open. ### A hard cap does not stop most runs partway **What you see:** a budget in **hard cap** mode reads paused at 100%, but a run that was already going keeps working until it finishes, and the budget ends up past its limit. **What it affects:** only Clawboo Native reports cost while it works, so only a Clawboo Native run can be stopped partway (in team chat and one-to-one chat, only on a model Clawboo prices). Every other run is charged when it finishes. New runs are refused once the budget is paused, on every path. **What to do:** to end a run that is going, press **Stop** in the chat. See [What a hard cap stops](/concepts/governance#what-a-hard-cap-stops). **Status:** open. ### A tenant budget never warns or pauses **What you see:** a tenant budget, made before the Governance form stopped offering **tenant** or through the API, stays at \$0 spent. **What it affects:** nothing charges a tenant budget, so there is no budget for the whole install. **What to do:** set budgets per team or per agent instead. **Status:** open. ## Checks on finished work ### Most work is never checked **What you see:** tasks reach **Done** without a check result. **What it affects:** Clawboo checks work only for a task-runner run that worked in its own git worktree and changed files. Team chat, Routines created in the app and OpenClaw work are never checked. When a check does run, it uses the verify command in the worktree's `init.sh`. That starts as a placeholder, and a placeholder counts as a failure, so the first check fails unless the agent sets a real command. **What to do:** review results yourself. For runs that are checked, name the test command in the task, for example: "Before you finish, set `VERIFY_CMD` in `init.sh` to `npm test`." See [How work is checked](/concepts/verification). **Status:** open. ## OpenClaw agents ### OpenClaw agents share one memory across teams **What you see:** a fact an OpenClaw agent saves shows up for OpenClaw agents on other teams. **What it affects:** OpenClaw agents read and save only global facts, because one Gateway setting serves all of them and Clawboo cannot tell which OpenClaw agent is asking. Agents on other runtimes read their own team's facts and global ones. **What to do:** for work that needs memory kept per team, use another runtime. See [Memory](/using/memory). **Status:** open. ### Connectors you share with an OpenClaw agent do not reach it **What you see:** an OpenClaw agent says it has no tool for a connector you drew to it on the graph, the browser connectors included. **What it affects:** OpenClaw agents call Clawboo's tools without an agent identity, so connectors shared with a specific agent do not reach them. They can use apps reached through Composio, and every action in those apps asks you first. **What to do:** give connector work to an agent on another runtime. See [OpenClaw agents and connectors](/using/connectors#openclaw-agents-and-connectors). **Status:** open. ### OpenClaw ignores TOOLS.md **What you see:** an OpenClaw agent's **Agent Files** include a **TOOLS** tab, but what you write there changes nothing. **What it affects:** OpenClaw 2026.9 and later, the line Clawboo installs, does not load TOOLS.md. **What to do:** put tool guidance in the agent's AGENTS.md. See [Agents](/using/agents). **Status:** open. ## Teams and Boo Zero ### Archived teams cannot be restored from the dashboard **What you see:** an archived team disappears from the team rail and every other view, and no screen lists archived teams. **What it affects:** the team's agents, chats and tasks are kept, but the dashboard offers no way back to them. **What to do:** restore the team through the API, then reload the dashboard. Find its id, then clear the archive flag (use your port if it is not 18790): ```bash theme={"theme":"github-dark"} curl 'http://localhost:18790/api/teams?includeArchived=true' curl -X PATCH 'http://localhost:18790/api/teams/TEAM_ID' -H 'Content-Type: application/json' -d '{"isArchived": 0}' ``` With an access token set, send your access cookie too. See [Teams](/using/teams). **Status:** open. ### Boo Zero's Global Brief is not used **What you see:** what you write in **Details** > **Brief** on Boo Zero's page has no effect on what Boo Zero does. **What it affects:** Clawboo saves the Global Brief, but no chat or task sends it to Boo Zero, whatever the description above the box says. **What to do:** for guidance Boo Zero must follow in a team, add a [team rule](/using/teams). In Boo Zero's own chat, start a message with @ and a team's name to bring that team's brief into the message. See [What Boo Zero reads](/using/boo-zero). **Status:** open. ### Saving SOUL.md can overwrite custom personality instructions **What you see:** after you edit and save SOUL.md under **Agent Files**, your custom personality instructions are gone from the file the agent follows. **What it affects:** while custom instructions are on, the SOUL tab shows the slider personality instead of your text. Saving that tab writes the slider text over your custom instructions. **What to do:** while custom instructions are on, change the personality only in the **Personality** section. If it happens, click in your custom instructions and press `Cmd/Ctrl+S` to write them back. See [Agents](/using/agents). **Status:** open. ### Two team actions need an OpenClaw Gateway, even without OpenClaw agents **What you see:** **Refresh Protocol** on a team reports **Not connected to Gateway**, and **Remove Connection** on a route on the Org chart does nothing. **What it affects:** both actions run only while the dashboard is connected to an OpenClaw Gateway, even for teams with no OpenClaw members. Adding a route works without one. **What to do:** connect a Gateway, or remove the route line from the agent's AGENTS.md on its page. See [Org chart and team graphs](/using/org-chart) and [Teams](/using/teams). **Status:** open. ## Memory and observability ### Memory similarity search looks for Ollama on this computer only **What you see:** with Ollama on another machine and `OLLAMA_BASE_URL` pointing at it, your agents chat on that Ollama, but Memory does not use it for similarity search. **What it affects:** Memory always looks for Ollama at `http://localhost:11434` and ignores `OLLAMA_BASE_URL`. Without an Ollama there, Memory uses an OpenAI key if you connected one, which sends fact text to OpenAI, or keyword search only. **What to do:** run Ollama with the `nomic-embed-text` model on the computer that runs Clawboo. See [Turn on similarity search](/using/memory#turn-on-similarity-search). **Status:** open. ### OpenTelemetry export does nothing on an npm install **What you see:** with `OTEL_EXPORTER_OTLP_ENDPOINT` set, no traces reach your collector, and **System Health** shows the **OTel exporter** check as **Degraded**, even though its observability line reads **OTel exporter active**. **What it affects:** the npm package does not include the OpenTelemetry SDK, so the exporter cannot load. Traces stay in Clawboo's own event log, where the Observability panel shows them. **What to do:** if you need export, run Clawboo from a source checkout of the repository, which installs the SDK. See [Observability](/concepts/observability). **Status:** open. ## Setup and platform ### Runtime requirements that catch people out | Runtime | Limit | What to do | | - | - | - | | Claude Code | Needs an Anthropic API key: a `claude` CLI signed in with a Claude subscription is not enough. On an npm install it also needs the Claude Agent SDK next to Clawboo, which a one-off `npx` run cannot load. | Run `npm install -g clawboo @anthropic-ai/claude-agent-sdk`, then `clawboo restart`. See [Claude Code](/runtimes/claude-code). | | Codex | Connects only with **Sign in with ChatGPT**, never an API key. A Codex too old to have `codex app-server` runs without live steering: a comment waits for its next run. | Update Codex with `npm install -g @openai/codex@0`. See [Codex](/runtimes/codex). | | Hermes | Installs only on Python 3.11 to 3.13. The in-app install picks the newest Python 3.11 or later it finds, so it fails when that one is 3.14 or newer. | Install it yourself with a supported Python: `pipx install --python python3.13 'hermes-agent[anthropic]<1'`. See [Hermes](/runtimes/hermes). | | OpenClaw | On Windows, the dashboard cannot run OpenClaw's own **Sign in with ChatGPT**. | Run the command the setup panel shows in your own terminal. See [OpenClaw](/runtimes/openclaw). | ### The full Marketplace needs an internet connection **What you see:** the Marketplace shows only 5 teams and 15 agents. **What it affects:** Clawboo's server downloads the full catalog from the Clawboo repository on GitHub, and refreshes it at most every 6 hours. Offline, or until the download lands, you see only the built-in pack. **What to do:** check that the computer running Clawboo can reach GitHub, then wait for the next refresh. See [Marketplace](/using/marketplace). **Status:** open, by design. ## Self-hosting ### Binding to one network address cuts agents off from Clawboo's tools **What you see:** with `HOST` set to the computer's own network address, other machines reach the dashboard, but agents lose their Clawboo tools, such as the board and memory. **What it affects:** Clawboo's agents and background jobs connect to `127.0.0.1`, and a bind to one address does not listen there. **What to do:** use `HOST=0.0.0.0` with an access token, `CLAWBOO_ALLOWED_HOSTS` and `CLAWBOO_ALLOWED_ORIGINS`, or keep the default and reach Clawboo through an SSH tunnel or a reverse proxy. See [Reach Clawboo from another machine](/guides/self-host-securely). **Status:** open. ### The clawboo command cannot manage a server that uses an access token **What you see:** `clawboo`, `clawboo stop` or `clawboo restart` prints `A Clawboo server is running on port 18790, but it requires an access token.` and exits. **What it affects:** the launcher cannot check a token-protected server, so it will not start a second one, stop it or restart it. `clawboo stop` and `clawboo restart` print a command to stop the server by hand. Starting a new server with `STUDIO_ACCESS_TOKEN` set also confuses it: after about 45 seconds it prints `Dashboard is taking too long to start.` and exits, while the server keeps running. **What to do:** stop and start that server the way you started it, for example with `Ctrl+C` in its terminal or through your service manager. To open a server you just started, visit `http://localhost:18790/?access_token=` (use your port if it is not 18790). See [Run the Clawboo server](/operating/deployment) and [CLI reference](/reference/cli). **Status:** open. ### One install serves one person **What you see:** everyone who can open the dashboard sees every team, agent, task and memory fact. **What it affects:** Clawboo has no user accounts and keeps no data apart per person. Anyone with the access token gets the whole install. **What to do:** run one install per person, each under its own user account on the computer or in its own virtual machine. Separate data folders (`CLAWBOO_HOME`) under one account do not keep people apart: they share `~/.openclaw` and its saved keys, and coding agents can read any file that account can. See [Data and state](/operating/data-and-state). **Status:** open, by design. ### Live event streams have no limit **What you see:** nothing in normal use. **What it affects:** each open live event stream (`GET /api/obs/stream`) keeps polling the database until its client disconnects, and nothing limits how many can be open. This matters only when many clients can reach the server. **What to do:** behind a reverse proxy, limit concurrent connections there, and keep the access token on. See the [Observability API](/reference/rest-api/observability). **Status:** open. ## Issues in 0.5.0 that are fixed on main These problems affect the 0.5.0 release. They are fixed on main and ship with the next release, which the [changelog](/appendices/changelog) describes under **Unreleased**. | What you see on 0.5.0 | Area | | - | - | | Requests to `/api/mcp/` through a reverse proxy on the same machine skip the access token (see the [first entry on this page](#security-and-access)) | Security | | A `STUDIO_ACCESS_TOKEN` with an unsupported character turns the access check off (see [the entry above](#an-access-token-with-an-unsupported-character-turns-the-access-check-off)) | Security | | Clawboo writes `~/.openclaw/.env`, OpenClaw agents' `auth-profiles.json`, `settings.json` and `clawboo backup` files with your default permissions | Security | | Over HTTP, a TeamChat MCP session with no signed identity can read and post in any team's [peer chat](/concepts/peer-chat) as any agent, and `create_subtask` can file work under another team's task | Security | | Claude Code agents load your own `~/.claude` settings, plugins, MCP servers and memory (see [the entry above](#claude-code-agents-follow-your-own-claude-code-settings)) | Security | | A remembered **Always** on a connector tool skips the prompt even under access set to ask every time (`approvalPolicy: always`) | Security | | An `allow_always` answer to a risky hand-off, sent through the API, stops that leader's risky hand-offs from asking again, with no end date | Security | | `/api/mcp/config` signs any team id, and replacing the attach secret means editing the database by hand | Security | | Picking **Ollama** during setup records nothing: a new team runs on Anthropic, and its first message says Clawboo Native has no provider key | Ollama | | Setup accepts an Ollama with no model pulled, then every run fails with "model not found" | Ollama | | Agents run on Anthropic when an `ANTHROPIC_API_KEY` is in your shell or in `~/.openclaw/.env`, even after you picked another default provider | Providers | | **Set up OpenClaw** asks for an Anthropic, OpenAI or OpenRouter key even when Ollama is your provider | OpenClaw | | After you change OpenClaw's Gateway port, **Set up OpenClaw** still points Clawboo at port 18789 | OpenClaw | | Without an OpenClaw Gateway, **Create Boo** is missing, the graph's **New agent** fails with `gateway_disconnected`, and deleting agents does not work | Agents | | An OpenClaw agent deleted while the Gateway is down comes back on the next connect | OpenClaw | | Approving this device during OpenClaw setup can stick on "Approved. Reconnecting…" with only **Cancel** | OpenClaw | | The Gateway will not start after an OpenClaw upgrade until you run `openclaw doctor --fix` | OpenClaw | | A second update from the dashboard's update chip fails with "exit code 7" | Updates | | The API answers "Too many requests" after a run of page loads | Server | | With "reduce motion" turned on, the Radial layout of the Org chart (called Atlas in 0.5.0) piles each team's Boos on top of each other | Org chart | | With the sidebar hidden, a Boo elsewhere on the page can draw only its eyes and paws | Display | ## Related * [Troubleshooting](/help/troubleshooting): error messages and their fixes. * [Changelog](/appendices/changelog): what each release changed, and what is waiting for the next one. * [What agents can do](/concepts/permissions): what each runtime can reach on your computer. * [Budgets and spend](/using/budgets-and-spend): where spend shows and which costs are estimates. * [Security model](/operating/security): what protects an install, and the proxy rules for 0.5.0. # License & third-party notices Source: https://docs.claw.boo/appendices/license-and-notices Clawboo is MIT-licensed. What ships in the npm package and under which licenses, and where the marketplace content comes from. Clawboo is open-source software under the **MIT License**. This page summarizes that license, the notable third-party code that ships inside the published package, and the upstream content the [marketplace catalog](/reference/marketplace-catalog) is adapted from. The repository's two canonical files are the source of truth: * [`LICENSE`](https://github.com/clawboo/clawboo/blob/main/LICENSE): the MIT license text and copyright holder. * [`THIRD_PARTY_NOTICES.md`](https://github.com/clawboo/clawboo/blob/main/THIRD_PARTY_NOTICES.md): the full per-dependency license table and the verbatim upstream license texts. This page is a reading guide to those files, not a replacement for them. When in doubt, the two files above govern. ## At a glance | Item | Value | | - | - | | Project license | MIT | | Copyright holder | Sanreds (2026) | | Published npm package | `clawboo` (the CLI): license field `MIT` | | Repo workspace packages | All `@clawboo/*` are `private: true` and do not publish; they inline into the CLI's bundle | | Notable licenses | MIT, Apache-2.0 (`openai`, `drizzle-orm`, `@composio/client`), EPL-2.0 (`elkjs`), ISC (`lucide-react`), CC0-1.0 (`simple-icons`) | | Marketplace agent content | Mostly adapted from seventeen upstream repositories (sixteen MIT, one Apache-2.0), each pinned by commit SHA | ## Clawboo's own license: MIT The repository's [`LICENSE`](https://github.com/clawboo/clawboo/blob/main/LICENSE) is the standard MIT License, `Copyright (c) 2026 Sanreds`. It grants permission to use, copy, modify, merge, publish, distribute, sublicense, and sell copies, with the usual "AS IS", no-warranty disclaimer, on the condition that the copyright notice and permission notice are retained in copies or substantial portions. The only npm-published artifact is the **`clawboo` CLI package** ([`apps/cli/package.json`](https://github.com/clawboo/clawboo/blob/main/apps/cli/package.json)), whose `license` field is `MIT`. Everything you install with `npm install -g clawboo` (or `npx clawboo@latest` to try it without installing) ships under that license. See [CLI reference](/reference/cli) for what the package contains and [Run the Clawboo server](/operating/deployment) for how it starts. > [!NOTE] > > Every workspace library under `packages/` is named `@clawboo/*` and marked `private: true`. None of them publish to npm independently; they are bundled into the CLI's `dist/server.js` and `dist/ui/` at assembly time. So "the published software" is exactly the one `clawboo` package, under MIT. See the [package overview](/reference/packages/index). ## Bundled third-party dependencies The code listed below ships inside the published `clawboo` package. Most of it is bundled (inlined) at build time into the server bundle (`dist/server.js`) and the dashboard UI (`dist/ui/`), so it does **not** appear as separate entries in the installed package's `node_modules`. The package also declares nine runtime dependencies that npm installs normally, each with its own license file in `node_modules`: `@clack/prompts`, `@composio/client`, `better-sqlite3`, `chalk`, `commander`, `ora`, `pino`, `pino-pretty` and `ws`. For the inlined packages, the aggregated notices ship in [`THIRD_PARTY_NOTICES.md`](https://github.com/clawboo/clawboo/blob/main/THIRD_PARTY_NOTICES.md) (included in the package as `dist/THIRD_PARTY_NOTICES.md`), which is also the authoritative table; the summary here highlights the licenses you most need to know about. | Package | License | Role | | - | - | - | | `@anthropic-ai/sdk` | MIT | Provider SDK for [Clawboo Native](/runtimes/native) | | `openai` | Apache-2.0 | Provider SDK (also OpenRouter / Ollama via base URL) | | `@modelcontextprotocol/sdk` | MIT | The [MCP](/operating/mcp-servers) server/client transport | | `croner` | MIT | Cron parsing for [Routines](/concepts/scheduling) | | `better-sqlite3` | MIT | The SQLite driver for [Clawboo's database](/operating/data-and-state) | | `drizzle-orm` | Apache-2.0 | Typed schema/query layer over SQLite | | `zod` | MIT | Runtime validation across packages | | `react`, `react-dom` | MIT | Dashboard UI | | `@xyflow/react` | MIT | React Flow canvas for the [Org chart and team graphs](/using/org-chart) | | `elkjs` | EPL-2.0 | Graph layout backend for the Org chart and team graphs | | `framer-motion` | MIT | UI animation | | `zustand` | MIT | Client state | | `codemirror` | MIT | The agent-file editor | | `lucide-react` | ISC | Icons | | `simple-icons` | CC0-1.0 | Brand marks (provider/runtime logos) | | `tailwindcss` | MIT | Styling | | `pino`, `ws` | MIT | Logging and WebSocket transport | A few notes on the non-MIT licenses, restated from [`THIRD_PARTY_NOTICES.md`](https://github.com/clawboo/clawboo/blob/main/THIRD_PARTY_NOTICES.md): * **Apache-2.0** (`openai`, `drizzle-orm`, `@composio/client`): used under the terms of the Apache License, Version 2.0. * **EPL-2.0** (`elkjs`): the graph-layout backend is used under the Eclipse Public License, Version 2.0. It is bundled unmodified; its source is available on npm and at the upstream repository linked in the notices file. * **CC0-1.0** (`simple-icons`): the brand-mark *paths* are CC0-1.0, but the brand logos themselves remain the property of their respective owners. Clawboo renders provider and runtime marks from `simple-icons` where it carries one. Development-only dependencies (Playwright, MSW, Vitest, Turbo, tsup, ESLint, jest-axe, axe-core) are used to build and test Clawboo and are **not** shipped in the npm package. Their licenses are listed in the notices file. ## Marketplace content The marketplace catalog holds **436 agents** and **85 teams** in **nineteen packs** (see the [marketplace catalog reference](/reference/marketplace-catalog) for the full breakdown). The npm package includes only the built-in pack; the dashboard downloads the rest. Most of the agent content is **adapted from seventeen upstream repositories**, sixteen MIT-licensed and one Apache-2.0, each pinned by commit SHA so the provenance is reproducible. The pack files under the repository's `catalog/packs/` folder are written by hand and reviewed like any other file, and `pnpm catalog:build` turns them into the index the dashboard downloads. Each pack carries its own `NOTICE.md` alongside the summary here. | Upstream | License | Pinned commit | Adapted into | | - | - | - | - | | [`msitarzewski/agency-agents`](https://github.com/msitarzewski/agency-agents) | MIT | `64eee9f8e04f69b04e78e150d771a443c64720be` | 116 agency agents and 5 workflow teams | | [`wshobson/agents`](https://github.com/wshobson/agents) | MIT | `d82998e7df393c671ede2387a8435075f0b633f5` | 97 engineering agents and 24 teams | | [`VoltAgent/awesome-claude-code-subagents`](https://github.com/VoltAgent/awesome-claude-code-subagents) | MIT | `c9e51ec0b3d43f5dcdd0b558a6cd28ba6ada97c1` | 24 research and orchestration agents (teams are original) | | [`coreyhaines31/marketingskills`](https://github.com/coreyhaines31/marketingskills) | MIT | `b1aaa3619e747f4a836c61e03084c4a531de1262` | 26 growth-marketing agents, 5 teams, 9 pack skills | | [`garrytan/gstack`](https://github.com/garrytan/gstack) | MIT | `394db326f2d3aaccd4804fe846b82aaa7d189dee` | 14 founder-sprint agents and 4 teams (role structure only, every word written by Clawboo) | | [`mattpocock/skills`](https://github.com/mattpocock/skills) | MIT | `6654f6b60cd9d5be8b54c6fafe44346dabeb3b76` | 12 engineering-craft agents, 3 teams, 7 pack skills | | [`alirezarezvani/claude-skills`](https://github.com/alirezarezvani/claude-skills) | MIT | `19392f7a08264ed00486a251f5b2098321771f94` | 18 business and compliance agents and 4 teams | | [`phuryn/pm-skills`](https://github.com/phuryn/pm-skills) | MIT | `18468a95b427e70e258b51389796367c6f684e7d` | 12 product-craft agents and 3 teams | | [`google/skills`](https://github.com/google/skills) | Apache-2.0 | `0dad3f947e45a736060e524bbefa3eab692809f9` | 7 ads and analytics agents and 1 team | | [`TheCraigHewitt/skills`](https://github.com/TheCraigHewitt/skills) | MIT | `fdbf39b61fbc8cb7cea67949bfb5e8fc567bbc51` | 14 creator and founder operations agents and 4 teams | | [`calesthio/generative-media-skills`](https://github.com/calesthio/generative-media-skills) | MIT | `8c85352d5d75d4dcbe58480bd138e37b9742bab1` | 12 generative-media agents, 4 teams, 9 pack skills | | [`AgriciDaniel/claude-repurpose`](https://github.com/AgriciDaniel/claude-repurpose) | MIT | `669187e2b69ef3c854d149657b7fb2483263dab4` | 8 content-repurposing agents and 2 teams | | [`charlie947/social-media-skills`](https://github.com/charlie947/social-media-skills) | MIT | `d2e948719eafc8ed9e2436357ad18489bb371a81` | 12 creator-studio agents and 3 teams | | [`thatrebeccarae/claude-marketing`](https://github.com/thatrebeccarae/claude-marketing) | MIT | `a8a63ec1341f05ec9c1e9cb52b4edeb14e3bdcba` | 6 lifecycle-commerce agents, 2 teams, 7 pack skills | | [`kgelster/awesome-ecom-skills`](https://github.com/kgelster/awesome-ecom-skills) | MIT | `0b6f9e51b4a14b030ab52a2f1ff8a320bdc50070` | 5 storefront-catalog agents, 1 team, 6 pack skills | | [`black-forest-labs/skills`](https://github.com/black-forest-labs/skills) | MIT | `8907d515b0ac270a988ec7a239add81ee13d6cba` | 2 image and shot direction agents and 4 pack skills (no teams) | | [`heygen-com/skills`](https://github.com/heygen-com/skills) | MIT | `1bd5e4d33a028dfed3abf504c5e3dd644fb9ea8a` | 1 presenter-video agent and 3 pack skills (no teams) | | Clawboo built-in | first-party (MIT, this repo) | n/a (local, no upstream SHA) | 15 built-in agents (5 hand-authored 3-agent teams) | | Clawboo life and home | first-party (MIT, this repo) | n/a (local, no upstream SHA) | 35 agents and 10 teams | Every pack records its upstream repository and pinned commit in its manifest's `provenance` block. Entries from two packs (agency-agents and VoltAgent) also carry an `origin.url` linking back to the exact upstream file at that commit, for example: ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} https://github.com/msitarzewski/agency-agents/blob/64eee9f8e.../engineering/engineering-backend-architect.md ``` Entries are **adapted, not verbatim**: they were pruned, renamed, re-described, and re-formatted by the Clawboo maintainers, and each stores the whole edited instruction body (never a summary) in its `IDENTITY.md` document. The upstream license texts are reproduced in [`THIRD_PARTY_NOTICES.md`](https://github.com/clawboo/clawboo/blob/main/THIRD_PARTY_NOTICES.md), each alongside a note recording those modifications. > [!NOTE] > > The two first-party packs, the 15 built-in agents and the 35 life-and-home agents, are content authored in this repo. They carry no external attribution and no `origin.url`. The founder-sprint pack also ships under the `clawboo` publisher folder, but it is adapted rather than first-party: it carries the `garrytan/gstack` provenance and its own `NOTICE.md`. The 44 curated skills in the marketplace's **Skills** tab are first-party annotations, except that the process taught by 14 of them is adapted from [`obra/superpowers`](https://github.com/obra/superpowers) (MIT, pinned at `b36e0829c6d0140e93cfef2ca599b1b07d4a7797`). Seven packs also declare skills of their own, 45 in total; those declarations are covered by the same pack notices as the agents that use them. ## Runtime acknowledgements Clawboo coordinates other open-source AI agent [runtimes](/runtimes/index) as peer teammates. Each runs on its own terms under its own license. Clawboo drives each runtime through its own CLI, SDK or Gateway, and the runtimes reach Clawboo's board, memory and tools over [MCP](/operating/mcp-servers). The acknowledgements section of [`THIRD_PARTY_NOTICES.md`](https://github.com/clawboo/clawboo/blob/main/THIRD_PARTY_NOTICES.md) credits: * **OpenClaw**: runs its agents through its own Gateway. See [OpenClaw](/runtimes/openclaw). * **Hermes** (`hermes-agent`), **Claude Code** (Anthropic Claude Agent SDK), and **Codex** (OpenAI Codex CLI): the runtimes Clawboo starts for each run. It also credits prior art in the agent-orchestration space (Paperclip, vibe-kanban, Nous Research's `hermes-paperclip-adapter`, and Graphify) as design inspiration. ## Related * [`LICENSE`](https://github.com/clawboo/clawboo/blob/main/LICENSE) and [`THIRD_PARTY_NOTICES.md`](https://github.com/clawboo/clawboo/blob/main/THIRD_PARTY_NOTICES.md): the governing files in the repository. * [Marketplace catalog reference](/reference/marketplace-catalog): the pack format, the sources and counts, and the checks that keep them accurate. * [CLI reference](/reference/cli): the one published npm package. * [Choose a runtime](/runtimes/index): the runtimes Clawboo coordinates. * [Contributing](/appendices/contributing): how to contribute to the project. # What an agent sees Source: https://docs.claw.boo/concepts/agent-context What Clawboo puts in front of an agent each time it runs, in order: what stays the same, what changes, what is left out, and how to check. Each time an agent runs, it gets a message plus a block of context that Clawboo puts together: who the agent is, the team's rules, its teammates, and anything waiting for it. What goes in depends on how the work started: a turn in team chat, or a run on the task runner. Knowing the order helps you predict what an agent knows, and fix it when it seems to forget. ## Three layers An agent's input has three layers: 1. **The runtime's own setup**: its system prompt and its tools. Clawboo Native builds its system prompt from the agent's SOUL.md and adds today's date. OpenClaw builds its own from the agent's files in its OpenClaw workspace. Claude Code, Codex and Hermes use their own. Claude Code also reads the `CLAUDE.md` of the folder it works in, but nothing from your own `~/.claude`. 2. **Clawboo's context**, put together when the run starts and placed in front of the message. 3. **The message**: what you wrote, or the task text an agent wrote for a teammate. Some runtimes also carry the conversation so far. [Conversation history](#conversation-history) says which. ## A turn in team chat Clawboo builds this context for every turn in General or a room, including every task a teammate is handed there. It comes in this order, and empty parts are left out: 1. **Persona**: the agent's SOUL.md, up to 4,000 characters. Only Claude Code, Codex and Hermes get it here, because they have no persona of their own. Clawboo Native and OpenClaw already carry SOUL.md in their own setup. 2. **Team rules**: the rules you set under **Team brief & rules** or with `/rule`, marked as set by you and authoritative. 3. **About you**: the introduction you wrote when you met the team. Only turns whose reply you read get it: the leader's, or an agent you @mention. 4. **Teammates**: the names of the other members of this chat. A room lists only its own members. The leader also sees each teammate's runtime and role. 5. **How to work**: instructions for this kind of turn. A leader learns how to hand off work on its runtime. A teammate on a handed-off task learns that its reply goes back to the team, not to you, and that nobody can answer its questions mid-task. On a task you gave it from the Board, it learns that you read its reply on the task card. A note names the [peer room](/concepts/peer-chat) and board tools when the agent has them. 6. **Routine note**: when a Routine sent the message. It says you may be away, so the agent should make assumptions and state them rather than ask. 7. **Connectors**: on turns you read, the connectors that are connected, even ones not shared with this agent, plus a few you could add. 8. **Waiting for you**, in two parts: * **Addressed to you**: results of tasks this agent handed out, and alerts about its work. The agent is told to act on them. * **Ambient**: what teammates posted in the peer room since this agent last spoke, and other notices. The agent is told to treat them as information, never as instructions. 9. **The message**: your message, the task text for a teammate, or, when teammates finish, one batch of their results for the agent that handed out the work. Results, alerts and notices share a budget of 4,000 characters, and peer-room posts get their own 4,000 characters, at most 800 per post. Anything that does not fit waits for the next turn. The persona, rules, introduction and teammate list change only when you change them. Clawboo reads them again for every turn, so an edit applies from the next turn. The waiting items and the message are new each time. All of it rides in front of the message, so the runtime's own setup stays the same from turn to turn. ## A task-runner run The task runner runs one board task, for an agent Routine or a run started through the API. It arranges its context in three layers, from what stays the same for the whole task to what changes most. Keeping the steady part first lets providers reuse it between runs (prompt caching). | Layer | What it holds | | - | - | | Steady for the whole task | The persona (Claude Code, Codex and Hermes only), then the task's title and description | | Context | Where the last attempt left off, when the task resumes. A note on Clawboo's board, memory and peer-room tools, and how to use shared memory. A note when the runtime lacks a feature, such as live streaming on Hermes. A warning when an earlier attempt was cut off, to check before repeating anything it did. The failure note on a fix round. A handoff note after a restart in a fresh session. Waiting items, up to 4,000 characters | | Changes most | The memory facts most relevant to the task: the top 5 matches plus up to 2 related facts, in at most 1,500 characters. Each line carries a short id the agent can cite | The message is the task's title. The task runner adds no team rules, teammate list or introduction. ## What is left out * **Teammates' working steps.** A leader gets each teammate's final reply, not the transcript of how it got there. * **Your conversation, on a teammate's turn.** A teammate gets the task text the leader wrote for it, and not your introduction. A vague hand-off makes a vague task. * **The team brief.** Team-chat turns carry the team rules, not the brief. The brief reaches Boo Zero only when you mention the team (`@TeamName`) in Boo Zero's own chat. * **The global brief.** The brief under **Details** > **Brief** on Boo Zero's page is saved but not sent to any agent. * **Memory, on team-chat turns.** Nothing is added for them; agents look facts up with their memory tools. Only task-runner runs get facts added. * **The time of day.** Clawboo Native's setup carries today's date (in UTC), not the time. * **Tools a run cannot use.** A tool that is off is left out rather than refused. A Clawboo Native agent whose shell commands are off, for example, sees no command tool at all. ## Conversation history * **A leader in team chat.** A Clawboo Native or Codex leader continues the same conversation, separately in General and in each room. A Claude Code or Hermes leader starts each turn without the earlier conversation. OpenClaw keeps the conversation in its own Gateway session. * **A teammate on a handed-off task.** Clawboo Native, Claude Code, Codex and Hermes teammates start each task without earlier conversation. An OpenClaw teammate's Gateway session can still hold its earlier turns. * **A one-to-one chat with a Clawboo Native, Claude Code, Codex or Hermes agent** continues the conversation: each message resumes the session the one before it saved. On its first turn, and after `/reset`, the agent also gets up to 8 of its own saved notes, without the team's notes. Claude Code, Codex and Hermes also get their persona with every message. * **Boo Zero's one-to-one chat** adds Boo Zero's rules to every message. Mention a team with `@TeamName` and the message also carries that team's brief, board, saved memory and recent chat. * **Starting fresh.** `/reset` (or `/new`) in a chat ends what the agents carry from it, and the messages stay on screen. See [Team chat](/using/team-chat). ## When the context window fills * **On the task runner**, a run that runs out of room before it finishes starts again in a fresh session, with a short handoff note on its last progress. Running out of room means hitting its turn limit, or using more than 85% of the runtime's context window. This happens at most 3 times per task. Clawboo Native and Claude Code report a 200,000-token window; Codex and Hermes report none, so the 85% rule does not apply to them. * **A Clawboo Native run** makes at most 16 model calls by default, counting each round of tool use as one. * **In team chat** nothing restarts. A Clawboo Native leader's carried conversation is trimmed to its most recent turns, about 240,000 characters. Other runtimes manage their own windows. ## Check what an agent saw * **The task card.** Open it from the chat or the [Board](/using/board). It shows the exact task text the teammate received, its comments (including its report) and its activity. * **The Activity card** on an agent's page, or beside a team's chat. It lists every tool call and result as it happens, including memory searches and hand-offs. * **Memory** in the sidebar. Open a fact to see its history; a **cited** entry means a task-runner run was given that fact. * **Team brief & rules** in the chat header: the rules every team-chat turn carries. * **Details** > **Agent Files** on an agent's page: the SOUL.md that becomes its persona. * **Settings** > **Observability**: traces of task-runner runs. The dashboard does not show the assembled context itself. The lists on this page are the map for working it out. > [accordion] Under the hood > > * **Team-chat context.** `buildServerTeamContext` in `apps/web/server/lib/teamChat/contextPreamble.ts`. The waiting items are read when the run starts, not when it is queued (`serverDeliver.ts`), and framed by `buildTurnEnvelope` (`packages/team-orchestration/src/turnEnvelope.ts`). Budgets: `INBOX_BUDGET_CHARS` (`packages/db/src/inbox.ts`) and `CATCHUP_BUDGET_CHARS` (`apps/web/server/lib/teamChat/peerCatchup.ts`). > * **Task-runner context.** `assembleTiers` (`packages/executor/src/tiers/index.ts`), called from `apps/web/server/lib/executorRunner.ts`. The memory block is an `auto-memory` section built in `apps/web/server/lib/memoryInjection.ts`, with its limits in `apps/web/server/lib/defaults.ts`. A run started with `POST /api/runtimes/:id/run` can skip it with `disableMemoryAutoInject: true`. > * **Delivery.** Every runtime gets Clawboo's context in front of the message, separated by a blank line, in the run's first user message. > * **Persona.** `apps/web/server/lib/runtimes/personaBlock.ts`. > * **Restarts and limits.** `DEFAULT_ROTATION` in `packages/executor/src/session-rotation.ts`; `DEFAULT_MAX_TURNS` in `packages/adapters/native/src/agentConfig.ts`; the 240,000-character bound in `apps/web/server/lib/runtimes/native/conversation.ts`. > * **Conversation pointers.** `apps/web/server/lib/teamChat/nativeTeamSession.ts` keeps them for Clawboo Native and Codex leaders. A one-to-one chat keeps one pointer per agent, whatever its runtime (`nativeChatSessionSettingKey` in `apps/web/server/lib/agentChat/driveAgentChat.ts`). The one-to-one notes come from `apps/web/server/lib/agentChat/memoryRecall.ts`. ## Related * [How a team works](/intro/how-it-works): the two paths these contexts belong to. * [Team chat](/using/team-chat): team rules, `/rule`, `/reset` and rooms. * [Memory](/concepts/memory): how shared memory is saved and searched. * [Agents](/using/agents): which agent files each runtime reads. * [Limits and defaults](/reference/limits-and-defaults): every number on this page. # Agents and Boo Zero Source: https://docs.claw.boo/concepts/agent-model What an agent is, how Boo Zero is chosen to lead every team, how it differs from a team Leader, and why an agent keeps its runtime. An **agent** is one AI worker in Clawboo. It has a name, a personality, a team and a runtime, the engine that runs its turns. The dashboard draws each agent as a **Boo**, the ghost avatar you see in the sidebar, the Org chart and team chat. One agent, **Boo Zero**, leads every team. Use this page to understand what an agent is made of, who leads a team, and what you can change after you create an agent. ## The mental model Think of an agent as a staff card. The card says who the agent is and which team it works on. The runtime is the engine behind the card: it reads the conversation, calls tools and writes the reply. The board holds the work the agent has been given. ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} flowchart LR A["Agent
name, personality, team"] -->|runs on| R["Runtime
does the thinking"] A -->|works on| B["Board
its tasks"] ``` Clawboo keeps every agent's card in its own database. That is why the sidebar and the Org chart still list your agents while a runtime is offline: you can see an agent you cannot reach at the moment. ## Every agent runs on one runtime A runtime is the software that runs an agent's turns. Clawboo supports five, and they work in three ways: | How it runs | Runtimes | What that means for you | | - | - | - | | Built in | Clawboo Native | Runs inside the Clawboo server, on the provider key or local Ollama you connect. | | Connected | OpenClaw | Runs in your OpenClaw Gateway, a separate program Clawboo connects to. Clawboo keeps its own copy of each agent in sync. | | Started per run | Claude Code, Codex, Hermes | Coding tools that Clawboo starts for a turn or a task, and stops when it ends. | The runtime is set when the agent is created and never changes. No dashboard control or API call moves an existing agent to another runtime. To try the same role on a different runtime, create a new agent there. Which runtime a new agent gets: * **Create Boo**, and adding an agent on the Org chart, put the agent on OpenClaw while the Clawboo server is connected to a Gateway, and on Clawboo Native otherwise. * Deploying a team lets you pick the runtime for each member in **Customize team**, so one team can mix runtimes. After that you can edit its personality and its files. See [Manage agents](/using/agents) for the controls and [Choose a runtime](/runtimes/index) to compare the runtimes. A Clawboo Native agent that has no key for its provider shows **No provider key** at the top of its chat. Connect that provider in [Providers and runtimes](/using/providers-and-runtimes) and it can run again. ## Boo Zero leads every team **Boo Zero** is the one agent that leads all of your teams, and every room inside each team. When you send a message in a team chat, Boo Zero answers it or hands the work to a teammate. Start a message with `@` and a teammate's name to send it to that teammate instead. [How work is delegated](/concepts/delegation-and-orchestration) explains the hand-off. Usually Boo Zero belongs to no team, which is how it can preside over all of them. Clawboo picks Boo Zero in this order, and the first match wins: 1. **An agent designated as Boo Zero**, on any runtime. The dashboard has no control for this. If you set up Clawboo with **Sign in with ChatGPT**, the lead of the team you deploy during setup (its Leader, or else its first agent) is designated automatically, unless you already have a Boo Zero from this rung or the next. You can also set one through the API (see the [Boo Zero API](/reference/rest-api/boo-zero)). 2. **The Boo Zero that Clawboo creates for you** on Clawboo Native. Clawboo creates it as soon as a Clawboo Native agent is on a team and a provider is connected for Clawboo Native. 3. **Your OpenClaw Gateway's default agent**, when you use OpenClaw and neither of the above exists. If none of these exists, each chat is led by the team's Leader (below) when that agent is in the chat, and otherwise by the chat's first member. A team with no active members has no lead at all. To rename Boo Zero, open Boo Zero's own page and use its **Brief** tab. The briefs you can write for Boo Zero reach it less often than their names suggest: the global brief is saved but not read, and a team's brief is read only when you mention that team in Boo Zero's own chat. [Configure Boo Zero](/using/boo-zero) lists exactly what Boo Zero reads. ## A team's Leader Some teams also have a **Leader**: one member marked with a **Leader** badge. When you deploy a team, Clawboo gives the badge to the member whose name or role reads as a leadership title, such as CEO, CTO or Team Lead. Many teams have no Leader, and that is fine: Boo Zero coordinates them. On Clawboo Native, the Leader is set up as a lead. It gets the lead instructions and, unless you pick a model, the provider's default lead model. The other members get the provider's default specialist model, often a faster, cheaper one. | | Boo Zero | A team's Leader | | - | - | - | | How many | One for your whole Clawboo | At most one per team, often none | | Team membership | Usually none | A member of its own team | | Answers team chat | Every team and every room | Only when there is no Boo Zero, in chats it belongs to | | How it is chosen | Designated, created by Clawboo, or your Gateway's default (in that order) | Detected from names and roles at deploy; in a ChatGPT sign-in setup, the first agent when none is detected | ## What you control * **Who an agent is**: its personality and files, on the agent's page. See [Manage agents](/using/agents). * **Its team**: set when you create it. **Create Boo** adds the new agent to the team you have selected. An agent belongs to one team at a time, or to none. * **Its runtime**: chosen once, when you create the agent or deploy its team. * **Boo Zero's name**: on Boo Zero's page. [Configure Boo Zero](/using/boo-zero) covers the name, the briefs, and which of them Boo Zero reads. ## Limits * An agent's runtime cannot be changed. Create a new agent to use another runtime. * There is no dashboard control to choose a different Boo Zero. * A team chat needs at least one active member. Boo Zero does not lead an empty team. > [accordion] Under the hood > > * **The agent record.** Every agent is an `AgentRecord` row in Clawboo's database with `id`, `runtime`, `teamId`, `participantKind`, `personalityConfig` and `execConfig`, among others. Three kinds of source fill it. The OpenClaw source syncs from the Gateway, and a re-sync rewrites only Gateway-owned fields, keeping Clawboo's own. The Clawboo Native source and one source per coding runtime keep the only copy in Clawboo's database. See [AgentSource](/internals/agent-source). > * **Runtime classes.** Each adapter declares a `runtimeClass`: `native` (Clawboo Native), `connected-substrate` (OpenClaw) or `wrapped-oneshot` (Claude Code, Codex, Hermes). Clawboo routes runs on this class, never on a runtime id. See [RuntimeAdapter](/internals/runtime-adapter). > * **Boo Zero resolution.** The server resolves Boo Zero in the order above and returns its id as `defaultId` from `GET /api/agents`. `GET /api/boo-zero/override` reports `{ overrideAgentId, effective, tier }`, and `POST /api/boo-zero/override` with `{ "agentId": "" }` designates one (`null` clears it). `isDefault` on a record marks the Gateway's own default agent, which is Boo Zero only when nothing outranks it. > * **Sessions.** Boo Zero talks to each team in a team-scoped session, `agent::team:` for General, with `:room:` added for each room, so its conversations never mix. > * **Reserved fields.** `participantKind` is `agent` or `human`. Nothing runs a human participant: a Routine aimed at one is refused. `tenantId` is always `null`. > * **`providerReady`.** Set on Clawboo Native agents only. It is `false` when no key resolves for the agent's provider or its fallbacks, which is what the **No provider key** label reads. > * **Command approval on OpenClaw.** The Gateway, not Clawboo, enforces an OpenClaw agent's command-approval setting, so Clawboo writes the setting there when it creates the agent and when you change it. If the Gateway refuses it at creation, the agent is removed and not created. See [OpenClaw](/runtimes/openclaw). ## Related * [Teams, rooms and runtimes](/concepts/teams-and-planes): what a team shares and what each runtime keeps to itself. * [How work is delegated](/concepts/delegation-and-orchestration): how Boo Zero hands work to teammates. * [Choose a runtime](/runtimes/index): what each of the five runtimes is good at. * [Configure Boo Zero](/using/boo-zero): Boo Zero's name, global brief and team briefs. * [Manage agents](/using/agents): create, edit and remove agents. # Capabilities Source: https://docs.claw.boo/concepts/capabilities What the capability inventory shows, the six places its skills, tools and connectors come from, and which of them Clawboo can change. A **capability** is a skill, a tool or a connector that an agent can use. Each runtime keeps track of its own in a different way, so Clawboo gathers them into one **capability inventory**. You see it in the **Capabilities** panel (**Settings**, then **Workspace**, then **Capabilities**) and as the tiles around each Boo on the Org chart. For every entry, the inventory also records who owns it, which decides whether Clawboo can change it or can only show it. Use this page to understand what the inventory shows, where entries come from, and what you can change. ## The mental model Think of the inventory as one stock list for several stockrooms. Clawboo owns some of the stockrooms, so it can switch items on and off there. The others belong to a runtime: Clawboo can read the labels, and in one case ask the runtime to make a change, but it never rearranges a runtime's own shelves. ## What the inventory shows * **Three kinds of entry**: skills, tools and connectors. * **Where each one belongs**: the runtime that owns it, and whether it belongs to one agent or to every agent on that runtime. * **A status**: **Ready**, **Disabled**, **Pending auth** or **Unavailable**. An unavailable entry is greyed out, with the reason. * **The action Clawboo can take**, if any: **Enable** or **Disable**. An entry Clawboo can only show reads "built-in, managed by" and the runtime's name. The panel groups entries by runtime, with a filter for each one, a **Refresh** button, and any pending tool approvals at the top. Sometimes a runtime cannot be read, for example while your OpenClaw Gateway is offline. The panel then says so and shows the last entries it knew for that runtime. ## Where entries come from The inventory reads from six sources. What Clawboo may do with an entry depends on its source. | Source | What it reads | Can Clawboo change it? | | - | - | - | | Clawboo | The tools Clawboo's tool broker serves (such as `web_search`), each Clawboo Native agent's switches for the Memory, Tasks and Tools servers, and skills installed from the Marketplace on any agent | Yes. Clawboo owns these, so **Enable** and **Disable** work. Skills are installed from the [Marketplace](/using/marketplace). | | Hermes | The `SKILL.md` skills and the `mcp.json` connectors in each Hermes agent's home, and Hermes's built-ins | No, shown only. Hermes owns its skills, and Clawboo rewrites `mcp.json` on every run. | | Claude Code | Clawboo's servers attached to Claude Code, and Claude Code's built-ins | No, shown only. | | Codex | Clawboo's four servers attached to Codex, and Codex's built-ins | Not at present. Each server row always reads **Pending auth**, even after you sign in, with a disabled **Enable** and a hint to run `codex login`. Clawboo refuses any change to these entries. | | OpenClaw | The Gateway's `tools.allow` and `tools.deny` lists, its MCP servers and plugins, and OpenClaw's built-ins | Partly. Allow and deny entries can be switched, and Clawboo asks the Gateway to make the change. Other servers and plugins are shown only, and so are Clawboo's own servers registered there. | | Connectors | The connectors Clawboo is running for you, from the Connectors page | Not from this panel. Connect and disconnect on the [Connectors](/using/connectors) page. | ## Connectors and grants Connecting a connector makes it available, but gives it to no agent. An agent reaches a connector only through a **grant**: your permission for that agent to use it, with an access level and a rule for when to ask you first. You manage grants on the Org chart; [Connectors](/using/connectors) has the steps. On the Org chart, each Boo's tiles show its own entries plus the entries its runtime shares with every agent, such as built-ins. A connector tile appears on a Boo only when that agent holds a grant for it. A connector whose list of tools has changed since you approved it shows **Changed since you approved it**. Read what changed before you trust it again, rather than signing in again. ## What you control * **Switch entries on or off**: in the Capabilities panel, for entries Clawboo owns and for OpenClaw's allow and deny entries. See [Capabilities](/using/capabilities). * **Add skills**: install them from the [Marketplace](/using/marketplace). * **Add connectors and decide who uses them**: connect on the Connectors page, then give agents access on the Org chart. See [Connectors](/using/connectors). * **Approve tool calls**: answer the approvals waiting at the top of the panel, or in [Approvals](/using/approvals). ## Limits * Clawboo never changes Hermes skills, or your own Claude Code settings in `~/.claude`. * OpenClaw's MCP servers and plugins cannot be switched from Clawboo; change them in OpenClaw. * Codex entries cannot be changed from Clawboo. * Clawboo cannot tell OpenClaw agents apart, so the connectors you give an OpenClaw agent on the Org chart do not reach it. Its use of apps through Composio asks for your approval each time. > [accordion] Under the hood > > * **The record.** Each entry is a `CapabilityRecord`. Its `id` has the form `:::::`, and it carries `kind`, `scope` (`agent` or `global` in practice), `manageability`, `status`, `available`, `writable`, `hint`, `health` (`unknown`, `ok`, `needs-auth`, `degraded`, `error` or `drift`) and, for granted connectors, `grantId`. > * **Manageability.** `managed`: Clawboo owns it. `external-write`: the runtime owns the store and Clawboo would write through it (Codex, blocked today). `runtime-of-record`: the runtime owns it and Clawboo asks the runtime to change it (OpenClaw allow and deny lists). `observe-only`: read only. > * **Writes.** The REST handler refuses an enable or disable on an `observe-only` or non-writable entry with `422`, and an unknown entry with `404`. The panel hides more buttons than that: it offers none for unavailable or pending-auth entries either. An install takes the owning runtime from the agent's own record, never from the request. > * **Degraded sources.** Each source's last good read is kept in the `capabilities` table. When a source fails to read, its cached rows are served instead, and fresh rows win when both exist. > > See the [Capabilities API](/reference/rest-api/capabilities) and the [packages overview](/reference/packages/index). ## Related * [Capabilities](/using/capabilities): the Capabilities panel, step by step. * [Connectors](/using/connectors): connect apps and servers, and give agents access. * [Org chart](/using/org-chart): every team's Boos and their capability tiles. * [Teams, rooms and runtimes](/concepts/teams-and-planes): what each runtime keeps to itself. * [Memory](/concepts/memory): the other shared resource every teammate uses. # How work is delegated Source: https://docs.claw.boo/concepts/delegation-and-orchestration How a lead hands work to a teammate: what the teammate receives, what comes back, how each runtime signals a hand-off, and the limits. When Boo Zero, or any agent, decides that a teammate should do part of the work, it **delegates**. Clawboo turns the hand-off into a task on the board, sends it to the teammate, and brings the result back to whoever handed it off. You see the task appear on the board, and a short reply from the lead when the work is done. Use this page to understand what a teammate is given, what comes back, and the limits that keep a team from running away. ## How a hand-off flows ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} sequenceDiagram participant You participant Lead as Boo Zero participant Board participant Mate as Teammate You->>Lead: Message in team chat Lead->>Board: Hand-off becomes a task Board->>Mate: Task delivered Mate->>Board: Final reply added, task Done Board->>Lead: Task update Lead->>You: One short summary ``` **1. You send a message** You write in General or a room. Boo Zero answers it, unless you start the message with `@` and a teammate's name. A simple question gets a direct answer; hands-on work gets handed off. **2. The lead hands off** The lead calls its delegate tool, or writes a delegate line, naming a teammate and the task. Clawboo only acts on these structured hand-offs. An `@`-mention in the middle of a reply hands off nothing. **3. Clawboo creates a task** The hand-off becomes a card on the board, claimed for that teammate and moved to In progress. If the teammate is busy, the card waits in To do until it is free. You can watch it in the team chat's **Tasks** card and on the board. **4. The teammate works on its own runtime** The teammate runs the task on whichever runtime it uses, with its own tools. **5. The result comes back** The teammate's final reply is added to the card, the card moves to Done, and the result goes to whoever handed it off. The lead then writes you one short summary. ## What the teammate receives * **The task the lead wrote.** The teammate does not see your conversation with the lead, so a good hand-off is self-contained. Clawboo's delegate tools ask the lead for exactly that. * **The team rules and the list of teammates.** * **A note about its role.** It is told that it works on one task for its lead and cannot reach you: its reply goes back to the lead. * **What is waiting for it.** Results of work it handed out, your comments on its tasks, alerts, and new posts in the agents' peer room (see [Peer chat](/concepts/peer-chat)). * **Its usual tools**, including shared memory and the board. Your introduction to the team is not included: it rides only the turns whose reply you read. [What an agent sees](/concepts/agent-context) lists everything that goes into a turn. ## What comes back * **A summary, not a transcript.** The teammate's final reply is recorded as a comment on its card, with very long output trimmed. Its intermediate steps stay out of the lead's view. * **A task update for whoever handed it off.** Results that finish within a few seconds of each other arrive together in one update. A task the lead handed out reports to the lead. A sub-task reports to the teammate that handed it off, and that report climbs the chain to the lead. Only the lead's summary reaches the chat. * **A completion line in the chat**, so you can see the task finished even before the lead replies. * **An honest empty result.** If the teammate returned nothing, the lead is told so, rather than seeing a false success. A task you give an agent yourself, with **New task** on the board, reports to nobody: its result stays on the card for you to read. If a result arrives when no run is listening, it waits in the agent's mailbox. The agent reads it when its next run starts or, while it is running, alongside the answer to its next call to one of Clawboo's task or peer-room tools. ## How each runtime signals a hand-off Each runtime hands work off in its own way. Clawboo reads only these structured signals, never intent guessed from prose. | Agent handing off on | How it hands off | | - | - | | Clawboo Native | Calls its `delegate` tool with a teammate's name and the task. | | Claude Code, Codex, Hermes | Calls the `team_delegate` tool, which Clawboo provides on team chat runs. | | OpenClaw | Writes `the task` in its reply. A `sessions_send` call to a teammate also counts. | Any agent's finished reply can also carry `` lines, or a `` made of `` blocks: an ordered plan in which each step waits for the one before it. Clawboo forgives small slips such as smart quotes or a missing `<`. Sometimes a reply looks like a hand-off but cannot be read, for example because it names no real teammate. Clawboo then tells the lead once to send it again in the right form. ## Busy teammates A teammate works one hand-off at a time. If it is busy, the new task waits in To do and starts as soon as the teammate is free. A message for a busy agent waits for its current turn to end; it never interrupts it. ## Risky hand-offs wait for you Clawboo holds a hand-off whose task text contains any of these words, in any letter case: delete, destroy, deploy, publish, release, secret, credential, prod or production, the phrases drop table and rm -rf, and api\_key, api-key, apikey, force-push, force\_push or forcepush. It asks you to approve it, and the request appears in Needs you and in [Approvals](/using/approvals). If you decline, or nobody answers within 5 minutes, the hand-off is skipped and the lead is told. The check is a plain whole-word match on the task text, so ordinary work that mentions a release can trip it, while "deleted", "secrets", or "API key" and "force push" written with a space do not. Steps of a `` are not checked this way. ## Limits | Limit | Value | When it is reached | | - | - | - | | How deep hand-offs go | Three tasks below the lead | The lead hands a task to A, A hands part of it to B, and B to C. C cannot hand off further and is told to do the work or report back. | | Hand-offs in one turn | 8 | Extra hand-offs in the same turn are not started, and the agent is told to send them again later. | | A teammate goes quiet | 8 minutes with no activity; 24 minutes while it is inside one tool call, or waiting for another run on the same agent | The task is marked blocked as **Timed out**, and the lead is told. | | Repeated failures | 3 failures in a row of the same task by the same teammate | Clawboo stops handing that task to that teammate and tells the lead to do it, reassign it, or tell you it cannot be done. | [Limits and defaults](/reference/limits-and-defaults) lists these with every other number Clawboo enforces. ## Where a failure shows up When a handed-off task fails, you can see it in three places: 1. **On the board.** The task moves to Needs you as **Failed** or **Timed out**, with the reason as a comment on its card. 2. **In the lead's next turn.** The task update says the work did not complete and why, so the lead can retry it, give it to someone else, or tell you. 3. **In the plan.** Steps that were waiting on the failed task are cancelled, and the update names them. If a hand-off cannot start at all, for example because of the depth limit or a declined approval, the lead is told at once. When the hand-off came from a task, a note is added to that task's card. ## Stop, and your own moves Pressing **Stop** in a chat halts that chat's running work. Tasks it was running go back to To do without counting as failures, and they do not restart on their own: Needs you shows them as **Stopped**. Work that had been handed out but had not started yet never starts. Moving a card yourself outranks the run. Clawboo stops the run working on that task. When you mark the task Done or cancel it, whoever handed it off is told, and a cancelled task's waiting steps are cancelled with it. A late result from the stopped run is kept as a comment and changes nothing else. See [The board](/concepts/the-board). ## Two ways work runs Everything on this page happens in team chat, which is how most work runs. Clawboo has a second path, the task runner, used by agent Routines and the runtime run API. Per-task worktrees, automatic checks, automatic memory recall and spending breakers apply only there. [How a team works](/intro/how-it-works) compares the two. > [accordion] Under the hood > > * **The engine.** Each chat, General and every room, has one long-lived orchestration engine on the server. It reads typed run events, never prose. A hand-off is a tool call whose name matches `sessions_send` or has a `delegate` segment (so `delegate`, `team_delegate` and namespaced forms count), or a `` or `` block in a finished turn. A `` wins over `` blocks in the same turn. Self-delegations, unknown names and empty tasks are dropped. > * **Spawning.** For each hand-off the engine checks the teammate's session, the repeated-failure breaker, the depth cap and the risky-word approval. Then it creates the task, claims it, opens a run record and delivers the message. A busy teammate gets a deferred `todo` task that the dispatcher fires when the teammate is free. > * **No sub-agents.** Every delegated run carries a blocklist of `sessions_send`, `sessions_spawn` and `sessions_yield`. OpenClaw cannot block tools per message, so Clawboo denies `sessions_spawn` and `sessions_yield` in the Gateway's `tools.deny` and repeats the team contract in each OpenClaw turn. > * **Mailbox.** Undelivered notices live in the agent's durable mailbox with the kinds `task_update`, `alert` and `signal`. > > See [Executor runner](/internals/executor-runner) for the other path and [team-orchestration](/reference/packages/team-orchestration) for the engine package. ## Related * [The board](/concepts/the-board): statuses, Needs you and when a task restarts by itself. * [Teams, rooms and runtimes](/concepts/teams-and-planes): which chat runs a hand-off. * [Peer chat](/concepts/peer-chat): the agents' own room, separate from hand-offs. * [Governance](/concepts/governance): budgets, caps and approvals across Clawboo. * [Chat with your team](/using/team-chat): sending messages, `@` mentions and Stop. # Budgets and guardrails Source: https://docs.claw.boo/concepts/governance The limits Clawboo puts on spend, stuck runs and delegation: which are on by default, which you turn on, and which kinds of run each one covers. Clawboo puts limits on what your agents spend and on how far their work spreads. A few limits are always on. The one that stops spending, a budget in **hard cap** mode, is off until you create one. These guardrails narrow the damage a run that goes wrong can do. They cannot rule out a runaway agent, and most of them cover only some of the ways work runs. Use this page to learn what each guardrail does and where it applies. To create, raise or resume a budget, see [Budgets and spend](/using/budgets-and-spend). ## Where work runs decides which guardrails apply Work reaches your agents along two main paths. [How a team works](/intro/how-it-works) explains both. * **Team chat.** Messages in General and rooms, the hand-offs they lead to, and the tasks you give an agent on the Board. Most work runs here. * **The task runner.** Agent routines on Clawboo Native, Claude Code, Codex and Hermes, and runs started through the API with `POST /api/runtimes/:id/run`. An agent routine on OpenClaw takes a third, smaller path: a dispatcher that sends the work over the OpenClaw Gateway and waits for the result. The guardrails differ by path: | Guardrail | Default | Team chat | Task runner | OpenClaw agent routine | | - | - | - | - | - | | Spend counted against budgets | On, once a budget exists | Yes | Yes | Yes | | Budget warnings in the audit log | On, once a budget exists | No | Yes | Yes | | Hard cap refuses the next run | Off until you set one | Yes | Yes | Yes | | Hard cap stops a run partway | Off until you set one | Clawboo Native on a priced model | Clawboo Native | No | | Per-run cost cap | Off | No | Routines set up through the API | No | | Circuit breakers | On | No | Yes | No | | Delegation depth and fan-out caps | On | Yes | Depth only | No | | Approval for risky delegations | On | Yes | No | No | One-to-one chat follows its own rules. One-to-one chat with a Clawboo Native, Claude Code, Codex or Hermes agent charges only that agent's budget, so a paused hard cap on that agent refuses a turn before it starts, and the team's budget does not. As in team chat, only a Clawboo Native run on a priced model can stop partway, and every other turn is charged when it ends. One-to-one chat with an OpenClaw agent is not charged to any budget. ## Budgets A **budget** is a dollar limit on one scope: * **agent**: everything one agent spends. * **mission**: one delegation tree, counted on its top task (the first task in the chain of hand-offs). * **team**: everything a team's runs spend. There is also a **tenant** scope, which the Governance form no longer offers. Nothing charges a tenant budget, so one made earlier or through the API never warns and never pauses. Each budget has a mode: * **warn only** (the default). Clawboo counts spend and never pauses anything. From 80% of the limit the budget reads **soft\_capped**, and it stays there past 100%. * **hard cap** (you opt in). Spend is counted the same way. At 100% the budget reads **paused**, and what that stops depends on the path (see the table above and [What a hard cap stops](#what-a-hard-cap-stops)). Spend counts against a scope only after a budget exists for it. With no budget, nothing is measured against a limit, though costs still show where Clawboo reports them. ### How spend is measured Not every runtime reports what a run cost. Claude Code reports a dollar figure. Clawboo Native works one out from token counts for six models it has list prices for: claude-haiku-4-5, claude-sonnet-4-6, gpt-4o, gpt-4o-mini, and the OpenRouter ids anthropic/claude-haiku-4.5 and openai/gpt-4o-mini. These are the **priced models**. The default leader models (claude-sonnet-5 on Anthropic, gpt-5.4 on OpenAI) are not on the list. Everything else is an estimate: from token counts where the runtime reports them, or from the length of the text when it reports none, on every path. Estimates count toward budgets the same as reported figures. A model your own Ollama serves is the exception: it costs nothing, so it is recorded at \$0, never estimated. The cost table in [Budgets and spend](/using/budgets-and-spend) has the details for each runtime. ### What a hard cap stops **In the task runner**, Clawboo checks the agent, mission and team budgets before a run starts and refuses the run if a hard cap among them is paused. During the run it counts each cost report, and once a hard cap pauses, it stops the run at its next sign of more work: more text, a tool call or another model turn, or 5 seconds of silence. Only Clawboo Native reports cost while it works, once per model turn. Claude Code and Codex report cost only as the run ends. If that report takes a hard cap to its limit, the work is already done, so Clawboo keeps it: the result is posted, the task moves on as usual with the comment "Budget cap reached (``) by this run's final cost report", and the next run is refused. A run that ends without any cost report is charged an estimate from its text. **In team chat**, Clawboo checks the agent, team and, for a run that works on a task, mission budgets before each run starts, and refuses the run if a hard cap among them is paused. A message you send then gets a notice in the chat instead of a reply. A handed-off task, or a task you gave an agent on the Board, is not started and waits in **Needs you** with the same notice in a comment. Once a run is going, a Clawboo Native run on a priced model stops after its next model turn when a hard cap pauses. Every other run (Claude Code, Codex, Hermes, OpenClaw, and Clawboo Native on other models) is charged when it finishes, so it can take a budget past its limit, and the next run is refused. To end a run that is going, press **Stop** in the chat. **For an OpenClaw agent routine**, a paused hard cap refuses the fire before it starts. OpenClaw reports no cost while it works, so Clawboo estimates the run from its text when it ends, whether it succeeded, failed, timed out or was stopped. If that estimate crosses the cap, the task gets the comment "Budget cap reached" and the routine's next fire is refused. When a hard cap stops a task-runner run, the run is cancelled and the task goes back to To do with the comment "Auto-paused: `` budget reached. Raise the cap (or resume) to continue." If the run was a routine fire, the routine then stops until you resume it, and the task the fire created waits in **Needs you** (see [How routines run](/concepts/scheduling)). When a hard cap stops a team-chat run, its task fails. ### Resuming and raising a budget **Resume** puts a paused budget back to active. If spend is still at or over the limit, the next cost report pauses it again, and the row shows a **will re-pause** pill. To make room, raise the limit with **Set cap** on the budget's row. Saving a budget again keeps its mode unless you pick another. **Set cap** on a row keeps it, the create form switches to the mode of the budget you pick, and `POST /api/governance/budgets` keeps it when the request sends no `mode`. There is one more way a hard cap appears: creating a Clawboo Native agent through the API with a `budgetUsd` value also creates a hard-cap budget for that agent. ### The per-run cost cap A routine created through the API can carry a per-run cost cap (`maxNodeCents` in its task template). The task runner stops that routine's run once the run itself has cost that much, the same way a paused hard cap would. Nothing else sets it, and it is off by default. The Governance panel shows a **per-node cost** chip with the value **per-run**: that chip names the feature, it is not a limit in force. ## Circuit breakers Circuit breakers stop a task-runner run that is going nowhere, before a budget would. They are always on in the task runner. They do not run in team chat or for OpenClaw routines. | Breaker | Trips when | Default | | - | - | - | | Iteration cap | A run makes more tool calls than the limit | More than 30 | | Repeat failure | The same tool call, with the same input, keeps failing | 3 times in a row | | No progress | Tool results in a row add no new successful output | 6 in a row | | Token velocity | The run uses tokens faster than the limit, measured over 15 seconds or more | More than 200,000 a minute | | Repeat denial | The same policy refusal comes back | 2 times in a row | A successful call that repeats an earlier one counts as neither progress nor failure, so an agent that re-reads a file is not penalized. Token velocity needs at least two cost reports in one run, so in practice it can trip only on Clawboo Native. When a breaker trips, the run is cancelled, the task gets a comment that starts `[stopped: ]`, and the task goes back to To do. For a routine fire, the routine then stops and its task waits in **Needs you**, as with a hard cap. A worktree, if the run had one, is kept. Each trip is written to the audit log as a `circuit_break` event. A run started through the API can tune the limits with a `breakerConfig` object. ## Caps on delegation and task creation These caps are always on. Clawboo enforces them in code, whatever the model asks for. * **Depth.** A chain of hand-offs can be three tasks deep. The leader hands off the first task, and the agents on the next two levels can each hand off once more. The agent on a third-level task cannot delegate: it is told to do the work itself or report back. The Governance panel shows this as **max spawn depth** 2, meaning a task can have at most two tasks above it. The same limit applies to multi-step plans and to subtasks agents file through Clawboo's Tasks tools, and the task runner refuses a task that sits deeper. * **Fan-out.** One team-chat turn can start at most 8 hand-offs at once (the steps of a multi-step plan are not counted). The rest are not started, the leader is told how many were dropped, and the audit log records a `cap_hit` event. * **Repeated failures.** After the same task fails 3 times in a row with the same teammate, the leader cannot hand it to that teammate again. It is told to do the work itself, give it to someone else, or tell you it cannot be done. The count resets when the Clawboo server restarts. * **Children.** When an agent files subtasks itself through Clawboo's Tasks tools, one parent task can have at most 24 child tasks. Deleting a child frees a slot. Finished and cancelled children still count. * **New top-level tasks.** The Tasks tools refuse to create a top-level task once 30 were created in the last 5 minutes, counting tasks from every source. Tasks you create on the Board are never refused by these caps, though they count toward the 5-minute total. ## Approval for risky delegations When an agent in team chat hands off a task whose text contains a risky word, Clawboo asks you before the hand-off happens. The approval appears with your other pending approvals, for example in the Governance panel's **Approval queue** (see [Approvals](/using/approvals)). The check is a plain match on whole words, in any letter case: * delete, destroy, deploy, publish, release, secret, credential, prod, production * the phrases drop table and rm -rf * api\_key, api-key or apikey, and force-push, force\_push or forcepush Because it matches whole words only, "deleted", "deployment" or "secrets" do not trigger it, and neither do "API key" or "force push" written with a space. The steps of a multi-step plan are not checked at all. Treat the check as a speed bump, not a review of what the task does. The request waits up to 5 minutes. If you allow it, the hand-off goes ahead. If you choose **Don't allow**, or nobody answers in time, the hand-off is skipped and the agent that handed it off is told it was not approved. If Clawboo cannot record the request, the hand-off is skipped too. The approval card in the dashboard offers only a one-time allow and **Don't allow**. Nothing is remembered: an approval resolved as `allow_always` through the API (`POST /api/tools/approvals/:id/resolve`) allows that one hand-off, and the leader's next risky hand-off asks again. ## Running commands and using apps Separate approvals decide whether an agent may run a shell command or act in a connected app without asking you: * An OpenClaw agent's **Command Execution** setting (new OpenClaw agents start at **Ask for Unknown**). See [Command permissions](/using/command-permissions). * A Clawboo Native agent's **Let this Boo ask to run commands** switch, off by default. When it is on, the agent asks before every command, and a run can ask at most 10 times. Commands are offered on runs that have a working folder, which includes team chat and one-to-one chat, and never on Windows. * Approvals for actions in connected apps. See [Approvals](/using/approvals). Claude Code, Codex and Hermes run with their own approval prompts turned off, so Clawboo does not ask before their commands. [What agents can do](/concepts/permissions) explains what that means for your files. ## What these guardrails do not cover * **What a run can touch.** Budgets, breakers and caps limit spend and effort. A run that stays under its budget can still read and change anything its runtime can reach. See [What agents can do](/concepts/permissions). * **Stuck runs in team chat.** Team chat has no circuit breakers. Besides the delegation caps, its guard against a stuck run is time. A handed-off task whose run reports nothing for 8 minutes is failed (24 minutes while the agent is inside one tool call), and any run silent for 30 minutes is ended. The task runner and one-to-one chat end a run after the same 30 minutes of silence. * **Warnings from team-chat spend.** Spend from team chat moves a budget's bar and status in the Governance panel, but no warning event is written to the audit log. * **Expensive but healthy runs.** The breakers are tuned so a healthy run never trips them, so a costly run that keeps making progress passes them. A hard-cap budget is the only limit set in dollars, and it holds only as far as [What a hard cap stops](#what-a-hard-cap-stops) describes. > [accordion] Under the hood > > * Budget statuses are `active`, `soft_capped` (from 80%) and `paused` (a hard cap at 100%). A warn-only budget never reads `paused`. The modes are stored as `warn` and `cap`, and spend is kept in fractions of a cent so tiny costs add up. > * Each recorded amount is charged to every scope that has a budget: the agent, the team and, when the run works a task, the mission. Each scope is updated in its own write. > * The audit log's event types are `budget` (warnings and auto-pauses), `circuit_break`, `cap_hit`, `approval`, `verification`, `tool_call` and `install`. > * A risky-delegation approval is a row in the tool-call approvals table with the scope key `delegate:code` and `neverRemember` set. Every risky hand-off opens a new row; an earlier `allow_always` row skips nothing. > * Budgets over REST: `GET` and `POST /api/governance/budgets`, `POST /api/governance/budgets/:scope/:scopeId/resume` (an optional `graceUsdCents` raises the limit to current spend plus the grace and keeps the mode) and `GET /api/governance/audit`. See the [Governance API](/reference/rest-api/governance). > * The run loop that applies budgets and breakers is described in [Executor runner](/internals/executor-runner). ## Related * [Budgets and spend](/using/budgets-and-spend): create, raise and resume budgets, see where spend shows, and read the cost table. * [What agents can do](/concepts/permissions): what a run can touch, which these limits do not bound. * [How routines run](/concepts/scheduling): what a refused or stopped run does to a routine. * [Approvals](/using/approvals): where pending approvals appear and how to answer them. * [Governance API](/reference/rest-api/governance): budgets, the audit log and delegation approval over REST. * [Limits and defaults](/reference/limits-and-defaults): every number on this page in one place. # Key concepts Source: https://docs.claw.boo/concepts/index The eleven words you meet across Clawboo, each in one sentence with an everyday comparison, why it exists and where to find it in the app. Clawboo uses a small set of words for its parts. Learn these eleven and the rest of the docs will make sense. Each entry gives a one-sentence definition, an everyday comparison, why the thing exists, and where you find it in the app. ## The whole system in one minute You give a team work in its chat. Boo Zero, the team's leader, answers or splits the work into tasks on the Board. Teammates do the tasks and report back, and Boo Zero sends you the combined answer. Each teammate runs on a runtime and thinks with a provider's models. Along the way agents can save facts to memory and use the connectors you share with them. Routines start work on a schedule, and anything that needs your decision waits under **Needs you**. ```mermaid theme={"theme":"github-dark"} flowchart LR you["You"] -->|"message in General or a room"| lead["Boo Zero"] lead -->|"hands off"| board["Tasks on the Board"] board --> mates["Teammates"] mates -->|"results"| lead lead -->|"combined answer"| you routine["Routine"] -.->|"on a schedule"| lead mates -.->|"asks first"| needs["Needs you"] needs -.-> you ``` [How a team works](/intro/how-it-works) follows one message through this loop in detail. ## The concepts ### Agent (Boo) An agent is an AI worker with a name, a role and a personality. Each one appears in the app as a Boo, a small mascot. * **Like:** a teammate with a job description. * **Why it exists:** several agents with narrow roles can work at once, and each stays focused on its part. * **Where you see it:** the Boos at the top of the sidebar for the selected team. Click one to open its page; **Create Boo** adds a new one. * **More:** [Agents](/using/agents) ### Boo Zero Boo Zero is your team leader: it reads what you ask, answers it or splits it into tasks for the team, and brings the results back to you. * **Like:** a project manager who takes the brief and hands out the work. * **Why it exists:** you ask once, in one place, instead of instructing each agent. * **Where you see it:** the red Boo at the top of the far-left rail opens Boo Zero's own page. In a team's chat, Boo Zero answers unless you @mention someone. Clawboo sets Boo Zero up for you; without one, the team's own **Leader** or its first member leads. * **More:** [Boo Zero](/using/boo-zero) ### Team A team is a group of agents that work together, with its own chat, tasks and rules. * **Like:** a department, or a small agency crew. * **Why it exists:** each kind of work keeps its own people, history and rules in one place. * **Where you see it:** the team icons on the far-left rail. The **+** below them opens ready-made teams in the Marketplace. * **More:** [Teams](/using/teams) ### Room A room is an extra chat inside a team, with some of its members, its own conversation and its own tasks. * **Like:** a breakout meeting. * **Why it exists:** a few agents can work on one topic without crowding the team's main chat. * **Where you see it:** every team has **General (Main Room)**, and **New room** in the sidebar adds another. Boo Zero leads each room the way it leads General. * **More:** [Work with your team in chat](/using/team-chat) ### Board and task A task is one piece of work with an owner and a status. The Board shows every task, and each hand-off between agents creates one. * **Like:** a shared wall of sticky notes that move across columns. * **Why it exists:** work stays visible and survives a closed tab or a restart, and you can change it at any time. * **Where you see it:** **Board** in the sidebar, and the **Tasks** card beside a team's chat. **New task** on the Board gives an agent work directly. * **More:** [Board](/using/board) ### Runtime A runtime is the program that runs an agent: Clawboo Native (built in), Claude Code, Codex, Hermes or OpenClaw. * **Like:** if the AI model is the brain, the runtime is the body. It decides which tools the agent has and how it uses them. * **Why it exists:** runtimes are good at different jobs, such as coding, and one team can mix them. * **Where you see it:** **Settings** > **Runtimes**. You pick each agent's runtime when you set up a team. * **More:** [Choose a runtime](/runtimes/index) ### Provider A provider is the service whose AI models do the thinking, such as Anthropic, OpenAI, OpenRouter or a local Ollama. * **Like:** a utility. You connect your account once, and it bills you for what your agents use. A local model through Ollama has no bill, and Clawboo's spend numbers record it as free. * **Why it exists:** Clawboo is free and brings no models of its own, so you choose who answers, and who bills you. * **Where you see it:** **Settings** > **Providers**. Setup connects your first one. * **More:** [Providers and runtimes](/using/providers-and-runtimes) A runtime and a provider are different things. A Claude Code agent (a runtime) uses Anthropic's models (a provider), and a Clawboo Native agent can use any of 11 providers. ### Memory Memory is the team's shared notebook: facts agents save so that teammates can find them later. * **Like:** a team wiki. * **Why it exists:** what one agent learns stays available after the chat moves on. * **Where you see it:** **Memory** in the sidebar shows every saved fact and where it came from. **Add a fact** writes one yourself. * **More:** [Memory](/using/memory) ### Connector A connector links Clawboo to an outside app or tool, such as GitHub, a web browser or Gmail, so agents can use it. * **Like:** a key card for one door, handed only to the agents that need it. * **Why it exists:** agents can act where your work lives, and you decide which agent reaches which app. * **Where you see it:** **Connectors** in the sidebar connects one. Then share it with an agent on the Org chart or a team graph: drag the connector onto the Boo and pick **Read**, **Write** or **Admin**. * **More:** [Connectors](/using/connectors) ### Routine A routine sends work on a schedule, either into a team's chat or straight to one agent. * **Like:** a recurring calendar entry that does the job instead of reminding you. * **Why it exists:** regular work, such as a Monday report, happens without you starting it. * **Where you see it:** **Settings** > **Routines**. Upcoming runs also show on the Board, and on an agent's **Tasks & Routines** card. * **More:** [Routines](/using/routines) ### Approval and Needs you **Needs you** is where everything waiting on your decision collects: approval requests, failed or stopped tasks, and tasks with no agent. An approval is a request that needs your yes first, such as running a command, calling a connector or handing off risky work. * **Like:** the in-tray on your desk. * **Why it exists:** agents work unattended, so the few things that need a person wait in one place. * **Where you see it:** the **Needs you** column on the Board, the **Tasks** card beside a team's chat, and approval cards in the chat itself. * **More:** [Approvals](/using/approvals) and [What agents can do](/concepts/permissions) ## The building blocks Each concept has a page that goes deeper. Start with the four big-picture pages, then follow whichever building block you need. | Page | What it explains | | - | - | | [How a team works](/intro/how-it-works) | What happens to a message, and the two ways Clawboo runs agents | | [What an agent sees](/concepts/agent-context) | The context every turn and run carries, and what it leaves out | | [What agents can do](/concepts/permissions) | What each runtime can reach, and when Clawboo asks you first | | [How work is checked](/concepts/verification) | When finished work is tested, and what is never checked | | [Agents and Boo Zero](/concepts/agent-model) | What an agent is, and how Boo Zero is chosen | | [Teams, rooms and runtimes](/concepts/teams-and-planes) | What a team shares, and what each runtime keeps to itself | | [The board](/concepts/the-board) | Task statuses, Needs you, and how work survives a restart | | [How work is delegated](/concepts/delegation-and-orchestration) | How a leader hands off work and gets the results back | | [How agents talk to each other](/concepts/peer-chat) | The room agents use among themselves | | [Memory](/concepts/memory) | Shared and private memory, and how facts are found | | [Capabilities](/concepts/capabilities) | The skills, tools and connectors each agent can use | | [Budgets and guardrails](/concepts/governance) | Spend limits, caps and circuit breakers | | [Observability](/concepts/observability) | The event log, traces and fleet health | | [Worktrees and handoff](/concepts/worktrees-and-handoff) | Separate checkouts for coding tasks, and passing a task between runtimes | | [How routines run](/concepts/scheduling) | The engine behind Routines | ## Next steps * [Quickstart](/getting-started/quickstart): get a team running. * [How a team works](/intro/how-it-works): follow one message from start to finish. * [Glossary](/appendices/glossary): every other term, and the old names you may still see. # Memory Source: https://docs.claw.boo/concepts/memory How shared team memory works, how it differs from each runtime's own memory, how it learns from use, and when facts are recalled automatically. Clawboo gives each team a **shared memory**: facts and how-tos that any teammate can save and recall, whatever runtime it uses. Separately, some runtimes keep **their own memory**, such as Hermes's `MEMORY.md`. Clawboo leaves that alone and never shares it. Shared memory also learns from use: agents report which facts helped, and the facts the team trusts rise to the top. Use this page to understand what shared memory holds, who can see a fact, when agents recall facts without asking, and how search and embeddings work. ## The mental model Shared memory is the team's notebook. When an agent learns something the team should keep, such as the staging deploy command or the tone you prefer, it writes it down. Any teammate can look it up later. A runtime's own memory is usually that agent's private diary; Claude Code is the exception, described under "Each runtime's own memory" below. Clawboo never copies between the two: an agent chooses what to write in the shared notebook. ```mermaid theme={"theme":"github-dark"} flowchart LR A["Agent on any runtime"] -->|memory_save| M[("Shared memory")] M -->|memory_search| B["Teammate on any runtime"] A -.-> D["Its runtime's own memory
(not shared)"] ``` ## What shared memory holds * **Facts.** A short statement with a title, content and optional tags, for example "Staging deploys run `pnpm deploy:staging`". * **Procedures.** A named how-to. Saving one under the same name again adds a new version. Before anything is stored, Clawboo masks values that look like secrets, such as API keys, in a fact's title and content and in a procedure's content. An agent's save whose content is nothing but a secret is refused. Masking catches credential-shaped values only, so it is not a guarantee that no sensitive text is ever saved. Agents work with four Memory tools: `memory_save`, `memory_search`, `memory_browse` and `memory_feedback`. You browse, search and add facts in the **Memory** panel (**Settings**, then **Workspace**, then **Memory**); see [Memory](/using/memory). ## Who can see a fact | Saved by | Filed under | Readable by | | - | - | - | | An agent on Clawboo Native, Claude Code, Codex or Hermes, during a run for a team | That team | Every member of the team, and you | | The same agents, during a run with no team | Global | Everyone | | An OpenClaw agent | Global | Everyone | | You, in the Memory panel | Global | Everyone | The panel labels each fact **Team-shared**, **Agent-scoped** or **Global**. When an agent searches, it sees its team's facts, facts filed for that agent, and global facts, never another team's. OpenClaw agents see global facts only: one Gateway configuration serves all of them, so Clawboo cannot tell which team an OpenClaw agent works for. The scope comes from the agent's connection, not from what the model asks for, so an agent cannot widen what it sees or file a fact under another team. ## Memory learns from use When an agent uses a fact, it can report how it went with `memory_feedback`. The outcomes are **useful**, **dead end** (it misled or wasted effort) and **corrected** (it was wrong, and the note holds the correction). In the Memory panel's graph you can do the same: click a fact, then **Helpful** or **Outdated**. Clawboo turns these reports into a status on each fact: | Status | When | | - | - | | preferred | Two or more different reporters found it useful, and none reported against it. | | tentative | One reporter found it useful, and none reported against it. | | contested | Reports disagree. The panel shows it as "contested · verify". | | dead end | Only negative reports. | A "different reporter" is a different agent, or the same agent on a different task, so your own clicks alone cannot make a fact preferred. Older reports count for less: a report's weight halves every 30 days. Search results show each fact's status and id, so an agent can report on the fact it used. ## The memory graph The Memory panel draws your facts as a graph. Facts that share tags are linked, versions of a procedure are linked, and, when embeddings are on, facts with a similar meaning are linked too. Clusters of related facts are grouped and coloured together. Click a fact to read it, see its history, and mark it **Helpful** or **Outdated**. ## When facts are recalled without asking Whether an agent starts with facts already in hand depends on how the run started: | How the run started | Facts added at the start | | - | - | | Team chat: your messages, every hand-off and every task you start from the board | None. The agent searches memory with `memory_search` when it needs to. | | A one-to-one chat with a Clawboo Native, Claude Code, Codex or Hermes agent | Up to 8 of the agent's own and global notes, on the first turn of a conversation and again after `/reset`. Team facts are left out. | | The task runner: an agent Routine on Clawboo Native, Claude Code, Codex or Hermes, or the runtime run API | An `` block with up to 5 facts that best match the task's title and description, within 1,500 characters, plus up to 2 closely related facts. The run is also told how shared memory differs from its own, and asked to report on the facts it used. | Most work runs through team chat, so most runs start with no facts and rely on the agent to search. A direct run through the runtime run API can turn the block off with `disableMemoryAutoInject: true`. [How a team works](/intro/how-it-works) explains the two paths. The block ranks preferred facts higher, flags contested ones, and leaves dead-end facts out; they can still be found with a search. Before a fact enters another agent's prompt, Clawboo drops it if it contains instructions aimed at the agent, such as "ignore your previous instructions", and masks secrets again. ## How search works Keyword search always works, with no setup. When embeddings are on, a search is **hybrid**: it weighs meaning (60%) and keyword match (40%), so a search for "deploy steps" can find a fact that says "release process". A fact embedded by a different model matches on keywords only. ## Embeddings: local first Embeddings are what let memory match on meaning. Clawboo picks a provider in this order: | Order | Provider | When Clawboo uses it | What leaves your machine | | - | - | - | - | | 1 | Ollama at `http://localhost:11434`, model `nomic-embed-text` | Whenever it answers with that model installed | Nothing | | 2 | OpenAI, model `text-embedding-3-small` | When Ollama cannot serve and you have connected an OpenAI key. Once any fact has been indexed by Ollama, only after you choose **Use OpenAI instead** during an Ollama outage, and only until Ollama is back or the key is removed. | Fact text and memory searches, to OpenAI | | 3 | None | Otherwise | Nothing; keyword search only | * To turn on local embeddings, run `ollama pull nomic-embed-text`, or press **Install model** in the Memory panel. * Memory always looks for Ollama at `http://localhost:11434`. `OLLAMA_BASE_URL` does not change it. * The OpenAI key counts when you connected it in Clawboo or exported `OPENAI_API_KEY`. A key that only OpenClaw holds is never used for memory. * Set `CLAWBOO_DISABLE_EMBEDDINGS=1` to stay on keyword search. * No restart is needed. Clawboo re-checks every 10 minutes while Ollama serves and every 30 seconds otherwise, and indexes facts saved without an embedding in the background. ## Each runtime's own memory | Runtime | Its own memory | Shared with teammates? | | - | - | - | | Clawboo Native | Its saved conversations, in its own folder under `~/.clawboo/runtimes/clawboo-native/` | No | | Hermes | `MEMORY.md`, `state.db` and the skills it creates, in its own Hermes home under `~/.clawboo/runtimes/hermes/` | No. Clawboo never edits them. | | Codex | None that Clawboo keeps. Its folder under `~/.clawboo/runtimes/codex/` holds its login and session files. | Not applicable | | OpenClaw | Kept in your OpenClaw Gateway's own state | No | | Claude Code | None of yours: Claude Code's auto memory is off. It reads the `CLAUDE.md` of the folder it works in | Only through that folder | > [!NOTE] > > Claude Code agents do not load your own Claude Code memory: not your `~/.claude/CLAUDE.md` or rules, not a `CLAUDE.md` in any folder above the one they work in, and not Claude Code's auto memory. They do read the `CLAUDE.md` of the folder they work in, such as a repository's, so a `CLAUDE.md` in a team's folder reaches every Claude Code agent of that team. See [Claude Code](/runtimes/claude-code#your-own-claude-code-setup-stays-out). Clawboo never copies a runtime's own memory into shared memory, or the other way round. In task-runner runs, each agent is told that its own memory is private to it and that knowledge the team needs belongs in shared memory. See [Teams, rooms and runtimes](/concepts/teams-and-planes) for where each runtime keeps its state. ## What you control * **See and add facts**: in the Memory panel, which also shows the graph and each fact's status. See [Memory](/using/memory). * **Correct the team's memory**: mark a fact **Helpful** or **Outdated** in the graph. * **Turn on matching by meaning**: install `nomic-embed-text` in Ollama, or press **Install model**. * **Decide about OpenAI**: choose **Use OpenAI instead** during an Ollama outage, or set `CLAWBOO_DISABLE_EMBEDDINGS=1` so memory never calls an embedding provider. ## Limits * Team chat runs, which are most runs, get no facts added automatically. Agents have to search. * OpenClaw agents read and save global facts only. * Masking secrets is a pattern match on credential-shaped values, not a guarantee. * With OpenAI as the embedding provider, fact text and searches are sent to OpenAI. * The Memory panel saves facts as global; it does not file them under a team. > [accordion] Under the hood > > * **Storage.** Facts and procedures live in `memory_facts` and `memory_procedures`, with an FTS5 table for keyword search and a stored vector per fact. Feedback lives in `memory_outcomes`. Tables: [Database schema](/reference/database-schema). > * **Tools.** `memory_save` saves a fact, or a versioned procedure when `procedureName` is set. `memory_search` takes `mode` (`fts`, `vector` or `hybrid`; hybrid when embeddings are available, otherwise `fts`) and `limit` (1 to 100). `memory_browse` takes `limit` (1 to 200). `memory_feedback` takes a fact id, or an id prefix of at least 8 characters, an `outcome`, and a note, which `corrected` requires. See [MCP tools](/reference/mcp-tools). > * **Scope binding.** Clawboo binds each run's Memory connection to its team and agent: in process for Clawboo Native, and in a signed URL for the other runtimes. A bound save is filed under the team only. A run with no team reads global facts only, and so does an HTTP connection without a valid signature, which also saves to global. The `clawboo-mcp-memory` stdio program, which you run yourself, takes its scope from the tool arguments and defaults to `~/.openclaw/clawboo/clawboo.db`, not the server's database, unless you set `CLAWBOO_DB_PATH`; see [MCP servers](/operating/mcp-servers). > * **Automatic recall.** The task runner searches in hybrid mode with the task's title and description, scoped to the task's team and agent. Each line in the block starts with an 8-character `(id …)` so the agent can cite it, and each fact shown is recorded as cited once per task. The block goes into the part of the prompt that is rebuilt every run. > * **Learning.** Status rules: mixed signals give `contested`, negatives only give `dead_end`, two or more distinct `(agent, task)` reporters of `useful` give `preferred`, one gives `tentative`. Weights decay with a 30-day half-life. > * **REST.** `GET` and `POST /api/memory`, `GET /api/memory/browse`, `/graph`, `/outcomes` and `/provider`, `POST /api/memory/feedback`, `/embedding/reindex` and `/embedding/install`. See the [Memory API](/reference/rest-api/memory). ## Related * [Memory](/using/memory): the Memory panel, step by step. * [Teams, rooms and runtimes](/concepts/teams-and-planes): what a team shares and where each runtime keeps its state. * [What an agent sees](/concepts/agent-context): everything that goes into an agent's turn. * [Claude Code](/runtimes/claude-code): running Claude Code agents, and the setup they share with you. * [MCP tools](/reference/mcp-tools): the Memory tools' inputs and outputs. # Observability Source: https://docs.claw.boo/concepts/observability What Clawboo records about the work your agents do, where to see it in the app, what traces cover, and what OpenTelemetry export needs. Clawboo records what your agents do in an event log kept in its own database: tasks moving on the board, runs starting and ending, tool calls and their results, errors, costs and routine runs. The **Activity** views, the **Observability** panel, the **Fleet** panel's recent issues and the live status on a team's graph are all built from this log. The log stays on your machine. Clawboo sends no telemetry, and sending traces to a tool of your own takes extra setup (see [OpenTelemetry export](#opentelemetry-export)). Use this page to learn what is recorded, where to look, and where the gaps are. A tour of the panels themselves is in [Observability](/using/observability). ## What gets recorded Every event has a kind. There are 25: | Group | Kinds | | - | - | | Board | `task_created`, `task_claimed`, `status_changed`, `comment_added`, `dep_linked` | | Runs | `execution_started`, `execution_completed`, `session_rotated` | | Tools | `tool_call`, `tool_result` | | Cost | `cost` | | Approvals | `approval_requested`, `approval_resolved` | | Errors | `error` | | Traces | `span_start`, `span_end` | | Routines | `routine_fired`, `routine_dispatched`, `routine_completed`, `routine_error` | | Peer chat | `team_chat_post`, `speaker_selected`, `turn_bound_hit` | | Connectors | `grant_decision`, `connector_health` | How much each run records depends on how it started (see [How a team works](/intro/how-it-works) for the two paths): * **Team chat** records board changes, runs, tool calls and errors. It writes no `cost` events and no trace spans. * **The task runner** (agent routines on Clawboo Native, Claude Code, Codex and Hermes, and runs started through the API) records all of that, plus a `cost` event for each cost report and a trace for each run. * **OpenClaw activity that Clawboo did not start**, such as an OpenClaw agent's own cron job or a message from another channel, is still recorded as tool calls and results while Clawboo is connected to the Gateway. A connector's grant decision is its own event because a denied call never produces a tool call, so a denial would otherwise leave no trace. Event data is scrubbed of secrets before it is stored, and masked again when the app shows it. ## Where to look | Where | What it shows | | - | - | | **Activity** card in a team chat, an agent page or a task | The live stream of events for that team, agent or task | | **Activity** dock on the Org chart | The same stream for every team | | **Team graph** card | A live status dot on each Boo while it is working, stalled or a zombie (see below) | | Settings > Insights > **Observability** | Fleet health, error classes and harness bugs, traces, the delegation graph, and **Run smoke evals** | | Settings > Insights > **Fleet** | Agents per runtime, task and verification pass rates and task spend over 24 hours, and recent issues | | Settings > Insights > **Governance**, **Audit log** | Budget warnings and pauses, approvals, breaker trips and cap hits, which go to a separate audit log | | Settings > System > **System Health** | Among other checks, whether the OpenTelemetry exporter started | **Fleet health** sorts each agent by its open run: **working** (an open run with a recent event), **idle** (no open run), **stalled** (no event for 5 minutes) or **zombie** (no event for 30 minutes, so the process is almost certainly gone). ## What traces cover A trace groups one run's events into a timeline: the run itself, with each tool call inside it. Runs of tasks in the same delegation tree share one trace, keyed on the tree's top task. Only runs the task runner starts have traces. Team-chat work and OpenClaw routines are recorded as events without a trace, so they never appear in the **Traces** list or in OpenTelemetry export. Most everyday work happens in team chat, so an empty **Traces** list is normal if that is how you use Clawboo. ## Errors and harness bugs Clawboo sorts every error a run reports into a class, by keywords in its code and message. The first class that fits wins, in the order of this table: | Class | Typical cause | | - | - | | `RateLimited` | The provider is throttling you: 429, rate limit, quota, resource exhausted | | `UserAborted` | The run was stopped or cancelled | | `Timeout` | Something took too long: timeout, timed out, ETIMEDOUT, deadline exceeded | | `UnexpectedEnv` | A missing file or program, or a permission problem: ENOENT, EACCES, EPERM, command not found | | `ContextOverflow` | The prompt was too long for the model | | `InvalidArgs` | The request was malformed: 400, 422, validation, missing required field | | `ProviderError` | The provider failed: 500, 502, 503, 504, upstream, overloaded, service unavailable | | `Unknown` | Nothing matched | | `PolicyDenied` | A guardrail refused a tool call. Task-runner runs only, checked before every other class | An `Unknown` error is flagged as a **harness bug**: most likely a defect in Clawboo rather than in your prompt or your provider. If you see one, please [open an issue](https://github.com/clawboo/clawboo/issues) with the error text. A `PolicyDenied` error is never a harness bug; it means a guardrail did its job. ## How much the views read, and how long the log keeps * Fleet health and the delegation graph read the newest 5,000 events, so older agents and tasks drop out of them. A single trace shows at most 5,000 events. The error list shows the newest 500 errors, and its harness-bug count covers only those. * Nothing prunes the event log or the tool-call audit, so both grow as you use Clawboo. Stored tool results are the exception: they are deleted after 7 days (`CLAWBOO_TOOL_RESULT_TTL_MS`). To back up or reset your data, see [Data and state](/operating/data-and-state). ## OpenTelemetry export Clawboo can also send its traces to an OpenTelemetry collector, such as Jaeger or any OTLP endpoint. Export is off unless you set `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) before you start Clawboo. `OTEL_SERVICE_NAME` sets the service name, `clawboo` by default. Only task-runner runs have traces, so only those are exported. > [!NOTE] > > **The npm package does not include the OpenTelemetry SDK.** On an install from npm (`npx clawboo@latest` or a global install), setting the endpoint exports nothing. The exporter cannot load, traces stay in the local log, and **System Health** shows the **OTel exporter** check as degraded. Its **observability** row still reads "OTel exporter active", because that row only reports that an endpoint is set. Export works where the two packages it needs, `@opentelemetry/sdk-node` and `@opentelemetry/exporter-trace-otlp-http`, can be loaded, as when you run Clawboo from a source checkout of the repository. > [accordion] Under the hood > > * The log is the `orchestration_events` table in Clawboo's SQLite database. Each row has an auto-increment `seq` (the order of events, and the cursor for the live stream), a UUID `id`, a timestamp, the kind, team, task, agent and runtime ids, trace and span ids, and a scrubbed JSON `data` field. A row with an unknown kind is stored as `error`. > * The live stream is `GET /api/obs/stream`, a Server-Sent Events feed polled every 750 ms that resumes from `Last-Event-ID` or `?since=`. With no cursor it replays the scoped log from the start, then follows it. > * `POST /api/obs/ingest` accepts five kinds from outside the server: `tool_call`, `tool_result`, `error`, `grant_decision` and `connector_health`, at most 200 per batch. Board, cost and approval events are never accepted from outside. > * The MCP stdio servers write to this log only when they open the server's database. The attach configs Clawboo generates set `CLAWBOO_DB_PATH` so they do. > * Errors are classified on both paths. A refused tool call is recognized as `PolicyDenied` only in the task runner; in team chat the same refusal is classified by its message. ## Related * [Observability](/using/observability): a tour of the Observability and Fleet panels. * [Budgets and guardrails](/concepts/governance): spend limits, circuit breakers and caps, and the audit log they write to. * [Events and errors](/reference/events-and-errors): every event kind and error class in detail. * [Observability API](/reference/rest-api/observability): read events, traces and the live stream over REST. * [Environment variables](/reference/environment-variables): the OpenTelemetry and retention settings. * [System Health](/using/system-health): the checks Clawboo runs at startup, including the OpenTelemetry exporter. # How agents talk to each other Source: https://docs.claw.boo/concepts/peer-chat The agents' own peer room: how it differs from the team chat you see, who can post and read, and the bounded exchange you start through the API. The agents on a team share a **peer room**: a notice board that only agents read, separate from the team chat you see. An agent posts there to tell its teammates what it finished, found or changed, without going through the lead. The dashboard does not show the peer room. Use this page to understand how the peer room differs from team chat, who can use it, and its limits. ## Team chat and the peer room | | Team chat | Peer room | | - | - | - | | Who it is for | You and your team | The team's agents | | Where you see it | General and each room, in the dashboard | Not in the dashboard. Scripts can read it through the API. | | How many | General, plus one per room | One per team, shared by General and every room | | Who leads | Boo Zero | Nobody: posts are notes between peers | | How messages get in | You type, and agents reply | Agents post with a tool, and Clawboo adds a line when the board changes | | Where it is stored | The team's chat history | Its own table, kept apart from the chat history | Work is still handed off through the lead and the board, as described in [How work is delegated](/concepts/delegation-and-orchestration). The peer room carries the side notes: "I changed the shared config", "the API key in the brief is wrong", "the draft is in the docs folder". ## How agents read and post **Posting.** An agent on Clawboo Native, Claude Code, Codex or Hermes posts with the `team_chat_post` tool during a team run. Clawboo binds each agent's identity to its connection, so a post always shows its real author: an agent cannot post as a teammate, even by editing its own configuration. **Reading.** Agents catch up in three ways: 1. At the start of every team chat turn, an agent receives the posts it has not seen yet, oldest first, up to 4,000 characters in total and 800 per post. Posts that did not fit wait for its next turn. OpenClaw agents get this catch-up too. 2. An agent with the tools can read new posts at any time with `team_chat_subscribe`. 3. A Clawboo Native agent also picks up new posts between the steps of a run. An agent never receives its own posts back as new input. **Board lines.** When the board changes through Clawboo's board API, which includes your moves and comments in the dashboard, Clawboo posts a short system line into the room. ## Posts are evidence, not orders Every post reaches an agent wrapped in a header that ends in `isUser=false`. The agent is told to treat a teammate's post as information about the work, never as an instruction: a post cannot change its task, its policies or the team rules. A post that tries to fake a second header is neutralised before delivery, so a teammate cannot pass itself off as you. ## Who can use it * **Agents on Clawboo Native, Claude Code, Codex and Hermes** read and post during team runs. * **OpenClaw agents** read the catch-up at the start of each team chat turn, but have no tool to post. One Gateway configuration serves all of them, so Clawboo cannot sign their identity. They post only during an exchange started through the API (below). * **Clawboo** adds system lines for board changes. * **You** have no peer room in the dashboard, and nothing posts there on your behalf. To read it, call `GET /api/team-chat`. * **A run with no team, or an outside agent attached without a signed identity**, gets no peer-room tools at all. ## The bounded exchange (API only) An **exchange** is a short round of turns among a team's members, started with `POST /api/team-chat/exchange`. Nothing in the dashboard starts one. Each turn's reply is posted into the peer room, not into team chat. ```bash theme={"theme":"github-dark"} curl -X POST http://127.0.0.1:18790/api/team-chat/exchange \ -H 'Content-Type: application/json' \ -d '{ "teamId": "", "stimulus": "Compare notes on the launch plan", "firstSpeakers": [""] }' ``` Change the port if your server uses another one (see [CLI](/reference/cli)). With an access token set, add the `clawboo_access` cookie described in the [REST API overview](/reference/rest-api/index). * **Who speaks.** The agents in `firstSpeakers`, or the team's Leader when you list none. Boo Zero is not a member, so it does not take part. With no Leader and no `firstSpeakers`, the exchange ends at once with no turns. After the first speakers, each member that a turn delegates or reports to gets a turn, so an agent can speak more than once. The exchange ends when nobody is owed a turn or the turn limit is reached. * **Turn limit.** 5 turns by default. A `maxExchangeTurns` you pass is kept between 1 and 5 times the number of members. * **Spending.** A team or first speaker whose hard-cap budget is paused refuses the exchange before any turn runs. Every later turn checks its speaker's budget and the team's again: a paused hard cap refuses that turn, adds a notice to the team chat and ends the exchange. A turn that trips a hard cap ends it too. * **One at a time.** A second exchange for the same team while one is running gets `409`. Closing the request stops the exchange before its next turn. * **Errors.** `400` without a `teamId`, `404` for an unknown team, and `422` when the team has no agents, a first speaker is not a member, or a budget is paused. Each exchange records a `speaker_selected` event per turn and one `turn_bound_hit` event when it ends, with the reason and the number of turns taken. See [Observability](/concepts/observability). ## Limits * One peer room per team. Rooms do not get their own; General and every room share it. * The dashboard neither shows the peer room nor starts an exchange. * OpenClaw agents cannot post outside an exchange. * An agent's catch-up holds at most 4,000 characters per turn, and 800 per post. * Posting is up to each agent. Clawboo tells agents with the tools when a post helps a teammate, but nothing makes them post. > [accordion] Under the hood > > * **Storage.** Posts live in the `team_chat` table, one room per team with the id `team:`. Each post has an author, a `kind` (`peer` or `system`) and a per-room sequence number `seq`. The dashboard's transcript is the separate `chat_messages` table. > * **Reading through the API.** `GET /api/team-chat?teamId=&sinceSeq=&limit=` returns `{ roomId, posts, nextSeq }`. Pass `roomId` instead of `teamId` to name the room directly. > * **The envelope.** A delivered post reads `[Inter-session message · from= · kind= · seq= · isUser=false]`, then the body with every line prefixed `| `. An embedded header in the body is replaced with `[peer-quoted header]`. > * **Identity.** Clawboo Native agents attach to the TeamChat tools in process with a fixed identity. Claude Code, Codex and Hermes attach over HTTP with a URL whose team and author Clawboo signs. An unsigned or edited URL is treated as unidentified and served no TeamChat tools. Only the `clawboo-mcp-teamchat` stdio program, which you run yourself, takes its author from tool arguments. > * **Catch-up cursor.** Each agent has a cursor per room. A catch-up advances it only past the posts it actually delivered. > * **OpenClaw.** The Gateway registers only Clawboo's Memory, Tasks and Tools servers for OpenClaw agents. TeamChat is left out, because a single Gateway configuration cannot carry a per-agent identity. > * **Exchange engine.** The next speaker is an agent that owes a turn, fewest turns first, avoiding the one who spoke last. With nobody owing a turn, the Leader is picked unless it spoke last. In normal use each turn obliges nobody else, so the exchange ends once every listed speaker has had a turn. ## Related * [How work is delegated](/concepts/delegation-and-orchestration): how real work moves between agents, through the board. * [Teams, rooms and runtimes](/concepts/teams-and-planes): General, rooms and what a team shares. * [Chat with your team](/using/team-chat): the team chat you use in the dashboard. * [MCP servers](/operating/mcp-servers): Clawboo's four MCP servers, including TeamChat, and how agents attach. * [Observability](/concepts/observability): where exchange events land. # What agents can do Source: https://docs.claw.boo/concepts/permissions What each runtime can reach on your computer, when Clawboo stops to ask you first, and how to set up a safer install. An agent can act in two ways. It uses its runtime's own tools, such as a shell or a file editor, and it uses Clawboo's tools: the board, memory, the agents' [peer room](/concepts/peer-chat) and the connectors you share with it. This page answers two questions for each runtime. What can the agent reach? And when does Clawboo stop and ask you first? The short answer: Claude Code, Codex and Hermes run with their own permission prompts and sandboxes turned off, so they can work unattended. Clawboo asks you before a smaller set of actions that pass through Clawboo itself. > [!WARNING] > > A git worktree keeps two agents' file changes apart. It is not a sandbox. Claude Code, Codex and Hermes run as you: they can read and change any file your user account can, reach the network, and start other programs. Without a worktree, as in team chat or an agent's own chat, they start in their team's folder, `~/.clawboo/workspaces/`. That folder is only where they start, not a fence: from there they can still reach Clawboo's database and saved keys, like any other file you can open. ## Each runtime at a glance | Runtime | Its own prompts and sandbox | Where it works | Clawboo asks you before | | - | - | - | - | | Clawboo Native | None of its own: Clawboo provides its tools | Task runner with a repository: the task's worktree, except for research and review tasks. Otherwise, including one-to-one chat: the team's folder | Each shell command, once you turn shell commands on | | Claude Code | Off (`bypassPermissions`) | Task runner with a repository: the task's worktree, except for research and review tasks. Otherwise: the team's folder | Nothing it does with its own tools | | Codex | Off (approval `never`, sandbox `danger-full-access`) | Same as Claude Code | Nothing it does with its own tools | | Hermes | Off (`--yolo`) | Same as Claude Code | Nothing it does with its own tools | | OpenClaw | The OpenClaw Gateway's own settings | The agent's own OpenClaw workspace, never a Clawboo worktree | Shell commands, following the agent's **Command Execution** setting | On every runtime, calls to connectors go through Clawboo and follow the rules in [Connectors](#connectors). Team chat and the task runner are the two ways Clawboo runs agents; [How a team works](/intro/how-it-works) explains which work takes which path. ## Clawboo Native Clawboo itself runs these agents, so every tool they have is one Clawboo built: * **In team chat, in one-to-one chat and on research and review tasks** it works in its team's folder, with tools to list, read and write files there. In one-to-one chat, an agent on no team works in `~/.clawboo/workspaces/_no-team`. * **On a task-runner run with a repository** it gets the same tools inside the task's worktree. * **File tools stay inside their folder.** A path outside it is refused, and so is a path that leads out of it through a symlink. * **In every run** it also works through Clawboo's tools: the board, memory, the peer room, handing work to teammates, and the connectors you share with it. * **Shell commands are off by default.** To turn them on for one agent, open its page, then **Details** > **Permissions**, and switch on **Let this Boo ask to run commands**. With shell commands on, every command asks you first. There is no "Always" for them, and a card waits 10 minutes for your answer. One run can ask about at most 10 commands. Commands are offered only on runs that have a working folder, and never on Windows. A command you approve runs as you, with network access. Each command is a program and its arguments, run directly rather than through a shell. Before a card reaches you, Clawboo turns away commands whose effect the card could not show: * **59 named programs that run other commands**: shells such as `bash`, wrappers such as `env`, `xargs` and `sudo`, interpreters such as `python` and `node`, package runners such as `npx`, build tools such as `make`, and remote runners such as `ssh` and `docker`. * **Scripts whose first line names one of them**, such as a file starting `#!/usr/bin/env python3`. * **Programs given by a relative path**, such as `./build.sh`. The list is matched on the program's file name, with versions such as `python3.11` included, and checked again on the real file after following links. It is a speed bump, not a boundary: programs that are not on it, such as `git`, `find` or `awk`, can still start a shell. Your answer on each card is the real control. ## Claude Code, Codex and Hermes Clawboo starts these three runtimes with their own approval prompts and sandboxes turned off: * **Claude Code** runs in permission mode `bypassPermissions`. * **Codex** runs with approval policy `never` and sandbox `danger-full-access`. * **Hermes** runs with `--yolo`. They can run any command, edit any file and reach any address your user account can. Clawboo puts no approval step in front of their own tools. Where they start depends on the path: * **Task runner, with a repository:** in the task's own git worktree, a separate checkout under `~/.clawboo/worktrees`. That is their starting folder, not a fence. A task that changes files but names no repository is refused, rather than run somewhere else. * **Task runner, research or review task:** no worktree, even with a repository. Agent Routines you create in the app run this way. They start in the team's folder, the same as team chat. A task with no team starts in a shared folder, `~/.clawboo/workspaces/_no-team`. * **Team chat**, including board tasks you give an agent: no worktree. They start in the team's folder, `~/.clawboo/workspaces/` (under `CLAWBOO_HOME` when you set it). Every member and every room of the team shares this one folder. * **One-to-one chat** on the agent's page: no worktree. They start in the team's folder, or in `~/.clawboo/workspaces/_no-team` for an agent on no team. Clawboo creates the team's folder the first time a run needs it, readable only by your user account. If it cannot create the folder, the run does not start. The folder only sets where these runtimes start; it fences nothing in. Before starting them, Clawboo removes its own secrets from the environment it passes on: the dashboard access token, the OpenClaw Gateway token and the key that unlocks Clawboo's saved keys. It also removes a list of common cloud and code-hosting tokens. This works by name and is best effort. A runtime can still read credential files on disk, such as `~/.openclaw/.env`, where keys saved on the **Providers** page are copied in plain text. Claude Code starts without your own Claude Code setup: your `~/.claude` settings, plugins, hooks, MCP servers and memory do not apply. It still loads the Claude Code files of the folder it starts in, such as a repository's `CLAUDE.md` and `.claude/settings.json`. Leaving your setup out is not a sandbox: Claude Code can still read any file your user account can, `~/.claude` included. ## OpenClaw OpenClaw agents run inside the OpenClaw Gateway, in workspaces the Gateway manages. Clawboo never gives them a worktree, and OpenClaw's own sandbox settings decide what they can touch ([OpenClaw sandboxing](https://docs.openclaw.ai/gateway/sandboxing)). Shell commands follow each agent's **Command Execution** setting, on the agent's page under **Details** > **Permissions**: | Setting | What happens | | - | - | | **Run Freely** | Commands run without asking | | **Ask for Unknown** | Asks before a command that is not on its list | | **Always Ask** | Asks before every command | An OpenClaw agent you create in Clawboo starts on **Ask for Unknown**. The cards appear under **Needs you** and in the chat. Clawboo's server keeps them, so they wait even with the tab closed: until the Gateway's deadline, or 30 minutes when it sets none. [Command permissions](/using/command-permissions) shows what OpenClaw already allows without asking. Clawboo also denies OpenClaw's own sub-agent tools (`sessions_spawn`, `sessions_yield`), so an OpenClaw agent cannot start helpers outside the team. ## Connectors A connector links an outside app or tool, such as GitHub or a web browser. Every call to one goes through Clawboo, whatever runtime the agent uses: * **An agent reaches a connector only after you share it.** Drag the connector onto the agent on the [Org chart](/using/org-chart) or a team graph, and pick **Read**, **Write** or **Admin**. Connecting a connector gives no agent access by itself. * **Every new agent gets the two built-in browser connectors** (Playwright and Chrome DevTools) at **Write**. You can stop sharing them on the graph. * **With Write, risky calls ask first.** For a connector that can reach the internet, any call that changes something asks you. When the card offers **Do not ask again for this for 30 days**, ticking it allows that tool for 30 days. * **A run that might leak data always asks.** Once a run has read private data and taken in untrusted content, every connector call that can send data out asks you, and the card offers no way to skip it next time. * **OpenClaw agents call connectors without an agent identity.** Connectors you share with an agent do not reach them, and their calls to apps through Composio ask you each time. See [Connectors](/using/connectors) to connect and share them. ## What Clawboo checks itself These controls are Clawboo's own. None of them adds an approval step to the tools Claude Code, Codex and Hermes bring with them. ### Approvals Everything that waits for your yes lands in the same places: the **Needs you** column on the [Board](/using/board), the **Tasks** card in a team's chat, and cards in the chat itself. That covers Clawboo Native shell commands, OpenClaw shell commands, connector calls that need a yes, and risky hand-offs. [Approvals](/using/approvals) shows what each card does. ### Risky hand-offs When an agent in team chat hands off a task whose text contains a risky word, the hand-off waits for you. The check matches whole words, in any letter case: * delete, destroy, deploy, publish, release, secret, credential, prod, production * the phrases drop table and rm -rf * api\_key, api-key or apikey, and force-push, force\_push or forcepush Other forms do not trigger it: "deleted", "deployment" and "secrets" pass, and so do "API key" and "force push" written with a space. The steps of a multi-step plan are not checked at all. If you choose **Don't allow**, or nobody answers within 5 minutes, the hand-off is skipped and the agent that handed it off is told. The card in the dashboard offers only a one-time allow and **Don't allow**. The request is filed under the chat's leader. Nothing is remembered: an `allow_always` answer sent through the API allows that one hand-off, and the next risky hand-off asks again. [Budgets and guardrails](/concepts/governance) has the full rules. ### Budgets A budget counts what your agents spend and warns you as it fills. That is the default. A **hard cap**, which pauses work at 100%, is opt-in. In team chat a paused hard cap refuses new runs, but it stops a run already going only on Clawboo Native with a model Clawboo prices. [Budgets and guardrails](/concepts/governance) has the details for each path. ### Circuit breakers Only task-runner runs have circuit breakers. Such a run stops after more than 30 tool calls, the same failing call 3 times in a row, 6 tool results in a row that add nothing new, more than 200,000 tokens a minute, or 2 refusals in a row. Team chat has its own limits instead. A turn can hand off at most 8 tasks, and hand-offs go at most three levels deep. Clawboo stops handing a task to a teammate that failed it 3 times, and stops a teammate that goes silent for 8 minutes. [Limits and defaults](/reference/limits-and-defaults) lists every number. ### Images in agent text An image in an agent's markdown never loads on its own. In chat, a task's report and its comments, it shows as a link named after its alt text, and it opens only when you click it. That way an agent steered by something it read cannot send data out in an image's address without your click. ## What this does not cover * **Clawboo sandboxes no runtime.** A worktree is a starting folder. The network, files outside it and other programs stay within reach of Claude Code, Codex and Hermes. * **Approvals guard against a confused or misled model, not against programs on your computer.** With no access token set, any program on your computer can call Clawboo's API, including a command an agent runs, and it could answer an approval card itself. Setting `STUDIO_ACCESS_TOKEN` closes the API to other user accounts, and Clawboo never hands the token to the agents it starts. A program running as you can still read your files, including Clawboo's database and key vault, so nothing in Clawboo fully contains it. See [Security](/operating/security). * **Clawboo does not limit where agents connect.** Runtimes reach their providers, and anything else on the network, directly. Only connector calls pass through Clawboo. ## A safer setup * **Give Clawboo its own user account.** For work you do not fully trust, run Clawboo under a separate user account on your computer, or in a virtual machine. A coding runtime can then reach only what that account can. * **Hand coding runtimes only work you would let them do unattended.** In team chat they run without prompts, and their team's folder does not limit what they can reach. * **Connect only accounts you would hand to the agent yourself.** Share each connector only with the agents that need it, at **Read** unless they must change things. * **Turn on shell commands sparingly.** Leave Clawboo Native shell commands off unless an agent needs them, and keep OpenClaw agents on **Ask for Unknown** or **Always Ask**. * **Put a budget on spend**, and switch it to **hard cap** when money matters: see [Budgets and spend](/using/budgets-and-spend). * **Set an access token** before you open Clawboo to other computers, and to keep other user accounts on this one out of the API: see [Self-host securely](/guides/self-host-securely). > [accordion] Under the hood > > * **Driver settings.** Claude Code runs through the Agent SDK `query()` with `permissionMode: 'bypassPermissions'` and `allowDangerouslySkipPermissions: true`. It loads only the project settings source (`settingSources: ['project']`), with auto memory off and every `CLAUDE.md` above the working folder excluded (`apps/web/server/lib/runtimes/claudeCodeIsolation.ts`). Codex runs `codex app-server` with `approvalPolicy: 'never'` and `sandbox: 'danger-full-access'`; its `codex exec` fallback passes `--dangerously-bypass-approvals-and-sandbox` (`codexAppServerDriver.ts`, `codexDriver.ts`). Hermes runs `hermes chat` with `--yolo --accept-hooks` (`hermesDriver.ts`). > * **Working folder.** Team-chat runs (`serverDeliver.ts` and `runTeamExchange.ts` in `apps/web/server/lib/teamChat/`) and task-runner runs without a worktree set `cwd` to `/workspaces/`, or `workspaces/_no-team` for a task with no team, for Claude Code, Codex, Hermes and Clawboo Native (`apps/web/server/lib/runtimes/teamWorkdir.ts`). The folder is created with mode `0700` before the run starts, and a run whose folder cannot be created fails instead of starting in the server's working directory. One-to-one chat (`apps/web/server/lib/agentChat/driveAgentChat.ts`) sets the same `cwd` for those four runtimes, with `_no-team` for an agent on no team. > * **Task-runner isolation.** Task kinds `research` and `review` get no worktree. Any other kind needs a `repoPath`, or the run fails with `workspace_unavailable` (`apps/web/server/lib/executorRunner.ts`, `packages/worktrees/src/isolation.ts`). > * **Native tools.** `buildFileTools` and `buildExecTool` return no tools when the run has no `cwd` (`apps/web/server/lib/runtimes/native/`). The file tools refuse a path that is a symlink, or that resolves outside the `cwd` once every symlink above it is followed (`resolveJailedOnDisk` in `fileTools.ts`). > * **Environment scrubbing.** `apps/web/server/lib/runtimes/childEnv.ts` lists the removed variables. > * **Connector decisions.** `decideGrant` in `packages/governance/src/grants/decide.ts`; a new grant's approval policy defaults to `risk`. > * **Risky hand-offs.** The word list is in `apps/web/server/lib/teamChat/riskyDelegation.ts`. Each risky hand-off opens its own approval under the scope `delegate:code`, marked never-remember (`apps/web/server/api/delegationApproval.ts`). > * **No hidden approval layer.** The task runner's `routeApprovalsThroughClawboo` flag only adds a note to the prompt (`apps/web/server/lib/degradation.ts`). ## Related * [Approvals](/using/approvals): answer the cards under Needs you. * [Command permissions](/using/command-permissions): what OpenClaw agents may already run without asking. * [Budgets and guardrails](/concepts/governance): spend limits, caps and circuit breakers on each path. * [Security](/operating/security): the access token and network exposure. * [Worktrees and handoff](/concepts/worktrees-and-handoff): what a worktree is for. # How routines run Source: https://docs.claw.boo/concepts/scheduling What happens when a routine fires: the ticker, team and agent routines, missed runs, failures that stop a routine, and the one-owner rule. A **routine** is work Clawboo starts on a schedule. Each time it fires, it hands its instructions to a team or to one agent, with nobody at the dashboard. This page explains what happens when a routine fires, what happens when a fire fails, and the rules that keep a routine from running twice. To create and manage routines, see [Routines](/using/routines). ## How a routine fires * **The routine is a row in Clawboo's database.** It holds who the routine goes to, its schedule, its instructions, its status and the time of its next run. * **The schedule** is a cron expression, such as `0 9 * * 1-5` for weekdays at 9:00, or a single time written as `once@` followed by an ISO date and time. Cron times use the time zone of the machine the Clawboo server runs on. * **The ticker**, a timer inside the Clawboo server, wakes when the next routine is due, and at least once a minute. Each time, it finds the routines that are due, claims each one so that it fires only once, and hands it on. Routines that fall due together fire side by side. Nothing fires while the Clawboo server is stopped. * **Run now** queues a routine immediately, and the ticker fires it straight away. Each fire then goes one of two ways: ```mermaid theme={"theme":"github-dark"} flowchart LR T["Ticker"] --> TR["Team routine"] T --> AR["Agent routine"] TR --> G["Message in the team's General chat"] AR --> K["New task for the agent"] K --> R["Task runner: Clawboo Native, Claude Code, Codex, Hermes"] K --> O["OpenClaw dispatcher"] ``` ## Missed and overlapping runs * **Missed times are not made up.** If Clawboo was stopped, or the machine was asleep, when a routine was due, the routine fires once when the ticker next checks. It then carries on from its next scheduled time after that run. * **A routine never overlaps itself.** Scheduled times that pass while a run is still going are skipped. * **A restart in the middle of a fire** leaves a recurring routine waiting for its next scheduled time, and stops a one-time routine with an error for you to check. A fire that was claimed but had not started yet runs as soon as the server is back. The cut-off run shows as **interrupted** in the routine's history after its next fire. * **A one-time routine** fires once, then shows as **finished** and never runs again. ## What a team routine does A team routine posts its instructions into the team's **General** chat, never into a room. The message is labelled **Routine** with the routine's name instead of **You**, and it is routed like a message you type: if it starts with `@Name`, it goes to that teammate; otherwise it goes to the team's lead (Boo Zero when you have one, else the team's Leader, else its first active member). The lead is told the message came from a routine and that you may not be watching, so it should carry the work out and make reasonable assumptions instead of asking questions and waiting. The fire counts as done, shown as **posted**, once the message reaches the lead, even if the lead's turn waits behind one already running. From there it is ordinary team chat, with the same hand-offs, Board tasks, **Stop** button and [guardrails](/concepts/governance) as a message you typed. A team routine creates no Board task of its own. A team fire fails when the team no longer exists, is archived, has no active members, or does not take messages from routines. It also fails when a paused hard-cap budget on the team or on the agent it goes to refuses that agent's turn as it starts. If the agent was busy when the routine fired, the fire already counted as **posted**, so a refusal when its turn comes shows only in the chat. ## What an agent routine does An agent routine creates a new Board task for its agent on every fire, with the routine recorded as the task's scheduler. (A routine bound to an existing task through the API runs that task instead.) Then it runs the task in one of two ways, depending on the agent's runtime: | | Clawboo Native, Claude Code, Codex, Hermes | OpenClaw | | - | - | - | | Runs through | The task runner | A small dispatcher that sends the task over the Gateway | | Budget check before it starts | Yes | Yes | | Circuit breakers | Yes | No | | Worktree and verification | Only when the task template names a repository (API only) | Never | | Time limit | Ended after 30 minutes with no activity | 10 minutes for the whole run | | Trace in Observability | Yes | No | On OpenClaw, the Gateway must be connected, or the fire fails. A finished OpenClaw run gets a comment on its task saying it completed without verification. If a person moves the task's card out of In progress on the Board while it runs, the run stops at once. That counts as the outcome, not as a failure, and the routine stays on its schedule. The Routines dialog sets who a routine goes to, its schedule, its name and its instructions. The other task template fields are set only through the [Schedules API](/reference/rest-api/schedules): a repository path (`repoPath`), the task kind, the model, the priority, a per-run cost cap (`maxNodeCents`) and an existing task to bind to (`teamTaskId`). An agent routine made in the dashboard therefore has no repository. It runs as a research task without a worktree, the same way the agent works in team chat: see [What agents can do](/concepts/permissions) for where Claude Code, Codex and Hermes start in that case. ## When a fire fails, the routine stops A routine whose fire fails stops itself. Its dialog shows **Stopped after a failed run.** with the error, and it does not run again until you press **Resume**. Common causes: * an agent routine's run is refused before it starts, because a hard-cap budget for the agent, the team or the task's delegation tree is already paused; * a team routine's message is refused the same way, because a hard-cap budget for the team or the agent it goes to is already paused; * a circuit breaker or a hard cap stopped an agent routine's run while it was still working (a Claude Code or Codex run whose last cost report crosses a hard cap keeps its result, and the paused budget refuses the routine's next run instead); * the run itself failed or timed out; * the OpenClaw Gateway was not connected (OpenClaw agents only); * the agent was removed, or the team was deleted or archived. The task that a failed agent fire created is moved to Blocked, so it waits in **Needs you**, with a comment saying the scheduled run failed and the task was set aside. The routine files a fresh task on its next run. A task the routine is bound to is left as the run left it. Two outcomes are not failures. If another agent had already claimed the task, the work is happening, and the fire counts as done. If a person moved the task's card, their move is the outcome. Both leave the routine on its schedule. Routines stop instead of retrying on purpose. A routine that failed on every fire and kept going would spend money and fill the board with copies of the same failure. Stopping it once puts the cause in front of you. ## One scheduler per task Every Board task has one scheduler on record: you (a task made by hand), Clawboo's routines, or OpenClaw. Two schedulers firing the same task would run it twice, so Clawboo keeps it to one: * **Binding to an existing task** (`teamTaskId`, through the API) is refused when another scheduler already owns the task. A task you made by hand is taken over by the routine. * **A bound routine must be one-time.** A task can be claimed only once, so a recurring schedule on a bound task is refused. A team routine cannot be bound to a task at all. * **The claim is the backstop.** If two fires ever reach one task, only one of them can claim and run it. ## OpenClaw's own cron jobs The OpenClaw Gateway has its own scheduler, which an OpenClaw agent can use to wake itself. The Routines view lists those jobs in a separate section, **OpenClaw's own cron jobs**, so everything scheduled is in one place. You can enable, disable, run or delete a Gateway job there, but not edit it. While the Gateway is disconnected, the section shows the last list it sent, or hides the jobs until it reconnects. Your own routines are not affected. Clawboo never uses the Gateway's scheduler for routines, and refuses to schedule team work through it. Claude Code, Codex, Hermes and Clawboo Native have no scheduler of their own that Clawboo drives, so a routine is how you schedule them. The Board's calendar shows the upcoming runs of your routines, but not of Gateway jobs. > [accordion] Under the hood > > * A routine's statuses are `idle`, `queued`, `claimed` (shown as **starting**), `running`, `paused` and `error`. The ticker picks up only `idle` routines whose next run is due. `paused` and `error` wait for a person. > * Each fire is claimed with a conditional database update, so two tickers, or two server processes, cannot both fire it. A lost claim is dropped and never retried. > * After a restart, a `claimed` fire is queued again, a recurring `running` fire is re-armed for its next time with the note `orphaned mid-dispatch (restart)`, and a one-time `running` fire moves to `error`. > * The part that hands a fire on, by posting to the team or by creating and running the agent's task, is the wake bridge. It picks the agent path from the runtime's declared capabilities, never from its name. > * Each fire writes `routine_fired`, then `routine_dispatched` (with the path `team-chat`, `one-shot` or `connected`), then `routine_completed` or `routine_error` to the event log. > * `CLAWBOO_ROUTINE_OPENCLAW_TIMEOUT_MS` changes the 10-minute limit for OpenClaw runs. The [Schedules API](/reference/rest-api/schedules) lists the template fields and the error codes for ownership conflicts and bound schedules. ## Related * [Routines](/using/routines): create, edit, pause, resume and run routines. * [Budgets and guardrails](/concepts/governance): which guardrails cover team routines and agent routines. * [Worktrees and handoff](/concepts/worktrees-and-handoff): when a routine's task gets a worktree. * [What agents can do](/concepts/permissions): where an agent routine without a repository runs. * [Schedules API](/reference/rest-api/schedules): template fields, one-time schedules and error codes. # Teams, rooms and runtimes Source: https://docs.claw.boo/concepts/teams-and-planes What a team is, how its General chat and rooms run, what the whole team shares, and what each runtime keeps to itself. A **team** is a group of agents that work together. It has one board, one set of team rules and a shared memory. You talk to a team in its **General** chat, or in a **room**, a smaller conversation with some of its members. A team can mix runtimes: one member on Clawboo Native, another on Claude Code, a third on OpenClaw. Use this page to understand what a team has, how its chats run, and what the team shares compared with what each runtime keeps to itself. ## The mental model Picture a small office. Everyone shares the whiteboard (the board), the house rules (team rules) and the filing cabinet (shared memory). Each person also keeps a private notebook, which here is the runtime's own memory and settings. Teammates cannot read each other's notebooks. Claude Code is the exception: every Claude Code agent uses your own Claude Code setup, as the table below shows. ```mermaid theme={"theme":"github-dark"} flowchart TB subgraph SHARED["Shared by the whole team"] BOARD["Board"] RULES["Team rules"] MEM["Shared memory"] end A["Agent on Clawboo Native"] --> SHARED B["Agent on Hermes"] --> SHARED C["Agent on OpenClaw"] --> SHARED A -.-> PA["Its saved conversations"] B -.-> PB["Its Hermes memory and skills"] C -.-> PC["Its state in your Gateway"] ``` Solid lines are what every member reaches the same way, whatever its runtime. Dotted lines are what each runtime keeps for itself. OpenClaw is a partial exception, described [below](#openclaw-shares-less). ## What a team has * **Members.** Agents on any mix of runtimes. An agent is on one team at a time, or on none. Boo Zero leads every team, usually without being a member of any (see [Agents and Boo Zero](/concepts/agent-model)). * **General.** The team's main chat, with every member in it. The sidebar shows it as **General (Main Room)**. It is always there: you can clear it, but not rename or delete it. * **Rooms.** Extra chats with some of the members, made with **New room**. Each room has its own conversation and its own list of tasks, and Boo Zero leads it too. You can rename, pin, clear or delete a room. Deleting a room archives it: its conversation is kept, its running work stops, and its tasks stay on the board. * **One board.** Every task from every chat of the team. A task remembers which chat it came from, and that chat runs it. If the task's room was deleted, General runs it. See [The board](/concepts/the-board). * **Team rules.** Rules you set, such as "always cite sources". Every agent gets them on every turn it takes in the team's chats, in General and in every room, including work handed off there. * **Your introduction.** What you write the first time you open a team. It reaches the agents whose replies you read (the lead, or an agent you address directly), not the teammates working on handed-off tasks. * **A team brief.** Boo Zero's notes about the team. Boo Zero reads it, up to 900 characters, only in its own one-to-one chat, when you start a message there with `@` and the team's name. In team chat, the lead sees the roster with each member's role instead. * **Shared memory.** A fact a member saves during a team run is filed under the team, so any teammate on any runtime can recall it. See [Memory](/concepts/memory). * **A peer room.** A notice board that only the team's agents read, separate from General and the rooms. The dashboard does not show it; see [Peer chat](/concepts/peer-chat). You set the rules and the brief in the team settings, behind the gear in a team chat's header. [Manage teams](/using/teams) has the steps, and [Chat with your team](/using/team-chat) covers General, rooms and clearing a chat. ## How a team's chats run Each chat, General and every room, has its own **coordinator** on the Clawboo server. The coordinator is the part of Clawboo that routes your message, turns each hand-off into a task on the board, and reports results back. It runs on the server, so work keeps going when you close the browser, and it picks up unfinished work after a restart. A coordinator sees only its own chat's members and tasks, so two rooms never take each other's work. An agent that belongs to two rooms still runs one turn at a time. Routines use the same path. A team Routine posts its instructions into the team's General chat as if you typed them, and Boo Zero answers. An agent Routine gives that agent a task on the board. Clawboo never drives a runtime's own scheduled jobs. See [How routines run](/concepts/scheduling). ## Shared and private **Shared** means Clawboo stores it, and every teammate reaches it the same way, whatever its runtime: the board, the team's chats, team rules and shared memory. **Private** means the runtime keeps it for itself: its own memory, skills, saved sessions and settings. Clawboo does not copy it into shared memory or hand it to teammates (Claude Code aside, see the table). Private here is about coordination, not security. Most runtimes can read any file your user account can; see [What agents can do](/concepts/permissions). Clawboo gives three runtimes a folder of their own per agent, inside Clawboo's home folder (`~/.clawboo`, or `CLAWBOO_HOME` if you set it). Only your user account can open these folders. | Runtime | Where its own state lives | What Clawboo does there | | - | - | - | | Clawboo Native | `~/.clawboo/runtimes/clawboo-native/`: its saved conversations | Creates the folder. A lead's conversation continues from one turn to the next. | | Hermes | `~/.clawboo/runtimes/hermes/`: its `MEMORY.md`, `state.db` and the skills it creates | Writes three files and nothing else: `mcp.json` on every run, `config.yaml` copied once from your `~/.hermes` if missing, and `auth.json` copied from `~/.hermes` when yours is newer. It never writes to your own `~/.hermes`. | | Codex | `~/.clawboo/runtimes/codex/`: that agent's `CODEX_HOME` | Copies in your `codex login` so the agent can run, and keeps its session files, so a Codex lead keeps its conversation between turns. | | Claude Code | Your own `~/.claude`, where Claude Code saves each run's session files | Nothing. Claude Code agents start without your own Claude Code settings, MCP servers and memory, and with its auto memory off. See [Claude Code](/runtimes/claude-code#your-own-claude-code-setup-stays-out). | | OpenClaw | Your OpenClaw Gateway's own state | Nothing. The Gateway owns it. | ## OpenClaw shares less OpenClaw agents share the board, but one Gateway configuration serves all of them, so Clawboo's tools cannot tell one OpenClaw agent from another. As a result, OpenClaw agents: * read and save shared memory in the global area only, so they do not see the team's own facts; * have no tool for the agents' peer room (see [Peer chat](/concepts/peer-chat)); * cannot claim or assign board tasks through Clawboo's task tools; * do not get the connectors you give them on the Org chart, and ask you for approval each time they use an app through Composio. They still receive the team rules and the roster, and they hand work to teammates with a delegate line in their reply. See [OpenClaw](/runtimes/openclaw). ## Mixed runtimes in one team A team does not have one runtime; each member has its own. Boo Zero leads any mix, and every member receives work and reports back the same way. What differs is how each runtime signals a hand-off and what it can do on your machine: * [How work is delegated](/concepts/delegation-and-orchestration) lists the hand-off signal for each runtime. * [What agents can do](/concepts/permissions) explains file access and approvals per runtime. * [Run a mixed-runtime team](/guides/multi-runtime-team) walks through building one. ## What you control * **Members and runtimes**: pick each member's runtime when you deploy a team, in **Customize team**. To add a member later, select the team and use **Create Boo**. See [Manage teams](/using/teams). * **Rooms**: create one with **New room**, then rename, pin, clear or delete it from its menu in the sidebar. See [Chat with your team](/using/team-chat). * **Team rules and the team brief**: in the team settings behind the gear in a team chat's header, or capture a rule with `/rule` in the composer. ## Limits * An agent is on one team at a time. * Team rules reach team chat turns only. A run started by an agent Routine does not include them. * The team brief is not part of team chat turns; only Boo Zero's one-to-one chat reads it, when you mention the team. * Deleting a room cannot be undone from the dashboard. Its conversation is kept, but the room is gone from the sidebar. > [accordion] Under the hood > > * **Storage.** A team is a row in the `teams` table (name, icon, colours, an optional `leaderAgentId`, `isArchived`). Membership lives on each agent's `teamId`. Rooms are `rooms` and `room_members` rows; General is the implicit room with id `null` and has no row. The visible transcript of every chat is `chat_messages`, keyed by session keys such as `agent::team:` and `...:room:`. > * **One engine per chat.** The server keeps one long-lived orchestration engine per chat, keyed `` for General and `:room:` for a room. Its roster is the room's live members plus Boo Zero. An idle engine is dropped after 30 minutes and rebuilt on the next message. See [Executor runner](/internals/executor-runner) for the other run path. > * **Per-agent homes.** A runtime whose adapter declares a persistent per-identity home gets `/runtimes//`, created with mode `0700`. That is Clawboo Native, Hermes and Codex. Claude Code declares none and OpenClaw's Gateway owns its own state. See [RuntimeAdapter](/internals/runtime-adapter). > * **OpenClaw identity.** The Gateway registers Clawboo's Memory, Tasks and Tools servers once for every OpenClaw agent, so those sessions carry no verified agent identity. Unverified callers get global-only memory, no `claim_task` or `assign_task`, and no TeamChat tools. ## Related * [Agents and Boo Zero](/concepts/agent-model): who leads a team, and what an agent is made of. * [The board](/concepts/the-board): the task list every chat of a team shares. * [Memory](/concepts/memory): shared memory compared with each runtime's own memory. * [Peer chat](/concepts/peer-chat): the agents' own room, separate from the chats you see. * [Manage teams](/using/teams): create teams, set rules and briefs. # The board Source: https://docs.claw.boo/concepts/the-board How tasks move on a team's board: statuses, Needs you, one agent per task, steering by comment, and when work restarts by itself. The **board** is a team's task list. Every piece of work a teammate is asked to do becomes a task card on it. A card appears when Boo Zero hands work to a teammate, when you give an agent a task with **New task**, and when an agent Routine fires. The board is the record of who is doing what. The chat only describes it. Use this page to understand what a card's status means, why a task is waiting, and when work restarts on its own. For the controls, see [Track work on the board](/using/board). ## The mental model A card moves through statuses, mostly from left to right: **Backlog** to **To do** to **In progress** to **Done**. One agent owns a card while it works on it. A card can wait on other cards. If the agent working on a card stops responding, the card goes back to To do, and Clawboo decides whether to run it again. ```mermaid theme={"theme":"github-dark"} flowchart LR BL[Backlog] --> TD[To do] --> IP[In progress] --> D[Done] IP --> NY[Needs you] NY --> TD ``` ## Statuses and columns | Status | What it means | Where the card shows | | - | - | - | | Backlog | Parked. Not ready to work yet. | Backlog | | To do | Ready, and waiting for its agent to start. | To do | | In progress | An agent is working on it. | In progress | | In review | The finished work is being checked. | In progress, with a **Verifying** badge | | Blocked | Stuck: a run failed or went quiet, a check could not pass it, or an agent marked it blocked. | Needs you | | Done | Finished. | Done | | Cancelled | Dropped. | Cancelled | Done and Cancelled are final: a card in either cannot move again. The board does not let you move a card into In progress or In review by hand, because each means an agent or a check is working on it. A task that reaches In progress some other way, with no agent and no run behind it, shows in Needs you as **Not started**. ## One agent per task A task is worked by one agent at a time. When an agent starts a task, it claims the card, and only one claim can win, even when several agents try at the same moment. The others see the card as taken and leave it. Each task also remembers which chat it came from: General or one of the team's rooms. That chat's coordinator runs it (see [Teams, rooms and runtimes](/concepts/teams-and-planes)). If the room was deleted, General runs it. ## Tasks that wait on other tasks A task can depend on other tasks. It becomes ready only when every task it depends on is Done, and ready tasks start in order of priority. A plan of hand-offs works this way: each step waits for the one before it. Clawboo refuses a dependency that would make a loop, because a loop could never finish. When a handed-off task fails, or you cancel a task, the tasks still waiting on it in To do or Backlog are cancelled too. That way they do not sit there forever. Whoever handed out the work is told which steps were dropped. ## Comments steer the work A comment you add to a task is a reply to the agent the task belongs to: the agent working on it, or the agent it is bound to while it waits. Write it in the board's task drawer, in a team chat's **Tasks** card, or in **Tasks & Routines** on the agent's page. What the reply does depends on where the task stands: * **In progress.** The reply goes into the running work. Clawboo Native, Claude Code, Codex and OpenClaw take it at their next step. Hermes pauses and resumes the same session with your reply as its next input. An older Codex CLI without `codex app-server` cannot take it mid-run, so it waits for the next run. * **Waiting on you, or in To do.** The reply starts the agent on the task again, as **Retry** does, so answering a blocked task needs no second click. Replies sent within 15 seconds of each other start one run. A plan step that still waits on an earlier task keeps the reply until it can start. * **Being checked, or in Backlog.** The reply waits in the agent's mailbox, which the agent reads when its next run starts. * **Done or Cancelled.** A finished task cannot run again, so the reply is saved but not sent. You can send it instead as a follow-up task for the same agent. Clawboo puts every reply it sends into the agent's mailbox first, so a reply that misses a run is read at the next one. Under each reply, a note says where it went. Clawboo passes on the first 1,000 characters. See [Steer a running task](/using/board#steer-a-running-task) for each case, and [Choose a runtime](/runtimes/index) for how each runtime takes a reply mid-run. ## Needs you **Needs you** is the board's first column. It holds everything that will not move without a person: pending approvals, and tasks that are stuck for one of these reasons. | Label | What happened | | - | - | | Failed | The run ended with an error. | | Timed out | The agent stopped responding and the run was ended. | | Stopped | You stopped the run. It will not restart on its own. | | Needs review | A check could not pass the work. | | Blocked | An agent marked it blocked. | | Unassigned | No agent is bound to it, so no one will pick it up. | | Not started | It is in progress, but no agent and no run is working on it. | Each card leads with the one action that gets it going: **Retry** runs it again, **Assign** hands a task with no agent to one, and **Review** or **Open** opens the task. Its **More** menu holds the rest, including **Dismiss**, which moves the task to Cancelled for good. Failed and Timed out show a count, such as **Failed 3×**, when it has happened more than once in a row. ## When work restarts by itself Clawboo starts a task in To do on its own when it is ready and bound to an agent. It holds back when you stopped the task's last run, or when its last 3 runs in a row failed or timed out. A successful run, or a Stop, resets that count. | What happened | Where the task goes | Does it run again by itself? | | - | - | - | | A handed-off task failed or went quiet in team chat | Blocked, shown in Needs you as **Failed** or **Timed out** | No. Whoever handed it off is told, and you can **Retry** it. | | The process running it died, or the Clawboo server restarted | To do | Yes, until 3 runs in a row have failed. Then it waits in Needs you. | | You pressed **Stop** | To do | No. It waits in Needs you as **Stopped**. | Clawboo notices a dead run in two ways. A running task proves it is alive every 30 seconds; after 3 minutes with no sign of life, the run is timed out and the task returns to To do. When the server starts, runs left over from before that have shown no sign of life for 3 minutes are marked failed, and their tasks return to To do. The limits for a teammate that goes quiet in team chat are on [How work is delegated](/concepts/delegation-and-orchestration). ## The board and the chat The board is the record of the work. Chat messages describe it, but ordinary chat text does not change the board: only hand-offs (a delegate tool call, or a `` or `` in a reply, see [How work is delegated](/concepts/delegation-and-orchestration)), Clawboo's own task handling, your moves on the board, and agents' task tools do. When the board changes through Clawboo's board API, which includes your moves and comments in the dashboard, Clawboo also posts a short system line into the agents' peer room. The dashboard does not show that room; see [Peer chat](/concepts/peer-chat). ## Where the work happens Most tasks run through team chat. An agent Routine that names no repository runs on a separate path (see [Two ways Clawboo runs agents](/intro/how-it-works#two-ways-clawboo-runs-agents)). Neither gets a git worktree of its own or an automatic check. Coding runtimes start in the team's folder (`~/.clawboo/workspaces/`), with their own approval prompts turned off. They can still read and change any file your user account can. A task lands in Done when its agent reports back. Runs that name a repository, started through the runtime run API or by an agent Routine (the repository is set through the API), get an isolated worktree for work that changes files. If the agent changed files there, the change is checked before the task can be Done. A worktree keeps file changes apart; it is not a sandbox. See [What agents can do](/concepts/permissions), [When work is checked](/concepts/verification) and [Worktrees and handoff](/concepts/worktrees-and-handoff). ## What you control * **Give an agent a task**: use **New task** on the board. It starts at once if the agent is free, and waits in To do otherwise. * **Move a card**: drag it, or change its status in the task drawer. Your move is final. Clawboo stops any run working on the task, and tells whoever handed it off when you finish or cancel it. The agent cannot move the card back: its own status changes are refused until a new run on the task starts. * **Steer a task**: reply on it in the board's task drawer, the team chat's **Tasks** card or the agent's page. * **Unstick a task**: use **Retry**, assign an agent, or **Dismiss** from Needs you. [Track work on the board](/using/board) has the steps for each. ## Limits * One agent works a task at a time. To split work, hand out separate tasks. * Agents that create tasks with Clawboo's task tools are capped: at most 24 child tasks under one parent (finished ones count) and 30 new top-level tasks in 5 minutes. Tasks you create on the board are not capped. See [Governance](/concepts/governance). * Most work runs without a worktree or a check, as described in [Where the work happens](#where-the-work-happens). > [accordion] Under the hood > > * **Tables.** The board is the `tasks`, `task_deps`, `task_comments`, `workspaces` and `execution_processes` tables in Clawboo's SQLite database. Every write goes through one data-access layer in `@clawboo/db`, and the legal status changes are declared once in [`@clawboo/board-core`](/reference/packages/board-core), which the server and the board UI both read. > * **Claiming.** A claim is a single conditional update that succeeds only for a `todo` task with no assignee, so a lost race returns `409` and is never retried. Any move to `todo` clears the assignee and the stored check result. > * **Recovery.** A boot pass reaps orphaned runs, and a sweep every 60 seconds times out runs whose task has not been touched for 3 minutes (`CLAWBOO_BOARD_STALE_TTL_MS`). The re-run rule is `MAX_AUTO_FIRES = 3` over each task's run ledger; a run that ended `cancelled` is never re-fired. Both passes put stuck work straight back to `todo`, outside the normal status rules. > * **Rooms.** Each task carries a `room_id`. The server's dispatcher fires ready tasks per chat, and a task whose room is archived goes to General. > > The claim statement, the write-contention recipe, the dependency walks and both recovery passes are described in [Board internals](/internals/board-internals). The REST surface is the [Board API](/reference/rest-api/board). ## Related * [How work is delegated](/concepts/delegation-and-orchestration): how a hand-off becomes a task and how results come back. * [Teams, rooms and runtimes](/concepts/teams-and-planes): which chat runs a task. * [When work is checked](/concepts/verification): which tasks get an automatic check before Done. * [Track work on the board](/using/board): the columns, the task drawer and every action. * [Board internals](/internals/board-internals): claims, contention and recovery for contributors. # How work is checked Source: https://docs.claw.boo/concepts/verification When Clawboo runs a test command and a reviewer on finished work, what each outcome means on the Board, and what is never checked. Clawboo can check an agent's work before the task counts as done. It runs the project's test command, and for bigger or handed-off changes a second agent reviews the change. These checks run in one narrow case: a task-runner run that worked in its own git worktree and changed files. Most work, including everything handed off in team chat, is never checked. ## When checks run Checks run only when all three of these are true: 1. **The task ran on the task runner.** That means an agent Routine whose task names a repository (set through the API), or a run started with `POST /api/runtimes/:id/run` and a `repoPath`. [How a team works](/intro/how-it-works) explains the two paths. A worktree completed through the board's workspace API is also checked, without a reviewer. 2. **The task changes files**, so it got its own git worktree. Research and review tasks get none. 3. **The agent changed something.** A run that changed no files lands in **Done** without a check. Nothing in the dashboard starts a checked run. A Routine you create in the app sends only its instructions, so it runs without a repository, and its work is not checked. ### Never checked * Work handed off in team chat, in General or a room, including the steps of a plan * Tasks you give an agent on the Board, and reruns from **Retry** or **Assign** * One-to-one chats * OpenClaw work. An OpenClaw Routine run gets a comment saying it completed without verification. * Routines created in the app * Research and review tasks * Tasks you move to **Done** yourself A task with no check result reaches **Done** normally. Unchecked is not the same as failed. ## What happens during a check 1. The agent's run ends. On the [Board](/using/board) the card stays in **In progress** with a **Verifying** badge. 2. Clawboo runs the verify command named in the worktree's `init.sh`, inside the worktree, and reads its exit code. The command gets 10 minutes. 3. If the command passes and the change is large or was handed off, a reviewer agent reads the committed change. 4. Clawboo stores the result on the task and moves the card to match. ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} flowchart TD A["Agent finishes"] --> B{"Changed files?"} B -->|No| D["Done, unchecked"] B -->|Yes| C["Run the verify command"] C -->|Fails| F["Fail"] C -->|Passes| R{"Large or handed off?"} R -->|No| P["Pass: Done"] R -->|Yes| V["Reviewer reads the change"] V -->|Blocking issue| F V -->|Nothing blocking| P F --> L{"Attempt left?"} L -->|"Yes: fix note"| A L -->|No| N["Needs you"] ``` ## The three outcomes | Outcome | When | What you see | | - | - | - | | **Pass** | The command passed, and the reviewer, if it ran, found nothing blocking | The card moves to **Done** | | **Fail, attempt left** | The command failed, or the reviewer found a blocking issue | The task goes back to the agent with a note: what failed, why, and how to fix it. The agent tries again | | **Fail, no attempt left** | The last attempt failed too. By default that is the second attempt | The card moves to **Needs you** with a comment, and whoever handed out the task (or the team's leader) gets a notice | In **Needs you**, the card reads **Needs review** while the test command still fails. When only reviewer findings are left, it reads **Blocked**. Each task gets two attempts by default: the first, plus one fix round. The `CLAWBOO_MAX_FIX_CYCLES` environment variable sets the number of fix rounds; `0` means a single attempt. ## The verify command in practice Clawboo does not know your project's test command. Each new worktree gets an `init.sh` whose verify line is a placeholder: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} VERIFY_CMD='echo "[init] configure VERIFY_CMD in init.sh"' ``` A placeholder counts as no command, and no command counts as a failure, because a check cannot pass work it never tested. So the first check fails unless the agent has already set a real command. The fix note tells the agent to set `VERIFY_CMD` in `init.sh`. If it does, and that command passes, the second attempt can pass. Otherwise the task ends in **Needs you**. > [!TIP] > > To get a useful check, say in the task which command proves the work, for example: "Before you finish, set `VERIFY_CMD` in `init.sh` to `npm test`." The agent that did the work can edit `init.sh`, so it can also weaken its own check. The reviewer is the independent part. ## When the reviewer runs The reviewer runs only after the test command passes, and only when at least one of these holds: * The task was handed out by another agent (it has a parent task). * More than 5 files changed. * More than 300 lines changed, counting lines added and removed. These thresholds are fixed. A small change on a task nobody handed out is judged by the test command alone. How the reviewer works: * **It is a second run of the same runtime.** It starts a fresh session, on a separate checkout of the committed change that has no branch to push. It gets none of the first agent's session or runtime home. * **It uses the same model** unless you set `CLAWBOO_REVIEWER_MODEL`, which picks a different model on that runtime. * **Only five kinds of finding block:** security, crash, data loss, wrong algorithm, and missing acceptance criteria. Style, performance and other notes are recorded and never block. * **It cannot hold the work back by failing.** If the reviewer fails to run, the test command alone decides. A reply Clawboo cannot read becomes one note that does not block. * **Its spend is not recorded.** Your provider bills the reviewer's run, but it does not count toward budgets or show in the cost views. ## What you can do * **Move a card while it is being checked.** If you move it to **Done** or **Cancelled**, your move wins. Clawboo keeps the check's result on the task but moves nothing. * **Accept the work anyway.** While the card is still in **In progress** with **Verifying**, move it to **Done**: if the stored result is a failure, the board asks you to confirm with **Complete anyway**, and Clawboo records the override in the audit log. Once the card is in **Needs you**, the dashboard cannot move it to **Done**; through the API, move the task to `in_progress` and then to `done` with `humanOverride` (see the [Board API](/reference/rest-api/board)). * **Run it again.** **Retry**, or another agent picked from the card's **More** menu, sends the task back to **To do** and clears its check result. That rerun goes through the team, not the task runner, so it has no worktree and is not checked. * **Drop it.** **Dismiss** moves the task to **Cancelled**. ## The failures checks are for | Failure | What Clawboo does | What you see | What you can do | | - | - | - | - | | The agent says it is done, but tests fail | Runs the verify command itself and trusts only its exit code | The task goes back for a fix, then to **Needs you** | Read the failure comment on the card | | The agent marks its own work | A reviewer in a fresh session reads large or handed-off changes | Blocking findings send the work back | Set `CLAWBOO_REVIEWER_MODEL` for a different model | | No test command is set | The placeholder counts as a failure | **Needs review** after the last attempt | Name the command in the task | | A fix loop that never ends | Two attempts by default, then stop | The card waits in **Needs you** | Retry, complete it anyway, or dismiss it | | A check that hangs | Stops the command after 10 minutes, with everything it started | Counts as a failure | Make the command finish faster | ## Limits * Only task-runner runs in a worktree are checked, and the dashboard cannot start one. * A check is only as strong as its command, and the agent can edit `init.sh`. * The reviewer runs on the agent's own runtime, with the agent's model unless you set `CLAWBOO_REVIEWER_MODEL`. * Checked work stays on its own branch, `clawboo/task-`. Clawboo never merges it; merging is up to you. * A check is not continuous integration: it runs one command, once per attempt, with no pipeline, cache or test matrix. > [accordion] Under the hood > > * **Where it runs.** `verifyTask` (`apps/web/server/lib/verification/index.ts`) is called only from `actOnTaskWorkspace` (`apps/web/server/lib/worktrees.ts`). The task runner calls it with a reviewer when a run completes (`apps/web/server/lib/executorRunner.ts`). `PATCH /api/board/:taskId/workspace` with `{"action": "complete"}` calls it without one, so that route never runs the reviewer. > * **The command.** The gate reads `VERIFY_CMD='...'` from `init.sh`, then the `- Verify:` line of `VERIFICATION.md`. A command containing `configure VERIFY_CMD` counts as none (`packages/governance/src/verify/commandParser.ts`). It runs through a shell in the worktree with Clawboo's secrets removed from its environment, keeps the last 4,000 characters of output, and refuses a worktree outside the Clawboo folder (`apps/web/server/lib/verification/deterministicGate.ts`). > * **The stored result.** Statuses are `pass`, `fail` and `completed_with_debt`, which is given at the last attempt. `PATCH /api/board/:taskId` refuses `done` with `409 verification_required` while a stored result blocks it, unless the request sets `humanOverride`, which writes a `verification` audit row. A stored result that cannot be parsed does not block, and `completed_with_debt` over a passing command does not block a later move to Done. > * **Skipped by Clawboo.** A run that changed nothing, and an OpenClaw Routine completion (an `unverified` audit row), both reach `done` without the gate. > * **The reviewer trigger.** `shouldRunCritic` in `packages/governance/src/verify/criticTrigger.ts`. It also accepts a `riskFlag`, which nothing sets. > * **Clearing.** Any move to `todo` clears the stored result. > > See [Executor runner](/internals/executor-runner) and the [Board API](/reference/rest-api/board). ## Related * [How a team works](/intro/how-it-works): which work runs on the task runner. * [Worktrees and handoff](/concepts/worktrees-and-handoff): the checkout a check runs in. * [Board](/using/board): Needs you, Retry and Dismiss. * [What agents can do](/concepts/permissions): why a worktree is not a sandbox. * [Environment variables](/reference/environment-variables): `CLAWBOO_MAX_FIX_CYCLES` and `CLAWBOO_REVIEWER_MODEL`. # Worktrees and handoff Source: https://docs.claw.boo/concepts/worktrees-and-handoff When a task gets its own git worktree, what Clawboo writes into it, how another runtime picks the work up, and how you get the result back. A **worktree** is a second checkout of your git repository, on its own branch, where one task's agent makes its changes. It keeps two tasks that work on the same repository from editing the same files at once. It leaves you a branch to review when the work is done. It also carries a few files that let a different runtime, or a person, pick the task up where it stopped. Clawboo makes a worktree only in a few cases, and a worktree is not a sandbox. This page covers when a task gets one, what is in it, how a handoff works, and how you get the work back. ## When a task gets a worktree A task gets a worktree only when the task runner runs it with a repository path: * an agent routine whose task template names a `repoPath`, which you set through the [Schedules API](/reference/rest-api/schedules); * a run started with `POST /api/runtimes/:id/run` and a `repoPath`. You can also create one ahead of time with `POST /api/board/:taskId/workspace`. The task runner uses it when it later runs that task. Everything else runs without a worktree: * **Team chat.** Chats, hand-offs and the Board tasks a team works on never get one, on any runtime. * **Routines made in the dashboard.** The Routines dialog has no repository field, so an agent routine made there runs as a research task, without a worktree. * **OpenClaw agents.** They work in their own workspace, which the OpenClaw Gateway manages. Clawboo never gives them a worktree. In the task runner, the task's kind decides too: | Kind | Gets a worktree | | - | - | | `code` (the default) | Yes | | `research`, `review` | No | | Any other value | Yes | A task-runner run of a kind that needs a worktree, with no repository path, is refused rather than run somewhere else. The task gets the comment `[blocked: no isolation] This task changes files, so it needs an isolated git worktree, but no repository path was provided for it.` and goes back to To do. > [!WARNING] > > **Without a worktree, coding agents start in their team's folder.** In team chat, and in any other run without a worktree, Claude Code, Codex, Hermes and Clawboo Native start in `workspaces/` inside your Clawboo folder (`~/.clawboo`, or `CLAWBOO_HOME` if you set it). Every member of the team shares it, and a task or an agent with no team uses `workspaces/_no-team`. The folder only sets where Claude Code, Codex and Hermes start: they run with their own approval prompts turned off, so they can read and change any file your user account can, Clawboo's database and saved keys included. See [What agents can do](/concepts/permissions). ## What a worktree is * **A separate checkout.** It lives in your Clawboo folder, at `worktrees//`. Your own checkout and its working files are not touched, though `git worktree list` and `git branch` in your repository show the extra checkout and its branch. * **Its own branch.** The branch is `clawboo/task-`, cut from your repository's current `HEAD`. A caller that creates the worktree through the API can name another commit or ref. * **A starting commit.** Before the agent starts, Clawboo commits five bookkeeping files to the branch, with the message `clawboo: scaffold task `. The agent's changes are measured from that commit, so the bookkeeping never counts as work. A worktree keeps one task's file changes apart from another's. It does not limit what the agent's process can do: the process runs with your user account's permissions, and it can read files outside the checkout, reach the network and start other programs. [What agents can do](/concepts/permissions) covers what each runtime can reach. ## The files in a worktree Each file answers a question that a runtime picking the task up cold needs answered from the checkout alone: | File | What it holds | | - | - | | `TASK.md` | The task's title, id and description, sections for acceptance criteria and known gotchas, and how to work the task | | `task-progress.md` | Where things stand: what is done, in progress and blocked | | `DECISIONS.json` | Decisions made along the way, and why | | `init.sh` | The install, verify and start commands | | `VERIFICATION.md` | The verify command and the evidence that the work is done; the verification check adds its results here | | `AGENT_HANDOFF.json` | Written when a run finishes: what is done, what is broken or unverified, and the next step | Clawboo writes the first five when it creates the worktree. `AGENT_HANDOFF.json` appears when the first run in it finishes successfully. `init.sh` starts with placeholder commands that only print a reminder to set them. Clawboo does not fill them in: the agent, or you, has to replace them with real ones. Until the verify command is set, the verification check cannot pass the task. See [Verification](/concepts/verification) for when that check runs. ## The cross-runtime handoff When a task-runner run in a worktree finishes successfully, Clawboo writes `AGENT_HANDOFF.json` from the run's report. It records who handed off and on which runtime, the run's report (as what is done, or as the next step when the run pauses for another runtime), anything broken or unverified, and how to re-enter the work. If an earlier attempt on the task was interrupted, the handoff says so, so that the next runtime checks before it redoes anything with side effects. The next run on that task, on any runtime, starts from the checkout alone: 1. Clawboo reads `AGENT_HANDOFF.json`, or `task-progress.md` when there is no handoff yet, and adds what is done, what is broken and the next step to the run's prompt. 2. `TASK.md` tells the agent to run `./init.sh` to confirm the starting point, then carry on from the next step. Picking the work up needs no chat history and no access to the board. That is what lets a task move from one runtime to another, for example from Claude Code to Codex, or to a person. * A run started through the API with `keepForResume: true` writes the handoff, keeps the worktree, and puts the task back in To do, ready for a different runtime to pick up. * The handoff also stores the runtime's own session id. It is used only when the next run is on the same runtime, to resume that session. A different runtime works from the handoff alone. * A person can write a handoff too, with `POST /api/board/:taskId/workspace/handoff`. Its `runtime` field can be `human`. The [Cross-runtime handoff guide](/guides/cross-runtime-handoff) walks through it step by step. ## Getting the work back Clawboo never merges. When a run in a worktree finishes, what happens next depends on whether it changed anything besides the bookkeeping files: * **It changed files.** The worktree and the `clawboo/task-` branch are kept, and the task goes to verification. Review the work and merge it into your own branch yourself. * **It changed nothing.** The worktree and its branch are removed, and the task lands Done without a verification check. Changes can still be uncommitted in the worktree folder when a run ends. Clawboo commits them to the branch when the verify command passes on a task-runner run, when a worktree is paused, and before a cleanup removes the folder. To see the work in the app, open the **Workspace** tab on the agent's page, or the **Workspace** section of the task on the Board, which shows the changed files and the diff. ### Cleanup When the Clawboo server starts, it removes worktree folders that are older than 72 hours, or beyond the newest 25 for a repository. Tasks that are in progress or in review are skipped. Before removing a folder, Clawboo commits any uncommitted work to the branch. The branch itself is kept, so nothing is lost: if the task runs again, Clawboo rebuilds the checkout from the branch. Branches are never deleted by cleanup. Delete the `clawboo/task-` branches you no longer need with git. > [accordion] Under the hood > > * Clawboo's commits on a task branch use the messages `clawboo: scaffold task `, `clawboo: checkpoint task ` (after the verify command passes, before the reviewer), `clawboo: pause task ` and `clawboo: gc autosave task `. > * `PATCH /api/board/:taskId/workspace` with `{ "action": "pause" }` commits the work, removes the folder and keeps the branch. `{ "action": "complete" }` runs the empty-or-changed decision above and, for changed work, the verify command without the reviewer. `POST /api/board/:taskId/workspace` refuses a `research` or `review` kind with `422 no_isolation`. > * The reviewer works in a detached checkout at the committed work, under `reviews/` in your Clawboo folder. The checkout has no branch of its own. > * If `AGENT_HANDOFF.json` cannot be read or parsed, Clawboo treats it as missing and falls back to the `## Done` and `## Blocked` sections of `task-progress.md`. > * Every git call Clawboo makes has a 60-second timeout. > * The lifecycle functions are documented in [@clawboo/worktrees](/reference/packages/worktrees), and the run loop in [Executor runner](/internals/executor-runner). ## Related * [What agents can do](/concepts/permissions): what each runtime can reach, with or without a worktree. * [Verification](/concepts/verification): when finished work is checked, and what a failing check does. * [Cross-runtime handoff guide](/guides/cross-runtime-handoff): pause a task on one runtime and resume it on another. * [How routines run](/concepts/scheduling): why routines made in the dashboard get no worktree. * [Board API](/reference/rest-api/board): the `/api/board/:taskId/workspace` routes. # Tour the dashboard Source: https://docs.claw.boo/getting-started/dashboard-tour A map of the Clawboo dashboard: the team rail, the agent column, the sidebar nav, Settings, team chats, the Org chart and agent pages. Use this page to find your way around. The dashboard is one browser window: a sidebar on the left, and the page you are working on to its right. When you open Clawboo, it starts on the Org chart. ![The Clawboo dashboard: on the left the team rail and the agent column with the team's agents, its General chat and the nav; in the middle a team chat; on the right the Team graph and Tasks cards](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-space.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c6bed9e50135bba92df482a1ddf6cd8d) ## The sidebar The sidebar has two parts: a narrow team rail on the far left, and the agent column next to it. ### Team rail * **Boo Zero** (the red mascot at the top) opens Boo Zero's own page, where you talk to the leader of all your teams. Right-click it and choose **Show all agents** to open the Org chart. * **One icon per team.** Click an icon to open that team's **Home**. Right-click it for **Archive**, **Refresh Protocol**, **Delete team only** and **Delete with agents**; [Teams](/using/teams) explains each. Archiving takes a team off the rail, which puts its chats and Home out of reach. Nothing is deleted, but the dashboard has no list of archived teams to bring one back from. * **+** (**Create a team**) opens the Marketplace's **Teams** tab, where you deploy a ready-made team or start one from scratch. ### Agent column From top to bottom: * **The team's name** (or **All Agents** when no team is selected), a search for agents, and the button that hides the sidebar. * **The team's agents**, each drawn as a Boo. A pulsing green dot marks an agent that is working and a red dot one with an error. Boo Zero comes last. Click an agent to open its page. * **The team's chats:** **New room**, then **General**, the chat with the whole team, then any rooms you made. A room is a smaller group of teammates with its own chat; see [Team chat](/using/team-chat). * **Create Boo** adds an agent to the team. It runs on OpenClaw while an OpenClaw Gateway is connected, and on Clawboo Native otherwise. * **The nav**, described below, and the theme switch (**System**, **Light**, **Dark**). Drag the agent column's edge to resize it; double-click the edge to reset it. To hide the whole sidebar, drag the edge far to the left, click the hide button, or press `Cmd/Ctrl + \`. While it is hidden, a button at the start of each page's header brings it back, and hovering that button shows the sidebar without docking it. ### The nav | Item | What it opens | Shortcut | | - | - | - | | **Home** (when a team is selected) | The team's Home page, a placeholder. To work with the team, open **General** | None | | **Org chart** | Every team on one live chart, under Boo Zero | `Cmd/Ctrl + 1` | | **Board** | Every task by status on **Kanban**, or by day in **Today**, **Week** and **Month**, with anything waiting on you in **Needs you** | `Cmd/Ctrl + 2` | | **Connectors** | Apps and tools your agents can use, such as GitHub, Gmail or a local folder | `Cmd/Ctrl + 3` | | **Memory** | The team memory, as a graph and a list | `Cmd/Ctrl + 4` | | **Marketplace** | Ready-made agents, teams and skills | None | | **Settings** | Everything else, in one window (below) | `Cmd/Ctrl + ,` | There is no separate approvals page. Requests that wait for your OK appear in **Needs you** on the Board and in a tray above the chat's message box; see [Approvals](/using/approvals). ## Settings **Settings** opens a window with eleven items in three groups. **Search settings** at the top finds an item by name, or by words such as "api key" or "cron". | Group | Item | What it's for | | - | - | - | | Workspace | **Providers** | Add, check and remove AI provider keys, and sign in with your ChatGPT subscription | | Workspace | **Runtimes** | Connect and manage Clawboo Native, OpenClaw, Claude Code, Codex and Hermes | | Workspace | **Memory** | The team memory as a list (the sidebar's **Memory** adds the graph) | | Workspace | **Capabilities** | The tools, skills and connectors your agents can use | | Workspace | **Routines** | Work that runs on a schedule, for a team or for one agent | | Insights | **Fleet** | All agents at a glance: 24-hour task and verify pass rates, spend, health per runtime, recent issues | | Insights | **Tokens Used** | Token counts by team and agent, for every runtime | | Insights | **Observability** | Traces, errors and health views from Clawboo's event log | | Insights | **Governance** | Budgets, limits and the audit log | | System | **System** | OpenClaw's default command approval (while a Gateway is connected), OpenClaw and Node.js versions, and **Check for Updates**, which updates OpenClaw, not Clawboo | | System | **System Health** | Startup checks, with a way to run them again | [Providers and runtimes](/using/providers-and-runtimes) walks through the first two. To update Clawboo itself, see [Install](/getting-started/installation). ## Team chats Click **General** or a room to open a team chat: the conversation on the left, cards on the right. The header shows the chat's members (in a room, click them to **Edit room members**), a gear for the team's **brief & rules**, and one button per card: * **Browser**: the page each of the chat's agents has open in its web browser. Every new agent gets the built-in browser connectors. * **Team graph** (**Room graph** in a room): the team, with a live status on each agent. * **Tasks**: the chat's tasks in **Needs you**, **In progress**, **To do**, **Done** and **Cancelled**. Open a task to read its comments, steer it, or open it on the Board. * **Activity**: a live feed of the agents' tool calls, results and errors. The graph and **Tasks** start open. Your browser remembers which cards you keep open and how you sized them. The first time you open a team's chat, a one-time introduction screen comes first; see [Work with your first team](/getting-started/first-team). ## The Org chart and a team's graph The **Org chart** shows every team at once, with Boo Zero at the top. It opens in a radial layout; switch to **Tree** at the top right. A single team's graph is the **Team graph** card beside its chat. [Org chart](/using/org-chart) covers both, including how to build a team on the canvas. ![The Org chart in its Tree layout with team halos on: Boo Zero at the top, and each team's agents in a labeled row beneath it](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/ghost-graph.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=34b71bf37bb52bcf2ad5ea30a3ffa335) ## The agent page Click an agent to open its page: its chat on the left, and cards on the right that you turn on and off from the chat's header. * **Graph**: the agent with its skills, connectors and model. Change the model from the card's top corner. * **Tasks & Routines**: the agent's tasks, which you can open and steer, and the routines that send it work, with **New routine**. * **Browser** and **Activity**: as in a team chat. * **Details**: **Workspace**, **Personality**, **Permissions** (on OpenClaw and Clawboo Native agents), **Brief** (on Boo Zero only) and **Agent Files**, its SOUL.md and other files. Which files an agent follows depends on its runtime. **Graph** and **Details** start open. [Agents](/using/agents) covers each card. In Boo Zero's own chat, start your message with `@` and a team's name, or click the team's chip above the message box, to bring that team's brief into your message; see [Boo Zero](/using/boo-zero). ![An agent page: the chat on the left, the Graph card at the top right with the agent's skills, connectors and model, and the Details card below it with its section pills and the personality presets and sliders](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/agent-detail.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=38162b571ca208c4b6fe39de92c24f72) ## Keyboard shortcuts | Keys | What they do | | - | - | | `Cmd/Ctrl + 1` to `4` | Open the Org chart, Board, Connectors or Memory | | `Cmd/Ctrl + ,` | Open Settings | | `Cmd/Ctrl + \` | Hide or show the sidebar | | `Escape` | Close Settings; otherwise go from an agent page, Boo Zero's page, a team's Home or a team chat to the welcome screen | Shortcuts other than `Cmd/Ctrl + \` don't fire while you type in a text field. The welcome screen has buttons for the Org chart, the Marketplace and **Cost**, which opens **Tokens Used**. [Keyboard shortcuts](/reference/keyboard-shortcuts) also lists the Board and graph keys. ## Next steps * [Work with your first team](/getting-started/first-team): give a team a job and find the results. * [Team chat](/using/team-chat): rooms, mentions and clearing a chat. * [Board](/using/board): tasks, **Needs you** and steering. * [Org chart](/using/org-chart): the all-teams chart and each team's graph. * [Agents](/using/agents): the agent page, models, personality and files. * [Keyboard shortcuts](/reference/keyboard-shortcuts): every shortcut in one place. # Work with your first team Source: https://docs.claw.boo/getting-started/first-team Give your team a job, watch Boo Zero split it into tasks, find the results, and steer, approve or stop the work. Use this page once you have a deployed team, for example from the [Quickstart](/getting-started/quickstart). You'll open the team chat, give the team a job, watch it work, find the results, and steer or stop the work. ## Before you begin * **A deployed team.** Deploy one in the [Quickstart](/getting-started/quickstart), or click **+** (**Create a team**) at the bottom of the left rail to pick one from the Marketplace. * **A connected AI provider or runtime**, so the agents can answer. ## Steps **1. Open the team chat** Click the team's icon on the left rail to open its Home, then click **General** below the team's agents. General is the chat with the whole team. The first time you open a team's chat, a one-time introduction comes first: 1. **Meet your team** shows the agents and the leader. Click **Get Started**. 2. **Introduce yourself:** write who you are and what you're working on (at least 5 characters), then click **Continue to Team Space**. Clawboo saves it to the team's context. A team you deployed during setup skips the first screen. [Team chat](/using/team-chat) covers this screen, rooms and clearing a chat. **You should see** the chat on the left, with the **Team graph** and **Tasks** cards on the right. **2. Give the team a job** Type your request in the message box and press Enter (Shift+Enter starts a new line). Your message goes to Boo Zero, which decides who should do what. To talk to one agent instead, start the message with `@` and its name; the message is delivered exactly as you typed it. Ask for work with clear parts and a clear result. For example: ```text theme={"theme":"github-dark"} Write a welcome email series for new customers of our budgeting app: three short emails with subject lines, and a note on when to send each one. ``` ```text theme={"theme":"github-dark"} Turn these meeting notes into a short summary, a list of decisions, and a task list with an owner for each task: (paste your notes here) ``` A quick question gets a direct answer. Work with several parts is split into tasks. **You should see** task cards appear in the chat as Boo Zero hands out the parts. **3. Watch the team work** Each hand-off becomes a task on the board, assigned to one teammate. You can follow it in three places: * **Task cards in the chat.** Each card names its teammate and shows a status that moves from **Queued** to **Working** to **Done**. A task that needs you shows why instead, such as **Failed** or **Stopped**. * **The Tasks card** beside the chat sorts this chat's tasks into **Needs you**, **In progress**, **To do**, **Done** and **Cancelled**. * **The Team graph card** shows the team, with a live status on each agent. The [Board](/using/board) holds every task from every team, by status and by day. Team chat runs on the Clawboo server, so the work continues if you close the browser tab. **4. Find the results** * **Boo Zero's answer.** When the teammates finish, Boo Zero posts one summary in the chat. * **Each teammate's answer** is saved on its task. Open **Comments & activity** on the task card in the chat, or open the task on the Board, and read the teammate's comment. * **Files.** Agents return their results as text. Claude Code, Codex, Hermes, Clawboo Native and OpenClaw agents can also write files. In team chat, Claude Code, Codex, Hermes and Clawboo Native start in a folder the whole team shares (`~/.clawboo/workspaces/`), which each agent's **Workspace** tab shows. Claude Code, Codex and Hermes can write outside it too, so name the full folder path you want when you ask for files somewhere else. OpenClaw agents work in their own OpenClaw workspace. [What agents can do](/concepts/permissions) explains what each runtime may touch. ![Boo Zero's summary in a team chat: it reports what three teammates produced for a pricing page and what is still open, with the Team graph and Tasks cards on the right](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/leader-synthesis.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=a780a23f9ba8a5c4c2d2de9ee0e37f8b) **5. Steer, approve or stop** * **Steer a running task.** Open the task in the **Tasks** card, write in **Comment or steer this task…** and send. The agent working on it reads your comment while it works, and a task that has not started yet gets it when it does. [Choose a runtime](/runtimes/index) shows how each runtime takes a comment. * **Approve.** Some actions wait for your OK. They appear in **Needs you** on the Board and in a tray above the message box; see [Approvals](/using/approvals). Claude Code, Codex and Hermes use their own built-in tools without asking. * **Stop.** While the team works, the send button turns into **Stop**. Stop ends the team's runs in this chat and drops hand-offs that have not started. Tasks that were running wait in **Needs you** as **Stopped** and don't restart on their own; retry them from there when you're ready. ## How Boo Zero hands out work Boo Zero answers what it can and hands out the rest. Each hand-off becomes a task on the board, assigned to a teammate. When the teammate finishes, its result is saved as a comment on the task and passed back to the agent that handed it out. Boo Zero then writes your answer. > [accordion] Under the hood > > The leader hands off a job with a structured signal, never by Clawboo guessing from its prose. The signal depends on the runtime the leader runs on: > > | Leader runs on | How it hands off a job | > | - | - | > | Clawboo Native | Calls its `delegate` tool | > | Claude Code, Codex or Hermes | Calls the `team_delegate` tool on Clawboo's TeamChat server | > | OpenClaw | Writes a `` block in its reply | > > Clawboo limits how deep hand-offs can nest and how many one turn can send out, and stops sending a task to a teammate that keeps failing it. [Limits and defaults](/reference/limits-and-defaults) has the numbers, and [How a team works](/intro/how-it-works) follows the whole path. ## Good to know * **Team chat work is not checked automatically.** Review the results, or add a reviewer agent to the team and ask Boo Zero to have it check the work. See [How work is checked](/concepts/verification). * **A team costs more tokens than one agent.** Boo Zero, each teammate and the final summary are separate model runs. See [Budgets and spend](/using/budgets-and-spend). ## Next steps * [Team chat](/using/team-chat): rooms, mentions and clearing a chat. * [Teams](/using/teams): change a team's members, leader, brief and rules. * [Board](/using/board): every task, steering comments and **Needs you**. * [Routines](/using/routines): give the team work on a schedule. * [Tour the dashboard](/getting-started/dashboard-tour): find your way around the rest of the app. # Install, update and remove Clawboo Source: https://docs.claw.boo/getting-started/installation What Clawboo needs, how to run it with npx or a global npm install, what the clawboo command starts, and how to stop, update or uninstall it. Clawboo installs from npm and runs as a small server on your computer that you use in your browser. This page covers what you need, the two ways to run it, what the `clawboo` command does, and how to stop, update and remove it. To go straight to a working team, follow the [Quickstart](/getting-started/quickstart). > [!NOTE] > > These docs track the latest code. The latest npm release is 0.5.0, so some features described here arrive with the next release; see the Unreleased section of the [changelog](/appendices/changelog). ## Requirements * **Node.js 22.12 or newer.** Check with `node --version`. On an older Node, `clawboo` stops right away with a message naming the version it found. Get Node from [nodejs.org](https://nodejs.org) or a version manager such as nvm or fnm. * **A terminal and a web browser.** You type one command; everything after that happens in the browser. * **An AI provider, chosen during setup:** an API key, a local Ollama, or a ChatGPT subscription. You don't need one to install. * **Only if you add OpenClaw teammates:** the OpenClaw release Clawboo installs needs Node 24.22.1 or later on the 24 line, or 26.12.1 or later. Node 22 and Node 25 cannot run it. Clawboo does not check this before it installs OpenClaw, so compare `node --version` first. There is no Clawboo account, Docker image or database to set up. ## Run Clawboo > [tab] Try it with npx > > ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} > npx clawboo@latest > ``` > > Keep `@latest`: a bare `npx clawboo` can reuse an older copy from npm's cache. An npx run cannot update itself from the dashboard; see [Update Clawboo](#update-clawboo). > [tab] Install it globally > > ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} > npm install -g clawboo > clawboo > ``` > > A global install gives you a lasting `clawboo` command and one-click updates from the dashboard. On a first start the terminal shows: ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} ✔ Dashboard started ✔ Clawboo opened at http://localhost:18790 ``` Your browser opens on the setup wizard. Continue with the [Quickstart](/getting-started/quickstart). ### Add Claude Code teammates (optional) The Claude Code runtime needs the Claude Agent SDK, which the `clawboo` package does not include because it would add about 210 MB to every install. Install the two together, globally: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} npm install -g clawboo @anthropic-ai/claude-agent-sdk ``` To check it, run `npm ls -g @anthropic-ai/claude-agent-sdk`: it lists the package when it is installed. A one-off `npx` run cannot load the SDK. Then connect Claude Code in **Settings → Runtimes**; see [Claude Code](/runtimes/claude-code). ## What the clawboo command does 1. Checks your Node.js version. 2. Says whether an OpenClaw Gateway answers on its default port (18789). This is information only; you don't need a Gateway. 3. Finds the Clawboo server if it is already running, or starts it. If the running server is older than the command you ran, it offers to restart it on the new version. 4. Opens your browser at the dashboard. **The server keeps running after you close the terminal.** Run `clawboo` again to reopen the dashboard: it finds the running server instead of starting a second one. | Detail | Value | | - | - | | Address | `http://localhost:18790`, or the next free port up to 18809 | | Who can connect | Only this computer: the server listens on 127.0.0.1 unless you set `HOST` | | Server log | `~/.clawboo/server.log`, which rolls over to `server.log.1` past 5 MB | | Fixed port | `CLAWBOO_API_PORT=18800 clawboo` uses exactly that port and fails if something holds it | The [CLI reference](/reference/cli) covers how the command finds the server, and every flag. ## Stop and restart ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} clawboo stop clawboo restart ``` * `clawboo stop` stops the server. Running it when nothing is running is safe. * `clawboo restart` stops the server, starts a fresh one on the same port and opens the browser. Add `--no-open` to skip the browser. * `clawboo backup` saves a one-file copy of the database in the folder you run it from, even while the server runs. If you run Clawboo with npx, use `npx clawboo@latest stop` and so on. ## Update Clawboo When a newer version is on npm, an **Update available** chip appears at the bottom left of the dashboard. * **Global install:** click the chip (**Click to update**). Clawboo installs the new version, restarts the server and reloads the page. * **npx run:** click the chip (**Copy command**) to copy `npx clawboo@latest`, then run it in a terminal. It notices the older server still running and offers to restart it. To update a global install from a terminal instead: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} npm install -g clawboo@latest clawboo ``` The second command offers to restart the older server; `clawboo -y` restarts it without asking. Your data stays where it is: a newer version adds what it needs to your existing database when it opens it. ## Uninstall 1. Optional: in **Settings → Providers**, click **Disconnect** on each key you added there. Keys saved in that panel are also written in plain text to OpenClaw's `~/.openclaw/.env` and to each OpenClaw agent's key file, and **Disconnect** removes every copy. 2. Stop the server and remove the package with the two commands below. 3. To delete your data as well, remove the `~/.clawboo` folder. ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} clawboo stop npm uninstall -g clawboo ``` > [!WARNING] > > Deleting `~/.clawboo` permanently removes your teams, tasks, chats, memory, settings and saved keys. To keep a copy of the database, run `clawboo backup` before you uninstall. If you only used npx, there is no global package: run `npx clawboo@latest stop`, then remove `~/.clawboo` if you want. OpenClaw, Codex, Claude Code and Hermes are separate programs, and uninstalling Clawboo leaves them in place. ## Where your data lives Clawboo keeps its data in `~/.clawboo` (set `CLAWBOO_HOME` to use another folder): the database, settings, the encrypted key vault and the server log. Setting up OpenClaw, and saving keys under **Settings → Providers**, also write to OpenClaw's `~/.openclaw` folder. [Data and state](/operating/data-and-state) lists every file and covers backup and reset. ## Troubleshooting > [accordion] Clawboo requires Node.js 22.12 or newer > > Your Node is too old for Clawboo. Install Node 22.12 or newer from [nodejs.org](https://nodejs.org), or switch with a version manager (`nvm install 22` or `fnm use 22`), then run the command again. > [accordion] Dashboard is taking too long to start. > > The first start can be slow, especially on Windows while antivirus software scans the new files. The command waits up to 45 seconds. When it gives up, it prints the last lines of `~/.clawboo/server.log` and the log's full path: read the log for the reason, fix it, then run `clawboo` again. > [accordion] Could not find the Clawboo server. > > The installed package is incomplete. Run `npx clawboo@latest`, or reinstall with `npm install -g clawboo`. > [accordion] A Clawboo server is running on port N, but it requires an access token. > > You set `STUDIO_ACCESS_TOKEN` to protect the dashboard, so the command cannot talk to the server. Open `http://localhost:18790/?access_token=YOUR_TOKEN` once in your browser (use the port the message names), or unset the variable and restart the server. `clawboo stop` and `clawboo restart` cannot confirm a protected server either; they print a command that stops it by hand. > [accordion] The dashboard opened on an unexpected port > > If your shell sets a `PORT` variable, Clawboo uses that port instead of 18790. Unset it, or choose a port with `CLAWBOO_API_PORT`. The address Clawboo prints is always the right one. > [accordion] The update chip says the update failed > > The chip shows npm's own reason. A permission error means npm cannot write to its global folder, which is common with a system-wide Node. The chip then suggests running the update with `sudo` in a terminal. To avoid `sudo`, install Node with a version manager such as nvm or fnm, which keeps global packages in your home folder, and install Clawboo again under it. If the update installed but the page did not come back, run `clawboo restart`. For other problems, see [Troubleshooting](/help/troubleshooting). ## Next steps * [Quickstart](/getting-started/quickstart): connect an AI provider and deploy your first team. * [Data and state](/operating/data-and-state): every file Clawboo keeps, backup and reset. * [CLI reference](/reference/cli): every command and flag. * [Remote access](/guides/self-host-securely): reach Clawboo from another machine. # Quickstart Source: https://docs.claw.boo/getting-started/quickstart Start Clawboo, connect an AI provider with an API key, a ChatGPT sign-in or Ollama, deploy a ready-made team and give it its first job. In this quickstart you start Clawboo, connect one AI provider, deploy a ready-made team and give it its first job. After the first command, everything happens in your browser. ## Before you begin * **Node.js 22.12 or newer.** Run `node --version` to check. [Install](/getting-started/installation) covers Node and the other ways to run Clawboo. * **One way to reach an AI model:** * an API key from OpenAI, Anthropic, OpenRouter, Google, xAI, Groq, Mistral, Together, Cerebras or Moonshot, * a ChatGPT subscription, or * [Ollama](https://ollama.com) running on this computer with a chat model pulled. * **No Clawboo account.** Clawboo is free; the provider you connect bills the model usage. ## Steps **1. Start Clawboo** Run this in a terminal: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} npx clawboo@latest ``` Clawboo starts a local server and opens your browser. Prefer a lasting `clawboo` command? Install it globally instead; see [Install](/getting-started/installation). **You should see** the welcome screen, "Your AI agents, visible.", with a **Get Started** button. Click it. **2. Connect your AI provider** The **Connect your AI provider** screen sets up the AI your team runs on. Pick one path: > [tab] API key > > 1. Click **OpenAI**, **Anthropic** or **OpenRouter**, or open **More providers** for Google, xAI, Groq, Mistral, Together, Cerebras or Moonshot. For OpenAI, also click the **API key** card: the screen starts on **Sign in with ChatGPT**. > 2. Paste your key into **API Key**. No key yet? **Get a key** opens the provider's key page. > 3. Optional: choose the **Model** for your team's lead. Teammates you add default to a faster, cheaper model. > 4. Click **Continue**. Clawboo checks the key with the provider (**Verifying…**), then saves it in its encrypted vault on your computer (**Connecting…**). > > ![The Connect your AI provider screen with OpenRouter selected: the provider cards, the API key field and the model picker for the team's lead](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/onboarding-connect-provider.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=ed89bb116c838cbd989115481e9227d7) > [tab] Sign in with ChatGPT > > 1. Keep **OpenAI** and **Sign in with ChatGPT** selected. Both are selected when the screen opens. > 2. If the panel asks you to install the Codex CLI first, click **Install Codex**. > 3. Click **Sign in with ChatGPT** and finish signing in on the page that opens in your browser. Already signed in to Codex? Click **Already signed in? Re-check**. > 4. When the panel says your team will run on your ChatGPT subscription, click **Continue**. > > Your team runs on Codex, with no API key. This path has no model picker. > [tab] Ollama > > 1. Make sure Ollama is running and has a chat model. If it has none, run `ollama pull llama3.2`. > 2. Click **Ollama**. > 3. Choose a **Model** from the ones your Ollama has pulled. It runs every agent on the team, the lead and the specialists alike. Skip models marked "(can't use tools)": agents use tools on most turns. > 4. Click **Continue**. > > No key is needed, and the model runs on your computer. **You should see** the **Add more runtimes** screen. If a check fails, the reason appears on the screen instead; see [If something goes wrong](#if-something-goes-wrong). **3. Skip the extra runtimes** **Add more runtimes** lists Codex, Claude Code, Hermes and OpenClaw. Connecting them is optional, and you can do it later from **Settings → Runtimes**. Click **Continue**. If you want them later: Claude Code also needs the Claude Agent SDK installed with Clawboo (see [Install](/getting-started/installation)), and **Set up OpenClaw** installs and starts a local OpenClaw Gateway for you (see [OpenClaw](/runtimes/openclaw)). **You should see** **Choose your first team**. **4. Deploy a team** The **Create a team** window opens by itself. If you close it, **Browse teams** brings it back. 1. Find a team that fits your work, such as a marketing or research team, and click **Deploy** on its card. **Details** shows who is on it first. 2. In **Customize team**, change the name, badge or colors if you like. Every teammate runs on what you connected in step 2 unless you pick another runtime or model for it here. 3. Click **Deploy team**. **You should see** a progress count while each agent is created, then **Team deployed!**, then **Your team is ready** with your agents and a "Led by" badge naming Boo Zero, the leader that coordinates the team. On the ChatGPT path, one of the team's own agents takes the Boo Zero role. **5. Introduce yourself** Click **Open my dashboard**. Your team's chat, **General**, opens on a one-time introduction screen. Write a line about who you are and what you're working on (at least 5 characters), then click **Continue to Team Space**. Clawboo saves it to the team's context. **You should see** the team chat, with your introduction and a short acknowledgement from Boo Zero. The first time, a tour may offer to show you around; **Skip tour** closes it. **6. Give your team its first job** Paste this into the message box at the bottom, or write your own request, and press Enter: ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} Plan a launch for our new budgeting app: write a short announcement, draft three social posts, and list five questions customers will ask first. ``` **You should see** task cards appear in the chat as Boo Zero hands the parts to teammates. Each card moves from **Queued** to **Working** to **Done**, and the **Tasks** card beside the chat lists the same tasks. When the teammates finish, Boo Zero posts one summary of what the team produced. A quick question gets a direct answer instead of tasks. If Boo Zero answers a request with several parts by itself, ask it to hand the parts to the team. ## What you should see Your team's chat on the left, with the **Team graph** and **Tasks** cards on the right. Task cards in the chat show who is working on what, and each one opens on the [Board](/using/board). ![A team chat in Clawboo: teammates' updates and two task cards marked Working on the left, and on the right the Team graph card, with Boo Zero above the team lead and four specialists, and the Tasks card, with one task that needs you and five in progress](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-space.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c6bed9e50135bba92df482a1ddf6cd8d) Every message uses your provider's tokens, and a team's hand-offs add model runs of their own. [Budgets and spend](/using/budgets-and-spend) shows how to watch spend and set a limit. ## If something goes wrong > [accordion] Invalid API key. > > The provider rejected the key. Copy it again from the provider's key page, check that you picked the matching provider, and click **Continue** again. > [accordion] Could not reach the provider > > The message reads like "Could not reach openai." This computer could not reach the provider within 8 seconds, often because of a proxy, a firewall or no network. Fix the connection and try again. If you are sure the key is right, **Continue anyway** saves it without the check. > [accordion] Ollama isn't answering. > > Start Ollama (open the Ollama app, or run `ollama serve`), then click **Continue** again. Don't have Ollama? The screen links to its download. > [accordion] Ollama is running, but has no chat model yet. > > Run `ollama pull llama3.2`, then click **Check again**. Embedding-only models can't run agents. If the screen says Ollama doesn't have the model you picked, run the `ollama pull` command it shows, then click **Check again**. > [accordion] Install Codex fails > > The panel shows the install output and the reason. A permission error means npm can't write to its global folder: run the `sudo` command the message suggests in a terminal yourself. Then click the **API key** card and **Sign in with ChatGPT** again, so the panel checks for Codex once more. > [accordion] The browser didn't open > > Open the address the terminal printed after "Clawboo opened at", usually `http://localhost:18790`. > [accordion] The team doesn't answer > > Look for an error message in the chat first; it usually names the problem. Then check that your provider is still connected: a key or your ChatGPT subscription in **Settings → Providers**, or Ollama (which must be running) on **Clawboo Native** in **Settings → Runtimes**. Also look in **Needs you** on the Board for work waiting on you. [Troubleshooting](/help/troubleshooting) covers more cases. ## Next steps * [Work with your first team](/getting-started/first-team): find the results, steer a task and stop work. * [Tour the dashboard](/getting-started/dashboard-tour): the sidebar, Settings and the other views. * [Providers and runtimes](/using/providers-and-runtimes): add more keys, or Claude Code, Codex, Hermes and OpenClaw teammates. * [Run Clawboo without an API key](/guides/no-api-key): Ollama and ChatGPT sign-in in more depth. * [Budgets and spend](/using/budgets-and-spend): watch what your team spends. # Run a content or research team Source: https://docs.claw.boo/guides/content-team Deploy a marketing or research team from the marketplace, give it rules, steer its tasks, repeat the job weekly and copy out what it writes. This recipe sets up a small marketing team that writes for you: a blog post, social posts and search keywords from one request. You deploy a ready-made team, tell it who you are and how you like things done, follow its tasks, correct one while it runs, and have it repeat the job every week. Everything happens in the dashboard; you need no code. The same steps work for a research team. Pick **Research Lab** instead of **Marketing Squad** in step 1. **What you'll build** * The **Marketing Squad** team (Content Writer Boo, SEO Analyst Boo and Social Media Manager Boo), led by Boo Zero, the leader who hands work to teammates. * Team rules that every agent follows, and a separate room for social posts. * A weekly routine that asks the team for next week's posts. **Time:** about 20 minutes. ## Before you begin * Clawboo is installed and onboarding is done, with one way to run agents connected: an API key, **Sign in with ChatGPT**, or a local Ollama model. See the [Quickstart](/getting-started/quickstart), or [Run Clawboo without an API key](/guides/no-api-key). * If you only signed in with ChatGPT (no API key and no Ollama), you will set every member to **Codex** in step 1. ## What this team can and cannot do Read this before you rely on the team. What an agent can do depends on its runtime, the engine it runs on: * **Clawboo Native** runs a marketplace team when no OpenClaw Gateway is connected. Its agents plan, write and review using the model's own knowledge and whatever you give them. They read the team's board and share a team memory. In team chat they can read and write files only in the team's own folder, and they cannot browse the web, open your other files, or send and post anything. [Connectors](/using/connectors) add those abilities. * **OpenClaw** agents use the tools your OpenClaw Gateway gives them. * **Claude Code, Codex and Hermes** agents bring their own tools for commands and files, and run with their own approval prompts turned off. Read [What agents can do](/concepts/permissions) before you put one on a team. The skills a catalog agent lists, such as `web-search` or `email-draft`, describe its role. They do not give it a working tool. Nobody checks writing work automatically. A task that finishes in team chat goes to **Done** without a verification step, so read what the team wrote before you publish it. See [How work is checked](/concepts/verification). ## Steps **1. Deploy the Marketing Squad** 1. In the sidebar, click **Marketplace**, then the **Teams** tab. 2. Find **Marketing Squad**. Search for it, or pick the **Marketing** category. Click **Details** to see its three members, then **Deploy**. 3. **Customize team** opens. Each member's row has a runtime menu. Keep the suggestions, or pick a runtime per member (see [Mix runtimes on one team](/guides/multi-runtime-team)). 4. Click **Deploy team**. You should see **Team deployed!**, and then the team's **General** chat opens. With no OpenClaw Gateway connected, each row reads **OpenClaw unavailable · using Clawboo Native**. That is expected: the team runs on Clawboo Native. If you only signed in with ChatGPT, set every row to **Codex** before you click **Deploy team**, because Clawboo Native needs an API key or Ollama. A member's runtime cannot be changed after the deploy. ![The Marketplace Teams tab with team cards, each with Details and Deploy buttons](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-gallery.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=8a66172b8d46c009793cf4c2513f41a5) The Teams tab of the Marketplace. Marketing Squad and Research Lab are built in, so they are there even offline. **2. Meet your team and introduce yourself** On the **Meet your team** card, click **Get Started**. Then write a few lines about you and the work: your company, your audience, the voice you want, and what you need help with. Click **Continue to Team Space**. You should see the **General** chat, with the **Team graph** and **Tasks** cards beside it. Your introduction is saved with the team and goes into every turn its agents take. **3. Set the team's rules** 1. At the top of the chat, click the gear button. Its label is the team's name followed by "brief & rules". 2. Under **Rules**, write one rule per line, for example: ```text theme={"theme":"github-dark"} Write in British English. Our readers are owners of small cafes. Never invent statistics. Say when a number needs a source. ``` 3. Click **Save rules**. You should see "Team rules saved." The rules go into every turn of every agent and of Boo Zero in this team. To add one rule without opening the sheet, type `/rule` and the rule in the chat box, for example `/rule Keep posts under 150 words`, and press Enter. You should see "Team rule saved." The **Brief** in the same sheet describes the team. Boo Zero reads it only when you start a message in Boo Zero's own chat with `@` and the team's name. Team chat turns use your rules and your introduction instead, so put anything the team must follow in the rules. ![The team settings sheet with a Brief text box and a Rules text box](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-brief-rules.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=d1288a89f78913bef451605b564344b9) The brief and rules sheet. Save rules applies them from the team's next turn. **4. Ask for the work in General** Type the request in the chat box and press Enter. Say exactly what you want back: ```text theme={"theme":"github-dark"} Write a 600-word blog post announcing our new weekend brunch menu, three LinkedIn posts that link to it, and five search keywords to target. ``` Boo Zero answers simple questions itself. For work like this it hands parts to teammates. Each hand-off appears as a task card in the chat and in the **Tasks** card, where it moves from **To do** to **In progress** to **Done**. While the team works, the **Send** button turns into **Stop**. When the tasks are done, Boo Zero posts one short summary. You should see one or more task cards, each naming the teammate doing it. ![A team chat with agent messages and task cards on the left, and the Team graph and Tasks cards on the right](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-space.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c6bed9e50135bba92df482a1ddf6cd8d) A team's General chat with its Team graph and Tasks cards. **5. Open a room for focused work (optional)** 1. In the sidebar, at the top of the team's chats, click **New room**. 2. Name it, for example "Social posts". Under **Members**, tick **Social Media Manager Boo**. Click **Create room**. The room opens as its own chat. Boo Zero leads it and hands work only to the members you picked. The team's rules, brief and memory are shared with **General**, and the room's tasks still appear on the team's board. **6. Follow a task and steer it while it runs** 1. In the **Tasks** card, click a task under **In progress**. If the card is closed, click **Show tasks** at the top of the chat. 2. Read **Latest report** and **Progress** to see where it is. 3. In the **Comment or steer this task…** box, type a correction, for example "Keep it under 500 words and leave out prices". Click **Send**. The line beside **Send** says when the agent sees your comment. On most runtimes it reads "… gets this now, before its next step"; a Hermes agent pauses to read it, then carries on. You should see a note under your comment such as "Sent to Content Writer Boo’s run". For a task that failed or was stopped, sending a comment starts its agent on it again, and the agent reads your comment as the run begins. To see every task the team has, click **Board** in the sidebar. Its columns are **Needs you**, **Backlog**, **To do**, **In progress**, **Done** and **Cancelled**. See [Board](/using/board). **7. Repeat the job every week** 1. Open **Settings** (`Cmd/Ctrl + ,`). Under **Workspace**, click **Routines**, then **New routine**. 2. Choose **A team task**, and pick **Marketing Squad** under **Team**. 3. Under **What should happen**, write the request, for example "Draft next week's three LinkedIn posts from this week's blog post, and suggest one topic for the next post." 4. Give it a **Name**, such as "Weekly posts", and pick **Weekly · Mon 9am** under **Runs**. Click **Create routine**. You should see the routine under **Team routines**. Every Monday at 9:00, in the time zone of the computer running Clawboo, it posts your instructions in **General** as "Routine · Weekly posts", and Boo Zero takes it from there. To try it now, open the routine and click **Run now**. Clawboo must be running for a routine to fire. A run that was due while Clawboo was not running fires once when it starts again. A run that fails stops the routine until you resume it. See [Routines](/using/routines). **8. Find and copy the results** * **Boo Zero's summary** is in the chat where you asked. * **Each teammate's full write-up** is on its task. In the **Tasks** card, click the task and read **Output**. On the **Board**, click the card and read the agent's report. * **Facts the team saved** are under **Memory** in the sidebar. To copy a result, select the text and copy it (`Cmd/Ctrl + C`). There is no export button. ## Check that it worked * **General** shows your request, the teammates' task cards and Boo Zero's summary. * The **Tasks** card lists the tasks under **Done**. * A task's detail names the runtime that ran it and its cost. A cost Clawboo estimated rather than received from the provider is marked "est.". * **Settings** > **Routines** lists your routine under **Team routines**. ## Variations | You want | Do this | | - | - | | A research team | Deploy **Research Lab** (Paper Reader Boo, Data Analyst Boo, Summarizer Boo). Paste the text you want read: on Clawboo Native the team cannot open links, or files outside its team folder. | | A bigger catalog | Online, the marketplace adds teams such as **SEO Content Studio**, **Writing Room** and **Market Intelligence Unit**. Offline it shows only the five built-in teams. | | Web search or your own apps | On the **Connectors** page, connect a tool such as **Exa Search** (web search, needs an Exa API key) or **Composio** (Gmail, Slack and more), and give your agents access. See [Connectors](/using/connectors). | | No API key | Run the team on a local Ollama model or on your ChatGPT plan. See [Run Clawboo without an API key](/guides/no-api-key). | | Different engines for different members | Put a member on Claude Code, Codex, Hermes or OpenClaw. See [Mix runtimes on one team](/guides/multi-runtime-team). | | A spending limit | Set a budget for the team. See [Budgets and spend](/using/budgets-and-spend). | ## Troubleshooting > [accordion] Every member reads OpenClaw unavailable · using Clawboo Native > > **What it means:** a marketplace team suggests OpenClaw, and no OpenClaw Gateway is connected, so each member falls back to Clawboo Native. > > **Fix:** nothing, if Clawboo Native is what you want. To run the members on OpenClaw, set up OpenClaw first (see [OpenClaw](/runtimes/openclaw)), then deploy the team. > [accordion] Every reply or task fails as soon as it starts > > **What it means:** the members run on a runtime that is not connected, for example Clawboo Native with no API key and no Ollama. > > **Fix:** > > 1. Open **Settings** > **Runtimes** and connect the runtime. See [Providers and runtimes](/using/providers-and-runtimes). > 2. On the **Board**, click **Retry** on each failed task in **Needs you**. > > If that runtime cannot be connected, delete the team with **Delete with agents** (see [Teams](/using/teams)) and deploy it again with each member on a connected runtime. A member's runtime cannot be changed after the deploy. > [accordion] The team makes up facts, or says it cannot browse > > **What it means:** on Clawboo Native the agents have no web access, so they answer from the model's own knowledge. > > **Fix:** paste the source material into your request, or connect a search connector such as **Exa Search** and give your agents access. > [accordion] I can't type in General, and the Send button says Stop > > **What it means:** the team is still working on your last request. > > **Fix:** to change a task while it runs, comment on it in the **Tasks** card. To end every run in this chat, click **Stop**. Stopped tasks wait in **Needs you** on the **Board**. > [accordion] The weekly routine did not run > > **What it means:** Clawboo has not been running since the routine was due (it runs once when Clawboo starts again), or an earlier run failed and stopped the routine. > > **Fix:** open **Settings** > **Routines** and look at the routine's status. If it failed, read the error, then click **Resume**. Click **Run now** to test it. > [accordion] A team from the catalog is missing > > **What it means:** the marketplace could not load the online catalog and shows "Showing the built-in teams only." > > **Fix:** check your network connection and click **Try again**. ## Next steps * [Team chat](/using/team-chat): rooms, clearing a chat, and who answers a message. * [Board](/using/board): every task, the Needs you column, and steering. * [Routines](/using/routines): schedules, pausing, and what happens when a run fails. * [Connectors](/using/connectors): give your agents apps and web search. * [What agents can do](/concepts/permissions): read this before you add coding agents to a team. # Hand a task from one runtime to another Source: https://docs.claw.boo/guides/cross-runtime-handoff Start a coding task on one runtime in its own git worktree, pause it, and finish it on another runtime through the REST API. Use this recipe to start a code change on one runtime, for example Claude Code, pause it, and have another runtime, for example Codex, finish it in the same git checkout. The second runtime learns what is done and what is next from files in that checkout, not from a chat history. This is a developer recipe: you drive every step through the REST API. > [!NOTE] > > A handoff needs a git worktree, a separate checkout Clawboo makes for one task. A run gets one only when it is started with `POST /api/runtimes/:id/run` and a `repoPath`, or by an agent routine whose task template names a `repoPath` (set through the API; the routine dialog has no repository field). Team chat, rooms, delegations and tasks you start from the board never get a worktree, so they never write a handoff. **What you'll build:** one task, started by Claude Code with a pause at the end, then finished by Codex in the same checkout, with the result left on a branch for you to merge. **Time:** about 20 minutes, plus however long the two runs take. ## Before you begin * Two of these runtimes connected: Clawboo Native, Claude Code, Codex, Hermes. OpenClaw cannot take part: `/api/runtimes/openclaw/run` answers `404` with `unknown runtime 'openclaw'`. See [Providers and runtimes](/using/providers-and-runtimes). * A git repository on this computer with at least one commit, and its absolute path. * The server address. The examples use `http://127.0.0.1:18790`; `clawboo` prints the address it actually uses. The `curl` examples assume no access token. If you set `STUDIO_ACCESS_TOKEN`, add `-b 'clawboo_access='` to each `curl` command; see [Authentication](/reference/rest-api/index#authentication). > [!WARNING] > > A worktree keeps a task's file changes apart from your own checkout. It is not a sandbox. Claude Code, Codex and Hermes run with their approval prompts turned off and can read and change any file your user account can. See [What agents can do](/concepts/permissions). ## How it works ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} sequenceDiagram participant You participant A as Runtime A participant W as Task worktree participant B as Runtime B You->>A: run with keepForResume A->>W: edit files, write AGENT_HANDOFF.json Note over W: task back to To do, checkout kept You->>B: run the same task B->>W: read the handoff, finish the work Note over W: check runs, task Done or Blocked ``` ## Steps **1. Create the task without an assignee** ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -s -X POST http://127.0.0.1:18790/api/board \ -H 'Content-Type: application/json' \ -d '{"title":"Add input validation to the signup form","description":"Reject an empty email and passwords under 12 characters, with tests. Set VERIFY_CMD in init.sh to npm test.","teamId":""}' ``` The response holds the new task, in status `todo`. Keep its `id`. Leave out `assigneeAgentId`. A task created for an agent goes straight to the team's chat engine, which runs it without a worktree, and your run in the next step then gets a `409`. The `teamId` is optional (`GET /api/teams` lists your teams' ids): with it, the task shows on that team's board and its spend counts toward the team's budget. **2. Start the task on the first runtime** ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -s -X POST http://127.0.0.1:18790/api/runtimes/claude-code/run \ -H 'Content-Type: application/json' \ -d '{"taskId":"","assigneeAgentId":"","repoPath":"/absolute/path/to/repo","keepForResume":true}' ``` The request stays open until the run ends, which can take minutes. Keep it open, and call the server directly rather than through a proxy with a short timeout: closing the connection cancels the run and its process, and the task goes back to To do. What the fields do: * `keepForResume: true` (the boolean) makes the run pause at the end instead of finishing. * `repoPath` is the repository the worktree branches from. The run's `kind` defaults to `code`, which needs it. Read-only `research` and `review` runs get no worktree. * `assigneeAgentId` becomes the run's identity. Memory and the board credit the work to it, and Clawboo Native, Codex and Hermes keep a home folder per identity. Use the id of an agent on the task's team (from `GET /api/agents`), or leave it out to use the runtime's id. Only letters, digits, `-` and `_` are allowed. Clawboo claims the task and makes the worktree: a folder under `~/.clawboo/worktrees/` on a new branch, `clawboo/task-`, in your repository. The agent works there. At the end, Clawboo writes `AGENT_HANDOFF.json` into the worktree, with the agent's summary as the next step, and puts the task back in To do. The worktree stays, and no check runs on a pause. You should get a `200` whose JSON includes `"ok": true`, `"status": "todo"`, `"usedWorktree": true` and the agent's `summary`. **3. Read the handoff (optional)** ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -s http://127.0.0.1:18790/api/board//workspace ``` The response has four parts: `ok`, `workspace` (including `worktreePath`, the folder, and `branch`), `resume` (what the next runtime will read: `done`, `broken`, `next`, and `lastRuntime`), and `handoff` (the parsed `AGENT_HANDOFF.json`). The handoff is structured data, so the next runtime reads fields rather than prose. **4. Finish the task on the second runtime** Send the same `taskId` and `repoPath` to the other runtime, without `keepForResume`: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -s -X POST http://127.0.0.1:18790/api/runtimes/codex/run \ -H 'Content-Type: application/json' \ -d '{"taskId":"","assigneeAgentId":"","repoPath":"/absolute/path/to/repo"}' ``` Clawboo finds the task's worktree and reuses it. If the folder was removed by a cleanup, Clawboo rebuilds it from the branch, which is why the second run needs `repoPath` too. Codex starts from the handoff: what is done, what is broken, and what to do next. **5. Let the check decide where the task lands** When the second run ends, Clawboo compares the worktree with its starting point: | What changed | What happens | The task ends in | | - | - | - | | Nothing | Clawboo removes the worktree and its branch. | **Done** | | Files changed | The check runs: the verify command from the worktree's `init.sh` and, for a large change, a separate review run. | **Done** when it passes. A failure is retried inside the same request; once the attempts are used up (2 by default), **Blocked**, waiting in **Needs you**. | The request returns once that is settled. The worktree starts with a placeholder verify command, and a placeholder counts as a failed check, which is why the task description in step 1 asks the agent to set `VERIFY_CMD`. See [How work is checked](/concepts/verification). **6. Review and merge the work** Clawboo never merges or pushes. The work stays on the branch `clawboo/task-` in your repository, and in the worktree folder: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} git -C /absolute/path/to/repo log --oneline main..clawboo/task- git -C /absolute/path/to/repo diff main...clawboo/task- git -C /absolute/path/to/repo merge clawboo/task- ``` Replace `main` with the branch you started from. An agent may leave its changes uncommitted in the worktree folder. Clawboo commits them once the verify command passes, when you pause the workspace, and before a cleanup removes the folder. If the branch shows nothing new, look in the folder from `workspace.worktreePath`. ## Check that it worked * After the first run, `GET /api/board/` shows the task in `todo`, and the workspace response shows `resume.hasHandoff: true` with `lastRuntime` set to `claude-code`. * After the second run, `GET /api/board//executions` lists a run by each runtime. * The task ends in `done` or `blocked`, and `git -C /absolute/path/to/repo branch` lists `clawboo/task-` unless the run changed nothing. ## Variations | You want | Do this | | - | - | | Hand the task to a person | Write the handoff yourself with `POST /api/board//workspace/handoff`, for example `{"handoffFrom":"Sam","runtime":"human","nextBestStep":"Finish the password rule and its test"}`. `handoffFrom` and `runtime` are required. The person works in the folder from `workspace.worktreePath`. | | Pause without a run | `PATCH /api/board//workspace` with `{"action":"pause"}` commits any uncommitted work, removes the folder and keeps the branch. The next run rebuilds the folder from the branch when you pass `repoPath`. | | Resume on the same runtime | Send the second run to the runtime that did the first. Where the runtime supports it, it also picks up its own earlier session; a different runtime starts from the handoff file alone. | | See the worktree's files and diff | `GET /api/board//workspace/detail` returns the task notes in the worktree and the diff against its starting point. | | Run it on a schedule | Create an agent routine through the API with a `repoPath` in its task template. Each fire makes a new task and runs it in a worktree, without a pause. See the [Schedules API](/reference/rest-api/schedules). | ## Troubleshooting > [accordion] The run answers 409 > > **What it means:** Clawboo could not claim the task. It is not in To do, it already has an assignee (for example because you created it with `assigneeAgentId`), or you closed the connection before the claim. > > **Fix:** check the task with `GET /api/board/`. Do not retry in a loop. To run a task that is assigned or **Blocked**, move it back to To do first with `PATCH /api/board/` and `{"status":"todo"}`, which also clears the assignee. > [accordion] The run answers 422 and the task has a \[blocked: no isolation] comment > > **What it means:** the run changes files, so it needs a worktree, and Clawboo could not make one. The comment reads "This task changes files, so it needs an isolated git worktree, but no repository path was provided for it." when `repoPath` is missing, or names the path when Clawboo could not create the worktree there. The task goes back to To do. > > **Fix:** pass `repoPath` with the absolute path of a git repository that has at least one commit. For read-only work, send `"kind":"research"` or `"kind":"review"` instead. > [accordion] The run answers 404 unknown runtime 'openclaw' > > **What it means:** OpenClaw agents cannot be run this way. The run route accepts `clawboo-native`, `claude-code`, `codex` and `hermes`. > > **Fix:** use one of those runtimes. > [accordion] curl seems to hang > > **What it means:** the run is still going. The request returns only when the run ends. > > **Fix:** leave it open. If you stop it, Clawboo cancels the run, and the task goes back to To do with the worktree kept. > [accordion] The first run finished instead of pausing > > **What it means:** the run body did not carry `"keepForResume": true` as a boolean, so the run went on to the check. > > **Fix:** send `true`, not `"true"`. A task that reached Done cannot be reopened, so create a new task for the rest of the work. A Blocked task can go back to To do with `PATCH /api/board/` and `{"status":"todo"}`. > [accordion] The task ended Blocked > > **What it means:** the check failed on every attempt. A placeholder verify command in `init.sh` always fails. > > **Fix:** read the task's comments with `GET /api/board/` to see what failed. Set `VERIFY_CMD` in the worktree's `init.sh`, move the task back to To do, and run it again with the same `repoPath`. ## Next steps * [Worktrees and handoff](/concepts/worktrees-and-handoff): the files in a task's worktree and what each one is for. * [How work is checked](/concepts/verification): the verify command, the review run, and when a task lands Blocked. * [Runtimes API](/reference/rest-api/runtimes): every field and status of `POST /api/runtimes/:id/run`. * [Board API](/reference/rest-api/board): the task and workspace routes used here. * [Mix runtimes on one team](/guides/multi-runtime-team): put agents on different runtimes in team chat. # Recipes Source: https://docs.claw.boo/guides/index End-to-end walkthroughs that combine several Clawboo features to finish one real job, for people who do not write code and for developers. Each recipe takes one real job from start to finish: the clicks, what you should see, and what to do when something goes wrong. The pages under Use Clawboo explain one feature at a time; recipes show how the features fit together. Every recipe assumes Clawboo is installed and onboarding is done. If not, start with the [Quickstart](/getting-started/quickstart). ## If you don't write code | Recipe | What you get | Who it is for | | - | - | - | | [Run a content or research team](/guides/content-team) | A marketplace team that writes to your rules, takes your comments while it works, and repeats the job every week | Founders, marketers and small teams | | [Run Clawboo without an API key](/guides/no-api-key) | A team on a free local model (Ollama) or on your ChatGPT plan | Anyone without an API key, or who wants prompts to stay on their computer | | [Mix runtimes on one team](/guides/multi-runtime-team) | One team whose members run on different engines, picked in the dashboard | Anyone with two or more runtimes connected | | [Repeat work on a schedule](/using/routines) | A routine that sends your team the same request every day or week | Anyone with a recurring report or check | | [Keep spend under a limit](/using/budgets-and-spend) | Spend tracking, warnings, and an optional hard cap | Anyone who pays a provider per token | | [Give your team an app](/using/connectors) | Gmail, Slack, Notion, web search and other tools your agents can use | Teams whose work lives in other apps | ## If you write code | Recipe | What you get | Who it is for | | - | - | - | | [Hand a task from one runtime to another](/guides/cross-runtime-handoff) | A coding task started on one runtime in its own git worktree, paused, and finished on another | Developers who drive Clawboo through its REST API | | [Attach an outside agent](/operating/mcp-servers) | Your own Claude Code, Codex or other MCP client connected to your team's board and memory | Developers with their own agent setup | | [Reach Clawboo from another machine](/guides/self-host-securely) | The dashboard on another computer, through an SSH tunnel or a proxy with an access token | People who run Clawboo for others | | [Back up and move Clawboo](/operating/data-and-state) | Where Clawboo keeps its data, and how to back it up and restore it | Anyone moving to a new computer | ## Related * [Quickstart](/getting-started/quickstart): install Clawboo and connect AI before you start a recipe. * [Use Clawboo](/using/index): one page per feature, for when you need the details. * [Choose a runtime](/runtimes/index): what each runtime needs and what it can do. * [Troubleshooting](/help/troubleshooting): look up an error message and its fix. # Mix runtimes on one team Source: https://docs.claw.boo/guides/multi-runtime-team Give each member of a team its own runtime in the Customize team step, and learn what each runtime brings and how team memory differs between them. Every agent on a team has its own runtime, the engine that does its thinking and acting. One team can mix them: a Claude Code agent fixes a bug, a Codex agent reviews the fix, and a Clawboo Native agent writes the changelog, while Boo Zero leads all three. Use this recipe when two or more runtimes are connected and you want each member on the one that suits its job. **What you'll build:** the built-in **Dev Team** (Bug Fixer Boo, Code Reviewer Boo and Doc Writer Boo) with each member on a different runtime. **Time:** about 15 minutes, plus the time to install any runtime you don't have yet. ## Before you begin Clawboo is installed and onboarding is done. At least two runtimes must read **Connected** in **Settings** > **Runtimes**. Each one needs: | Runtime | What it needs | | - | - | | Clawboo Native | Built in. An API key from a supported provider, or a local Ollama model. | | OpenClaw | A local OpenClaw Gateway. **Set up OpenClaw** installs and starts one for you. | | Claude Code | The `claude` CLI, which Clawboo installs, and an Anthropic API key. A global npm install of Clawboo also needs `@anthropic-ai/claude-agent-sdk`. | | Codex | The `codex` CLI, which Clawboo installs, and **Sign in with ChatGPT**. | | Hermes | Python 3.11 to 3.13 (Clawboo installs Hermes with pipx or pip), plus an OpenRouter, Anthropic or OpenAI key, or a ChatGPT sign-in. | For the click path per runtime, see [Providers and runtimes](/using/providers-and-runtimes). ## Steps **1. Connect the runtimes you want** Open **Settings** (`Cmd/Ctrl + ,`) and click **Runtimes** under **Workspace**. On each runtime's row, use its button: **Install**, **Connect**, **Sign in**, or **Set up** for OpenClaw. You should see two or more rows read **Connected**. Claude Code reads **Needs key** until an Anthropic API key is connected; a signed-in `claude` CLI on its own does not count. **2. Start the deploy** In the sidebar, click **Marketplace**. On the **Teams** tab, find **Dev Team** and click **Deploy**. **Customize team** opens. **3. Pick a runtime for each member** Under **Teammate colors**, each member's row has a runtime menu with **Clawboo Native**, **OpenClaw**, **Claude Code**, **Codex** and **Hermes**. For example: * **Bug Fixer Boo**: Claude Code * **Code Reviewer Boo**: Codex * **Doc Writer Boo**: Clawboo Native A runtime that is not connected shows **Connect →** in the menu. Choosing it closes this dialog and opens **Settings** > **Runtimes** (for OpenClaw, its setup), so connect first and then deploy. Clawboo Native, OpenClaw and Hermes members also get a model menu. Claude Code and Codex members run on their own default model. **4. Deploy the team** Click **Deploy team**. You should see **Team deployed!**, and then the team's **General** chat opens. A member keeps the runtime it was deployed on; the dashboard cannot change it later. **5. Meet the team and hand it work** On the **Meet your team** card, click **Get Started**, introduce yourself, and click **Continue to Team Space**. Then ask for work that needs each member, for example: ```text theme={"theme":"github-dark"} In ~/code/my-app, fix the failing date test, have the fix reviewed, and write a two-line changelog entry. ``` Boo Zero splits the request into tasks. Each task runs on its teammate's own runtime, and all of them land on the same board and in the same **Tasks** card. > [!WARNING] > > In team chat, Claude Code, Codex and Hermes run with their own approval prompts turned off. They start in the team's folder (`~/.clawboo/workspaces/`), and can still read and change any file your user account can. There is no isolated checkout on this path. Point them only at work you have committed or backed up, and read [What agents can do](/concepts/permissions). For a separate git checkout per task, see [Hand a task from one runtime to another](/guides/cross-runtime-handoff). ## Check that it worked * On the **Team graph** card beside the chat, each Boo carries a small badge for its runtime. * In the **Tasks** card, a finished task's detail names the runtime that ran it. * The tasks of all three members appear on the same **Board**. ## What each runtime brings | Runtime | Good for | Tools of its own | A comment on a running task | Spend shown | | - | - | - | - | - | | Clawboo Native | Writing, planning, coordinating | None in team chat beyond memory, the board, hand-offs and connectors | Reaches it before its next step | Exact or estimated; free on your own Ollama | | OpenClaw | Agents that use your OpenClaw setup | Whatever your Gateway gives its agents | Reaches it before its next step | Estimated from text length | | Claude Code | Code changes | Commands, file edits and more, with prompts off | Reaches it before its next step | Reported by Claude Code | | Codex | Code changes on a ChatGPT plan | Commands and file edits, with its sandbox off | Reaches it before its next step | Estimated from tokens | | Hermes | A helper that grows its own memory and skills | Its own tools, every call approved automatically | It pauses, reads it, then carries on | Estimated from text length | The [runtime overview](/runtimes/index) compares them in full, and [Budgets and spend](/using/budgets-and-spend) explains estimated costs. ## How memory differs Clawboo keeps one shared memory, and every team member uses it through memory tools. What a member sees depends on its runtime: | Runtime | Clawboo's team memory | Memory of its own | | - | - | - | | Clawboo Native | Reads the team's, its own and global facts. Saves go to the team. | Its conversation history, kept per agent. | | Claude Code | Same as Clawboo Native. | None. Your own `~/.claude` setup and Claude Code's auto memory are left out. | | Codex | Same as Clawboo Native. | A separate Codex home for each agent. | | Hermes | Same as Clawboo Native. | Its own memory and skills, kept across runs in its home under `~/.clawboo/runtimes/hermes`. | | OpenClaw | Global facts only. It never sees facts saved for the team, and what it saves is visible to every team. | Whatever OpenClaw keeps for the agent. | In team chat, agents look things up in memory when they decide to. Clawboo adds relevant facts to an agent's prompt by itself only for task runs started through the REST API or an agent routine. See [Memory](/using/memory). ## Who leads a mixed team Boo Zero leads every team, whatever runtimes the members use. After an onboarding with only a ChatGPT sign-in, Boo Zero is the first Codex agent that onboarding created. Otherwise it is a Clawboo Native agent once any team has a Clawboo Native member and a native provider is connected, and the OpenClaw Gateway's default agent until then. See [Boo Zero](/using/boo-zero). ## Variations | You want | Do this | | - | - | | Add a member later | **Create Boo** and the graph's add button make an OpenClaw agent when the Gateway is connected, and a Clawboo Native agent otherwise. Claude Code, Codex and Hermes agents come only from a Marketplace deploy: a team, or one agent from the **Agents** tab, which makes a one-agent team. | | Move a member to another runtime | Deploy the team again with the new runtime, then delete the old team. | | A code change in its own git checkout | Start the task through the REST API with a repository path. See [Hand a task from one runtime to another](/guides/cross-runtime-handoff). | | No API key on any member | Use local Ollama members and Codex members. See [Run Clawboo without an API key](/guides/no-api-key). | ## Troubleshooting > [accordion] A runtime in the menu shows Connect → > > **What it means:** that runtime is not connected yet. > > **Fix:** close the dialog, connect the runtime in **Settings** > **Runtimes** until it reads **Connected**, then deploy the team again. > [accordion] Claude Code is installed but the menu doesn't offer it > > **What it means:** Claude Code needs an Anthropic API key before it reads **Connected**. A signed-in `claude` CLI does not count. > > **Fix:** connect an Anthropic API key on the Claude Code row in **Settings** > **Runtimes**. See [Claude Code](/runtimes/claude-code). > [accordion] Connect an OpenClaw Gateway to deploy the OpenClaw members, or switch them to another runtime. > > **What it means:** at least one member is set to OpenClaw, and no OpenClaw Gateway is connected. > > **Fix:** set those members to another runtime, or set up OpenClaw first. See [OpenClaw](/runtimes/openclaw). > [accordion] No agents could be created. Check that the runtime is connected, then retry. > > **What it means:** no member could be created on its runtime. The new team stays, empty. > > **Fix:** check each member's runtime in **Settings** > **Runtimes**. Then close the dialog, delete the empty team (right-click its icon in the team rail and choose **Delete team only**), and deploy from the **Marketplace** again. If only some members fail, a message lists them, for example "2 of 3 agents created". > [accordion] An OpenClaw member doesn't know what the team saved > > **What it means:** OpenClaw agents read and save global facts only. > > **Fix:** add the facts it needs under **Memory** in the sidebar. Facts you add there are global, so every agent can read them. Or give the work to a member on another runtime. > [accordion] A Hermes member forgot what it learned > > **What it means:** each Hermes agent's memory lives in a home named after the agent's id. An agent you deploy again is a new agent with a new, empty home. > > **Fix:** keep the original agent. Deleting it and deploying again starts its memory from scratch. ## Next steps * [Choose a runtime](/runtimes/index): what each runtime supports, side by side. * [What agents can do](/concepts/permissions): permissions per runtime, before you give coding agents real work. * [Hand a task from one runtime to another](/guides/cross-runtime-handoff): pause a coding task on one runtime and finish it on another. * [Memory](/using/memory): browse and add the facts your team shares. * [Budgets and spend](/using/budgets-and-spend): which runtimes report real cost and which are estimated. # Run Clawboo without an API key Source: https://docs.claw.boo/guides/no-api-key Run your agents on a free local Ollama model or on your ChatGPT plan instead of a pay-per-token API key, and know the limits of each path. Clawboo itself is free. What costs money is the AI model your agents use, and an API key bills you for every token. Two paths need no API key: a local model that runs on your own computer through Ollama, and your ChatGPT plan through the Codex runtime. This recipe sets up either one and says what to expect from it. **What you'll build:** a team that runs on a local Ollama model or on your ChatGPT plan, and optionally an OpenClaw Gateway on the same local model. **Time:** about 15 minutes, plus the time to download a model for Ollama. ## Choose a path | | Local model (Ollama) | ChatGPT plan | | - | - | - | | What you pay | Nothing. The model runs on your computer. | Your ChatGPT subscription, within its usage limits. | | Where your prompts go | To Ollama on your computer. | To OpenAI. | | Runtime your agents use | Clawboo Native, built into Clawboo. | Codex, which Clawboo installs for you. | | What you need | Ollama and one model that can use tools. | A ChatGPT account with a subscription. | | Quality | Depends on the model your computer can run. | ChatGPT's own models. | ## Before you begin * Clawboo is installed. See [Install Clawboo](/getting-started/installation). * If you have not finished onboarding yet, you do the connect step during onboarding. If you have, you do it in **Settings**. Both ways are below. ## Set it up > [tab] Local model (Ollama) > > **1. Install and start Ollama** > > Download Ollama from [ollama.com/download](https://ollama.com/download) and open it. Ollama keeps running in the background. From a terminal, `ollama serve` starts it too. > > **2. Pull a model that can use tools** > > ```bash theme={"theme":"github-dark"} > ollama pull llama3.2 > ``` > > `llama3.2` is the model Clawboo suggests. Any chat model works if Ollama reports that it can use tools: agents call tools on almost every turn (memory, the board, handing work to teammates). Clawboo's model menus mark the others **(can't use tools)**. Embedding models such as `nomic-embed-text` never appear in those menus, because they cannot answer a chat. > > **3. Connect Ollama to Clawboo** > > * **During onboarding:** on **Connect your AI provider**, click **Ollama** (Local · no key needed). Pick your model under **Model** and click **Continue**. > * **After onboarding:** open **Settings** (`Cmd/Ctrl + ,`), then **Runtimes**. Expand **Clawboo Native** (its button reads **Connect** or **Manage**). Under **LLM providers**, find **Ollama** and click **Use**. > > You should see onboarding move on to **Add more runtimes**, or, in **Settings**, Ollama listed among the connected providers. To make Ollama the provider new agents use, expand its row and click **Make default**. > > **4. Deploy a team** > > In onboarding, **Choose your first team** puts every member on Clawboo Native with your Ollama model. From the **Marketplace**, check that each member's runtime reads **Clawboo Native** in **Customize team**, then click **Deploy team**. > > Every agent, the lead and the specialists alike, uses the same Ollama model. One model means your computer does not reload a different one at each hand-off. > > **5. Let team memory search by meaning (optional)** > > ```bash theme={"theme":"github-dark"} > ollama pull nomic-embed-text > ``` > > When Ollama on this computer has `nomic-embed-text`, Clawboo's team memory uses it to find facts by meaning, without sending them anywhere. Without it, and with no OpenAI key connected, memory search matches keywords. Clawboo looks for it at `http://localhost:11434` only. > > To change the model later, open **Settings** > **Runtimes** > **Clawboo Native** > **Manage**, expand the **Ollama** row and pick a **Default model**. Boo Zero switches to it at once, and agents you create afterwards get it. An existing agent keeps its model until you change it on the agent's page. > [tab] ChatGPT plan > > **1. Sign in with ChatGPT** > > * **During onboarding:** on **Connect your AI provider**, click **OpenAI**, then **Sign in with ChatGPT** (marked Recommended). If Codex is not installed yet, click **Install Codex** first. Click **Sign in with ChatGPT**: the ChatGPT sign-in page opens in your browser. Approve it there. When you see "Your team will run on your ChatGPT subscription. No API key needed.", click **Continue**. > * **After onboarding:** open **Settings** (`Cmd/Ctrl + ,`), then **Runtimes**. On the **Codex** row, click **Install** if it shows, then **Sign in**, then **Sign in with ChatGPT**. You can also start from **Settings** > **Providers** > **ChatGPT subscription** > **Connect**. > > You should see the **Codex** row read **Connected**. > > **2. Deploy a team on Codex** > > In onboarding, **Choose your first team** puts every member on Codex, and the first Codex agent it creates becomes the team's lead and Boo Zero. > > From the **Marketplace**, set each member's runtime to **Codex** in **Customize team** before you click **Deploy team**. The menu suggests OpenClaw, or Clawboo Native when no OpenClaw Gateway is connected, and Clawboo Native cannot run without an API key or Ollama. A member's runtime cannot be changed after the deploy. > > **3. Send a first request** > > Ask the team for something in **General**. When a task finishes, its detail shows **Codex** as the runtime and a cost marked "est.". > > Codex runs your account's default model, so there is no model menu for Codex members. > > > [!WARNING] > > > > Clawboo runs Codex with its own sandbox and approval prompts turned off. A Codex agent can run commands and change files anywhere your user account can, without asking. Read [What agents can do](/concepts/permissions) before you give it real work. ## Check that it worked 1. In the team's **General** chat, ask for something small, for example "List three names for a weekly newsletter." 2. Boo Zero replies, or hands the work to a teammate as a task card. 3. Open the finished task in the **Tasks** card. Its detail names the runtime that ran it: **Clawboo Native** for Ollama, **Codex** for your ChatGPT plan. ## Limits to know ### Local models * **Every agent needs tool use.** A model without it fails with "… can't use tools, and this agent needs them." Pick one Ollama reports as tool-capable. * **Quality depends on your computer.** A model small enough for a laptop makes more mistakes than a hosted one, most often when it hands work to teammates. If tasks go wrong, try the largest tool-capable model your computer runs comfortably. * **Costs read free.** A run on a model your own Ollama serves is recorded at \$0, shows as "free" on task cards and charges no budget. Ollama's cloud models (a `-cloud` tag) run on ollama.com instead, so Clawboo estimates them at a default rate, marks them "est." and counts them toward budgets. See [Budgets and spend](/using/budgets-and-spend). ### ChatGPT plan * **One plan, shared limits.** Codex, and OpenClaw or Hermes if you sign them in too, all use your one ChatGPT plan. They share its usage limits with each other and with your own Codex use. * **Costs show as estimates.** Codex reports tokens but not dollars, so Clawboo estimates a cost and marks it "est.". Your plan is a flat fee, so the estimate is not your bill, but it counts toward budgets like any other cost. * **Coding agent, full access.** Codex works with your account's permissions and its approval prompts off (see the warning above). ## Run OpenClaw on your local Ollama (optional) OpenClaw agents can run on the same local model. Do the Ollama path above first, so Clawboo knows you chose Ollama. OpenClaw also needs a Node.js version it accepts: 24.22.1 or a later 24.x, or 26.12.1 or newer. **1. Start the OpenClaw setup** Open **Settings** > **Runtimes**. On the **OpenClaw** row, click **Set up**, then **Set up OpenClaw**. Clawboo installs OpenClaw if it is missing, points it at your local Ollama, starts the Gateway and connects. **2. Approve this device** When OpenClaw asks, click **Approve this device**. You should see the **OpenClaw** row read **Connected**. **Set up OpenClaw** reuses the first credential it finds, in this order: a provider API key you connected, OpenClaw's own ChatGPT sign-in, an offer to sign OpenClaw in with ChatGPT (when Codex is signed in), and then your local Ollama. So OpenClaw runs on Ollama only when no API key and no ChatGPT sign-in are connected. For Ollama, Clawboo adds the line `OLLAMA_API_KEY=ollama-local` to `~/.openclaw/.env`. It is a marker OpenClaw needs, not a secret. If OpenClaw's default model is not pulled, the setup stops with the `ollama pull` command and a **Check again** button. An OpenClaw agent whose model is an `ollama/` one is recorded at \$0, like Clawboo Native on Ollama. More on OpenClaw: [OpenClaw](/runtimes/openclaw). ## Variations | You want | Do this | | - | - | | Ollama on another computer | Start the Clawboo server with `OLLAMA_BASE_URL` set to that Ollama's address, ending in `/v1` (for example `http://192.168.1.20:11434/v1`). Agent runs use it; team memory still looks for its embedding model at `localhost:11434`. | | Hermes on your ChatGPT plan | Once Codex is connected, the **Hermes** card in **Settings** > **Runtimes** offers **Use my ChatGPT subscription**. Hermes signs in with a one-time code, which your ChatGPT account must allow under **Settings** > **Security**. | | Local and ChatGPT members on one team | Connect both, then pick **Clawboo Native** or **Codex** per member. See [Mix runtimes on one team](/guides/multi-runtime-team). | | An API key after all | Add one in **Settings** > **Providers**. See [Providers and runtimes](/using/providers-and-runtimes). | ## Troubleshooting > [accordion] Ollama is running, but has no chat model yet > > **What it means:** Ollama answered, but it has no model that can hold a chat. Embedding models do not count. > > **Fix:** run `ollama pull llama3.2`, then click **Check again**. > [accordion] Ollama doesn't have this model yet, so this run couldn't start > > **What it means:** the agent's model is not pulled on this computer. > > **Fix:** run the `ollama pull` command the message shows, or pick a model you have pulled in the agent's model menu on its page. > [accordion] This model can't use tools, and this agent needs them > > **What it means:** the model cannot call tools, and every agent uses tools. > > **Fix:** pick a tool-capable model on the agent's page, or pull one with `ollama pull llama3.2`. > [accordion] Ollama isn't answering on this machine > > **What it means:** Clawboo could not reach Ollama at its address (`http://localhost:11434` unless you set `OLLAMA_BASE_URL`). > > **Fix:** open Ollama, or run `ollama serve`, then try again. If you don't have it, install it from [ollama.com/download](https://ollama.com/download). > [accordion] Tasks on a ChatGPT-only setup fail as soon as they start > > **What it means:** the members were deployed on Clawboo Native or OpenClaw, which need an API key, Ollama or a Gateway. > > **Fix:** delete the team with **Delete with agents** and deploy it again with every member set to **Codex** in **Customize team**. > [accordion] Sign in with ChatGPT does not finish > > **What it means:** the browser sign-in was closed or failed. For Hermes, your ChatGPT account may not allow sign-in with a one-time code. > > **Fix:** click **Sign in with ChatGPT** again. For Hermes, turn on device code sign-in under **Settings** > **Security** in ChatGPT, then retry. If the button keeps failing, run the command the card shows in a terminal, then click **Re-check**. ## Next steps * [Run a content or research team](/guides/content-team): put your new setup to work. * [Providers and runtimes](/using/providers-and-runtimes): manage keys, Ollama and sign-ins in **Settings**. * [Clawboo Native](/runtimes/native): the runtime your Ollama agents use. * [Codex](/runtimes/codex): the runtime your ChatGPT plan powers. * [Budgets and spend](/using/budgets-and-spend): how estimated costs reach budgets. # Reach Clawboo from another machine Source: https://docs.claw.boo/guides/self-host-securely Open your Clawboo dashboard from another computer through an SSH tunnel, a reverse proxy with an access token, or a network bind. Clawboo listens only on the computer it runs on. Use this guide to open the dashboard from somewhere else: your laptop reaching a home server or a VPS, or another computer on your network. Each path below keeps the dashboard private to you. For why each step matters, see the [Security model](/operating/security). > [!WARNING] > > **Security advisory: clawboo 0.5.0 and older behind a reverse proxy.** On these releases the access token does not cover `/api/mcp/`. Any request that reaches Clawboo from `127.0.0.1` skips the access cookie on every `/api/mcp/` path, including `/api/mcp/config`. A reverse proxy on the same machine delivers every request from `127.0.0.1`. So anyone who can reach your proxy can use Clawboo's MCP servers and ask `/api/mcp/config` for an attach URL signed as any of your agents, with that agent's connector grants. This holds even with `STUDIO_ACCESS_TOKEN` set. > > If you already run Clawboo behind nginx, Caddy or a Cloudflare Tunnel, add the rule below for your proxy now, or upgrade once a release newer than 0.5.0 is out. Run `clawboo --version` to see your version. > [tab] nginx > > ```nginx theme={"theme":"github-dark"} > # Inside the server block that proxies to Clawboo. > location ~* ^/api/mcp(/|$) { > return 404; > } > ``` > [tab] Caddy > > ```caddyfile theme={"theme":"github-dark"} > # Inside the site block, next to reverse_proxy. > @mcp path /api/mcp /api/mcp/* > respond @mcp 404 > ``` > [tab] Cloudflare Tunnel > > ```yaml theme={"theme":"github-dark"} > # config.yml: the deny rule must come before the rule that reaches Clawboo. > ingress: > - hostname: dash.example.com > path: '(?i)^/api/mcp(/|$)' > service: http_status:404 > - hostname: dash.example.com > service: http://127.0.0.1:18790 > - service: http_status:404 > ``` The rules ignore letter case, because 0.5.0 also routes variants such as `/API/MCP/config`. Under a path prefix, add the same rule for the prefixed path (`/clawboo/api/mcp`), because Clawboo also answers `/api/` without the prefix. Releases after 0.5.0 require the access cookie on these paths. The four MCP server paths also accept an agent's attach token, but `/api/mcp/config` never does, and an attach token that arrives through a proxy is refused. The proxy recipes below keep the rule as a second layer. ## Choose a path | Path | Best for | What you set up | | - | - | - | | SSH tunnel (recommended) | You, from any computer that can SSH to the server | Nothing on the Clawboo side | | Reverse proxy | A web address with HTTPS, such as `https://dash.example.com` | An access token, two allowlist variables, and a proxy rule that blocks `/api/mcp/` | | Network bind | Another computer on a network you trust, or a container | `HOST=0.0.0.0`, an access token, and the two allowlist variables | Clawboo has no user accounts. Anyone who has the access token can do everything you can, so keep it to yourself. ## Before you begin * Clawboo is installed on the server with `npm install -g clawboo` ([Install Clawboo](/getting-started/installation)). * You know the server's Clawboo port. While Clawboo runs, `cat ~/.clawboo/api-port.txt` prints it; it is usually `18790`. * You know your version: run `clawboo --version`. On 0.5.0 or older, read the advisory above first. ## Option 1: SSH tunnel (recommended) The tunnel forwards a port on your computer to Clawboo's port on the server. Clawboo keeps listening only on the server itself, and SSH does the authentication. **1. Start Clawboo on the server** Run `clawboo` on the server as usual, with `HOST` unset. On a server without a desktop the browser does not open; that is fine. **2. Open the tunnel from your computer** ```bash theme={"theme":"github-dark"} ssh -N -L 18790:127.0.0.1:18790 you@your-server ``` Use the server's Clawboo port on both sides. With a different local port, your browser's address no longer matches Clawboo's port, and Clawboo blocks the dashboard's requests (unless you add `http://localhost:` to `CLAWBOO_ALLOWED_ORIGINS`). **3. Open the dashboard** Open `http://localhost:18790` on your computer. You should see your dashboard. The tunnel stays open until you stop the `ssh` command. The tunnel needs no access token, but anyone else who can log in to the server can reach Clawboo's port too. On a server other people use, also create an access token as in Option 2 and start the server directly with it. ## Option 2: Reverse proxy A reverse proxy such as nginx, Caddy or a Cloudflare Tunnel gives Clawboo a web address with HTTPS. The proxy runs on the same machine, and Clawboo keeps listening only on `127.0.0.1`. **1. Create an access token and save it** ```bash theme={"theme":"github-dark"} openssl rand -hex 24 ``` This prints a 48-character token. Save it in your password manager now: you need it to sign in, and the server must start with the same value every time. If you write your own token, use only letters, digits, `.`, `_`, `~` and `-`. With any other character the server refuses to start. **2. Start Clawboo with the token and your address** Replace the token and `dash.example.com` with your own: ```bash theme={"theme":"github-dark"} export STUDIO_ACCESS_TOKEN='paste-your-token-here' export CLAWBOO_ALLOWED_HOSTS=dash.example.com export CLAWBOO_ALLOWED_ORIGINS=https://dash.example.com export CLAWBOO_API_PORT=18790 mkdir -p ~/.clawboo cd ~/.clawboo NODE_ENV=production node "$(npm root -g)/clawboo/dist/server.js" ``` Leave `HOST` unset, because the proxy connects to `127.0.0.1`. `CLAWBOO_API_PORT` pins the port the proxy forwards to. The two allowlist variables let requests that use your address pass Clawboo's same-origin guard. Without them the dashboard page loads, but every API call fails with `403 Cross-origin request blocked.` Start the server directly like this rather than with `clawboo`, which cannot manage a server that has a token. You should see a log line saying Clawboo is ready. To keep it running after you log out, [run it as a service](/operating/deployment#run-it-as-a-service). **3. Point the proxy at Clawboo** > [tab] nginx > > ```nginx theme={"theme":"github-dark"} > server { > listen 443 ssl; > server_name dash.example.com; > # Add your ssl_certificate and ssl_certificate_key lines here. > > # Clawboo's MCP endpoints are for agents on this machine only. > location ~* ^/api/mcp(/|$) { > return 404; > } > > location / { > proxy_pass http://127.0.0.1:18790; > proxy_set_header Host $host; > proxy_set_header X-Forwarded-Proto $scheme; > # The Gateway WebSocket at /api/gateway/ws > proxy_http_version 1.1; > proxy_set_header Upgrade $http_upgrade; > proxy_set_header Connection "upgrade"; > # Live updates stream, and some requests stay open for a long time. > proxy_buffering off; > proxy_read_timeout 1h; > } > } > ``` > [tab] Caddy > > ```caddyfile theme={"theme":"github-dark"} > dash.example.com { > @mcp path /api/mcp /api/mcp/* > respond @mcp 404 > reverse_proxy 127.0.0.1:18790 > } > ``` > > Caddy gets the certificate, passes `Host` through, sets `X-Forwarded-Proto` and proxies WebSockets without extra settings. > [tab] Cloudflare Tunnel > > ```yaml theme={"theme":"github-dark"} > # ~/.cloudflared/config.yml, below your tunnel and credentials-file lines. > ingress: > - hostname: dash.example.com > path: '(?i)^/api/mcp(/|$)' > service: http_status:404 > - hostname: dash.example.com > service: http://127.0.0.1:18790 > - service: http_status:404 > ``` > > The tunnel checks rules from first to last, so the deny rule must come before the Clawboo rule. Leave `httpHostHeader` unset so Clawboo sees the hostname visitors use. Whatever the proxy, it must pass the `Host` your browser used and `X-Forwarded-Proto`. Clawboo checks `Host` against the allowlist, builds the redirect after sign-in from both, and marks the cookie `Secure` when `X-Forwarded-Proto` is `https`. **4. Sign in once** Open `https://dash.example.com/?access_token=`. Clawboo sets a cookie and removes the token from the address bar, and you should see your dashboard. Each browser signs in once. The token appears in that first request's URL, so a proxy access log can record it; keep such logs private. > [!WARNING] > > **A reverse proxy is not authentication.** The proxy delivers every request from `127.0.0.1`, so to Clawboo the whole internet looks like a local caller. Without `STUDIO_ACCESS_TOKEN`, the dashboard and every `/api/` route are open to anyone who can reach the proxy, and the server starts without complaint. Keep the token unless the proxy authenticates every request itself (HTTP basic auth, an SSO proxy such as oauth2-proxy, Cloudflare Access). If your proxy does authenticate every request and you leave the token out, Clawboo logs a one-time `SECURITY:` warning on the first proxied request. That is expected. If the proxy runs on a different machine, Clawboo must listen on the network. Use the bind and token from Option 3, point the proxy at the server's address, and keep the allowlists set to your public name. ## Option 3: Network bind A network bind makes Clawboo listen on every interface of the machine, so other computers on the network can open it directly. Use it on a network you trust, or for a container whose port you publish. **1. Create an access token and save it** Run `openssl rand -hex 24` and save the token, as in Option 2. Clawboo refuses to start on a network bind without a working token. **2. Start Clawboo on all interfaces** Replace the token and `192.168.1.10` with the server's address on your network: ```bash theme={"theme":"github-dark"} export HOST=0.0.0.0 export STUDIO_ACCESS_TOKEN='paste-your-token-here' export CLAWBOO_ALLOWED_HOSTS=192.168.1.10 export CLAWBOO_ALLOWED_ORIGINS=http://192.168.1.10:18790 export CLAWBOO_API_PORT=18790 mkdir -p ~/.clawboo cd ~/.clawboo NODE_ENV=production node "$(npm root -g)/clawboo/dist/server.js" ``` Use `HOST=0.0.0.0`, not the server's own address. Clawboo's agents and background jobs connect to `127.0.0.1`, and a bind to one address does not listen there, so agents would lose their Clawboo tools. In a container, `HOST=0.0.0.0` is also what lets the published port reach the server. **3. Sign in once** On the other computer, open `http://192.168.1.10:18790/?access_token=`. You should see your dashboard. Plain `http://` sends the cookie unencrypted, so anyone who can watch the network can copy it and use the dashboard. For anything beyond a network you trust, put a TLS proxy in front (Option 2). ## Serve Clawboo under a path prefix To share one hostname with other apps, serve Clawboo under a prefix such as `/clawboo`. Add `CLAWBOO_BASE_PATH` to the variables from Option 2; the allowlists name the shared host: ```bash theme={"theme":"github-dark"} export CLAWBOO_BASE_PATH=/clawboo export CLAWBOO_ALLOWED_HOSTS=apps.example.com export CLAWBOO_ALLOWED_ORIGINS=https://apps.example.com ``` The proxy must pass the prefix through unchanged. In nginx, that means a `proxy_pass` with no path after the port: ```nginx theme={"theme":"github-dark"} location ~* ^/clawboo/api/mcp(/|$) { return 404; } location /clawboo/ { # No trailing slash: nginx then forwards /clawboo/... as it arrived. proxy_pass http://127.0.0.1:18790; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; proxy_read_timeout 1h; } # Send the bare prefix to its trailing-slash form. location = /clawboo { return 308 /clawboo/; } ``` With a trailing slash after the port, nginx strips the prefix, Clawboo redirects the page back to the prefix, and the browser loops until it gives up. Sign in at `https://apps.example.com/clawboo/?access_token=`. Under a prefix, the dashboard, the API, the live streams and the Gateway WebSocket all live below it, and the prebuilt package needs no rebuild. The access cookie's path is the prefix, so other apps on the same host never receive it. The value may use only letters, digits, `.`, `_`, `~` and `-` between slashes, and its first part must not be `api`. An invalid value stops the server at startup with `SECURITY/CONFIG: CLAWBOO_BASE_PATH is invalid`. ## Check that it worked From another computer, run these with your own address in place of `dash.example.com`: ```bash theme={"theme":"github-dark"} curl -i https://dash.example.com/api/settings curl -i https://dash.example.com/api/mcp/config curl -i https://dash.example.com/api/mcp/tasks ``` * **`/api/settings`** should answer `401`: Clawboo is reachable and wants the token. A `403` means `CLAWBOO_ALLOWED_HOSTS` does not include the name you used. A `200` means a request with no credentials got through, so anyone who can reach this address can use Clawboo: set `STUDIO_ACCESS_TOKEN`. * **The two `/api/mcp/` addresses** should answer `404` from your proxy. A `401` means Clawboo refused them but your deny rule is missing. A `200` or `400` means they reached Clawboo's MCP endpoints: add the deny rule now. Then sign in with your browser, open the board and send a message in a team chat. If both work, the allowlists are right. ## Settings at a glance | Variable | What it does | | - | - | | `STUDIO_ACCESS_TOKEN` | The access token for the dashboard and API | | `HOST` | The interface Clawboo listens on: `127.0.0.1` by default, `0.0.0.0` for a network bind | | `CLAWBOO_ALLOWED_HOSTS` | Host names or addresses people use to reach Clawboo, comma-separated | | `CLAWBOO_ALLOWED_ORIGINS` | Full origins such as `https://dash.example.com`, comma-separated | | `CLAWBOO_API_PORT` | A fixed port; the server stops with an error if it is taken | | `CLAWBOO_BASE_PATH` | A path prefix such as `/clawboo` | | `CLAWBOO_ALLOW_INSECURE=1` | Lets a network bind start without a token, which leaves every API route open to the network | [Environment variables](/reference/environment-variables) lists every setting. ## Troubleshooting > [accordion] Every action fails with 403 Cross-origin request blocked. > > Clawboo's same-origin guard does not recognize the name or origin in the request. Set `CLAWBOO_ALLOWED_HOSTS` to the host name and `CLAWBOO_ALLOWED_ORIGINS` to the full origin (scheme, host, and the port if it is not the default), then restart. Through an SSH tunnel, use the same port on both ends. > [accordion] 401: Clawboo access token required > > This browser has no cookie yet, or the token changed. Open your Clawboo address with `?access_token=` once. > [accordion] 401: Invalid Clawboo access token. > > The token in the URL does not match the `STUDIO_ACCESS_TOKEN` the server started with. Check for missing or extra characters. > [accordion] After signing in, the browser goes to an http address or to 127.0.0.1 > > Clawboo builds the redirect from the `Host` and `X-Forwarded-Proto` it receives. Make the proxy pass both; in nginx, `proxy_set_header Host $host;` and `proxy_set_header X-Forwarded-Proto $scheme;`. > [accordion] The boot log says STUDIO\_ACCESS\_TOKEN contains an unsupported character > > The token has a character other than letters, digits, `.`, `_`, `~` and `-`, so the server stopped rather than run without the token check. Make a new token with `openssl rand -hex 24`, start the server with it, and sign in again. > [accordion] The log says a request reached this server through a reverse proxy > > The server has no access token, so the dashboard and every `/api/` route are open to anyone who can reach the proxy. Set `STUDIO_ACCESS_TOKEN` and restart, unless the proxy authenticates every request. The warning appears once per server start and never blocks a request. > [accordion] clawboo says a server is running but requires an access token > > That is expected on a token-protected install: `clawboo`, `clawboo stop` and `clawboo restart` cannot use its API. Stop the server with Ctrl+C in its terminal, with your service manager, or with the command `clawboo stop` prints. > [accordion] The server exits with SECURITY: refusing to start > > `HOST` is a network address and the server has no access token. Set `STUDIO_ACCESS_TOKEN` (letters, digits, `.`, `_`, `~` and `-` only), or unset `HOST` to listen on `127.0.0.1` again. If the message names an unsupported character in `STUDIO_ACCESS_TOKEN`, see the entry above. > [accordion] Agents lost their Clawboo tools after setting HOST > > `HOST` names a single address, so Clawboo no longer listens on `127.0.0.1`, where its agents connect. Use `HOST=0.0.0.0` instead. > [accordion] Live updates stop, or long requests are cut off, behind nginx > > By default nginx buffers responses and closes a connection after 60 seconds without data. Add `proxy_buffering off;` and a longer `proxy_read_timeout`, as in the recipe above. > [accordion] MCP attach config shows Could not load attach config. > > That section, on the OpenClaw row under **Settings** then **Runtimes**, reads `/api/mcp/config`, which your proxy now denies. That is expected. Open the dashboard through an SSH tunnel when you need it. > [accordion] An agent on another computer cannot attach to the MCP servers > > The proxy rule blocks it, and releases after 0.5.0 also refuse an attach token that arrives through a proxy. Give that computer an SSH tunnel to the server's Clawboo port instead; see [MCP servers](/operating/mcp-servers). ## Limits * **One token, full access.** There are no accounts or roles. To rotate the token, change the variable, restart the server and sign in again in every browser. * **One rate-limit allowance behind a proxy.** Clawboo counts requests by the connection's address, so every browser behind the proxy shares one allowance. See [Limits and defaults](/reference/limits-and-defaults). ## Next steps * [Security model](/operating/security): what each protection covers, and where your keys are stored * [Run the Clawboo server](/operating/deployment): run it as a service, update it and read its logs * [MCP servers](/operating/mcp-servers): attach agents from other tools to Clawboo * [Data and state](/operating/data-and-state): back up and move your Clawboo folder * [Environment variables](/reference/environment-variables): every setting the server reads # Troubleshooting Source: https://docs.claw.boo/help/troubleshooting Find the message you see and fix it: start-up, providers and Ollama, runtimes and the OpenClaw Gateway, team chat, the board, budgets and remote access. Use this page when something in Clawboo does not work. Start with the five quick checks below. Then find the message you see: entries are grouped by area, and each one says what the message means, how to fix it, and how to avoid it next time. Each entry links to the page that covers its topic in full. ## First 60 seconds **1. Check that the server is running** Run `clawboo` in a terminal (`npx clawboo@latest` if you use npx). It finds the Clawboo server that is already running, or starts one, then opens your browser and prints the address after **Clawboo opened at**. Use that address. It is `http://localhost:18790` unless that port was taken. If the command prints a message instead, look it up under [Install and launch](#install-and-launch). **2. Read the server log** The server writes its log to `~/.clawboo/server.log` (to the folder in `CLAWBOO_HOME`, if you set it). Show the last lines: > [tab] macOS and Linux > > ```bash theme={"theme":"github-dark"} > tail -n 50 ~/.clawboo/server.log > ``` > [tab] Windows > > ```powershell theme={"theme":"github-dark"} > Get-Content "$HOME\.clawboo\server.log" -Tail 50 > ``` A server you start yourself with `node` logs to its terminal instead. After an update from the dashboard, the restarted server writes no log until you run `clawboo restart`. **3. Open System Health** In the dashboard, open **Settings** (in the sidebar, or press `Cmd+,` on a Mac and `Ctrl+,` elsewhere), then **System Health**. Each startup check reads **OK**, **Degraded** or **Fatal**, with a line on what it found. A failed check does not stop the server: it keeps serving and reports the problem here. Fix what the check names, then click **Re-run probe**. See [System Health](/using/system-health). **4. Check that a provider is connected** Open **Settings > Runtimes**. The runtime your agents use should read **Connected**. **Needs key**, **Needs login** or **Not installed** means it cannot run agents yet. The keys you saved are listed under **Settings > Providers**. See [Providers and runtimes](/using/providers-and-runtimes). **5. Read the notice, then look in Needs you** When a run fails before the agent writes anything, the chat shows a notice with the reason. Find that notice on this page. Then open the **Board**: its **Needs you** column holds approvals waiting for an answer and tasks that will not move by themselves. If none of this helps, go through [When an agent is not doing what you asked](#when-an-agent-is-not-doing-what-you-asked), or [report a bug](#report-a-bug). ## Install and launch ### Clawboo requires Node.js 22.12 or newer **What it means:** your Node.js is older than Clawboo needs. The message names the version you have. **Fix:** install a current Node.js from [nodejs.org](https://nodejs.org), or switch with a version manager (`nvm install 22` or `fnm use 22`). Then run `clawboo` again. **Prevent it:** if you plan to add OpenClaw, choose a Node.js version in OpenClaw's narrower range, listed under [Before you begin](/runtimes/openclaw#before-you-begin). ### Dashboard is taking too long to start. **What it means:** `clawboo` started the server but did not hear back from it within 45 seconds. Below the message it prints the last lines of the server log and the log's full path. **Fix:** read those log lines. They point to one of three cases: 1. **An error.** For example "CLAWBOO\_API\_PORT=18800 is already in use.", "No free port available in range 18790-18809." or a line that starts with "SECURITY: refusing to start". Find the message on this page, fix it, then run `clawboo` again. 2. **You set `STUDIO_ACCESS_TOKEN`, and the log has a line containing `Clawboo ready`.** The server is running. The command cannot confirm a server protected by an access token, so it gives up after 45 seconds and exits with an error. Open `http://localhost:/?access_token=` in your browser; the port is in `~/.clawboo/api-port.txt`. 3. **No error, on a first start on Windows.** Antivirus software scanning the new files can slow the first start. Run `clawboo` again. **Prevent it:** start a token-protected server directly or as a service, not with `clawboo`. [Run the Clawboo server](/operating/deployment#start-the-server-directly) and [Reach Clawboo from another machine](/guides/self-host-securely) show how. ### A Clawboo server is running on port N, but it requires an access token. **What it means:** this install sets `STUDIO_ACCESS_TOKEN`, so the server answers only browsers that carry its access cookie, and the command cannot use it. It does not start a second server. **Fix:** open `http://localhost:/?access_token=` once in your browser, with the port the message names. `clawboo stop` and `clawboo restart` refuse to touch this server too; they print a command that stops it by its port. **Prevent it:** manage a token-protected server with its own terminal or a service manager. See [Token-gated installs](/reference/cli#token-gated-installs). ### Could not find the Clawboo server. **What it means:** the command could not find the server files that come with it, so the install is incomplete. **Fix:** reinstall with `npm install -g clawboo`, or run `npx clawboo@latest`. ### More install messages > [accordion] CLAWBOO\_API\_PORT=N is already in use. > > **What it means:** you set `CLAWBOO_API_PORT`, and another program already uses that port. With this variable set, Clawboo uses exactly that port and does not look for another. > > **Fix:** free the port, choose another one, or unset `CLAWBOO_API_PORT` so Clawboo picks a free port from 18790 to 18809. If an older Clawboo holds the port, run `clawboo stop` first. A `PORT` variable set by a hosting platform works the same way and fails with "PORT=N is already in use (set by hosting platform)." > [accordion] No free port available in range 18790-18809. > > **What it means:** all 20 ports Clawboo tries are taken, often by older Clawboo servers or by other programs. > > **Fix:** run `clawboo stop`, or set `CLAWBOO_API_PORT` to a port you know is free. > [accordion] The dashboard opened on an unexpected port > > **What it means:** your shell sets a `PORT` variable, and Clawboo uses it in place of 18790. > > **Fix:** unset `PORT`, or choose a port with `CLAWBOO_API_PORT`. The address that `clawboo` prints is always the right one. > [accordion] The browser didn't open > > **What it means:** Clawboo could not open a browser, for example on a computer without a screen. The server runs anyway. > > **Fix:** open the address printed after **Clawboo opened at**, usually `http://localhost:18790`. > [accordion] No Gateway detected > > **What it means:** this line is information only. On every start, `clawboo` checks whether an OpenClaw Gateway answers on its default port, 18789. > > **Fix:** nothing to fix. You need a Gateway only for OpenClaw agents; see [OpenClaw](/runtimes/openclaw). > [accordion] The previous server was stopped and could not be replaced. > > **What it means:** `clawboo` or `clawboo restart` stopped an older server to start a newer one, and the new one did not start. No server is running. > > **Fix:** read the server log for the reason, then run `clawboo`. > [accordion] The update chip says the update failed > > **What it means:** the **Update available** chip shows npm's own reason. "Permission denied. A global install needs elevated permissions." means npm cannot write to its global folder, which is common with a Node.js installed for the whole system. > > **Fix:** run the update in a terminal (`npm install -g clawboo@latest`, then `clawboo restart`), or install Node.js with a version manager such as nvm or fnm, which keeps global packages in your home folder. If the update installed but the page did not come back, run `clawboo restart`. See [Update Clawboo](/getting-started/installation#update-clawboo). ## Onboarding and providers Clawboo checks a key before it saves one, with a single request to the provider's list of models. During setup, a failed check reads **Key didn't verify**, followed by one of the reasons below. ### Invalid API key. **What it means:** the provider turned the key down. **Fix:** 1. Copy the key again from the provider's key page, or create a new one. 2. Check that you picked the provider the key belongs to. 3. Paste it again and save. ### Could not reach openai (timed out). **What it means:** this computer could not reach the provider within 8 seconds. The message names the provider; without "(timed out)" the connection failed outright. A proxy, a firewall or no network is the usual cause. **Fix:** fix the connection and try again. If you are sure the key is right, save it without the check: **Continue anyway** during setup, **Save anyway** under **Settings > Providers**, or **Connect anyway** on a runtime's row. ### Clawboo Native has no provider key connected. **What it means:** a Clawboo Native agent tried to run, and its provider has no key Clawboo can use. The full notice reads "Open Settings → Runtimes → Clawboo Native to connect a provider." The agent's page header also reads **Disconnected** or **No provider key**. **Fix:** save a key for that provider under **Settings > Providers**, or open **Settings > Runtimes > Clawboo Native** and connect a provider. If you disconnected Clawboo Native earlier, click **Connect** on its row and choose a saved key with **Use**. **Prevent it:** a runtime's **Disconnect** also deletes keys that other runtimes share; see [Disconnect a key or a runtime](/using/providers-and-runtimes#disconnect-a-key-or-a-runtime). ### Ollama isn't answering on this machine. Start it (ollama serve) and try again. **What it means:** Clawboo could not reach Ollama, at `http://localhost:11434` unless you set `OLLAMA_BASE_URL`. During setup, the same problem reads "Ollama isn't answering." **Fix:** open the Ollama app, or run `ollama serve`, then try again. Don't have Ollama? The setup screen links to its download. ### Ollama doesn't have llama3.2 yet, so this run couldn't start. **What it means:** the agent's model is not pulled into your Ollama. The message names the model and the command that pulls it. **Fix:** run the command, for example `ollama pull llama3.2`, then send your message again. Or pick a model you have pulled in the agent's model control. **Check again** on the Ollama row refreshes the list of models. ### This model can't use tools, and this agent needs them **What it means:** the message starts with the model's name. Agents need tools to hand off work, use the board and reach apps, and this model has none. In the model list, Clawboo marks such models **(can't use tools)**. **Fix:** pick a model that can use tools, or pull one, for example with `ollama pull llama3.2`. **Prevent it:** pull a model that supports tools before you deploy a team on Ollama. See [Run Clawboo without an API key](/guides/no-api-key). ### More provider messages > [accordion] Provider returned 429. > > **What it means:** the provider answered the key check with an error other than a rejected key; the number varies. 429 means the provider is limiting your requests. A number from 500 up is a problem on the provider's side. > > **Fix:** wait a moment, check your account with the provider, then try again. > [accordion] Ollama is running, but has no chat model yet. > > **What it means:** Ollama answers, but it has no model that can chat. Models made only for search (embedding models) do not count. > > **Fix:** run `ollama pull llama3.2`, then click **Check again**. > [accordion] The codex CLI isn't installed yet. Install it first, then sign in. > > **What it means:** **Sign in with ChatGPT** runs through Codex, which is not installed yet. > > **Fix:** install Codex from **Settings > Runtimes > Codex**, or with the **Install Codex** button during setup, then sign in again. > [accordion] Sign-in timed out. Try again. > > **What it means:** the ChatGPT sign-in in your browser was not finished in time. > > **Fix:** click **Retry**. Or run `codex login` in a terminal, then click **Re-check** on the **Codex** row. > [accordion] My team stopped working after I disconnected another runtime > > **What it means:** **Disconnect** on a runtime deletes every saved key that runtime uses, and runtimes share keys. Disconnecting Claude Code, for example, deletes the Anthropic key that Clawboo Native and Hermes may also use. > > **Fix:** save the key again under **Settings > Providers**. See [Disconnect a key or a runtime](/using/providers-and-runtimes#disconnect-a-key-or-a-runtime). > [accordion] The Marketplace shows only 5 teams and 15 agents > > **What it means:** Clawboo could not download the Marketplace catalog from GitHub, so it shows its built-in teams. **Showing the built-in teams only.** with a **Try again** button means your browser could not reach the server. > > **Fix:** check your network, then click **Try again** if it is shown. Restarting Clawboo makes the server download the catalog again. See [Marketplace](/using/marketplace). ## Runtimes and the OpenClaw Gateway ### Approve this device **What it means:** OpenClaw asks you to approve each new device once before it lets Clawboo connect. The setup shows this card; an error that mentions `NOT_PAIRED` means the same thing. **Fix:** 1. Click **Approve this device** on the card. 2. If the setup then says "OpenClaw still needs this device approved. Retry to approve it again.", click **Retry** for a fresh card. 3. If the card keeps failing, approve from a terminal, as [Approve from a terminal instead](/runtimes/openclaw#approve-from-a-terminal-instead) shows. Then click **Set up OpenClaw** or **Reconnect OpenClaw** again. ### OpenClaw Gateway is offline **What it means:** the OpenClaw Gateway stopped, or Clawboo cannot reach it ("OpenClaw Gateway is unreachable"). Your OpenClaw agents are paused; agents on other runtimes keep working. **Fix:** click **Reconnect** on the banner, or **Reconnect OpenClaw** in **Settings > Runtimes**. If the banner offers **Restart Gateway** instead, the Gateway turned down Clawboo's token, which it reads only when it starts; click it. ### Could not reach your OpenClaw agents **What it means:** a team-chat message for an OpenClaw agent could not be delivered, because the Gateway is not running. Clawboo reconnects and retries once before it shows this notice. **Fix:** bring the Gateway back with **Reconnect OpenClaw** in **Settings > Runtimes** (or **Reconnect** on the banner), then send the message again. ### The Claude Code runtime needs @anthropic-ai/claude-agent-sdk **What it means:** a Claude Code run failed because the Claude Agent SDK is missing. The `clawboo` package leaves it out because it would add about 210 MB to every install. The **Claude Code** row can still read **Connected**, because that checks only the `claude` program and your key. **Fix:** ```bash theme={"theme":"github-dark"} npm install -g clawboo @anthropic-ai/claude-agent-sdk clawboo restart ``` Then send the message or start the task again. A one-off `npx` run cannot load the SDK, so switch to the global install first. See [Claude Code](/runtimes/claude-code). ### The Hermes install fails with (from versions: none) **What it means:** pip found no Hermes release for the Python it used. Hermes needs Python 3.11, 3.12 or 3.13. Clawboo picks the newest Python 3.11 or later that it finds, so a Python 3.14 wins over a 3.12 you also have, and the install fails. **Fix:** install Hermes yourself with a supported Python, then click **Refresh** at the top of **Settings > Runtimes**: ```bash theme={"theme":"github-dark"} pipx install --python python3.12 'hermes-agent[anthropic]<1' ``` **Prevent it:** keep a Python from 3.11 to 3.13 for Hermes. See [Hermes](/runtimes/hermes). ### More runtime messages > [accordion] Claude Code reads Needs key, but my claude CLI is signed in > > **What it means:** Claude Code agents in Clawboo need an Anthropic API key. The `claude` program's own sign-in does not count. > > **Fix:** connect a key on the **Claude Code** row, or save one under **Settings > Providers**. > [accordion] Codex reads Needs login after I signed in from a terminal > > **What it means:** the row has not checked again since you signed in. > > **Fix:** click **Sign in** on the **Codex** row, then **Re-check**. A sign-in from any terminal on this computer counts. > [accordion] Hermes needs Python 3.11 or newer, but the Python on this machine is 3.9 > > **What it means:** the newest Python Clawboo found is too old, and pipx is not installed. On a Mac, Apple's own Python is often 3.9. > > **Fix:** install a newer Python (for example `brew install python@3.12`) or pipx (`brew install pipx`), then click **Retry**. If Clawboo finds no Python at all, the message reads "Python 3.11+ (with pip or pipx) not found."; the fix is the same. > [accordion] Permission denied. A global install may require elevated permissions. > > **What it means:** npm cannot write to its global folder while installing Claude Code or Codex. For OpenClaw the message reads "Permission denied. Your Node.js installation may require sudo for global installs." > > **Fix:** use Node.js from a version manager (nvm or fnm) or from Homebrew, which install global packages without `sudo`, then click **Retry**. The message also names the `sudo` command if you prefer it. > [accordion] Gateway did not become reachable within 60s > > **What it means:** the Gateway did not answer on its port within 60 seconds. When the Gateway's log names a reason, the message reads "Gateway did not start:" followed by it. > > **Fix:** read the Gateway's log at `~/.clawboo/gateway.log`, fix what it reports, then click **Retry**. OpenClaw's [troubleshooting guide](https://docs.openclaw.ai/help/troubleshooting) covers its own errors. > [accordion] OpenClaw needs openclaw doctor --fix, and running it failed > > **What it means:** OpenClaw wanted a one-time update to its data before starting, and the `openclaw doctor --fix` that Clawboo ran for it failed. > > **Fix:** run `openclaw doctor --fix` in a terminal to see the whole report, then click **Retry** or **Reconnect OpenClaw**. > [accordion] An action fails with gateway\_disconnected > > **What it means:** the action needs the Gateway, for example creating an OpenClaw agent or saving its files, and Clawboo's server is not connected to it. > > **Fix:** start the Gateway from **Settings > Runtimes > OpenClaw**, then try again. Agents on other runtimes do not need the Gateway. ## Team chat and delegation ### An agent does not reply **What it means:** the message reached no agent that could run, or the run is waiting on something. **Fix:** check these in order. 1. **A notice in the chat.** When a run fails before the agent writes anything, the chat shows the reason. Look it up on this page. 2. **The runtime is connected.** In **Settings > Runtimes**, the agent's runtime reads **Connected**. An OpenClaw agent also needs the OpenClaw Gateway online. 3. **Needs you.** A command, an app call or a risky hand-off may be waiting for your answer on the **Board**. 4. **The agent is busy.** An agent works on one thing at a time, so new work waits until it is free. A waiting task stays in **To do**. 5. **A budget.** A budget in hard cap mode that reached its limit refuses new runs, with a notice in the chat; see [Budgets and costs](#budgets-and-costs). If it still does not answer, go through [When an agent is not doing what you asked](#when-an-agent-is-not-doing-what-you-asked). ### No response from the delegate for 8 minutes. **What it means:** a teammate took a hand-off and then reported nothing for 8 minutes, so Clawboo ended its run and failed the task. The leader is told. A teammate inside one long tool call, such as a build, gets 24 minutes: "The delegate has been inside one tool call for over 24 minutes with no result." **Fix:** open the task in **Needs you** on the **Board** (it reads **Timed out**) and click **Retry**. If the teammate keeps timing out, check its runtime and model. ### The run ended without reporting a result. **What it means:** the connection to the agent's runtime dropped before the run finished, so there is no reason to report. The notice goes on: "The connection to the runtime may have dropped; try sending again." **Fix:** send the message again. If it happens every time, read the server log, and check the runtime in **Settings > Runtimes**. ### The agent ignored my file **What it means:** either the agent's runtime does not read that agent file, or the agent could not reach a file on your computer. **Fix:** * **Agent files.** Only OpenClaw reads AGENTS.md, IDENTITY.md and TOOLS.md as instructions, and OpenClaw 2026.9 and later skips TOOLS.md, so put tool notes in AGENTS.md. On the other runtimes the agent follows SOUL.md: all of it on Clawboo Native, the first 4,000 characters on Claude Code, Codex and Hermes. See [Edit the agent files](/using/agents#edit-the-agent-files). * **Files on your computer.** In team chat and in an agent's own chat, a Clawboo Native agent can open files only in its team's folder (`~/.clawboo/workspaces/`). Claude Code, Codex and Hermes agents start in that folder too, so give them the file's full path. They can reach any file your user account can; see [What agents can do](/concepts/permissions). ### More chat messages > [accordion] The message box is greyed out > > **What it means:** while agents work, **Stop** replaces the send button. The box also stays off when the chat has no agents, for example a room whose members all left the team. > > **Fix:** press **Stop**, or wait for the work to finish. In a room, add members with **Edit room members**. See [Team chat](/using/team-chat). > [accordion] Could not reach the team right now. Please try again in a moment. > > **What it means:** your message could not be delivered to the team, for a reason other than the OpenClaw Gateway. > > **Fix:** send it again in a moment. If it keeps happening, read the server log. > [accordion] Delegation to Ada not approved (expired); skipped. > > **What it means:** the leader handed off work to a teammate (here, Ada) in words that looked risky, such as delete, deploy or secret, so Clawboo asked you first. Nobody answered within 5 minutes, so the team skipped that hand-off. If you clicked **Don't allow**, the message reads "(deny)" instead. > > **Fix:** answer risky hand-offs in **Needs you** on the **Board**, then ask the team again. See [Approvals](/using/approvals). > [accordion] My @ mention went to the leader instead > > **What it means:** a mention picks the agent only at the very start of the message, followed by a space. In a room, only the room's members can be mentioned. > > **Fix:** start the message with `@Name `. To reach an agent outside the room, add it to the room or talk to it in General. > [accordion] Gateway not connected… in an agent's own chat > > **What it means:** this is an OpenClaw agent, and its chat goes through the OpenClaw Gateway, which is not connected. Clawboo Native, Claude Code, Codex and Hermes agents chat through the Clawboo server and never show this. > > **Fix:** connect the Gateway in **Settings > Runtimes**. > [accordion] This run stopped because the runtime believed it was out of context room. > > **What it means:** the runtime decided the conversation was too big for the model. That usually comes from a wrong context-window setting, or a long list of connector tools, rather than a long conversation. > > **Fix:** check the model's real context window in **Settings > Runtimes**, and give the agent fewer connectors. Starting the conversation fresh does not fix the cause. > [accordion] The run failed: the run went quiet for too long and was ended > > **What it means:** the agent's run sent nothing for 30 minutes, so Clawboo ended it. > > **Fix:** send the message again. If the agent was working on something slow, split the job into smaller steps. ## The board and tasks ### A task waits in Needs you **What it means:** the task will not move by itself. Its badge says why: | Badge | What happened | | - | - | | **Failed** | The last run ended in an error | | **Timed out** | The agent stopped responding and its run was ended | | **Stopped** | You pressed **Stop**; it never restarts by itself | | **Needs review** | Its work did not pass the check | | **Blocked** | An agent marked it blocked | | **Unassigned** | No agent is bound to it | | **Not started** | It is in In progress with no agent, so no run is going | **Fix:** read the reason on the card, then click its button: **Retry** runs it again, **Assign** gives it to an agent, and **Review** or **Open** opens the task. The card's **More** menu (the three dots) holds the other choices, including **Dismiss** to cancel it. If the agent asked you something, answer it in the task's reply box instead: a reply to a task in **Needs you** that has an agent starts the agent on it again. See [Things that need you](/using/board#things-that-need-you). **Prevent it:** after three failed runs in a row, Clawboo stops retrying a task by itself, so a task that keeps failing usually has a setup problem. Fix the cause before you click **Retry**. ### A task reached Done with no check mark **What it means:** this is normal for most tasks. Clawboo checks work only on a task run that worked in its own git worktree and changed files, and only the API starts those runs. Work handed off in team chat, tasks you start on the **Board** and all OpenClaw work reach **Done** without a check. **Fix:** review the result yourself. See [How work is checked](/concepts/verification). ### More board messages > [accordion] A task has been In progress for a long time > > **What it means:** the agent may still be working, or its run may have stopped reporting. Clawboo ends a run that stays silent too long and releases its task, which then runs again or waits in **Needs you** as **Timed out**. > > **Fix:** reply on the task to steer it ([Steer a running task](/using/board#steer-a-running-task)), or press **Stop** in its team's chat. The exact waits are in [Limits and defaults](/reference/limits-and-defaults). > [accordion] A reply says Not sent > > **What it means:** the note under your reply says why it did not reach an agent. The reply itself is saved on the task. > > * "Not sent: the task is finished": the task is **Done** or **Cancelled** and cannot run again. Click **Send as a follow-up task** to give the same agent a new task with your reply as its brief. > * "Not sent: no agent has this task": nobody is bound to the task. Give it to an agent with **Assign** in **Needs you**, then reply again. > * "Not sent: something went wrong": Clawboo saved the reply but could not deliver it, for example because the database was busy. Send it again, or click **Retry** on the task. > [accordion] I cannot move a card to In progress > > **What it means:** only an agent's run puts a task in progress, so the board does not offer that move. > > **Fix:** move the task to **To do** with an agent bound to it, or use **Retry** or **Assign** in **Needs you**. > [accordion] This task changes files, so it needs an isolated git worktree, but no repository path was provided for it. > > **What it means:** a run started through the API was for a task that changes files, and named no repository. The task comment starts with `[blocked: no isolation]`, and the task goes back to **To do**. > > **Fix:** pass a `repoPath` that points to a git repository, or start the task as a research or review task. See [Worktrees and handoff](/concepts/worktrees-and-handoff). > [accordion] Couldn’t load the board. > > **What it means:** the board's first read failed, usually because the Clawboo server is not answering. > > **Fix:** click **Retry**. If it keeps failing, go back to [First 60 seconds](#first-60-seconds). ## Budgets and costs ### Auto-paused: team budget reached. Raise the cap (or resume) to continue. **What it means:** a budget in hard cap mode reached its limit, so Clawboo stopped the run, sent the task back to **To do** and left this comment. The word before "budget" names the budget that ran out: agent, team, or mission (a task plus everything handed off from it). "node" means a per-run limit, which only a routine set up through the API can carry. A Claude Code or Codex run whose final cost report crossed the cap had already finished, so it is not stopped; see **Budget cap reached** below. In team chat the stopped run's task fails instead, and the chat can show "The run failed: the run was stopped by a budget cap". **Fix:** open **Settings > Insights > Governance**. The budget reads **paused**: click **Resume**, or raise its limit with **Set cap**. If spend is still over the limit, a resumed budget pauses again at the next charge, so raise the limit to keep working. See [Resume or raise a budget](/using/budgets-and-spend#resume-or-raise-a-budget). ### The team budget is paused at its hard cap, so this run did not start. **What it means:** a budget in hard cap mode reached its limit, so Clawboo refused the run before it started. The word before "budget" names the budget: agent, team, or mission (a task plus everything handed off from it). In team chat Clawboo checks the agent's and the team's budgets, and the mission's for a run on a task; one-to-one chat checks only the agent's. A handed-off task, or one you gave an agent on the Board, carries the same sentence in a comment and waits in **Needs you**. **Fix:** open **Settings > Insights > Governance** and raise the budget's limit with **Set cap**, or click **Resume**. Then send your message again, or press **Retry** on the task. See [Resume or raise a budget](/using/budgets-and-spend#resume-or-raise-a-budget). ### My hard cap did not stop the team **What it means:** a paused hard cap refuses new runs, but a run already going finishes unless it is Clawboo Native on a priced model, which stops after its next model turn. Every other run is charged when it finishes, which can take the budget past 100%. If new messages still get answers, the budget is **warn only**, which never pauses. **Fix:** press **Stop** in the team's chat to halt it right away. [What a hard cap stops](/concepts/governance#what-a-hard-cap-stops) explains what a cap can stop on each path, and [Budgets and spend](/using/budgets-and-spend) shows how to watch spend. ### A cost reads est., or a local model shows a cost **What it means:** "est." (a `~` before the amount on a card) marks an estimate: the runtime reported no charge, so Clawboo priced the run itself. Models Clawboo has no price for are estimated at $3 per million input tokens and $15 per million output tokens. A model your own Ollama serves costs nothing and reads **free**, so a cost on an Ollama run means Clawboo did not count it as local: one of Ollama's cloud models (a `-cloud` tag), which runs on ollama.com, or an OpenClaw agent whose model in `openclaw.json` is not an `ollama/` one. **Fix:** nothing to fix. [Budgets and spend](/using/budgets-and-spend) lists what each runtime reports and what counts toward a budget. ### More budget messages > [accordion] Tokens Used shows zero for my agents > > **What it means:** **Tokens Used** lists Clawboo Native, Claude Code, Codex and Hermes runs only from the release that added them, so their earlier work reads zero, and it does not count their one-to-one chats. An OpenClaw agent's turns count only when Clawboo's server sees them and can match them to an agent in Clawboo. > > **Fix:** see older costs on each task's card and, once you add a budget, on its row in **Settings > Insights > Governance**. > [accordion] Budget cap reached > > **What it means:** a run went over a hard cap as it ended: an OpenClaw agent's routine run, or an agent routine or API run whose last cost report crossed the cap (usually Claude Code or Codex, which report cost only then). The run is recorded, any result it produced is kept, and the next run is refused. > > **Fix:** raise the cap, or **Resume** the budget, in **Settings > Insights > Governance**. ## Self-hosting and remote access > [!WARNING] > > On clawboo 0.5.0 and older, a reverse proxy on the same machine leaves every `/api/mcp/` path open, even with an access token set. Deny all of `/api/mcp/` at the proxy, or upgrade once a release newer than 0.5.0 is out. [Security model](/operating/security) has the rules for nginx, Caddy and Cloudflare Tunnel. ### Cross-origin request blocked. **What it means:** every action in the dashboard fails with a 403, because Clawboo's same-origin guard does not recognize the address in the request. That happens when you reach Clawboo by a name or port it was not told about, for example through a reverse proxy. **Fix:** set `CLAWBOO_ALLOWED_HOSTS` to the host name you use and `CLAWBOO_ALLOWED_ORIGINS` to the full origin (scheme, host and any port), then restart the server. Through an SSH tunnel, use the same port on both ends. See [Reach Clawboo from another machine](/guides/self-host-securely). ### Too many requests. Wait a moment and retry. **What it means:** one address sent more requests to Clawboo's API in a minute than its limit allows (a 429). Routes that install software, change runtime settings or grant access have a lower limit and say "Too many requests for this operation. Wait a moment and retry." Page loads and dashboard files have an allowance of their own. **Fix:** wait a minute, then try again. The usual cause is a script or tool calling the API in a tight loop, or a page that keeps reloading: stop it. Behind a reverse proxy every request arrives from the same address, so all your browsers and tools share one allowance. The limits are in [Limits and defaults](/reference/limits-and-defaults#network-and-http). ### Clawboo access token required **What it means:** the server runs with `STUDIO_ACCESS_TOKEN`, and this browser has no access cookie yet (a 401). "Invalid Clawboo access token." means the token in the address does not match the server's. **Fix:** open your Clawboo address once with `?access_token=` added. Check the token for missing or extra characters. ### SECURITY: refusing to start **What it means:** the server exits with code 1 rather than leave the API open. Either `HOST` makes it listen on a network address and it has no access token, or `STUDIO_ACCESS_TOKEN` has a character other than letters, digits, `.`, `_`, `~` and `-`, which stops it on any address (the message names the character). **Fix:** the message says which case it is. * **A character the token cannot use:** replace the token with one made by `openssl rand -hex 24` (output from `openssl rand -base64` can contain `+`, `/` or `=`), or unset `STUDIO_ACCESS_TOKEN`. Unsetting `HOST` does not help here. * **A network address with no token:** set `STUDIO_ACCESS_TOKEN` (letters, digits, `.`, `_`, `~` and `-` only), or unset `HOST` to listen on this computer only. ### Agents lost their Clawboo tools after setting HOST **What it means:** `HOST` names a single network address, so the server stops answering on `127.0.0.1`, the address Clawboo's own agents use to reach its tools. Their calls to the board, memory and team chat fail. **Fix:** use `HOST=0.0.0.0`, which answers on every address including `127.0.0.1`, or keep the default and reach Clawboo through an SSH tunnel. Either way, set an access token. See [Reach Clawboo from another machine](/guides/self-host-securely). ### More remote-access messages > [accordion] STUDIO\_ACCESS\_TOKEN contains an unsupported character > > **What it means:** the token uses a character other than letters, digits, `.`, `_`, `~` and `-`, so the server refuses to start, on any address and even with `CLAWBOO_ALLOW_INSECURE=1`. The message starts with `SECURITY: refusing to start` and names the character. On 0.5.0 and older the server turned the token check off instead and kept running. > > **Fix:** make a new token with `openssl rand -hex 24`, restart the server with it, and sign in again. > [accordion] SECURITY: a request reached this server through a reverse proxy > > **What it means:** a request came through a proxy and the server has no access token, so anyone who can reach the proxy can use the dashboard and the API. The warning appears once per server start and never blocks a request. > > **Fix:** set `STUDIO_ACCESS_TOKEN` and restart, unless the proxy itself checks every request. > [accordion] Not found. This Clawboo is served under /clawboo/ > > **What it means:** the server runs under a path prefix (`CLAWBOO_BASE_PATH`), and the address you opened is outside it. The message names the prefix. > > **Fix:** open the address with the prefix, for example `http://localhost:18790/clawboo/`. > [accordion] clawboo stop says the server requires an access token > > **What it means:** the command cannot stop a server protected by an access token. > > **Fix:** stop it with Ctrl+C in its terminal, with your service manager, or with the command that `clawboo stop` prints. See [Token-gated installs](/reference/cli#token-gated-installs). ## When an agent is not doing what you asked Work through these causes in order. The first ones are the most common. 1. **The wrong runtime or model.** On the agent's page, the **Graph** card shows the model, beside the runtime's icon. Pick another model where the runtime lets you ([Change the model](/using/agents#change-the-model)). An agent cannot move to another runtime, so create a new one on the runtime you want. Also check who got the message. An @mention picks an agent only at the very start of the message; otherwise the team's leader answers and decides whether to hand the work on. 2. **No provider, or not the one the agent uses.** A Clawboo Native agent's page header reads **Disconnected** or **No provider key** when it has no provider it can use; click **Set up in Runtimes**. For the other runtimes, check that the runtime's row in **Settings > Runtimes** reads **Connected**. 3. **The model cannot use tools.** Agents hand off work, use the board and reach apps through tools. A model without them fails with "can't use tools, and this agent needs them", and the model list marks such Ollama models **(can't use tools)**. Pick a model that can. 4. **The agent never read your file.** Which agent files count depends on the runtime, and agents in team chat cannot always reach files on your computer. See [The agent ignored my file](#the-agent-ignored-my-file). 5. **The connector is not shared with that agent.** Connecting an app makes it available, but gives no agent access. Give it to the agent on the Org chart, a team graph or the agent's **Graph** card. See [Give a connector to an agent](/using/connectors#give-a-connector-to-an-agent). 6. **An approval is waiting.** A command, an app call or a risky hand-off can wait for your answer in **Needs you** on the **Board**. An unanswered risky hand-off is skipped after 5 minutes. See [Approvals](/using/approvals). 7. **A budget paused the work.** In **Settings > Insights > Governance**, a hard cap budget that reached its limit reads **paused**. Click **Resume**, or raise the limit with **Set cap**. See [Budgets and spend](/using/budgets-and-spend). 8. **The task is blocked.** A task in **Needs you** says why it stopped. Read the reason on its card, then use its button (**Retry**, **Assign**, **Review** or **Open**), or pick **Dismiss** from the card's **More** menu. See [A task waits in Needs you](#a-task-waits-in-needs-you). ## Report a bug **1. Search first** Look through the [open issues](https://github.com/clawboo/clawboo/issues) for your problem. For questions and ideas, use [Discussions](https://github.com/clawboo/clawboo/discussions). **2. Pick a template** Open a [new issue](https://github.com/clawboo/clawboo/issues/new/choose) and choose the template that fits: | Template | Use it for | | - | - | | **Bug Report** | Something is broken | | **Setup & install help** | `clawboo` will not start, or a runtime will not connect | | **Documentation issue** | A page in these docs is wrong, missing or unclear | | **Feature Request** | An improvement or a new feature | | **Runtime / adapter request** | Support for another agent runtime | **3. Say what happened** Include: * Your Clawboo version (`clawboo --version`), Node.js version (`node -v`) and operating system. * The runtime and model of each agent involved. * What you did, what you expected and what happened, as steps someone else can repeat. * The exact message you saw, and the last lines of `~/.clawboo/server.log`. * Any check in **System Health** that does not read **OK**. * For an OpenClaw problem, the OpenClaw version (shown in **Settings > System**, or run `openclaw --version`) and the last lines of `~/.clawboo/gateway.log`. **4. Remove secrets before you post** Take out: * Provider keys. They start with `sk-`, `sk-ant-`, `sk-or-` and similar. * Your `STUDIO_ACCESS_TOKEN`, and any address containing `access_token=`. * MCP attach URLs, which carry signatures, and the OpenClaw Gateway token. * Anything copied from `~/.openclaw/.env` or `~/.clawboo/secrets`. Clawboo's logger masks values that look like keys, but not everything in the log goes through it, so read what you paste. To report a security vulnerability, do not open a public issue. Report it privately, as the [security policy](https://github.com/clawboo/clawboo/security/policy) describes: on the repository's **Security** tab, choose **Report a vulnerability**. ## Related * [FAQ](/appendices/faq): short answers to common questions. * [Known issues](/appendices/known-issues): limitations we know about. * [Providers and runtimes](/using/providers-and-runtimes): connect keys, sign-ins and runtimes. * [Limits and defaults](/reference/limits-and-defaults): every timeout and limit. * [CLI reference](/reference/cli): every `clawboo` command and what it prints. * [Data and state](/operating/data-and-state): where files live, backup and reset. # AgentSource and the agent registry Source: https://docs.claw.boo/internals/agent-source How Clawboo records which agents exist: the AgentSource trait, its record types, the five registered sources, and the OpenClaw sync rules. `AgentSource` answers one question: where does the agent list come from? Clawboo's answer is that SQLite holds the record of every agent, and each upstream is one `AgentSource` that syncs into it. Reads come from SQLite, so the fleet renders even when a runtime is offline. Writes, agent-file reads and writes, and live session lists go to the source that owns the agent and mirror back. Use this page when you work on agent creation, deletion, sync, or the server's Gateway connection. For what an agent is from a user's point of view, see [the agent model](/concepts/agent-model); for the REST surface, see the [Agents API](/reference/rest-api/agents). ## What it is, and what it isn't `AgentSource` is about who exists. How an agent runs is the [`RuntimeAdapter`](/internals/runtime-adapter) trait, and the two layers never reach into each other: a source produces and persists `AgentRecord`s, an adapter executes a run. The board is a third, separate layer: it references agents by id and owns only task state. The trait and record types live in `@clawboo/agent-registry`, a pure, browser-safe package with no `dependencies` key at all. The concrete sources live server-side in `apps/web/server/lib/agentSource/`. ## The five registered sources The server registers five sources in one `AgentRegistry` (`apps/web/server/lib/agentSource/registry.ts`): | Source | `sourceId` | Upstream | Writes | New agent id | | - | - | - | - | - | | `OpenClawAgentSource` | `openclaw` | the OpenClaw Gateway | create and file calls need the server's Gateway connection; a delete made offline is queued | the Gateway's agent id | | `ClawbooNativeAgentSource` | `clawboo-native` | SQLite itself | always work | `native--<6 chars>` | | `RuntimeAgentSource` (x3) | `claude-code`, `codex`, `hermes` | SQLite itself | always work | `--<6 chars>` | `getRegistry()` returns a process-wide singleton that exposes them as `.source` (OpenClaw), `.nativeSource`, `.runtimeSources`, and the multiplexer as `.registry`. A `SIGTERM` or `SIGINT` handler stops the OpenClaw connection on shutdown. ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} flowchart TD REST["/api/agents"] --> REG[AgentRegistry] REG --> OCS[OpenClawAgentSource] REG --> NAT[ClawbooNativeAgentSource] REG --> RAS["RuntimeAgentSource x3"] OCS -->|"sync: agents.list in"| DB[(SQLite)] OCS -->|"writes and files out"| GW[(OpenClaw Gateway)] GW -.->|"presence, heartbeat, agent: resync"| OCS NAT <--> DB RAS <--> DB ``` One rule shapes every operation: registry reads come from SQLite, and writes go to the owning upstream and mirror back. A disconnected Gateway therefore degrades a write on an OpenClaw agent (`503`), never a registry read: `GET /api/agents` still answers, flagged `stale`. Listing an OpenClaw agent's sessions is the exception (see `listSessions` below). ## The AgentSource trait ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} export interface AgentSource { readonly id: RuntimeId // Reads: SQLite-backed, work even when the upstream is down listAgents(opts?: { includeArchived?: boolean; teamId?: string }): Promise getAgent(id: string): Promise listTeams(opts?: { includeArchived?: boolean }): Promise listSessions(agentId: string): Promise // Writes: require a live upstream, throw when disconnected createAgent(input: CreateAgentInput): Promise updateAgent(id: string, patch: UpdateAgentInput): Promise archiveAgent(id: string): Promise // Agent files: route through the source readFile(agentId: string, name: AgentFileName): Promise writeFile(agentId: string, name: AgentFileName, content: string): Promise // Lifecycle and observability start(): Promise stop(): Promise health(): Promise sync(): Promise // idempotent upstream-to-SQLite reconcile events(): AsyncIterable } ``` The contracts worth pinning down: * **`listSessions` is the one read that can fail on a disconnect.** Sessions change too fast to cache, so the OpenClaw source asks the Gateway live and throws `gateway_disconnected` when it is down. The SQLite-backed sources read the `sessions` table. * **`AgentFileName` is a local literal.** The package keeps its own copy of the seven names (`AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `TOOLS.md`, `HEARTBEAT.md`, `MEMORY.md`) so it stays dependency-free. Which files each runtime actually consumes is `AGENT_FILE_RUNTIME_SUPPORT` in `@clawboo/protocol`. * **`events()` is consumer-terminated**, like `RuntimeAdapter.events()`. The `AgentEvent` union is `agent-upserted`, `agent-status`, `agent-archived`, `sync-complete` and `connection`. * **`health()` reports a connection state** (`connected`, `connecting`, `reconnecting`, `disconnected`) plus the last sync time. The SQLite-backed sources always report `connected`. * **`sync()` is idempotent** and must preserve every column Clawboo owns. That rule is the core of the OpenClaw source. ## The record types `AgentRecord` is Clawboo's own shape, not an OpenClaw protocol shape, and a source adapts its upstream into it in both directions. Each field is annotated in `packages/agent-registry/src/records.ts` with who owns it: * **Upstream-owned** (overwritten on every OpenClaw sync): `displayName`, `emoji`, `avatarUrl`, `sessionKey`, `isDefault`, `sourceAgentId`. `status` is marked Gateway-synced, but the sync writes it only as `'idle'` on first insert and `'archived'` when the agent leaves the list. * **Clawboo-owned** (set by Clawboo and preserved across a sync): `teamId`, `personalityConfig`, `execConfig`, `avatarSeed`, `participantKind`, `runtime`, `capabilities`, `tenantId`. * **Clawboo's column, managed by sync**: `archivedAt`. The OpenClaw sync sets it when an agent leaves the Gateway's list and clears it when the agent returns. * **Optional, per source**: `model?` (the configured model, surfaced for the native and coding-runtime sources) and `providerReady?` (native only: whether a key, a fallback key, or a set-up keyless Ollama resolves for the agent's provider). `RuntimeId` is the same open set as the executor's (`'openclaw' | 'claude-code' | 'codex' | 'hermes' | (string & {})`). `AgentRecordStatus` is `idle | running | error | sleeping | archived`, a superset of the Gateway's status that adds the `archived` tombstone. `TeamRecord` carries a computed `agentCount`; `SessionRecord` carries the upstream session key as `sourceSessionId` and a status of `active`, `idle` or `closed`. The write inputs are thin. `CreateAgentInput` is `{ name, teamId?, personalityConfig?, execConfig?, avatarSeed?, files?, tenantId? }`, where `files` maps agent-file names to content written at create time and `tenantId` is resolved at the REST handler. `UpdateAgentInput` is `{ displayName?, teamId?, personalityConfig?, execConfig?, avatarSeed?, status? }`. > [!NOTE] > > `participantKind` (`'agent' | 'human'`) and `tenantId` are reserved fields. The only branch on `participantKind` is the wake bridge, which throws `NotImplementedError` for a human Routine target. No request filters by tenant: every row carries `tenantId: null`. ## The registry `AgentRegistry` is a thin catalog keyed by `RuntimeId`: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} export class AgentRegistry { register(source: AgentSource): void unregister(id: RuntimeId): void get(id: RuntimeId): AgentSource | undefined default(): AgentSource // first registered; throws if none has(id: RuntimeId): boolean list(): AgentSource[] } ``` `default()` returns the first registered source, which is the OpenClaw source. REST handlers route a per-agent operation by the row's `sourceId` (`sourceForAgent` in `apps/web/server/api/agents.ts`): look up the row, take `registry.get(row.sourceId)`, and fall back to the OpenClaw source for an unknown id so its `404` semantics hold. `GET /api/agents` calls `listAgents()` on every registered source and flattens the result. Alongside the list it returns `defaultId` (the runtime-neutral Boo Zero from `resolveBooZero`: an override, else the native Boo Zero, else the OpenClaw default), `mainKey`, `stale` and `lastSyncedAt` (both from the OpenClaw connection), and `pendingDeleteIds` (see [Pending deletes](#pending-deletes)). ## OpenClawAgentSource The OpenClaw source holds the server's own `GatewayClient`, separate from any browser tab, and reconciles the Gateway's agent list into SQLite. The client is injected through a `makeClient` dependency, so tests pass a fake that implements only the `OpenClawClientLike` slice the source uses. ### Reads and writes Reads (`listAgents`, `getAgent`, `listTeams`) select from SQLite (`listAgents` is scoped to `sourceId = 'openclaw'`, `getAgent` looks a row up by id alone, and teams are shared), parse the stored `identityJson` for the display name, emoji and avatar, and build the `sessionKey` as `agent::`. They work whether or not the Gateway is up. `createAgent`, `archiveAgent` and the file calls go through the Gateway and guard on a live connection with `requireClient()`, which throws `Error('gateway_disconnected')`; the REST layer maps that to `503`. * **`createAgent`** asks the Gateway for its config path, derives a `workspace-` directory beside it, calls `agents.create`, clears any pending-delete marker the returned id still carries, writes `SOUL.md`, `IDENTITY.md`, `TOOLS.md` and `AGENTS.md` through `agents.files.set` (browsing guidance goes into `AGENTS.md`, because the OpenClaw 2026.9 Gateway does not load `TOOLS.md`), tries `CLAWBOO.md` best-effort, applies the command-approval posture (below), and only then inserts the SQLite row. * **`updateAgent`** patches the Clawboo-owned columns in SQLite and does not touch the Gateway. * **`archiveAgent`** is a hard delete: it deletes the agent upstream, then removes the SQLite row, its cost records, its approval history, and its `boo-zero:display-name:` setting. * **`readFile` and `writeFile`** call `agents.files.read` and `agents.files.set`. > [!NOTE] > > The reversible `archivedAt` tombstone and the `archiveAgent` hard delete are different paths. Sync sets the tombstone when an agent disappears upstream and clears it if the agent comes back. `archiveAgent` is the delete a person asks for. ### The posture write that can fail a create The command-approval posture is written between the file writes and the SQLite insert, and it is the one step that can fail the whole call. * **What is written.** The caller's `execConfig.execAsk`, or `DEFAULT_EXEC_ASK` (`'on-miss'`, labelled Ask for Unknown in the dashboard) when the caller sends no `execConfig` at all. It goes through `applyExecApprovalPolicy` and `upsertExecApprovalPolicy` (`exec.approvals.set` under the Gateway's compare-and-swap, retried against a fresh read up to three attempts in all), keyed by OpenClaw's agent id, because the Gateway keys its policy by its own ids. * **When it is skipped.** When the value fails `isExecAsk` (it is not `'off'`, `'on-miss'` or `'always'`), including an `execConfig` with no `execAsk`. The agent is then created with no posture sent. * **What a refusal does.** The source deletes the agent it just created upstream (best-effort; a failure there is logged) and throws `Could not set this agent's command permissions on the Gateway, so it was not created: `. The SQLite insert never runs, so there is no half-created row. `POST /api/agents` answers `500` with that message. A Gateway that is simply down never gets this far: `requireClient()` throws first and the route answers `503`. Nothing re-asserts a posture later. The only other write site is `POST /api/exec-settings`, and there a Gateway refusal answers `502 { ok: false, savedLocally: true }`, leaving the refused value in Clawboo's `exec_config`. `sync()` never touches the approvals store. See [Command permissions](/using/command-permissions). ### The idempotent sync `sync()` first sends any deletes it owes (see [Pending deletes](#pending-deletes)), then calls `agents.list()` and hands the result to `upsertFromList`, which runs the whole reconcile in one transaction: 1. **Upsert.** Each listed agent is inserted, or on conflict updated with a `set` clause that touches only Gateway-owned columns: `name`, `gatewayId`, `sourceAgentId`, `identityJson`, `archivedAt: null` (revival) and `updatedAt`. It never writes `teamId`, `personalityConfig`, `execConfig`, `avatarSeed`, `participantKind`, `runtime`, `capabilities` or `tenantId`, so a re-sync can never clobber an agent's team, personality or avatar. 2. **Archive.** Any live `openclaw` row missing from the list gets `archivedAt` and `status = 'archived'`, and an `agent-archived` event fires. 3. **Metadata.** The list's `defaultId`, `mainKey` and `scope`, plus the sync time, are upserted into the `settings` table inline (the transaction handle is not typed as a `ClawbooDb`, so `setSetting` cannot be reused there). Re-running the sync against an unchanged Gateway leaves every Clawboo-owned column exactly as it was. The dashboard pushes the list it reads from the Gateway to `POST /api/agents/sync`, which runs the same `upsertFromList`, so SQLite stays current even when the server's own connection is degraded. ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} flowchart LR L["agents.list() result"] --> U[upsertFromList] U -->|"set = Gateway-owned columns only"| A[(agents)] U -->|"missing upstream: archive"| A U -->|"defaultId, mainKey, scope, lastSynced"| S[(settings)] ``` ### Pending deletes `DELETE /api/agents/:agentId` with the Gateway down still removes the local row, and for an OpenClaw agent it records an `agent-source:openclaw:pendingDelete:` row in `settings`. The response is `{ ok: true, upstreamDeleted: false, upstreamDeletePending: true }`. * **What sync does.** `sync()` sends `agents.delete` for every pending id before it lists. While a marker exists, `upsertFromList` skips that id, so the agent is never revived or re-created, whether the list came from the server's sync or the dashboard's `POST /api/agents/sync` push. `POST /api/teams/:id/agents` refuses to assign a pending agent with `409 agent is being deleted`. * **What clears a marker.** The delete lands; a list arrives without the agent; the Gateway refuses the delete outright (a `GatewayResponseError` with code `INVALID_REQUEST` or `retryable: false`, logged as a warning, after which the agent comes back on that same sync); or `createAgent` gets the same id back from `agents.create`. * **What keeps it.** A transport failure leaves the marker for the next sync. `GET /api/agents` exposes the markers as `pendingDeleteIds`, and the dashboard's Gateway hydrate leaves those agents out of the fleet. ### Connection lifecycle `start()` opens the connection. With no Gateway URL configured, the source stays `disconnected` and reads still serve SQLite. On a successful connect it: 1. registers Clawboo's MCP servers in the Gateway config, at the top-level `mcp.servers` key: `clawboo-memory`, `clawboo-tasks` and `clawboo-tools`, all unbound (one process-wide config cannot carry a per-agent identity). TeamChat is deliberately not registered, and a stale `clawboo-teamchat` entry is pruned; 2. in the same `config.patch`, unions `sessions_spawn` and `sessions_yield` (plus any ids the operator listed) into the Gateway's top-level `tools.deny`, so a team agent cannot launch its own throwaway sub-agents. Clawboo records the ids it wrote so it can later take back only its own; 3. runs an initial `sync()`. The patch is skipped when nothing changed, because the Gateway rate-limits control-plane writes. The `onStatus` callback flips the connection state and schedules a resync on reconnect; `onGatewayEvent` schedules one on every `presence`, `heartbeat` or `agent` frame. Resyncs are debounced by 750 ms, so the registry tracks fleet changes without polling. Reconnect ownership is split. The source owns only the retry after an initial connect fails: exponential backoff from 2 s to 60 s, `.unref()`'d so it never holds the process open. An auth-class failure (bad token, unpaired device, bad connect params, a lockout) backs off to at least 2 minutes, honouring the Gateway's `retryAfterMs`, so retries never trip the Gateway's failed-auth lockout. Once a connection has opened, the `GatewayClient`'s own reconnect loop (800 ms to 15 s) owns later drops. The two are gated on disjoint conditions, so they never race. ### The operator surface Beyond the trait, the source exposes `operatorCall`, `onGatewayBroadcast`, `onConnectionChange`, `operatorClient` and `reconnectAndWait`. They ride the same single connection, and they are what makes OpenClaw the runtime Clawboo drives over a live connection rather than a spawned process. Their consumers: * the team engine, which runs every OpenClaw team-chat turn through `buildOpenClawServerAdapter()`; * the Routines wake bridge, which runs OpenClaw routine fires through a thin dispatcher; * the `openclaw-gateway-cron` schedule source and the OpenClaw capability source; * the exec-approval surface and the session activity watcher (see [Gateway connection internals](/internals/gateway-and-events)). `reconnectAndWait(timeoutMs)` (default 8,000 ms) is the delivery-resilience primitive. When a team-chat send finds the connection down, the engine reconnects, waits for it, and retries the delivery once, persisting a visible "could not reach your OpenClaw agents" message only if that also fails. This works because the Gateway Clawboo starts is spawned detached with its output in `gateway.log` in the Clawboo state directory, so it survives a server restart. ## ClawbooNativeAgentSource The native source has no remote upstream: SQLite is its upstream. Reads and writes are direct SQL, `start()` and `sync()` do nothing, `stop()` only drops event listeners, `health()` always reports `connected`, and writes never throw `gateway_disconnected`, so a native agent can be created, edited and deleted with no Gateway at all. * **Storage.** Agent files and the per-agent `AgentConfig` live in `settings` rows under a per-agent key prefix; sessions come from the `sessions` table the native harness writes. * **`SOUL.md` is the persona.** `writeFile('SOUL.md', ...)` re-derives `AgentConfig.systemPrompt`, which is what a native run reads; an emptied file falls back to the default prompt, and the same resolved text is stored, so the editor never shows one thing while the agent runs another. * **`createAgent`** mints a `native--<6 chars>` id, validates the config with `agentConfigSchema`, inserts the row, stores the config and files, and, when the config sets `budgetUsd`, creates an agent-scope budget in `cap` mode. * **`archiveAgent`** is a hard delete of the row, its cost records, approval history and sessions, its config and file rows, and its team-session resume pointers. The `archivedAt` tombstone is unused, because a source with no remote upstream can never see an agent "gone upstream". * **`getAgent` is scoped.** It returns `null` for a row owned by another source, which is what lets the registry aggregate every source's list without overlap. ## RuntimeAgentSource One `RuntimeAgentSource` (`apps/web/server/lib/agentSource/runtimeAgentSource.ts`) is registered for each coding runtime in `CODING_RUNTIME_SOURCE_IDS` (`claude-code`, `codex`, `hermes`, listed in `registry.ts`). Like the native source, SQLite is its upstream: reads and writes are direct, `start()` and `sync()` do nothing, and it is always `connected`. Unlike the native source it keeps no `AgentConfig`. The server runs these agents from the row's `runtime`, the runtime's own credentials, and the task or message it delivers. Files given at create time (filtered to the seven agent-file names) are stored in `settings` under `runtime-agent-file::`. Of those, only `SOUL.md` reaches a prompt: the server injects it as a `[Your persona]` block on both the team-chat and board paths (`runtimes/personaBlock.ts`). `AGENTS.md` draws the agent's routing edges on the Org chart. A coding-runtime agent's model, when set, lives in `execConfig` and is surfaced as `model`. `archiveAgent` hard-deletes the row and its cost records, approval history, sessions, file rows and team-session resume pointers. ## Cleanup is scoped to OpenClaw rows `POST /api/agents/cleanup-ghosts` deletes local rows that are missing from a supplied list of live Gateway ids. It compares only rows with `sourceId = 'openclaw'`, and so does the sync's archive step. > [!CAUTION] > > Keep that scoping on any sweep that reconciles against a Gateway list. The list comes from the Gateway, so only Gateway-owned rows may be compared against it. An unscoped sweep would delete every native and coding-runtime agent the moment a Gateway list arrived. ## Server connect requirements (OpenClaw 2026.5 and later) A headless Node connection to the Gateway is not a browser connection, and the differences are easy to break. Four options in the registry's `connectOptions()` are load-bearing: | Option | Why | | - | - | | `clientName: 'cli'` | The Gateway checks `client.id` against a fixed allowlist. A custom id such as `clawboo-server` is rejected (`invalid connect params: at /client/id`, the socket closes with `1008`). The browser ids (`openclaw-control-ui`, `webchat-ui`) also require a browser `Origin`. `cli` has no origin rule. | | `origin` and `webSocketImpl` | The global undici WebSocket sends no `Origin`, which trips `CONTROL_UI_ORIGIN_NOT_ALLOWED`. The source injects the `ws` package's WebSocket and presents the Gateway host's own origin (`resolveServerOrigin`), the same recipe the proxy uses upstream. | | `disableDeviceAuth: true` and `signConnect` | The browser device path needs `crypto.subtle` and `localStorage`, which Node lacks. The source skips it and signs the connect with the already-paired proxy device identity. If signing throws, the connect proceeds without device fields. | | `caps: ['tool-events', 'exec-approvals']` | The Gateway sends tool-call frames only to connections that declared `tool-events`, and routes held commands only to connections that declared `exec-approvals`. The latter is declared only because the approval handler exists: a declaring connection that never answers turns a fast refusal into a 30-minute hang. | > [!WARNING] > > These options travel together. Changing `clientName` away from an allowlisted programmatic id, or dropping the injected `WebSocket` or `origin`, leaves the server's connection permanently disconnected. That shows up not as a connection error but as `503`s on every OpenClaw agent write and a fleet that renders from stale SQLite. Verify against a running Gateway, not only a typecheck. ## Design rationale The central decision: SQLite is the source of truth for who exists, and each upstream is the source of truth for how its runtime runs. That buys an offline-tolerant dashboard (reads never need the Gateway), lets a new runtime plug in as a new source behind the same interface, and keeps Clawboo's own configuration (team, personality, avatar) in columns no upstream can overwrite. The cost is a second persistence layer and a sync discipline that must be exact. Restricting the upsert's `set` clause to Gateway-owned columns is what keeps a routine resync from overwriting Clawboo's configuration. The SQLite-backed sources pay a different price: they get offline writes for free but use hard deletes rather than a reversible tombstone, because nothing remote can go missing. ## Boundaries * **Not the runtime.** A source never executes a turn; that is `RuntimeAdapter`. * **Not the board.** No agent identity, files or session state live in board tables. * **One team per agent.** An agent row has a single `teamId`. * **Reserved fields.** `participantKind: 'human'` and per-tenant filtering are not implemented. ## Related * [The agent model](/concepts/agent-model): what an agent is, Boo Zero, and runtime classes * [The RuntimeAdapter trait](/internals/runtime-adapter): the parallel "how they run" layer * [Capability and schedule sources](/internals/seams): the multiplexers that mirror this pattern * [Gateway connection internals](/internals/gateway-and-events): the browser proxy and the server's Gateway connection * [Agents API](/reference/rest-api/agents): the REST surface over the registry * [Database schema](/reference/database-schema): the `agents`, `teams` and `sessions` tables # Architecture invariants Source: https://docs.claw.boo/internals/architecture-invariants The structural rules every Clawboo change keeps, from SQLite as the agent record to the browser-only Gateway proxy path, and the code that enforces each one. An architecture invariant is a structural rule the codebase holds no matter what changes around it. Break one and a whole class of bug becomes possible: the fleet list disappears during a Gateway outage, an edge on the Org chart stops meaning anything, or the browser ends up holding a credential it should never see. This page lists the seven invariants and the code, test or lint rule that enforces each. Read it before a change that touches the agent registry, the Gateway connection, a package boundary, the Org chart, or the database schema. ## What counts as an invariant An invariant is something you can point a grep, a test or one file at and say "this holds, and here is where it would break". It is not: * **a setting.** Nothing turns an invariant off; * **a style convention**, such as where files live or how components are named, which review enforces; * **a behavioural guarantee** spread across many files, such as "team chat never floods with dozens of replies", which tests and the coordination code uphold. ## The model ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} flowchart TB subgraph Browser["Browser (dashboard)"] Pipeline["Bridge, Policy, Handler (3)"] end subgraph Server["Clawboo server"] Proxy["Gateway proxy: /api/gateway/ws (2)"] REST["REST API and agent registry"] SQLite[("SQLite: who exists (1)")] Source["Server Gateway connection"] end Gateway["OpenClaw Gateway"] Browser -->|"same-origin WebSocket only"| Proxy Browser -->|"GET /api/agents"| REST REST --> SQLite Proxy -->|"token and device signature added"| Gateway Proxy -->|"frames"| Pipeline Source -->|"sync in: Gateway-owned columns only"| SQLite Source <--> Gateway ``` Numbers refer to the invariants below. ## The seven invariants ### 1. SQLite records who exists; each upstream owns how its runtime runs SQLite is the record of every agent: the `agents` and `teams` rows are canonical for who exists. The server registers five agent sources: one for the OpenClaw Gateway, one for Clawboo Native, and one SQLite-backed source each for Claude Code, Codex and Hermes. The OpenClaw source syncs the Gateway's list into SQLite over the server's own connection. The dashboard reads the fleet from `GET /api/agents`, which serves every source from SQLite and flags `stale: true` when the OpenClaw connection is down, so the fleet still renders with the Gateway offline. The rule that makes this safe is a column-scoped sync. When the Gateway's list is upserted (`upsertFromList` in `apps/web/server/lib/agentSource/openClawAgentSource.ts`), the conflict-update clause writes only Gateway-owned columns: `name`, `gatewayId`, `sourceAgentId`, `identityJson`, `archivedAt` and `updatedAt`. It never writes `teamId`, `personalityConfig`, `execConfig`, `avatarSeed`, `participantKind`, `runtime`, `capabilities` or `tenantId`, so a re-sync can never undo a team assignment or a personality setting. The Gateway stays canonical for what the registry does not own: live sessions and the execution stream, exec approvals, cron jobs and runtime config. Clawboo drives those over two connections. The browser's proxied connection carries 1:1 chat with an OpenClaw agent (`client.call('chat.send', ...)` in `apps/web/src/features/chat/chatSendOperation.ts`); the server's own connection runs OpenClaw team-chat turns and Routine fires, and the Gateway cron list. See [AgentSource](/internals/agent-source). ### 2. The browser reaches the Gateway only through the same-origin proxy The browser never talks to the Gateway directly. Its only path is the proxy route on its own origin: `resolveProxyGatewayUrl` (`packages/gateway-client/src/helpers.ts`) builds `ws(s):///api/gateway/ws` from `window.location` and the app's base path, so there is nothing to point at a remote host. * Every browser caller connects with `disableDeviceAuth: true`. The proxy injects the upstream token server-side, so the server never sends the saved credential to the browser, only a `hasToken` flag from `GET /api/settings`. The browser sends a token only when the operator types a new one on the connect screen. The proxy also signs the connect frame with a persistent Ed25519 device identity, so no browser context needs its own device key. * The server's upgrade handler (`apps/web/server/index.ts`) accepts exactly `/api/gateway/ws` or `/api/gateway/ws`. It runs the always-on Origin and Host guard first and answers a refused handshake with `403 Forbidden`, then hands the socket to the proxy. Every other upgrade path is destroyed. * The proxy's own `allowWs` check repeats the path, the Origin guard and the optional access-token gate, so a request that slips past the router is still refused. The only other connection to the Gateway is the server's own (`OpenClawAgentSource`), signed with the same proxy device identity. See [Gateway connection internals](/internals/gateway-and-events). ### 3. Every Gateway frame the browser receives goes Bridge, then Policy, then Handler In the browser, every Gateway event frame passes through one three-stage pipeline with no shortcuts. `processEvent` in `@clawboo/events` makes the contract explicit: classify the frame (`classifyEvent`), derive intents (`derivePolicy`), apply them (`handler.applyIntents`). The `useGatewayEvents` hook is its only caller and holds one `client.onEvent` subscription, so a frame takes exactly one path. The middle stage is the load-bearing one: Policy is pure. `derivePolicy` turns a classified event into an `EventIntent[]` and dispatches nothing, which is why it is unit-tested without a Gateway, a store or a clock. Only the Handler holds state. The rule covers frames the browser receives. The server's consumers of the same Gateway (the registry resync, the session activity watcher, the exec-approval surface, and the OpenClaw runtime adapter) use their own code and map frames to `RuntimeEvent`s where they need to. A new cross-cutting browser concern belongs on an intent, never on a second raw-frame subscriber. See [The event pipeline](/internals/event-pipeline). ### 4. Packages never import apps Dependencies flow one way: `apps/` depends on `packages/`, never the reverse. That is what keeps every `@clawboo/*` package independently buildable, browser-safe where it claims to be, and free of app coupling. A repo-wide search for a package `src/` file importing from `apps/` (or `@clawboo/web`) finds no import statements, only comments that name a caller. ESLint enforces it (`eslint.config.mjs`): `no-restricted-imports` with the `PACKAGES_TO_APPS` patterns on `packages/**`. The same file draws the boundary inside `apps/web`: the Express server may not import the browser code and the browser code may not import the server, with extra selectors that catch dynamic `import()` calls. `apps/web/server/lib/__tests__/importBoundary.test.ts` pins that `apps/web`'s lint script covers `server/` as well as `src/`, so the server rule cannot be silently dropped. Anything two layers share belongs in a package. ### 5. Every graph edge maps to real state The Org chart and the team graphs draw edges only from real configuration or board state (`apps/web/src/features/graph/useGraphData.ts`). There are four edge kinds: * **Dependency edges** (agent to agent) come from each agent's `AGENTS.md` routing rules: `parseAgentsMd` resolves `@mentions` and routing phrases such as "route to @Name" against the fleet's names and yields one edge per target, an instruction the agent will act on. * **Skill and resource edges** (agent to capability) come from real `CapabilityRecord`s in the capability inventory, and the server-evaluated `available` flag decides whether a node renders greyed out. * **Grant edges** connect an agent to a grant-backed connector capability. The edge is the authorization, so it carries the grant's id, state and mode. To make the tree readable the graph also adds structure, never decoration. Boo Zero links to each team, and on the Org chart invisible `team-root` junctions group a team's members. These edges encode the real team-membership hierarchy and are tagged `isSynthetic: true`, so a consumer that reads `AGENTS.md` routing can tell them apart. Three node kinds come from the graph layer rather than from capability records: Boo Zero's Leadership orbital (skill id `clawboo-leadership`), a Model orbital on every agent (skill id `clawboo-model`, showing the runtime's default label when the model is unknown), and a "+N more" overflow tile (skill id `more`) that stands in for capabilities that did not fit the ring. The `clawboo-` ids are reserved so they cannot collide with a real capability. ### 6. Every Boo is a real agent record Each Boo on a graph is backed by a real `AgentRecord`: the graph builds one node per agent in the registry, with no placeholder or mock agents. Boo Zero is a real record too (teamless in the database); the graph places it at the root of the tree, draws it slightly larger and gives it the Leadership orbital, but it is an agent, not an invented node. The corollary lives on the server. Anything that reconciles local rows against a Gateway list is scoped to `sourceId = 'openclaw'`, because the list came from the Gateway and only Gateway-owned rows can be missing from it. The sync's archive step and the `POST /api/agents/cleanup-ghosts` route both apply that scope, and the dashboard keeps SQLite current by pushing the Gateway list it reads to `POST /api/agents/sync`. A native or coding-runtime agent can never be mistaken for a ghost of an OpenClaw list. ### 7. The schema is idempotent and additive There is no migration ladder. The `CREATE TABLE IF NOT EXISTS` block in `ensureSchema` (`packages/db/src/schemaBootstrap.ts`) is the only schema source and declares every table, index and trigger on a fresh database. Three things keep it honest: * **No migration runner ships.** The `@clawboo/db` package's `files` excludes `drizzle`, and there is no `db:migrate` or `db:generate` script (only `db:studio`). `schemaSource.test.ts` pins this posture. * **The type layer and the DDL agree.** `schemaSource.test.ts` builds a real database through `createDb()` and asserts that every table and column in the Drizzle `schema.ts` type layer matches the live DDL, and the other way round. * **An existing database is reconciled up to the DDL.** `IF NOT EXISTS` skips the whole `CREATE TABLE` when the table exists, so a new column would never reach an older database on its own. `reconcileSchema` derives the declared columns from the same DDL and adds what is missing, before the DDL batch runs (an index over a new column cannot exist until the column does). See [Database schema](/reference/database-schema#upgrading-an-existing-database). A database opens with one idempotent DDL block, once per process for the server, which then holds one connection for its lifetime. > [!CAUTION] > > The in-place upgrade is additive only. A new column on an existing table must be addable: it may not be `PRIMARY KEY`, `UNIQUE` or a `STORED` generated column; its `DEFAULT` must be a constant, not an expression or `CURRENT_TIMESTAMP`; a `NOT NULL` column needs a non-null `DEFAULT`; and a `REFERENCES` column may not have a non-null `DEFAULT`. A declaration that breaks a rule fails the test suite. If one ever reaches a user, the bootstrap throws a message naming the column and the remedy, and the boot probe reports a fatal `databaseSchema` check. Changing an existing column's type or constraints is not an in-place upgrade; a removed column stays, unused, in existing files. ## Design rationale The invariants make whole categories of failure impossible rather than merely caught. * **SQLite as the agent record (1)** keeps the dashboard working when the Gateway is down, because who exists lives locally and only how an agent runs needs the upstream. The cost is a second persistence layer and the sync discipline that keeps the two from fighting. * **The same-origin proxy (2)** makes the browser structurally unable to read the saved Gateway token or the device key. The cost is an extra hop and a proxy that must inject auth correctly on every connect. * **The pure Policy stage (3)** makes decision logic testable with no Gateway, store or clock, at the cost of three stages where a naive version would dispatch inline. * **One-way dependencies (4)** keep packages buildable and browser-safe, at the cost of keeping app glue in `apps/`. * **Real edges and real records (5, 6)** give a graph you can trust: what you see is what the system will do. The cost is the synthetic-edge tagging and the source-scoped reconciliation. * **The idempotent schema (7)** makes a fresh install need no migration step and makes the upgrade path free to maintain, because it is derived from the DDL. The cost is that a column can be added to an existing database but not changed in place. ## Boundaries * **Structural, not behavioural.** The invariants fix the shape of the system: who is canonical, who talks to whom, which way dependencies point. They do not prove a feature behaves correctly; tests do that. * **Invariants 2 and 3 are about the OpenClaw Gateway.** The other runtimes never use the proxy or the browser pipeline; they emit `RuntimeEvent`s on the server through their adapters (see [the RuntimeAdapter trait](/internals/runtime-adapter)). * **Additive schema only.** A change that must rewrite a column in place is not supported by the upgrade path and needs a deliberate design. ## Related * [AgentSource](/internals/agent-source): the sources and the sync rule behind invariant 1 * [Gateway connection internals](/internals/gateway-and-events): the proxy, the guards and the server connection behind invariant 2 * [The event pipeline](/internals/event-pipeline): Bridge, Policy and Handler (invariant 3) * [Monorepo and build](/internals/monorepo-and-build): the package layout invariant 4 protects * [Database schema](/reference/database-schema): the tables the DDL declares (invariant 7) * [Contributing](/appendices/contributing): how to propose a change # Board internals Source: https://docs.claw.boo/internals/board-internals The board data-access layer: the atomic claim, the transactional state machine, rooms, dependencies, the fire policy, SQLite contention and crash recovery. [The board](/concepts/the-board) is where Clawboo keeps task state. This page is for people working on its data-access layer: the module that owns every board write, the conditional `UPDATE` that makes single-assignee work race-free, the state machine enforced inside a write transaction, the SQLite recipe that keeps many concurrent writers safe, the recursive queries over dependencies and parents, and the passes that recover stuck work. ## The modules Everything lives in `packages/db/src/board/`: | Module | Holds | | - | - | | `repository.ts` | Every board read and write: create, claim, status changes, dependencies, comments, workspaces, execution rows, reconciliation. | | `contention.ts` | `withWriteRetry` and `immediateWrite`, the write recipe. | | `schemas.ts` | Zod shapes for REST bodies and for the ancestor walk's raw rows. | | `verification.ts` | `setTaskVerification`, which persists a verdict for the `done` gate. | | `events.ts` | The in-process lifecycle bus every write publishes to after it commits. | | `rooms.ts` | Chat rooms: the `rooms` and `room_members` tables. | | `attempts.ts` | `classifyAttempts`, which reads a task's execution ledger as crash, error, success or cancelled attempts. | | `attention.ts`, `runCost.ts`, `runningSince.ts` | Read helpers the board REST routes attach to each task: why it needs a person, what its runs cost, when its current run started. | The pure rules sit one package over in [`@clawboo/board-core`](/reference/packages/board-core): `state-machine.ts` (the transition table), `dispatch.ts` (the fire policy), `attention.ts` (why a task needs a person) and `runCost.ts`. `@clawboo/db` re-exports the transition helpers, `TASK_STATUSES` and `summarizeRunCost` unchanged, and wraps the fire policy and the attention rule in its own repository and read helpers. `board-core` imports nothing outside its own files, which is what lets the dashboard bundle the same rules the server enforces. The repository takes the transition table and fire policy from `board-core`, and the creation caps plus the shared verdict rule `isVerdictPromotable` from `@clawboo/governance`. The connection pragmas are set in `packages/db/src/db.ts`. The layer is the only code that touches the board tables: `tasks`, `task_deps`, `task_comments`, `workspaces` and `execution_processes`, plus the chat-room tables in `rooms.ts`. `apps/web` never reaches them with raw Drizzle; it calls a repository function. Reads that could be tenant-scoped take an optional `scope` with a `tenantId`, but nothing passes one: every row has `tenant_id` null. ## The state machine `@clawboo/board-core`'s `state-machine.ts` holds a `TaskStatus` union of seven values and one transition table: ```ts theme={"theme":"github-dark"} const LEGAL: Record = { backlog: ['todo', 'blocked', 'cancelled'], todo: ['in_progress', 'blocked', 'backlog', 'cancelled'], in_progress: ['in_review', 'done', 'blocked', 'todo', 'cancelled'], in_review: ['done', 'in_progress', 'blocked', 'cancelled'], blocked: ['todo', 'in_progress', 'backlog', 'cancelled'], done: [], cancelled: [], } ``` `canTransition(from, to)` is `true` when `from === to` (re-emitting a transition is harmless) and otherwise checks `LEGAL[from]`. `legalTargets(from)` returns that row as a fresh array, which is how the dashboard's status editor offers only legal moves. `isTerminal(status)` is true for `done` and `cancelled` (a test pins it to an empty `legalTargets`), and `isLocked(status)` is true for `in_progress` and `in_review`, where a task is owned and its assignee must not change. One table serves three consumers: the repository enforces it, `@clawboo/team-orchestration` types its board client with `TaskStatus`, and the board UI derives its columns and editor from `TASK_STATUSES` and `legalTargets`. `updateStatus` in the repository enforces the table inside the write: ```mermaid theme={"theme":"github-dark"} flowchart TD A["updateStatus(taskId, to, opts)"] --> B["BEGIN IMMEDIATE, read the row"] B --> D{row exists?} D -->|no| RNF[not_found] D -->|yes| E{"canTransition(from, to)?"} E -->|no| RIL[illegal_transition] E -->|yes| F{"to done, verdict not promotable, no humanOverride?"} F -->|yes| RVR[verification_required] F -->|no| CA{"completedAt outside createdAt to now + 5 min?"} CA -->|yes| RCA[invalid_completed_at] CA -->|no| G["UPDATE; after commit emit status_changed"] ``` The row is read inside the `BEGIN IMMEDIATE` transaction, so two concurrent callers cannot both pass a stale check; a REST-layer pre-check is only fast-fail ergonomics. `PATCH /api/board/:taskId` maps `not_found` to `404`, `invalid_completed_at` to `400` and the rest to `409`. Three side effects ride the same write: * **The first move to `done` or `cancelled` stamps `completedAt`, once.** Writing the same terminal status again (a run's late result after a person marked the task done) leaves it alone. `opts.completedAt` back-dates the stamp for a person marking work done on the day it was really done; it must fall between the task's `createdAt` and now plus five minutes of clock skew (a time inside the skew is recorded as now), or the move is refused with `invalid_completed_at`. * **Any move to `todo` releases the task.** It clears `assigneeAgentId`, `assigneeRuntime` and the stored `verification` verdict, from any source status. Clearing the assignee lets the [claim](#the-atomic-claim) re-acquire the task (its guard requires a null assignee); clearing the verdict means a previous runtime's failing verdict cannot block a fresh runtime's completion. `releaseTask` does the same three-field clear directly, for a task still `in_progress`, and publishes `task_released`. * **A person's move is stamped.** With `opts.actor: 'person'` the write sets `personMovedAt` to now. An agent's own write through the Tasks MCP tools (`update_task_status`, `block_task`, `unblock_task`) passes `opts.unlessPersonMoved`, and is refused with `person_moved` when `personMovedAt` is later than the task's latest run start (the latest, across its executions, of each one's `startedAt`, else `claimedAt`, else `createdAt`), or when the task has no execution yet. The run the person overruled cannot put the card back; a run started after the move writes as usual. The two recovery passes write outside the table on purpose: they release stuck `in_progress` and `in_review` work straight to `todo`, and `in_review` to `todo` is not a legal transition. See [Orphan and stale reconciliation](#orphan-and-stale-reconciliation). After the transaction commits, `updateStatus` publishes `status_changed` on the lifecycle bus, so no subscriber can act on an uncommitted change. `opts.actor: 'person'` rides on the event when a person made the move; only the board's `PATCH` route sets it, and subscribers leave a person's move to that route, which stops the run working on the task and tells the delegator (see [`PATCH /api/board/:taskId`](/reference/rest-api/board)). A person's move back to `todo` also wakes the team's dispatch pump (the loop that starts the next ready task, described under [The fire policy](#the-fire-policy)). ### The `done` gate The move to `done` is gated inside the board itself: ```ts theme={"theme":"github-dark"} if (to === 'done' && !opts.humanOverride && !verdictCellPromotable(row.verification)) { return { ok: false, reason: 'verification_required' } } ``` `verdictCellPromotable` is a light read of the stored JSON cell. The full zod parse happens on write (`setTaskVerification`); here an inline `JSON.parse` plus the shared `isVerdictPromotable` rule avoids importing `verification.ts`, which would create an import cycle with `getTask`. | Stored cell | Promotable | Why | | - | - | - | | `null` or absent | yes | An unverified task is not failing. The gate blocks only a verdict that exists and fails. | | unparseable | yes | Cannot tell, so do not block. | | `pass` | yes | Verified. | | `completed_with_debt` over a passing command check | yes | Debt covers open critic findings, never a failing build or test command. | | `completed_with_debt` over a failing command check | no | A red command check routes to a person. | | `fail` | no | Known to fail. | Only an explicit `opts.humanOverride: true` bypasses the gate. `PATCH /api/board/:taskId` accepts `humanOverride` and records the bypass in the governance audit log; the Tasks MCP `update_task_status` tool and the team engine cannot pass it. Two server paths set it too: an empty-diff worktree completion (`apps/web/server/lib/worktrees.ts`), which has nothing to verify, and the OpenClaw Routine dispatcher (`routines/openclawDispatch.ts`), which records the run as unverified first. > [!NOTE] > > `isVerdictPromotable(null)` in `@clawboo/governance` returns `false`, while `verdictCellPromotable(null)` returns `true`. They do not contradict each other: the repository calls the shared rule only when a cell exists, because a missing verdict means unverified, which the gate deliberately allows. Do not pass the null straight through. ## The atomic claim A task is worked by one assignee at a time. `claimTask` guarantees it with one conditional statement rather than a read followed by a write: ```sql theme={"theme":"github-dark"} UPDATE tasks SET assignee_agent_id = ?, assignee_runtime = ?, status = 'in_progress', updated_at = ? WHERE id = ? AND status = 'todo' AND assignee_agent_id IS NULL AND dropped = 0 RETURNING * ``` Because the guard is part of the statement that writes, at most one concurrent caller wins. The winner gets the row back through `RETURNING` and a `task_claimed` lifecycle event fires; every loser gets zero rows. A zero-row result is data, not an error. `claimTask` checks whether the row exists and returns `{ ok: false, reason: 'conflict' }` (someone else owns it) or `{ ok: false, reason: 'not_found' }`, which REST maps to `409` and `404`. The rule the whole system follows is never retry a `409`: a retry would either do nothing or fight for work that is already taken. > [!TIP] > > The never-retry rule is structural. The claim runs inside `withWriteRetry`, which retries only transient SQLite lock errors and returns a zero-row result unretried, so an application conflict and database lock contention travel through different mechanisms. See [the contention recipe](#the-contention-recipe). Re-claiming an abandoned `in_progress` task is not the claim's job: its `WHERE` matches only `todo`. Liveness lives in one place; [reconciliation](#orphan-and-stale-reconciliation) releases such a task to `todo`, and an ordinary claim re-acquires it. Reconciliation reaps only work that has a `running` execution. An `in_progress` task with no assignee and no running execution was dragged there by hand, which starts nothing, so no pass will move it. `isStrandedInProgress` recognizes it, the Needs you rule reports it as `unstarted`, and a person hands it back: `assignTaskDelegation` (re-bind to an agent and release to `todo` in one `BEGIN IMMEDIATE` transaction) and the retry route both accept it, as they accept a `todo`, `backlog` or `blocked` task. ## Rooms Every task carries a `room_id`. Null means the team's General chat; a string names a room. That chat's engine runs the task, and a task whose room was archived falls back to General (`roomIdForTask` in `apps/web/server/lib/teamChat/roomRoster.ts`). Repository reads that take a room filter (`listTasks`, `getReadyTasks`) read `undefined` as every room, `null` as General only, and a string as that room only. `rooms.ts` stores a team's extra conversations in `rooms` and `room_members`. General has no row. A room's live roster is its stored members intersected with the team's active agents, so an agent that is archived or moves to another team drops out of every room without a sweep. Names are whitespace-collapsed and capped at `ROOM_NAME_MAX` (80) characters; deleting a room archives it and keeps its conversation. ## Dependencies and the recursive queries Tasks form a blocks and blocked-by graph in `task_deps`, with a composite primary key on `(task_id, depends_on_task_id)` that prevents duplicate edges. `linkDep` writes an edge inside `immediateWrite`: it refuses a self-link, then probes reachability with a recursive query and throws `TaskDependencyCycleError` when the new edge would close a direct or transitive cycle. Only then does it insert, with `onConflictDoNothing`, so re-linking an existing edge stays harmless. Probe and insert share one transaction, so two concurrent links cannot race a cycle into the graph. ```sql theme={"theme":"github-dark"} WITH RECURSIVE dependencies(id) AS ( SELECT depends_on_task_id FROM task_deps WHERE task_id = :dependsOnTaskId UNION SELECT td.depends_on_task_id FROM task_deps td JOIN dependencies dep ON td.task_id = dep.id ) SELECT id FROM dependencies WHERE id = :taskId LIMIT 1 ``` A cycle is refused at write time because nothing downstream can recover from one: every task in it would stay un-ready forever and nothing would report it. `TaskDependencyCycleError` carries `code: 'task_dependency_cycle'`; the `link_task` MCP tool returns it as a tool error, and the REST dependency route answers `409` with that code. A task is **ready** when it is `todo`, not dropped, and every dependency is `done`. `getReadyTasks` expresses the last part with `NOT EXISTS (SELECT 1 FROM task_deps d JOIN tasks dt ON dt.id = d.depends_on_task_id WHERE d.task_id = tasks.id AND dt.status != 'done')` and orders by priority, then `updatedAt`, both descending. It accepts a team and a room filter. Two recursive reads walk the graph: * **`getDependents`** walks downstream: every task transitively blocked by a given one. It returns raw rows (the caller filters by status). `cancelDependents` uses it to cancel only the still-pending (`todo` or `backlog`) members when a blocker fails or a person cancels it, and returns them so the engine can report the stalled plan; tasks already running, done or cancelled are untouched. * **`getAncestors`** walks the parent chain through `parent_task_id` and returns `{ id, parent_task_id, title, status }` per ancestor, parsed with `ancestorRowsSchema` (the codebase's rule is not to trust a type over raw query output). It uses `UNION`, not `UNION ALL`, so the walk terminates even on a corrupt parent cycle. `getAncestors` counts depth at two call sites that read the same `DEFAULT_MAX_DEPTH` (2): the team engine refuses to delegate from a task already at the cap (`ancestors.length >= max`, since the child would be one past it), and the executor runner refuses to dispatch a task past it (`ancestors.length > max`). The split is what makes the deepest creatable task also runnable. ### Capped creation The Tasks MCP create tools call the repository's bounded creates, which return a result rather than throwing (a throw would reach the model as a protocol error instead of a tool result): * **`createCappedSubtask`** does the parent lookup, an indexed count of the parent's live children, a step-bounded parent-chain walk and the insert in one `BEGIN IMMEDIATE` transaction. It refuses with `parent_not_found`, `child_cap` (24 live children, `DEFAULT_MAX_CHILDREN`) or `depth_cap` (`DEFAULT_MAX_DEPTH`, 2). One transaction matters because the Tasks MCP runs as a stdio process per attached runtime on the shared file, so a count-then-insert gap would let two agents both add the 25th child. It walks the chain itself because `getAncestors` takes a connection rather than a transaction handle, and a hard step bound cannot spin on corrupt data while holding the write lock. * **`createCappedRootTask`** guards parentless tasks with a rolling-window rate instead: 30 live roots per 5 minutes (`DEFAULT_MAX_ROOT_CREATES`, `DEFAULT_ROOT_CREATE_WINDOW_MS`), refused with `root_rate_cap`. Rows created through REST or Routines count too, so an agent cannot launder rows through another surface; dropping rows frees quota early. The team engine, the REST board and the evals harness create their tasks, parented ones included, with the uncapped `createTask`. `createSubtask` is a thin uncapped wrapper over it that inherits the parent's team and room; only tests call it. Every count comes from board state, never from the model. ## The fire policy The dispatch pump starts ready tasks and re-fires released work, and `ledgerAllowsAutoFire` in `@clawboo/board-core` (`dispatch.ts`) decides whether a ready task may fire, from its execution ledger: | Last runs in the ledger | Fires? | Why | | - | - | - | | none | yes | Never delivered (fresh, deferred or newly assigned). | | last run `running` | no | Something owns it. | | last run `cancelled` | no | A person stopped it; only a person re-queues it. | | `MAX_AUTO_FIRES` (3) unsuccessful runs in a row at the end | no | Parked in Needs you instead of being fed forever. | | otherwise (`failed`, `timed_out`, an orphan reaped as failed) | yes | Infrastructure death, not intent. | A success or a `cancelled` run resets the streak (`trailingFailedRuns`). The ledger is the counter, so the cap survives a restart. The repository re-exports the rule as `isLedgerAutoFireable`, and `listRoomsWithFireableDelegations` (with `listTeamsWithFireableDelegations` on top) tells the server's dispatch pump which (team, room) engines have a ready task bound to an agent whose ledger allows a fire. A task's bound agent is the `:agent:` segment of its `sourceDelegationId` (`delegationTargetOf`), written by every engine delegation and by a person assigning a task on the board. `attempts.ts` reads the same ledger differently: `classifyAttempts` separates a process that keeps dying (an orphan reap or a stale sweep) from a run that keeps failing, and the executor runner turns the result into the interrupted-work note it adds to the next attempt's prompt. ## Worktree pointers and execution rows The board owns pointers into the worktree subsystem, not the worktree. `setTaskWorkspaceRefs` records `worktreeRef` and `branchRef` on the task, and a `workspaces` row tracks the checkout from `active` to `archived` (cleanup) or `stale` (garbage collection). `POST /api/board/:taskId/workspace` refuses to provision a worktree for a `research` or `review` task with `422 no_isolation`. See [Worktrees and handoff](/concepts/worktrees-and-handoff). Each run is an `execution_processes` row, a per-run ledger for any executor. `createExecutionProcess` opens it as `running` (optionally with the git commit before the run); `completeExecutionProcess` closes it with an outcome (`succeeded`, `failed`, `timed_out` or `cancelled`), optional token and cost figures, and the git commit after the run. The close is guarded on `status = 'running'`, so the first terminal wins and a losing writer gets `null` back and must not emit a second `execution_completed`. That guard matters because the ledger feeds the fire policy, where `cancelled` means a person's Stop: a late abort overwriting a swept `timed_out` row would turn an infrastructure death into a Stop and park re-fireable work for good. By convention a row is opened only after a successful claim, because crash recovery reads this ledger on restart. Each row also records its run's cost: `costUsd`, its tokens, and a `costSource` of `reported` (the runtime priced the run), `local` (a model the user's own Ollama served, at \$0), `token-estimate` or `char-estimate` (Clawboo estimated it). * The team engine's drain writes them at the end of a run through `recordExecutionUsage`, which matches the row whatever its status, because a Stop or the idle watchdog can close the row before the run's own terminal arrives. It touches only the cost, token and `runtime` columns, never the status, completion time, summary or error, so first-terminal-wins still holds. * `tasks.cost_usd` is the sum of the task's rows, recomputed from the ledger after each cost write (in the same transaction for `recordExecutionUsage`) rather than added to, so rewriting a run's cost never counts it twice. * The board reads each task's total, whether any of it is estimated, and whether all of it ran locally, with `runCostForTasks` (one ledger read per board) and the pure `summarizeRunCost`. `runningSinceForTasks` reads when each task's newest running execution started, which is how an In progress card shows how long its run has gone. ## The contention recipe Many agents may write one SQLite file, and processes outside the server (the MCP stdio servers a runtime spawns) open the same file the server uses. Without care, concurrent writers pile up behind SQLite's single-writer lock. The answer is five connection pragmas plus two application-level pieces. Every connection sets these pragmas when it opens (`openDb` in `packages/db/src/db.ts`). The server opens exactly one connection at boot; each MCP stdio server opens its own: | Pragma | Value | Why | | - | - | - | | `journal_mode` | `WAL` | Readers and one writer proceed concurrently. | | `foreign_keys` | `ON` | Referential integrity is enforced. | | `synchronous` | `NORMAL` | The usual WAL balance of durability and speed. | | `busy_timeout` | `250` (ms) | Wait a quarter-second for the write lock, then hand over to the jittered retry below. | | `wal_autocheckpoint` | `50` (pages) | SQLite's own passive checkpoint keeps the WAL small without an application-level write counter. | `contention.ts` adds: * **`withWriteRetry(fn)`** runs a synchronous write and retries only transient lock errors (`SQLITE_BUSY`, `SQLITE_BUSY_SNAPSHOT`, `SQLITE_LOCKED`, as `isBusyError` recognizes them) with a jittered backoff of 20 to 150 ms, until a wall-clock budget runs out: 1,500 ms by default (`CLAWBOO_DB_WRITE_BUDGET_MS`), with a 24-attempt backstop. The budget is a deadline rather than a count because the retry sleep blocks the calling thread, which in the server is the event loop; worst case is the budget plus one `busy_timeout`, about 1.75 s. On exhaustion it throws `WriteBudgetExhaustedError`, which keeps `code = 'SQLITE_BUSY'` so existing callers still recognize it. A zero-row result is returned unretried, which is what lets callers honour never-retry-a-409; any other error propagates at once. * **`immediateWrite(db, cb)`** runs `cb` in `db.transaction(cb, { behavior: 'immediate' })`. `BEGIN IMMEDIATE` takes the write lock up front instead of upgrading mid-transaction, which avoids lock-upgrade deadlocks. It is itself wrapped in `withWriteRetry`, so the whole transaction re-runs on a transient lock error. `updateStatus`, `assignTaskDelegation`, `linkDep`, the capped creates and both reconciliation passes run through it. > [!WARNING] > > The budget applies per outermost write, not per request: a handler doing three writes can block for three times the worst case. It also relies on the rule that an `immediateWrite` body uses its `tx` handle directly and never calls a `withWriteRetry`-wrapped function. Nest them and the budgets multiply. The retry sleep is synchronous and does not spin: it uses `Atomics.wait` on a throwaway `SharedArrayBuffer`. `better-sqlite3` is fully synchronous, so making every repository method `async` just to await a backoff would cost more than it gives. The recipe is exercised by `packages/db/src/board/__tests__/board.contention.test.ts`, which runs 20 worker threads against one database file when `CLAWBOO_CONCURRENCY_TEST=1` (CI's `test` job sets it). ## Orphan and stale reconciliation Two recovery passes keep stuck work from piling up: one for crashes, one for abandonment. Each runs in a single `immediateWrite` transaction, publishes its `task_released` events only after commit, and never blocks boot. Both release work straight to `todo`, bypassing the transition table. **`reconcileOrphans` runs once at startup and is liveness-aware.** It reaps a `running`, un-tombstoned execution row only when its task row is missing or the task's `updatedAt` is older than `opts.staleAfterMs` (the package default is `6 * TASK_HEARTBEAT_MS`; the server passes the same `CLAWBOO_BOARD_STALE_TTL_MS` the sweep uses). A row whose task still beats belongs to a live process, such as a second Clawboo on the same state directory or the previous server's drains during a fast restart, and must survive boot. Each reaped execution is marked `failed` with `orphaned: process not alive on restart` and `recovery_tombstone = 1`, so a second pass is a no-op, and its task is released to `todo` if it is `in_progress` or `in_review`, clearing the assignee, the assignee runtime and the verdict. A run that died but is not yet stale at boot keeps missing beats, and the interval sweep releases it once the TTL passes. **`reconcileStaleInProgress` runs at boot and then on an interval.** It releases an `in_progress` task whose owner has stopped proving it is alive, whether that owner died with a previous server or wedged inside the current one. A task that is `in_progress`, not dropped, last updated before the TTL cutoff, and has a `running` execution is timed out (the execution becomes `timed_out` with `stale: no heartbeat within the watchdog window`) and released to `todo`. A task with no running execution has nothing beating it and is skipped. The `task_released` event detaches the task from any team engine still mapping it to a session and wakes that team's dispatch pump. The server wires it as one immediate sweep plus a `setInterval(...).unref()` loop. > [!NOTE] > > `tasks.updatedAt` is a liveness signal. Every drain that claims a task also beats it: `startTaskHeartbeat` bumps `updatedAt` every `TASK_HEARTBEAT_MS` on a timer, not on events, so a run that is working silently keeps beating. The beat is what makes a short TTL safe, so a drain that claims a task without beating it will have its live work swept. There are three: the executor runner, the team engine's `serverDeliver`, and the OpenClaw routine dispatcher. Any new one must beat too. The timers that interact with these passes: | Timer | Default | Override | What it bounds | | - | - | - | - | | `TASK_HEARTBEAT_MS` | 30 s | none | How often a drain beats its task. | | Stale TTL | 3 min | `CLAWBOO_BOARD_STALE_TTL_MS` | Missed beats before the sweep (and the boot reap) treats a run as dead. | | Stale sweep interval | 60 s | `CLAWBOO_BOARD_STALE_SWEEP_MS` | How often `reconcileStaleInProgress` runs. | | Engine watchdog sweep | 30 s | none | How often each chat's engine checks its delegations. | | `DELEGATION_IDLE_TIMEOUT_MS` | 8 min | none | A delegate silent this long is failed by its engine. | | `OPEN_TOOL_CALL_TIMEOUT_MS` | 24 min | none | The allowance while a delegate is inside an unresolved tool call. | | `PENDING_START_TIMEOUT_MS` | 24 min | none | The allowance for a delegation still waiting to start behind another run. | | `HOME_MUTEX_ACQUIRE_MS` | 10 min | `CLAWBOO_HOME_MUTEX_ACQUIRE_MS` | The wait for a persistent-home agent's run lock before the start fails. | The engine's idle watchdog is the first responder for a delegate that goes quiet, and it runs on the server whether or not a browser is open. Its idle window restarts when the run starts, so time spent waiting for a lock never counts against the 8 minutes. The stale TTL plus the sweep interval must stay under that 8-minute window: the sweep's `task_released` is what detaches a stale session before the watchdog reaches it. ## Board changes in the peer room After a board REST route writes a change, `reflectToRoom` (`apps/web/server/lib/teamChat/reflect.ts`) posts a `kind: 'system'` line authored by `clawboo` into the team's agent peer room (the `team_chat` table, read by the TeamChat MCP tools). That room is not the General chat a person sees. The post runs after the write, best-effort, and is never a way back into the board: the board is the authority and the room line is narration. See [Peer chat](/concepts/peer-chat). ## Schema: no migration ladder The `CREATE TABLE IF NOT EXISTS` block in `ensureSchema` (`packages/db/src/schemaBootstrap.ts`) is the only schema source: it declares every table and column on a fresh database. Upgrading an existing file is `reconcileSchema`, which is derived from the same DDL and only adds: it `ALTER TABLE ... ADD COLUMN`s whatever an existing database is missing and leaves extra columns alone, so an older file keeps a removed column harmlessly. Changing a column's type or constraints is not an in-place upgrade. A new column must be addable to a table that already has rows; the rules are on [Database schema](/reference/database-schema#upgrading-an-existing-database). The package ships no `db:migrate` or `db:generate` script (only `db:studio`) and no `drizzle/` directory. `schema.ts` is the Drizzle type layer over the same tables, used for typed queries and `$inferSelect` / `$inferInsert`, never to migrate. `schemaSource.test.ts` keeps the two in step: it builds a database through `createDb()`, reads each table's columns with `PRAGMA table_info`, and asserts they match the type layer both ways. It compares column names only (type, `NOT NULL`, `DEFAULT`, keys and indexes are not checked, because the Drizzle-to-PRAGMA mapping is lossy), excludes the FTS5 tables, and also pins that no migration runner ships. `schemaReconcile.test.ts` guards the upgrade path: it holds every column added after the snapshot in `schemaBaseline.ts` to the addability rule. Re-snapshot that file only to record a deliberate exception, never to make a failing test pass. ## Design rationale The board is a hand-written repository rather than a generic ORM facade, for the same reason the claim is a conditional `UPDATE` rather than a read-modify-write: the properties that matter (one assignee, legal transitions, crash recovery) are easier to see and to test as explicit single-purpose functions. The pure state machine and the contention helpers are split out so each can be reasoned about alone: transition legality with no database, lock handling with no business logic. SQLite with the WAL recipe is a deliberate choice: it runs in-process, needs no database server, and handles team-scale write contention. The cost is a second persistence layer beside each runtime's own session state, and a recovery story (the two passes and the stale TTL) that holds only as long as every claiming drain beats its task. ## Boundaries * **Not the agent or session registry.** The board references agents, runtimes and sessions by id, with no foreign key, and owns no agent identity, agent files or live session state. Its foreign keys all point inside the board: `parent_task_id` at the parent task, each `task_deps`, `task_comments`, `workspaces` and `execution_processes` row at its task, and an execution row at its workspace. See [AgentSource](/internals/agent-source). * **Not a general-purpose tracker.** The statuses, transitions and recovery passes are tuned for runtime-driven work. * **Single tenant.** Every board table has a `tenant_id` column, always null; no read filters by it. ## Related * [The board](/concepts/the-board): the user-level model this layer implements * [The executor runner](/internals/executor-runner): the consumer that claims, runs, verifies and releases * [Verification](/concepts/verification): the gate behind `verification_required` * [Database schema](/reference/database-schema): every table and column * [Board API](/reference/rest-api/board): the REST surface over these functions # Design system Source: https://docs.claw.boo/internals/design-system How the dashboard theme works: tokens in globals.css, the light and dark switch, surface tiers, motion, type, team colors, and the shared UI components. The dashboard's look lives in one CSS file and a small runtime. Every color, shadow, font, motion curve and type step is a CSS custom property in [`apps/web/src/app/globals.css`](https://github.com/clawboo/clawboo/blob/main/apps/web/src/app/globals.css), and switching the whole theme is one class on ``. A set of shared components in `apps/web/src/features/shared/` packages those tokens for the patterns that repeat. Use this page when you change UI: add a component, touch a color, or change how the theme switches. `globals.css` is the source of truth for every value below. ## Rules for UI code * **Theme tokens, never raw hex.** Use the CSS variables and the Tailwind utilities built on them. Brand marks are the only exception. * **Lucide icons only.** Never an emoji in the UI. * **A shared component before a new one.** Check [the shared components](#shared-components) first. * **Overlays dismiss through `useDismissableLayer`.** A capture-phase key listener fails lint. * **Muted text uses `--muted-foreground`,** not a low-opacity `text-foreground/N`. A test holds it to the contrast floor. * **A filled button uses the `-solid` token** (`bg-primary-solid`, `bg-destructive-solid`) under a white label. See [Contrast](#focus-and-contrast). ## Tooling Tailwind 4 runs through `@tailwindcss/postcss` (`apps/web/postcss.config.mjs`), and `globals.css` starts with `@import 'tailwindcss'`. The shadcn/ui config is `apps/web/components.json`; shadcn components are set up per app, not shipped from `@clawboo/ui`. ## The model ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} flowchart TD subgraph tokens["globals.css"] root[":root: light values"] dark[".dark: dark overrides"] theme["@theme inline: --color-* map to var(--*)"] end root --> theme dark --> theme theme --> utils["Tailwind utilities: bg-surface, text-mint, shadow-tier-floating"] theme --> tiers["Tier classes: .surface-raised-tier and friends"] utils --> comp["Components"] tiers --> comp tp["ThemeProvider"] -->|"adds or removes .dark on html"| dark init["inline script in index.html"] -->|"sets .dark before React mounts"| dark ``` The indirection is what makes the theme switch at runtime. A utility such as `bg-surface` resolves to `--color-surface`, which the `@theme inline` block maps to `var(--surface)`, which `:root` sets to `#ffffff` and `.dark` sets to `#111827`. Flip the class and every `bg-surface` element repaints, with no rebuild. Component code names a semantic token, never a color. ## Theme architecture ### `@theme inline` aliases Near the top of `globals.css`, a Tailwind 4 `@theme inline` block holds pure aliases: `--color-surface: var(--surface)`, `--color-mint: var(--mint)`, and so on. Tailwind generates utilities from the `--color-*` names, and each one points at a second variable that each theme redefines. The `inline` keyword is what makes Tailwind read the value from the cascade at runtime instead of baking it in at build time. Just above that block, `@custom-variant dark (&:where(.dark, .dark *))` makes `dark:` utilities follow the `.dark` class rather than a media query. The app decides the theme itself, including a System mode that resolves to light or dark. ### `:root` light, `.dark` overrides Light is the default. `:root` declares every token with its light value and `color-scheme: light`; `.dark` redeclares the same names with dark values and `color-scheme: dark`. The light theme is paper white (`--background: #f8fafc`, `--surface: #ffffff`, slate-900 text). The dark theme uses `--background: #0a0e1a`, `--surface: #111827` and `#e8e8e8` text. Brand colors are deeper in light mode for contrast on white: OpenClaw Red is `#d82947` in light and `#e94762` in dark, mint `#059669` and `#34d399`, amber `#d97706` and `#fbbf24`. A few tokens look the same in both themes: * **Terminal surfaces:** `--terminal-bg: #0d1117` and `--terminal-fg: #e8e8e8`, declared with the same values in `:root` and `.dark`, so install and Gateway log windows always read as a terminal. * **Motion tokens:** declared once in `:root` (see [Motion](#motion-and-reduced-motion)). ### The one-class flip Two pieces apply the `.dark` class, and they must agree: 1. **`ThemeProvider`** (`apps/web/src/features/theme/ThemeProvider.tsx`) is a React context with the preference `theme: 'system' | 'light' | 'dark'` and the result `resolvedTheme: 'light' | 'dark'`. It reads `localStorage['clawboo.theme']` and defaults to `'light'` when nothing is stored, so onboarding happens in light mode. It follows `prefers-color-scheme` only while the preference is System, and adds or removes `.dark` on `document.documentElement` whenever `resolvedTheme` changes. `useTheme()` returns `{ theme, resolvedTheme, setTheme }` and throws outside the provider. The provider wraps the app in `apps/web/src/app/providers.tsx`. 2. **An inline script in `apps/web/index.html`** runs before React mounts, so the page never flashes the wrong theme. It repeats the provider's logic: read `clawboo.theme`, default to `'light'`, resolve System through `matchMedia`, set `.dark`. The switch the user clicks is `ThemeToggle` (`apps/web/src/features/theme/ThemeToggle.tsx`): a segmented System, Light and Dark radio group with icons only (Monitor, Sun, Moon). Each segment calls `setTheme` directly, and the System segment's tooltip shows the mode it resolves to, such as "System (dark)". > [!NOTE] > > The inline script and `ThemeProvider`'s resolver are deliberate copies, because the script must run before any bundle loads. Keep the storage key, the light default and the System branch identical in both. A hard refresh that flashes the wrong theme means they have drifted. Components that draw their own themed surfaces read `resolvedTheme` and pick a matching theme object; the code editor, for example, exports `clawbooEditorThemeDark` and `clawbooEditorThemeLight`. ## Surface elevation The visual hierarchy has four tiers. Tier 1 is the base `--surface`. Tiers 2 to 4 each bundle a background, border, shadow and (for the glass tiers) a backdrop blur into one class, so a card, popover or modal gets its elevation from one `className`. | Tier | Class | Used for | Treatment | | - | - | - | - | | 1 | (`bg-surface`) | The base panel surface | Opaque base | | 2: Raised | `.surface-raised-tier` | Cards in normal flow (marketplace cards) | Opaque lift + subtle shadow | | 3: Floating | `.surface-floating-tier` | Popovers, dropdowns, context menus, tooltips | Glass: translucent + `blur(16px) saturate(140%)` | | 4: Overlay | `.surface-overlay-tier` | Modals, sheets, command palettes, wizards | Glass: translucent + `blur(20px) saturate(140%)` + lifted shadow | The raised tier uses `--surface-raised` (`#fdfdfe` light, `#141b2e` dark), `--border` and `--shadow-raised`. The floating and overlay tiers use translucent surfaces (`--surface-floating`, `--surface-overlay`), borders that read on glass (`--border-floating`, `--border-overlay`) and heavier shadows (`--shadow-floating`, `--shadow-overlay`). Shadows follow the theme: soft and slate-tinted in light mode, a deep black drop in dark mode. A tier class brings only background, border, shadow and blur. Radius, padding, an accent border and positioning stay on the element. A glass tier sits over a scrim: `--overlay-scrim` (modal strength) or `--overlay-scrim-soft` (sheet strength). `--code-block-bg` is the matching token for code rows and command previews. ```tsx theme={"theme":{"light":"github-dark","dark":"github-dark"}} // Modal: overlay tier, with a separate scrim element behind it … // Card: raised tier as a base, accent border kept inline … ``` The shadows also exist alone as utilities (`shadow-tier-raised`, `shadow-tier-floating`, `shadow-tier-overlay`), generated from the `--shadow-tier-*` aliases, for when you want the shadow without the rest of the tier. ## Motion and reduced motion Motion tokens are the same in both themes and live in `:root`: | Token | Value | Use | | - | - | - | | `--motion-fast` | `150ms cubic-bezier(0.4, 0, 0.2, 1)` | Hover, focus, button press | | `--motion-base` | `200ms cubic-bezier(0.4, 0, 0.2, 1)` | State changes, tab switches | | `--motion-emphasized` | `300ms cubic-bezier(0.32, 0.72, 0, 1)` | Modal or sheet enter | | `--motion-easing-standard` | `cubic-bezier(0.4, 0, 0.2, 1)` | Easing only, for custom `transition` | | `--motion-easing-spring` | `cubic-bezier(0.32, 0.72, 0, 1)` | Spring-feel easing only | The first three are full duration-and-easing values for a `transition` (`transition: border-color var(--motion-fast)`); the last two are curves for when you pick your own duration. Framer Motion springs cover choreography that CSS transitions cannot express. Reduced motion is handled in three places: * A global `@media (prefers-reduced-motion: reduce)` rule cuts CSS animation and transition durations to `0.001ms`, freezes the skeleton shimmer to a static tint, and sets `scroll-behavior: auto`. * One `` in `app/providers.tsx` makes Framer Motion follow the setting. Framer Motion does not do that on its own. * The graph's two `requestAnimationFrame` loops (the idle float in `useFloatingMotion` and the relaxation pass in `graphPhysics`) are out of CSS's reach, so they read the setting through `lib/prefersReducedMotion.ts`. The float stays parked, and the relaxation runs to rest in one pass, so nodes land where the animation would have left them. Both follow live changes to the OS setting. ## Focus and contrast A global `:focus-visible` rule draws a 2px ring (`outline: 2px solid var(--primary)`) on every keyboard-focused control. CodeMirror, which draws its own focus, and React Flow's pane and handles are excluded. `.react-flow__node` is not excluded, because nodes are real Tab stops; Boo nodes draw the ring on their inner `.group`, since the node wrapper is a large transparent box around a smaller shape. `--muted-foreground` holds at least 4.5:1 against the five surface tokens the test checks (`--background`, `--surface`, `--card`, `--popover`, `--surface-raised`) in both themes. `apps/web/src/app/__tests__/tokenContrast.test.ts` parses `globals.css` and enforces the floors: every text token clears 4.5:1 on every surface it is painted on, every status color clears 3:1, and both button-label-on-fill pairs clear 4.5:1. Its `KNOWN_DEBT` list is empty; adding to it means shipping text below AA on purpose. The brand red is two tokens, because one value cannot do both jobs in dark mode. As text, the red must be light enough to read on dark surfaces; under a white button label, it must be dark enough for the label to read. | Token | Role | Dark | Light | | - | - | - | - | | `--primary` | Text, accents, focus ring | `#e94762` (4.52:1 as text) | `#d82947` | | `--primary-solid` | The fill under a white label | `#c83b53` (4.99:1 with white) | aliases `--primary` | `--destructive` and `--destructive-solid` follow the same pattern. Light mode has no conflict, so the `-solid` tokens alias their base there. Use `bg-primary-solid` or `bg-destructive-solid` when a filled button carries `text-primary-foreground` or `text-destructive-foreground`, and plain `bg-primary` for tints, rings and borders. The contrast test checks the label pairs against the `-solid` fills, and `apps/web/src/app/__tests__/solidFillPairing.test.ts` fails any `.tsx` file that puts a label token on a plain `bg-primary`, `bg-destructive` or `var(--primary)` fill. ## Type scale Two typefaces cover three tokens. `--font-display` and `--font-body` are both **Inter** (loaded from Google Fonts), told apart by weight and letter spacing. `--font-mono` is Geist Mono, a local `@font-face` from a woff2 file, for code and data. The `@theme inline` block exposes them as the `font-display`, `font-body` and `font-mono` families. The custom steps fill the gaps in Tailwind's default scale (`text-xs` 12, `text-sm` 14, `text-base` 16, `text-lg` 18, `text-2xl` 24): | Token | Size / line height | Utility | For | | - | - | - | - | | `--text-micro` | 11 / 14 | `text-micro` | Micro labels, count pills | | `--text-compact` | 13 / 18 | `text-compact` | Compact agent and row names | | `--text-section` | 22 / 28 | `text-section` | Section headers | | `--text-page` | 28 / 34 | `text-page` | Page titles | | `--text-hero` | 40 / 46 | `text-hero` | The welcome hero, big headlines | Titles of 18px and up use `--font-display` (Inter, bold) with negative letter spacing; body text and small headings use `--font-body`; code, tool-call lines, timestamps and metric columns use Geist Mono. The `.font-data` class bundles mono with `tabular-nums`, so numeric columns (cost, tokens, ports, durations) line up and do not jitter as values change. ## Brand palette and team colors ### Brand colors The brand is three accents (OpenClaw Red as primary, mint and amber) plus a category palette. Each accent has a per-theme value and an `rgb` triplet companion (`--primary-rgb`, `--mint-rgb`, `--amber-rgb`, `--foreground-rgb`), so code can build a translucent overlay as `rgb(var(--mint-rgb) / 0.2)` instead of a fixed alpha hex. | Token | Light | Dark | Role | | - | - | - | - | | `--primary` | `#d82947` | `#e94762` | OpenClaw Red: accent, emphasis | | `--mint` | `#059669` | `#34d399` | Success, working, done | | `--amber` | `#d97706` | `#fbbf24` | Warning, pending | The **category palette** (`--category-data`, `--category-comm`, `--category-code`, `--category-file`, `--category-web`, `--category-other`) gives each classification a stable color for skill, cron and source badges. `comm` aliases mint and `file` aliases amber; `data`, `code` and `web` are their own hues, one step deeper in light mode, and `other` is a neutral gray in both. Mix opacity with `color-mix(in srgb, var(--category-X) N%, transparent)`. > [!NOTE] > > `@clawboo/ui` also exports a `tokens` constant of literal hex values and font names. It does not track `globals.css` (its accent is `#E94560` and its fonts are not Inter), and it does not switch with the theme. Never use it for themed UI. ### Team color collections A team picks a color collection, not individual colors, and each of its Boos gets a distinct color generated from it. The recipes are in `apps/web/src/lib/teamPalettes.ts`, and the team's choice is the `color_collection_id` column on `teams`. There are eight collections: `vivid-pop`, `dusty-pastel-pro`, `coastal-mist`, `executive-jewel`, `sharp-saas`, `soft-neutral-editorial`, `monochrome-accent` and `classic`. `generateTeamColors(collectionId, count, theme, hueRotation)` builds a generative palette. It divides the OKLCH hue wheel into `count` even steps from the recipe's `hueOffset`, alternates lightness (so a team still reads as distinct in grayscale or with color-vision deficiency), picks the lightness band by theme, nudges any adjacent pair that ends up too close, and maps each result into sRGB with culori's `clampChroma` (lower chroma, keep hue and lightness). A per-team `hueRotation` from `hueRotationFromSeed` (an FNV-1a hash of the team id, 0 to 359 degrees) shifts which hues appear, so two teams on the same collection and size do not look identical. `classic` is the default. It holds a fixed list (the Boo tints without Boo Zero's red) and ignores the OKLCH fields, and `pickBooColor` in `apps/web/src/lib/resolveTeamBooColor.ts` gives each agent `resolveBooTint(agentId)`, a stable per-agent hash. The tint list itself is `TINTS` in `@clawboo/boo-avatar` (re-exported by `@clawboo/ui`): index 0 (`#ff4d4d`, OpenClaw Red) is reserved for Boo Zero, and every other agent hashes into `TINTS[1..9]`. The create-team dialog mints the team id on the client, so the palette it previews (seeded by that id) is the one the deployed team gets. Choosing a collection in the dashboard is covered in [Teams](/using/teams). ## Shared components The app-wide building blocks live in `apps/web/src/features/shared/`. Each one bundles the right tokens, so it stays correct in both themes, accessible and safe under reduced motion. Reach for one before you write a new pattern. | Component | What it gives you | | - | - | | `Button`, `IconButton` | Every button treatment: variants `primary`, `solid`, `secondary`, `outline`, `ghost`, `danger`; sizes `sm`, `md`, `lg`. | | `Chip` | A rounded filter pill with an optional icon and active state. It is a button only when given an `onClick`; otherwise it renders as a label. | | `Switch` | A toggle switch (`role="switch"`, keyboard support, `aria-checked`), brand red when on. | | `Tabs` | An underline tab bar: the active tab gets a foreground label and a red underline. | | `SegmentedControl` | A pill track with the active segment as a raised chip, for small mutually exclusive choices. | | `SearchInput` | A search field with a leading icon, an optional clear button and the red focus ring; `quiet` drops the border for tight rows. | | `PanelHeader` | The shared panel header: optional icon, title, subtitle and a right-aligned actions slot. | | `Modal` | The app's one modal: scrim, `role="dialog"` with `aria-modal`, focus trap and return, Escape, and the enter and exit motion. Variants `center` and `drawer`. | | `ConfirmDialog` | Replaces `window.confirm`. Mounted once at the app root and driven by `confirm()` from `stores/confirm.ts`. | | `FloatingCard` | A side card beside a chat (agent graph, files, team graph) with a slim header for title, actions, maximize and close. | | `GlossTerm` | A jargon tooltip: a dotted underline with a definition popover on hover or focus. | | `CommandStream` | A terminal-style log box, always dark. Pair it with `useCommandLog` for the 200-line capped append pattern. | | `Skeleton` | A shimmer placeholder (`width`, `height`, `radius`) that freezes to a static tint under reduced motion. `aria-hidden`. | | `Spinner` | A `Loader2` spinner that becomes a static ring under reduced motion. `aria-hidden`. | | `StatusPill` | The mono, uppercase status indicator. `tone` is `working`, `done`, `idle`, `warning`, `success` or `error`; leave out `label` for a dot only. | | `EmptyState` | The branded empty state: icon disc, Lucide icon, title, helper text and an optional action. `tone` is `neutral`, `mint`, `amber` or `primary`. | | `FormattedAlert` | An in-flow callout strip with a leading icon. `tone` is `info`, `warning` or `error`; errors use `role="alert"`, the others `role="status"`. Not for toasts. | | `Select` | A custom listbox popover, not a native `select`: arrow-key navigation, check marks, viewport flipping and an optional search filter. `size` is `sm` or `md`. | | `ErrorBoundary` | The error boundary and its fallback card (Try again, Reload). `variant` is `app`, `panel` (default) or `compact`. | | `LazyBoundary` | `ErrorBoundary`, `Suspense` and a retry that re-imports, in one wrapper. Every lazily loaded surface renders through it. | | `ResizeHandle` | The divider between two resizable panels: a groove line and a drag grip. | | `SECTION_LABEL` (`sectionLabel.ts`) | The class recipe for uppercase mono section labels. | Three hooks sit in the same folder: `useDismissableLayer` (below), `useFocusTrap` (moves focus into a dialog, keeps Tab inside it, and restores focus on close) and `useMenuKeyboard` (the WAI-ARIA menu keyboard pattern with a roving tab index, used by the context menus). ### Dismissal: one stack, one owner Overlays do not each listen for Escape. `useDismissableLayer.ts` keeps one `keydown` and one `pointerdown` listener on `document`, registered at module load, and hands each event to the topmost open layer only. `Modal`, `Select`'s popover, `ConfirmDialog`, the context menus and the model pickers register there while open. Two rules set the order: * A `popover` layer always outranks a `dialog`, so a dropdown inside a modal closes on its own and the modal, with its half-filled form, waits for the next Escape. * The listener runs in the bubble phase, so a handler nearer the target (a React `onKeyDown`, CodeMirror's keymap) runs first and can claim the key by calling `preventDefault()`. That is the contract for element-scoped Escape handlers. An outside press dismisses only the topmost layer that owns presses and lets the click carry on, so a click on a dialog's button both closes an open dropdown and activates the button. A lint rule in `eslint.config.mjs` rejects capture-phase key listeners anywhere in `apps/web/src`. ## The `@clawboo/ui` package `@clawboo/ui` is private like every `@clawboo/*` package, and it ships only inside the CLI bundle. It exports: * `BooAvatar` and `BooVariantAvatar`, the agent avatars; * from `@clawboo/boo-avatar`, re-exported by name: `BOO_VARIANTS`, `loadBooVariant`, `resolveBooTint`, `TINTS` and `variantIdForRuntime` (the runtime-specific Boo for an agent); * `cn` (class merging), `cva` and its `VariantProps` type, from `class-variance-authority`; * the legacy `tokens` constant (see the note under [Brand colors](#brand-colors)). The shared components above are app code in `apps/web`, not part of this package. There is no externally published Clawboo UI kit. ## Related * [Theming](/using/theming): switching between System, Light and Dark in the dashboard. * [Teams](/using/teams): picking a team's color collection. * [Monorepo and build](/internals/monorepo-and-build): the lint rules and where `apps/web` sits in the build. * [Contributor overview](/internals/index): the repo map. # The event pipeline Source: https://docs.claw.boo/internals/event-pipeline How @clawboo/events turns the OpenClaw Gateway frames the browser receives into store updates, through pure Bridge and Policy layers and a stateful Handler. `@clawboo/events` turns the OpenClaw Gateway's raw WebSocket event frames into dashboard state. Three stages do the work: the **Bridge** classifies a frame, the **Policy** decides what should happen, and the **Handler** applies it. The first two are pure functions (the Bridge only stamps the current time on each frame) that you can test without a Gateway, a browser or a store; only the Handler holds state. Use this page when you change how the dashboard reacts to Gateway frames: live streaming text, an agent's status, approval cards, or a committed chat turn. For the Gateway connections themselves (the browser proxy and the server's own connection), see [Gateway connection internals](/internals/gateway-and-events). ## Which frames go through it The pipeline runs in the browser only. The `useGatewayEvents` hook makes one `client.onEvent` subscription and calls `processEvent(frame, handler)` for every frame, so each Gateway frame the browser receives takes exactly one path through the three stages. (`GatewayClient` consumes `connect.challenge` itself, before the fan-out.) That hook is the only caller of `processEvent`. The server reads the same Gateway over its own connection, and those frames never pass through this pipeline: * `OpenClawAgentSource` resyncs the agent registry on `presence`, `heartbeat` and `agent` frames; * the session activity watcher reads `session.message` commits for the activity feed, OpenClaw spend and 1:1 transcripts; * the exec-approval surface mirrors `exec.approval.*` requests into Clawboo's approvals queue; * the OpenClaw `RuntimeAdapter`, which runs OpenClaw team-chat turns and Routine fires, maps frames to `RuntimeEvent`s with `mapFrameToRuntimeEvents`. Two of them, the session activity watcher and the OpenClaw adapter, reuse the Bridge's pure parsers; none of them uses its intents. Runtimes other than OpenClaw never produce Gateway frames: they emit `RuntimeEvent`s on the server (see [the RuntimeAdapter trait](/internals/runtime-adapter)). The package's only runtime dependencies are `@clawboo/gateway-client`, `@clawboo/protocol` and `@clawboo/logger`, and it imports nothing from `node:*`, so it ships in the browser bundle. ## The three layers * **Bridge** (`bridge.ts`): parsing, pure apart from the timestamp. `classifyEvent(frame)` answers "what is this frame?" and returns a `ClassifiedEvent` (`kind`, the extracted `agentId` and `sessionKey`, a timestamp). It never decides what to do. * **Policy** (`policy/*`): pure decisions. `derivePolicy(event)` answers "what should happen?" and returns an array of typed `EventIntent`s: a description, not a mutation. * **Handler** (`handler.ts`): the one stateful stage. `createEventHandler(deps)` dispatches each intent to injected store writers, applying the guards (debounce, stale-run dropping) as it goes. `processEvent(frame, handler)` threads a frame through all three: `classifyEvent`, then `derivePolicy`, then `handler.applyIntents`. ```mermaid theme={"theme":"github-dark"} flowchart LR F[Raw EventFrame] --> B["Bridge: classifyEvent()"] B --> C[ClassifiedEvent] C --> P["Policy: derivePolicy()"] P --> I["EventIntent[]"] I --> H["Handler: applyIntents()"] H --> Q["Patch queue (batched per animation frame)"] H --> Z[(Zustand stores)] Q --> Z ``` ## Stage 1, Bridge: `classifyEvent` `classifyEvent(frame)` switches on the frame's `event` name. The mapping is small and total; anything unrecognized becomes `unknown` rather than throwing: | Frame `event` | Classified `kind` | | - | - | | `presence`, `heartbeat` | `summary-refresh` | | `chat` | `runtime-chat` | | `agent` | `runtime-agent` | | `exec.approval.pending` / `.requested` / `.resolved` | `approval` | | anything else | `unknown` | Agent identity is extracted defensively. For `chat` frames the `agentId` comes from the session key `agent::` (`SESSION_KEY_RE`). For `agent` frames a top-level `agentId` wins, else the session key is parsed. For approval frames the `agentId` may be at the top level or inside `payload.request`, and both are checked. The Bridge also owns small pure helpers the rest of the code reuses: * `parseChatPayload` and `parseAgentPayload` shape-check a payload and return `null` on anything malformed. A chat payload needs a `runId`, a `sessionKey` and a known `state`; an agent payload needs only a `runId`. The Policy router turns that `null` into an `ignore` intent instead of a crash. * `parseSessionMessagePayload` parses a committed `session.message` row. It is a sibling of `parseChatPayload`, not a widening of it: a committed row has no streaming `state`, and its `runId` is optional because a row from a run nobody in Clawboo started still carries work. Its consumers are on the server (the session activity watcher and `@clawboo/adapter-openclaw`). * `isReasoningStream(stream)` decides whether a stream is a thinking trace. Its deny list (`assistant`, `tool`, `lifecycle`) is checked before its allow list (`reason`, `think`, `analysis`, `trace`), so an `assistant` stream is never read as reasoning. * It re-exports `extractText`, `extractThinking` and `extractToolLines` from `@clawboo/protocol` so consumers have one import surface. There is no per-run de-duplication in the Bridge. Duplicate suppression happens once, in the Handler's `closedRuns` guard. ## Stage 2, Policy: `derivePolicy` `derivePolicy(event)` routes on the classified `kind` to one of four pure deciders, each in its own file: ```ts theme={"theme":"github-dark"} // packages/events/src/policy/index.ts export function derivePolicy(event: ClassifiedEvent): EventIntent[] { switch (event.kind) { case 'summary-refresh': return decideAgentEvent(event) case 'runtime-chat': { const payload = parseChatPayload(event.payload) if (!payload) return [{ kind: 'ignore', reason: 'malformed chat payload' }] return decideWorkChatEvent(event, payload) } case 'runtime-agent': { const payload = parseAgentPayload(event.payload) if (!payload) return [{ kind: 'ignore', reason: 'malformed agent payload' }] return decideWorkAgentEvent(event, payload) } case 'approval': return decideTrustEvent(event) case 'unknown': default: return [{ kind: 'ignore', reason: 'unknown event kind' }] } } ``` The router re-validates each payload with the Bridge's parser, so a malformed payload always becomes one `{ kind: 'ignore', reason }` intent. Nothing in this layer throws. Every intent except `ignore` carries a `plane` field (`work`, `agent` or `trust`) naming the kind of state it touches. * **`decideAgentEvent`** (`agent.ts`), for `summary-refresh`: one `scheduleSummaryRefresh` intent with `delayMs: 750`, and `includeHeartbeatRefresh` set only when the frame was a `heartbeat`. It asks the Handler to debounce a fleet reload rather than changing fleet state itself. * **`decideWorkChatEvent`** (`work.ts`), for `runtime-chat`. A `delta` becomes a `queueLivePatch` carrying the streaming text and thinking trace. A `final` becomes a `clearPendingLivePatch` plus a `commitChat` that finalizes the turn: its `outputLines` hold the reply text and the tool lines extracted from the final message, it carries the frame's `runId` for the Handler's stale-run guard and an `idle` patch, and it carries a `cost` from `deriveChatCost`. If the final message had no thinking trace, a `requestHistoryRefresh` follows. The web app answers it with a best-effort `listAgentSessions(agentId)` call whose result it discards; the transcript is built from the frames. `aborted` and `error` each become a `clearPendingLivePatch` plus a `commitChat` with status `idle` or `error`. * **`decideWorkAgentEvent`** (`work.ts`), for `runtime-agent`. The `lifecycle` stream's `start`, `end` and `error` phases become `updateAgentStatus` intents (`running`, `idle`, `error`); the `assistant` and reasoning streams become `queueLivePatch` intents. The `tool` stream and unknown streams return `ignore`; tool lines reach the transcript through the final message's `outputLines`. * **`decideTrustEvent`** (`trust.ts`), for `approval`: `approvalPending` for `exec.approval.pending` and `.requested`, else `approvalResolved`. A missing `agentId` is an `ignore`, not an error. `deriveChatCost(payload, responseText)` uses the Gateway's usage block when the message carries one. Otherwise it estimates the output side at about 4 characters per token and leaves `inputTokens: null`, because pricing the prompt needs a transcript read and Policy stays pure; the host fills it in. Because every decider reads only its inputs and returns an array, the same input always derives the same intents. That is what `policy-work.test.ts` and `policy-trust.test.ts` exercise: feed a `ClassifiedEvent`, assert the `EventIntent[]`, with no Gateway and no store. ## The seam: `EventIntent` The boundary between pure and stateful is a typed union. Policy emits these; the Handler consumes them: ```ts theme={"theme":"github-dark"} // packages/events/src/types.ts (abridged) export type EventIntent = | { kind: 'queueLivePatch' plane: 'work' agentId: string sessionKey?: string patch: AgentStatusPatch } | { kind: 'clearPendingLivePatch'; plane: 'work'; agentId: string } | { kind: 'commitChat' plane: 'work' agentId: string sessionKey?: string runId: string | null patch: AgentStatusPatch outputLines: string[] cost: ChatCost | null } | { kind: 'updateAgentStatus' plane: 'agent' agentId: string runId: string | null patch: AgentStatusPatch } | { kind: 'scheduleSummaryRefresh' plane: 'agent' delayMs: number includeHeartbeatRefresh: boolean } | { kind: 'requestHistoryRefresh' plane: 'agent' agentId: string reason: 'chat-final-no-trace' } | { kind: 'approvalPending'; plane: 'trust'; agentId: string; payload: unknown } | { kind: 'approvalResolved'; plane: 'trust'; agentId: string; payload: unknown } | { kind: 'ignore'; reason: string } ``` An intent describes what should happen and never calls a store. That indirection keeps Policy pure (a function that only builds plain objects cannot write to a store) and lets the Handler apply its guards to every intent the same way, whichever decider produced it. ## Stage 3, Handler: `applyIntents` `createEventHandler(deps)` returns `{ applyIntents, dispose }`. The `deps` (`EventHandlerDeps` in `types.ts`) are injected state queries and store writers: `getAgentRunId`, `dispatchIntent`, `queueLivePatch`, `clearPendingLivePatch`, `appendOutputLines`, `requestHistoryRefresh`, `loadSummarySnapshot` and `refreshHeartbeatLatest`, plus an injectable `setTimeout` / `clearTimeout` pair and an optional `log`. The package never imports `apps/web`; the wiring goes the other way. `applyIntents(intents, event)` is a `switch` over `intent.kind`, and most cases delegate to one dependency. Two carry the logic. **The `closedRuns` guard.** The Handler keeps a `Map` from `runId` to `{ expiresAt, byCommit }`, with a 30-second TTL (`CLOSED_RUN_TTL_MS`) and at most 500 entries (`CLOSED_RUNS_MAX_SIZE`, oldest evicted). It is pruned at the top of every `applyIntents`. * A `commitChat` that closes its run (the agent's `runId` is null after dispatch) records the incoming frame's `runId` with `byCommit: true`. A later `commitChat` for that run is a replay and is dropped, so a duplicate `final` never mints a second set of transcript entries. * A terminal `updateAgentStatus` records the run with `byCommit: false`, but only if the dispatch actually cleared the agent's `runId` (it compares the value before and after). A run closed this way still owes its reply, because OpenClaw sends the `agent` lifecycle end before the `chat` final, so the `final` is let through unless a different run is already live (then it is a stale replay whose idle patch would wrongly stop the live run). * A terminal `updateAgentStatus` for a run already in the map is dropped, so a late lifecycle `end` cannot flip a freshly started run back to idle. The guard compares the incoming frame's `runId`, which is why both intents carry one. The agent's current `runId` cannot work: by the time a stale frame arrives, the run it names is already cleared, so the current value is `null` or the next run's id. A missing `runId` fails open, because losing a real message is worse than showing a duplicate. **Pending approvals.** While an exec approval is pending for an agent, the hook's `dispatchIntent` deliberately skips the terminal status patch: the model's stream ended but the run is alive, waiting on the decision. The agent's `runId` therefore survives the dispatch, the run is not marked closed, and the real final that arrives after the approval resolves still goes through. **The summary-refresh debounce.** `scheduleSummaryRefresh` cancels any pending timer and re-arms one, so a burst of `presence` and `heartbeat` frames collapses into one fleet reload after `delayMs`. `dispose()` clears the timer and the `closedRuns` map. The hook calls it from its effect cleanup, which runs when the tab's Gateway client is replaced or cleared and on unmount, so nothing leaks into the next client. ## The patch queue Streaming deltas arrive faster than the screen redraws. `createPatchQueue(onFlush)` (`patch-queue.ts`) keeps a `Map` from agent id to its pending updates; `enqueue` merges an incoming patch into that agent's entry and schedules a flush on the next `requestAnimationFrame`, and `flush` hands everything accumulated to `onFlush` once. The store is written at most once per animation frame per agent. The merge has one rule: a change of `runId` discards the old streaming state. When an incoming patch carries a different `runId` than the pending one, the queue replaces the entry instead of merging it, so a stale partial stream from a finished run never bleeds into a new run's text: ```ts theme={"theme":"github-dark"} // packages/events/src/patch-queue.ts (abridged) const existingRunId = typeof existing['runId'] === 'string' ? existing['runId'] : '' const incomingRunId = typeof patch.updates['runId'] === 'string' ? patch.updates['runId'] : '' if (incomingRunId && existingRunId && incomingRunId !== existingRunId) { state.pending.set(patch.agentId, { ...patch.updates }) // replace, do not merge } else { state.pending.set(patch.agentId, { ...existing, ...patch.updates }) // merge } ``` The queue schedules through `requestAnimationFrame` and cancels through `cancelAnimationFrame` only when those globals exist, so the same code is a harmless no-op in Node tests. `dispose()` cancels any pending frame and clears the map. ## Wiring in the web app The binding to real stores lives in one place, the `useGatewayEvents` hook (`apps/web/src/features/connection/useGatewayEvents.ts`). For each Gateway client the tab holds, it: 1. creates a patch queue whose `onFlush` writes each batch into the fleet store; 2. builds the Handler with store-backed dependencies: `getAgentRunId` reads the fleet store, `dispatchIntent` applies status, commit and approval intents (including the pending-approval rule above), `queueLivePatch` feeds the patch queue, `loadSummarySnapshot` reloads the fleet from the server, and `requestHistoryRefresh` makes the best-effort sessions call described above; 3. subscribes once with `client.onEvent` and runs `processEvent(frame, handler)` for every frame; 4. starts a 5-second sweep that removes expired approval cards and adds a "timed out" line to the agent's transcript, because the Gateway emits no resolved event when an approval times out. It is a plain interval, so it keeps running in a hidden tab. For a team session key (`agent::team:` and the room variant), the hook skips any `chat` frame it receives: no transcript commit and no live streaming-text writes. The server's team engine is the only writer of team chat: it persists each turn and streams it to the dashboard over `GET /api/teams/:id/chat/stream`. The cleanup function unsubscribes, clears the sweep, and calls `patchQueue.dispose()` and `handler.dispose()`. ## Cost on this path OpenClaw turn cost is recorded on the server, not here. The session activity watcher reads the Gateway's `session.message` commits on the server's own connection and records each turn's real model and token counts in `cost_records`, including turns no browser saw. It prices a turn with the provider beside the model, so a turn a local Ollama served is recorded at \$0. `recordChatCost` in the hook therefore returns early for an agent whose runtime is `openclaw`, so a turn is never billed twice. Clawboo Native, Claude Code, Codex and Hermes runs reach `cost_records` from the server too: the task runner and the team engine write one row per run with `recordRunTokens` (`runTokenLog.ts`). For any other fleet row whose turn arrives as a Gateway `chat` frame, `recordChatCost` still takes the `commitChat` intent's `cost`, estimates a missing input side from the agent's last user message at about 4 characters per token, and posts `/api/cost-records`. Cost rides the `commitChat` intent rather than a second raw-frame subscription, so it sits behind the same `closedRuns` guard and a replayed frame is never charged twice. A new cross-cutting concern belongs on an intent, not on another `client.onEvent` subscriber. ## Design rationale **Pure Bridge and Policy, stateful Handler.** What a frame means and which intents it produces are the parts that are hard to get right. Keeping them pure makes them deterministic and testable in isolation, and pushes every piece of timing state (the debounce timer, the stale-run map) into one auditable place. **Intents over direct store calls.** Policy could call the stores directly and skip the union. It does not, because the indirection is what enforces purity and lets the Handler apply its guards uniformly. **Batching over a write per frame.** Writing the store on every streaming delta would thrash React's render path. Coalescing per agent per animation frame keeps the live fleet view smooth without dropping data, and the replace-on-new-`runId` rule keeps the coalescing correct across run boundaries. **Injected effects.** The Handler takes its timers and every store writer as dependencies, so the package stays browser-agnostic and tests drive it with fake timers and spy dispatchers. ## Boundaries * **OpenClaw frames in the browser only.** The pipeline maps OpenClaw Gateway frames to the live fleet view. Server-side consumers of the same Gateway, and every other runtime, use other code (see [Which frames go through it](#which-frames-go-through-it)). * **Not the team engine.** Bridge, Policy and Handler keep an agent's status, streaming text and approval cards in sync. Turning delegation signals into durable board work is the team engine's job, on the server. See [Delegation and orchestration](/concepts/delegation-and-orchestration). * **Not a transport.** The package never opens a socket. The socket, reconnects and the same-origin proxy live in `@clawboo/gateway-client` and `@clawboo/gateway-proxy`; see [Gateway connection internals](/internals/gateway-and-events). ## Related * [Gateway connection internals](/internals/gateway-and-events): the browser proxy, the handshake, and the server's own Gateway connection * [Architecture invariants](/internals/architecture-invariants): the rule this pipeline upholds for browser frames * [The RuntimeAdapter trait](/internals/runtime-adapter): how the other runtimes emit lifecycle events * [`@clawboo/events` notes](/reference/packages/index#events): where the package sits among the workspace packages * [OpenClaw](/runtimes/openclaw): connecting and using the OpenClaw runtime # The executor runner Source: https://docs.claw.boo/internals/executor-runner How runTaskOnRuntime drives one board task on a non-OpenClaw runtime: refusals, claim, workspace, prompt, spend and breakers, rotation and verification. The executor runner, `runTaskOnRuntime` in `apps/web/server/lib/executorRunner.ts`, drives one existing board task through one runtime from claim to finish. Given a task id, an assignee and an adapter factory, it claims the task, opens an execution row, sets up a worktree when the task changes files, assembles a tiered prompt, drains the adapter's event stream while recording spend and running the circuit breakers, rotates the session when it runs out of room, runs the verification gate, and writes a handoff for the next runtime. It is one of two execution paths, and not the default one: * **The executor runner** serves `POST /api/runtimes/:id/run` and agent Routine fires on Clawboo Native, Claude Code, Codex and Hermes. With `CLAWBOO_ENABLE_MOCK_RUNTIME=1`, Routine fires on `clawboo-mock` use it too; the run route accepts only the four real runtimes. * **The team engine** (`serverDeliver`, driven by the team orchestrator) runs every team-chat turn, every delegation, and every board task a person starts. It provisions no worktree, passes the team's folder (`/workspaces/`, from `teamWorkdirFor`) as the working directory for Claude Code, Codex, Hermes and Clawboo Native, and has no verification, memory auto-injection or circuit breakers. See [How a team works](/intro/how-it-works). Use this page when you change how a task runs on a spawned runtime, or need to know what a run through the runtime run API does. ## Where it sits The runner talks to a runtime only through the [`RuntimeAdapter`](/internals/runtime-adapter) trait and an injected adapter factory (`adapterFactoryFor(runtimeId)` in production). It is not: * **the team engine**, which decides what to delegate and runs team chat; * **the OpenClaw path**: OpenClaw runs over its live Gateway connection, so the runner refuses it before claiming anything, and OpenClaw routine fires use a separate dispatcher (see [How a Routine fire picks its path](/internals/seams#how-a-routine-fire-picks-its-path)); * **the verification critic**: it triggers the verification gate, which reuses the runner's adapter factory to drive an independent reviewer. `runTaskOnRuntime(input)` returns a `RunTaskResult` and never throws for an expected board outcome. A refusal is `{ ok: false, reason }`; `runtimesRunPOST` maps it to an HTTP status: | `reason` | Meaning | REST status | | - | - | - | | `not_found` | The task does not exist. | `404` | | `conflict` | The claim was lost, the caller went away, or a fix cycle lost the task. | `409` | | `too_deep` | The task sits deeper than the depth cap. | `422` | | `connected_substrate` | The runtime runs over a live connection (OpenClaw). | `422` | | `budget_paused` | A cap-mode budget on the agent, mission or team is paused. | `422` | | `workspace_unavailable` | The task changes files but no worktree could be provided. | `422` | A completed run answers `200` with the result (`doneReason`, final `status`, `summary`, `costUsd`, `usedWorktree`, `degradations`). An unexpected throw answers `500`. The request blocks for the whole run, and the handler aborts the run if the client disconnects. ## The lifecycle ```mermaid theme={"theme":"github-dark"} flowchart TD A[runTaskOnRuntime] --> R{"refused before the claim?"} R -->|"not_found, too_deep, connected_substrate, budget_paused"| X[return the reason] R -->|no| C{atomic claim} C -->|lost| X2[return conflict] C -->|won| W{workspace} W -->|"changes files, no repo or provision failed"| U["fail the execution, release, workspace_unavailable"] W -->|ok| P[assemble prompt and start the run] P --> D["drain events: spend, breakers, idle guard"] D -->|"out of room, rotations left"| P D --> T["terminal: budget pause, breaker trip, success, or failure"] ``` ## Before the claim Everything that can refuse a run without changing the board runs before the claim, so a misrouted or over-budget call never opens an execution row or spawns a process. In order: 1. **Aborted waiter.** A run for a persistent-home runtime waits its turn on a per-agent lock (`homeDispatchMutex`, below). The wait is bounded by `HOME_MUTEX_ACQUIRE_MS` (10 minutes, `CLAWBOO_HOME_MUTEX_ACQUIRE_MS`); past it the run rejects with `MutexAcquireTimeoutError`. If the caller disconnected or a person moved the card while it waited, the runner returns `conflict` without touching the board. 2. **Task exists**, else `not_found`. 3. **Depth.** A task with more than `MAX_SPAWN_DEPTH` (2) ancestors is refused with `too_deep`. Creation and dispatch read the same `DEFAULT_MAX_DEPTH` from `@clawboo/governance` but compare differently: a create is refused when the parent is already at the cap, a dispatch only when the task itself is past it. So anything that can be created can also run. 4. **Connected runtime.** `resolveRuntimeIntegration(caps)` gives OpenClaw a `connected` home, and the runner returns `connected_substrate`. 5. **Budget preflight.** A paused budget in `cap` mode on the agent, the mission (the root task of the delegation tree) or the team returns `budget_paused`. Budgets in `warn` mode, the default, never read paused and never block. The integration plan also decides serialization. A persistent home (Clawboo Native, Codex, Hermes) is one state directory per (runtime, agent), so two concurrent runs of the same agent wait for each other on `homeDispatchMutex`, keyed on the home path. The team engine and 1:1 chat take the same lock for their own runs of that agent. An ephemeral home (Claude Code) is never keyed. ## The claim The claim is one conditional `UPDATE` through the board's `claimTask` (see [the atomic claim](/internals/board-internals#the-atomic-claim)). A lost claim returns `conflict` and is never retried: another runner legitimately owns the task. A verification fix cycle is the one dispatch that does not claim; it checks that the task is still `in_progress` and still assigned to this agent, and returns `conflict` otherwise. Right after the claim the runner opens an `execution_processes` row and emits `execution_started`. The row's run reason is `verify-fix` on a fix cycle, `resume-via-handoff` when the runtime cannot resume natively, and `run` otherwise. A liveness heartbeat (`startTaskHeartbeat`, every 30 seconds) starts before the mutex wait and stops in a `finally`, so it spans the wait, the run and every fix cycle; that beat is what keeps the stale sweep from releasing a live run. ## The workspace `acquireWorkspace` sets up a workspace only when the adapter declares `capabilities().worktrees`. Clawboo Native, Claude Code, Codex and Hermes all do; the test-only `clawboo-mock` does not, so its runs get no working directory. Whether a task needs a worktree comes from its kind (`isolationForTask` in `@clawboo/worktrees`): `research` and `review` need none; `code`, the default, and any unknown kind need one. There are four outcomes: * **Reuse.** The task already has a worktree, which is the cross-runtime continuation path. If garbage collection reaped it (the row is `stale`, the directory is missing, or git does not list it as a worktree), the runner rebuilds it from the retained branch with `resumeTaskWorkspace`. * **Provision.** A file-changing task with a `repoPath` gets a fresh checkout on a `clawboo/task-` branch from `provisionTaskWorkspace`, with the handoff scaffold files. * **None.** A `research` or `review` task gets no worktree. Its run starts in the team's folder from `taskRunWorkdirFor` (`apps/web/server/lib/runtimes/teamWorkdir.ts`): `/workspaces/`, or `workspaces/_no-team` for a task with no team, created owner-only (`0700`) first. If the folder cannot be created, the run is refused as below, with `Could not create the working folder for this run: ` as the reason. * **Refused.** A file-changing task with no `repoPath`, or one whose worktree cannot be provisioned, does not run. The runner adds a `[blocked: no isolation] ` system comment, closes the execution as `failed` with `workspace_unavailable: `, releases the task to `todo`, and returns `workspace_unavailable`. The refusal happens after the claim, so a refused task shows one failed execution on its ledger. Two callers set the kind: `POST /api/runtimes/:id/run` defaults it to `code`, so a request without `repoPath` is refused; the Routines wake bridge runs an agent Routine that has no `repoPath` as `research`, so it runs without a worktree instead. > [!WARNING] > > The refusal exists because of how the drivers run. Claude Code runs with `permissionMode: 'bypassPermissions'`, Codex with `--dangerously-bypass-approvals-and-sandbox` (app-server: approval policy `never`, sandbox `danger-full-access`), and Hermes with `--yolo` and `--accept-hooks`. A run with no working directory would start in the server's own working directory, which for an npm install is the Clawboo state directory holding the database and the secrets vault. The worktree keeps a task's file changes on its own branch, and the team's folder only sets where a read-only run starts; neither is a sandbox, and the process still has every permission of the user who started Clawboo. Team-engine runs start in the team's folder too. See [What agents can do](/concepts/permissions). When a worktree exists, the runner reconstructs a `ResumeState` from its `AGENT_HANDOFF.json` and scaffold files, so a different runtime picks the task up where the last one left it. See [Worktrees and handoff](/concepts/worktrees-and-handoff). ## Prompt assembly The prompt goes through `assembleTiers` from `@clawboo/executor/tiers`, in three tiers ordered so the frozen part forms the cacheable prefix: | Tier | Content | | - | - | | `stable` | The persona block, then `# Task: ` and the description. A board run skips the team-chat preamble, so for Claude Code, Codex and Hermes the runner injects the agent's `SOUL.md` itself as a `[Your persona]` block. | | `context` | The resume handoff; the MCP note (it names `team_chat_post` only for a team task, because an unscoped run gets no TeamChat tools); memory guidance; `Degradations applied: ...`; the interrupted-work note built from earlier attempts' ledger rows (`classifyAttempts`, `buildResumeNote`); the fix note on a fix cycle; the rotation handoff on a successor; and the inbox digest. | | `volatile` | The auto-injected memory block. | The inbox digest packs up to 20 undelivered mailbox rows for the assignee into an addressed section and an ambient section, the same split the team-chat path renders, and marks only the rendered rows delivered once the run has started. **Memory auto-injection** (`buildMemoryInjection`) seeds the most relevant team facts for the task into the volatile tier, so the runtime starts with the team's knowledge without calling the Memory MCP tools (it still can). It searches the same memory store the `/api/memory` routes use, keeps the top 5 facts within 1,500 characters, drops any candidate the injection scanner flags, scrubs secrets, and injects nothing when memory is empty. It runs once per task and is reused across rotations. `disableMemoryAutoInject` turns it off (eval runs set it). See [Memory](/concepts/memory). A persistent-home runtime also gets `homeDir`, `<Clawboo state dir>/runtimes/<runtime>/<agent>`, created owner-only (`0700`) before the driver touches it; the critic's own run gets none, so the reviewer never shares the builder's memory. The run context (`RuntimeRunContext`) carries the `cwd` (the worktree, else the team's folder), the model, the native resume id (only when the last runtime on this worktree was the same one), the MCP base URL, the memory scope (team, agent, the attach secret, and provenance stamps), the home directory and any provider keys. The run starts with session key `runtime:<runtime>:task:<taskId>`, the task title as its message, and `childToolBlocklist: ['sessions_send']`. ## The event drain `adapter.start` returns a `RunHandle`, and the runner drains `adapter.events(run)` through `withIdleTimeout` until a terminal `done`: * **`text-delta`**: accumulated into the run's text. The `reasoning` channel is skipped, so the summary shows what the agent did, not its private reasoning. * **`cost`**: recorded against budgets (below) and emitted as a `cost` event. * **`tool-call`** (settled calls only, not each streaming-input delta), **`tool-result`** and **`error`**: emitted as observability events and fed to the circuit breaker. An `error` goes through the error taxonomy: an unknown class is a harness bug and raises an alert; a recognized policy denial is expected governance, not a bug. * **`done`**: captures the reason and summary and ends the pass. The idle guard aborts a run that is silent for 30 minutes (`CLAWBOO_RUN_SILENT_TIMEOUT_MS`), then allows 5 seconds for the adapter's own terminal. A silent timeout is recorded as a failure with the cause stated ("runtime silent: no events for N minutes"), never as a clean abort and never as a reason to rotate. A pass that finishes without reporting any usage logs a `spend_unreported` warning, because budgets then under-count that run. An external `abortSignal` (the REST handler wires it to the response's `close`) aborts the current run, even across a rotation, and forces the aborted terminal. ### Stopped by a person `runTaskOnRuntime` folds the caller's signal and a person's stop into one `AbortController` and registers the run with the per-task stop registry (`registerRunStop`, `apps/web/server/lib/runStops.ts`) for its whole life, fix cycles included. After a board `PATCH` writes a move, `noteHumanMove` calls `stopTaskRuns(taskId, note)` for every move except into `in_progress` or `in_review`. That aborts the run and leaves a note such as `Stopped: a person moved the card to Done.`, which becomes the run's system comment. The result carries `stoppedByPerson: true`, so a caller treats the move as the outcome rather than a failure (a Routine fire stopped this way stays armed). The OpenClaw routine dispatcher registers with the same registry; the team engine stops the runs it started itself. ### Steering The live run is also registered with the per-task steer registry (`registerRunSteer`, `runSteers.ts`), re-pointed on every rotation and fix cycle. A person's comment on the task reaches it through `steerTaskRun` after the comment is written to the agent's mailbox. How each runtime takes a steer is described on [the RuntimeAdapter trait](/internals/runtime-adapter) page. ### Spend and budgets On every `cost` event the runner records spend against the agent, mission and team budgets with `recordSpend`. A runtime that reports usage but no USD (`costUsd: null`) is priced from its exact token counts with `estimateRunCostUsdFromUsage`, which falls back to the shared default rate for an unpriced model; a reported `costUsd` is used as is. An event flagged `local`, or a null-USD event whose model `isLocalModel` recognises (an `ollama/` id without a cloud tag), costs an exact $0. A `done` that carries `costUsd` is charged only when its pass had no `cost` event. A pass that ends with no spend at all is priced from characters with `estimateRunCostUsd`, from the prompt it was sent and the text it produced, at the model the run was given (`input.model`), or $0 when that model is local. An aborted pass is charged this way too; only a pass that ended on its own logs the `spend_unreported` warning. The per-run ceiling accumulates whole cents while the budget ledger takes fractional cents, so sub-cent events are carried, not lost. The execution row's `costSource` comes from the run's least trustworthy part: `char-estimate` if any pass was priced from characters, else `token-estimate` if any was priced from tokens, else `reported` if any part was paid, else `local`. When the run recorded a cost, `recordRunTokens` (`runTokenLog.ts`) also writes one `cost_records` row for **Tokens Used**, with tokens guessed from characters for the passes that reported none. Budgets default to warn mode. A warn budget records spend and writes a `budget` audit row when it crosses 80% and 100%, but never pauses. Only a `cap` budget that crosses 100%, or the per-run `maxNodeCents` ceiling (set only by a Routine template through the API), stops the live run, and not at once: the crossing leaves a pending stop. The runner aborts the run at its next event that means more work (any event except a `done`, a fatal `error`, or a `status` outside `thinking` and `running`), or when `BUDGET_STOP_GRACE_MS` (5 seconds) passes first. If the run reaches a `done` other than `aborted` first, the report that crossed was the run's last, and the run keeps its result (see **Cap reached at the end** below). That is the usual case for Claude Code and Codex, which send their one cost report beside their `done`. The explicit `mode === 'cap'` check backs up the database layer's rule that a warn budget never reads paused. See [Governance](/concepts/governance). ### Circuit breakers The circuit breaker halts a run that makes no progress, repeats a failing tool or burns tokens, before the dollar ceiling. It reads typed signals only: token counts on `cost`, a tool signature on `tool-call` and `tool-result` (correlated by `toolCallId`), and the typed code of a policy-denied `error`. The defaults (`BREAKER_DEFAULTS` in `@clawboo/governance`): | Reason | Trips when | | - | - | | `iteration-cap` | more than 30 settled tool calls in the run | | `repeat-failure` | 3 consecutive failures of the same tool call | | `no-progress` | 6 consecutive results with no new successful output | | `token-velocity` | over 200,000 tokens a minute, after 15 seconds | | `repeat-policy-denied` | 2 identical typed denials | `POST /api/runtimes/:id/run` accepts a partial `breakerConfig` to override them. The breaker is fed only while no budget stop is pending, and its teardown runs after the budget teardown, so the budget wins a tie and a run gets at most one `adapter.abort`. ## Session rotation The runner's unit of context is the run, not the model turn: the runtime owns its inner loop. When a pass ends out of room (an explicit `done` with reason `max_turns`, or a non-success `done` whose input tokens crossed 85% of the declared context window) and rotations are left (`maxRotations`, default 3), the runner calls `rotateSession`. That serializes the predecessor best-effort, builds a short handoff note (the last summary, never the transcript), and starts a fresh successor session (`<sessionKey>:r<n>`) whose prompt carries the note in its context tier. The watermark reads input tokens alone, because the prompt is what occupies the window. A runtime that declares no context window and never emits `max_turns` never rotates. Successors always start fresh (`ctx.resume = null`): resuming the exhausted session would exhaust it again. Budget and breaker state are cumulative across rotations; only the per-pass counters reset. Each rotation records session lineage and emits `session_rotated`. A budget, breaker, cancel or silent-timeout stop ends the run and never rotates, and neither does a pass whose last cost report crossed a cap. ## Terminal handling After the loop exactly one terminal branch runs. Every branch closes the execution row only if it is still `running` (a stale sweep may have closed it first), and emits `execution_completed` only when its close landed. **Budget pause.** Audit a `budget` row with reason `auto_pause`, add the comment `Auto-paused: <scope> budget reached. Raise the cap (or resume) to continue.`, close the execution `cancelled` with `budget_paused:<scope>`, and release the task to `todo`, ready to retry once someone raises the cap. **Cap reached at the end.** Not a branch of its own. When the cost that crossed a cap came as the run ended (the pass's `done` was not `aborted`, or its characters were priced after it ended on its own), the runner audits a `budget` row with reason `cap_reached_at_end` and adds the comment `Budget cap reached (<scope>) by this run's final cost report. The run had already ended, so what it produced is kept; the next run is refused until you raise the cap or resume.` For the per-run ceiling the comment reads `This run's cost ceiling was reached by its final cost report. The run had already ended, so what it produced is kept.` The run then takes the success or failure branch like any other, and the paused budget refuses the next run at pre-flight. **Breaker trip.** Audit a `circuit_break` row, add `[stopped: <reason>] <detail> Released to todo for re-planning.`, close the execution `cancelled` with `circuit_broken:<reason>`, and release to `todo`. The worktree stays intact, so a retry resumes from it. **Success.** The summary is scrubbed of secrets before it lands in a board comment, the execution row, the handoff or the HTTP response. The runner then writes the agent's comment, closes the execution `succeeded`, and: * **With a worktree:** persists the native session id into `AGENT_HANDOFF.json` (best-effort) and, if a previous attempt crashed, records that its outcome is unknown. With `keepForResume` it releases the task to `todo` for another runtime to pick up. Otherwise it calls `actOnTaskWorkspace(taskId, 'complete')`, which runs the verification gate and routes the status: | Outcome | Status | | - | - | | empty diff | `done`, without running the gate (nothing to check) | | `pass` | `done` | | `fail` with attempts left | `in_progress`; the runner re-dispatches a fix cycle with the structured `{ what, why, howToFix }` note | | `fail` with the attempts spent | `blocked`, with a system comment and a notice to the delegator (or the team leader) | | `completed_with_debt` (only at exhaustion) | `blocked` | The attempt budget is `CLAWBOO_MAX_FIX_CYCLES` plus one, 2 by default. Fix cycles run outside the home mutex, each taking it fresh, because the mutex is not re-entrant. The gate reads its command from `VERIFY_CMD` in the worktree's `init.sh` (or the `- Verify:` line in `VERIFICATION.md`). The scaffold writes a placeholder there, so a task whose agent never sets a real command fails verification. If a person moved the task to `done` or `cancelled` while the check ran, their move stands and the verdict stays only as a record. The critic reuses this run's adapter factory on a detached checkout with a fresh session and no builder `homeDir`; `CLAWBOO_REVIEWER_MODEL` can make it a different model. See [Verification](/concepts/verification). * **Without a worktree** (a `research` or `review` task): `updateStatus(db, taskId, 'done')`. If the board refuses (the task was released, the verification gate holds a non-promotable verdict, or the row is gone), the runner keeps the result as a `[late result ...]` system comment that says which. **Failure.** A system comment records the outcome (a person's stop note, or `Run <reason>: <summary>`), the execution closes `cancelled` for an abort or `failed` otherwise, and a `done` with reason `error` is classified through the taxonomy and alerts on an unknown class. A run that attempted a native resume and failed clears the stored session id, so the next dispatch falls back to the prose handoff instead of retrying a stale id. Finally `releaseTask` returns a task still `in_progress` to `todo`; a task a person already moved stays where they put it, and the result reports its real status. ## Degradation notes `planDegradations(caps)` (`apps/web/server/lib/degradation.ts`) derives three flags from the adapter's capabilities: | Flag | Set when | Effect | | - | - | - | | `resumeViaHandoff` | `!caps.resume` | A note, and the execution's run reason becomes `resume-via-handoff`. | | `routeApprovalsThroughClawboo` | `!caps.toolApproval` | A note (`approvals-via-clawboo (no native tool approval)`) and nothing else. | | `coarseStreaming` | `!caps.streaming` | A note that progress arrives in blocks, not tokens (Hermes). | `describeDegradations` renders the notes into the prompt's context tier and the result's `degradations`, so a gap is visible. The flags do not change tool handling: Clawboo adds no approval gate for a runtime that lacks one. Every real adapter declares `resume` and `toolApproval`, so in practice only Hermes gets a note. ## Tracing Every dispatch runs inside `withTaskSpan`. The trace id is the mission root, so a multi-agent task renders as one trace. The run's span id is derived from its task id, and its parent span is either an explicit cross-process `parentTraceparent` or the parent task's span. Lifecycle events (`execution_started`, `cost`, `tool_call`, `tool_result`, `error`, `session_rotated`, `execution_completed`) go through `emitEvent`, which is best-effort. The Org chart's live overlay and the trace view read this same event log. See [Observability](/concepts/observability). ## Design rationale "Drive one runtime through one task" is one testable function with one set of invariants, instead of being spread across the team engine, the worktree code and each adapter. It buys three things: * **Capability-driven routing.** The connected-runtime refusal, home serialization, degradation notes and the rotation watermark all come from declared capabilities, so a new runtime is a new adapter and driver, not a runner change. * **One teardown per run.** Cumulative budget and breaker state above the rotation loop, the breaker yielding to a budget stop, and never rotating over a stop guarantee one `adapter.abort` and one terminal, even across a rotation chain. * **Recoverability.** Every terminal closes the execution row and either sets a real status or releases the task; the board's orphan and stale passes clean up after a crash (see [Orphan and stale reconciliation](/internals/board-internals#orphan-and-stale-reconciliation)). The cost is a second concurrency boundary, the per-agent home lock, on top of the board's claim: the claim de-duplicates a task, not a writer to one runtime's state directory. ## Related * [The RuntimeAdapter trait](/internals/runtime-adapter): the interface the runner consumes * [Board internals](/internals/board-internals): the claim, the transitions and the recovery passes * [How a team works](/intro/how-it-works): team chat versus the task runner * [What agents can do](/concepts/permissions): the coding runtimes' safety posture * [Verification](/concepts/verification) and [Governance](/concepts/governance): the gate and the budgets this runner enforces * [Runtimes API](/reference/rest-api/runtimes): `POST /api/runtimes/:id/run` # Gateway connection internals Source: https://docs.claw.boo/internals/gateway-and-events How Clawboo connects to the OpenClaw Gateway: the browser proxy, the handshake and device pairing, the server connection, and the guards in front of the API and the proxy. The OpenClaw Gateway is the long-running OpenClaw service that runs OpenClaw agents. It accepts a `connect` handshake, answers RPC calls (`chat.send`, `sessions.list`, `agents.create` and more), and broadcasts event frames (`chat`, `agent`, `exec.approval.*`, `presence`, `heartbeat`, `session.message`). Clawboo talks to it over two separate connections: one per browser tab through a same-origin proxy, and one long-lived connection held by the server. Use this page when you work on the proxy, device pairing, or the server's Gateway connection. For setting up OpenClaw as a user, see [OpenClaw](/runtimes/openclaw). For how browser frames become dashboard state, see [The event pipeline](/internals/event-pipeline). ## Two connections | | Browser connection | Server connection | | - | - | - | | Opened by | each dashboard tab's `GatewayClient`, through `/api/gateway/ws` | `OpenClawAgentSource`, when the server starts | | Credentials | the proxy injects the token and signs the device fields | the token, plus `signConnect` with the proxy's device identity | | `client.id` | `webchat-ui` | `cli` | | `caps` | `exec-approvals` | `tool-events`, `exec-approvals` | | Carries | 1:1 chat with an OpenClaw agent, live streaming and status, approval cards in that tab | every OpenClaw team-chat turn and Routine fire, the registry sync, the Gateway cron list on the Routines view, exec approvals with no tab open, and session activity and spend | The browser never talks to the Gateway directly, and the server never sends the saved Gateway token to the browser. The server's connection is the one that keeps working with every tab closed. ```mermaid theme={"theme":"github-dark"} flowchart LR subgraph Browser SPA["GatewayClient (per tab)"] end subgraph Server["Clawboo server"] G["Origin guard and access gate"] P["Gateway proxy: /api/gateway/ws"] S["OpenClawAgentSource connection"] DB[(SQLite)] end GW[OpenClaw Gateway] SPA -->|"same-origin WebSocket"| G --> P P -->|"token and device signature added"| GW S -->|"cli id, signConnect, origin"| GW S --> DB ``` ## The same-origin proxy `createGatewayProxy` (`@clawboo/gateway-proxy`) runs a `ws` `WebSocketServer` in `noServer` mode. The server's upgrade handler routes `/api/gateway/ws`, or `<base path>/api/gateway/ws` when Clawboo is mounted under a subpath (`CLAWBOO_BASE_PATH`), into the proxy after the Origin guard passes; it answers a refused handshake with `403 Forbidden` and destroys any other upgrade. The browser builds the URL from its own origin with `resolveProxyGatewayUrl()`, so it never hardcodes a port; the `ws://localhost:18790/...` fallback applies only where `window` is undefined (SSR and Node tests). The proxy is eager: when a browser socket opens, it immediately loads the upstream settings and opens its own socket to the Gateway, before the first browser message. The Gateway sends a `connect.challenge` event right after the handshake. The proxy captures its nonce to sign the connect, and the browser's client sends `connect` when the challenge arrives (or after 750 ms without one). Frames buffer in both directions until each side is ready, up to `maxPendingFrames` (512); an overflow closes the connection rather than growing without bound. Every frame passes through untouched except a `connect` request, which the proxy rewrites: 1. **Token injection.** If the browser sent no `auth.token` and the server has an upstream token, the proxy adds it. `GET /api/settings` exposes only a `hasToken` flag, never the saved value. The browser sends a token only when the operator types a new one on the connect screen; the proxy keeps that one. 2. **Device signing.** The proxy signs the connect params with a persistent Ed25519 identity, loaded once and shared by all connections (`loadOrCreateProxyDeviceIdentity`, `signConnectParams`, using the challenge nonce). It replaces any device fields the browser sent, and if signing fails it strips the device fields and forwards without them rather than failing the connect. Server-side signing is what lets any browser context (a preview pane, a private window, a new machine) connect without registering a device key of its own. The proxy also pings the upstream every `keepaliveIntervalMs` (30 seconds) and terminates a connection whose pong has not arrived by the next ping, so a half-open socket is torn down within one or two intervals instead of stalling an RPC. ## The connect handshake The browser's `GatewayClient.connect()` opens the socket, waits briefly for `connect.challenge`, then sends a `connect` request: | Field | Value | Why | | - | - | - | | `minProtocol` / `maxProtocol` | 3 / 4 | Clawboo negotiates either connect protocol, so older and newer Gateways both work. | | `role` | `operator` | The dashboard acts as an operator. | | `scopes` | `operator.admin`, `operator.approvals`, `operator.pairing` | Admin calls, answering approvals, and pairing. | | `client.id` | `webchat-ui` | The Gateway allowlists `client.id`. `openclaw-control-ui` claims to be the Gateway's own bundled Control UI, which the Gateway refuses unless its build id matches (`protocol mismatch: Control UI updated; reload this page to continue`). | | `caps` | `exec-approvals` (`GATEWAY_BROWSER_CAPS`) | Makes the socket an approval surface. The Gateway routes a held command only to connections that declared it; with no route, a run is denied as headless. | | device auth | `disableDeviceAuth: true` | The proxy signs the device fields, so the client's own `crypto.subtle` path is skipped. | A rejected connect is parsed into a `GatewayResponseError` carrying a `code`. ## Device pairing A new device must be approved once before the Gateway accepts it. An unapproved device's connect fails with `{ code: 'NOT_PAIRED', message: 'pairing required: device is not approved yet' }`. The dashboard branches on that code (in `GatewayConnectScreen`, `GatewayBootstrap` and `OpenClawInlineSetup`) and shows a one-click approval card. **1. Preview the pending request** The card posts to `POST /api/system/approve-device`, which runs `openclaw devices approve --latest`. On the CLI that is a preview: it prints the pending request id and exits non-zero by design. **2. Extract the request id** The handler matches `/openclaw devices approve\s+([a-f0-9-]{36})/i` against the captured stdout and stderr. No match answers `404` (no pending request found). **3. Approve it** The handler runs `openclaw devices approve <UUID>`. A non-zero exit here is a real failure and is reported with the CLI's stderr. **4. Reconnect** The dashboard connects again. On the connect screen that is one retry of the connect. In the OpenClaw setup flow, a failed reconnect (a second `NOT_PAIRED` included) shows the error with a **Retry** button, and Retry brings back a fresh approval card when the device still needs one. > [!WARNING] > > The endpoint parses the OpenClaw CLI's output for the request id. If a future OpenClaw release changes that wording, the parse fails and the endpoint answers `404`. The approval card also shows the manual commands (`openclaw devices approve --latest`, then `openclaw devices approve <requestId>`) so nobody is stuck. ## The server connection `OpenClawAgentSource` (`apps/web/server/lib/agentSource/openClawAgentSource.ts`) opens the server's single Gateway connection and signs it with the same proxy device identity through the gateway-client `signConnect` hook. Its connect options (`clientName: 'cli'`, an injected `ws` WebSocket with the Gateway host's origin, `signConnect`, and `caps: ['tool-events', 'exec-approvals']`) and why each is required are listed on the [AgentSource](/internals/agent-source) page. The connection has four jobs: 1. **Run OpenClaw agents for Clawboo.** Every OpenClaw turn in team chat (`buildOpenClawServerAdapter` over `operatorClient()`, used by `serverDeliver` and the peer exchange) and every OpenClaw Routine fire (`routines/openclawDispatch.ts`) runs over this connection, as do the read and write calls behind the Gateway cron list on the Routines view (the `openclaw-gateway-cron` schedule source). None of this needs a browser tab. 2. **Keep the registry in sync.** It reconciles the Gateway's `agents.list` into SQLite and resyncs on `presence`, `heartbeat` and `agent` frames. Reads serve SQLite, so the fleet renders when the Gateway is down. Creating an agent and reading or writing its files need a live Gateway; a delete made while the Gateway is down is queued for the next sync, and `updateAgent` writes SQLite only. 3. **Answer exec approvals with no tab open.** Because it declares `exec-approvals`, the Gateway routes a held shell command to it. The exec-approval surface (`execApprovalSurface.ts`) mirrors each `exec.approval.requested` into Clawboo's `tool_call_approvals` queue, keyed by the Gateway's own request id so a redelivery lands on the same row, and relays the answer back with `exec.approval.resolve`. It decides nothing: the command stays held by the Gateway, and the policy that decides which commands ask is OpenClaw's own. A card expires at the Gateway's deadline, or after 30 minutes, checked every 30 seconds; an expiry is never recorded as a deny. See [Approvals](/using/approvals). 4. **Record activity nobody started from Clawboo.** The session activity watcher (`sessionActivityWatcher.ts`) calls `sessions.subscribe` on every connect (a subscription dies with its socket) and reads `session.message`, which fires when a transcript row commits rather than on who is watching. A cron job the agent owns, an incoming channel message or someone typing at OpenClaw's own terminal still produces activity rows. The same frames carry real token counts, so the watcher records OpenClaw spend in `cost_records`, and it saves 1:1 transcripts so a conversation is kept even with no tab open. There is no other server-side Gateway client; every server feature that needs OpenClaw shares this one connection, and its reconnect and retry rules live in the source (see [Connection lifecycle](/internals/agent-source#connection-lifecycle)). ## Guards in front of the API and the proxy Two independent checks sit in front of `/api/*` and the Gateway WebSocket upgrade. Nothing guards the server's own outbound connection to the Gateway. [Security](/operating/security) is the canonical page; this is how they apply to the Gateway path. **The Origin and Host guard is always on** (`packages/gateway-proxy/src/origin-guard.ts`). Every `/api/*` request and every WebSocket upgrade is checked against an allowlist of origins and hosts, which defends against cross-site WebSocket hijacking (a page riding the proxy's injected token) and DNS rebinding on a default loopback install with no configuration. `CLAWBOO_ALLOWED_ORIGINS` and `CLAWBOO_ALLOWED_HOSTS` only widen the list; nothing disables it. A refused HTTP request gets `403 { "error": "Cross-origin request blocked." }`, and a refused upgrade gets `403 Forbidden`. **The access gate is optional** (`access-gate.ts`), active only when `STUDIO_ACCESS_TOKEN` is set. Opening `/?access_token=<token>` once sets an `HttpOnly` cookie and redirects; after that every `/api/*` request and the WebSocket upgrade need the cookie. The token is compared after SHA-256 hashing both sides, with `timingSafeEqual`. A token containing a character outside `[A-Za-z0-9._~-]` stops the server at boot on every bind (`accessTokenRefusal.ts`), and `createAccessGate` throws on one. The server also refuses to start on a non-loopback bind with no token unless `CLAWBOO_ALLOW_INSECURE=1`. > [!NOTE] > > The gate admits one other credential: the `attachToken` that the server writes into the MCP attach URLs it gives to the runtimes it spawns (whose environment is scrubbed of the access token), and into the Memory, Tasks and Tools URLs it registers in a Gateway on the same machine. Only the four transport routes (`/api/mcp/tasks`, `/memory`, `/tools`, `/teamchat`) accept it; `/api/mcp/config` never does. It is accepted only on a direct loopback request with no forwarding headers, never from another host or through a proxy, because a same-host reverse proxy delivers every remote request from loopback. ## What the browser does with frames Each tab's frames run through the [event pipeline](/internals/event-pipeline): the Bridge classifies a frame, the Policy derives intents, and the Handler applies them to the stores. Two rules there depend on the split above: * **Team chat is the server's.** For a team session key (`agent:<id>:team:<teamId>`, with or without a room suffix) the browser skips any `chat` frame it receives rather than writing a second copy: no transcript commit and no live streaming-text writes. The team engine persists each turn and streams it back over `GET /api/teams/:id/chat/stream`. * **OpenClaw spend is the server's.** OpenClaw turns are billed from the Gateway's real token counts by the session activity watcher, so the browser's cost path skips OpenClaw agents. A non-OpenClaw agent whose turn reaches the browser as a Gateway `chat` frame is billed from the frame's usage block when it has one, and otherwise estimated at about 4 characters per token. ## Design rationale **A proxy rather than a direct browser connection.** A direct connection would have to ship the upstream token to the browser and make every browser context register its own device key. The proxy keeps the credential on the server and reuses one device identity, at the cost of one hop and a rewrite of the `connect` frame, paid once per connection. **One server connection for everything the server does.** Funnelling registry sync, OpenClaw runs, approvals, cron and session activity through one `OpenClawAgentSource` connection gives exactly one place that owns the connect options, the reconnect rules and the pairing state on the server. The Gateway accepts many connections per device (every browser tab is one), so the server's is one more. **Declare only what is answered.** The server declares `exec-approvals` only because a handler mirrors every request it receives into the approvals queue and relays the answer a person gives. A declaring connection that never answered would turn the Gateway's fast refusal into a held command waiting 30 minutes for nobody. ## Boundaries * **OpenClaw only.** The proxy, the handshake and the pairing flow exist for the OpenClaw Gateway. The other runtimes run on the server through their adapters (see [the RuntimeAdapter trait](/internals/runtime-adapter)). * **Pairing depends on the OpenClaw CLI.** `approve-device` is a convenience over `openclaw devices approve`, not a reimplementation of OpenClaw's pairing. * **The server keeps the Gateway credentials.** The saved token and the Ed25519 device identity stay on the server, and the identity signs the Gateway handshake only. Dashboard access is the separate, optional `STUDIO_ACCESS_TOKEN`, held in the browser as an `HttpOnly` cookie while the gate is on. ## Related * [OpenClaw](/runtimes/openclaw): connecting and using the OpenClaw runtime * [The event pipeline](/internals/event-pipeline): how browser frames become dashboard state * [AgentSource](/internals/agent-source): the server connection's sync, connect options and reconnect rules * [Architecture invariants](/internals/architecture-invariants): the same-origin rule and the browser pipeline rule * [Security](/operating/security): the Origin guard, the access gate and remote access * [Gateway client and proxy](/reference/packages/gateway-client): the `@clawboo/gateway-client` and `@clawboo/gateway-proxy` packages # Contributor overview Source: https://docs.claw.boo/internals/index Where contributors start: how the monorepo is laid out, how the pieces fit together at runtime, and which internals page covers the code you are changing. This tab is for people who change Clawboo's code: fix a bug, add a runtime, touch the board, or cut a release. It explains how the repository is laid out, how the parts talk to each other while Clawboo runs, and where to read before you change something. If you want to use Clawboo rather than change it, start with the [Quickstart](/getting-started/quickstart). The process rules (setup, branches, the local gate, changesets) are on [Contributing](/appendices/contributing) and in the repo's [`CONTRIBUTING.md`](https://github.com/clawboo/clawboo/blob/main/CONTRIBUTING.md). ## How the codebase is laid out Clawboo is a TypeScript monorepo. pnpm workspaces link the packages and Turborepo runs the build in dependency order. `pnpm-workspace.yaml` has four globs: `packages/*`, `packages/adapters/*`, `apps/*` and `docs`. | Path | What lives there | | - | - | | `apps/cli` | The `clawboo` CLI, the only package published to npm. At release its `dist/` also carries the server bundle, the SPA and the MCP stdio bins. | | `apps/web/src` | The dashboard: a React single-page app built with Vite. | | `apps/web/server` | The Express server: the REST API (`server/api/`), the team engine, the task runner and the runtime drivers (`server/lib/`). | | `packages/*` | 27 shared packages under the `@clawboo/*` scope, one of them the config-only `tsconfig`. | | `packages/adapters/*` | 6 runtime adapters: `native`, `openclaw`, `claude-code`, `codex`, `hermes`, and `mock` (a test-only runtime). | | `docs/` | This site: hand-edited Mintlify pages, deployed as they are. | | `catalog/` | Marketplace content (agent and team packs). Not a workspace member. | | `website/` | The marketing site, with its own lockfile. Not a workspace member. | | `scripts/`, `tests/e2e/` | Repo scripts (bundle assembly, the clean-install gate, catalog tools) and the Playwright specs. | So `packages/` holds 33 `@clawboo/*` packages: 27 at the top level and 6 adapters. Every one of them is `private: true`. The build inlines the libraries the server needs into one bundle, which ships inside the `clawboo` package. Dependencies flow one way: apps import packages, and a lint rule fails any package that imports an app. [Monorepo and build](/internals/monorepo-and-build) covers the build order and what ships. The packages group by job: | Job | Packages | | - | - | | Board and coordination | `db`, `board-core`, `team-orchestration`, `governance`, `compaction` | | Running agents | `executor` (the `RuntimeAdapter` trait), `adapters/*`, `worktrees`, `model-catalog` | | OpenClaw Gateway | `gateway-client`, `gateway-proxy`, `events`, `protocol` | | Registries | `agent-registry`, `capability-registry`, `scheduler` | | Tools and connectors | `mcp` (the Tasks, Memory, Tools and TeamChat servers), `connector-catalog` | | Observability and evals | `obs`, `evals`, `logger` | | UI | `ui`, `boo-avatar` | | Shared plumbing | `config`, `control-client`, `process-lookup`, `pack-format`, `tsconfig` | The [package overview](/reference/packages/index) says what each package is for, who uses it and how the packages depend on each other, and links the deeper package guides. ## Architecture at a glance Clawboo runs as one local Node process. The CLI starts it, the browser talks to it, and it keeps its state in one SQLite file. Agents run on the runtimes you connect, and every runtime reaches Clawboo's board and memory through MCP servers. ```mermaid theme={"theme":"github-dark"} flowchart TB cli["clawboo CLI"] -->|"starts it, then exits"| server browser["Browser: the dashboard"] -->|"same-origin HTTP and WebSocket"| api subgraph server["Clawboo server: one Node process"] api["Express: /api/* and the SPA files"] engine["Team engine"] runner["Task runner"] mcp["MCP servers"] end server --- db[("SQLite: clawboo.db")] engine --> rt["Clawboo Native, Claude Code,<br/>Codex, Hermes"] runner --> rt engine --> gw["OpenClaw Gateway"] api -->|"WebSocket proxy"| gw rt -->|"MCP"| mcp gw -->|"MCP"| mcp ``` ### The CLI starts the server, then exits `clawboo` is a launcher. It first looks for a dashboard that is already running: the port in `CLAWBOO_API_PORT` (or `CLAWBOO_API_URL`), then the port the server wrote to `~/.clawboo/api-port.txt`, then ports 18790 to 18809. A port counts only when `GET /api/settings` answers with Clawboo's JSON shape, because other services listen in that range. If nothing answers, the CLI forks the bundled `server.js` as a detached process with `~/.clawboo` as its working directory, waits for it, and opens the browser. The code is in `apps/cli/src/lifecycle.ts`. ### The server serves the dashboard and the API on one origin The Express server serves the built SPA and every `/api/*` route from the same origin, so the browser needs no backend address. It binds `127.0.0.1` unless `HOST` says otherwise, and listens on the first free port from 18790 to 18809 (or exactly `CLAWBOO_API_PORT`). The SPA builds its requests with `apiFetch()` and `apiUrl()` from `@clawboo/control-client`, so the app also works under a path prefix (`CLAWBOO_BASE_PATH`). An optional access token, `STUDIO_ACCESS_TOKEN`, gates `/api/*` and the Gateway WebSocket. A non-loopback bind without a token refuses to start unless `CLAWBOO_ALLOW_INSECURE=1` is set. [Security](/operating/security) has the full model; the entry point is `apps/web/server/index.ts`. Prompts go only to the providers and apps you connect, and Clawboo itself sends no telemetry. The server makes three calls of its own. It fetches the marketplace catalog index from GitHub (`catalogIndex.ts`, overridable with `CLAWBOO_CATALOG_INDEX_URL`) and checks npm for a newer `clawboo` (`updateCheck.ts`), each cached for 6 hours. When a model picker opens, it also reads OpenRouter's public model list without a key (`openrouterModels.ts`, cached for 30 minutes). ### SQLite holds the state One file, `clawboo.db` in `~/.clawboo` (or `CLAWBOO_HOME`), holds the board, the agent registry, memory, budgets and the event log. Server code reaches it through one shared connection, `getDb()` in `apps/web/server/lib/db.ts`. The schema is the `CREATE TABLE IF NOT EXISTS` DDL in `packages/db/src/schemaBootstrap.ts`. Opening an older file adds the columns it is missing; there is no migration ladder. See [Board internals](/internals/board-internals) and the [database schema](/reference/database-schema). ### Agents run on runtimes A runtime is the engine an agent runs on. Clawboo Native runs inside the server process and calls the provider SDKs directly. Claude Code (through the Claude Agent SDK), Codex (`codex app-server`) and Hermes (its CLI) run as local processes the server starts. OpenClaw agents run inside the OpenClaw Gateway. Each runtime has a `RuntimeAdapter` that maps its output onto one normalized event stream; see [The RuntimeAdapter trait](/internals/runtime-adapter). ### MCP servers give every runtime the same tools Clawboo hosts four MCP servers over the same SQLite data: Tasks (the board), Memory, Tools and TeamChat. Clawboo Native connects to them in-process. Claude Code, Codex and Hermes attach over loopback HTTP at `/api/mcp/*`; each run's attach URL carries a token the server checks. OpenClaw agents get Tasks, Memory and Tools through the Gateway's config, and no TeamChat. The same servers ship as stdio bins (`clawboo-mcp-tasks` and three more) for agents outside Clawboo; those bins open `~/.openclaw/clawboo/clawboo.db` unless `CLAWBOO_DB_PATH` says otherwise. See [MCP servers](/operating/mcp-servers). ### The OpenClaw Gateway is reached two ways The browser opens a WebSocket to `/api/gateway/ws`, and the server proxies it to the Gateway. The server adds the saved Gateway token and signs the device handshake, so the browser needs no Gateway credential of its own. This connection carries OpenClaw 1:1 chat and the live fleet view, which the browser processes through the pipeline in `@clawboo/events`. The server also keeps its own Gateway connection. It runs OpenClaw team-chat turns and Routine fires, syncs OpenClaw agents into SQLite, and records their activity and spend. It also mirrors OpenClaw's shell-command approval requests, so they still reach you when no dashboard tab is open. See [Gateway connection internals](/internals/gateway-and-events). ### The event log feeds observability The server appends coordination events (a task created, a delegation routed, a run started, a cost recorded) to the `orchestration_events` table. The Observability panel and the live status layer on a team's graph read from that log. The graph's layout comes from the team roster and each agent's routing, not from the log. See [Observability](/concepts/observability). ### The two execution paths Most work reaches a runtime along one of two server paths. They share the board and the runtimes, but not the safety and quality machinery, so check which path your change sits on. [How a team works](/intro/how-it-works) explains the same split for users. | | Team engine | Task runner | | - | - | - | | What it runs | Team chat turns, delegations and plan steps; board tasks a person assigns or retries; team Routine fires | `POST /api/runtimes/:id/run`; Routine fires aimed at one Clawboo Native, Claude Code, Codex or Hermes agent | | Code | `apps/web/server/lib/teamChat/` over `@clawboo/team-orchestration` | `runTaskOnRuntime` in `apps/web/server/lib/executorRunner.ts` | | Runtimes | All five; OpenClaw over the server's Gateway connection | All except OpenClaw, which it refuses | | Git worktree | Never | For a task that changes files in a named repo; such a task with no repo is refused | | Working folder | The team's folder, `<CLAWBOO_HOME>/workspaces/<teamId>` (`runtimes/teamWorkdir.ts`) | The worktree, or for a `research` or `review` task the team's folder (`_no-team` when it has no team) | | Verification | No | Only for a worktree run that changed files | | Circuit breakers | No | Yes | | Memory auto-injection | No | Yes | Two smaller paths sit beside these. A Routine fire aimed at one OpenClaw agent goes through a dispatcher on the server's Gateway connection (`apps/web/server/lib/routines/openclawDispatch.ts`), with no worktree and no verification. A 1:1 chat with a Clawboo Native, Claude Code, Codex or Hermes agent runs one conversational turn per message in `apps/web/server/lib/agentChat/driveAgentChat.ts`, with no board task, in the agent's team folder (`workspaces/_no-team` for an agent on no team). Claude Code, Codex and Hermes run with their own sandboxes and approval prompts turned off on every path, 1:1 chat included. A worktree keeps a task's file changes apart, but it is not a sandbox, and neither is the team's folder that a run without a worktree starts in. [What agents can do](/concepts/permissions) is the page that explains this; [Verification](/concepts/verification) and [Governance](/concepts/governance) cover the checks and budget guards per path. ## Where to start reading 1. [Contributing](/appendices/contributing): set up the repo, run the dev stack and pass the local gate. 2. [Monorepo and build](/internals/monorepo-and-build): the workspace, the build order and what ships. 3. [Architecture invariants](/internals/architecture-invariants): the rules every change keeps. 4. The page and the code for the area you are changing: | Area | Start in | | - | - | | REST API | `apps/web/server/api/`; every route is registered in `api/index.ts` | | Team chat and delegation | `apps/web/server/lib/teamChat/` and `packages/team-orchestration` | | Running one task | `apps/web/server/lib/executorRunner.ts` | | Runtime drivers and adapters | `apps/web/server/lib/runtimes/` and `packages/adapters/*` | | Board data and task states | `packages/db/src/board/` and `packages/board-core` | | Routines | `apps/web/server/lib/routines/` and `packages/scheduler` | | MCP servers | `packages/mcp` | | OpenClaw Gateway | `packages/gateway-client`, `packages/gateway-proxy`, `packages/events`, `apps/web/server/lib/agentSource/` | | Dashboard UI | `apps/web/src/features/*`; view state in `apps/web/src/stores/view.ts` | | The CLI | `apps/cli/src/` (`lifecycle.ts` finds and starts the server) | > [accordion] Dashboard views in code > > The main area shows one `ViewMode` from `apps/web/src/stores/view.ts`: an agent page (`agent`), a team chat in General or a room (`groupChat`), a team's Home (`teamspace`), Boo Zero (`booZero`), the welcome screen (`welcome`), or a nav panel (`nav`). Each nav panel has a `NavView` id, which is what `navigateTo(view)` takes. The labels come from `apps/web/src/lib/navLabels.ts`. > > | `NavView` id | Label | Where it lives | > | - | - | - | > | `graph` | Org chart | Sidebar, `Cmd/Ctrl+1` | > | `board` | Board | Sidebar, `Cmd/Ctrl+2` | > | `connectors` | Connectors | Sidebar, `Cmd/Ctrl+3` | > | `memory` | Memory | Sidebar, `Cmd/Ctrl+4`, and Settings | > | `marketplace` | Marketplace | Sidebar | > | `providers` | Providers | Settings, Workspace | > | `runtimes` | Runtimes | Settings, Workspace | > | `capabilities` | Capabilities | Settings, Workspace | > | `routines` | Routines | Settings, Workspace | > | `fleet` | Fleet | Settings, Insights | > | `cost` | Tokens Used | Settings, Insights | > | `obs` | Observability | Settings, Insights | > | `governance` | Governance | Settings, Insights | > | `system` | System | Settings, System | > | `health` | System Health | Settings, System | ### Caution surfaces A few files hold rules whose breakage shows up far from the edit. Read the comment block at the top of each, and the linked page, before you change one. | File | What it protects | | - | - | | `packages/db/src/board/repository.ts` (`createCappedSubtask`) | Counting a parent's children and inserting the new one happen in one `BEGIN IMMEDIATE` transaction, so caps hold across processes. Only the real-concurrency suite in CI's Test job catches a break. | | `packages/db/src/board/contention.ts` and `packages/db/src/db.ts` | The SQLite write recipe: WAL, `busy_timeout` and a jittered retry. See [Board internals](/internals/board-internals). | | `apps/web/server/lib/db.ts` (`getDb()`) | One connection per server process. Server code never opens or closes its own; `sharedConnection.test.ts` enforces it. | | `apps/web/server/lib/executorRunner.ts` (`acquireWorkspace`) | A task that changes files gets a worktree or is refused. See [The executor runner](/internals/executor-runner). | | `apps/web/server/lib/agentSource/registry.ts` (`connectOptions`) | How the server's own Gateway connection authenticates. See [AgentSource](/internals/agent-source). | | `apps/web/src/features/connection/useGatewayEvents.ts` (`recordChatCost`) | Skips OpenClaw agents, because the server records their spend. Recording it here too would bill each turn twice. | | `.github/workflows/publish.yml` | The publish job has no changeset gate, on purpose. See [Release process](/internals/release-process). | | `apps/web/index.html` and `apps/web/src/features/theme/ThemeProvider.tsx` | The pre-mount theme script and the provider must resolve the theme the same way. See [Design system](/internals/design-system). | ## Read it when | Page | Read it when | | - | - | | [Contributing](/appendices/contributing) | You are about to open your first pull request. | | [Monorepo and build](/internals/monorepo-and-build) | You add a package, change a dependency edge, or need to know what reaches the npm package. | | [Testing](/internals/testing) | You add a test, a CI job fails, or you touch the e2e sandbox. | | [Release process](/internals/release-process) | You write a changeset or cut a release. | | [Design system](/internals/design-system) | You change UI: tokens, themes or the shared components. | | [Architecture invariants](/internals/architecture-invariants) | Before you change coordination, the Gateway path or the schema. | | [Gateway connection internals](/internals/gateway-and-events) | You work on the OpenClaw connection or the WebSocket proxy. | | [The RuntimeAdapter trait](/internals/runtime-adapter) | You add a runtime or change how one is driven. | | [AgentSource](/internals/agent-source) | You change how agents are created, synced or deleted. | | [Capability and schedule sources](/internals/seams) | You change what the Capabilities panel or Routines read from each runtime. | | [The executor runner](/internals/executor-runner) | You change how one task runs: worktrees, prompts, verification, breakers. | | [Board internals](/internals/board-internals) | You change task states, claims or the SQLite write path. | | [The event pipeline](/internals/event-pipeline) | You change how the browser handles Gateway frames. | | [Package overview](/reference/packages/index) | You need to know which package owns something and what it may import. | | [License and notices](/appendices/license-and-notices) | You add a dependency or bundled third-party content. | ## Related * [`CONTRIBUTING.md`](https://github.com/clawboo/clawboo/blob/main/CONTRIBUTING.md): the process rules in the repo itself. * [How a team works](/intro/how-it-works): the same system from a user's point of view. * [REST API reference](/reference/rest-api/index): every route the server registers. * [Glossary](/appendices/glossary): the terms these pages use. # Monorepo and build Source: https://docs.claw.boo/internals/monorepo-and-build How the pnpm and Turborepo workspace is wired, the real build order, what the npm package contains, the root commands, and the lint and schema rules. Clawboo is one pnpm workspace built with Turborepo. Shared libraries live under the `@clawboo/*` scope in `packages/`, and two apps consume them: `apps/web` (the dashboard and its Express server) and `apps/cli` (the `clawboo` command). Use this page when you add a package, change a dependency edge, add a root command, or need to know what ends up in the npm package. What each package is for, who uses it, and the dependency layers are in the [package overview](/reference/packages/index), which links the deeper package guides. How the parts behave at runtime is on the [contributor overview](/internals/index). ## The workspace `pnpm-workspace.yaml` declares four globs: ```yaml theme={"theme":{"light":"github-dark","dark":"github-dark"}} packages: - 'packages/*' - 'packages/adapters/*' - 'apps/*' - 'docs' ``` The second glob matters. The runtime adapters sit one level deeper, in `packages/adapters/*`, so without it pnpm would not find them and `workspace:*` links to them would fail. `packages/adapters` itself has no `package.json`. | Part | Count | Notes | | - | - | - | | `packages/*` | 27 | Includes the config-only `@clawboo/tsconfig` (`base.json`, `react.json`, `node.json`), which is a devDependency everywhere and has no code. | | `packages/adapters/*` | 6 | `native`, `openclaw`, `claude-code`, `codex`, `hermes`, and `mock`: a fault-injection runtime that appears only when `CLAWBOO_ENABLE_MOCK_RUNTIME=1` is set. | | `apps/*` | 2 | `@clawboo/web` (private) and `clawboo` in `apps/cli` (the one public package). | | `docs` | 1 | `@clawboo/docs` (private): this Mintlify site, deployed as it is. | All 33 packages under `packages/` are `private: true`, and so are `@clawboo/web` and `@clawboo/docs`. Apps depend on them through `workspace:` links, so there is no publish-then-install loop: Turbo builds a package's `dist/` and the app that depends on it reads that directly. ### Folders that are not workspace members `website/` (the Astro marketing site, with its own lockfile) and `catalog/` (the marketplace content) sit outside every glob on purpose. `catalog/` is JSON content: agent prose, team routing, and the artifacts built from them. As a workspace member, every content pull request would run `turbo lint`, `turbo typecheck` and `turbo test` over files with no code. It has no `package.json` and appears in no Turbo task; `scripts/catalog/*` reach it from the repo root, and `.github/workflows/catalog-ci.yml` is its fast CI path. See [the marketplace catalog reference](/reference/marketplace-catalog). ## Layer boundaries `apps/web` is two build targets in one package: tsup bundles `server/` for Node, and Vite bundles `src/` for the browser. With the one-way rule between apps and packages, that gives three boundaries. `eslint.config.mjs` enforces all three: | Rule | Why | | - | - | | `apps/web/server/**` may not import `apps/web/src/**` | Keeps browser code out of the server bundle. | | `apps/web/src/**` may not import `apps/web/server/**` | Server modules pull in `node:*` and native modules that would be dragged into the browser bundle. | | `packages/**` may not import `apps/**` | Dependencies flow one way; a package that reached into an app would invert the build graph. | Code two layers both need goes into a `@clawboo/*` package instead. `@clawboo/model-catalog` is the example: the model catalog both the SPA and the server read. The rules match relative paths only, so a bare specifier such as `react-dom/server` is not mistaken for a crossing. The two `apps/web` rules pair `no-restricted-imports` with a `no-restricted-syntax` selector, because `no-restricted-imports` does not see dynamic `import()`. The server rule runs only because `apps/web`'s `lint` script is `eslint src/ server/`; [`importBoundary.test.ts`](/internals/testing) fails if that script stops covering `server/`. ### Other SPA lint rules The same config holds three more rules for `apps/web/src`. Each one fails lint: | Rule | Use instead | Why | | - | - | - | | No capture-phase `keydown` or `keyup` listeners | Register the overlay with `useDismissableLayer()`, or listen in the bubble phase | A capture listener runs before the dismiss stack and every React `onKeyDown`, so two overlays act on one Escape. | | No root-absolute `/api/...` URL in `fetch()` or `new EventSource()` | `apiFetch()` and `apiUrl()` from `@clawboo/control-client` | Clawboo can be served under a path prefix (`CLAWBOO_BASE_PATH`); a hardcoded root path skips the prefix and 404s. | | No root-absolute static asset path in a JSX `src`, `href` or `poster` | A relative path such as `logo.svg` | A relative path resolves against the `<base href>` the server injects, which is right at the root and under a prefix. | The repo's own `scripts/` folder is linted too: `pnpm lint` ends with `pnpm lint:repo` (`eslint scripts/ --max-warnings 0`). ## The build pipeline `turbo.json` defines five tasks, and each one depends on the upstream build: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "globalDependencies": ["eslint.config.mjs"], "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "dev": { "cache": false, "persistent": true, "dependsOn": ["^build"] }, "lint": { "dependsOn": ["^build"] }, "typecheck": { "dependsOn": ["^build"] }, "test": { "dependsOn": ["^build"] } } } ``` * **`^build`** means "build every dependency of this package first". Turbo reads each `package.json`'s `@clawboo/*` edges and sorts them, so nobody maintains the order by hand. `lint`, `typecheck` and `test` depend on it too, so they run against fresh `dist/` output, even on a clean checkout. * **`outputs: ["dist/**"]`** lets Turbo skip an unchanged package and restore its `dist/` from cache. * **`globalDependencies`** puts `eslint.config.mjs` into every task's cache key, so an ESLint config change invalidates the whole cache. * **`dev`** is a long-running watcher: never cached, always persistent. Each library builds with tsup, which writes CJS, ESM and `.d.ts` files to `dist/`. `apps/web` builds in three steps: `vite build` for the SPA, then one tsup run for the server bundle (`tsup.server.config.ts`) and one for the four MCP stdio bins (`tsup.mcp-bins.config.ts`). `apps/cli` is one tsup run with two entries, `src/index.ts` and `src/bootstrap.ts`. ## Build order Turbo computes the exact order from the `package.json` graph. The table groups packages by depth: the longest chain of `@clawboo/*` dependencies beneath each one (`tsconfig` left out, since it has no code). | Depth | Packages | | - | - | | 0 | `agent-registry`, `board-core`, `boo-avatar`, `capability-registry`, `compaction`, `config`, `connector-catalog`, `executor`, `governance`, `logger`, `model-catalog`, `obs`, `pack-format`, `process-lookup`, `protocol`, `scheduler`, `worktrees` | | 1 | `adapter-claude-code`, `adapter-codex`, `adapter-hermes`, `adapter-mock`, `adapter-native` (each needs `executor`); `control-client` (`agent-registry`); `db` (`board-core`, `compaction`, `governance`, `obs`); `gateway-client` (`logger`); `gateway-proxy` (`config`); `team-orchestration` (`board-core`, `executor`, `governance`); `ui` (`boo-avatar`); the `clawboo` CLI (`config` and `process-lookup`, as devDependencies) | | 2 | `evals` (`db`, `executor`, `governance`, `obs`); `events` (`gateway-client`, `logger`, `protocol`); `mcp` (`db`) | | 3 | `adapter-openclaw` (`events`, `executor`, `gateway-client`, `logger`, `protocol`) | | 4 | `@clawboo/web` | `apps/web` depends directly on 31 workspace packages, all six adapters included. `boo-avatar` reaches it through `ui`, so every package except the config-only `tsconfig` ends up in the app. `board-core` holds the task state machine that `db`, `team-orchestration` and the board UI all import. `executor` has no runtime dependencies and exports three entry points: `.`, `./contract` and `./tiers`. ## What publishes `pnpm build` produces a `dist/` for every package, but only `clawboo` (the CLI in `apps/cli`) reaches npm. Every other package is `private: true`, so `pnpm changeset publish` skips it, and `packagePosture.test.ts` fails the build if a second package goes public. The server's libraries ship inside the CLI as one bundle. `tsup.server.config.ts` marks the whole `@clawboo/*` scope `noExternal`, so `dist/server.js` inlines every workspace library it uses. `scripts/assemble-cli.sh` (run by `pnpm assemble`) then copies the server bundle, the SPA, the four MCP bins and `THIRD_PARTY_NOTICES.md` into `apps/cli/dist/`. It fails if a `marketplace-catalog-*` chunk shows up in the SPA assets: only the small built-in seed may ship, and the full catalog is fetched at runtime. `apps/cli/package.json` publishes `files: ["dist"]`, so the tarball holds: | File | What it is | | - | - | | `dist/bootstrap.js` | The `clawboo` bin. It checks the Node version before anything else loads, then loads `index.js`. | | `dist/index.js` | The CLI itself. | | `dist/server.js` | The Express server with the workspace libraries inlined. | | `dist/ui/` | The built SPA. | | `dist/bin/{tasks,memory,tools,teamchat}.js` | The stdio MCP servers, published as the `clawboo-mcp-tasks`, `-memory`, `-tools` and `-teamchat` bins. | | `dist/THIRD_PARTY_NOTICES.md` | Licenses of the bundled dependencies, which do not travel in `node_modules`. | npm adds its usual files on top (`package.json`, the README, the license). A few modules stay outside the server bundle (the CLI's own `dist/index.js` also leaves its declared `@clack/prompts`, `chalk`, `commander` and `ora` external): * **External and declared in the CLI's `dependencies`:** `better-sqlite3`, `ws`, `pino`, `pino-pretty` and `@composio/client`. * **External and optional:** `@opentelemetry/*` and `@anthropic-ai/claude-agent-sdk`. Both load lazily, and the npm package ships neither. Without OpenTelemetry, traces stay in the event log and OTLP export is unavailable. Without the Agent SDK, a Claude Code run fails with a message that says what to install (see [Claude Code](/runtimes/claude-code)). * **Inlined:** everything else, including `express` and its middleware, `drizzle-orm`, `@noble/ed25519`, the provider SDKs (`@anthropic-ai/sdk`, `openai`) and `croner`, so a clean install runs Clawboo Native and Routines with nothing extra. The CLI's only workspace edges are devDependencies (`@clawboo/config`, `@clawboo/process-lookup`, `@clawboo/tsconfig`). tsup inlines whatever is not listed in `dependencies`, so those reach the published package already bundled. `pnpm test:bundle-externals` checks the shipped bundles statically: every module they still load must be declared, a Node builtin, or on the optional list in `scripts/lib/bundle-externals.mjs`. [Testing](/internals/testing) covers that check and the clean-install gate. ## Root commands Run these from the repo root. The Turbo-backed ones fan out across the workspace in build order. | Command | What it does | | - | - | | `pnpm build` | `turbo build`: every package and app, in dependency order, cached. | | `pnpm dev` | `turbo dev --concurrency=20`: builds the dependencies, then starts each member's `dev` script (see below). | | `pnpm lint` | `turbo lint && pnpm lint:repo`: ESLint in every package, the docs frontmatter and heading check (the `docs` package's `lint`), then `scripts/`. | | `pnpm lint:repo` | `eslint scripts/ --max-warnings 0`. | | `pnpm format` | `prettier --write .`: fixes formatting. | | `pnpm format:check` | `prettier --check .`: the first step of CI's Lint job. A formatting-only failure turns that job red while ESLint passes. | | `pnpm typecheck` | `turbo typecheck`: `tsc --noEmit` in every package. The web app and CLI builds do not type-check, so their type errors surface here. | | `pnpm test` | `turbo test`: each package's own Vitest suites. | | `pnpm test:coverage` | The same suites plus a per-package coverage table. CI's Test job runs this; there is no threshold. | | `pnpm e2e` | `playwright test`: the sandboxed end-to-end suite. It needs `pnpm build` first. | | `pnpm assemble` | `pnpm build && bash scripts/assemble-cli.sh`: build, then copy the server bundle, SPA and MCP bins into `apps/cli/dist/`. | | `pnpm catalog:build` | Rebuilds `catalog/dist/**` and the compiled marketplace seed from the packs. | | `pnpm catalog:verify` | Every marketplace content rule, then a `--check` that `catalog/dist` and the seed match a rebuild. Offline. | | `pnpm verify:connectors` | Checks the committed connector catalog. Offline; add `--live` to re-resolve pinned packages and dial remote endpoints. | | `pnpm check:docs` | `docs/scripts/check-frontmatter.mjs`: fails on frontmatter that is not valid YAML, or a bare `%` in a heading. | | `pnpm test:clean-install` | Packs and installs the CLI tarball, then boots that install. Run `pnpm assemble` first. | | `pnpm test:bundle-externals` | The static check that the assembled bundles load only declared, builtin or optional modules. | | `pnpm prepublish:check` | `pnpm catalog:verify && pnpm verify:connectors && pnpm assemble && pnpm test:clean-install`. | A Husky pre-commit hook runs `lint-staged`, which applies `eslint --fix` and Prettier to staged code files and Prettier to staged JSON, `.md`, YAML and CSS files. Staged `.mdx` files get no Prettier pass, so run `pnpm format` before committing one. ### The dev stack Two members have a `dev` script, so `pnpm dev` starts both: * **`apps/web`** runs `scripts/dev-orchestrator.cjs`. It picks the API port first: exactly `CLAWBOO_API_PORT` if set (and exits if that port is busy), otherwise the first free port of 20 from `CLAWBOO_API_PORT_START` (default 18790). It then runs `pnpm dev:api` (`tsx watch server/index.ts --dev`) and `pnpm dev:ui` (Vite on port 5173) with that port in their environment, so the UI's proxy and the API agree. * **`docs`** runs `mint dev --port 3111`, the Mintlify preview. It needs the Mintlify CLI (`npm i -g mint`, or `npx mint`). The repo needs Node `>=22.12.0` and pnpm `>=9`; `packageManager` pins `pnpm@9.15.0`, and `corepack enable` selects it. CI uses Node 22 and `pnpm install --frozen-lockfile`, so an out-of-date lockfile fails the install. ## Schema changes Besides the usual build, lint, typecheck and test, `@clawboo/db` has one script: `db:studio` (`drizzle-kit studio`, a browser over `packages/db/dev.db`). There is no `db:migrate`, no `db:generate`, and no migration ladder. The schema is the `CREATE TABLE IF NOT EXISTS` DDL in `ensureSchema` (`packages/db/src/schemaBootstrap.ts`), and it is the only place tables are created. `schema.ts` is the Drizzle type layer for typed queries; it never applies a migration. When the server opens an older database, `reconcileSchema` (`packages/db/src/schemaReconcile.ts`) reads the column list back out of the same DDL and adds any column the file is missing. That makes one rule load-bearing. A new column on an existing table must be addable to a database that already exists: * no `PRIMARY KEY`, no `UNIQUE`, and no `STORED` generated column; * a `DEFAULT` must be a literal, not an expression; * a `NOT NULL` column must have a non-NULL `DEFAULT`; * a `REFERENCES` column must not have a non-NULL `DEFAULT`. `schemaReconcile.test.ts` enforces it against the snapshot in `packages/db/src/__tests__/schemaBaseline.ts`; a column added after the snapshot counts as new and is held to the rule, so the snapshot needs no upkeep. The reconciler only adds columns. It leaves a column the DDL no longer declares in place, and it never changes a column's type, `NOT NULL`, `DEFAULT`, constraints or index definitions, so none of those changes is an in-place upgrade. `schemaSource.test.ts` checks that every `schema.ts` table and column matches the DDL, and that no migration scripts or `drizzle/` directory come back. The per-table reference is the [database schema](/reference/database-schema). ## Related * [Package overview](/reference/packages/index): what each package is for, who uses it, and the dependency layers. * [Testing](/internals/testing): the test layers and the CI job list. * [Release process](/internals/release-process): changesets and the publish workflow. * [Marketplace catalog](/reference/marketplace-catalog): the pack format behind `catalog:build` and `catalog:verify`. * [Contributor overview](/internals/index): the repo map and the runtime architecture. # Release process Source: https://docs.claw.boo/internals/release-process How Clawboo ships: changesets, the two-phase publish workflow, what it reruns before npm, provenance, and why only the clawboo CLI publishes. A Clawboo release publishes one npm package: `clawboo`, the CLI in `apps/cli`. Changesets track the version and write the changelog, and `.github/workflows/publish.yml` does the rest in two phases: it opens a Version PR, and when that PR merges it publishes. Contributors add changesets; maintainers merge the Version PR. Use this page when you write a changeset, cut a release, or need to know why the publish posture looks the way it does. The build side (Turbo, the bundle, `assemble-cli.sh`) is in [Monorepo and build](/internals/monorepo-and-build). ## What ships `packages/` holds 33 `@clawboo/*` packages (27 at the top level, 6 under `packages/adapters/*`), and every one is `private: true`, like `@clawboo/web` and `@clawboo/docs`. The only public package is `apps/cli`, named `clawboo` (no scope), with `files: ["dist"]`. So `pnpm changeset publish` skips everything else and publishes one tarball. The libraries still ship, inside that tarball. `tsup.server.config.ts` inlines the whole `@clawboo/*` scope into `dist/server.js`, and `scripts/assemble-cli.sh` copies the server bundle, the SPA and the MCP bins into `apps/cli/dist/`. [What publishes](/internals/monorepo-and-build#what-publishes) lists the tarball's contents and its few external modules. ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} graph TD libs["33 private @clawboo/* packages"] -- "tsup inlines /^@clawboo/" --> bundle["apps/web dist/server.js"] bundle -- "assemble-cli.sh copies" --> clidist["apps/cli/dist/: server.js, ui/, bin/"] clidist -- "files: dist" --> npm["npm: clawboo"] ``` `apps/web/server/lib/__tests__/packagePosture.test.ts` enforces this. It asserts that the set of public packages is exactly `clawboo (apps/cli)`, and that no public package has a runtime dependency on a private one, which would publish a manifest pointing at packages npm does not have. ## Versions `apps/cli/package.json` holds the version of the last release. The `.changeset/*.md` files on `main` describe the next one, so `ls .changeset` tells you what the next `publish.yml` run does: any changeset file means it opens or updates a Version PR, none means it runs `changeset publish`. That publishes only when the version in `apps/cli/package.json` is not on npm yet; otherwise the run succeeds and publishes nothing. The CLI's version string comes from the build. `apps/cli/tsup.config.ts` sets `define: { __CLI_VERSION__: JSON.stringify(pkg.version) }`, and `apps/cli/src/version.ts` exports it as `VERSION`, falling back to `0.0.0-dev` when the code runs unbundled under tsx or Vitest. `clawboo --version` prints it, and the CLI passes it to every server it starts as `CLAWBOO_VERSION`. ## Changesets The whole `.changeset/config.json`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "$schema": "https://unpkg.com/@changesets/config@4.0.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": ["@clawboo/docs", "@clawboo/web"], "privatePackages": { "version": false, "tag": false } } ``` The root `package.json` pins `@changesets/cli` at `^3.0.2`. The fields that matter: * **`privatePackages: { version: false, tag: false }`** is what keeps every private package out of versioning and tagging, so only `clawboo` gets a version bump and a git tag. * **`ignore`** keeps `@clawboo/web` and `@clawboo/docs` out of the flow as well. They never publish on their own, so a changeset never names them. * **`commit: false`** means `pnpm changeset` writes files and commits nothing. The Changesets action makes the release commit, titled `chore: version packages`. * **`access: "public"`** applies to the one package that publishes. * **`updateInternalDependencies: "patch"`** would re-version internal dependency edges, but no other package publishes, so it changes nothing today. ### Write a changeset ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} pnpm changeset ``` Pick `clawboo` and a bump level (`patch`, `minor` or `major`), then write the summary. The command creates a file under `.changeset/`; commit it with your change. The file looks like this: ```md theme={"theme":{"light":"github-dark","dark":"github-dark"}} --- 'clawboo': patch --- One or two sentences a user of the npm package can read. ``` Add one for any change a user of the npm package would notice, including dashboard changes in `apps/web`: the server and the SPA ship inside `clawboo`. A code change deep in a library such as `@clawboo/db` is also released as a `clawboo` bump. Docs-only, CI-only and test-only changes need no changeset. ## The publish workflow `publish.yml` has one job, `publish`, and it runs on every push to `main` with no path filter and no condition. Its steps, in order: 1. Check out with `fetch-depth: 0` (Changesets needs the full history), set up pnpm and Node 22 with the npm registry. 2. `pnpm install --frozen-lockfile`. 3. `pnpm exec playwright install --with-deps chromium`, for the clean-install gate's browser step. 4. `pnpm build`, `pnpm lint`, `pnpm typecheck`, `pnpm test`. 5. `pnpm catalog:verify`. A content-only pull request skips the test matrix by design, so this is what keeps a bad marketplace pack off npm. 6. `bash scripts/assemble-cli.sh`, then `pnpm test:clean-install`: the tarball about to be published is installed and booted first. 7. `changesets/action`, pinned to the v2.1.2 commit, with `publish-script: pnpm changeset publish`, `commit-message` and `pr-title` both `chore: version packages`, and `push-with-git-cli: true`. `lint`, `typecheck` and `test` run before assembly, so a failure costs no bundle work and no Turbo task can restore a stale `apps/cli/dist` over the fresh one. The build does not type-check the web app or the CLI, so `typecheck` is the step that stops a type error there from reaching npm. The rerun is partial. It does not repeat `pnpm format:check`, `pnpm e2e`, `pnpm verify:connectors`, the cross-platform tests, the website build, the real-concurrency suite, the entry-chunk and catalog size checks, or CodeQL; those ran on the pull request. It also needs the network: the install, the Chromium download, and the real `npm install` inside the clean-install gate. The catalog gate is offline by design: it reads what is committed and never fetches the upstream sources the content came from. The last step gives the flow its two phases: * **Changesets present** (a feature PR with a `.changeset/*.md` just merged): the action opens or updates the Version PR, titled `chore: version packages`. Its commit consumes the changeset files, bumps `clawboo` in `apps/cli/package.json` and adds the entry to `apps/cli/CHANGELOG.md`; merging it lands those on `main`. * **No changesets present** (the Version PR just merged): the action runs `pnpm changeset publish`, which publishes `clawboo` to npm with `NODE_AUTH_TOKEN` from the `NPM_TOKEN` secret, then pushes the `clawboo@<version>` git tag and creates the GitHub release. ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} sequenceDiagram participant PR as Feature PR with a changeset participant Main as main participant Pub as publish.yml participant VPR as Version PR participant npm PR->>Main: merge Main->>Pub: push runs the workflow Pub->>Pub: build, lint, typecheck, test, catalog:verify, assemble, test:clean-install Pub->>VPR: open or update "chore: version packages" VPR->>Main: merge (bumps clawboo, writes the changelog) Main->>Pub: push runs the workflow again Pub->>Pub: the same checks Pub->>npm: pnpm changeset publish ``` The workflow's concurrency group never cancels a run in progress. `changeset publish` pushes to npm before it creates the tags, so a cancel between the two would leave a version on npm with no tag behind it. > [!WARNING] > > The `publish` job has no changeset gate, and must not get one. A condition such as "run only when `.changeset/*.md` files exist" is right for the run that opens the Version PR, but it skips the run that publishes: by then the Version PR has merged and Changesets has consumed the files. The action decides between the two phases itself; it needs the job to run every time. A comment in `publish.yml` says the same where someone would add the gate. ## Provenance The published tarball carries an [npm provenance](https://docs.npmjs.com/generating-provenance-statements) statement: a signed record that it was built from this repository, at a given commit, by this workflow. Three things produce it: * `id-token: write` in the `publish` job's permissions, which lets the job mint the short-lived OIDC token npm checks. It is why that job's permissions are wider than the workflow's `contents: read` default. * `NPM_CONFIG_PROVENANCE: true` in the Changesets step's environment, which turns provenance on for the publish. * A `repository` field in `apps/cli/package.json` (this repo, `directory: apps/cli`). npm refuses provenance for a package that does not say where it came from. Anyone can check an install with `npm audit signatures`. That matters for a tool that is installed with `npx` and then starts coding agents on the user's machine. ## Cut a release **1. Add a changeset** Run `pnpm changeset` on your feature branch, pick `clawboo`, and commit the new `.changeset/*.md` file with the change. **2. Pass CI** Every job in `ci.yml` must be green, and CodeQL too. [Testing](/internals/testing) lists the jobs. **3. Merge the feature pull request** `publish.yml` runs its checks, then opens or updates the `chore: version packages` pull request. Check that its version bump and changelog entry are what you expect. **4. Merge the Version PR** `publish.yml` runs its checks again and then `pnpm changeset publish`. When the run is green, the new version is on npm with its git tag and GitHub release. **5. Check npm** Run `npm view clawboo@<version> version`. Asking for the exact version avoids a cached answer; the registry can take a few minutes to show a new release. There is no manual `npm publish` step. Releases go through this flow only. ## Design notes **One published artifact.** Clawboo is used as a product, not as a set of libraries. Keeping every `@clawboo/*` package private means no semver contract for 33 internal packages, no publish-then-install loop, and no partial release where the CLI ships against a library that never reached npm. `packagePosture.test.ts` turns that posture into a test. **Inlined libraries.** A clean `npx clawboo` install should need nothing beyond the CLI's short `dependencies` list, so tsup inlines everything else, the provider SDKs and `croner` included. The clean-install gate proves it at runtime, and `pnpm test:bundle-externals` proves it statically. **Checks inside `publish.yml`.** The pull request already ran CI, but a merge race or a lockfile change can make `main` differ from what the pull request tested. Rerunning the key checks right before `changeset publish` stops that commit from reaching npm. Running them as steps in the same job, rather than waiting on another workflow's result, keeps the release self-contained in one file. ## Limits * **One package.** No `@clawboo/*` library publishes on its own. Making one public is a posture change, and `packagePosture.test.ts` fails until the test is changed with it. * **An npm package, not a deployment.** `publish.yml` publishes to npm; running a Clawboo instance is covered under [Deployment](/operating/deployment). * **No migration step.** A new version brings a user's existing database up to date the first time it opens it, by adding missing columns. See [Schema changes](/internals/monorepo-and-build#schema-changes). ## Related * [Monorepo and build](/internals/monorepo-and-build): the bundle, `assemble-cli.sh` and the root commands. * [Testing](/internals/testing): the CI jobs and the clean-install gate. * [Contributing](/appendices/contributing): where changesets fit in a pull request. * [Changelog](/appendices/changelog): the release history. # The RuntimeAdapter trait Source: https://docs.claw.boo/internals/runtime-adapter The interface every runtime implements: RuntimeAdapter, the RuntimeEvent union, the contract suite, prompt tiers, and a checklist for adding a runtime. `@clawboo/executor` defines the one interface Clawboo uses to drive every runtime: the `RuntimeAdapter` trait. An adapter starts a run on its runtime and translates the runtime's own frames into a shared `RuntimeEvent` stream. Every server path that drives a run (the executor runner, the team engine, 1:1 agent chat, the verification critic) reads that stream and never a runtime's native format. Use this page when you change an adapter, consume the event stream, or [add a runtime](#add-a-runtime). ## What the package holds The package is pure: no workspace dependencies, no runtime dependencies and no `node:*` imports, so it builds browser-safe and is inlined into the CLI bundle. Its main entry (`packages/executor/src/index.ts`) exports: * the trait and its types (`RuntimeAdapter`, `Capabilities`, `RunHandle`, `StartOpts`, `SteerResult`, `SteerOptions`, `SessionCodec`); * the `RuntimeEvent` union and the `assertExhaustive` guard; * `resolveRuntimeIntegration`, which turns declared capabilities into an integration plan; * `RuntimeRegistry`, a `Map` wrapper the server does not use (see [How the server builds adapters](#how-the-server-builds-adapters)); * `createAsyncQueue`, the callback-to-iterator bridge; * the idle guard `withIdleTimeout` with `DEFAULT_RUN_SILENT_TIMEOUT_MS` and `IDLE_ABORT_GRACE_MS`; * the steering helpers `steerThroughDriver` and `steeringOf`; * the session-rotation helpers (`shouldRotate`, `buildRotationHandoffNote`, `rotateSession`, `DEFAULT_ROTATION`). Two subpaths sit beside it. `@clawboo/executor/contract` holds the shared adapter test suite, which imports vitest, so it never rides the main entry. `@clawboo/executor/tiers` holds the prompt-tier helpers. The trait is deliberately narrow. Claiming a task, provisioning a worktree, running verification and writing the handoff belong to the [executor runner](/internals/executor-runner). Which agents exist belongs to [AgentSource](/internals/agent-source). An adapter knows neither. ## The six adapters | Package | Runtime id | Notes | | - | - | - | | `@clawboo/adapter-native` | `clawboo-native` | Clawboo Native, the in-process harness. | | `@clawboo/adapter-claude-code` | `claude-code` | Drives the Claude Agent SDK. | | `@clawboo/adapter-codex` | `codex` | Drives `codex app-server` (falls back to `codex exec`). | | `@clawboo/adapter-hermes` | `hermes` | Runs `hermes chat -q`, one process per run. | | `@clawboo/adapter-openclaw` | `openclaw` | Drives an agent over a live OpenClaw Gateway connection. | | `@clawboo/adapter-mock` | `clawboo-mock` | Fault-injection runtime for contributors. Executes nothing. | > [!NOTE] > > `clawboo-mock` synthesizes its whole event stream from directives in the message, so silence, a crash, an unresolved tool call or a slow start can be reproduced through the real server paths. It is listed only when `CLAWBOO_ENABLE_MOCK_RUNTIME=1`, never appears in a normal install, declares no steering and no worktrees, and does not run the contract suite. ## The trait ```ts theme={"theme":"github-dark"} interface RuntimeAdapter { readonly id: RuntimeId readonly participantKind: ParticipantKind capabilities(): Capabilities health(): Promise<HealthResult> start(task: TaskHandle, opts: StartOpts): Promise<RunHandle> events(run: RunHandle): AsyncIterable<RuntimeEvent> abort(run: RunHandle): Promise<void> setModel(run: RunHandle, model: string): Promise<void> writeContext(run: RunHandle, key: string, value: string): Promise<void> steer?(run: RunHandle, text: string, opts?: SteerOptions): Promise<SteerResult> readonly sessionCodec?: SessionCodec dispose?(): Promise<void> } ``` ### Identity: `id` and `participantKind` `RuntimeId` is an open set: `'openclaw' | 'claude-code' | 'codex' | 'hermes' | (string & {})`. The `(string & {})` member turns the named ids into autocomplete hints, so a new runtime id is a value, not a type change. `participantKind` is `'agent' | 'human'`. Every adapter sets `'agent'` and nothing in the code branches on it. `'human'` is reserved so a person could later sit behind the same interface; there is no human-participant adapter. ### `capabilities()` `capabilities()` returns a `Capabilities` block. The base fields describe the runtime: | Field | Type | Meaning | | - | - | - | | `streaming` | `boolean` | Emits incremental text deltas rather than one message at the end of a turn. | | `mcp` | `boolean` | Can discover and call MCP tool servers. | | `worktrees` | `boolean` | Can run work in a per-task git worktree. | | `resume` | `boolean` | Can resume a prior session from a serialized handle. | | `toolApproval` | `boolean` | The runtime can raise tool-approval prompts for a host to answer. | | `models` | `string[]` | Model ids the runtime can use (empty when unknown). | | `contextWindowTokens?` | `number` | The context window, when known. Drives the rotation watermark; omitted or 0 disables it. | | `steering?` | `'live' \| 'interrupt-resume' \| 'none'` | How `steer()` reaches a running turn. Omitted means `'none'`. | > [!WARNING] > > `toolApproval: true` describes the runtime, not Clawboo's use of it. Clawboo drives the coding runtimes headless with their own approval prompts and sandboxes turned off: Claude Code with `permissionMode: 'bypassPermissions'` (plus `allowDangerouslySkipPermissions: true`), Codex with `--dangerously-bypass-approvals-and-sandbox` (or, through `codex app-server`, approval policy `never` and sandbox `danger-full-access`), and Hermes with `--yolo` and `--accept-hooks`. A per-task git worktree keeps file changes apart when the executor runner provisions one; it is not a sandbox. See [What agents can do](/concepts/permissions). ### Integration fields and `resolveRuntimeIntegration` A second group of optional fields tells the host how deeply to integrate the runtime, so the host routes by declared data and never by a runtime-id switch: * `runtimeClass?: 'wrapped-oneshot' | 'connected-substrate' | 'native'`. A wrapped one-shot is a process spawned per run; a connected substrate is a long-lived service the host drives over its live connection (OpenClaw); native is the in-process harness. Omitted means `'wrapped-oneshot'`. * `nativeHome?: { scope: 'per-identity' | 'per-run', persist: boolean }`: a claim about the home directory the runtime accrues state in. The adapter never computes a path; the host materializes it. * `nativeSkills?` and `nativeMemory?`: `'preserve' | 'none'`, whether the runtime's own on-disk skills or memory must survive across runs. * `nativeChannels?: 'gateway' | 'none'`: whether the runtime owns its own delivery channels. * `nativeScheduler?: boolean`: informational only. The host's scheduler always decides when work runs. `resolveRuntimeIntegration(caps)` (`packages/executor/src/integration.ts`) turns those fields into the plan the host executes: | Declared | `home.kind` | Preserve flags | | - | - | - | | `runtimeClass: 'connected-substrate'` | `connected` | Both false; `useGatewayChannels` follows `nativeChannels` | | Any other class with `nativeHome` `per-identity` and `persist: true` | `persistent` | `preserveSkills` / `preserveMemory` follow the declaration | | Anything else (including an omitted block) | `ephemeral` | Both false | `coRunScheduler` is always `false`. Preserve flags are clamped to a persistent home, so a misdeclared adapter degrades to the plain one-shot path instead of a contradictory plan. The executor runner refuses a `connected` home before it claims anything, and serializes runs of a `persistent` home per agent. What each adapter declares (`packages/adapters/*/src/adapter.ts`): | Runtime | Class | `nativeHome` | Streaming | MCP | Worktrees | Steering | Preserves | | - | - | - | - | - | - | - | - | | `clawboo-native` | `native` | per-identity, persist | yes | yes | yes | `live` | memory | | `claude-code` | `wrapped-oneshot` | none (sessions in the user's `~/.claude`) | yes | yes | yes | `live` | nothing | | `codex` | `wrapped-oneshot` | per-identity, persist (`CODEX_HOME`) | yes | yes | yes | `live` | nothing | | `hermes` | `wrapped-oneshot` | per-identity, persist | no | yes | yes | `interrupt-resume` | skills, memory | | `openclaw` | `connected-substrate` | none (the Gateway owns it) | yes | no | no | `live` | skills, memory | | `clawboo-mock` | omitted | none | yes | no | no | `none` | nothing | Every real adapter declares `resume: true` and `toolApproval: true`; the mock declares both false. Native and Claude Code declare a 200,000-token context window; the others declare none, so the rotation watermark stays off for them. The runtime descriptor (`RUNTIME_DESCRIPTORS` in `apps/web/server/lib/runtimes/descriptor.ts`) answers a different question: how to install and authenticate a runtime (package, health binary, auth kind, vault env var). The class lives on the adapter because it describes how the host drives the runtime once it is online. ### `start()` and the late-bound `runId` `start(task, opts)` delivers the run's opening message and returns a `RunHandle`: ```ts theme={"theme":"github-dark"} interface RunHandle { readonly adapterId: string readonly sessionKey: string runId: string | null } ``` `runId` is mutable and late-bound. A runtime usually reports its run id on the first lifecycle frame rather than from `start()`, so callers begin with `runId: null`, and `events()` re-binds `run.runId` from the first frame of each run. `TaskHandle` (`{ taskId?, teamId? }`) carries optional board references, so an adapter can run board-backed or ad hoc work. `StartOpts` carries the runtime-side `agentId`, the `sessionKey`, the `message`, an optional `model` and `context` block, and a `childToolBlocklist` of tools the run must not use (the runner passes `['sessions_send']`). The blocklist is advisory for runtimes that cannot restrict tools per run, so the host also enforces the real limit elsewhere: the board's depth cap, and for OpenClaw the sub-agent tools in the Gateway's `tools.deny`. ### `events()`: a continuous observer `events(run)` returns an `AsyncIterable<RuntimeEvent>` for `run.sessionKey`. > [!NOTE] > > `events()` is a continuous observer. A Gateway session hosts many successive runs on one `sessionKey`, so `done` and `error` are yielded as events but do not necessarily end the stream. The consumer ends observation itself (`break` or `iterator.return()`), which releases the subscription. For a one-shot CLI process the stream ends when the process exits. Either way, the consumer drives termination. Adapters subscribe when `events()` is called, not on the first `next()`, so frames that arrive between subscription and the first pull are buffered rather than dropped. The OpenClaw adapter shows the shape: it subscribes through `client.onEvent`, filters frames by `sessionKey`, re-binds `run.runId` from each frame, maps frames to `RuntimeEvent`s with `mapFrameToRuntimeEvents`, and unsubscribes only when the iterator's `return()` runs. ### `abort()`, `setModel()`, `writeContext()` * `abort(run)` cancels the live run. The OpenClaw adapter sends `chat.abort` when a `runId` is bound and always sends `sessions.abort` too, which covers the window before the run id binds and any queued work. * `setModel(run, model)` switches the model mid-session (OpenClaw: `sessions.patch`). * `writeContext(run, key, value)` writes a context file (OpenClaw: `agents.files.set`). It is not a mid-run channel: a spawned CLI never re-reads the file during a run, and a running OpenClaw session does not re-read an agent file. Use `steer()` for that. ### `steer()`: a message into a running run `steer(run, text, opts?)` puts a message in front of the model during the run it is aimed at. It is how a person's comment on a task reaches the agent working on it. The adapter declares how in `capabilities().steering`: * `'live'`: the runtime takes the message into the turn in progress at its next boundary without stopping anything (Clawboo Native, Claude Code, Codex, OpenClaw). * `'interrupt-resume'`: the runtime has no way in mid-run, so the driver stops the process and resumes the same session with the message as the next input (Hermes). The stop is absorbed inside the driver and never surfaces as a terminal event, so the host sees one run that carries on. An aborted terminal would otherwise reach the team engine's failure path. * `'none'` or omitted: no mid-run channel. A host must not call `steer()` on such a run. `steer()` is optional and never throws. It resolves `{ delivered: true, mode }` when the runtime accepted the message for this run, or `{ delivered: false, reason }` with `reason` one of `no-run`, `run-ended`, `unsupported` or `rejected` (plus an optional `detail`). The server's comment path writes the agent's mailbox first, steers second, and marks the mailbox row delivered only when the steer lands, so a refusal falls back to the next run reading the mailbox. `SteerOptions.passive` marks an ambient note rather than a person's instruction: it is delivered if the run reads input again, but never keeps a finishing run going and never stops a run (an `'interrupt-resume'` adapter refuses it). Adapters with a per-run driver share their `steer()` body through `steerThroughDriver(driver, text, mode, opts)`; `steeringOf(caps)` reads an omitted claim as `'none'`. ### `sessionCodec` and `dispose` `sessionCodec` (optional) serializes a run's session to a string and restores it; the runner uses it to persist a native session id for a same-runtime resume. `dispose` (optional) releases adapter-level resources. ## The RuntimeEvent union Every adapter normalizes its runtime's signals into one union with seven kinds: ```ts theme={"theme":"github-dark"} type RuntimeEventKind = 'text-delta' | 'tool-call' | 'tool-result' | 'status' | 'cost' | 'done' | 'error' ``` Each event carries a common envelope: ```ts theme={"theme":"github-dark"} interface RuntimeEventBase { runId: string sessionId: string | null // the runtime's session handle ts: number seq: number // strictly increasing per stream } ``` `seq` orders events that share a millisecond `ts`, so a consumer can always reconstruct causal order. | Kind | Payload (beyond the envelope) | Notes | | - | - | - | | `text-delta` | `text`, `channel?: 'assistant' \| 'reasoning'` | `channel` separates visible output from reasoning traces. | | `tool-call` | `toolCallId`, `name`, `input`, `partial` | `partial: true` while the input JSON is still streaming. | | `tool-result` | `toolCallId`, `name`, `output`, `isError` | The outcome of a tool call. | | `status` | `phase: 'init' \| 'thinking' \| 'running' \| 'turn-complete'`, `model?`, `detail?` | A lifecycle signal with no text. | | `cost` | `costUsd: number \| null`, `usage`, `model`, `estimated?`, `local?` | Token usage, plus USD when the runtime reports it. | | `done` | `reason: 'success' \| 'max_turns' \| 'aborted' \| 'error'`, `summary`, `usage?`, `costUsd?` | Terminal summary for one run or turn. | | `error` | `code: string \| null`, `message`, `fatal` | A recoverable or fatal failure. | The union encodes two real differences between runtimes in its types: 1. **Not every runtime reports USD.** `cost.costUsd` is `number | null`, and `estimated?` marks a derived value. A runtime that cannot supply USD emits `null` rather than an invented number; the runner then estimates spend from the token usage (see [the executor runner](/internals/executor-runner)). `local?` marks a model the user's own Ollama served, whose `costUsd` of 0 is exact rather than unknown. 2. **Not every runtime streams text.** A runtime without deltas emits one `text-delta` carrying the whole message. `assertExhaustive(x: never): never` guards `switch` statements over the union, so a new variant is a compile error at every switch that forgets it. ## How the server builds adapters The server does not keep a registry of adapter instances. `apps/web/server/lib/runtimes/index.ts` builds a fresh adapter for each run: * `enabledRuntimeIds()` lists the non-OpenClaw runtimes: the four real ones, plus `clawboo-mock` only when `CLAWBOO_ENABLE_MOCK_RUNTIME=1`. * `adapterFactoryFor(id)` returns a factory that takes a `RuntimeRunContext` (working directory, model, resume id, MCP base URL, memory scope, home directory, provider keys) and constructs the adapter wired to its real driver and health check. `buildRuntimeAdapterFactories()` maps every enabled id to its factory. * OpenClaw adapters are built per delivery over the server's shared Gateway connection: `buildOpenClawServerAdapter()` (`runtimes/serverAdapter.ts`) for team chat, and the Routines wake bridge for OpenClaw routine fires. `RuntimeRegistry` (register, unregister, get, has, ids, list) is exported from the package and unit-tested, but nothing in the server or the dashboard uses it. ## The async queue Adapters bridge a callback source (a WebSocket `onEvent`, a subprocess stdout reader) into an `AsyncIterable` by pushing into `createAsyncQueue<RuntimeEvent>()` and letting the consumer pull with `for await`. The queue has one consumer. Backpressure is drop-oldest at `max` (default 1000), because orchestration cares about tool calls and `done` more than every text delta. `close()` ends the stream, and the iterator's `return()` closes the queue, which is how `events()` releases its subscription. ## The idle guard `withIdleTimeout(source, { idleMs, onIdle, graceMs? })` re-yields an event stream and bounds the silence between events. The timer resets on every event, so a long run that keeps streaming never trips it. When silence passes `idleMs`, `onIdle()` fires (the caller aborts the run there), then a grace window (`IDLE_ABORT_GRACE_MS`, 5 seconds) lets the adapter surface its own terminal before the stream ends. The executor runner, the team engine's `serverDeliver` and the 1:1 chat's `driveAgentChat` drain their runs through it; the other `events()` loops do not. Their ceiling is `DEFAULT_RUN_SILENT_TIMEOUT_MS` (30 minutes), overridable with `CLAWBOO_RUN_SILENT_TIMEOUT_MS`. The runner reports a silent timeout as a failed run with the cause stated, and a 1:1 chat that has not replied yet gets a notice that the run went quiet for too long. ## The contract suite Every real adapter passes one shared suite, `runAdapterContract(harness)` from `@clawboo/executor/contract`. The adapter supplies an `AdapterTestHarness`: how to make the adapter on a fake transport, start a run, emit a native frame, and read the side effects it issued. The harness translates abstract `ContractFrames` (`delta`, `toolCall`, `final`, `aborted`, `error`) into the runtime's own frames, and the suite asserts on the normalized output: * `id` is a non-empty string and `participantKind` is `'agent'` or `'human'`; * `capabilities()` returns a well-formed shape; * `health()` resolves within two seconds; * `start()` returns a handle with the adapter's id and an unbound `runId` (`null`); * `events()` round-trips a stream that ends in `done: success` with strictly increasing `seq`; * `runId` binds late, from the first lifecycle frame; * `abort()` issues a cancel side effect and the stream surfaces `done: aborted`; * `setModel()` and `writeContext()` each issue their side effect. > [!NOTE] > > `vitest` stays external in the package's tsup config. A bundled copy would register `describe` and `it` on a detached collector, and an adapter's contract tests would silently never run. Keeping it external binds the suite to the consumer's vitest at runtime. The package also tests the suite itself against a minimal `InMemoryAdapter` (`packages/executor/src/__tests__/contract-selftest.test.ts`), which doubles as the smallest reference implementation of the trait. ## Prompt tiers: `@clawboo/executor/tiers` The `./tiers` subpath keeps a provider's prompt cache warm. A prompt has three tiers, ordered by how often they change: | Tier | Changes | Typical content | | - | - | - | | `stable` | Frozen for the session | Identity, tool guidance, skills index. | | `context` | Infrequently | The caller's system message, team manifest, project files. | | `volatile` | Every turn | Memory snapshot, last handoff summary, a date-only timestamp. | `assembleTiers(tiers)` joins them in that order and returns the `prompt`, the `stablePrefix` (stable plus context, identical turn over turn), its UTF-8 byte length, and suggested `cacheBreakpoints` (byte offsets an Anthropic-style consumer can map to `cache_control` markers; OpenAI caches prefixes on its own and the OpenClaw Gateway owns its caching, so both ignore them). Two helpers keep the prefix stable: * `sortToolDefs(defs)` sorts tool definitions by name without mutating the input, because tool order is part of the cache key. * `dateStamp(d)` returns a `YYYY-MM-DD` (UTC) stamp. A minute or second timestamp in the prompt would bust the cache on every rebuild; a runtime that needs wall-clock time should get it from a tool. The executor runner assembles every run's prompt through `assembleTiers` (see [Prompt assembly](/internals/executor-runner#prompt-assembly)), and Clawboo Native's conversation loop uses `dateStamp` for its date header. ## Session rotation The rotation helpers answer "the session ran out of room before the task finished". Rotation happens between runs, never mid-generation, because the runtime owns its inner loop. `shouldRotate` fires when `tokensUsed / contextWindow` reaches `thresholdPct` (`DEFAULT_ROTATION.thresholdPct`, 0.85) and stays inert when the runtime declares no window. `rotateSession` takes the adapter, a `restart` closure (the host re-assembles the prompt with a short handoff note from `buildRotationHandoffNote` and calls `adapter.start`), and an optional `recordRotation` callback for lineage. `DEFAULT_ROTATION.maxRotations` (3) bounds the chain of successor sessions per task. The runner's use of it is described in [Session rotation](/internals/executor-runner#session-rotation). ## Add a runtime A new runtime touches more than the trait. Wire it in this order, and grep for an existing id such as `'hermes'` to catch anything this list misses. **1. Write the adapter package** Create `packages/adapters/<id>` exporting a class that implements `RuntimeAdapter`. Declare only capabilities that are true; omit the integration fields you cannot back. Add a test that calls `runAdapterContract` from `@clawboo/executor/contract` against a fake driver, like the existing `packages/adapters/*/src/__tests__/adapter.test.ts`. **2. Write the server driver** Add a driver under `apps/web/server/lib/runtimes/` that spawns or connects to the runtime and consumes a `RuntimeRunContext` (`runtimes/types.ts`). Honour `ctx.cwd`, `ctx.homeDir`, `ctx.mcpBaseUrl` and `ctx.apiKeyEnv` the way the existing drivers do. **3. Register the id and descriptor** Add the id to `NonOpenClawRuntimeId` and `NON_OPENCLAW_RUNTIME_IDS`, and a `RUNTIME_DESCRIPTORS` entry (install command, health binary, auth kind, vault env var), in `apps/web/server/lib/runtimes/descriptor.ts`. **4. Add the factory** Add a case to `adapterFactoryFor()` in `apps/web/server/lib/runtimes/index.ts` that builds the adapter over your driver, with a health check. Add the adapter package to `apps/web/package.json`. **5. Add a capability source** Implement a `CapabilitySource` in `apps/web/server/lib/capabilitySource/`, register it in `capabilitySource/registry.ts`, and add its id to `CapabilitySourceId` and `SOURCE_IDS` in `packages/capability-registry/src/`. See [Capability and schedule sources](/internals/seams). **6. Add an agent source** For a runtime whose agents live only in SQLite, add the id to `CODING_RUNTIME_SOURCE_IDS` in `apps/web/server/lib/agentSource/registry.ts`, which registers a `RuntimeAgentSource` for it. A runtime with its own upstream needs its own `AgentSource`. See [AgentSource](/internals/agent-source). **7. Declare agent files and prompt behaviour** Add a row to `AGENT_FILE_RUNTIME_SUPPORT` in `packages/protocol/src/index.ts` listing only the files the runtime really consumes. If it reads no persona file of its own, add it to `RUNTIMES_WITHOUT_NATIVE_PERSONA` in `runtimes/personaBlock.ts`, and check `memoryGuidance.ts` and `teamChat/contextPreamble.ts`. **8. Surface it in the dashboard** Add the id to the client `RuntimeId` union in `packages/control-client/src/runtimes.ts` and an entry to `apps/web/src/features/runtimes/runtimeCatalog.ts`, then follow the remaining UI references (brand mark, Boo variant, runtime pickers). You should then see the runtime on the Runtimes settings panel, be able to create an agent on it, and run a board task on it through `POST /api/runtimes/<id>/run`. ## Related * [The executor runner](/internals/executor-runner): claim, worktree, run, verify and hand off one task through an adapter * [Capability and schedule sources](/internals/seams): the capability and schedule multiplexers a new runtime plugs into * [AgentSource](/internals/agent-source): where a runtime's agents are recorded * [What agents can do](/concepts/permissions): the per-runtime safety posture * [Choose a runtime](/runtimes/index): the runtimes as users see them * [Write a runtime adapter](/reference/packages/executor): the package-level guide to building on `@clawboo/executor` * [Runtime adapters](/reference/packages/adapters): the six adapter packages compared # Capability and schedule sources Source: https://docs.claw.boo/internals/seams How CapabilitySource and ScheduleSource merge per-runtime adapters into one record stream, gate writes by manageability, and route by id prefix. Two server-side layers follow the same pattern as [AgentSource](/internals/agent-source): a neutral record type, a per-source adapter trait, a multiplexer that merges every adapter's records into one stream, and a process-wide singleton that registers the adapters. * **`CapabilitySource`** projects what each runtime can do (skills, tools, connectors) as `CapabilityRecord`s. Six sources are registered: one for each of the five real runtimes (the test-only `clawboo-mock` has none) plus one for Clawboo's own outbound connectors. The [Org chart](/using/org-chart) and the [Capabilities panel](/using/capabilities) both read this stream. * **`ScheduleSource`** projects when work fires as `ScheduleRecord`s. Two sources are registered: Clawboo Routines and the OpenClaw Gateway's own cron. Use this page when you add a source, change how a capability or schedule is written, or debug a degraded source. ## The shared shape | Part | `AgentSource` | `CapabilitySource` | `ScheduleSource` | | - | - | - | - | | Record type | `AgentRecord` | `CapabilityRecord` | `ScheduleRecord` | | Registry | `AgentRegistry` (a catalog) | `CapabilityMultiplexer` (merges `read()`) | `ScheduleMultiplexer` (merges `read()`) | | Id composition | the row's `sourceId` | `${sourceId}:${rawKey}` | `${source}:${sourceScheduleId}` | | Singleton | `getRegistry()` | `getCapabilityMultiplexer()` | `getScheduleMultiplexer()` | | Registered sources | 5: OpenClaw, native, and one SQLite-backed source each for claude-code, codex, hermes | 6: native, hermes, claude-code, codex, openclaw, connector | 2: `clawboo-routine`, `openclaw-gateway-cron` | The record and trait packages, `@clawboo/capability-registry` and `@clawboo/scheduler`, are dependency-light and browser-safe, because the dashboard imports the record types to type the REST responses. `@clawboo/capability-registry` has no runtime dependencies and re-declares the small foreign types it needs (`CapabilityAvailability` mirrors `@clawboo/db`'s `AvailabilityRequirement`). `@clawboo/scheduler` is the only package that imports `croner`, and only in `occurrence.ts`; everything downstream deals in precomputed epoch-millisecond timestamps. The concrete sources live in `apps/web/server/lib/capabilitySource/` and `apps/web/server/lib/scheduleSource/`. These layers merge and route; they own no canonical rows. The capability projection is persisted to a `capabilities` table, but only as a cache for when a source is offline; each source's backing store (the tool broker's `tool_registry`, a Hermes home's `SKILL.md` files, the Gateway config) stays canonical. The Routines source reads the `scheduled_runs` ledger and the Gateway-cron source reads the live Gateway. ### Routing by id prefix The composite id is the routing key. `parseCapabilityId` and `parseScheduleId` find the owning source by matching the id against a fixed list of known source ids as a prefix. They do not split on the first colon: a raw key may itself contain colons (brokered ids look like `native:tool_registry:echo`), and an id whose prefix is not a registered source parses to `null`, so it routes nowhere instead of to a plausible wrong source. ## `read()` never rejects A source's `read()` returns degradation as a value. Each returns `{ records, status }`, where `status` is `{ sourceId, ok, degraded, reason?, at }`; a disconnected Gateway returns no records and `{ ok: false, degraded: true, reason: 'gateway_disconnected' }` (the Gateway-cron source serves its last list instead, with reason `stale_cache`, when it has one). The multiplexer also wraps every `read()` in its own `try/catch`, so even a source that breaks the contract becomes a degraded status entry rather than failing the merge: ```ts theme={"theme":"github-dark"} // CapabilityMultiplexer.read(): defensive on both ends for (const source of this.sources.values()) { try { const result = await source.read() records.push(...result.records) sources.push(result.status) } catch (err) { sources.push({ sourceId: source.id, ok: false, degraded: true, reason: err instanceof Error ? err.message : String(err), at: Date.now(), }) } } return { records, sources } ``` A degraded source never fails the request: it rides in the `sources` array and `GET /api/capabilities` or `GET /api/schedules` answers `200`. (`GET /api/capabilities` answers `500` only when the server-side merge itself throws, for example on a database error.) For capabilities, `loadCapabilities` (`capabilitySource/service.ts`) goes one step further. It persists each healthy source's records into the `capabilities` table (a reconcile scoped to that source) and, for a degraded source, serves that source's last-good rows. A Gateway that drops for one read still shows its tools, stale but present. ## Manageability: the write gate Clawboo observes every runtime's capabilities and schedules but manages only what the owning runtime allows. The line is a manageability tier, and the UI and the write path are both a pure function of it: neither may offer an action the owner forbids. **`ScheduleSource` declares manageability per source.** Each source is uniformly `managed`, `external-write` or `observe-only` through a `readonly manageability` field, so the multiplexer can refuse a write before touching the source. | Tier | Meaning | Schedule source | | - | - | - | | `managed` | Clawboo owns the rows | `clawboo-routine` (the `scheduled_runs` ledger) | | `external-write` | The runtime owns the store; Clawboo writes through its API | `openclaw-gateway-cron` (operator `cron.*` calls) | | `observe-only` | Clawboo can read but never write | none today | **`CapabilitySource` declares manageability per record**, because one source can emit records of mixed tiers (OpenClaw surfaces `runtime-of-record` `tools.*` records, `observe-only` `clawboo-*` servers and a built-ins record from the same `read()`). There is no `manageability` field on the capability trait. The REST layer resolves the target record and rejects `observe-only` or non-`writable` records, and each `source.write()` refuses again. | Tier | Meaning | Capability example | | - | - | - | | `managed` | Clawboo owns the durable row | brokered MCP tools and curated skills (native source) | | `external-write` | The runtime owns the store; Clawboo writes through it | Codex's attached Clawboo MCP servers (refused today) | | `runtime-of-record` | The runtime owns it; Clawboo changes it through the runtime's API | OpenClaw `tools.allow` and `tools.deny` via `config.patch` | | `observe-only` | Clawboo can read but never write | runtime built-ins, Hermes `SKILL.md`, connector records | A record also carries `writable` (default `true`). A source sets `writable: false` on a record it cannot write yet, such as an OpenClaw MCP server or plugin entry, so the panel renders no dead Enable or Disable button. The `writable` column is not persisted, so when the REST mapper serves last-good rows for an offline source it re-derives `writable` from the row's origin and kind with the same predicate that source's `write()` uses. ## The six capability sources Each source projects the same `CapabilityRecord` shape. The origin and manageability of its records encode what Clawboo may do with that runtime. They are registered in this order in `apps/web/server/lib/capabilitySource/registry.ts`: | Source | What it reads | Manageability | Can `write()`? | | - | - | - | - | | `native` | the tool broker's `tool_registry` (global tools), each native agent's MCP toggles, the per-agent `skills` table | `managed` | yes: install, enable and disable through the tool-broker pipeline | | `hermes` | each per-identity Hermes home's `SKILL.md` files and `mcp.json` connectors | `observe-only` | no: Clawboo never writes a Hermes skills directory | | `claude-code` | the Clawboo MCP servers attached per run, plus Claude Code built-ins | `observe-only` | no: Clawboo keeps Claude Code off the user's own `~/.claude` setup | | `codex` | the Clawboo MCP servers attached to Codex, plus Codex built-ins | `external-write`, status `manageable-but-pending-auth` | no: shown with the hint to run `codex login`; writes are refused | | `openclaw` | the Gateway's `tools.allow` and `tools.deny`, `mcp.servers` and `plugins.entries`, over the shared connection | `runtime-of-record` (`tools.*` writable; other MCP servers and plugins `writable: false`); `clawboo-*` servers `observe-only` | yes for `tools.*` through `config.patch`; nothing else yet | | `connector` | Clawboo's own live outbound MCP connections (from the connector supervisor, in memory) plus one record per app a connected Composio session reaches (from cache) | `observe-only`, `writable: false` | no: connect and disconnect live on the connectors REST surface | Three details carry invariants the rest of the system depends on: * **The native source's curated-skill records carry the agent's runtime.** A curated catalog skill installed on an OpenClaw agent is a Clawboo-managed annotation, so the record's `runtime` is the agent's while its manageability stays `managed`. The panel groups it under the agent's runtime and Clawboo still owns the write. * **The OpenClaw source reuses the shared operator connection.** It is constructed with `getRegistry().source` and never opens a second Gateway connection. A `config.patch` carries the `{ raw, baseHash, replacePaths }` envelope the Gateway expects, where `replacePaths` names every array the patch replaces (OpenClaw 2026.9 refuses a patch that would shorten an undeclared array). * **The connector source reads no network.** The supervisor already holds every live connection and its tool list, and the Composio app list comes from a cache the connectors shelf refreshes, so a graph rebuild never waits on a remote server. The native source's `write()` is the only durable managed write, and it reuses the existing pipeline rather than forking it: an install runs `evaluateInjection` and writes the same `skills` table the `POST /api/skills` path writes, with the same `appendAudit` trail. The payload is scanned as two surfaces. The name and skill content are scanned as `prompt` (prose bound for an agent's context); a connector's command, arguments and environment are scanned as `exec` (spawn-bound, so every rule that fires blocks). ## The two schedule domains `ScheduleRecord.domain` separates two kinds of cron, and keeping them apart is the point of this layer: * **`team-task`**: Clawboo's own scheduler for team work, the `clawboo-routine` source over the `scheduled_runs` ledger. It is the one external wake for every runtime, whether a Routine posts into a team's chat or files a task for one agent. * **`runtime-own-life`**: a runtime's own cron, the `openclaw-gateway-cron` source over the live Gateway. The Routines view is an operator surface over these jobs, not their owner. There are only two sources because Claude Code, Codex, Hermes and Clawboo Native have no live scheduler of their own (scheduling them is a Clawboo Routine), and `hermes gateway` is never launched. ```mermaid theme={"theme":"github-dark"} flowchart TD subgraph mux["ScheduleMultiplexer"] r["clawboo-routine: team-task, managed"] g["openclaw-gateway-cron: runtime-own-life, external-write"] end r --> ledger[("scheduled_runs")] g -->|"operator cron.* calls"| gw[OpenClaw Gateway] mux -->|"GET /api/schedules"| tab[Routines view] ``` The multiplexer's write gate runs in order: an unknown source raises `UnknownScheduleError`; an `observe-only` source raises `UnsupportedScheduleWriteError`; a `team-task` create aimed at a `runtime-own-life` source raises `TeamTaskDomainViolationError`. The Gateway-cron source also refuses a `team-task` create itself, so the domain rule is defended twice and Clawboo never files a team task into a runtime's own scheduler. ### Typed errors Every scheduling error carries a `readonly code` so callers branch on structure, never on message text. The REST mapper (`mapScheduleError` in `apps/web/server/api/schedules.ts`) turns them into status codes: | Error | Status | Meaning | | - | - | - | | `InvalidCronSpecError`, a zod error | `400` | A malformed spec or task template. The body's `code` is `invalid_body`. | | `BoundRecurringScheduleError` (`bound_recurring_schedule`) | `400` | A recurring routine bound to an already-claimable task. | | `InvalidRoutineTargetError` (`invalid_routine_target`) | `400` | A team routine with no team, an agent routine with no agent, an agent not on the paired team, or a target that does not exist. | | `UnknownScheduleError` (`unknown_schedule`) | `404` | An unknown composite id or source. | | `DuplicateFiringOwnerError`, `IllegalScheduleTransitionError` | `409` | The one-firing-owner refusal, or an illegal routine status change. Never retried. | | `TeamTaskDomainViolationError` (`team_task_domain_violation`) | `422` | A team-task create aimed at a runtime-own-life source. | | `UnsupportedScheduleWriteError` (`unsupported_schedule_write`) | `403` | A write against an `observe-only` source. | | `ScheduleSourceUnavailableError` (`schedule_source_unavailable`) | `503` | The Gateway connection behind the source is down; the body's `error` is `gateway_disconnected`. | | `NotImplementedError` (`not_implemented`) | none | Raised by the wake bridge at fire time for a human-participant Routine target, which is not built. | The one-firing-owner rule is the scheduler's version of the board's atomic claim. A board task carries a `scheduledBy` owner; binding a Routine to a task whose `scheduledBy` is a different owner other than `'manual'` is refused at registration with `DuplicateFiringOwnerError`. Like a lost claim, it is never retried: another engine owns that task's cadence. A runtime's own cron is a different domain and never trips the rule. The capability errors are the smaller mirror: `UnknownCapabilityError` answers `404`, `UnsupportedCapabilityWriteError` (raised by the shared `unsupported()` helper every `observe-only` source's `write()` calls) answers `422`, and a write that hits a disconnected Gateway answers `503 gateway_disconnected`. ## How a Routine fire picks its path The wake bridge (`apps/web/server/lib/routines/wakeBridge.ts`) turns a Routine fire into real work. It branches first on the Routine's target. A team Routine posts its instructions into the team's General chat through the team engine, addressed to the team's lead. An agent Routine becomes a board task for that agent, and the bridge then branches on the runtime's integration plan, `resolveRuntimeIntegration(caps).home.kind`, never on a runtime id: * **`persistent` or `ephemeral` home** (Clawboo Native, Claude Code, Codex, Hermes): `runTaskOnRuntime`, the same [executor runner](/internals/executor-runner) a `POST /api/runtimes/:id/run` uses. That path has budget checks, circuit breakers, a worktree when the task changes files and has a repository path, and the verification gate when that worktree has a diff. A template with no `repoPath` runs without a worktree (a file-changing kind becomes `research`), so it gets no verification. * **`connected` home** (OpenClaw): a thin dispatcher on the shared Gateway connection (`routines/openclawDispatch.ts`). It runs a budget preflight, claims the task, beats it, records spend, and stops the run on a 10-minute watchdog (`CLAWBOO_ROUTINE_OPENCLAW_TIMEOUT_MS`). It has no worktree, no verification gate and no circuit breakers; a finished run lands `done` with the comment "Completed without verification" and an unverified audit row. The branch reads the adapter's declared `runtimeClass`, so a new connected runtime routes here without the bridge learning its name. ## Design rationale **A multiplexer rather than a catalog.** Capabilities and schedules are heterogeneous and partial: six capability sources and two cron systems, any of which may be offline. The merged view is the product, so the registry's job is the merge: collect every `read()`, keep failures as status entries, and present one stream. That is what lets the Org chart and the Capabilities panel share one query and makes a degraded source visible but not fatal. **Per-record manageability for capabilities, per-source for schedules.** The shape follows the data. Every Routine is `managed` and every Gateway cron job is `external-write`, so a per-source gate is honest. A capability source is not uniform, and a per-source tier would either offer to write a built-in or refuse to manage a managed skill. The per-record tier costs the `writable` re-derivation in the REST mapper but keeps the gate truthful. **Browser-safe record packages.** The record types cross the wire to the dashboard, so the packages that define them cannot pull in a Gateway client, a SQLite driver or `croner`. ## Boundaries * **Not stores.** The `capabilities` table is a cache for offline sources; the schedule sources read the `scheduled_runs` ledger and the live Gateway. * **No per-runtime special cases.** The UI and the write path depend only on `manageability` and `writable` (capabilities) or `manageability` and `domain` (schedules). A new runtime declares its tiers and nothing in the panel or the multiplexer learns its name. * **Reserved fields.** `CapabilityRecord.tenantId` and the schedule `tenantId` are always `null`. The `'human'` capability runtime and the human-participant Routine are typed but not built. * **Writes not offered yet.** OpenClaw MCP server and plugin toggles are not writable, and Codex capability writes are refused; both show as non-writable or pending-auth rather than as a dead button. ## Related * [AgentSource](/internals/agent-source): the agent registry these two mirror * [The executor runner](/internals/executor-runner): where an agent Routine fire becomes a run * [Capabilities](/concepts/capabilities) and [Scheduling](/concepts/scheduling): the concepts behind these streams * [Adding a runtime](/internals/runtime-adapter#add-a-runtime): where a new capability source fits * [Schedules API](/reference/rest-api/schedules) and [Capabilities API](/reference/rest-api/capabilities): the REST surfaces # Testing strategy Source: https://docs.claw.boo/internals/testing The test layers (Vitest, sandboxed Playwright, the clean-install gate, evals), the standing guard tests, and every CI job with what it checks. Clawboo has one test layer for each way it can break: logic, the assembled app in a browser, the published package on a fresh machine, the orchestrator's own guarantees, and the repo's structural rules. This page explains each layer, what it protects, and how CI runs it. Use it when you add a test, when a CI job fails, or when you touch the end-to-end sandbox. ## The local gate Run what CI runs before you push. All of these are root scripts: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} pnpm format:check pnpm lint pnpm typecheck pnpm test pnpm e2e pnpm catalog:verify pnpm verify:connectors pnpm assemble && pnpm test:clean-install ``` `pnpm e2e` needs `pnpm build` first and a Chromium download (`pnpm exec playwright install chromium`, once per clone). `pnpm test:clean-install` needs the same browser and network access for its `npm install`. Neither will run against a dashboard you started yourself: the e2e guard refuses an unsandboxed server, and the clean-install gate refuses to start while a dashboard answers on ports 18790 to 18809. [CI jobs](#ci-jobs) maps each command to its job. ## The layers ```mermaid theme={"theme":{"light":"github-dark","dark":"github-dark"}} flowchart TD A["pnpm test (Vitest)"] --> A1["node project: logic and server integration"] A --> A2["jsdom project: React components"] B["pnpm e2e (Playwright)"] --> B1["mock Gateway, sandboxed HOME"] C["pnpm test:clean-install"] --> C1["pack, install and boot the tarball"] D["@clawboo/evals"] --> D1["smoke tasks inside pnpm test"] E["standing guards"] --> E1["structural rules, inside pnpm test"] ``` * **Vitest** checks that the logic holds, and that the server's stateful parts work against real git and real SQLite. * **Playwright** checks that the assembled app renders and round-trips in a browser, without touching your real data. * **The clean-install gate** checks that the packed npm package installs, boots and runs one agent task on a clean machine. * **The eval harness** checks the orchestrator's own guarantees by grading the board's final state, not a transcript. * **The standing guards** check the repo's structural rules: the schema source, the publish posture, layer boundaries, source hygiene. ## Vitest Every package with code, and both apps, has its own `vitest run` script, and `turbo test` runs them all. The root `vitest.config.ts` only globs `packages/*/src/**/*.test.ts`, for running package tests directly. `apps/web/vitest.config.ts` defines two projects that one `vitest run` runs together. Both set `restoreMocks`, and the run uses at most half the CPU cores (`maxWorkers: '50%'`). | Project | Files | Environment | Timeout | Setup files | | - | - | - | - | - | | `node` | `src/**/*.test.ts` and `server/**/*.test.ts` | Node | 30 s (60 s on Windows) | `server/__vitest__/setupHomedir.ts`, `setupEmbeddings.ts` | | `jsdom` | `src/**/*.test.tsx` | jsdom | 15 s | `src/__vitest__/setup.ts` | **The `node` project** holds pure logic, the Zustand stores, and the server's integration tests. Those run against a real temporary git repository and a real SQLite file, with `$HOME` pointed at a throwaway directory, so they never touch your `~/.clawboo` or `~/.openclaw`. The long timeouts give real-git tests room when the jsdom project runs at the same time. `setupHomedir.ts` makes `$HOME` decide `os.homedir()` on Windows too, so the same sandboxing works there. `setupEmbeddings.ts` sets `CLAWBOO_DISABLE_EMBEDDINGS=1` unless a suite opts back in, so no test embeds fixtures through your own Ollama or provider key. `apps/web/server/lib/__tests__/executorRunner.integration.test.ts` drives the board, a runner-provisioned worktree, verification, governance and observability together, with a fake `RuntimeAdapter`. It needs no network and no key. The same file has a live variant that drives the real Claude Code runtime; it is skipped unless `CLAWBOO_LIVE_ACCEPTANCE=1` is set, costs real money, and uses your real `$HOME` for Claude's sign-in. **The `jsdom` project** holds React Testing Library tests for the dashboard. `setup.ts` registers the `jest-dom` matchers and the `jest-axe` accessibility matcher, starts the shared msw server, and shims `matchMedia` and `ResizeObserver`. The msw server listens with `onUnhandledRequest: 'error'`, so a component that calls an `/api/*` route with no matching handler fails its test. A few default handlers keep common background calls quiet: the GitHub star count (the one cross-origin call panels make on mount), the approvals poll and similar reads. A test that asserts on one of them overrides it with `server.use(...)`. `apps/cli` has its own suites for launcher logic that is risky to get wrong and awkward to run end to end. `discovery.test.ts` decides what a single port is (Clawboo, an access-gated Clawboo, or something else). `discoveryCandidates.test.ts` decides which ports get tried, with the TCP layer mocked so it never probes your own 18790 to 18809. ## Playwright end-to-end tests `playwright.config.ts` drives the assembled app. Its `webServer` builds the SPA (`build:ui`) and then runs `start`, which serves it from the TypeScript server through tsx (`CLAWBOO_UI_DIR=./dist/ui tsx server/index.ts`). The e2e suite therefore never runs the bundled `dist/server.js`; the clean-install gate does. | Setting | Value | | - | - | | Port | `19999`, outside the 18790 to 18809 window, so a `pnpm dev` instance never collides with a run | | Server start | Up to 180 s, because a cold `vite build` is slow | | Workers | One, in file order. Some specs read state that earlier specs leave, so do not shard the suite | | Retries | Two in CI, none locally | | Browser | Chromium only | | Existing server | Reused locally, never in CI | The specs talk to a mock Gateway (`tests/e2e/helpers/mockGateway.ts`) instead of a real OpenClaw Gateway. It is a small `ws` server that answers `connect`, `agents.list`, `agents.files.read` and `chat.send`, and pushes scripted chat frames. Its replies depend on the role: the leader answers a normal message with a `<delegate>` block (which creates a board task), the leader answers a `[Task Update]` with a plain synthesis (so it cannot loop), and a specialist answers with a report-up summary (which moves the task to done). One spec can therefore drive a full chat, board and chat round trip. The 18 spec files in `tests/e2e/` cover: * **Onboarding:** native setup, adding runtimes, reloading after setup, Codex, OpenClaw with Codex, keyless Ollama. * **Chat and board:** team navigation, team chat, the board round trip, board drag, the chat transcript's render window. * **Graph and memory:** the Org chart canvas, the memory graph. * **Platform:** connection and auto-connect, the agent list in the sidebar, a Gateway drop and reconnect through the real `/api/gateway/ws` proxy, serving under `CLAWBOO_BASE_PATH`, and the eval-smoke button. ### The sandbox The e2e fixtures delete teams between runs, so the suite must never reach your real database at `~/.clawboo/clawboo.db` (or wherever `CLAWBOO_HOME` points). Three layers keep it out: 1. **A temp sandbox at config load.** `playwright.config.ts` creates a directory under the OS temp root and points the spawned server's `HOME`, `OPENCLAW_STATE_DIR` and `CLAWBOO_HOME` at it. The same `webServer.env` sets `COREPACK_HOME` and `COREPACK_ENABLE_DOWNLOAD_PROMPT=0` (so corepack does not re-download pnpm into the sandbox), sets `CLAWBOO_DISABLE_EMBEDDINGS=1` (so the memory-graph spec is deterministic), and blanks the ten native provider keys and `OLLAMA_BASE_URL` (so your shell cannot steer the onboarding specs). 2. **`globalTeardown` cleanup.** `tests/e2e/globalTeardown.ts` removes the sandbox after the run, and only after checking that the path is under the OS temp directory. 3. **The `assertSandboxed` guard.** Before any destructive helper runs, `assertSandboxed` in `tests/e2e/helpers/fixtures.ts` checks the runner's sandbox markers and asks the live server, through `GET /api/system/status`, where its state directory is. Both must be under the temp root. This catches a stale, unsandboxed server you started yourself with `pnpm dev` or `pnpm start`, which Playwright would otherwise reuse. > [!WARNING] > > Any new Playwright helper that deletes or changes server state must call `assertSandboxed(request)` first. The temp sandbox and the teardown protect against a misconfigured run; only `assertSandboxed` protects against a stale server that points at your real data. ## The clean-install gate `pnpm test:clean-install` runs `scripts/test-clean-install.mjs`. It simulates `npx clawboo` on a fresh machine and checks that the published package reaches a working dashboard. Run `pnpm assemble` first: the gate packs what assembly produced. It tests a real install, not the repo build. It runs `pnpm pack` on `apps/cli` (pnpm rewrites `workspace:` versions, as at publish) and `npm install`s the tarball into a temp directory (npm's hoisted layout is what `npx` users get). Running `apps/cli/dist` in place would let Node resolve modules from the monorepo's `node_modules` and hide a missing dependency. The script refuses to start while anything answers as Clawboo on ports 18790 to 18809, because the CLI would attach to that dashboard instead. Stop your `pnpm dev` server first. Then it works through 20 numbered steps: | Steps | What they check | | - | - | | 1 to 5 | The assembled files exist; the tarball installs; the five bins exist with npm shims; `clawboo --version` runs through its shim; the static externals check passes on the install. | | 6 to 11 | A fake 401 listener holds port 18791 (like the OpenClaw Gateway's extra ports); a stub provider starts; the installed CLI starts with an isolated `$HOME` and announces a URL that is not 18791. | | 12 to 15 | `GET /` and a deep route return the SPA; `/api/settings` and `/api/system/status` return Clawboo's shapes. | | 16 | Headless Chromium boots the SPA: the sidebar and the onboarding wizard render, with no uncaught exception and no first-party console error. | | 17 | The installed `dist/bin/tasks.js` completes a JSON-RPC `initialize` and `tools/list`, and the list includes `list_tasks`. | | 18 | `POST /api/runtimes/clawboo-native/run` drives a board task to `done` against the stub provider. | | 19 | A second launch reuses the running dashboard through `api-port.txt` instead of forking another server. | | 20 | The server survives `npm install -g clawboo@latest` replacing its package folder: it does not run inside that folder, and `server.js` boots from a deleted directory. Skipped on Windows. | The run needs no API key: step 7's stub is an OpenAI-compatible server that Clawboo Native reaches as Ollama, through `OLLAMA_BASE_URL`. The task uses `kind: 'research'`, so no git repo or worktree is involved. To stay hermetic, the browser step aborts every request that is not first-party and counts only first-party console errors, so an unreachable font CDN cannot fail a release. One install shape needs a unit test instead. `npx` installs land under a dot directory (`~/.npm/_npx/...`), and CI checkouts never do. `apps/web/server/lib/__tests__/serveSpa.test.ts` runs the real `mountSpa` against a plain install path and a dotted one, and checks that the server injects the mount point into the shell it serves. ### The externals check `scripts/check-bundle-externals.mjs` (also `pnpm test:bundle-externals`) scans the Node entry points of the package: the manifest's `bin` targets (`dist/bootstrap.js` and the four `dist/bin/*.js`) plus `dist/server.js`. Every module they still `require()` or `import()` must be in the package's `dependencies`, a Node builtin, or on the optional list in `scripts/lib/bundle-externals.mjs`. It catches what booting cannot: a lazy import fails only when its feature is first used, long after CI went green. `dist/index.js` is not scanned, because `bootstrap.js` loads it through a runtime-built path the static scan cannot follow; the boot steps above are what prove its imports. The scanner is hand-written rather than a regex, because the inlined `ajv` source holds `require(...)` text in a string and esbuild writes module paths into comments. It tracks strings, template literals, comments and regex literals, and it runs its own fixtures before every check. The optional list holds `@opentelemetry/*` and `@anthropic-ai/claude-agent-sdk`; an entry there means a clean install cannot use that feature until the user installs the module. ## The eval harness `@clawboo/evals` is a private, server-only package that grades Clawboo's own orchestration, not a model's output. It catches a regression that spans subsystems, which no single unit test sees. The runner reports two numbers per task. **pass\@1** is the share of the K trials that passed. **pass^k** is that share raised to the power k: an estimate of how often k runs in a row would all pass. pass^k is the stricter bar. Each trial gets a fresh SQLite board from `makeBoardContext`, because leftover state makes failures correlate, and a task body that throws is recorded as a failed trial (`run-error`) rather than an unhandled rejection. * **Code graders** (`graders/code.ts`) inspect the outcome: the board's final state and the event log. They check board state, event counts, dependency readiness, or a custom predicate. * **Model graders** (`graders/model.ts`) are LLM-as-judge for qualities code cannot grade, such as handoff quality. No task uses them yet, so nothing runs them. Every task today is deterministic and tagged `smoke`, so the same set runs inside `pnpm test` and in the manual `evals.yml` workflow. That workflow adds the ablation scorecard: the capability tasks run in four variants (with and without the verifier, with and without structured state). The scorecard is a self-test of the harness wiring, not a measurement of the live orchestrator; the executor runner's integration test is what drives the real verifier and worktree. The dashboard can run the smoke set too. `POST /api/eval/smoke` runs `SMOKE_TASKS` against throwaway boards, with no model, key or network, and clamps `trials` and `k` to 1 to 3. The `09-eval-smoke` spec clicks that button and expects a 100% pass\@1. ## The standing guards Seven guard areas, in nine test files, freeze structural rules of the repo. They run inside `pnpm test` and fail the build as soon as a rule drifts. | Guard | Files | What it freezes | | - | - | - | | Schema source | `packages/db/src/__tests__/schemaSource.test.ts` | Every Drizzle `schema.ts` table and column matches the bootstrap DDL, and back (the FTS5 table excepted). No migration scripts, no `drizzle` entry in `files`, no `drizzle/` directory. | | Schema reconcile | `packages/db/src/__tests__/schemaReconcile.test.ts` (with `schemaBaseline.ts`), `apps/web/server/lib/__tests__/schemaUpgrade.test.ts` | The DDL parser sees exactly the columns SQLite creates; a populated older database gains a missing column without losing rows; a new column must be addable to an existing file; the server's `getDb()` boot path applies the upgrade. | | Publish posture | `apps/web/server/lib/__tests__/packagePosture.test.ts` | The only public package is `clawboo` (apps/cli), and no public package depends at runtime on a private one. | | Layer boundaries | `apps/web/server/lib/__tests__/importBoundary.test.ts` | Runs the real ESLint config against sample sources: each crossing is flagged, legitimate imports are not, and `apps/web`'s `lint` script still covers `server/`. | | Repo hygiene | `repoHygiene.test.ts` and `repoHygieneTracked.test.ts` (same folder) | Product source carries no internal scaffolding strings. One walks the filesystem (new files included); the other runs `git grep --untracked` over the tree. | | Browser-bundle purity | `apps/web/src/__tests__/browserBundlePurity.test.ts` | No file under `apps/web/src` value-imports `@clawboo/db` (type-only imports are fine), and the built `@clawboo/board-core` has zero bare import specifiers. | | Shared connection | `apps/web/server/lib/__tests__/sharedConnection.test.ts` | Bursts of 60 reads and of writes open no new SQLite connection (`dbOpenStats()` counters), closing an SSE stream does not close the shared handle, and server source has zero `createDb(` calls. | The schema checks compare names, not shapes: a change in a column's type, `NOT NULL`, `DEFAULT`, keys or indexes is not caught. Three related guards live elsewhere: `apps/web/src/features/layout/__tests__/entryImportGraph.test.ts` (walks the SPA's static import graph so the marketplace catalog stays out of the bundle), `apps/web/src/app/__tests__/tokenContrast.test.ts` (theme contrast, see [Design system](/internals/design-system)) and `serveSpa.test.ts` (above). ## Other test tools * **The real-concurrency suite.** `packages/db/src/board/__tests__/board.contention.test.ts` runs 20 worker threads against one SQLite file: writers that must never hit "database is locked", 20 racing claims with exactly one winner, and capped-subtask creates that must not overrun the cap. It needs `@clawboo/db`'s built `dist` and `CLAWBOO_CONCURRENCY_TEST=1`, so `pnpm test` skips it. It is the only guard on `createCappedSubtask`'s single-transaction count and insert. Run it before you touch that path: `pnpm --filter @clawboo/db build`, then `CLAWBOO_CONCURRENCY_TEST=1 pnpm --filter @clawboo/db test`. * **The fault-injection runtime.** `@clawboo/adapter-mock` registers as `clawboo-mock` only when `CLAWBOO_ENABLE_MOCK_RUNTIME=1` is set. It runs no model; it plays back silences, crashes, unresolved tool calls and slow starts through the real server paths. * **The live acceptance run.** `CLAWBOO_LIVE_ACCEPTANCE=1 CLAWBOO_LIVE_MODEL=<cheap-model> pnpm --filter @clawboo/web exec vitest run executorRunner.integration` runs the integration test's live variant against a real Claude Code. It needs Claude Code auth and costs money. * **The live connector check.** `pnpm verify:connectors --live` re-resolves every pinned connector package and dials each remote endpoint. CI runs only the offline half. ## CI jobs `.github/workflows/ci.yml` runs on every pull request to `main` and on every push to `main`, except a push that touches only `**.md` files, `LICENSE` or `.changeset/**`. Every job except `filter` uses Node 22 and `pnpm install --frozen-lockfile`. | Job | Runs | Notes | | - | - | - | | `filter` | Compares the pull request's base and head | Marks a pull request content-only when every changed path is under `catalog/` and none under `catalog/packs/clawboo/`. A push is never content-only. | | `lint` | `pnpm format:check`, then `pnpm lint` | Prettier first: it takes seconds and needs no build. Fix with `pnpm format`. | | `typecheck` | `pnpm typecheck` | | | `test` | `pnpm test:coverage`, then builds `@clawboo/db` and runs the real-concurrency suite with `CLAWBOO_CONCURRENCY_TEST=1` | The coverage table is for the log; there is no threshold. A test in the suite checks this CI step still exists. | | `test-cross-platform` | Windows and macOS: `pnpm build`, then Vitest for `@clawboo/worktrees` and for `@clawboo/web`'s `server/` tests | The only place unit tests run outside Linux. `fail-fast: false`, 20 minutes; Turbo runs 4 tasks at a time on Windows, 10 on macOS. | | `website` | `pnpm install` and `pnpm build` in `website/` | `website/` has its own lockfile and is outside the root workspace. | | `build` | `pnpm build`, then `node scripts/check-entry-chunk.mjs` | The second step checks the emitted bundle: the built-in seed is in a chunk, no other pack's prose is, and the size budgets hold. | | `catalog-verify` | `pnpm catalog:verify`, then `node scripts/catalog/budget.mjs` | Offline. | | `verify-connectors` | `pnpm verify:connectors` | Offline; the live half runs only on demand. | | `smoke-test-bundle` | Ubuntu, Windows and macOS: `playwright install --with-deps chromium`, `pnpm assemble`, `pnpm test:clean-install` | 20 minutes. The Windows leg guards the Windows-specific spawn and process-lookup code. | | `e2e` | Ubuntu: `playwright install --with-deps chromium`, `pnpm build`, `pnpm e2e` | 15 minutes. Uploads `playwright-report/` and `test-results/` even on success, since a retried pass is otherwise invisible. | Every job after `filter` skips on a content-only pull request. Those run `.github/workflows/catalog-ci.yml` instead: the catalog gate, its size budgets and a Prettier check of the catalog files, in about two minutes. The seed pack (`catalog/packs/clawboo/**`) is compiled into the tarball, so changing it counts as a product change and runs the full matrix. The split lives in a job rather than in `paths-ignore`, because `paths-ignore` has no way to make an exception for the seed. > [!WARNING] > > Never make `catalog-ci.yml` a required check in branch protection. It is path-filtered, so on a pull request that touches no catalog file it never reports, and a required check that never reports leaves the pull request pending forever. Require `ci.yml`'s `catalog-verify` job instead. `e2e` runs on Ubuntu only because the Playwright `webServer` command uses POSIX shell syntax. It needs `pnpm build` first: every `@clawboo/*` package resolves through its gitignored `dist/`, and nothing builds that during install. Other workflows: * **`codeql.yml`** runs GitHub code scanning over the TypeScript sources and the workflow files, on pull requests, pushes to `main` and a weekly schedule. `.github/codeql/codeql-config.yml` excludes only `catalog/`. * **`evals.yml`** is manual (`workflow_dispatch`). It builds `@clawboo/evals` and its dependencies, then runs its suite and the ablation self-test. * **`publish.yml`** runs on every push to `main` and repeats part of this gate before it publishes. [Release process](/internals/release-process) lists its steps. ## Limits * **No coverage threshold.** The suites test guarantees, not line counts. * **Whether a red job blocks the merge button is a repository setting.** The `main` branch ruleset's required checks decide that, not `ci.yml`. * **Live-model evals never gate a pull request.** Only the deterministic smoke set runs in `pnpm test`. * **The schema guards are name-level.** Type and constraint drift between `schema.ts` and the DDL is not checked. ## Related * [Monorepo and build](/internals/monorepo-and-build): the commands and the build order these tests rely on. * [Release process](/internals/release-process): the publish workflow that reruns part of this gate. * [The executor runner](/internals/executor-runner): the run flow the integration test exercises. * [Board internals](/internals/board-internals): the write path the real-concurrency suite protects. * [Contributing](/appendices/contributing): the pull request workflow around these checks. # How a team works Source: https://docs.claw.boo/intro/how-it-works What happens after you send a message: who answers, how work becomes tasks on the Board, and the two ways Clawboo runs agents. You talk to a team in its chat. The team's leader answers you, or splits the work into tasks for teammates, and every task is a card on the Board. Teammates send their results back to the leader, who replies with the combined answer. This page follows one message through that loop, then explains the two ways Clawboo runs agents. ## The pieces * **The Clawboo server** is one program that runs in the background on your computer. It runs your teams' work, so closing the browser tab does not stop it. It keeps teams, chats, tasks and memory in a database in your Clawboo folder (`~/.clawboo` by default). * **The dashboard** is the page you use in your browser. It talks only to that server. * **Runtimes** are the programs that run agents: Clawboo Native (built in), Claude Code, Codex, Hermes and the OpenClaw Gateway. See [Choose a runtime](/runtimes/index). * **Providers** are the AI services your agents' prompts go to, such as Anthropic, OpenAI or a local Ollama. Prompts go only to the providers and apps you connect. There is no Clawboo account, and Clawboo itself sends no telemetry. ## What happens when you send a message **1. You post in a team's chat** Write in **General** or in a room. To aim the message at one agent, start it with `@` and the agent's name. **2. Clawboo picks who answers** The agent you mentioned answers. Otherwise Boo Zero, your team leader, does. With no Boo Zero, the team's **Leader** answers if it is in this chat, and otherwise the chat's first member. **3. The turn runs on the server** The turn runs on that agent's runtime. You can close the tab; the chat catches up when you come back. **4. It answers, or hands off work** A quick question gets a direct answer. For work with several parts, the leader hands pieces to teammates, and each hand-off becomes a task on the Board, assigned to that teammate. Each runtime signals a hand-off its own way, and Clawboo reads all of them. **5. Teammates do their tasks** Teammates work at the same time. A busy teammate finishes its current task first, and the new one waits in **To do**. A teammate can hand part of its own task to someone else. **6. Results come back** As tasks finish, their results go back to whoever handed out the work, in one batch. Each result also lands on its task card. Clawboo tells the leader which tasks are still out. **7. The leader answers you** The leader combines the results into one reply in the chat. You should see that reply, and the task cards in **Done**. ```mermaid theme={"theme":"github-dark"} sequenceDiagram participant You participant Lead as Boo Zero participant Board participant Mate as Teammate You->>Lead: Message in General or a room Lead->>Board: Each hand-off becomes a task Board->>Mate: Task delivered Mate-->>Board: Result on the task card Board-->>Lead: Results, in one batch Lead-->>You: Combined answer ``` What each agent is told on every turn is listed in [What an agent sees](/concepts/agent-context). ## What you see while it works * **In the chat**, the reply streams in, and each hand-off appears as a task card with its status, such as **Queued**, **Working** or **Done**. * **On the Board** and in the chat's **Tasks** card, cards move from **To do** to **In progress** to **Done**. Anything that waits on you collects under **Needs you**. * **The Team graph** shows which agents are working. * **The Activity card** shows each tool call as it happens. ![The Board in Week view: Needs you holds a blocked task and two unassigned ones with Assign to and Dismiss, In progress holds running and Verifying cards, Done holds finished tasks, and two scheduled Routine runs sit in To do](https://mintcdn.com/privatedocs/tuYc7V3BIxpSQNHP/images/board-kanban.png?fit=max\&auto=format\&n=tuYc7V3BIxpSQNHP\&q=85\&s=17b420f2abf0c61098543eabcd17ffcf) ## How it ends * **The answer arrives** in the chat. * **You press Stop.** While agents in a chat are working, the send button turns into **Stop**. It ends every run in that chat, and their tasks go back to **To do**, where they do not restart on their own. Your other chats keep going. * **Something fails**, or **something needs you**: see the next section. ## When something goes wrong | What happens | What you see | What you can do | | - | - | - | | A teammate's run fails | Its task moves to **Needs you** with the reason in a comment, and whoever handed it out is told. Later steps of the same plan are cancelled | Click **Retry**, or use the card's **More** menu to give it to another agent or **Dismiss** it | | A teammate goes quiet for 8 minutes (24 inside one long tool call) | Clawboo stops the run and the task fails, as above | The same | | The same teammate fails the same task 3 times | Clawboo stops handing that task to it | Reword the request, or give the task to another agent | | A hand-off contains a risky word, such as deploy or delete | The hand-off waits under **Needs you**, and is skipped if nobody answers within 5 minutes | Allow it or turn it down: see [Approvals](/using/approvals) | [Troubleshooting](/help/troubleshooting) lists the messages you may see and what to do about each. ## Two ways Clawboo runs agents Everything above runs on Clawboo's **team chat** path. A second path, the **task runner**, runs one board task at a time, for agent Routines and for runs started through the API. The two paths do not give an agent the same things: | | Team chat | Task runner | | - | - | - | | What runs on it | Messages in General and rooms, hand-offs, plan steps, tasks you give an agent on the Board (including **Retry** and **Assign**), team Routines | Agent Routines on Clawboo Native, Claude Code, Codex and Hermes; runs started with `POST /api/runtimes/:id/run` | | Where Claude Code, Codex and Hermes work | The team's folder, `~/.clawboo/workspaces/<team id>`, shared by the whole team | The task's own git worktree when the task names a repository. A task that changes files without one is refused. Research and review tasks, such as agent Routines made in the app, start in the team's folder | | Clawboo Native file and shell tools | File tools stay inside the team's folder. An approved shell command starts there but runs as you, with network access | The same, in the worktree, or the team's folder for research and review tasks | | Work checked before **Done** | Never | When the run had a worktree and changed files | | Memory | Agents look facts up with their memory tools | The most relevant facts are added at the start | | Team rules, teammates and your introduction | Included | Not included | | Circuit breakers | None; the team limits below apply instead | Yes | | A hard-cap budget | Refuses new runs once reached; stops a Clawboo Native run partway only on a priced model | Refuses new runs once reached, and stops a Clawboo Native run partway | | Restart in a fresh session when the context window fills | No | Up to 3 times per task | An agent Routine you create in the app sends only its instructions, so it runs on the task runner without a repository: no worktree and no checks, but with memory added and circuit breakers on. A team Routine posts its instructions into the team's chat, like a message from you. Several rows have their own page: [What agents can do](/concepts/permissions) covers where agents work, [How work is checked](/concepts/verification) covers checks, and [Budgets and guardrails](/concepts/governance) covers budgets and breakers. ### OpenClaw and one-to-one chat * **OpenClaw agents never use the task runner.** In team chat they run over the Clawboo server's own connection to the OpenClaw Gateway, so they keep working with the tab closed. An OpenClaw agent Routine goes through a separate dispatcher, with no worktree, no checks and no circuit breakers, and it is stopped after 10 minutes by default. * **A one-to-one chat** on an agent's page is a plain conversation, outside both paths. A Clawboo Native, Claude Code, Codex or Hermes agent's chat runs on the server and starts in the agent's team folder. An OpenClaw agent's chat goes over the Gateway connection your browser holds. ### The team's own limits Team chat has no circuit breakers, but it has limits of its own: * One turn can hand off at most 8 tasks. Extra hand-offs are dropped with a note. * Hand-offs go at most three levels below the leader. The agent on a third-level task must do the work itself. * Clawboo stops handing a task to a teammate that has failed it 3 times in a row. * A teammate that goes quiet for 8 minutes is stopped (24 minutes while inside one tool call). [Limits and defaults](/reference/limits-and-defaults) lists every number. > [accordion] Under the hood > > * **One server.** The `clawboo` command starts a server that serves the dashboard and the `/api/*` routes on one port, the first free one from 18790. It listens on `127.0.0.1` unless you set `HOST`. [CLI](/reference/cli) covers ports and stopping the server, and [Security](/operating/security) covers the access token. > * **Two engines.** Team chat runs on one team engine per chat room (`teamOrchestrator.ts` and `serverDeliver.ts` in `apps/web/server/lib/teamChat/`). The task runner is `runTaskOnRuntime` in `apps/web/server/lib/executorRunner.ts`; see [Executor runner](/internals/executor-runner). > * **Hand-off signals.** A Clawboo Native leader calls its `delegate` tool. A Claude Code, Codex or Hermes leader calls the `team_delegate` tool. An OpenClaw agent writes a `<delegate to="@Name">` block, or uses `sessions_send`. A `<plan>` block sets up an ordered chain of steps. [Delegation and orchestration](/concepts/delegation-and-orchestration) has the details. > * **Clawboo's tools.** Every runtime reaches Clawboo's Tasks, Memory and Tools MCP servers, and every runtime except OpenClaw also gets TeamChat. See [MCP servers](/operating/mcp-servers). > * **Where agents are stored.** OpenClaw agents are mirrored from the Gateway. Other agents exist only in Clawboo's database. > * **The event log.** Each step is recorded in an event log that feeds the Activity cards, the Observability panel and the live status on a team's graph. A graph's layout comes from the team's members and each agent's routing, not from the log. > * **Other traffic.** Besides your providers and connected apps, Clawboo contacts GitHub for the marketplace catalog and npm to check for updates. ## Related * [Key concepts](/concepts/index): every term on this page in one sentence. * [What an agent sees](/concepts/agent-context): the context each turn and run carries. * [What agents can do](/concepts/permissions): what each runtime can reach on your computer. * [How work is checked](/concepts/verification): when finished work is tested. * [Work with your team in chat](/using/team-chat): rooms, Stop, `/reset` and the Tasks card. * [Delegation and orchestration](/concepts/delegation-and-orchestration): how hand-offs work in detail. # Welcome to Clawboo Source: https://docs.claw.boo/intro/index Clawboo puts a team of AI agents on your work. Ask once, and Boo Zero splits the job, hands it out, and brings the results back. Clawboo puts a whole team of AI agents on your work. You ask once in a team chat, and Boo Zero, your team leader, splits the request into jobs, hands each one to a teammate, and answers you with the results. Clawboo is free and open source. It runs on your computer and opens in your browser. Teammates can run on Claude Code, Codex, Hermes, OpenClaw or Clawboo's own built-in agents, and one team can mix them. ![A team chat in Clawboo: teammates' updates and two task cards marked Working on the left, and on the right the Team graph card, with Boo Zero above the team lead and four specialists, and the Tasks card, with one task that needs you and five in progress](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-space.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c6bed9e50135bba92df482a1ddf6cd8d) ## How it works | Step | What happens | | - | - | | **1. Ask once** | Tell your team what you need in its team chat, for example "Plan a launch for our new budgeting app: write a short announcement, draft three social posts and list five questions customers will ask first." | | **2. Boo Zero splits it into jobs** | Each job becomes a task on the board, handed to the teammate Boo Zero picks for it, whichever AI tool that teammate runs on. You don't route anything. | | **3. Watch the team work** | Follow the tasks live in the chat and on the board, and see what each agent is doing on the team graph. Want a second look? Add a reviewer agent to the team and ask Boo Zero to have it check the work. | | **4. Get it back, done** | Results come back to Boo Zero, which pulls them together and answers you in the chat. | A quick question gets a direct answer; only work with several parts is split into tasks. [How a team works](/intro/how-it-works) follows one request from start to finish. ## Who it's for * **Founders and small teams** who want a launch plan, a pricing page, a research brief or a weekly report worked on by several agents at once. * **Agencies and freelancers** with repeatable work, such as content, research or reports, that a ready-made team can take on. * **Developers** who want coding agents (Claude Code, Codex, Hermes) on one board next to the rest of the team. * **Anyone with a busy week.** The marketplace also has teams for life outside work, such as Morning Brief, Family Calendar and Trip Desk. You don't need to code. You start Clawboo with one terminal command; everything after that happens in the browser. ## What it costs Clawboo is free and open source under the MIT license. Your agents use the AI you connect, and model usage is billed by that provider. Two ways to start without an API key: a free local model through Ollama, or your ChatGPT subscription (**Sign in with ChatGPT** runs your team on Codex). ## Your data and privacy Clawboo keeps your teams, tasks, chats and memory in a database on your computer. Prompts go only to the providers and apps you connect. There is no Clawboo account, and Clawboo itself sends no telemetry. Clawboo also makes a few requests of its own. None of them includes your prompts: * **GitHub**, for the marketplace catalog and the star count shown in the dashboard * **The npm registry**, to check for updates * **OpenRouter**, for its public list of models in the model pickers * **Google Fonts**, for the dashboard's typeface Some runtimes can read and change files on your computer. [What agents can do](/concepts/permissions) explains what each one may touch. ## Find your way | I want to | Go to | | - | - | | Get a team running | [Quickstart](/getting-started/quickstart) | | Install, update or remove Clawboo | [Install](/getting-started/installation) | | Use Clawboo without an API key | [Run Clawboo without an API key](/guides/no-api-key) | | Understand what happens after I send a message | [How a team works](/intro/how-it-works) | | Add Claude Code, Codex, Hermes or OpenClaw teammates | [Choose a runtime](/runtimes/index) | | Let agents use Gmail, Slack, GitHub and other apps | [Connectors](/using/connectors) | | Run work every day or every week | [Routines](/using/routines) | | Keep spend in check | [Budgets and spend](/using/budgets-and-spend) | | Know what agents can touch on my computer | [What agents can do](/concepts/permissions) | | Fix a problem | [Troubleshooting](/help/troubleshooting) | | Build on Clawboo with the REST API or MCP | [Reference](/reference/index) | ## Next steps > [card] [Quickstart](/getting-started/quickstart) > > Start Clawboo, connect an AI provider, deploy a ready-made team and give it its first job. > [card] [How a team works](/intro/how-it-works) > > What happens between your message and Boo Zero's answer, and what you control along the way. > [card] [Runtimes](/runtimes/index) > > Compare Clawboo Native, Claude Code, Codex, Hermes and OpenClaw, and pick what each teammate runs on. > [card] [Use Clawboo](/using/index) > > How-to guides for teams, the board, connectors, routines, budgets and the rest of the dashboard. # What Clawboo is Source: https://docs.claw.boo/intro/what-is-clawboo What Clawboo does and does not do, how it compares with running one AI tool on its own, and the limits to know before you start. Clawboo is a free, open-source app that runs a team of AI agents on your computer. You give the team a job in a chat. Boo Zero, the team leader, hands parts of it to teammates. Each part becomes a task on a board, and Boo Zero answers you when the results are back. Use this page to decide whether Clawboo fits what you want to do, and what to expect from it. ## What Clawboo is * **A team around your AI tools.** Each agent runs on a runtime, the engine that does its thinking and acting. Clawboo has its own built-in runtime, Clawboo Native, and can also run agents on Claude Code, Codex, Hermes and an OpenClaw Gateway. One team can mix them. * **One board and one chat.** Every hand-off becomes a task on a board that survives restarts, and the team talks the work through in its team chat: **General**, plus any rooms you add. * **Shared team memory.** A fact one agent saves for the team can be found by its teammates. OpenClaw agents are the exception: they read and save only the facts every agent shares, not a team's. * **A marketplace of ready-made teams.** 400+ agents and 80+ teams load from the online catalog, from launch plans and pricing pages to a morning brief and a trip desk. Offline you still get 15 built-in agents in 5 teams. * **Controls for spend and risk.** Spend tracking, budgets, approvals for some risky actions, and limits on how far work can fan out. ## What Clawboo is not * **Not an AI model.** Your agents use the providers you connect (an API key, a local Ollama model or a ChatGPT subscription), and the provider bills the usage. * **Not a sandbox.** Agents act with your user account's permissions. See [What agents can do](/concepts/permissions). * **Not a shared workspace.** One person runs Clawboo on one computer, and there are no Clawboo accounts. ## When Clawboo fits Clawboo fits work with several parts that benefits from more than one specialist: a launch plan, a pricing page, a research brief, a weekly report. Founders, small teams and agencies deploy ready-made teams for this kind of work, and developers add coding agents (Claude Code, Codex, Hermes) to the same board. The marketplace also has teams for life outside work, such as Morning Brief, Family Calendar and Trip Desk. You don't need to code. You start Clawboo with one terminal command, and everything after that happens in the browser. ## Clawboo or one AI tool on its own | You want to | With one AI tool on its own | With Clawboo | | - | - | - | | Get a job with several parts done | You split the work and carry results between tools | Boo Zero splits it and hands each part to a teammate | | Use more than one AI tool | One tool per window | Agents on different runtimes share one team, one chat and one board | | Keep track of the work | A chat history | A task for every hand-off, on a board that survives restarts | | Share what agents learn | Each tool keeps its own memory | A shared memory that agents search and add to | | Keep spend in check | Your provider's limits | Spend tracking, budgets that warn as spend nears the limit, an opt-in hard cap | | See what is happening | Each tool's own output | A live team graph, activity feeds, the board, and an Org chart of all teams | | Check the work | You review it | You still review most of it (see Limits below) | A team is not always the better choice. It spends more tokens than one agent, because the leader, each teammate and the final summary are separate model runs. For a quick question or a one-step task, one agent on its own is faster and cheaper. Clawboo earns its keep when a job has several parts, or when you want the work tracked on a board instead of lost in a chat scroll. ## Limits * **Agents act with your permissions.** Claude Code, Codex and Hermes run with their own permission prompts and sandboxes turned off, so they can read and change any file your user account can. A git worktree keeps a task's file changes apart; it is not a sandbox. For work you don't trust, use a separate user account or a virtual machine. [What agents can do](/concepts/permissions) has the details for each runtime. * **Most work is not checked automatically.** Clawboo checks a task automatically only when it runs in its own git worktree of a repo you point it at and changes files. Team chat work is never checked automatically: review it yourself, or add a reviewer agent and ask Boo Zero to have it check the work. See [How work is checked](/concepts/verification). * **Spend numbers are partly estimates.** Some runtimes report their real cost, and for others Clawboo estimates it. Your provider's bill is the real number. A budget warns by default; a hard cap is opt-in and cannot stop every runtime in the middle of a run. See [Budgets and spend](/using/budgets-and-spend). * **Features differ by runtime.** OpenClaw needs its own Gateway, and agent files, steering and cost reporting work differently on each runtime. See [Choose a runtime](/runtimes/index). * **One person, one computer.** Clawboo listens only to your own computer unless you set it up otherwise. To reach it from another machine, see [Remote access](/guides/self-host-securely). ## What we do not claim * That Clawboo makes agents safe to run on anything. * That every finished task was checked. * That Clawboo's spend numbers match your provider's bill. * That nothing leaves your computer: prompts go to the providers and apps you connect. * That a team beats one good agent at every job. ## Related * [Quickstart](/getting-started/quickstart): get a team running and give it a first job. * [How a team works](/intro/how-it-works): what happens between your message and the answer. * [What agents can do](/concepts/permissions): permissions and safety on each runtime. * [Choose a runtime](/runtimes/index): compare the five runtimes. * [FAQ](/appendices/faq): short answers to common questions. # Data and state Source: https://docs.claw.boo/operating/data-and-state Where Clawboo keeps its data, what it writes in the OpenClaw folder, how to back it up, and what a reset removes and leaves behind. Use this page to find Clawboo's data, back it up, move it or wipe it. Clawboo keeps its own state in one folder, `~/.clawboo`. It also writes a few files in OpenClaw's folder, `~/.openclaw`, and a reset of `~/.clawboo` leaves those alone. ## Where Clawboo keeps its data Clawboo's folder is `~/.clawboo`. To use another folder, set `CLAWBOO_HOME` before you start the server (a leading `~` is expanded). The database, the key vault, the logs, the worktrees and the team folders all move with it. ```bash theme={"theme":"github-dark"} CLAWBOO_HOME=/srv/clawboo clawboo ``` ```text theme={"theme":"github-dark"} ~/.clawboo/ ├── clawboo.db the database ├── clawboo.db-wal, -shm SQLite working files for the database ├── settings.json the OpenClaw Gateway link ├── api-port.txt the port the server is using ├── server.log, server.log.1 server output, when clawboo started the server ├── gateway.log, gateway.pid an OpenClaw Gateway that Clawboo started ├── connector-pids.json connector processes to clean up after a crash ├── proxy-device-identity.json Clawboo's device key for the Gateway ├── secrets/ │ ├── master.key the key that decrypts the vault │ ├── runtime-keys.json the encrypted vault │ ├── disconnected-runtimes.json │ └── keyless-providers.json ├── browser-profiles/ each agent's browser profile ├── frames/ screenshots from agents' browsers ├── runtimes/ per-agent homes for Codex and Hermes ├── catalog/ a cached copy of the Marketplace catalog ├── workspaces/ a folder for each team, where its agents start ├── worktrees/ git worktrees for board tasks └── reviews/ checkouts for verifying finished work ``` | Path | What it holds | Safe to delete? | What you lose | | - | - | - | - | | `clawboo.db`, `-wal`, `-shm` | Teams, agents, the board, chats, memory, routines, budgets, most dashboard settings, and the secret that signs MCP attach URLs | Only to start over, with the server stopped | All of that. Attach URLs you copied by hand stop working. | | `settings.json` | The Gateway URL and token, and when you dismissed the first-run screen. The access token is never read from here, only from `STUDIO_ACCESS_TOKEN` | Yes | The saved Gateway link. Clawboo falls back to the Gateway details in OpenClaw's `openclaw.json`. | | `api-port.txt` | The port of the running server | Yes | Usually nothing: `clawboo` also scans ports 18790 to 18809. | | `server.log`, `server.log.1` | Output of a server that `clawboo` started | Yes | Old log lines. | | `gateway.log`, `gateway.pid` | Output and process id of an OpenClaw Gateway that Clawboo started | Yes | Old log lines. Clawboo finds the Gateway by its port instead. | | `connector-pids.json` | Connector processes Clawboo started | Yes, with the server stopped | After a crash, leftover connector processes are not cleaned up at the next start. | | `proxy-device-identity.json` | The key pair Clawboo uses to identify itself to the OpenClaw Gateway | Only if you can pair again | Clawboo makes a new identity, so the Gateway may ask you to pair it again. | | `secrets/master.key` | The key that decrypts the vault | No | Every key in the vault: re-enter provider keys and reconnect connectors. | | `secrets/runtime-keys.json` | The encrypted vault: provider keys, connector credentials and sign-ins, the Composio key | Only to remove every saved key | The same as losing the master key. | | `secrets/disconnected-runtimes.json` | The runtimes you disconnected | Yes | A disconnected runtime can pick up a key from your shell or `~/.openclaw/.env` again. | | `secrets/keyless-providers.json` | Whether you chose local Ollama for Clawboo Native | Yes | Clawboo forgets the choice; choose Ollama again. | | `browser-profiles/` | Each agent's browser profile, including the sites it is signed in to | Yes | Agents' browser sign-ins and cookies. | | `frames/` | Screenshots saved from agents' browsers | Yes | Those screenshots. | | `runtimes/` | Per-agent homes for Codex and Hermes agents, including a copy of your Codex sign-in | Yes, when no run is active | Those agents' saved sessions; they start fresh. | | `catalog/` | A cached copy of the Marketplace catalog | Yes | Nothing. Clawboo downloads it again. | | `workspaces/` | One folder per team (`workspaces/<team id>`), where its agents start when a run has no worktree, plus `_no-team` for tasks with no team. Deleting a team deletes its folder | Yes, when no run is active | The files agents saved there. Clawboo creates a team's folder again on its next run. | | `worktrees/` | Git worktrees for board tasks that run in a repository | No: Clawboo removes old ones itself | Uncommitted work in those checkouts. | | `reviews/` | Read-only checkouts for verifying finished work | Yes, when no check is running | Nothing. | On macOS and Linux, Clawboo makes the database, its working files, `settings.json` and the folder readable only by your user account, and keeps `secrets/` the same way. A server started by `clawboo` also uses this folder as its working directory. A coding agent that runs without a worktree (every team-chat turn, every one-to-one chat, research and review tasks, and Routines you make in the dashboard) starts in its team's folder under `workspaces/` instead, created readable only by you. It still has your account's full file access; see [What agents can do](/concepts/permissions). ## What Clawboo writes in OpenClaw's folder OpenClaw keeps its state in `~/.openclaw` (`OPENCLAW_STATE_DIR` moves it). Clawboo writes there in these cases: | When you | Clawboo writes | | - | - | | Connect a key under **Settings > Providers** | The key, unencrypted, in `.env` and in each OpenClaw agent's `agents/<id>/agent/auth-profiles.json`, as well as in Clawboo's encrypted vault. **Disconnect** under Providers removes every copy. | | Set up OpenClaw from Clawboo | `openclaw.json` (a local Gateway and a default model) and `.env` (the Gateway token, plus the provider key, or `OLLAMA_API_KEY=ollama-local` for Ollama). The provider key also goes into each OpenClaw agent's `auth-profiles.json`. It fills in what is missing and keeps your other settings. | | Change an OpenClaw setting in the dashboard | `openclaw.json`: the default command approval in **Settings > System**, OpenClaw's default model in **Settings > Runtimes**, an OpenClaw agent's model, or agent-to-agent messaging when you add a route on the Org chart or deploy a team whose agents route work to each other. | | Create an OpenClaw agent or edit its files | Through the Gateway: the agent, a `workspace-<name>` folder, and its files such as `SOUL.md` and `AGENTS.md`. | | Connect to the Gateway (every time) | Through the Gateway: its config registers Clawboo's Memory, Tasks and Tools MCP servers and denies the tools that spawn sub-agents, keeping your own denials. | | Start a Gateway that asks for a repair | Clawboo runs `openclaw doctor --fix` once, then tries again. | | Run a Clawboo MCP stdio bin without `CLAWBOO_DB_PATH` | `~/.openclaw/clawboo/clawboo.db`, a separate database the bin creates. See [Two database paths](#two-database-paths). | On macOS and Linux, Clawboo makes `.env` and each `auth-profiles.json` readable only by you (mode `0600`) whenever it writes them, and creates `~/.openclaw` owner-only (`0700`) if it does not exist yet. An existing `~/.openclaw` keeps its mode. Clawboo also reads `openclaw.json` (the Gateway's port and token when `settings.json` has none), `.env` (the last place it looks for a provider key, after your environment and the vault), the agents' `auth-profiles.json` (which providers OpenClaw has keys or sign-ins for), and `state/openclaw.sqlite`. It opens that database read-only, to show the standing command permissions on an OpenClaw agent's **Permissions** tab and which browser tab belongs to which agent. Keys you enter during onboarding or on a **Runtimes** card go only into the vault. See [Providers and runtimes](/using/providers-and-runtimes) for where each kind of key is stored. ## What a reset leaves behind Resetting `~/.clawboo` never touches `~/.openclaw`, so after a full reset: * **Keys you connected under Providers keep working.** They are still in `~/.openclaw/.env` and in the OpenClaw agents' `auth-profiles.json`, and Clawboo reads `.env` when its vault has no key. To remove them, disconnect each one under **Settings > Providers** before you reset, or delete their lines from those files. * **Runtimes you disconnected can connect again on their own.** Clawboo records a disconnect in `~/.clawboo`, so after the reset a matching key in your shell or in `~/.openclaw/.env` reaches them again. * **OpenClaw agents stay in OpenClaw**, including agents Clawboo created, with their files. Clawboo finds the Gateway again from `openclaw.json`. * **The Gateway keeps Clawboo's MCP entries.** Clawboo rewrites them with a new attach token the next time it connects. ## Back up your data ### Back up the database `clawboo backup` copies the database into one consistent file. It is safe to run while the server is running. ```bash theme={"theme":"github-dark"} clawboo backup clawboo backup ~/backups clawboo backup ~/backups/clawboo.db --force ``` With no argument it writes `clawboo-backup-<date>-<time>.db` in the current folder. Give it a folder to write the timestamped file there, or a file name. It refuses to replace an existing file unless you add `-f` (`--force`). It copies the database only. The file holds all your teams, tasks, chats and memory, and the secret that signs MCP attach URLs, so on macOS and Linux it is readable only by you (mode `0600`) from the moment it is created. If the backup fails, the partial file is removed. A backup made with 0.5.0 or older has your default file permissions; restrict it: ```bash theme={"theme":"github-dark"} chmod 600 clawboo-backup-*.db ``` ### What else to copy The database is most of your data, but not all of it. Copy these too when you need them: | Copy | Why | | - | - | | `secrets/` (both files together) | Saved provider keys, connector credentials and sign-ins. One file is useless without the other. | | `settings.json` | The OpenClaw Gateway link. | | `proxy-device-identity.json` | Clawboo's identity with the Gateway, so you do not have to pair it again. | | `browser-profiles/` | The sites your agents' browsers are signed in to. | | `runtimes/` | Codex and Hermes agents' saved sessions, including a copy of your Codex sign-in. | | `workspaces/` | The files your agents saved in their team's folder. | | `~/.openclaw` (or your `OPENCLAW_STATE_DIR`) | OpenClaw's own agents, config and keys, if you use OpenClaw. Clawboo never backs it up. | ### Back up everything Stop the server and copy the whole folder. If you set `CLAWBOO_HOME`, use that folder in place of `~/.clawboo` here and in the commands below: ```bash theme={"theme":"github-dark"} clawboo stop cp -a ~/.clawboo ~/clawboo-backup-$(date +%Y%m%d) clawboo ``` The copy holds your vault and its key and your agents' signed-in browser profiles. Store it as carefully as a password file. > [!NOTE] > > With `STUDIO_ACCESS_TOKEN` set, `clawboo stop` and `clawboo restart` cannot identify the server through the access gate. They print its port and a command that stops it by port, then exit without stopping it. Run that command, or stop Clawboo through the service manager that runs it (systemd, Docker). Start it again [directly](/operating/deployment#start-the-server-directly) or through that service manager, not with `clawboo`, which cannot tell when a token-protected server is ready. `clawboo backup` works either way because it reads the database file directly. See the [CLI reference](/reference/cli). ### Restore **1. Stop the server** Run `clawboo stop`, or stop the service that runs Clawboo. **2. Put the backup in place** Copy the backup file over `~/.clawboo/clawboo.db`, or replace `~/.clawboo` with a whole-folder copy. **3. Remove stale working files** If you restored a single-file backup, delete any `clawboo.db-wal` and `clawboo.db-shm` left beside it from the old database. **4. Start Clawboo** Run `clawboo`. You should see your teams and board as they were when you made the backup. A database from another install carries that install's attach secret, so copy any attach URLs you set up by hand again from [MCP servers](/operating/mcp-servers). ## Hard reset Reset only to wipe data. An upgrade needs no reset; see [After an upgrade](#after-an-upgrade). > [!WARNING] > > Both resets delete data you cannot get back. Back up first if any of it matters, and stop the server before you delete anything. A running server keeps the old database open and goes on serving it, and `clawboo` attaches to that server, so the reset seems not to happen. With `STUDIO_ACCESS_TOKEN` set, `clawboo stop` cannot stop the server: stop it as the note under [Back up everything](#back-up-everything) says, and delete only after it has stopped. The commands below delete only if `clawboo stop` succeeds. ### Reset only the data ```bash theme={"theme":"github-dark"} clawboo stop && rm -f ~/.clawboo/clawboo.db ~/.clawboo/clawboo.db-wal ~/.clawboo/clawboo.db-shm clawboo ``` Clawboo creates an empty database at the next start. Your keys, settings, logs, browser profiles and team folders stay. You lose everything in the database: teams and their agents, the board, chats, memory, routines, budgets and most dashboard settings. OpenClaw agents themselves stay in OpenClaw. The secret that signs MCP attach URLs goes with the database. Attach URLs you copied by hand stop working; Clawboo's own runtimes and a local OpenClaw Gateway get new ones on their next run or connection. ### Reset everything ```bash theme={"theme":"github-dark"} clawboo stop && rm -rf ~/.clawboo clawboo ``` Clawboo starts as a new install. This also deletes the vault and its key, the device identity, the logs, the browser profiles, the team folders and the worktrees, including any uncommitted work in them (run `git worktree prune` in each repository afterwards). It does not touch `~/.openclaw`; see [What a reset leaves behind](#what-a-reset-leaves-behind). ## Two database paths The server always uses `clawboo.db` in Clawboo's folder (`~/.clawboo/clawboo.db`, or the same file under `CLAWBOO_HOME`). The four MCP stdio bins (`clawboo-mcp-tasks`, `clawboo-mcp-memory`, `clawboo-mcp-tools`, `clawboo-mcp-teamchat`) open the file named by `CLAWBOO_DB_PATH` instead, which defaults to `~/.openclaw/clawboo/clawboo.db` and is created empty when missing. The two defaults differ on purpose. For a stdio bin to see the server's board and memory, set `CLAWBOO_DB_PATH` to the server's database, or copy the stdio snippet from `GET /api/mcp/config?transport=stdio`, which sets it for you. The server itself ignores `CLAWBOO_DB_PATH`. See [MCP servers](/operating/mcp-servers). ## The server log A server started by `clawboo` writes its output to `~/.clawboo/server.log`. When `clawboo` starts a server and the log is over 5 MB, it first moves the old log to `server.log.1`. If the dashboard has not answered after about 45 seconds, `clawboo` prints the last lines of this log and its path. A server you start yourself, from a service manager or with `node`, writes to its own output instead. After the in-app update restarts the server, the new server's output is not written to `server.log`; run `clawboo restart` to capture it again. An OpenClaw Gateway that Clawboo started logs to `~/.clawboo/gateway.log`. The [CLI reference](/reference/cli) covers starting, stopping and finding the server. ## How Clawboo checks its data at startup Each time it starts, Clawboo checks its folder, database and vault, and shows the results in **Settings > System Health** (and at `GET /api/health`). | Check | When it fails | Severity | | - | - | - | | **Clawboo home writable** | Clawboo cannot create or write its folder. | Fatal | | **Database integrity** | The database cannot be opened or is damaged. | Fatal | | **Database schema** | A table or column the database needs is missing. The details name it. | Fatal | | **Secrets vault permissions** | Other users can read `secrets/`, `master.key` or the device identity (macOS and Linux). | Degraded | | **Master key** | The master key cannot decrypt the vault, so saved keys cannot be read. | Degraded | The server keeps running either way, so you can read each check's details in System Health: they say what is wrong and what to do. Treat a fatal check as broken until fixed, and use a [full reset](#reset-everything) only as the last resort. When **Master key** fails, re-enter your provider keys and reconnect your connectors, or reset. ## After an upgrade When a new version first starts, it adds any columns the database is missing, so your data carries over with no step from you. It also turns on read-only Tasks and TeamChat for any Clawboo Native agent that has both turned off. That repair runs once and records that it ran, so turning either tool off afterwards sticks. > [accordion] Under the hood > > * The database holds 35 tables and runs in SQLite's WAL mode, which is why the `-wal` and `-shm` files sit beside it. `clawboo backup` uses SQLite's online backup, so its copy needs no working files. See the [database schema](/reference/database-schema). > * `clawboo stop` sends the server a stop signal. On macOS and Linux the server stops its agent processes, folds the working files into `clawboo.db` and closes it, so after a clean stop `clawboo.db` alone holds every saved change. On Windows the process is ended at once with none of those steps, so copy the `-wal` and `-shm` files with the database. > * The `POST /api/system/configure-openclaw` route, which no dashboard screen calls, replaces `openclaw.json` and `.env` outright and mints a new Gateway token. The dashboard's OpenClaw setup uses the route that fills in only what is missing. ## Related * [CLI reference](/reference/cli): `clawboo backup`, `clawboo stop`, `clawboo restart`, and how `clawboo` finds the server. * [Environment variables](/reference/environment-variables): `CLAWBOO_HOME`, `CLAWBOO_DB_PATH`, `CLAWBOO_SECRETS_MASTER_KEY`, `OPENCLAW_STATE_DIR`. * [Security](/operating/security): the vault, the master key, and who can reach the server. * [MCP servers](/operating/mcp-servers): attach outside agents and point the stdio bins at the right database. * [Database schema](/reference/database-schema): every table in `clawboo.db`. # Run the Clawboo server Source: https://docs.claw.boo/operating/deployment Start the Clawboo server with the launcher or directly, keep it running as a service, update it, and find its port and logs. Clawboo runs as one server process. On one port it serves the dashboard, its API and a WebSocket for the OpenClaw Gateway. Use this page to start that server, keep it running as a service, update it and find its logs. To open the dashboard from another computer, see [Reach Clawboo from another machine](/guides/self-host-securely). ## Before you begin * **Node.js 22.12 or newer.** The `clawboo` command checks this and stops with a message on an older Node. * **Clawboo installed** with `npm install -g clawboo` ([Install Clawboo](/getting-started/installation)). The package holds the launcher, the server (`dist/server.js`), the dashboard files (`dist/ui`) and the four MCP programs (`dist/bin`). ## Ways to run it | How | Command | Use it for | | - | - | - | | The launcher | `clawboo` | Your own computer: it starts the server in the background and opens your browser | | A direct start | `node "$(npm root -g)/clawboo/dist/server.js"` | A service manager, a container, or any install with an access token | | A source checkout | `pnpm dev` | Working on Clawboo itself ([Monorepo and build](/internals/monorepo-and-build)) | ## Start with the launcher ```bash theme={"theme":"github-dark"} clawboo ``` The launcher does three things: 1. It looks for a Clawboo server that is already running ([how it finds the port](/reference/cli)) and opens your browser at it. If that server runs an older version than the launcher, it offers to restart it: `-y` restarts without asking, and `--no-version-check` skips the check. 2. If none is running, it starts the server in the background. The server's working directory is your Clawboo folder (`~/.clawboo`), and its output goes to `~/.clawboo/server.log`. 3. It waits up to 45 seconds for the server to answer, then opens `http://localhost:<port>/` (plus your `CLAWBOO_BASE_PATH`, when one is set). The server keeps running after the launcher exits. `clawboo stop` stops it, and `clawboo restart` replaces it on the same port; see the [CLI reference](/reference/cli). > [!NOTE] > > With `STUDIO_ACCESS_TOKEN` set, the launcher cannot read the server's status. It cannot tell when a token-protected server is ready, and it will not stop or restart one. `clawboo`, `clawboo stop` and `clawboo restart` report that a server is running and requires an access token. `clawboo stop` also prints a command that stops it by port. Start a token-protected server directly instead. ## Start the server directly ```bash theme={"theme":"github-dark"} mkdir -p ~/.clawboo cd ~/.clawboo NODE_ENV=production node "$(npm root -g)/clawboo/dist/server.js" ``` * **It runs in the foreground** and logs to the terminal. `NODE_ENV=production` gives plain JSON log lines, as with the launcher. * **Ctrl+C stops it cleanly.** It stops the agent processes it started, waits up to 5 seconds for them, and closes the database. * **Start it from your Clawboo folder**, as the launcher does. Claude Code, Codex and Hermes do not start there: a run without a worktree (every team-chat turn, research and review tasks, and Routines you make in the dashboard) starts in its team's folder under `~/.clawboo/workspaces`. * **The dashboard files** come from the `ui` folder next to `server.js`. `CLAWBOO_UI_DIR` points somewhere else. If Clawboo cannot read `index.html` there, page loads answer `503` with "The Clawboo dashboard could not be read from disk. Check CLAWBOO\_UI\_DIR." * **MCP attach snippets.** To make the stdio snippets from `GET /api/mcp/config` name the bundled MCP programs, also set `CLAWBOO_MCP_BIN_DIR="$(npm root -g)/clawboo/dist/bin"`, as the launcher does. ### Run it as a service A service manager keeps the server running after you log out and restarts it if it crashes. On Linux with systemd, a user service looks like this: ```ini theme={"theme":"github-dark"} # ~/.config/systemd/user/clawboo.service [Unit] Description=Clawboo [Service] WorkingDirectory=%h/.clawboo Environment=NODE_ENV=production EnvironmentFile=%h/.config/clawboo/clawboo.env ExecStart=/usr/bin/node /usr/lib/node_modules/clawboo/dist/server.js Restart=on-failure [Install] WantedBy=default.target ``` Replace the two paths in `ExecStart` with the output of `command -v node` and `npm root -g`. Agents and connectors find programs through `PATH`. If Node, npm or your agent runtimes live outside the system folders (under nvm, for example), add an `Environment=PATH=...` line with your shell's `PATH`. Put your settings in the environment file, one `NAME=value` per line, and keep the file readable only by you: ```bash theme={"theme":"github-dark"} mkdir -p ~/.clawboo ~/.config/clawboo touch ~/.config/clawboo/clawboo.env chmod 600 ~/.config/clawboo/clawboo.env ``` ```ini theme={"theme":"github-dark"} # ~/.config/clawboo/clawboo.env STUDIO_ACCESS_TOKEN=paste-your-token-here CLAWBOO_API_PORT=18790 CLAWBOO_ALLOWED_HOSTS=dash.example.com CLAWBOO_ALLOWED_ORIGINS=https://dash.example.com ``` Generate the token with `openssl rand -hex 24` and save it in your password manager before you start the server. Use only letters, digits, `.`, `_`, `~` and `-`: with any other character the server refuses to start. The file mode keeps other user accounts out of the file. Programs running as you, agents included, can still read it: the token guards the network, not your own account. Then turn the service on, and let it run while you are logged out: ```bash theme={"theme":"github-dark"} systemctl --user daemon-reload systemctl --user enable --now clawboo loginctl enable-linger "$USER" ``` `journalctl --user -u clawboo` shows its log. On macOS, run the same server command under launchd, with the same working directory and variables. Under launchd there is no journal, so set `StandardOutPath` and `StandardErrorPath` in its plist to a log file (a full path, as launchd does not expand `~`) and read that file. ## Ports | Setting | What the server does | | - | - | | Nothing set | Takes the first free port from `18790`, trying 20 ports (up to `18809`) | | `CLAWBOO_API_PORT=N` | Uses exactly `N`. If `N` is taken, it stops with `CLAWBOO_API_PORT=N is already in use.` | | `PORT=N` | Uses `N` when `CLAWBOO_API_PORT` is unset and the server is not in dev mode, for hosting platforms that set `PORT` | | `CLAWBOO_API_PORT_START=M` | Starts the search at `M` instead of `18790` | The server writes the port it chose to `~/.clawboo/api-port.txt`, and removes the file when it stops unless another instance has rewritten it. Pin the port when something else must find the server, such as a reverse proxy. To run a second, separate instance, give it its own `CLAWBOO_HOME` and port. ## Where the server keeps its files Everything lives in your Clawboo folder, `~/.clawboo` by default (`CLAWBOO_HOME` moves it): the database, settings, the encrypted key vault, logs, task worktrees and each team's folder. Clawboo also writes to OpenClaw's folder (`~/.openclaw`) when you connect a key under **Providers**, set up OpenClaw, or change OpenClaw settings from the dashboard. [Data and state](/operating/data-and-state) lists every file and covers backups and resets. ## Logs * A server the launcher started writes to `~/.clawboo/server.log`. When the launcher starts a server and the log is over 5 MB, it first moves the old log to `server.log.1`. * A direct start logs to its terminal, or to your service manager's journal. * An OpenClaw Gateway that Clawboo starts for you logs to `~/.clawboo/gateway.log`. * `LOG_LEVEL` sets how much the server logs: `info` by default, `debug` for more. * After an in-app update, the new server's output does not reach `server.log`. Run `clawboo restart` to capture it again. ## Update Clawboo > [tab] In the dashboard > > When a newer version is on npm, an **Update available** chip appears at the bottom left. On a global npm install it reads **Click to update**: one click installs `clawboo@latest` with npm, restarts the server on the same port and reloads the page. For an `npx` run or a source checkout, the chip reads **Copy command** and copies the command to run instead. If npm fails, the chip shows npm's reason; a permissions error suggests `sudo npm install -g clawboo@latest`. > [tab] With npm > > ```bash theme={"theme":"github-dark"} > npm install -g clawboo@latest > clawboo restart > ``` > > Or run `clawboo`: it notices that the running server is older and offers to restart it. > > With `STUDIO_ACCESS_TOKEN` set, `clawboo restart` cannot restart the server and the old version keeps running. Stop it with the command `clawboo restart` prints, or through the service manager that runs it, then start it again [directly](#start-the-server-directly) or through that service manager. > [tab] Under a service manager > > ```bash theme={"theme":"github-dark"} > npm install -g clawboo@latest > systemctl --user restart clawboo > ``` > > Skip the in-app update here. It replaces the server process itself, outside the service manager's control. To find updates, the server asks `registry.npmjs.org` for the latest version and keeps a successful answer for 6 hours. ## Behind a reverse proxy or on another machine * **A proxy on the same machine:** leave `HOST` unset, pin `CLAWBOO_API_PORT`, and set `STUDIO_ACCESS_TOKEN` (unless the proxy authenticates every request), `CLAWBOO_ALLOWED_HOSTS` and `CLAWBOO_ALLOWED_ORIGINS`. Deny `/api/mcp/` at the proxy. * **Another machine, a network or a container:** set `HOST=0.0.0.0` with the token and the two allowlists. Without a working token, the server refuses to start. * **A hostname shared with other apps:** serve Clawboo under a path prefix with `CLAWBOO_BASE_PATH`. [Reach Clawboo from another machine](/guides/self-host-securely) has the steps and the nginx, Caddy and Cloudflare Tunnel configs. On Clawboo 0.5.0 and older the `/api/mcp/` rule is required: see the advisory in the [Security model](/operating/security). ## Check that it is running ```bash theme={"theme":"github-dark"} cat ~/.clawboo/api-port.txt curl -s http://127.0.0.1:18790/api/settings curl -s http://127.0.0.1:18790/api/health ``` Use the port from the first command. `/api/settings` answers with `gatewayUrl`, `hasToken` and `firstRunDismissedAt`. `/api/health` returns the startup checks, and `"ok": true` means none of the fatal ones failed. On a token-protected install both answer `401`, which also means the server is up. The **System Health** panel shows the same report. ## Troubleshooting > [accordion] CLAWBOO\_API\_PORT=N is already in use. > > A pinned port gets no fallback. Free that port, choose another, or unset `CLAWBOO_API_PORT` so the server picks a free one. > [accordion] Dashboard is taking too long to start. > > The launcher waited 45 seconds without an answer. It prints the last lines of `~/.clawboo/server.log` and the log's path: read them for the cause. If you set `STUDIO_ACCESS_TOKEN`, the server may be running fine, because the launcher cannot see past the token. Open the dashboard with `?access_token=<your-token>`, and start token-protected servers directly from now on. > [accordion] A Clawboo server is running on port N, but it requires an access token. > > Expected on a token-protected install. Open the address the message prints, with your token in place of `<token>`. To stop the server, press Ctrl+C in its terminal, use your service manager, or run the command `clawboo stop` prints. > [accordion] Clawboo requires Node.js 22.12 or newer > > Install a current Node from nodejs.org, or switch with a version manager (`nvm install 22`, `fnm use 22`), then run `clawboo` again. > [accordion] Page loads answer 503: The Clawboo dashboard could not be read from disk. > > `CLAWBOO_UI_DIR` does not point at the dashboard files. Unset it to use the `ui` folder that ships next to `server.js`. > [accordion] Could not find the Clawboo server. > > The launcher cannot find `server.js` next to itself. Reinstall with `npm install -g clawboo`. > [accordion] SECURITY: refusing to start > > `HOST` is set to a network address and the server has no access token. See [Reach Clawboo from another machine](/guides/self-host-securely), or unset `HOST` to listen on `127.0.0.1` only. If the message names an unsupported character in `STUDIO_ACCESS_TOKEN`, the token has a character other than letters, digits, `.`, `_`, `~` and `-`, which stops the server on any address: replace it with one from `openssl rand -hex 24`. > [accordion] SECURITY/CONFIG: CLAWBOO\_BASE\_PATH is invalid > > The message names the problem with the value. Use a prefix like `/clawboo`, or unset the variable to serve at the root. ## Related * [Reach Clawboo from another machine](/guides/self-host-securely): SSH tunnels, reverse proxies, network binds and path prefixes * [Security model](/operating/security): what is protected by default, and where your keys are stored * [`clawboo` CLI reference](/reference/cli): every command, port discovery, stop and restart * [Data and state](/operating/data-and-state): every file in your Clawboo folder, backups and resets * [Environment variables](/reference/environment-variables): every setting the server reads * [Limits and defaults](/reference/limits-and-defaults): ports, timeouts, rate limits and other numbers # Self-hosting overview Source: https://docs.claw.boo/operating/index What to read when you run the Clawboo server for yourself or a small team, and how a fresh install behaves before you change anything. Use this section if you run the Clawboo server: on your own computer, on a shared machine or home server, or behind a reverse proxy so a small team can reach it. If you only use the dashboard on the computer where you installed it, the defaults below already keep it on that machine, and you can go straight to [Use Clawboo](/using/index). ## What you are running Clawboo is one Node.js program. It serves the dashboard and the REST API on one port, hosts Clawboo's [MCP servers](/operating/mcp-servers), and keeps its data in a SQLite file in `~/.clawboo`. Clawboo Native agents run inside that program. Claude Code, Codex and Hermes run as programs it starts on the same machine, and OpenClaw agents run in the OpenClaw Gateway. ## In this section | Page | Read it when | | - | - | | [Reach Clawboo from another machine](/guides/self-host-securely) | You want to open the dashboard from another computer, over an SSH tunnel, or through a reverse proxy. | | [Deployment](/operating/deployment) | You run the server as a service, in a container or from source, or you need its ports, logs and updates. | | [Security](/operating/security) | You want the security model: the access token, the same-origin guard, the key vault, and what each does. | | [Data and state](/operating/data-and-state) | You want to find, back up, move or reset Clawboo's data, or see what it writes in OpenClaw's folder. | | [MCP servers](/operating/mcp-servers) | You want an agent that Clawboo does not run to use the board, shared memory, tools or the peer room. | | [Production defaults](/operating/production-defaults) | You want to know what a fresh install allows and limits, and how to change each default. | ## The default posture * **Only this machine can connect.** The server listens on `127.0.0.1` until you set `HOST`. * **There is no access token.** Anyone who can reach the server can use it until you set `STUDIO_ACCESS_TOKEN`. With `HOST` set to anything but a loopback address, the server refuses to start without one, but a reverse proxy on the same machine does not trip that check. * **Browsers must use a local address.** The same-origin guard rejects API calls that arrive under another name, such as your proxy's domain, until you list it in `CLAWBOO_ALLOWED_HOSTS` and `CLAWBOO_ALLOWED_ORIGINS`. * **State lives in `~/.clawboo`.** Clawboo also writes provider keys and OpenClaw settings in OpenClaw's `~/.openclaw` folder. * **Spend is tracked, not capped.** Budgets warn by default. A hard cap is something you turn on. Prompts go only to the providers and apps you connect. No Clawboo account, and Clawboo itself sends no telemetry. The server also fetches the Marketplace catalog from GitHub, OpenRouter's public model list and the latest version from npm, and the dashboard loads its font from Google Fonts and its GitHub star count; [Production defaults](/operating/production-defaults#what-leaves-the-machine) lists each one. Claude Code, Codex and Hermes run with their own sandboxes and approval prompts turned off, so they can read and change any file your user account can. For work you do not trust, run Clawboo under a separate user account or in a virtual machine. See [What agents can do](/concepts/permissions). ## Related * [CLI reference](/reference/cli): start, stop and restart the server, find its port, and read its log. * [Environment variables](/reference/environment-variables): every variable the server reads. * [Limits and defaults](/reference/limits-and-defaults): every number, timeout and cap in one place. * [Troubleshooting](/help/troubleshooting): error messages and what to do about each one. # MCP servers Source: https://docs.claw.boo/operating/mcp-servers The four MCP servers Clawboo hosts, which runtimes get which, and how to attach an agent that Clawboo does not run. MCP (Model Context Protocol) is the standard way an agent calls outside tools. Clawboo hosts four MCP servers that let agents work as a team, and connects its own agents to them for you. Use this page to see which runtimes get which servers, or to attach an agent that Clawboo does not run, such as a Claude Code session you start yourself or your own MCP client. ## The four servers | Server | What an agent does with it | HTTP path | stdio command | | - | - | - | - | | Tasks | Reads and edits the board: lists, creates, claims and updates tasks | `/api/mcp/tasks` | `clawboo-mcp-tasks` | | Memory | Searches and saves shared facts | `/api/mcp/memory` | `clawboo-mcp-memory` | | Tools | Calls built-in tools and the connectors it has been granted | `/api/mcp/tools` | `clawboo-mcp-tools` | | TeamChat | Posts in and reads its team's peer room | `/api/mcp/teamchat` | `clawboo-mcp-teamchat` | TeamChat serves the team's peer room, where agents talk to each other; the dashboard does not show it. The [MCP tools reference](/reference/mcp-tools) lists every tool and its inputs. ## Which runtimes get which servers | Runtime | How it connects | What it gets | | - | - | - | | Clawboo Native | Inside the server, with no network hop | Memory, Tools, TeamChat and the read-only Tasks tools (`list_tasks`, `get_task`), by default | | Claude Code, Codex, Hermes | Over HTTP, with URLs Clawboo signs for each run | All four servers, bound to the run's team and agent | | OpenClaw | Over HTTP, with one set of URLs in the Gateway's config that all OpenClaw agents share | Tasks, Memory and Tools; no TeamChat | OpenClaw's URLs carry no agent identity, so OpenClaw agents attach as unsigned callers (see [What an attached agent can do](#what-an-attached-agent-can-do)). They read and save global memory only, cannot use `claim_task` or `assign_task`, and no agent's connector grants apply to them, so their connector calls ask you for approval. Only a Gateway on the same machine gets the attach token; a Gateway on another machine cannot reach these URLs, which point at `127.0.0.1`. ## Attach an outside agent Any MCP client can attach. Pick a transport first: | | HTTP | stdio | | - | - | - | | How it runs | The agent connects to the running Clawboo server | The agent starts a Clawboo bin as a child process | | Where the agent runs | On the same machine, or through an SSH tunnel | On the same machine | | Who the agent acts as | Set by the signed URL | Whoever each call names: the bin trusts you, the operator | | Database | The server's | Set `CLAWBOO_DB_PATH`; the snippet does it for you | The steps below attach over HTTP with Claude Code. The same snippet comes in Codex and OpenClaw formats, and any other client can use the URL. For stdio, see [The stdio bins](#the-stdio-bins). **1. Choose a team and an agent id** Find the team the outside agent works for, and choose an agent id for it that no Clawboo agent uses, such as `external-claude` (the warning below says why). List the teams, and the agents that already exist (replace `18790` with your server's port, which is in `~/.clawboo/api-port.txt`): ```bash theme={"theme":"github-dark"} curl -s http://127.0.0.1:18790/api/teams curl -s http://127.0.0.1:18790/api/agents ``` Each team has an `id`. Each agent has an `id`, a `displayName` and a `teamId`. With an access token set, add `-b "clawboo_access=$STUDIO_ACCESS_TOKEN"` to these requests and to the next one. **2. Get a signed attach URL** Ask Clawboo for the snippet. The team must exist, and an id that belongs to a Clawboo agent must be on that team. An id that no Clawboo agent uses is signed as you typed it, so copy it exactly. ```bash theme={"theme":"github-dark"} curl -s 'http://127.0.0.1:18790/api/mcp/config?runtime=claude-code&server=tasks&scopeTeamId=<team-id>&scopeAgentId=<agent-id>' ``` ```json theme={"theme":"github-dark"} { "ok": true, "config": { "id": "clawboo-tasks", "transport": "http", "snippet": "claude mcp add --transport http clawboo-tasks 'http://127.0.0.1:18790/api/mcp/tasks?scopeTeamId=<team-id>&scopeAgentId=<agent-id>&scopeSig=<signature>&attachToken=<token>'", "structured": { "clawboo-tasks": { "type": "http", "url": "http://127.0.0.1:18790/api/mcp/tasks?scopeTeamId=<team-id>&scopeAgentId=<agent-id>&scopeSig=<signature>&attachToken=<token>" } } } } ``` `snippet` is the line to paste. `structured` is the same server as an object, for clients you configure with JSON. | Param | Default | Values | | - | - | - | | `server` | `tasks` | `tasks`, `memory`, `tools`, `teamchat`. Anything else returns `400` with `unknown server: <x>`. | | `runtime` | `claude-code` | `claude-code`, `codex` or `openclaw` sets the snippet's format. Any other value returns no `config`. | | `transport` | `http` | `http`. Any other value returns a stdio snippet. | | `scopeTeamId` | none | The team the agent works for. An unknown team returns `400` with `unknown team: <id>`. | | `scopeAgentId` | none | The agent id it acts as. TeamChat needs both ids. A Clawboo agent from another team returns `400`. | **3. Add it to your agent** Paste the snippet. Keep the URL in single quotes: an unquoted `?` is a wildcard in zsh, and an `&` ends the command in any shell. ```bash theme={"theme":"github-dark"} claude mcp add --transport http clawboo-tasks 'http://127.0.0.1:18790/api/mcp/tasks?scopeTeamId=<team-id>&scopeAgentId=<agent-id>&scopeSig=<signature>&attachToken=<token>' ``` For Codex, the snippet is a `[mcp_servers.clawboo-tasks]` entry for `~/.codex/config.toml`. Other clients take the `url` from `structured`. **4. Repeat for each server it needs** Each server has its own URL, so get one for each of `server=memory`, `server=tools` and `server=teamchat` that the agent should use. You should see the servers in your agent's list (in Claude Code, `claude mcp list`). > [!WARNING] > > Do not sign the URL for the id of an agent Clawboo also runs. The outside agent would take that agent's team notices: each tool result carries up to 5 of them and marks them delivered, so the Clawboo agent never sees them. A new id of your own avoids this, and it shows as the author of the outside agent's peer-room posts. ## What an attached agent can do What an agent may do depends on the URL it attaches with. A URL counts as signed only when its `scopeSig` matches the ids in it. A URL without a signature, or with any id edited by hand, is served as unsigned, and so is the default snippet you get without `scopeTeamId` or `scopeAgentId`. | Server | Unsigned URL | Signed for a team | Signed for a team and an agent | | - | - | - | - | | Tasks | Every team's board. Every tool except `claim_task` and `assign_task`; comments post as a plain agent comment. | That team's board only, with every tool. | The same, and the agent's undelivered team notices arrive with tool results. | | Memory | Global facts only. Saves go to the global tier, with no author recorded. | Team and global facts. Saves are shared with the team. | The same, plus that agent's own facts. | | Tools | Built-in tools. Connectors granted to an agent or a team do not apply. | Adds connectors granted to the team. | Adds connectors granted to that agent. | | TeamChat | No tools. | No tools: TeamChat needs an agent too. | Posts and reads as that agent in the team's peer room. | A stdio bin is different: it runs as you, the operator. Tasks, Memory and TeamChat take the team and agent from each call's arguments, and Tasks offers all of its tools. ## The attach token Every URL from `GET /api/mcp/config` carries an `attachToken`, the same value for every URL on an install. It matters once you set `STUDIO_ACCESS_TOKEN`: * A request to one of the four server paths without the access cookie is let in only when its token is valid and it came straight to the server's loopback port: from `127.0.0.1` or `::1`, with none of the headers a proxy adds (`Forwarded`, `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Proto`, `X-Real-IP`). * `/api/mcp/config` always needs the cookie, because it signs the identity you ask for. So does `POST /api/mcp/attach-secret/rotate`. * Treat an attach URL like a password. Anyone who has it and can reach the loopback port can use the servers as the identity it carries. With no access token set, the bind is the only boundary: anything that can reach the server's port can use these servers and `/api/mcp/config`. > [!NOTE] > > The attach token, and the rule that an unsigned TeamChat session gets no tools, ship in the next release after 0.5.0. On 0.5.0 and older, every request from this machine reaches `/api/mcp/` without the cookie, `/api/mcp/config` included, and an unsigned TeamChat session takes its author and room from tool arguments. If a reverse proxy on the same machine fronts one of those releases, deny all of `/api/mcp/` at the proxy. See [Security](/operating/security). > [accordion] If an attach URL leaks > > The token and the signatures never change on their own. To replace them, ask Clawboo to make a new signing secret. The server keeps running. With an access token set, add `-b "clawboo_access=$STUDIO_ACCESS_TOKEN"`: > > ```bash theme={"theme":"github-dark"} > curl -s -X POST http://127.0.0.1:18790/api/mcp/attach-secret/rotate > ``` > > You should see `"ok":true` and a `gateway` value. Every URL you copied stops working at once, so get new ones from `/api/mcp/config`. Clawboo's own runtimes get new URLs on their next run. `gateway` says what happened to a local OpenClaw Gateway's URLs: `updated` means it has the new ones; `retrying` means it did not take them yet and Clawboo asks again over the next few minutes; `not-connected` means it gets them the next time Clawboo connects to it. If it still has the old ones after that, restart the Clawboo server so it connects again. A run already in progress keeps its old URLs: for the rest of that run Clawboo's servers treat it as an unsigned caller, or refuse it when an access token is set. ## Agents on another machine or in a container HTTP attach is built for agents on the same machine as Clawboo. For an agent somewhere else: * **Another machine.** Open an SSH tunnel to the Clawboo machine and keep the same port on both ends, so the attach URL works unchanged. The tunnel delivers the agent's requests to the loopback port directly, so the attach token counts. ```bash theme={"theme":"github-dark"} ssh -N -L 18790:127.0.0.1:18790 you@clawboo-host ``` * **A container.** On Linux, a container started with `--network host` connects from the machine's own loopback address, and the token counts. A container on its own network connects from another address, so it needs the access cookie on every request (for Claude Code, `--header "Cookie: clawboo_access=<token>"`), a server that listens beyond loopback, and the host name it uses (such as `host.docker.internal`) in `CLAWBOO_ALLOWED_HOSTS`. * **Through your reverse proxy.** The proxy's headers make Clawboo refuse the token, so the agent needs the access cookie, and the proxy must pass `/api/mcp/`. If your proxy denies that path, use an SSH tunnel instead. [Reach Clawboo from another machine](/guides/self-host-securely) covers tunnels, wide binds and proxies. ## The stdio bins The four bins come with the `clawboo` npm package, which needs Node.js 22.12 or newer. A bin talks over its standard input and output, so it needs no running server and no access token. It opens the database named by `CLAWBOO_DB_PATH`, which by default is a different file from the server's (see [Data and state](/operating/data-and-state)). The stdio snippet sets it for you: ```bash theme={"theme":"github-dark"} curl -s 'http://127.0.0.1:18790/api/mcp/config?runtime=claude-code&server=tasks&transport=stdio' ``` For Claude Code the snippet looks like this: ```bash theme={"theme":"github-dark"} claude mcp add clawboo-tasks -e CLAWBOO_DB_PATH=/Users/you/.clawboo/clawboo.db -- node /path/to/clawboo/dist/bin/tasks.js ``` With a global npm install the bins are on your `PATH` too, so `clawboo-mcp-tasks` can replace the `node` command. ## Check it works * **List the tools.** In your agent, list the tools `clawboo-tasks` offers. A signed URL offers 12; an unsigned one offers 10, without `claim_task` and `assign_task`. * **Same board.** Create a task in the dashboard, then have the agent call `list_tasks`. The task should appear. If it does not appear over stdio, check `CLAWBOO_DB_PATH`. * **Peer room.** After the agent calls `team_chat_post`, read the peer room with `GET /api/team-chat?teamId=<team-id>`. The post should be there under the agent id you signed. `team_chat_subscribe` never returns an agent's own posts, and it marks teammates' posts `isUser=false` so they read as evidence, not instructions; see [Peer chat](/concepts/peer-chat). > [accordion] Talk to a server with curl > > A raw client must send `Accept: application/json, text/event-stream` on every POST, or the server answers `406`. The first request must be `initialize`; its response carries an `mcp-session-id` header that every later request repeats. Replies arrive as server-sent events, on lines that start with `data:`. > > ```bash theme={"theme":"github-dark"} > URL='http://127.0.0.1:18790/api/mcp/tasks?attachToken=<token>' > > curl -si -X POST "$URL" \ > -H 'Content-Type: application/json' \ > -H 'Accept: application/json, text/event-stream' \ > -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' > > curl -s -X POST "$URL" \ > -H 'Content-Type: application/json' \ > -H 'Accept: application/json, text/event-stream' \ > -H 'mcp-session-id: <session-id>' \ > -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' > > curl -s -X POST "$URL" \ > -H 'Content-Type: application/json' \ > -H 'Accept: application/json, text/event-stream' \ > -H 'mcp-session-id: <session-id>' \ > -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' > ``` > > This URL carries no scope, so the list has no `claim_task` or `assign_task`. ## Troubleshooting > [accordion] 401 Clawboo access token required, on an MCP URL > > **What it means:** the access token is set, and the request had no cookie and no valid attach token, or it did not come straight to the loopback port. > > **Fix:** copy the URL again from `/api/mcp/config` (sending the cookie). A URL from another install, from before a data reset, or from a version without attach tokens has no valid token. Point the agent at `127.0.0.1` directly, not through a proxy. If the agent's environment sets `HTTP_PROXY` or `HTTPS_PROXY`, add `127.0.0.1` and `localhost` to its `NO_PROXY`. > [accordion] 406 Not Acceptable > > **What it means:** a POST did not accept both `application/json` and `text/event-stream`. > > **Fix:** send `Accept: application/json, text/event-stream`. MCP client libraries do this for you. > [accordion] 400 No valid session; send an initialize request first. > > **What it means:** the first request was not `initialize`, or the session id is unknown. Sessions live in the server's memory, so a server restart ends every session. > > **Fix:** send `initialize` again, or reconnect the client. > [accordion] 400 Invalid or missing MCP session id. > > **What it means:** a GET or DELETE named no live session. > > **Fix:** open a session with `initialize` first, and send its `mcp-session-id`. > [accordion] claim\_task is missing, or TeamChat lists no tools > > **What it means:** the URL is unsigned. TeamChat also stays empty for a URL signed with a team but no agent. > > **Fix:** get the URL from `/api/mcp/config` with `scopeTeamId`, and with `scopeAgentId` for TeamChat. Do not edit the ids in a signed URL. > [accordion] A stdio agent sees an empty board > > **What it means:** the bin opened its default database, not the server's. > > **Fix:** set `CLAWBOO_DB_PATH` to the server's `clawboo.db`, or use the stdio snippet, which sets it. > [accordion] 400 unknown team, or agent is not on team, from the config call > > **What it means:** `scopeTeamId` names no team, or `scopeAgentId` is a Clawboo agent that belongs to another team. > > **Fix:** copy the team's `id` from `GET /api/teams`. For an outside agent, choose an id no Clawboo agent uses; for one of your agents, pass its own team. > [accordion] The config call returns ok but no config > > **What it means:** `runtime` is not `claude-code`, `codex` or `openclaw`. > > **Fix:** ask for one of those three formats and take the `url` from `structured`. > [accordion] 403 Cross-origin request blocked. > > **What it means:** the request named a host that the same-origin guard does not allow, such as a container's `host.docker.internal`. > > **Fix:** add that host name to `CLAWBOO_ALLOWED_HOSTS` and restart the server. > [accordion] Under the hood > > * Every URL starts with the server's own loopback address and port, never the request's `Host` header, so a forged `Host` cannot point an agent somewhere else. > * Tasks, Memory and Tools URLs carry `scopeTeamId`, `scopeAgentId` and `scopeSig`. TeamChat URLs carry `roomTeamId`, `postAuthorAgentId` and `scopeSig`; for its own orchestrated runs Clawboo adds `delegate=1` inside the signature, which unlocks `team_delegate`. Memory URLs can also carry run details (`provRuntime`, `provTaskId`, `provSessionKey`) signed by `provSig`; they never widen what the agent can read. > * `/api/mcp/config` never writes `scopeTenantId`, and adding one by hand breaks the signature. It checks the team against the `teams` table and an agent id against the `agents` table. > * The signing secret lives in the database (`settings` key `mcp-attach-scope-secret`), so a data reset or a database from another install changes every token and signature, and so does `POST /api/mcp/attach-secret/rotate`. > * The request log masks `attachToken`, `scopeSig` and `provSig`. ## Related * [MCP tools reference](/reference/mcp-tools): every tool and its inputs. * [Tools and MCP API](/reference/rest-api/tools-and-mcp): the request and response shapes for these routes. * [Security](/operating/security): the access gate, and what to deny at a reverse proxy. * [Reach Clawboo from another machine](/guides/self-host-securely): tunnels, wide binds, proxies and the access token. * [Data and state](/operating/data-and-state): the two database paths, and where the signing secret lives. * [Peer chat](/concepts/peer-chat): how agents use the peer room that TeamChat serves. # Production defaults Source: https://docs.claw.boo/operating/production-defaults What a fresh Clawboo install allows and limits out of the box, and how to change each default before you run it for a team. Use this page before you run Clawboo for anyone but yourself. It lists the defaults that decide who can reach the server, how much it accepts, what limits spending, and what leaves the machine, with how to change each one. Every other number, such as breaker thresholds and timeouts, is in [Limits and defaults](/reference/limits-and-defaults). ## At a glance | Default | Out of the box | How to change it | | - | - | - | | Network bind | `127.0.0.1`: only this machine can connect | Set `HOST`, for example `HOST=0.0.0.0`. See [Reach Clawboo from another machine](/guides/self-host-securely). | | Port | The first free port from 18790 to 18809 | `CLAWBOO_API_PORT` pins one port (startup fails if it is taken). `CLAWBOO_API_PORT_START` moves the scan. | | Access token | Off: anyone who can reach the server can use it | Set `STUDIO_ACCESS_TOKEN`, using only letters, digits, `.`, `_`, `~` and `-`. Any other character stops the server at startup. | | Wide bind without a token | The server refuses to start | Set a token. `CLAWBOO_ALLOW_INSECURE=1` starts it with no protection. | | Same-origin guard | On: API calls must arrive under a local name (`localhost`, a `127.x` address, `::1`, or the bind address) | `CLAWBOO_ALLOWED_HOSTS` and `CLAWBOO_ALLOWED_ORIGINS` add names. Nothing turns the guard off. | | Path prefix | The dashboard is served at `/` | `CLAWBOO_BASE_PATH`, for example `/clawboo`. | | API rate limit | 3,000 requests a minute per client address, across `/api` | Fixed. | | Sensitive-route limit | 60 requests a minute per client address, on 26 routes | Fixed. | | Request body | JSON up to 2 MB | Fixed. | | Budgets | None exist. A new budget is **warn only** | Create one in **Settings > Insights > Governance** and choose **hard cap**. | | Install-wide spend cap | None | No setting. Cap each agent or team instead. | | Coding runtimes' sandboxes and approval prompts | Off for Claude Code, Codex and Hermes | No setting. For untrusted work, run Clawboo under a separate user account or in a VM. See [What agents can do](/concepts/permissions). | | Clawboo Native shell commands | Off for every agent | Turn on **Let this Boo ask to run commands** on the agent's **Permissions** tab. | | OpenTelemetry export | Off: traces stay in the local event log | `OTEL_EXPORTER_OTLP_ENDPOINT`. The npm package does not include the OpenTelemetry SDK; see below. | | Log level | `info` | `LOG_LEVEL`, for example `debug`. | | Data folder | `~/.clawboo` | `CLAWBOO_HOME`. See [Data and state](/operating/data-and-state). | | Vault master key | `secrets/master.key`, next to the encrypted vault | `CLAWBOO_SECRETS_MASTER_KEY`, set to the existing key. A new key makes every saved key unreadable; see [Security](/operating/security). | | Telemetry | None | Nothing to turn off. | | Requests Clawboo makes itself | The Marketplace catalog from GitHub and the latest version from npm (each cached 6 hours), and OpenRouter's public model list (cached 30 minutes). The dashboard also loads its font from Google Fonts and the GitHub star count. | `CLAWBOO_CATALOG_INDEX_URL` points the catalog elsewhere. The others have no setting. | The server reads environment variables when it starts, so restart it after you change one. **Settings > System Health** shows three of these defaults in its **Production defaults** card: the log level, the budget posture and observability. ## Request limits Clawboo counts requests per client address over a one-minute window, in memory. Over a limit it answers `429` with `Too many requests. Wait a moment and retry.`, or on a sensitive route `Too many requests for this operation. Wait a moment and retry.`, and sends standard `RateLimit-*` headers. The sensitive routes are the ones that install, connect, disconnect or sign out of a runtime; start a CLI sign-in; install, set up or edit OpenClaw, control its Gateway or approve a device; update Clawboo; connect, authorize, configure or disconnect a connector, add or remove a custom connector, or set the Composio key; change a native agent's shell switch; revoke a command-allowlist entry; and reindex or install memory embeddings. The 60-a-minute limit applies on top of the general one. The address is the connection's own, and forwarding headers are ignored. Behind a reverse proxy on the same machine, every request comes from `127.0.0.1`, so all your users share one count. The dashboard's page files have a separate limit of 3,000 a minute and do not count against the API. ## Budgets A fresh install has no budgets, so nothing caps spend. To set a ceiling, open **Settings > Insights > Governance**, create a budget for an agent or a team, and choose **hard cap**. When spend reaches the cap, the budget pauses and that scope gets no new work; how fast a running agent stops depends on its runtime. A **warn only** budget tracks spend against its limit and never stops work. See [Budgets and spend](/using/budgets-and-spend) for what each runtime reports and how a cap stops each kind of run. There is no install-wide cap. The API also accepts a `tenant` scope, but nothing charges or enforces it, and the Governance form does not offer it. Through the API, a new budget saved without a `mode` is warn only, and an existing one keeps its mode. System Health's **budget posture** row always shows the shipped default, `track-and-warn`, not the budgets you create. ## OpenTelemetry Clawboo keeps traces in its local event log. To also send them to an OTLP collector, set `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`); `OTEL_SERVICE_NAME` defaults to `clawboo`. Traces cover runs that go through the task runner, which are agent routines and `POST /api/runtimes/:id/run`. Team chat turns produce none. The `clawboo` npm package does not include the OpenTelemetry SDK, so on an npm install the export does not start. System Health's **OTel exporter** check then reports that the exporter failed to initialize, and traces stay local, even though the **observability** row reads that the exporter is active. Export works when you run Clawboo from a source checkout, where the SDK is installed. ## What leaves the machine Prompts go only to the providers and apps you connect. No Clawboo account, and Clawboo itself sends no telemetry. Besides your providers, the server makes three requests of its own. It fetches the Marketplace catalog from GitHub. It asks `registry.npmjs.org` for the latest Clawboo version when the dashboard checks for updates. And when the dashboard shows a model picker, it fetches OpenRouter's public model list, which sends no key. In your browser, the dashboard also loads its font from Google Fonts and its GitHub star count from `api.github.com`. ## Related * [Limits and defaults](/reference/limits-and-defaults): breaker thresholds, timeouts, caps and every other number. * [Reach Clawboo from another machine](/guides/self-host-securely): change the bind, set the token and the allowlists, and add a proxy. * [Security](/operating/security): what the access token, the guard and the vault protect against. * [Budgets and spend](/using/budgets-and-spend): set budgets and read what agents spend. * [Environment variables](/reference/environment-variables): every variable on this page and the rest. # Security model Source: https://docs.claw.boo/operating/security What protects a Clawboo install by default, what does not, where your keys are stored, and what changes when you expose the dashboard. Clawboo is built for one person on one computer. Out of the box it listens only on that computer and turns away web pages that try to reach it. This page explains what that protects, what it does not, where your keys are stored, and what changes when you open the dashboard to other machines. To set up remote access, follow [Reach Clawboo from another machine](/guides/self-host-securely). > [!WARNING] > > **Security advisory: clawboo 0.5.0 and older behind a reverse proxy.** On these releases the access token does not cover `/api/mcp/`. Any request that reaches Clawboo from `127.0.0.1` skips the access cookie on every `/api/mcp/` path, including `/api/mcp/config`. A reverse proxy on the same machine (nginx, Caddy, a Cloudflare Tunnel connector) delivers every request from `127.0.0.1`. So anyone who can reach the proxy can use Clawboo's MCP servers: your boards, memory, team chat and tools. They can also ask `/api/mcp/config` for an attach URL signed as any of your agents, with that agent's connector grants. This holds even with `STUDIO_ACCESS_TOKEN` set. > > If that is your setup, deny all of `/api/mcp/` at the proxy now with the rule below, or upgrade once a release newer than 0.5.0 is out. Run `clawboo --version` to see your version. > [tab] nginx > > ```nginx theme={"theme":"github-dark"} > # Inside the server block that proxies to Clawboo. > location ~* ^/api/mcp(/|$) { > return 404; > } > ``` > [tab] Caddy > > ```caddyfile theme={"theme":"github-dark"} > # Inside the site block, next to reverse_proxy. > @mcp path /api/mcp /api/mcp/* > respond @mcp 404 > ``` > [tab] Cloudflare Tunnel > > ```yaml theme={"theme":"github-dark"} > # config.yml: the deny rule must come before the rule that reaches Clawboo. > ingress: > - hostname: dash.example.com > path: '(?i)^/api/mcp(/|$)' > service: http_status:404 > - hostname: dash.example.com > service: http://127.0.0.1:18790 > - service: http_status:404 > ``` All three rules ignore letter case, because 0.5.0 also routes variants such as `/API/MCP/config`. Under a path prefix, add the same rule for the prefixed path (`/clawboo/api/mcp`), because Clawboo also answers `/api/` without the prefix. Releases after 0.5.0 let a request without the cookie reach the MCP servers only with an attach token (see [The access token](#the-access-token)). Keep the deny rule anyway, as a second layer. ## What is protected by default, and what is not | Who could reach Clawboo | Protected by default? | What protects it | | - | - | - | | Another computer on your network | Yes | Clawboo listens only on `127.0.0.1` | | A web page open in your browser | Yes | The same-origin guard | | Anyone who can reach a reverse proxy in front of Clawboo | No | The access token, or a proxy that authenticates every request | | Another user account on the same computer | Partly | Your Clawboo folder and the OpenClaw key files Clawboo writes are readable only by you; the API needs the access token | | Agents, connectors and other programs running as you | No | Nothing in Clawboo can fully stop them (see [What agents can do](#what-agents-can-do-on-your-machine)) | The last three rows share one cause: anything that reaches Clawboo from `127.0.0.1` looks like you. Without `STUDIO_ACCESS_TOKEN`, it can call every `/api/` route, including the ones that answer approvals, grant connector access and start agent runs. So treat approvals and connector grants as controls against a confused or prompt-injected model, not against other code on your machine. Setting the token, even on a loopback-only install, closes the API to other user accounts. The coding runtimes and connectors Clawboo starts never receive the token, but a program running as you can still read your files, including Clawboo's database and key vault. ## The loopback bind Clawboo listens on `127.0.0.1` unless you set `HOST`. `localhost`, `::1` and any `127.x.x.x` address also count as loopback. `HOSTNAME` is ignored, because Docker, systemd and many CI runners set it automatically; widening the bind takes an explicit `HOST`. To listen on the network, set `HOST=0.0.0.0`. Do not bind one network address such as `HOST=192.168.1.10`. Clawboo's own agents and background jobs connect to `127.0.0.1`, and a single-address bind does not listen there, so agents lose their Clawboo tools and `clawboo` cannot find the server. A non-loopback bind without a working access token refuses to start: the server logs `SECURITY: refusing to start` and exits with code 1. On a network, a script can send any `Host` or `Origin` header, so the same-origin guard cannot stop it and the token is the only authentication. `CLAWBOO_ALLOW_INSECURE=1` overrides the refusal and logs a `SECURITY:` warning instead. Use it only on a network where you would leave the dashboard open to everyone. ## The same-origin guard (always on) The guard stops web pages in your own browser from using Clawboo. Your browser can reach `127.0.0.1`, so without it any site you visit could send requests to the API or open the Gateway WebSocket. The guard checks every `/api/` request and the WebSocket upgrade at `/api/gateway/ws`, with or without an access token: * **Host** must be `localhost`, a `127.x.x.x` address, `::1`, the bind address, or a name in `CLAWBOO_ALLOWED_HOSTS`. This applies to every request, browser or not. It also blocks DNS rebinding, where an attacker's domain is pointed at `127.0.0.1`. * **Origin**, when the request has one, must exactly match an allowed origin. Those are `localhost`, `127.0.0.1` and `[::1]` at Clawboo's port (over `http` or `https`), the bind address at that port, and the entries in `CLAWBOO_ALLOWED_ORIGINS`. * **Sec-Fetch-Site**, when present, must be `same-origin`, `same-site` or `none`. A rejected request gets `403` with `{"error":"Cross-origin request blocked."}`, and a rejected WebSocket upgrade gets `403 Forbidden`. The dashboard page and its static files are not checked, because they hold no data. The two variables take comma-separated lists and only add entries; the loopback entries always stay. When you reach Clawboo by any other name or address, set both: `CLAWBOO_ALLOWED_HOSTS` for the name (`dash.example.com`) and `CLAWBOO_ALLOWED_ORIGINS` for the full origin (`https://dash.example.com`). Without them the dashboard page loads, but every API call fails with the 403 above. In dev mode (`pnpm dev`), the server also answers CORS requests, with credentials, for the allowed origins only. In dev those include the Vite dev server on port 5173. The guard is not authentication. A script or `curl` can send any `Host` and `Origin` it likes, which is why a network bind needs the access token. ## The access token `STUDIO_ACCESS_TOKEN` is an optional shared secret. Clawboo reads it from the environment when the server starts; a `studioAccessToken` entry in `settings.json` has no effect. When the token is set, every `/api/` path (in any letter case) and the Gateway WebSocket need the `clawboo_access` cookie. The dashboard page and static files load without it. > [!WARNING] > > **Use only letters, digits, `.`, `_`, `~` and `-` in the token.** With any other character the server refuses to start, on every bind: it logs `SECURITY: refusing to start`, names the character and the allowed set, and exits with code 1. `CLAWBOO_ALLOW_INSECURE=1` does not skip this, because it allows running with no token, not with one the check cannot use. Output from `openssl rand -base64` can contain `+`, `/` or `=`. Generate tokens with `openssl rand -hex 24` and save the value. On 0.5.0 and older, such a token turns the check off and the server keeps running, so behind a reverse proxy on the same machine the dashboard is open. **Sign in once per browser.** Open any Clawboo address with `?access_token=<token>` added, such as `https://dash.example.com/?access_token=<token>`. Clawboo checks the token, sets the cookie and redirects to the same address without the token. The cookie is `HttpOnly` and `SameSite=Lax`, its path is `/` (or your `CLAWBOO_BASE_PATH`), and it is `Secure` when the request carries `X-Forwarded-Proto: https`. The WebSocket accepts only the cookie. | Request | Answer | | - | - | | `?access_token=` with the right token | `302` redirect that sets the cookie | | `?access_token=` with a wrong token | `401` `{"error":"Invalid Clawboo access token."}` | | An `/api/` request without a valid cookie | `401` `{"error":"Clawboo access token required. Open /?access_token=<token> once to set a cookie."}` | **One secret, full access.** Everyone who has the token can do everything you can. There are no accounts, roles or per-person revocation. To rotate the token, change the variable and restart the server; every browser then signs in again. **Agents do not receive it.** Clawboo removes the token from the environment of the coding runtimes, Clawboo Native shell commands and connectors it starts. The exception is an OpenClaw Gateway that you start from the dashboard: it inherits the server's whole environment, token included. Clawboo's own agents reach the MCP servers another way: * **From the release after 0.5.0**, every MCP attach URL Clawboo writes for its agents carries an `attachToken`, derived from a per-install secret in Clawboo's database. A request without the cookie reaches the four MCP servers (`/api/mcp/tasks`, `/api/mcp/memory`, `/api/mcp/tools`, `/api/mcp/teamchat`) only when all of these hold: * it carries a valid attach token; * it connects directly from a loopback address; * it has none of the headers a proxy adds: `Forwarded`, `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Proto` or `X-Real-IP`. `/api/mcp/config` always needs the cookie. A leaked attach token is useless from another machine or through your proxy, and `POST /api/mcp/attach-secret/rotate` replaces every token and signature at once (see [MCP servers](/operating/mcp-servers#the-attach-token)). * **On 0.5.0 and older**, any request from `127.0.0.1` reaches all of `/api/mcp/` without the cookie. See the advisory at the top of this page. **The proxy warning.** With no token set, the first request that carries one of those proxy headers makes the server log a one-time `SECURITY:` warning. It says that the dashboard and every `/api/` route are open to anyone who can reach the proxy. The warning never blocks a request. **The `clawboo` command cannot manage a server that has a token.** Its status check gets a `401`, so `clawboo`, `clawboo stop` and `clawboo restart` report that a server is running and requires an access token, then exit. Run with the token in its environment and no server running, `clawboo` starts one but cannot see it answer: after about 45 seconds it prints `Dashboard is taking too long to start.` and exits, while the server keeps running. Start and stop a token-protected server directly or with a service manager, as described in [Run the Clawboo server](/operating/deployment). > [accordion] Under the hood > > * The token and the cookie value are each hashed with SHA-256 and compared in constant time, so the check leaks neither timing nor length. > * The token check lowercases the path before it tests for `/api/`, and the API routes match letter case exactly, so `/API/settings` cannot slip past it. > * The attach token is a hex HMAC-SHA256 keyed with a 32-byte secret stored in the database (`settings` key `mcp-attach-scope-secret`). It is the same in every attach URL on an install. Attach URLs that bind a team or an agent also carry a `scopeSig` signature over that scope. > * When the server's environment sets `HTTP_PROXY`, `HTTPS_PROXY` or `ALL_PROXY`, Clawboo adds `127.0.0.1`, `localhost` and `::1` to `NO_PROXY` for the agents it starts. Their MCP calls then never pass through that proxy. > * The request log masks `attachToken`, `scopeSig` and `provSig` in every URL it records. ## Keys and secrets on disk Clawboo encrypts the keys it stores for itself, but some copies are plain text. Paths below use the defaults; `CLAWBOO_HOME` moves `~/.clawboo` and `OPENCLAW_STATE_DIR` moves `~/.openclaw`. | What | Where | Encrypted | | - | - | - | | Keys you connect on a **Runtimes** card; connector credentials, sign-ins and Composio key | The vault: `~/.clawboo/secrets/runtime-keys.json`, with its key in `secrets/master.key` | Yes | | Keys you connect under **Providers** | The vault, plus OpenClaw's `~/.openclaw/.env` and every OpenClaw agent's `auth-profiles.json` | The vault copy only | | The Gateway token and the provider key that OpenClaw setup writes | `~/.openclaw/.env`, and the agents' `auth-profiles.json` | No | | The Gateway address and token Clawboo connects with | `~/.clawboo/settings.json` | No | | The secret that signs MCP attach URLs, and every board, chat and memory | `~/.clawboo/clawboo.db` | No | | The Gateway device key ([below](#the-openclaw-gateway-device-key)) | `~/.clawboo/proxy-device-identity.json` | No | Clawboo writes the Providers copies so the OpenClaw Gateway can use the same key. **Disconnect** under **Providers** removes the vault copy and the OpenClaw copies. A key you connect on a Runtimes card stays in the vault. For which runtime reads which key, and what each Disconnect removes, see [Providers and runtimes](/using/providers-and-runtimes). **The plain-text files are owner-only.** Clawboo writes `.env`, each `auth-profiles.json` and `settings.json` readable only by you (mode `0600`), and narrows an existing file to `0600` before it writes new contents. If it cannot narrow a file that others can read (one another user owns, say), it refuses to write the key into it. When Clawboo creates `~/.clawboo` or `~/.openclaw`, it makes them owner-only (`0700`); an existing `~/.openclaw` keeps its mode. A file that 0.5.0 or older wrote keeps its mode until Clawboo writes it again. Programs running as you can still read these files. Modes are best-effort on Windows. **What the vault protects.** Each value is encrypted with AES-256-GCM. The key sits next to the ciphertext, so the encryption protects `runtime-keys.json` when that file is copied or shared on its own. Anything that can read your Clawboo folder can read the key too. A wrong or missing key never yields part of a value: saved keys read as missing, and System Health reports that the master key changed. **Keeping the key elsewhere.** `CLAWBOO_SECRETS_MASTER_KEY` supplies the key from the environment instead of `master.key`. Move your existing key rather than creating a new one, or every saved key becomes unreadable: **1. Stop Clawboo** Stop the server with `clawboo stop`, Ctrl+C in its terminal, or your service manager. **2. Move the key out of the Clawboo folder** ```bash theme={"theme":"github-dark"} mkdir -p ~/.config/clawboo mv ~/.clawboo/secrets/master.key ~/.config/clawboo/master.key chmod 600 ~/.config/clawboo/master.key ``` **3. Start Clawboo with the key in its environment** ```bash theme={"theme":"github-dark"} export CLAWBOO_SECRETS_MASTER_KEY="$(cat ~/.config/clawboo/master.key)" ``` Then start the server as usual, with this variable set every time. If it is missing, Clawboo creates a new key and cannot read the keys you saved before. A value that is not a 32-byte key (as base64, 64 hex characters, or 32 plain characters) makes every save fail and every saved key read as missing. This keeps a copy or backup of your Clawboo folder from carrying the key. It does not stop a program running as you, which can read both files. **Treat the database and its backups as secrets.** `clawboo.db` holds every board, chat and memory, and the secret that signs MCP attach URLs. Anyone with a copy can sign attach URLs as any of your agents. Clawboo keeps the database and your Clawboo folder readable only by you (file modes are advisory on Windows). `clawboo backup` writes its file readable only by you (mode `0600`) from the first byte. A backup made with 0.5.0 or older has your default permissions, so restrict it with `chmod 600`. See [Data and state](/operating/data-and-state). ## What agents can do on your machine Agents work with your user account's permissions. Claude Code, Codex and Hermes run with their own sandboxes and approval prompts turned off, so they can read and change any file you can, run commands and reach the network. A task's git worktree keeps its file changes apart, but it is not a sandbox. A run without a worktree starts in its team's folder (`~/.clawboo/workspaces/<team id>`). That covers every team-chat turn, every one-to-one chat with these agents, research and review tasks, and every Routine you make in the dashboard. The folder only sets where the run starts: from there these runtimes can still reach Clawboo's database and key vault. Clawboo Native agents run a shell command only when their **Let this Boo ask to run commands** switch is on and you approve that command. Coding runtimes and connectors do not inherit Clawboo's own secrets from the environment (an OpenClaw Gateway that Clawboo starts does), but they can read credential files on disk. Run untrusted work under a separate user account or in a virtual machine. [What agents can do](/concepts/permissions) has the details for each runtime. ## The OpenClaw Gateway device key When Clawboo connects to an OpenClaw Gateway, its server signs the handshake with an Ed25519 device key stored in `~/.clawboo/proxy-device-identity.json`, readable only by you. The key identifies Clawboo to the Gateway and nothing else: it plays no part in signing in to the dashboard, which is the access token's job. The Gateway token stays on the server too. The browser never receives it, and `GET /api/settings` reports only whether one is set. To connect a Gateway, see [OpenClaw](/runtimes/openclaw). ## Redaction in responses and logs Clawboo masks credential-looking text at two points: * **Before saving.** Tool-call records, memory facts, governance audit entries and observability events have recognized secrets replaced with `[REDACTED]` before they are written. The command text of an OpenClaw command approval is stored as written. * **Before showing or logging.** Observability, audit, tool-call and System Health data is masked with `••••` before it is sent to the browser. So are the structured fields of every server log line. The masking covers values under keys that contain words such as `token`, `secret`, `password`, `auth`, `credential` or `cookie`, though token counts are kept. It also covers strings shaped like common API keys, `Bearer` headers, JWTs, private-key blocks and `NAME_KEY=value` lines. It is a safety net built on known formats, so a key in a new format can slip through. ## Rate limits Clawboo limits how many requests one client address can make per minute. Every `/api/` request counts toward a high general ceiling. Routes that install software, change runtime or OpenClaw setup, connect connectors or runtimes, start sign-ins, approve devices or update Clawboo have a much lower one. Over a limit, requests get `429` with a `Retry-After` header. The limits count the connection's own address and ignore `X-Forwarded-For`, so behind a reverse proxy every visitor shares one allowance. They stop runaway clients; they are not access control. The numbers are in [Limits and defaults](/reference/limits-and-defaults). ## Reaching Clawboo from another machine The rules in short; [Reach Clawboo from another machine](/guides/self-host-securely) has the steps. 1. **Prefer an SSH tunnel.** Clawboo keeps listening on `127.0.0.1`, and SSH does the authentication. 2. **Put the token behind every reverse proxy.** Set `STUDIO_ACCESS_TOKEN` unless the proxy authenticates every request itself. Also set `CLAWBOO_ALLOWED_HOSTS` and `CLAWBOO_ALLOWED_ORIGINS`, forward `Host` and `X-Forwarded-Proto`, and deny `/api/mcp/` at the proxy. 3. **For a network bind, use `HOST=0.0.0.0` with a token**, plus the two allowlists for the address people use. Plain `http://` sends the cookie unencrypted, so put TLS in front unless you trust the network. > [!WARNING] > > **A reverse proxy is not authentication.** nginx, Caddy and a Cloudflare Tunnel connector deliver every request from `127.0.0.1`, so to Clawboo the whole internet looks like a local caller. Without `STUDIO_ACCESS_TOKEN`, the dashboard and every `/api/` route are open to anyone who can reach the proxy, and the server starts without complaint. Set the token, or make the proxy authenticate every request (HTTP basic auth, an SSO proxy such as oauth2-proxy, Cloudflare Access). ## Limits of this model * **One person, one secret.** There are no user accounts, roles or per-team permissions, and the token gives full access. * **One store for the whole install.** All teams share one database and one vault, and each key name holds one value for the whole install. * **Local code is trusted.** Anything running as your user can read Clawboo's files and, without the token, use its API. ## Related * [Reach Clawboo from another machine](/guides/self-host-securely): SSH tunnels, reverse proxies and network binds, step by step * [What agents can do](/concepts/permissions): sandboxes, worktrees and approvals for each runtime * [Providers and runtimes](/using/providers-and-runtimes): connecting keys, and what Disconnect removes * [Data and state](/operating/data-and-state): every file Clawboo writes, backups and resets * [Environment variables](/reference/environment-variables): `HOST`, `STUDIO_ACCESS_TOKEN`, the allowlists and the rest * [Known issues](/appendices/known-issues): open problems, including the advisory above # CLI reference Source: https://docs.claw.boo/reference/cli Every clawboo command and flag: start the dashboard, stop or restart its background server, back up the database, and run the MCP stdio bins. The `clawboo` command starts the dashboard and manages the server behind it. Use this page to look up a command or flag, what it prints, and when it exits with an error. | Command | What it does | | - | - | | `clawboo` | Find or start the dashboard server, then open it in your browser | | `clawboo stop` | Stop the running dashboard server | | `clawboo restart` | Stop the server and start a fresh one on the same port | | `clawboo backup [dest]` | Write a one-file copy of the database, safe while the server runs | | `clawboo --version` | Print the CLI version (`-V` for short) | | `clawboo --help` | Print usage (`-h` for short) | | `clawboo-mcp-*` | Four MCP servers over stdio, for agent programs you set up yourself | Install once with `npm install -g clawboo`, then run `clawboo`. For a one-off run without installing, use `npx clawboo@latest`. Every command checks your Node version first: on Node older than 22.12 it prints "Clawboo requires Node.js 22.12 or newer", your version and how to switch, then exits with code 1. ## clawboo Run with no subcommand, `clawboo` gets you to a working dashboard: 1. It prints the logo and `Clawboo v<version>`. 2. It checks whether an OpenClaw Gateway answers on `localhost:18789` and says so. This is information only; the dashboard handles Gateway setup either way. 3. It looks for a running dashboard (see [Port discovery](#port-discovery)). 4. If it finds one, it compares that server's version with its own and may offer a restart (see [Version check](#version-check)). 5. If none is running, it starts one in the background and waits up to about 45 seconds for it to answer. 6. It opens the dashboard in your browser, prints "Clawboo opened at" with the URL, and lists next steps. The URL is `http://localhost:<port>`, or `http://localhost:<port><prefix>/` when you set `CLAWBOO_BASE_PATH`. The browser opens with `open` on macOS, `start` on Windows and `xdg-open` elsewhere. | Flag | Effect | | - | - | | `-V`, `--version` | Print the version and exit | | `-h`, `--help` | Print usage and exit | | `--no-version-check` | Attach to a running server without comparing versions | | `-y`, `--yes` | Restart an older running server without asking | `--no-version-check` and `-y` apply to `clawboo` itself. `clawboo restart` takes `--no-open`, `clawboo backup` takes `-f, --force`, and `clawboo stop` takes no flags. A mistyped subcommand, such as `clawboo stopp`, prints `error: unknown command 'stopp'` with the help text and exits 1 instead of starting anything. ### Version check A server keeps running the code it started with, so after an upgrade the running server can be older than the CLI you just installed. Before attaching, `clawboo` asks the server for its version (`GET /api/system/self-version?local=1`, with a 2-second timeout). The `local=1` part tells the server to skip its npm lookup, so this check stays on your machine. * It offers a restart only when both versions are real releases and the CLI is newer. It never offers a downgrade, and a source checkout (version `0.0.0-dev`) is never compared. * The prompt reads "Restart it on port N to run vX?" and defaults to yes. `-y` answers yes for you. * Declining, or pressing Ctrl-C at the prompt, keeps the running server and opens it. * When stdin or stdout is not a terminal, it never prompts. It attaches and tells you to run `clawboo restart` or pass `-y`. * If stopping the old server fails, it prints why and a manual stop command, then continues with the running server. * If it stopped the old server and the new one never answers, it prints "The previous server was stopped and could not be replaced. Nothing is running now." and exits 1. This check compares the server with your CLI. The **Update available** chip in the dashboard is a separate check against npm, described in the [System API](/reference/rest-api/system). ## Port discovery `clawboo`, `clawboo stop` and `clawboo restart` all find the server the same way, so they act on the same instance: 1. **`CLAWBOO_API_PORT`**, when set to a valid port (1-65535): only that port is checked. 2. **The port file** `<CLAWBOO_HOME>/api-port.txt` (default `~/.clawboo/api-port.txt`), which the server writes when it starts listening. 3. **The default range**, ports 18790 through 18809. Each candidate gets a quick TCP check, then a `GET /api/settings`. Only a reply with Clawboo's JSON shape counts, so another service listening in that range (OpenClaw's helper ports, a Chrome debugging port) is never mistaken for the dashboard. A server you started with a different `CLAWBOO_API_PORT_START` is found only through the port file. `clawboo backup` does not look for a server at all. ## The background server and its log The server runs detached: it keeps running after `clawboo` exits or you close the terminal. Stop it with `clawboo stop`. * **Normal install.** The CLI starts the bundled server with `~/.clawboo` as its working directory (created with owner-only permissions if missing) and `NODE_ENV=production`. * **Source checkout.** With no bundled server, it runs `npx tsx apps/web/server/index.ts` from the repository root, found by walking up to a `package.json` named `clawboo` (`CLAWBOO_SERVER_PATH` overrides the search). With neither, it prints "Could not find the Clawboo server. Install with: npm install -g clawboo" and exits 1. * **Log.** The server's output goes to `~/.clawboo/server.log`, with a header line `--- clawboo v<version> · pid <pid> ---` for each launch. A log over 5 MiB is renamed to `server.log.1` at the next launch. If the log cannot be opened, the output is discarded. * **Slow start.** The CLI checks for the dashboard every 500 ms, 90 times (about 45 seconds). If it never answers, the CLI prints "Dashboard is taking too long to start.", the last 12 lines of the log and the log's path, then exits 1. It never starts a second server. * **After an in-app update.** A server that the dashboard's update restarted writes nothing to this log. Run `clawboo restart` to get logging back. ## clawboo stop ```bash theme={"theme":"github-dark"} clawboo stop ``` Stops the dashboard server that `clawboo` would open. To target one port, run `CLAWBOO_API_PORT=<port> clawboo stop`. 1. It finds the server through [port discovery](#port-discovery) and confirms the port really is a Clawboo dashboard. 2. It looks up the process listening on that port (`lsof` on macOS and Linux, `netstat -ano` on Windows) and sends it SIGTERM. 3. It waits up to 4 seconds for the port to close. If it is still open, it checks that the same process still holds it, sends SIGKILL, and waits up to 2 more seconds. 4. It prints "Clawboo stopped (pid N)", or "(pid N, forced)" when SIGKILL was needed. With nothing running, it prints "No Clawboo server is running.", removes a leftover `api-port.txt` that names a dead server, and exits 0, so it is safe in teardown scripts. It never signals process 1, itself or its parent. When the stop fails it exits 1 and prints a manual stop command after one of these reasons: * "The server on port N did not exit." * "The process on port N belongs to another user." * "Port N is held by a process this launcher refuses to signal." * "Could not identify the process listening on port N." (also printed when neither `lsof` nor `netstat` is available) Stopping the server interrupts any work in progress. On macOS and Linux the server first signals the runtime processes it started and waits up to 5 seconds for them, removes `api-port.txt` if it still names its port, and checkpoints the database. On Windows the stop ends the process at once, so none of that runs: `clawboo stop` removes the port file itself and the database recovers when it is next opened. Routines are re-armed when the server starts again. An OpenClaw Gateway that Clawboo started keeps running. ## clawboo restart ```bash theme={"theme":"github-dark"} clawboo restart clawboo restart --no-open ``` Stops the server as `clawboo stop` does, then starts a new one on the same port, so a browser tab that is already open reconnects. Use it after `npm install -g clawboo@latest` (the running server keeps its old code until it restarts) or after you change an environment variable the server reads at startup. | Flag | Effect | | - | - | | `--no-open` | Do not open the browser after restarting | * The new server is pinned to the old port with `CLAWBOO_API_PORT` and waits up to 15 seconds for it to free (`CLAWBOO_AWAIT_PORT`). If something else took the port in between, the new server starts without the pin and picks a port the usual way. * With nothing running, it starts a server. * If the stop fails, it exits 1 and does not start a replacement, so you never end up with two servers. * If the new server never answers, it prints "The previous server was stopped and could not be replaced. Nothing is running now." and exits 1. In a source checkout run with `pnpm dev`, the dev script supervises the server and may start it again on its own. Restart through the dev script there. ## Token-gated installs When the server runs with `STUDIO_ACCESS_TOKEN` set, every `/api/*` request needs the access cookie, and the CLI does not have it. The CLI can still tell that a Clawboo server is answering, because the access gate's refusal names Clawboo. * **`clawboo`** prints "A Clawboo server is running on port N, but it requires an access token." and the address to open once, `http://localhost:<port>/?access_token=<token>`. Then it exits 1 without starting a second server. * **`clawboo stop` and `clawboo restart`** print the same notice and a manual stop command, then exit 1. They never signal a process they could not identify. * **A fresh start** with the token set works, but the CLI cannot see the gated server answer. After about 45 seconds it reports "Dashboard is taking too long to start." and exits 1 while the server keeps running. Open `http://localhost:<port>/?access_token=<token>`; the port is in `~/.clawboo/api-port.txt`. To restart a gated server, stop it by hand, then start it again: > [tab] macOS and Linux > > ```bash theme={"theme":"github-dark"} > lsof -nP -iTCP:<port> -sTCP:LISTEN -t | xargs kill > clawboo > ``` > [tab] Windows > > ```powershell theme={"theme":"github-dark"} > netstat -ano | findstr :<port> > taskkill /PID <pid> /F > clawboo > ``` The final `clawboo` starts the server but, as described above, reports a start timeout; open the dashboard with your token anyway. To skip the CLI for a gated install, start the server directly as shown in [Deployment](/operating/deployment). See [Self-host securely](/guides/self-host-securely) for choosing and setting the token. ## clawboo backup ```bash theme={"theme":"github-dark"} clawboo backup clawboo backup ~/backups/ clawboo backup ~/clawboo.db.bak --force ``` Copies the database to a single file. It opens `<CLAWBOO_HOME>/clawboo.db` (default `~/.clawboo/clawboo.db`) read-only and uses SQLite's online backup, so it is safe while the server runs and the copy includes the latest writes. The result needs no `-wal` or `-shm` files beside it. | Argument or flag | Effect | | - | - | | `[dest]` | Omitted: `clawboo-backup-YYYYMMDD-HHMMSS.db` (local time) in the current folder. An existing folder: that name inside it. Anything else: that file path. | | `-f`, `--force` | Replace an existing destination. Without it, the command refuses with "Refusing to overwrite (pass --force to clobber)" and exits 1. | It prints the file's path and size. With no database yet it prints "No Clawboo database found at" the path, "Start the server once to create it, or set CLAWBOO\_HOME.", and exits 1. To restore, stop the server (`clawboo stop`) and copy the backup over `clawboo.db`. A database backup does not include `~/.clawboo/secrets/`, where your saved provider keys and the key that decrypts them live. The copy is owner-only (mode `0600`, like the live database) before anything is written to it, and a failed backup removes its partial file. It holds your chats and settings, so keep it private. [Data and state](/operating/data-and-state) covers what a full backup needs. ## Exit codes Every command exits 0 on success and 1 on failure. Any command also exits 1 on Node older than 22.12 or when given an unknown subcommand. | Command | Exits 0 when | Exits 1 when | | - | - | - | | `clawboo` | The browser opened on a found, restarted or newly started dashboard | Only a token-gated dashboard was found; no server to start; the start timed out; an older server was stopped for a restart and not replaced | | `clawboo stop` | The server stopped, or nothing was running | The dashboard is token-gated; the process could not be stopped or identified | | `clawboo restart` | A server is running again (or was started from nothing) | The dashboard is token-gated; the stop failed; the new server did not answer | | `clawboo backup [dest]` | The copy was written | No database exists; the destination exists and `--force` was not given | ## Environment variables These are the variables the CLI itself reads or sets. Everything else in your shell passes through to the server unchanged; the server reads many more, listed in [Environment variables](/reference/environment-variables). | Variable | Read or set | Effect | | - | - | - | | `CLAWBOO_API_PORT` | read, and set | Read: check only this port. Set on the new server when a restart pins the old port. | | `CLAWBOO_AWAIT_PORT` | set | Set with the pin, so the new server waits up to 15 seconds for the port to free. | | `CLAWBOO_HOME` | read | Where `api-port.txt`, `server.log` and (for `backup`) `clawboo.db` live. Default `~/.clawboo`. Also the server's working directory. | | `CLAWBOO_BASE_PATH` | read | Added to every URL the CLI prints or opens. An invalid value is ignored here; the server itself refuses to start on it. | | `CLAWBOO_SERVER_PATH` | read | Repository root to use for a source-checkout start. | | `CLAWBOO_LAUNCH_DIR` | set | The folder you ran `clawboo` from, offered as "Where you started clawboo" when you set up a connector that needs a folder. | | `CLAWBOO_VERSION` | set | The CLI's version, which the server reports as its own. | | `CLAWBOO_MCP_BIN_DIR` | set (normal install) | Where the MCP stdio bins are, so the server can print stdio attach snippets. | | `NODE_ENV` | set | `production` on the server it starts. | ## MCP stdio bins The package also installs four MCP servers that speak over stdio. You add them to an agent program you set up yourself (for example, your own Claude Code or Codex); Clawboo's own agents reach the same servers over HTTP inside the dashboard server. | Bin | MCP server | | - | - | | `clawboo-mcp-tasks` | Tasks: the board | | `clawboo-mcp-memory` | Memory: saved facts and procedures | | `clawboo-mcp-tools` | Tools: built-in and connector tools | | `clawboo-mcp-teamchat` | TeamChat: the agents' peer room | By default the bins open `~/.openclaw/clawboo/clawboo.db`, which is a different file from the dashboard's `~/.clawboo/clawboo.db`. To work on the dashboard's board and memory, set `CLAWBOO_DB_PATH` to the dashboard's file. The stdio snippet from `GET /api/mcp/config?transport=stdio` already includes it. The bins and the server can use the same file at once. ```bash theme={"theme":"github-dark"} CLAWBOO_DB_PATH="$HOME/.clawboo/clawboo.db" clawboo-mcp-tasks ``` * The memory bin looks for an embedding provider every 60 seconds: a local Ollama with `nomic-embed-text` at `http://localhost:11434`, then `OPENAI_API_KEY` when remote embeddings are allowed. With neither, memory search is keyword-only. * The tools bin offers the tools whose requirements its own environment meets, for example `web_search` only when `TAVILY_API_KEY` is set. * A stdio caller has no Clawboo identity: TeamChat tools take the author and team as arguments. See [MCP servers](/operating/mcp-servers) to attach an outside agent and [MCP tools](/reference/mcp-tools) for every tool and its inputs. > [accordion] Under the hood > > * The `clawboo` bin is `dist/bootstrap.js`. It checks the Node version before loading any dependency, because the CLI is CommonJS that loads ESM-only packages, which Node 22.0 to 22.11 cannot do. Each subcommand repeats the check. > * The version is compiled in from `apps/cli/package.json`; running the source directly reports `0.0.0-dev`. > * The MCP bins are bundled with their dependencies, except `better-sqlite3`, `ws`, `pino` and `pino-pretty`, which resolve from the CLI's own dependencies. The OpenTelemetry packages are left out. > * The CLI reads the database only for `backup` and never changes settings or the vault. Its own writes are creating `~/.clawboo` when missing, appending to `server.log`, and removing a stale `api-port.txt`. > * Source of truth: `apps/cli/src/index.ts`, `apps/cli/src/lifecycle.ts`, `apps/cli/src/server-log.ts`, `apps/cli/src/node-version.ts`, `apps/cli/src/versionCheck.ts`. ## Related * [Installation](/getting-started/installation): install Clawboo and run it the first time * [Data and state](/operating/data-and-state): everything under `~/.clawboo`, full backup and reset * [Environment variables](/reference/environment-variables): every variable the server reads * [Limits and defaults](/reference/limits-and-defaults): the timeouts on this page next to every other number * [Troubleshooting](/help/troubleshooting): fixes for a start timeout and other errors # Configuration reference Source: https://docs.claw.boo/reference/configuration Where Clawboo keeps each setting: environment variables, settings.json, the database settings table, your browser, ~/.clawboo files and ~/.openclaw. Clawboo spreads its configuration over a few places. Use this page to find where a setting lives, how to change it, and which files Clawboo reads or writes. | Where | What it holds | How you change it | | - | - | - | | Environment variables | Bind address, ports, access token, URL prefix, folders, tuning | Your shell or service manager; see [Environment variables](/reference/environment-variables) | | `~/.clawboo/settings.json` | The OpenClaw Gateway URL and token, and the first-run flag | The dashboard's Gateway and OpenClaw setup, or by hand | | The `settings` table in `~/.clawboo/clawboo.db` | Most dashboard settings, such as the default model and team rules | The dashboard | | Your browser's local storage | View preferences such as the theme and sidebar width | The dashboard, separately in each browser | | `~/.clawboo/secrets/` | Provider keys and connector secrets, encrypted | **Settings > Providers**, **Settings > Runtimes**, Connectors | | `~/.openclaw/` | OpenClaw's own configuration, part of which Clawboo writes | OpenClaw, or the dashboard | `~/.clawboo` is the default for `CLAWBOO_HOME` and `~/.openclaw` the default for `OPENCLAW_STATE_DIR`; every path under them on this page moves with them. The `~/.codex`, `~/.hermes` and `~/.claude` paths below do not. ## settings.json `<CLAWBOO_HOME>/settings.json` holds the connection to your OpenClaw Gateway. Clawboo creates it the first time it saves a setting; a missing or unreadable file means the defaults apply. ```json theme={"theme":"github-dark"} { "gateway": { "url": "ws://localhost:18789", "token": "your-gateway-token" }, "firstRunDismissedAt": 1717286400000 } ``` | Key | Type | Default | Meaning | | - | - | - | - | | `gateway.url` | string | `ws://localhost:18789` | The Gateway's WebSocket URL. Through the API it must start with `ws://` or `wss://`. | | `gateway.token` | string | empty | The Gateway's auth token. `GET /api/settings` never returns it, only `hasToken`. | | `firstRunDismissedAt` | number | unset | When you dismissed the first-run nudge, in milliseconds since 1970. | How the token is resolved: 1. A value written as a template, such as `${GATEWAY_AUTH_TOKEN}`, is read from the server's environment first, then from OpenClaw's `~/.openclaw/.env`. 2. With no usable token, Clawboo falls back to OpenClaw's `openclaw.json` (`gateway.auth.token` and `gateway.port`). It uses that token and, when `settings.json` has no URL, connects to `ws://localhost:<port>`. 3. Otherwise the literal value is used. The file is plain JSON, readable only by you (mode 0600). A folder Clawboo creates for it is owner-only (mode 0700); a folder that already exists keeps its mode. Saving a setting narrows an older file to 0600 first, and refuses to save when it cannot and the file is readable by others. > [!WARNING] > > A `studioAccessToken` key in settings.json does nothing. The access gate reads only the `STUDIO_ACCESS_TOKEN` environment variable, so a token written here leaves the dashboard open. See [Environment variables](/reference/environment-variables) for the variable and its rules. ## Settings stored in the database Most settings you change in the dashboard are rows in the `settings` table of `clawboo.db`, so they travel with a database backup. | Setting | Where you change it | | - | - | | Default Clawboo Native provider and model | Onboarding, or **Make default** in the Clawboo Native providers list (**Settings > Runtimes**) | | Boo Zero's display name and global brief | Boo Zero's agent page, **Brief** | | Team rules and your introduction to a team | Team chat: `/rule`, the team's brief and rules sheet, the Know Your Team step | | Whether memory may use OpenAI for embeddings | Memory, when no local Ollama embedding model is available | | Custom connectors and connector arguments | Connectors | | A Clawboo Native agent's model and tool switches | The agent's page | | Agent files for agents that do not run on OpenClaw | The agent's **Agent Files** | | Chat state: resume points, Clear chat times, leader state | Set automatically as you chat | | The secret that signs MCP attach URLs | Created automatically; it never leaves the server | | OpenClaw sync state and the tools Clawboo denies there | Set automatically on each Gateway connection | The table has no schema of its own beyond key and value; [Database schema](/reference/database-schema) describes it. ## Preferences stored in your browser The theme, sidebar width and collapsed state, panel layout, Board view, Org chart layout and a few one-time flags live in your browser's local storage. They apply to one browser only, a database backup does not include them, and clearing the site's data resets them. ## Files under \~/.clawboo Clawboo owns everything in this folder. The bundled server also runs with it as its working directory. [Data and state](/operating/data-and-state) explains what to back up, what is safe to delete, and how to reset. | Path | What it holds | Sensitive | | - | - | - | | `clawboo.db`, `-wal`, `-shm` | The database: agents, teams, board, chats, memory, settings, the event log | Yes | | `settings.json` | The Gateway URL and token | Yes | | `secrets/master.key` | The key that decrypts the vault | Yes | | `secrets/runtime-keys.json` | Encrypted provider keys, connector secrets and sign-in tokens | Encrypted | | `secrets/disconnected-runtimes.json` | Which runtimes you disconnected | No | | `secrets/keyless-providers.json` | Whether you chose or removed keyless Ollama | No | | `proxy-device-identity.json` | The key pair Clawboo uses to sign in to the OpenClaw Gateway as a device | Yes | | `api-port.txt` | The port the server listens on; removed at shutdown if it still names that port | No | | `server.log`, `server.log.1` | Output of a server the CLI started | No | | `gateway.log`, `gateway.pid` | Output and process record of an OpenClaw Gateway that Clawboo started | No | | `connector-pids.json` | Connector processes Clawboo started, so leftovers from a crash are stopped at the next start | No | | `catalog/` | Verified marketplace packs, cached | No | | `frames/` | Saved screenshots from agents' browsers | May show private pages | | `browser-profiles/` | Each agent's browser profile, including sites it is signed in to | Yes | | `workspaces/<team id>/` | Each team's shared folder, where runs without a worktree start; `_no-team` for teamless tasks | Holds what agents saved there | | `worktrees/`, `reviews/` | Per-task git worktrees and review checkouts | No | | `runtimes/<runtime>/<agent>/` | Each Clawboo Native, Codex or Hermes agent's own runtime home | May hold runtime sign-in copies | The database files, `settings.json` and secrets are owner-only (mode 0600) in an owner-only folder (0700). ## Saved keys Provider keys, connector secrets and sign-in tokens are encrypted with AES-256-GCM in `secrets/runtime-keys.json`, under the key in `secrets/master.key`. This protects a copied or synced vault file; a program running as your user can still read both files. To keep the key apart from the vault, set `CLAWBOO_SECRETS_MASTER_KEY`. If the key changes, saved keys read as missing and **System Health** reports that the master key changed. A runtime looks for its key in this order: the server's environment, the vault, then OpenClaw's `~/.openclaw/.env`. After you disconnect a runtime on its card in **Settings > Runtimes**, that runtime uses only the vault until you connect it again. That disconnect deletes every vault key the runtime reads, including keys other runtimes share; [Providers and runtimes](/using/providers-and-runtimes) covers connecting and disconnecting. > [!WARNING] > > Keys you add in **Settings > Providers**, or in OpenClaw's setup inside Clawboo, are also written in plain text to OpenClaw's `~/.openclaw/.env` and to each OpenClaw agent's `auth-profiles.json`, because the OpenClaw Gateway reads them there. Disconnecting the provider removes both copies. A key you enter on a runtime's own card in **Settings > Runtimes** stays only in the encrypted vault. ## OpenClaw files Clawboo reads and writes Clawboo finds OpenClaw's folder from `OPENCLAW_STATE_DIR` (or the older `MOLTBOT_STATE_DIR` and `CLAWDBOT_STATE_DIR`), else the first of `~/.openclaw`, `~/.clawdbot` and `~/.moltbot` that exists, else `~/.openclaw`. It reads: * `openclaw.json`: the Gateway's port and token when `settings.json` has none, and the settings the **System** panel shows. * `.env`: a fallback for provider keys and for `${NAME}` token templates. * `agents/<id>/agent/auth-profiles.json`: which providers each OpenClaw agent has keys for. It writes: * **When you connect or disconnect a provider** in **Settings > Providers** or OpenClaw's setup: the key line in `.env` and the `<provider>:default` profile in every agent's `auth-profiles.json`. * **When you set up OpenClaw** from the dashboard: `openclaw.json` and `.env` (the Gateway token, plus the provider key or `OLLAMA_API_KEY=ollama-local` for Ollama), and the Gateway URL and token in `settings.json`. With `OLLAMA_BASE_URL` set, the Ollama address goes into `openclaw.json` too. * **When you change OpenClaw settings from the dashboard**: `openclaw.json`, and any keys you enter. That covers the **System** panel, OpenClaw's default model, an OpenClaw agent's model, and drawing a routing link on the Org chart, which turns on agent-to-agent messaging. * **Each time it connects to the Gateway**: through the Gateway, it registers Clawboo's Memory, Tasks and Tools servers under `mcp.servers` and adds OpenClaw's sub-agent tools (`sessions_spawn`, `sessions_yield`) to `tools.deny`, alongside your own entries. * **When the Gateway refuses to start** and asks for a repair: it runs `openclaw doctor --fix --non-interactive` once, then tries again. Clawboo writes `.env` and each `auth-profiles.json` owner-only (mode 0600), narrowing an existing file first, and creates OpenClaw's folder owner-only (0700) when it does not exist. Resetting `~/.clawboo` leaves all of these in place, so provider keys saved this way keep working for OpenClaw. `OPENCLAW_CONFIG_PATH` is not read; the config is always `openclaw.json` inside that folder. ## Other folders Clawboo reads * `~/.codex/auth.json` (or `$CODEX_HOME/auth.json`): to see whether you signed in with `codex login`. Each Codex agent then runs with its own home under `~/.clawboo/runtimes/`. * `~/.hermes/auth.json` (or `$HERMES_HOME/auth.json`): to see whether you signed in to Hermes. * `~/.claude`: Claude Code agents run with your real home folder and save their session files under `~/.claude/projects`. Clawboo starts them without your own settings, MCP servers and memory files from there. > [accordion] Under the hood > > * `settings.json` is read by `loadSettings()` and written by `saveSettings()` in `@clawboo/config`; `GET /api/settings` returns `gatewayUrl`, `hasToken` and `firstRunDismissedAt`, and `POST /api/settings` accepts only `gatewayUrl`, `gatewayToken` and `firstRunDismissedAt`. > * The server opens `<CLAWBOO_HOME>/clawboo.db`. The MCP stdio bins default to a different file, `~/.openclaw/clawboo/clawboo.db`, unless `CLAWBOO_DB_PATH` is set; the stdio snippets from `GET /api/mcp/config` set it. This split is deliberate. > * Source of truth: `packages/config/src/index.ts`, `apps/web/server/lib/secretsVault.ts`, `apps/web/server/lib/openclawEnv.ts`, `apps/web/server/lib/providerKeys.ts`, `apps/web/server/api/system.ts`, `apps/web/server/lib/agentSource/openClawAgentSource.ts`. ## Related * [Environment variables](/reference/environment-variables): every variable, including the ones that move these folders * [Data and state](/operating/data-and-state): back up, restore and reset * [Security](/operating/security): the access gate, the vault and what each protects * [Providers and runtimes](/using/providers-and-runtimes): connect keys and runtimes from the dashboard * [Settings API](/reference/rest-api/settings): `GET` and `POST /api/settings` # Database schema reference Source: https://docs.claw.boo/reference/database-schema Every table, column and index in the SQLite database that holds Clawboo state, and how the schema is created and upgraded. This page is for developers and operators who query or back up Clawboo's database directly. Clawboo keeps its durable state in one SQLite file, `clawboo.db`: 35 tables, plus a full-text search table for memory. Every column and index below comes from the schema definition in `packages/db/src/schemaBootstrap.ts`. To copy the database while Clawboo runs, use `clawboo backup` (see the [CLI reference](/reference/cli)). Where the file lives and how to reset it are in [Data and state](/operating/data-and-state). ## Tables at a glance | Table | Group | Holds | | - | - | - | | [`teams`](#teams) | Teams and agents | Teams | | [`agents`](#agents) | Teams and agents | The agent registry, for every runtime | | [`sessions`](#sessions) | Teams and agents | Clawboo Native sessions and rotated run sessions | | [`boo_zero_team_briefs`](#boo_zero_team_briefs) | Teams and agents | Boo Zero's brief for each team | | [`team_profiles`](#team_profiles) | Teams and agents | Stored team templates (unused by the server) | | [`chat_messages`](#chat_messages) | Chat | Chat transcripts | | [`rooms`](#rooms) | Chat | A team's chat rooms | | [`room_members`](#room_members) | Chat | Room membership | | [`tasks`](#tasks) | Board | Task cards | | [`task_deps`](#task_deps) | Board | Which task waits for which | | [`task_comments`](#task_comments) | Board | Comments and notes on tasks | | [`workspaces`](#workspaces) | Board | The git worktree a task ran in | | [`execution_processes`](#execution_processes) | Board | One row per run, with cost and status | | [`scheduled_runs`](#scheduled_runs) | Routines | Routines | | [`memory_facts`](#memory_facts) | Memory | Facts | | [`memory_procedures`](#memory_procedures) | Memory | Versioned procedures | | [`memory_outcomes`](#memory_outcomes) | Memory | Feedback on facts | | [`tool_registry`](#tool_registry) | Tools and approvals | Tool metadata and on/off switches | | [`tool_call_audit`](#tool_call_audit) | Tools and approvals | Audit of every brokered tool call | | [`tool_call_approvals`](#tool_call_approvals) | Tools and approvals | Approval requests | | [`tool_result_blobs`](#tool_result_blobs) | Tools and approvals | Oversized tool results, kept whole | | [`approval_history`](#approval_history) | Tools and approvals | A log of approval decisions | | [`connectors`](#connectors) | Connectors and grants | Connected connectors | | [`capability_grants`](#capability_grants) | Connectors and grants | Who may use which connector, tool or skill | | [`approval_rules`](#approval_rules) | Connectors and grants | Remembered **Always** decisions | | [`capabilities`](#capabilities) | Capabilities and skills | Every runtime's skills, tools and connectors | | [`skills`](#skills) | Capabilities and skills | Skills added to agents | | [`team_chat`](#team_chat) | Peer room and mailbox | Posts in the agents' own peer room | | [`agent_inbox`](#agent_inbox) | Peer room and mailbox | Each agent's mailbox | | [`cost_records`](#cost_records) | Spend, budgets and audit | Token and spend records | | [`budgets`](#budgets) | Spend, budgets and audit | Spend budgets | | [`governance_audit`](#governance_audit) | Spend, budgets and audit | Audit of governance decisions | | [`orchestration_events`](#orchestration_events) | Event log | The event log | | [`settings`](#settings) | Settings and layout | Key and value settings | | [`graph_layouts`](#graph_layouts) | Settings and layout | Saved graph positions | ## Conventions * Times are `INTEGER` epoch milliseconds (columns ending in `_at`, plus `ts` and `timestamp_ms`). * Flags are `INTEGER` `0` or `1`. JSON is stored as `TEXT`. * `tenant_id` columns are unused and always null. * Most ids are `TEXT`: UUIDs, or ids from an agent source. `chat_messages`, `cost_records`, `graph_layouts` and `approval_history` use an autoincrement `INTEGER` id, and `orchestration_events` is keyed by `seq`. * Foreign keys are switched on, but only the columns below are declared as foreign keys. Every other id column (for example `tasks.team_id` or `tasks.assignee_agent_id`) is a plain reference that SQLite does not check. * Nothing deletes rows from `orchestration_events`, `tool_call_audit` or `governance_audit`, so they grow until you reset or trim the database. ### Foreign keys | Column | References | When the referenced row is deleted | | - | - | - | | `agents.team_id` | `teams.id` | Refused while rows point at it | | `cost_records.agent_id` | `agents.id` | Refused while rows point at it | | `approval_history.agent_id` | `agents.id` | Refused while rows point at it | | `boo_zero_team_briefs.team_id` | `teams.id` | The brief is deleted with it | | `rooms.team_id` | `teams.id` | The rooms are deleted with it | | `room_members.room_id` | `rooms.id` | Memberships are deleted with it | | `room_members.agent_id` | `agents.id` | Memberships are deleted with it | | `tasks.parent_task_id` | `tasks.id` | Refused while rows point at it | | `task_deps.task_id`, `task_deps.depends_on_task_id` | `tasks.id` | Refused while rows point at it | | `task_comments.task_id` | `tasks.id` | Refused while rows point at it | | `workspaces.task_id` | `tasks.id` | Refused while rows point at it | | `execution_processes.task_id` | `tasks.id` | Refused while rows point at it | | `execution_processes.workspace_id` | `workspaces.id` | Refused while rows point at it | In the column tables below, `FK` marks these columns and `cascade` marks the ones deleted with their parent. ## Teams and agents ### teams One row per team. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `name` | `TEXT`, not null | Display name | | `icon` | `TEXT`, not null | An emoji | | `color` | `TEXT`, not null | Hex color | | `color_collection_id` | `TEXT` | The Boo color collection the team's avatars use | | `template_id` | `TEXT` | The marketplace team it was deployed from, if any | | `leader_agent_id` | `TEXT` | The team's Leader, when one is set | | `is_archived` | `INTEGER`, not null, default `0` | `1` once the team is archived | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `idx_teams_name` (`name`). ### agents The agent registry. Every agent source (OpenClaw, Clawboo Native, Claude Code, Codex and Hermes) records its agents here, so the fleet still renders when the OpenClaw Gateway is offline. For OpenClaw agents, `name`, `status`, `identity_json` and `source_agent_id` are overwritten on every sync; the other columns are Clawboo's own. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `name` | `TEXT`, not null | Display name | | `gateway_id` | `TEXT`, not null | The agent's id in its source; for OpenClaw, the Gateway's agent id | | `avatar_seed` | `TEXT` | Seed for the agent's Boo avatar | | `personality_config` | `TEXT` | JSON `{ values, customText }`: personality sliders and custom text | | `exec_config` | `TEXT` | JSON run settings, such as an OpenClaw agent's exec policy or a coding-runtime agent's model | | `team_id` | `TEXT`, FK `teams.id` | Null when the agent belongs to no team | | `status` | `TEXT`, not null, default `'idle'` | Synced status | | `source_id` | `TEXT`, not null, default `'openclaw'` | The agent source that owns the row: `openclaw`, `clawboo-native`, `claude-code`, `codex` or `hermes` | | `source_agent_id` | `TEXT` | The upstream id; equal to `gateway_id` | | `identity_json` | `TEXT` | JSON identity from the Gateway (name, emoji, avatar, theme) | | `participant_kind` | `TEXT`, not null, default `'agent'` | `agent` or `human`; a routine cannot target a `human` | | `runtime` | `TEXT`, not null, default `'openclaw'` | The runtime the agent runs on: `openclaw`, `clawboo-native`, `claude-code`, `codex` or `hermes` (`clawboo-mock` when the mock runtime is enabled) | | `capabilities` | `TEXT` | JSON capability hints; unused | | `tenant_id` | `TEXT` | Unused | | `archived_at` | `INTEGER` | Set when the agent is archived (a soft delete); null while it is live | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `idx_agents_gateway_id` (`gateway_id`), `idx_agents_status` (`status`), `idx_agents_team_id` (`team_id`), `idx_agents_source` (`source_id`, `source_agent_id`). ### sessions Clawboo Native's sessions, and the chain of sessions a task-runner run moves through when it rotates. OpenClaw sessions live in the Gateway and are not stored here. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `source_id` | `TEXT`, not null, default `'openclaw'` | Who owns the session: `clawboo-native`, or the runtime of a rotated run | | `source_session_id` | `TEXT`, not null | The session's id in its source | | `agent_id` | `TEXT` | Plain reference to `agents.id` | | `team_id` | `TEXT` | | | `status` | `TEXT`, not null, default `'idle'` | For example `active`, or `closed` for a rotated-away session | | `parent_session_id` | `TEXT` | The session this one replaced after a rotation | | `runtime` | `TEXT` | | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `uniq_sessions_source` (unique: `source_id`, `source_session_id`), `idx_sessions_agent` (`agent_id`), `idx_sessions_parent` (`parent_session_id`). ### boo\_zero\_team\_briefs Boo Zero's brief for one team (Markdown), added to Boo Zero's context when it works on that team. | Column | Type | Notes | | - | - | - | | `team_id` | `TEXT`, PK, FK `teams.id`, cascade | | | `content` | `TEXT`, not null | Markdown | | `updated_at` | `INTEGER`, not null | | No indexes beyond the primary key. ### team\_profiles Stored team templates. The server neither reads nor writes this table. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `name` | `TEXT`, not null | | | `description` | `TEXT` | | | `agents_config` | `TEXT`, not null | JSON array of agent definitions | | `skills_config` | `TEXT`, not null | JSON array of skill references | | `graph_layout` | `TEXT` | JSON node positions | | `is_builtin` | `INTEGER`, not null, default `0` | `1` for a template that ships with Clawboo | | `created_at` | `INTEGER`, not null | | No indexes beyond the primary key. ## Chat The team chats you use in the dashboard (General and its rooms) and one-to-one agent chats. The agents' peer room is a different table, [`team_chat`](#team_chat). ### chat\_messages Chat transcripts, so history survives a page refresh. A team chat's rows use the session key `agent:<agentId>:team:<teamId>` in General and `agent:<agentId>:team:<teamId>:room:<roomId>` in a room. **Clear chat** keeps the rows and appends `#cleared:<ts>` to `session_key` and `entry_id`, so no reader matches them again. | Column | Type | Notes | | - | - | - | | `id` | `INTEGER`, PK, autoincrement | | | `session_key` | `TEXT`, not null | The conversation the entry belongs to | | `gateway_url` | `TEXT`, not null | The Gateway URL in use when it was saved; empty without a Gateway | | `entry_id` | `TEXT`, not null | Unique per entry, so a repeated save is ignored | | `timestamp_ms` | `INTEGER`, not null | Epoch milliseconds | | `data` | `TEXT`, not null | JSON transcript entry | Indexes: `uniq_chat_messages_entry_id` (unique: `entry_id`), `idx_chat_messages_session_ts` (`session_key`, `timestamp_ms`), `idx_chat_messages_session_id` (`session_key`, `id`). ### rooms A team's chat rooms: extra conversations inside its team chat, each with a subset of its members. General has no row; a room id of null means General. A room is archived, never deleted, and goes only with its team. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `team_id` | `TEXT`, not null, FK `teams.id`, cascade | | | `name` | `TEXT`, not null | The API collapses whitespace and allows at most 80 characters | | `tenant_id` | `TEXT` | Unused | | `archived_at` | `INTEGER` | Set when the room is archived; null while active | | `pinned_at` | `INTEGER` | Set when the room is pinned to the top of the room list | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `idx_rooms_team` (`team_id`). ### room\_members Which agents belong to which room. A room's live roster is its members that are still active agents of the room's team. | Column | Type | Notes | | - | - | - | | `room_id` | `TEXT`, not null, FK `rooms.id`, cascade | | | `agent_id` | `TEXT`, not null, FK `agents.id`, cascade | | | `created_at` | `INTEGER`, not null | When the agent joined; the roster is ordered by it | Primary key: `room_id`, `agent_id`. Indexes: `idx_room_members_agent` (`agent_id`). ## Board The board is where team work is coordinated. Statuses and legal moves are described in [The board](/concepts/the-board). ### tasks The task cards. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `title` | `TEXT`, not null | | | `description` | `TEXT` | | | `status` | `TEXT`, not null, default `'backlog'` | `backlog`, `todo`, `in_progress`, `in_review`, `blocked`, `done` or `cancelled`. Clawboo creates tasks in `todo` unless the creator picks a status, so the column default rarely applies | | `priority` | `INTEGER`, not null, default `0` | Higher runs first among ready tasks | | `team_id` | `TEXT` | Plain reference to `teams.id` | | `assignee_agent_id` | `TEXT` | Plain reference to `agents.id` | | `assignee_runtime` | `TEXT` | The runtime that claimed it | | `parent_task_id` | `TEXT`, FK `tasks.id` | The parent task, for a subtask | | `source_delegation_id` | `TEXT` | Set by the team engine: which delegation created the task and whom it reports back to | | `worktree_ref` | `TEXT` | The git worktree the work happened in | | `branch_ref` | `TEXT` | Its git branch | | `cost_usd` | `REAL`, not null, default `0` | The sum of the task's recorded run costs | | `parent_session_id` | `TEXT` | Unused | | `dropped` | `INTEGER`, not null, default `0` | `1` once soft-deleted | | `tenant_id` | `TEXT` | Unused | | `verification` | `TEXT` | JSON verification verdict; null until a check runs. While it holds a failing verdict, a move to `done` is refused (`verification_required`) unless a person overrides it | | `scheduled_by` | `TEXT`, not null, default `'manual'` | Who fires the task: `manual`, `clawboo` (the Routines engine) or `openclaw` | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | | `completed_at` | `INTEGER` | Set when the task first reaches `done` or `cancelled` | | `room_id` | `TEXT` | Plain reference to `rooms.id`: the room the task was delegated in; null means General. A subtask takes its parent's room | | `person_moved_at` | `INTEGER` | When a person last moved the card on the board or in the task drawer. While no run has started since, an agent's own status write through the tasks MCP tools is refused | Indexes: `idx_tasks_team_status` (`team_id`, `status`), `idx_tasks_team_room` (`team_id`, `room_id`), `idx_tasks_assignee` (`assignee_agent_id`), `idx_tasks_parent` (`parent_task_id`), `idx_tasks_parent_dropped_created` (`parent_task_id`, `dropped`, `created_at`). The `(parent_task_id, dropped, created_at)` index serves the Tasks MCP creation caps, which count live children per parent and new root tasks per 5 minutes. ### task\_deps Dependency edges: `task_id` waits for `depends_on_task_id` to be `done`. | Column | Type | Notes | | - | - | - | | `task_id` | `TEXT`, not null, FK `tasks.id` | | | `depends_on_task_id` | `TEXT`, not null, FK `tasks.id` | | | `tenant_id` | `TEXT` | Unused | Primary key: `task_id`, `depends_on_task_id`. Indexes: `idx_task_deps_task` (`task_id`), `idx_task_deps_depends` (`depends_on_task_id`). ### task\_comments Comments and system notes on a task, such as report-backs and verification results. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `task_id` | `TEXT`, not null, FK `tasks.id` | | | `author_agent_id` | `TEXT` | | | `author_type` | `TEXT`, not null | `agent`, `user` or `system` | | `body` | `TEXT`, not null | | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | Indexes: `idx_task_comments_task` (`task_id`). ### workspaces The git worktree a task ran in. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `task_id` | `TEXT`, not null, FK `tasks.id` | | | `repo_path` | `TEXT`, not null | The repository the worktree was made from | | `branch` | `TEXT` | | | `worktree_path` | `TEXT` | The worktree folder | | `status` | `TEXT`, not null, default `'active'` | `active`, `archived` or `stale` | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | | `last_used_at` | `INTEGER` | | Indexes: `idx_workspaces_task` (`task_id`). ### execution\_processes One row per run of a task, on any runtime: its status, git checkpoints, and token and cost totals. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `task_id` | `TEXT`, not null, FK `tasks.id` | | | `workspace_id` | `TEXT`, FK `workspaces.id` | The worktree the run used, if any | | `executor_type` | `TEXT`, not null | The runtime id, or `openclaw` for runs the team engine fires (then `runtime` names the runtime that really ran) | | `runtime` | `TEXT` | The runtime that ran, when `executor_type` is the engine marker; otherwise null | | `status` | `TEXT`, not null, default `'queued'` | `queued`, `running`, `succeeded`, `failed`, `timed_out` or `cancelled` | | `claimed_at` | `INTEGER` | | | `started_at` | `INTEGER` | | | `completed_at` | `INTEGER` | | | `before_commit` | `TEXT` | Git commit before the run | | `after_commit` | `TEXT` | Git commit after the run | | `input_tokens` | `INTEGER` | | | `output_tokens` | `INTEGER` | | | `cache_read` | `INTEGER` | | | `cache_write` | `INTEGER` | | | `cost_usd` | `REAL` | | | `cost_source` | `TEXT` | `reported`, `local` (a model your own Ollama served), `token-estimate` or `char-estimate`; null with no cost | | `summary` | `TEXT` | The run's result summary | | `run_reason` | `TEXT` | Why the run started | | `error` | `TEXT` | Error summary or code; see [Events and errors](/reference/events-and-errors#execution_completed) | | `recovery_tombstone` | `INTEGER`, not null, default `0` | `1` once startup recovery has reaped the run, so it is never resumed twice | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | Indexes: `idx_exec_task` (`task_id`), `idx_exec_status` (`status`). ## Routines ### scheduled\_runs One row per routine. The row is the source of truth; the in-memory timer re-arms from `next_run_at` when the server starts. See [Routines](/using/routines). | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `agent_id` | `TEXT`, not null | Plain reference to `agents.id`; empty for a team routine | | `team_id` | `TEXT` | Plain reference to `teams.id` | | `cron_spec` | `TEXT`, not null | A cron expression, or `once@<ISO time>` for a one-time routine | | `task_template` | `TEXT`, not null | JSON description of the work each fire creates | | `status` | `TEXT`, not null, default `'idle'` | `idle`, `queued`, `claimed`, `running`, `paused` or `error` | | `last_run_at` | `INTEGER` | | | `next_run_at` | `INTEGER` | Null when nothing will fire: a spent one-time routine, a paused one, or one stopped by an error | | `scheduled_by` | `TEXT`, not null, default `'clawboo'` | The firing owner; the Routines engine writes `clawboo` | | `last_error` | `TEXT` | The last fire's error message | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `idx_scheduled_runs_next` (`next_run_at`), `idx_scheduled_runs_status_next` (`status`, `next_run_at`), `idx_scheduled_runs_agent` (`agent_id`). ## Memory Shared memory: facts, versioned procedures, and the feedback that scores them. See [Memory](/concepts/memory). ### memory\_facts Declarative facts. Keyword search uses the [`memory_facts_fts`](#full-text-search) table; meaning-based search uses `embedding`. The `created_by_*` and `source_*` columns record who saved a fact and never change who can see it. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `title` | `TEXT`, not null | | | `content` | `TEXT`, not null | | | `tags` | `TEXT`, not null, default `'[]'` | JSON array of strings | | `embedding` | `BLOB` | Float32 little-endian vector; null without an embedding provider | | `embedding_model` | `TEXT` | The provider id that produced the vector | | `scope_agent_id` | `TEXT` | Set for a fact private to one agent | | `scope_team_id` | `TEXT` | Set for a fact shared within one team; null means it belongs to no team | | `tenant_id` | `TEXT` | Unused | | `created_by_agent_id` | `TEXT` | | | `created_by_runtime` | `TEXT` | `user` when saved from the dashboard | | `source_task_id` | `TEXT` | The task whose run saved it | | `source_session_key` | `TEXT` | The session that saved it | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `idx_memory_facts_team` (`scope_team_id`), `idx_memory_facts_agent` (`scope_agent_id`), `idx_memory_facts_created` (`created_at`), `idx_memory_facts_model_created` (`embedding_model`, `created_at`, `id`). ### memory\_procedures Versioned procedures. Saving under an existing name and scope adds the next version. The provenance columns match `memory_facts`. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `name` | `TEXT`, not null | | | `version` | `INTEGER`, not null, default `1` | | | `content` | `TEXT`, not null | | | `scope_agent_id` | `TEXT` | | | `scope_team_id` | `TEXT` | | | `tenant_id` | `TEXT` | Unused | | `created_by_agent_id` | `TEXT` | | | `created_by_runtime` | `TEXT` | | | `source_task_id` | `TEXT` | | | `source_session_key` | `TEXT` | | | `created_at` | `INTEGER`, not null | | Indexes: `idx_memory_procedures_name` (`name`), `idx_memory_procedures_team` (`scope_team_id`). ### memory\_outcomes Feedback on facts, append-only. It feeds the learning status that recall shows. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `fact_id` | `TEXT`, not null | Plain reference to `memory_facts.id` | | `outcome` | `TEXT`, not null | `useful`, `dead_end` or `corrected` from agents and the dashboard, or `cited` when a run used the fact (written only by Clawboo) | | `note` | `TEXT` | The correction, for `corrected` | | `agent_id` | `TEXT` | | | `team_id` | `TEXT` | | | `task_id` | `TEXT` | | | `runtime` | `TEXT` | | | `created_at` | `INTEGER`, not null | | Indexes: `idx_memory_outcomes_fact` (`fact_id`), `idx_memory_outcomes_created` (`created_at`). ## Tools and approvals The tool broker behind the [Tools MCP server](/reference/mcp-tools#tools-server): tool settings, the audit trail, approvals, and stored oversized results. ### tool\_registry Saved tool metadata and the on/off switch. A tool with no row counts as enabled. | Column | Type | Notes | | - | - | - | | `name` | `TEXT`, PK | | | `description` | `TEXT`, not null | | | `input_schema` | `TEXT` | JSON Schema | | `availability` | `TEXT` | JSON requirement, such as an environment variable | | `owner` | `TEXT`, not null, default `'core'` | `core`, `plugin`, `channel` or `mcp` | | `provenance_signer_id` | `TEXT` | Signature fields; checking them is off by default | | `provenance_signature` | `TEXT` | | | `provenance_signed_at` | `INTEGER` | | | `enabled` | `INTEGER`, not null, default `1` | `0` makes the broker refuse the tool (`denied: disabled:<name>`) | | `connector_id` | `TEXT` | The connector the tool came from; null for a builtin | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `idx_tool_registry_owner` (`owner`), `idx_tool_registry_connector` (`connector_id`). ### tool\_call\_audit Append-only audit of every brokered call, written before and after it runs. Arguments and results are scrubbed of secrets before they are stored; the model still gets the real result. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `tool_name` | `TEXT`, not null | | | `agent_id` | `TEXT` | | | `phase` | `TEXT`, not null | `before` or `after` | | `decision` | `TEXT` | On `before` rows: `allow`, `deny`, `require_approval` or `rewrite` | | `args_summary` | `TEXT` | Scrubbed JSON | | `result_summary` | `TEXT` | Scrubbed and shortened result | | `is_error` | `INTEGER`, not null, default `0` | | | `tenant_id` | `TEXT` | Unused | | `grant_id` | `TEXT` | The grant the call was decided under; null for builtins | | `connector_id` | `TEXT` | The call's connector; null for builtins | | `rule_id` | `TEXT` | The remembered rule that decided it, if any | | `created_at` | `INTEGER`, not null | | Indexes: `idx_tool_audit_tool` (`tool_name`), `idx_tool_audit_created` (`created_at`), `idx_tool_audit_grant` (`grant_id`, `created_at`). ### tool\_call\_approvals Approval requests. `kind: 'tool'` is a call Clawboo itself is holding (the broker, a Clawboo Native agent's command, or a delegation). `kind: 'exec'` mirrors a shell command the OpenClaw Gateway is holding: its row id is the Gateway's request id, `tool_name` is `exec`, `tool_class` is `destructive`, and `args_summary` is `{ command, cwd }`. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `tool_name` | `TEXT`, not null | | | `agent_id` | `TEXT` | | | `args_summary` | `TEXT` | Scrubbed JSON | | `reason` | `TEXT` | Why it needs approval | | `status` | `TEXT`, not null, default `'pending'` | `pending`, `allow_once`, `allow_always`, `deny` or `expired` | | `task_id` | `TEXT` | Plain reference to `tasks.id`, so an expired approval can unblock its task | | `tenant_id` | `TEXT` | Unused | | `grant_id` | `TEXT` | The connector grant the call was checked against | | `connector_id` | `TEXT` | | | `never_remember` | `INTEGER`, not null, default `0` | `1` when "Always" must not be offered | | `rule_reason` | `TEXT` | Why the grant asked: `policy-always`, `policy-writes`, `risk-destructive`, `risk-external`, `lethal-trifecta`, `tainted-run` or `never-remembered` | | `tool_class` | `TEXT` | `read`, `write` or `destructive` | | `tool_summary` | `TEXT` | The tool's own one-line description (for `exec`, the first 200 characters of the command) | | `kind` | `TEXT`, not null, default `'tool'` | `tool` or `exec` | | `created_at` | `INTEGER`, not null | | | `expires_at` | `INTEGER`, not null | When the waiting call stops waiting; 5 minutes after creation for broker calls | | `resolved_at` | `INTEGER` | | Indexes: `idx_tool_approvals_status` (`status`), `idx_tool_approvals_created` (`created_at`), `idx_tool_approvals_grant` (`grant_id`, `status`). ### tool\_result\_blobs Tool results too large to hand a model whole (over 16 KiB). The model gets a trimmed view and a `tr_` handle that `read_tool_result` reads back. One result keeps at most 8 MiB. Rows older than 7 days are deleted (`CLAWBOO_TOOL_RESULT_TTL_MS`). | Column | Type | Notes | | - | - | - | | `handle` | `TEXT`, PK | An unguessable `tr_` id | | `tool_name` | `TEXT`, not null | | | `agent_id` | `TEXT` | | | `tenant_id` | `TEXT` | Unused | | `body` | `TEXT`, not null | | | `total_bytes` | `INTEGER`, not null | What the tool produced | | `stored_bytes` | `INTEGER`, not null | What was kept; less than `total_bytes` when the 8 MiB limit cut it | | `created_at` | `INTEGER`, not null | | Indexes: `idx_tool_result_blobs_created` (`created_at`). ### approval\_history A log of approval decisions posted to `POST /api/approvals`. | Column | Type | Notes | | - | - | - | | `id` | `INTEGER`, PK, autoincrement | | | `agent_id` | `TEXT`, not null, FK `agents.id` | | | `action` | `TEXT`, not null | `allow-once`, `allow-always` or `deny` | | `tool_name` | `TEXT`, not null | | | `details` | `TEXT` | JSON | | `created_at` | `INTEGER`, not null | | Indexes: `idx_approval_history_agent_id` (`agent_id`), `idx_approval_history_created_at` (`created_at`). ## Connectors and grants Connectors Clawboo runs, and the grants that decide which agents may use them. See [Connectors](/using/connectors). ### connectors One row per connector Clawboo has connected, written only after the connector answered. No secret is stored: `spec` holds only `secret:NAME` references. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `slug` | `TEXT`, not null | | | `catalog_id` | `TEXT` | The connector directory entry; null for one you added yourself | | `display_name` | `TEXT`, not null | | | `transport` | `TEXT`, not null | `stdio` or `streamable-http` | | `spec` | `TEXT`, not null, default `'{}'` | JSON launch or connection spec | | `spec_hash` | `TEXT`, not null | SHA-256 of the spec, for drift checks | | `tools_hash` | `TEXT` | SHA-256 of the tool list, for drift checks | | `egress_allow` | `TEXT`, not null, default `'[]'` | JSON array of hosts it may reach | | `trifecta` | `TEXT`, not null, has a default | JSON `{ readsPrivateData, ingestsUntrustedContent, canEgress }`; all `false` by default | | `health` | `TEXT`, not null, default `'unknown'` | `unknown`, `ok`, `needs-auth`, `degraded`, `error` or `drift`. Set to `ok` on connect and `error` when its process exits | | `desired_state` | `TEXT`, not null, default `'connected'` | `connected` or `disconnected`: whether you left it on. A start restores only `connected` ones | | `health_detail` | `TEXT` | | | `failures` | `INTEGER`, not null, default `0` | Consecutive failures | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `uniq_connectors_slug` (unique: `slug`), `idx_connectors_health` (`health`), `idx_connectors_catalog` (`catalog_id`). ### capability\_grants Grants: one row lets an agent, a team or every agent use a connector, tool or skill, and is also the edge the graph draws. There is no last-used column; that comes from `tool_call_audit`. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `grant_key` | `TEXT`, not null | The canonical (subject, capability) pair; unique, so one grant per pair | | `subject_kind` | `TEXT`, not null | `agent`, `team` or `global` | | `subject_id` | `TEXT` | Null for `global` | | `capability_kind` | `TEXT`, not null | `connector`, `tool` or `skill` | | `connector_id` | `TEXT` | Set for a connector grant (then `capability_id` is null) | | `capability_id` | `TEXT` | | | `tool_allow` | `TEXT`, not null, default `'["*"]'` | JSON glob list | | `tool_deny` | `TEXT`, not null, default `'[]'` | JSON glob list | | `mode` | `TEXT`, not null, default `'read'` | `read`, `write` or `admin` | | `approval_policy` | `TEXT`, not null, default `'risk'` | `never`, `risk`, `writes` or `always` | | `state` | `TEXT`, not null, default `'active'` | `proposed`, `active`, `suspended`, `revoked` or `expired` | | `origin` | `TEXT`, not null, default `'operator'` | `owner` (the runtime already attaches it; not drawn as an edge) or `operator` (a person shared it) | | `expires_at` | `INTEGER` | | | `spec_hash_pin` | `TEXT` | The connector spec hash approved; a mismatch denies calls (`spec-drift`) | | `tools_hash_pin` | `TEXT` | The tool list hash approved | | `call_ceiling_per_hour` | `INTEGER` | Hourly call limit; null for none | | `granted_by` | `TEXT` | | | `granted_at` | `INTEGER`, not null | | | `revoked_at` | `INTEGER` | | | `revoked_reason` | `TEXT` | | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `uniq_capability_grants_key` (unique: `grant_key`), `idx_grants_subject` (`subject_kind`, `subject_id`), `idx_grants_connector` (`connector_id`), `idx_grants_capability` (`capability_id`), `idx_grants_state` (`state`), `idx_grants_expires` (`expires_at`). ### approval\_rules Remembered **Always** decisions, each bound to a grant. Revoking the grant deletes its rules in the same step (there is no foreign key). A rule made from **Always** expires after 30 days. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `rule_key` | `TEXT`, not null | The canonical (grant, tool, argument shape) triple; unique | | `grant_id` | `TEXT`, not null | Plain reference to `capability_grants.id` | | `tool_name` | `TEXT`, not null | | | `args_shape` | `TEXT` | Null covers any arguments; a value limits the rule to one argument shape | | `decision` | `TEXT`, not null | `allow` or `deny` | | `created_from_approval_id` | `TEXT` | The approval it came from | | `expires_at` | `INTEGER`, not null | Required; a rule never lasts forever | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | Indexes: `uniq_approval_rules_key` (unique: `rule_key`), `idx_approval_rules_grant` (`grant_id`), `idx_approval_rules_expires` (`expires_at`). ## Capabilities and skills ### capabilities Every runtime's skills, tools and connectors in one list, which the **Capabilities** panel and the graph read. Each source re-reads its own rows without touching another source's. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | `<source_id>:<key>`, so the id is the upsert key | | `source_id` | `TEXT`, not null | The source that read it: `native`, `claude-code`, `codex`, `hermes`, `openclaw` or `connector` | | `source_key` | `TEXT`, not null | The item's own name in that source | | `kind` | `TEXT`, not null | `skill`, `tool` or `connector` | | `runtime` | `TEXT`, not null | The runtime it belongs to | | `scope` | `TEXT`, not null | `team`, `agent` or `global` | | `agent_id` | `TEXT` | Null for `team` and `global` scope | | `origin` | `TEXT`, not null | Where it was read from: `brokered-mcp`, `curated-skill`, `filesystem-skill-md`, `mcp-connector`, `runtime-builtin`, `openclaw-extension` or `external-vendor-cli` | | `manageability` | `TEXT`, not null | `managed`, `external-write`, `runtime-of-record` or `observe-only` | | `name` | `TEXT`, not null | | | `description` | `TEXT`, not null, default `''` | | | `availability` | `TEXT` | JSON requirement, or null | | `available` | `INTEGER`, not null, default `1` | `1` when its requirement is met | | `diagnostics` | `TEXT`, not null, default `'[]'` | JSON array of strings | | `provenance` | `TEXT` | JSON, or null | | `status` | `TEXT`, not null, default `'ready'` | `ready`, `disabled`, `manageable-but-pending-auth` or `unavailable` | | `tenant_id` | `TEXT` | Unused | | `synced_at` | `INTEGER`, not null | When its source last read it | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `idx_capabilities_source` (`source_id`), `idx_capabilities_runtime` (`runtime`), `idx_capabilities_agent` (`agent_id`), `idx_capabilities_kind` (`kind`). ### skills Skills added to agents from the Marketplace or the Capabilities panel. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `name` | `TEXT`, not null | | | `source` | `TEXT`, not null | `curated` or `capability` | | `category` | `TEXT` | | | `trust_score` | `REAL` | Not written | | `installed_at` | `INTEGER` | Epoch milliseconds | | `metadata` | `TEXT` | JSON, including `agentIds`, the agents that have the skill | Indexes: `idx_skills_source` (`source`), `idx_skills_category` (`category`). ## Peer room and mailbox The agents' peer room behind the TeamChat MCP server, and each agent's mailbox. Neither is the dashboard's team chat. ### team\_chat Posts in the agents' peer room. A post never changes the board. The table has no `tenant_id` column. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `room_id` | `TEXT`, not null | `team:<teamId>`; never a `rooms.id` | | `team_id` | `TEXT`, not null | | | `author_agent_id` | `TEXT`, not null | Taken from the poster's signed binding, never from tool arguments | | `body` | `TEXT`, not null | | | `kind` | `TEXT`, not null, default `'peer'` | `peer` (a teammate), `system` (board narration) or `user` | | `created_at` | `INTEGER`, not null | | | `seq` | `INTEGER`, not null | Position in the room, counting up from 1 | Indexes: `uniq_team_chat_room_seq` (unique: `room_id`, `seq`), `idx_team_chat_team` (`team_id`). ### agent\_inbox Each agent's mailbox of coordination notices. A notice is delivered once, by whichever channel reaches the agent first (the digest at the start of a run, or the MCP tool-result piggyback). Undelivered notices survive restarts. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `team_id` | `TEXT` | | | `agent_id` | `TEXT`, not null | The recipient | | `kind` | `TEXT`, not null | `task_update` (a delegated task finished), `alert` (a coordination failure) or `signal` (an FYI) | | `body` | `TEXT`, not null | | | `task_id` | `TEXT` | | | `created_at` | `INTEGER`, not null | | | `delivered_at` | `INTEGER` | Null while pending | | `delivered_via` | `TEXT` | `digest`, `mcp` or `signal` | | `tenant_id` | `TEXT` | Unused | Indexes: `idx_agent_inbox_pending` (`agent_id`, `delivered_at`). ## Spend, budgets and audit ### cost\_records Per-agent token and spend records, which the **Tokens Used** panel reads. Rows come from `POST /api/cost-records`, from the server watching OpenClaw sessions, and from each Clawboo Native, Claude Code, Codex or Hermes run when the server settles its cost (task-runner and team-chat runs). | Column | Type | Notes | | - | - | - | | `id` | `INTEGER`, PK, autoincrement | | | `agent_id` | `TEXT`, not null, FK `agents.id` | | | `model` | `TEXT`, not null | | | `input_tokens` | `INTEGER`, not null | | | `output_tokens` | `INTEGER`, not null | | | `cost_usd` | `REAL`, not null | US dollars | | `run_id` | `TEXT` | The run it belongs to | | `session_key` | `TEXT` | The conversation it belongs to; null on rows from `POST /api/cost-records` | | `created_at` | `INTEGER`, not null | | Indexes: `idx_cost_records_agent_id` (`agent_id`), `idx_cost_records_session` (`session_key`, `created_at`), `idx_cost_records_run_id` (`run_id`), `idx_cost_records_created_at` (`created_at`). ### budgets Spend budgets. Spend is tracked in whole and fractional cents so many tiny charges still add up. The default `mode` is `warn` (track and warn); `cap` (pause at 100%) is opt-in. See [Budgets and spend](/using/budgets-and-spend). | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `scope` | `TEXT`, not null | `agent`, `mission` (a root task and its subtasks), `team`, or `tenant` (accepted, never charged) | | `scope_id` | `TEXT`, not null | | | `limit_usd_cents` | `INTEGER`, not null | The limit, in cents | | `spent_usd_cents` | `INTEGER`, not null, default `0` | Spend in whole cents | | `spent_micro_cents` | `INTEGER`, not null, default `0` | Spend in ten-thousandths of a cent | | `status` | `TEXT`, not null, default `'active'` | `active`, `soft_capped` or `paused` | | `mode` | `TEXT`, not null, default `'warn'` | `warn` (warn at 80% and 100%, never pause) or `cap` (pause at 100%) | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `uniq_budgets_scope` (unique: `scope`, `scope_id`), `idx_budgets_status` (`status`). ### governance\_audit Append-only audit of governance decisions, with secrets scrubbed. | Column | Type | Notes | | - | - | - | | `id` | `TEXT`, PK | | | `event_type` | `TEXT`, not null | `install`, `approval`, `tool_call`, `budget`, `cap_hit` or `verification` | | `agent_id` | `TEXT` | | | `task_id` | `TEXT` | | | `team_id` | `TEXT` | | | `tenant_id` | `TEXT` | Unused | | `summary` | `TEXT`, not null | Scrubbed JSON | | `created_at` | `INTEGER`, not null | | Indexes: `idx_gov_audit_agent` (`agent_id`, `created_at`), `idx_gov_audit_created` (`created_at`). ## Event log ### orchestration\_events The append-only event log that observability reads. Kinds and their data are in [Events and errors](/reference/events-and-errors). | Column | Type | Notes | | - | - | - | | `seq` | `INTEGER`, PK, autoincrement | Increases with every row and is never reused, across processes | | `id` | `TEXT`, not null | Event UUID | | `ts` | `INTEGER`, not null | When the event happened | | `kind` | `TEXT`, not null | One of the 25 event kinds | | `team_id` | `TEXT` | | | `task_id` | `TEXT` | | | `agent_id` | `TEXT` | | | `runtime` | `TEXT` | | | `trace_id` | `TEXT` | Set on task-runner runs; shared by every run under one root task | | `span_id` | `TEXT` | | | `parent_span_id` | `TEXT` | | | `correlation_id` | `TEXT` | | | `data` | `TEXT`, not null | Scrubbed JSON payload | | `tenant_id` | `TEXT` | Unused | | `created_at` | `INTEGER`, not null | | Indexes: `uniq_orch_events_id` (unique: `id`), `idx_orch_events_team_seq` (`team_id`, `seq`), `idx_orch_events_task_seq` (`task_id`, `seq`), `idx_orch_events_trace_seq` (`trace_id`, `seq`), `idx_orch_events_kind_ts` (`kind`, `ts`), `idx_orch_events_created` (`created_at`). ## Settings and layout ### settings Key and value rows for most dashboard settings and for Clawboo's own state, including the per-install secret that signs MCP attach URLs (`mcp-attach-scope-secret`). What the rows hold is listed in [Configuration](/reference/configuration). | Column | Type | Notes | | - | - | - | | `key` | `TEXT`, PK | | | `value` | `TEXT`, not null | | | `updated_at` | `INTEGER`, not null | | No indexes beyond the primary key. ### graph\_layouts Saved node positions for the graphs. `name` is `atlas-radial` or `atlas-top-down` for the Org chart, `team-<teamId>` for a team graph, `team-<teamId>-room-<roomId>` for a room graph, and `default` with no team selected. | Column | Type | Notes | | - | - | - | | `id` | `INTEGER`, PK, autoincrement | | | `name` | `TEXT`, not null, default `'default'` | The layout name | | `gateway_url` | `TEXT`, not null | The Gateway URL in use; empty without a Gateway | | `layout_data` | `TEXT`, not null | JSON node and edge positions | | `created_at` | `INTEGER`, not null | | | `updated_at` | `INTEGER`, not null | | Indexes: `uniq_graph_layouts_name_url` (unique: `name`, `gateway_url`). ## Full-text search Keyword search over facts uses `memory_facts_fts`, an FTS5 virtual table with the columns `title`, `content` and `fact_id` (`fact_id` is stored but not indexed). The same schema definition creates it, and it is not counted among the 35 tables. Three triggers keep it in step with `memory_facts`: | Trigger | Runs | Does | | - | - | - | | `memory_facts_ai` | After insert | Adds the new fact to the search table | | `memory_facts_ad` | After delete | Removes the fact from the search table | | `memory_facts_au` | After update | Replaces the fact's row in the search table | ## How the schema is created There is no migration ladder: no numbered migration files, no `user_version` bookkeeping, and no `db:migrate` script. The `CREATE TABLE IF NOT EXISTS` statements in `packages/db/src/schemaBootstrap.ts` are the only place tables, indexes and triggers are created. `packages/db/src/schema.ts` is the Drizzle type layer for typed queries; it never changes the database. Contributor rules for schema changes are in [Monorepo and build](/internals/monorepo-and-build). ### Upgrading an existing database When the server starts, it brings the file up to the current schema in one transaction: 1. It compares each existing table's columns with the schema definition and adds any column the file is missing (`ALTER TABLE ... ADD COLUMN`, using the column's definition from the same source). 2. It then runs the definition, which creates any missing table, index or trigger. Columns go first because a new column usually comes with a new index on it. If any step fails, the transaction rolls back and the file stays exactly as it was. The one start that adds columns logs `SQLite: added missing columns to an existing database`. Nothing else changes in an existing database: * A column whose type, `NOT NULL` or `DEFAULT` changed keeps its old definition. Only new databases get the new one, and nothing reports the difference. * A column the definition no longer declares stays in place. That is also what an older Clawboo sees when it opens a newer database, so leaving the column keeps the newer version's data. * A changed index or trigger is skipped, because `IF NOT EXISTS` matches on the name. New table constraints and changes to the full-text table are not applied either. Clawboo never drops, rebuilds or resets a table to apply a change. A change of those kinds reaches only databases created after it. A column added to an existing table must be one SQLite can add in place: no `PRIMARY KEY`, `UNIQUE` or `STORED` generated column; a literal `DEFAULT`, not an expression such as `CURRENT_TIMESTAMP`; a `DEFAULT` on every `NOT NULL` column; and no non-null `DEFAULT` on a `REFERENCES` column. If such a column ever reached a user, the start would stop before changing the file. The server keeps running without a usable database, and **System Health** shows the `databaseSchema` check as failed with `SQLite schema cannot be upgraded in place` and an error naming the column. Tests catch such a column before release. The other `databaseSchema` failures are `core tables missing` and `schema is missing <n> column(s); queries touching them will fail`. ## Connection settings Every connection opens with: ```sql theme={"theme":"github-dark"} PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 250; PRAGMA wal_autocheckpoint = 50; ``` Several processes can open the file at once, for example the server and a stdio MCP bin pointed at it. Board writes take the write lock up front (`BEGIN IMMEDIATE`) and retry with jitter for up to 1.5 seconds (`CLAWBOO_DB_WRITE_BUDGET_MS`), which is why `busy_timeout` is short. The server holds one connection for as long as it runs, and on shutdown it checkpoints the write-ahead log into the main file (`wal_checkpoint(TRUNCATE)`) before closing. ## Where the file lives * The dashboard server uses `clawboo.db` in its data folder: `~/.clawboo/clawboo.db`, or the folder `CLAWBOO_HOME` names. * The stdio MCP bins use `CLAWBOO_DB_PATH`, or `~/.openclaw/clawboo/clawboo.db` when it is unset; `CLAWBOO_DB_PATH` affects only the bins. To make a bin share the dashboard's database, set `CLAWBOO_DB_PATH` to the dashboard's file. The stdio snippets from `GET /api/mcp/config` do this. * On open, Clawboo sets the folder to `0700` and the database and its `-wal` and `-shm` files to `0600`, because the file holds the secret that signs MCP attach URLs. Where that fails (for example on Windows, or for a file another user owns), Clawboo logs a warning and opens the file anyway. * At start, **System Health** runs `PRAGMA integrity_check` (`databaseIntegrity`) and the schema check above (`databaseSchema`). ## Related * [Data and state](/operating/data-and-state): where Clawboo keeps its files, and how to back up and reset. * [Configuration](/reference/configuration): what the `settings` rows hold. * [Events and errors](/reference/events-and-errors): the event kinds stored in `orchestration_events`. * [MCP tools](/reference/mcp-tools): the tools that read and write the board and memory tables. * [Monorepo and build](/internals/monorepo-and-build): the rules for changing the schema. # Environment variables Source: https://docs.claw.boo/reference/environment-variables Every environment variable Clawboo reads or sets, with its type, default, effect and an example, from HOST and STUDIO_ACCESS_TOKEN to tuning timers. These are the environment variables Clawboo's own code reads, and the ones it sets on programs it starts. Set them in the shell or service that runs `clawboo`. The server reads most of them once at startup, so run `clawboo restart` after a change. Durations are in milliseconds, and an unset, empty or non-numeric duration means the default. Each variable's section below says how it handles other values. Agent runtimes and OpenClaw read variables of their own, which this page does not cover. | Variable | Default | What it does | | - | - | - | | **Access and network** | | | | `HOST` | `127.0.0.1` | Address the dashboard listens on | | `STUDIO_ACCESS_TOKEN` | unset | Turns on the access gate for `/api/*` | | `CLAWBOO_ALLOW_INSECURE` | unset | Allows a wide bind with no token | | `CLAWBOO_ALLOWED_ORIGINS` | loopback only | Extra browser origins allowed to call the API | | `CLAWBOO_ALLOWED_HOSTS` | loopback and the bind host | Extra `Host` header values the server accepts | | `CLAWBOO_BASE_PATH` | unset (root) | Serves the whole app under a URL prefix | | `CLAWBOO_SECRETS_MASTER_KEY` | generated key file | Key that encrypts saved provider keys | | **Ports** | | | | `CLAWBOO_API_PORT` | unset (scan from 18790) | Exact port for the dashboard | | `CLAWBOO_API_PORT_START` | `18790` | First port of the 20-port scan | | `PORT` | unset | Hosting-platform port, used when `CLAWBOO_API_PORT` is unset | | **Folders** | | | | `CLAWBOO_HOME` | `~/.clawboo` | Folder for everything Clawboo stores | | `OPENCLAW_STATE_DIR` | `~/.openclaw` | OpenClaw's folder, which Clawboo reads and writes | | `CLAWBOO_DB_PATH` | `~/.openclaw/clawboo/clawboo.db` | Database file for the MCP stdio bins only | | `CLAWBOO_UI_DIR` | `<server folder>/ui` | Built dashboard files the server serves | | `CLAWBOO_CATALOG_INDEX_URL` | GitHub raw URL | Where the marketplace catalog comes from | | **Provider keys** | | | | `ANTHROPIC_API_KEY` | unset | Claude Code, Clawboo Native (Anthropic), Hermes | | `OPENAI_API_KEY` | unset | Clawboo Native (OpenAI), Hermes, memory embeddings | | `OPENROUTER_API_KEY` | unset | Hermes, Clawboo Native (OpenRouter) | | `GEMINI_API_KEY` and six more | unset | Clawboo Native through OpenAI-compatible providers | | `OLLAMA_BASE_URL` | `http://localhost:11434/v1` | Ollama address for Clawboo Native and OpenClaw setup | | `TAVILY_API_KEY` | unset | Shows the `web_search` built-in tool | | `COMPOSIO_BASE_URL` | `https://backend.composio.dev` | Composio API address for brokered apps | | **Runs and verification** | | | | `CLAWBOO_RUN_SILENT_TIMEOUT_MS` | `1800000` (30 min) | Silence after which a run is stopped | | `CLAWBOO_HOME_MUTEX_ACQUIRE_MS` | `600000` (10 min) | Wait for the same agent's previous run | | `CLAWBOO_MAX_FIX_CYCLES` | `1` | Retries after a failed verification | | `CLAWBOO_REVIEWER_MODEL` | the builder's model | Model for the verification reviewer | | `CLAWBOO_ROUTINE_OPENCLAW_TIMEOUT_MS` | `600000` (10 min) | Wait for an OpenClaw agent's Routine run | | **Board timing** | | | | `CLAWBOO_BOARD_STALE_TTL_MS` | `180000` (3 min) | Missed heartbeats before a running task is released | | `CLAWBOO_BOARD_STALE_SWEEP_MS` | `60000` (1 min) | How often that check runs | | `CLAWBOO_DISPATCH_PUMP_MS` | `60000` (1 min) | How often waiting delegated work is woken | | `CLAWBOO_DB_WRITE_BUDGET_MS` | `1500` | Retry time for a busy database write | | **Approvals and retention** | | | | `CLAWBOO_APPROVAL_TTL_MS` | `86400000` (24 h) | Age at which the reaper expires a forgotten approval | | `CLAWBOO_APPROVAL_REAPER_INTERVAL_MS` | `3600000` (1 h) | How often the reaper runs | | `CLAWBOO_TOOL_RESULT_TTL_MS` | `604800000` (7 days) | How long stored large tool results are kept | | **Memory, browsers and MCP** | | | | `CLAWBOO_DISABLE_EMBEDDINGS` | unset | `1` turns memory embeddings off | | `CLAWBOO_MAX_AGENT_BROWSERS` | `4` | Agent browsers open at once | | `CLAWBOO_AGENT_BROWSER_IDLE_MS` | `600000` (10 min) | Idle time before an agent browser closes | | `CLAWBOO_OPENCLAW_CDP_PORT` | `18800` | Debugging port of OpenClaw's browser | | `OPENCLAW_GATEWAY_PORT` | `18789` | Only used to work out that debugging port | | `CLAWBOO_MCP_PROBE_MS` | `60000` (1 min) | How often the server checks its MCP servers | | **Logging and tracing** | | | | `LOG_LEVEL` | `info` | Server log level | | `NODE_ENV` | unset (the CLI sets `production`) | `production` writes JSON logs | | `OTEL_EXPORTER_OTLP_ENDPOINT` | unset | Exports traces over OTLP | | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | unset | Traces-only OTLP endpoint | | `OTEL_SERVICE_NAME` | `clawboo` | Service name on exported traces | | **Read from other tools** | | | | `CODEX_HOME` | `~/.codex` | Where Clawboo looks for your `codex login` | | `HERMES_HOME` | `~/.hermes` | Where Clawboo looks for your Hermes sign-in | | `CLAUDE_CODE_USE_BEDROCK` | unset | Keeps AWS keys for runtimes Clawboo starts | | `HTTP_PROXY` and other proxy variables | unset | Clawboo keeps its own loopback traffic off the proxy | | **Testing and development** | | | | `CLAWBOO_ENABLE_MOCK_RUNTIME` | unset | `1` adds a fault-injection test runtime | | `CLAWBOO_SERVER_PATH` | found automatically | Source checkout the CLI starts in development | `HOSTNAME`, `OPENCLAW_CONFIG_PATH`, `CLAWBOO_API_URL` and `GATEWAY_AUTH_TOKEN` (outside a `${...}` template) are not read. Variables Clawboo sets on the processes it starts are listed under [Set by Clawboo](#set-by-clawboo). ## Access and network ### HOST The network address the dashboard listens on. * Type: hostname or IP address * Default: `127.0.0.1`, so only this machine can connect * Example: `HOST=0.0.0.0` Any address other than loopback (`localhost`, `127.x.x.x`, `::1`) exposes the dashboard to your network. The server then refuses to start, exiting with code 1, unless `STUDIO_ACCESS_TOKEN` holds a valid token or `CLAWBOO_ALLOW_INSECURE=1` is set. Prefer `0.0.0.0` to a single LAN address: Clawboo's own agents reach its MCP servers at `127.0.0.1`, which a single-address bind does not answer. A wide bind without `CLAWBOO_ALLOWED_ORIGINS` also logs a warning that browsers on other machines will be blocked. `HOSTNAME` is ignored. Docker, systemd and many CI runners set it automatically, so widening the bind always takes an explicit `HOST`. [Self-host securely](/guides/self-host-securely) walks through reaching Clawboo from another machine. ### STUDIO\_ACCESS\_TOKEN A shared secret that turns on the access gate. * Type: string of letters, digits, `.`, `_`, `~` and `-` * Default: unset, so the gate is off * Example: `STUDIO_ACCESS_TOKEN=$(openssl rand -hex 32)`, saved somewhere you can reuse it With a token set, every `/api/*` request and the Gateway WebSocket need the `clawboo_access` cookie. Open `http://<host>:<port>/?access_token=<token>` once to get it: the right token sets an HttpOnly cookie (scoped to your `CLAWBOO_BASE_PATH`, `SameSite=Lax`, and `Secure` when the request arrives with `X-Forwarded-Proto: https`) and redirects to the same address without the token. A wrong token answers 401 `Invalid Clawboo access token.` and a missing cookie answers 401 `Clawboo access token required. Open /?access_token=<token> once to set a cookie.` The page shell and its static files load without the cookie. Clawboo's own runtimes reach its MCP servers with a per-install attach token instead of the cookie, and only on direct connections from `127.0.0.1`. The variable is read only from the environment; a `studioAccessToken` key in `settings.json` does nothing. It is removed from the environment of every runtime Clawboo starts. The `clawboo` CLI cannot use a gated server; see [CLI reference](/reference/cli). > [!WARNING] > > Use only letters, digits, `.`, `_`, `~` and `-`. With any other character, such as the `+`, `/` or `=` in a base64 token, the server logs `SECURITY: refusing to start` with the character and the allowed set, and exits with code 1, on every bind. `CLAWBOO_ALLOW_INSECURE=1` does not skip this. On 0.5.0 and older, such a token turns the gate off with only a warning, which behind a reverse proxy on the same machine leaves every `/api/*` route open. With no token set, the first request that reaches the server through a reverse proxy logs a one-time `SECURITY:` warning, because such a proxy delivers every request from `127.0.0.1`. [Security](/operating/security) explains what the gate does and does not protect. ### CLAWBOO\_ALLOW\_INSECURE Lets the server start on a wide bind with no access token. * Type: flag, exactly `1` * Default: unset * Example: `CLAWBOO_ALLOW_INSECURE=1` With it, a non-loopback `HOST` and no token starts the server with a loud `SECURITY:` warning instead of exiting. It has no effect on a loopback bind or when a token is set, and it does not skip the refusal of a token with an unsupported character. Use it only behind a proxy that authenticates every request. ### CLAWBOO\_ALLOWED\_ORIGINS Browser origins, beyond this machine's, that may call the API. * Type: comma-separated origins, each `scheme://host[:port]` * Default: none extra. `http` and `https` on `localhost`, `127.0.0.1` and `[::1]` at the dashboard port, plus the bind address, are always allowed. * Example: `CLAWBOO_ALLOWED_ORIGINS=https://clawboo.example.com` A request to `/api/*` or the Gateway WebSocket from any other origin gets 403 `Cross-origin request blocked.` The list only adds origins; it never turns the check off. Entries that are not valid origins are skipped. ### CLAWBOO\_ALLOWED\_HOSTS `Host` header values the server accepts, beyond this machine's. * Type: comma-separated hostnames * Default: none extra. `localhost`, `127.x.x.x`, `::1` and the bind address are always accepted. * Example: `CLAWBOO_ALLOWED_HOSTS=clawboo.example.com` This guards against DNS rebinding. Behind a reverse proxy or on a LAN, set both this and `CLAWBOO_ALLOWED_ORIGINS`, or every `/api/*` call answers 403 `Cross-origin request blocked.` ### CLAWBOO\_BASE\_PATH A URL prefix to serve the whole app under, for sharing a hostname with other apps behind a proxy. * Type: URL path * Default: unset, so the app is served at `/` * Example: `CLAWBOO_BASE_PATH=/clawboo` How it behaves: * The dashboard, `/api`, the event streams and the Gateway WebSocket all move under the prefix. The built app needs no rebuild. * `clawboo`, `/clawboo` and `/clawboo/` all mean `/clawboo`. Empty or `/` means the root. * These values stop the server at startup with `SECURITY/CONFIG: CLAWBOO_BASE_PATH is invalid (<reason>)`: whitespace, a backslash, `?`, `#` or `%`, an empty or `.`/`..` segment, any character outside `A-Z a-z 0-9 . _ ~ -`, or a first segment of `api`. * The unprefixed `/api` stays available for the CLI and Clawboo's own runtimes, with the same checks. * `/` and the bare prefix redirect to `<prefix>/`. Any other unprefixed path answers 404 `Not found. This Clawboo is served under <prefix>/`. * The access cookie is scoped to the prefix, so other apps on the same hostname never receive it. The CLI adds the prefix to the URLs it prints. ### CLAWBOO\_SECRETS\_MASTER\_KEY The key that encrypts saved provider keys and connector secrets. * Type: a 32-byte key as base64, 64 hex characters, or exactly 32 characters * Default: unset, so Clawboo generates `<CLAWBOO_HOME>/secrets/master.key` * Example: `CLAWBOO_SECRETS_MASTER_KEY="$(cat ~/.config/clawboo/master.key)"` Set it to keep the key outside `~/.clawboo`, and give it the same value every time. An invalid value stops any use of the vault with an error that lists the accepted forms. A different key from the one that encrypted the vault makes saved keys read as missing, and **System Health** reports that the master key changed. It is removed from the environment of every runtime Clawboo starts. ## Ports ### CLAWBOO\_API\_PORT The exact port for the dashboard. * Type: whole number, 1 to 65535 * Default: unset, so the server takes the first free port from 18790 upward * Example: `CLAWBOO_API_PORT=18795 clawboo` With it set there is no fallback: a taken port stops the server with `CLAWBOO_API_PORT=N is already in use.` The CLI then checks only this port when it looks for a running dashboard, and `clawboo restart` sets it on the new server to keep the old port. ### CLAWBOO\_API\_PORT\_START The first port of the automatic scan. * Type: whole number, 1 to 65535 * Default: `18790` * Example: `CLAWBOO_API_PORT_START=19000` Used only when `CLAWBOO_API_PORT` and `PORT` are unset. The server tries 20 consecutive ports from here. The CLI does not scan from this value; it finds such a server through `api-port.txt`. ### PORT A port set by a hosting platform. * Type: whole number, 1 to 65535 * Default: unset * Example: `PORT=8080` Honored only when `CLAWBOO_API_PORT` is unset and the server is not running in development mode (`--dev`). Like `CLAWBOO_API_PORT` it has no fallback: a taken port stops the server with `PORT=N is already in use (set by hosting platform).` ## Folders ### CLAWBOO\_HOME The folder for everything Clawboo stores. * Type: path; a leading `~` is expanded and a relative path is resolved * Default: `~/.clawboo` * Example: `CLAWBOO_HOME=/srv/clawboo` It moves the database, `settings.json`, the vault, `api-port.txt`, the logs, worktrees, team folders, agent browser profiles and per-agent runtime homes. The bundled server also runs with it as its working directory. Use the same value for every `clawboo` command, or `stop` and `backup` look in the wrong place. [Configuration](/reference/configuration) lists every file inside. ### OPENCLAW\_STATE\_DIR OpenClaw's folder. * Type: path; a leading `~` is expanded * Default: the first of `~/.openclaw`, `~/.clawdbot` and `~/.moltbot` that exists, else `~/.openclaw` * Example: `OPENCLAW_STATE_DIR=/srv/openclaw` Clawboo reads OpenClaw's `openclaw.json`, `.env` and agent profiles here, and writes to them when you connect a provider key, set up OpenClaw or change its settings from the dashboard. `MOLTBOT_STATE_DIR` and `CLAWDBOT_STATE_DIR` are older names, checked in that order when this one is unset. [Configuration](/reference/configuration) lists every read and write. ### CLAWBOO\_DB\_PATH The database file the MCP stdio bins open. * Type: path (not `~`-expanded; use `$HOME`) * Default: `~/.openclaw/clawboo/clawboo.db` * Example: `CLAWBOO_DB_PATH="$HOME/.clawboo/clawboo.db" clawboo-mcp-tasks` Only the four `clawboo-mcp-*` bins read it; the dashboard server always uses `<CLAWBOO_HOME>/clawboo.db`. The bins' default is a different file, so set this whenever you run a bin yourself and want it to see the dashboard's board and memory. The stdio snippets from `GET /api/mcp/config` already include it. ### CLAWBOO\_UI\_DIR The folder of built dashboard files the server serves. * Type: path * Default: the `ui` folder next to the bundled server * Example: `CLAWBOO_UI_DIR=/opt/clawboo/ui` Not used in development mode, where Vite serves the dashboard. If the files cannot be read, the page answers 503 `The Clawboo dashboard could not be read from disk. Check CLAWBOO_UI_DIR.` ### CLAWBOO\_CATALOG\_INDEX\_URL Where the marketplace catalog index comes from. * Type: URL * Default: `https://raw.githubusercontent.com/clawboo/clawboo/refs/heads/main/catalog/dist/v1/index.json` * Example: `CLAWBOO_CATALOG_INDEX_URL=https://mirror.example.com/catalog/v1/index.json` A `catalog/dist/v1/` folder above the server, as in a source checkout, takes precedence. Pack files are fetched relative to the index, and each is checked against the index's digest before use. The index is cached for 6 hours. When it cannot be reached, the marketplace still shows the built-in pack. ## Provider keys Clawboo looks up a runtime's key in this order: the server's environment, the encrypted vault, then OpenClaw's `~/.openclaw/.env`. A runtime you disconnected on its card uses only the vault until you connect it again. An exported key makes a runtime's card read as connected, but **Settings > Providers** shows a provider as connected only when its key is in the vault or in OpenClaw's `.env`. [Providers and runtimes](/using/providers-and-runtimes) covers saving keys from the dashboard instead. ### ANTHROPIC\_API\_KEY * Type: string * Default: unset * Example: `ANTHROPIC_API_KEY=sk-ant-...` The key for Claude Code and for Clawboo Native's Anthropic models. Hermes accepts it as an alternative to its OpenRouter key. ### OPENAI\_API\_KEY * Type: string * Default: unset * Example: `OPENAI_API_KEY=sk-...` The key for Clawboo Native's OpenAI models. Hermes accepts it as an alternative to its OpenRouter key. Memory can also use it for embeddings (`text-embedding-3-small`) when no local Ollama embedding model is available and you allow it. Clawboo connects Codex through `codex login`, not this key. ### OPENROUTER\_API\_KEY * Type: string * Default: unset * Example: `OPENROUTER_API_KEY=sk-or-...` The main key for Hermes, and the key for Clawboo Native's OpenRouter models. ### OpenAI-compatible provider keys Clawboo Native reaches seven more providers through their OpenAI-compatible endpoints. Each key, on its own, is enough to connect Clawboo Native. All default to unset. | Variable | Provider | Endpoint | | - | - | - | | `GEMINI_API_KEY` | Google | `https://generativelanguage.googleapis.com/v1beta/openai` | | `XAI_API_KEY` | xAI | `https://api.x.ai/v1` | | `GROQ_API_KEY` | Groq | `https://api.groq.com/openai/v1` | | `MISTRAL_API_KEY` | Mistral | `https://api.mistral.ai/v1` | | `TOGETHER_API_KEY` | Together | `https://api.together.xyz/v1` | | `CEREBRAS_API_KEY` | Cerebras | `https://api.cerebras.ai/v1` | | `MOONSHOT_API_KEY` | Moonshot | `https://api.moonshot.ai/v1` | **Settings > Providers** also saves `MINIMAX_API_KEY`, `NVIDIA_API_KEY`, `HF_TOKEN` and `VENICE_API_KEY` into OpenClaw's `.env` for the OpenClaw Gateway. Clawboo's own runtimes do not use those four. ### OLLAMA\_BASE\_URL The address of an Ollama server, for keyless local models. * Type: URL including `/v1` * Default: `http://localhost:11434/v1` * Example: `OLLAMA_BASE_URL=http://192.168.1.20:11434/v1` Clawboo Native sends Ollama requests here and lists your pulled models from the same server. You do not need it to use a local Ollama: picking Ollama in onboarding or in Clawboo Native's providers list records that choice. With no recorded choice, setting this variable also counts as Ollama being set up. After you remove Ollama (**Disconnect Ollama**) or disconnect Clawboo Native, the variable no longer turns Ollama on; only picking Ollama again does. OpenClaw does not read this variable, so when Clawboo sets up OpenClaw on Ollama it writes the same address (without `/v1`) into `openclaw.json`. Memory embeddings ignore it and always look for Ollama at `http://localhost:11434`; with a remote Ollama, memory search stays keyword-only unless a local Ollama or an allowed OpenAI key provides embeddings. ### TAVILY\_API\_KEY * Type: string * Default: unset * Example: `TAVILY_API_KEY=tvly-...` When set in the server's environment (or the tools stdio bin's), the built-in `web_search` tool becomes available to agents. ### COMPOSIO\_BASE\_URL * Type: URL * Default: `https://backend.composio.dev` * Example: `COMPOSIO_BASE_URL=https://composio.example.com` The Composio API address for brokered apps. The Composio key itself is saved in the vault from **Connectors**; Clawboo does not read it from the environment. ## Runs and verification ### CLAWBOO\_RUN\_SILENT\_TIMEOUT\_MS * Type: milliseconds * Default: `1800000` (30 minutes) * Example: `CLAWBOO_RUN_SILENT_TIMEOUT_MS=3600000` How long a run may go without producing any event before Clawboo stops it. Every event restarts the clock, so a run that keeps working is never cut off. It applies to team chat runs (including delegated tasks and team Routines), to agent Routines on Clawboo Native, Claude Code, Codex and Hermes, and to one-to-one chat with those agents. A delegated task in team chat also has its own fixed 8-minute idle watchdog; see [Limits and defaults](/reference/limits-and-defaults). ### CLAWBOO\_HOME\_MUTEX\_ACQUIRE\_MS * Type: milliseconds * Default: `600000` (10 minutes) * Example: `CLAWBOO_HOME_MUTEX_ACQUIRE_MS=900000` How long a run waits for the same agent's previous run to finish before it gives up. It bounds the wait, never the run. ### CLAWBOO\_MAX\_FIX\_CYCLES * Type: whole number, 0 or more * Default: `1`, which allows two attempts * Example: `CLAWBOO_MAX_FIX_CYCLES=0` How many times a task that failed verification goes back to the same agent with the findings. `0` allows a single attempt. When the attempts run out, the task moves to **Needs you**. Only runs that are verified count; [How work is checked](/concepts/verification) explains which runs those are. ### CLAWBOO\_REVIEWER\_MODEL * Type: model id * Default: the model that did the work * Example: `CLAWBOO_REVIEWER_MODEL=claude-sonnet-4-6` The model for the verification reviewer. The reviewer runs on the same runtime as the work it checks, so name a model that runtime accepts. The verdict records which model reviewed. ### CLAWBOO\_ROUTINE\_OPENCLAW\_TIMEOUT\_MS * Type: milliseconds * Default: `600000` (10 minutes) * Example: `CLAWBOO_ROUTINE_OPENCLAW_TIMEOUT_MS=1200000` How long a Routine that runs an OpenClaw agent waits for that agent to finish. Past it, Clawboo stops waiting and returns the task to **To do**. ## Board timing ### CLAWBOO\_BOARD\_STALE\_TTL\_MS * Type: milliseconds, a whole number from 1 to 2147483647 * Default: `180000` (3 minutes) * Example: `CLAWBOO_BOARD_STALE_TTL_MS=240000` A running task proves it is alive with a heartbeat every 30 seconds. A task in **In progress** that misses heartbeats for this long is marked timed out and returned to **To do**. At startup the same value decides which runs a previous server left behind. Keep it well above 30 seconds, and keep it plus `CLAWBOO_BOARD_STALE_SWEEP_MS` under 8 minutes, the fixed delegation watchdog, or that watchdog fails the task first. ### CLAWBOO\_BOARD\_STALE\_SWEEP\_MS * Type: milliseconds, a whole number from 1 to 2147483647 * Default: `60000` (1 minute) * Example: `CLAWBOO_BOARD_STALE_SWEEP_MS=30000` How often the stale-task check runs. It also runs once at startup. ### CLAWBOO\_DISPATCH\_PUMP\_MS * Type: milliseconds, a whole number from 1 to 2147483647 * Default: `60000` (1 minute) * Example: `CLAWBOO_DISPATCH_PUMP_MS=30000` How often Clawboo looks for delegated work that is ready to run, and for messages waiting for an agent, and wakes those teams. Board changes trigger this at once; the interval is a backstop and is how waiting work resumes after a restart. The first pass runs 10 seconds after the server starts. ### CLAWBOO\_DB\_WRITE\_BUDGET\_MS * Type: milliseconds * Default: `1500` * Example: `CLAWBOO_DB_WRITE_BUDGET_MS=3000` How long one database write keeps retrying while another process holds the file. The retry pauses the server, so this caps how long one busy write can stall it (plus a final 250 ms wait). ## Approvals and retention ### CLAWBOO\_APPROVAL\_TTL\_MS * Type: milliseconds * Default: `86400000` (24 hours) * Example: `CLAWBOO_APPROVAL_TTL_MS=43200000` The age at which the hourly reaper marks a still-pending approval expired and unblocks any task it was holding (unless a failed verification is what holds it). It does not change how long an approval prompt waits for your answer: tool and delegation approvals expire after 5 minutes whatever this is set to. [Limits and defaults](/reference/limits-and-defaults) lists every approval window. ### CLAWBOO\_APPROVAL\_REAPER\_INTERVAL\_MS * Type: milliseconds * Default: `3600000` (1 hour) * Example: `CLAWBOO_APPROVAL_REAPER_INTERVAL_MS=600000` How often the reaper runs; it also runs once at startup. The same pass expires time-limited connector grants and remembered approvals, and deletes stored tool results older than `CLAWBOO_TOOL_RESULT_TTL_MS`. ### CLAWBOO\_TOOL\_RESULT\_TTL\_MS * Type: milliseconds * Default: `604800000` (7 days) * Example: `CLAWBOO_TOOL_RESULT_TTL_MS=86400000` A tool result too large for an agent's context is cut down, and the whole result (up to 8 MB) is stored so the agent can read the rest. The reaper deletes stored results older than this. ## Memory, browsers and MCP ### CLAWBOO\_DISABLE\_EMBEDDINGS * Type: flag, exactly `1` * Default: unset * Example: `CLAWBOO_DISABLE_EMBEDDINGS=1` Turns memory embeddings off in the server and in the memory stdio bin. Memory search then matches keywords only. ### CLAWBOO\_MAX\_AGENT\_BROWSERS * Type: whole number * Default: `4` * Example: `CLAWBOO_MAX_AGENT_BROWSERS=2` At most this many agent browsers run at once; each takes roughly 400 MB. A browser call beyond the limit closes an idle browser if there is one, otherwise it waits up to 60 seconds for a free slot. ### CLAWBOO\_AGENT\_BROWSER\_IDLE\_MS * Type: milliseconds * Default: `600000` (10 minutes) * Example: `CLAWBOO_AGENT_BROWSER_IDLE_MS=300000` An agent browser nobody has used for this long is closed. ### CLAWBOO\_OPENCLAW\_CDP\_PORT * Type: port * Default: `OPENCLAW_GATEWAY_PORT` plus 11, so `18800` * Example: `CLAWBOO_OPENCLAW_CDP_PORT=18850` The Chrome debugging port of OpenClaw's managed browser. Clawboo reads it to capture what an OpenClaw agent's browser shows. Set it if you moved that port in OpenClaw. ### OPENCLAW\_GATEWAY\_PORT * Type: port * Default: `18789` * Example: `OPENCLAW_GATEWAY_PORT=19789` Used only to work out the default debugging port above. It does not change which Gateway Clawboo connects to; `settings.json` holds that address. ### CLAWBOO\_MCP\_PROBE\_MS * Type: milliseconds * Default: `60000` (1 minute) * Example: `CLAWBOO_MCP_PROBE_MS=120000` How often the server checks its own Tasks, Memory, Tools and TeamChat servers. A server that fails the check is rebuilt, and retried after 30 seconds, doubling up to 5 minutes. ## Logging and tracing ### LOG\_LEVEL * Type: one of `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent` * Default: `info` * Example: `LOG_LEVEL=debug` The level for the server's log. On a CLI-started server the log goes to `~/.clawboo/server.log`. Credential-looking values are masked in every log line. ### NODE\_ENV * Type: string * Default: unset; the CLI sets `production` on the server it starts * Example: `NODE_ENV=production` With `production` the server logs one JSON object per line. Anything else gives colored, readable lines. ### OTEL\_EXPORTER\_OTLP\_ENDPOINT * Type: URL * Default: unset * Example: `OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318` With this or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` set, Clawboo exports traces over OTLP/HTTP for agent Routines on Clawboo Native, Claude Code, Codex and Hermes, and for runs started with `POST /api/runtimes/:id/run`. Team chat runs produce no traces. Without either variable, the local event log is the only trace store. > [!NOTE] > > The npm package does not include the OpenTelemetry SDK, so on an npm install these variables export nothing, and **System Health** reports that the exporter failed to initialize. Export works from a source checkout, where the SDK is installed. ### OTEL\_EXPORTER\_OTLP\_TRACES\_ENDPOINT * Type: URL * Default: unset * Example: `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces` The traces-only OTLP endpoint. Either this or `OTEL_EXPORTER_OTLP_ENDPOINT` turns export on. ### OTEL\_SERVICE\_NAME * Type: string * Default: `clawboo` * Example: `OTEL_SERVICE_NAME=clawboo-office` The service name on exported traces. ## Read from other tools ### CODEX\_HOME * Type: path * Default: `~/.codex` * Example: `CODEX_HOME=/srv/codex` Where Clawboo looks for the sign-in your `codex login` saved (`auth.json`). Each Codex agent then runs with its own Codex home under `~/.clawboo/runtimes/`, which Clawboo sets for that run. ### HERMES\_HOME * Type: path * Default: `~/.hermes` * Example: `HERMES_HOME=/srv/hermes` Where Clawboo looks for your Hermes sign-in (`auth.json`). Each Hermes agent runs with its own Hermes home under `~/.clawboo/runtimes/`. ### CLAUDE\_CODE\_USE\_BEDROCK * Type: flag; any value except empty, `0` or `false` turns it on * Default: unset * Example: `CLAUDE_CODE_USE_BEDROCK=1` Clawboo normally removes `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` from the environment of runtimes it starts. With this set, it keeps them, so Claude Code can use Amazon Bedrock. ### Proxy variables `HTTP_PROXY`, `HTTPS_PROXY` and `ALL_PROXY` (in either case), plus `NO_PROXY` and `no_proxy`. When any proxy variable is set, Clawboo adds `127.0.0.1,localhost,::1` to both `NO_PROXY` and `no_proxy` for the runtimes it starts, keeping any hosts already listed. Their calls to Clawboo's own MCP servers then skip the proxy, which would otherwise see the attach token and break the connection. ### GATEWAY\_AUTH\_TOKEN and other templates Clawboo does not read `GATEWAY_AUTH_TOKEN` as its own setting. When `settings.json` or `openclaw.json` holds a template such as `${GATEWAY_AUTH_TOKEN}` (any `${NAME}` works), Clawboo resolves `NAME` from its own environment first, then from OpenClaw's `.env`. It removes `GATEWAY_AUTH_TOKEN` from the environment of runtimes it starts. ### Operating system variables `PATH` and, on Windows, `PATHEXT`, `ComSpec` and `APPDATA` are read to find and start programs. Your home folder comes from the operating system (`HOME` or `USERPROFILE`). ## Set by Clawboo Clawboo sets these on processes it starts. Do not set them yourself. | Variable | Set on | Value | | - | - | - | | `CLAWBOO_VERSION` | The server the CLI starts | The CLI's version, which the server reports as its own. Without it the server reads its version from its package, or `0.0.0-dev` in a source checkout. The in-app update leaves it out, so the new server reads its version from disk. | | `CLAWBOO_LAUNCH_DIR` | The server the CLI starts | The folder you ran `clawboo` from, offered as "Where you started clawboo" in connector setup. Ignored unless it is an absolute path to an existing folder. | | `CLAWBOO_MCP_BIN_DIR` | The server the CLI starts (normal install) | The bundled `bin` folder, so stdio attach snippets point at the right files. Without it, snippets show a `<path-to>/dist/bin/<server>.js` placeholder. | | `CLAWBOO_API_PORT` with `CLAWBOO_AWAIT_PORT` | A restarted server | The port to keep; the new server waits up to 15 seconds for it to free. | | `NODE_ENV` | The server the CLI starts | `production` | | `CODEX_HOME`, `HERMES_HOME` | Each Codex or Hermes run | That agent's own home folder under `~/.clawboo/runtimes/` | | The runtime's provider key, such as `ANTHROPIC_API_KEY` | Each runtime run | The key found in the environment, the vault or OpenClaw's `.env` | | `NO_PROXY`, `no_proxy` | Each runtime run, when a proxy is set | Your list plus `127.0.0.1,localhost,::1` | | `OPENCLAW_SERVICE_REPAIR_POLICY`, `NO_COLOR` | The `openclaw doctor --fix` repair run | `external` (leave any system Gateway service alone) and `1` | | `CLAWBOO_DB_PATH` | Stdio attach snippets | The dashboard's database file | ## Testing and development ### CLAWBOO\_ENABLE\_MOCK\_RUNTIME * Type: flag, exactly `1` * Default: unset * Example: `CLAWBOO_ENABLE_MOCK_RUNTIME=1` Adds `clawboo-mock`, a fault-injection runtime for reproducing failures such as silence, a crash or a slow start. It runs nothing real: every event comes from directives in the task text. Any other value leaves it hidden from the dashboard and from `GET /api/runtimes`. ### CLAWBOO\_SERVER\_PATH * Type: path * Default: found by walking up from the CLI to a `package.json` named `clawboo` * Example: `CLAWBOO_SERVER_PATH="$HOME/code/clawboo" clawboo` The repository root the CLI starts the server from when there is no bundled server, as in a source checkout. A normal install ignores it. ## What runtimes inherit Runtimes Clawboo starts (Claude Code, Codex, Hermes) and the verification command get the server's environment, minus: * Clawboo's own secrets: `STUDIO_ACCESS_TOKEN`, `CLAWBOO_SECRETS_MASTER_KEY`, `GATEWAY_AUTH_TOKEN`, and any variable starting with `BETTER_AUTH`. * A fixed list of common third-party credentials: GitHub, GitLab and Bitbucket tokens, npm, PyPI and Cargo tokens, hosting and cloud tokens (DigitalOcean, Vercel, Netlify, Cloudflare, Heroku, Fly, Railway), Stripe, Twilio, SendGrid, Mailgun and Slack keys, database URLs and passwords, and Vault, 1Password, Doppler, Sentry, Codecov, Turbo, CircleCI and Docker tokens. * AWS access keys, unless `CLAUDE_CODE_USE_BEDROCK` is set. The removal works by name. Any other secret in the server's environment, including provider keys such as `OPENAI_API_KEY`, reaches every agent those runtimes run. Start Clawboo from a shell that holds only what agents may see. [What agents can do](/concepts/permissions) covers what else they can reach. > [accordion] Source of truth > > Server start: `apps/web/server/index.ts`, `lib/resolveHost.ts`, `lib/portUtils.ts`. Access: `packages/gateway-proxy/src/access-gate.ts`, `origin-guard.ts`. Paths and the base path: `packages/config/src/index.ts`, `basePath.ts`. Keys: `apps/web/server/lib/secretsVault.ts`, `lib/runtimes/descriptor.ts`. Runtime environments: `lib/runtimes/childEnv.ts`. Each timer's variable is read next to its default; [Limits and defaults](/reference/limits-and-defaults) gives the file and line. ## Related * [Configuration](/reference/configuration): the files these variables move, and what is stored where * [CLI reference](/reference/cli): the variables the `clawboo` command reads and sets * [Limits and defaults](/reference/limits-and-defaults): every number, including the ones you cannot change * [Self-host securely](/guides/self-host-securely): `HOST`, the access token and the allowlists, step by step * [Security](/operating/security): what the access gate, the origin check and the vault protect # Events and errors reference Source: https://docs.claw.boo/reference/events-and-errors The 25 orchestration event kinds and their data, the classes Clawboo sorts failures into, the codes in the log, and where each one appears. Clawboo records what its agents do in an append-only event log inside its SQLite database (the `orchestration_events` table). The **Observability** and **Fleet** panels and the **Activity** feeds on tasks, agents and teams all read it. This page lists every event kind with its data, the classes Clawboo sorts failures into, and the codes you may find in the log. These are the orchestration events defined in `@clawboo/obs`. The OpenClaw Gateway's own events, which the dashboard handles in the browser, are a separate stream (see [Event pipeline](/internals/event-pipeline)). ## Event kinds There are 25 kinds. New kinds are only ever added, so older logs keep parsing. | Kind | Group | Records | Written by | Activity badge | | - | - | - | - | - | | [`task_created`](#task_created) | Board | A task was created | Board API, team engine | task | | [`task_claimed`](#task_claimed) | Board | An agent was given a task | Board API, team engine | claim | | [`status_changed`](#status_changed) | Board | A task moved to another status | Board API, team engine | status | | [`comment_added`](#comment_added) | Board | A comment landed on a task | Board API, team engine | note | | [`dep_linked`](#dep_linked) | Board | A task was made to wait for another | Board API, team engine | (hidden) | | [`execution_started`](#execution_started) | Runs | A run of a task started | Task runner, team engine, board API, OpenClaw routine fires | run | | [`execution_completed`](#execution_completed) | Runs | A run finished | Task runner, team engine, board API, OpenClaw routine fires | run | | [`tool_call`](#tool_call) | Runs | An agent called a tool | Task runner, team engine, OpenClaw session watcher | tool | | [`tool_result`](#tool_result) | Runs | A tool returned | Task runner, team engine, OpenClaw session watcher | result | | [`cost`](#cost) | Runs | Spend and tokens reported during a run | Task runner, OpenClaw routine fires | cost | | [`session_rotated`](#session_rotated) | Runs | A run moved to a fresh session | Task runner | session rotated | | [`approval_requested`](#approval_requested) | Approvals | Declared; nothing writes it | None | approval | | [`approval_resolved`](#approval_resolved) | Approvals | An abandoned approval expired | Approval reaper | approval | | [`error`](#error) | Errors | A failure, sorted into an [error class](#error-classes) | Task runner, team engine | error | | [`span_start`](#span_start) | Traces | A task-runner run span opened | Task runner | (hidden) | | [`span_end`](#span_end) | Traces | A run span closed | Task runner | (hidden) | | [`routine_fired`](#routine_fired) | Routines | A due routine was picked up | Routines engine | routine fired | | [`routine_dispatched`](#routine_dispatched) | Routines | A routine's work was handed to its target | Routines engine | routine dispatched | | [`routine_completed`](#routine_completed) | Routines | A routine fire succeeded and the routine re-armed | Routines engine | routine completed | | [`routine_error`](#routine_error) | Routines | A routine fire failed | Routines engine | routine error | | [`team_chat_post`](#team_chat_post) | Peer room | An agent posted through the TeamChat MCP server | TeamChat MCP over HTTP | team chat post | | [`speaker_selected`](#speaker_selected) | Peer room | The agents' exchange picked the next speaker | Team exchange API | speaker selected | | [`turn_bound_hit`](#turn_bound_hit) | Peer room | An agents' exchange ended | Team exchange API | turn bound hit | | [`grant_decision`](#grant_decision) | Connectors | A grant check allowed, paused or refused a connector tool call | Tool broker | grant | | [`connector_health`](#connector_health) | Connectors | A connector's health changed | Connector supervisor | connector | * **Board API** means the board routes the dashboard calls. Board changes made through the [Tasks MCP tools](/reference/mcp-tools) or by the task runner itself update the live board views but are not written to the log as board events. * **Task runner** runs serve `POST /api/runtimes/:id/run` and agent routine fires on runtimes other than OpenClaw. **Team engine** runs serve team chat and the tasks it delegates. See [How a team works](/intro/how-it-works) for the two paths. * **Peer room** events concern the agents' peer room behind the [TeamChat server](/reference/mcp-tools#teamchat-server), not the team chat you use in the dashboard. * A runtime mirror can also push `tool_call`, `tool_result`, `error`, `grant_decision` and `connector_health` through `POST /api/obs/ingest` (see [Writing events from outside](#writing-events-from-outside)). ## Error classes Every failure an `error` event records is sorted into one class by matching its code and message. Anything that matches no class is `Unknown`, which Clawboo treats as a **harness bug**: a failure its run machinery could not explain, worth a look rather than routine noise. | Class | Meaning | Matches, for example (case-insensitive) | Harness bug | | - | - | - | - | | `RateLimited` | The provider throttled the request | `429`, `rate limit`, `too many requests`, `resource exhausted`, `quota` | No | | `UserAborted` | A person or Clawboo stopped the run | `abort`, `aborted`, `cancel`, `cancelled`, `sigint`, `sigterm` | No | | `Timeout` | Something took too long | `timeout`, `timed out`, `etimedout`, `deadline exceeded` | No | | `UnexpectedEnv` | The machine was missing something | `enoent`, `eacces`, `eperm`, `command not found`, `permission denied`, `not installed`, `cannot find module` | No | | `ContextOverflow` | The prompt was too long for the model | `context length`, `context window`, `prompt is too long`, `maximum context`, `too many tokens` | No | | `InvalidArgs` | The request was malformed | `400`, `422`, `invalid argument`, `bad request`, `validation`, `missing required`, `schema`, `malformed` | No | | `ProviderError` | The provider failed | `500`, `502`, `503`, `504`, `provider error`, `upstream`, `overloaded`, `service unavailable`, `bad gateway`, `api error` | No | | `Unknown` | Nothing matched, or the error had no text | Anything else | Yes | | `PolicyDenied` | A guardrail refused a call (expected) | Task-runner runs only; see [PolicyDenied](#policydenied) | No | ### PolicyDenied In a task-runner run, an `error` whose code is `policy_denied`, `permission_denied`, `denied`, `forbidden`, `unauthorized`, `eperm` or `eacces` (in any case) is recorded with `errorClass: 'PolicyDenied'` and `harnessBug: false`. That is how a Clawboo Native agent's refused tool call (a broker denial, or a [Tasks creation cap](/reference/mcp-tools#creation-caps)) is logged. Two such denials in a row in one run trip the circuit breaker (`repeat-policy-denied`). The team engine does not have this rule: it runs every error through the classifier. A tool denial in team chat therefore gets whichever class its message matches, often `Unknown`, and is then counted as a harness bug. ### Where errors show up | Place | What you see | | - | - | | **Settings > Insights > Observability**, **Error taxonomy** | Recent errors grouped by class with a count each; an `Unknown` row carries a **harness bug** label. "No errors" means none in the latest 500 | | **Observability** trace timeline | Each error as `[<class>] <message>` | | **Settings > Insights > Fleet**, **Recent issues** | Class, runtime and message per error; "All clear" when there are none | | A runtime's diagnostics drawer in **Runtimes** | **Recent errors** with class and message | | **Activity** on a task, agent or team | An **error** badge with the message | The error lists read the latest 500 `error` events, and the harness-bug count covers those 500 only. > [!TIP] > > A harness bug is not something your agent did wrong. It usually means a runtime returned an error message Clawboo does not recognize. If one keeps appearing, note the message (task-runner runs also write an error-level server log line with `"action": "harness_bug"`) and [open an issue](https://github.com/clawboo/clawboo/issues). ## The event envelope Every event has the same envelope, whatever its kind: ```ts theme={"theme":"github-dark"} { id: string // a UUID seq?: number // assigned by SQLite when the row is written ts: number // when the event happened, epoch milliseconds kind: OrchestrationEventKind // one of the 25 kinds teamId?: string | null taskId?: string | null agentId?: string | null runtime?: string | null traceId?: string | null spanId?: string | null parentSpanId?: string | null correlationId?: string | null tenantId?: string | null data: Record<string, unknown> // kind-specific; defaults to {} } ``` | Field | Meaning | | - | - | | `seq` | Increases with every row and is never reused, across every process writing the file. Order the log by `seq`. | | `ts` | When the writer says the event happened. The table also stores `created_at`, the insert time. | | `teamId`, `taskId`, `agentId`, `runtime` | What the event concerns. Any of them can be null. | | `traceId`, `spanId`, `parentSpanId` | Only on task-runner runs. Every run under one root task shares a trace, whose `traceId` is that root task's id; each run is a span. | | `correlationId` | An optional grouping key; most events leave it empty. | | `tenantId` | Unused; always null. | | `data` | The kind's payload, described below. It is an open object: readers must tolerate missing or extra fields. | That is 13 fields plus `data`. Before a row is stored, secrets in `data` are scrubbed, and an unrecognized `kind` is stored as `error`. Writing an event never fails the work that produced it: a failed write is dropped. Nothing deletes rows from the log, so it grows until you reset or trim the database (see [Data and state](/operating/data-and-state)). ## Board events Board events carry the task in `taskId` and its team in `teamId`. ### task\_created A task was created. ```ts theme={"theme":"github-dark"} { title?: string | null; status?: string; parentTaskId?: string | null; priority?: string | null } ``` ### task\_claimed An agent was given a task, by a claim or an assignment. ```ts theme={"theme":"github-dark"} { assigneeAgentId?: string | null; assigneeRuntime?: string | null } ``` ### status\_changed A task moved to another status. `to` is always present. ```ts theme={"theme":"github-dark"} { from?: string | null; to: string } ``` Some writers add fields the typed shape does not declare: * `by: 'person'` marks a move a person made on the board. * `reason` says why the server moved it: `retry` (a person pressed Retry), `assigned` (a person assigned the task), or `blocker_failed` (a task it waited on failed or was cancelled, so it was cancelled too). ### comment\_added A comment or system note landed on a task. ```ts theme={"theme":"github-dark"} { authorType?: string; body?: string } ``` ### dep\_linked A task was made to wait for another. The task that waits is `taskId`. ```ts theme={"theme":"github-dark"} { dependsOnTaskId: string } ``` ## Run events ### execution\_started A run of a task started. `execId` is the row in the `execution_processes` table. ```ts theme={"theme":"github-dark"} { execId: string; executorType?: string; runReason?: string | null } ``` ### execution\_completed A run finished. The cost and token fields carry the run's final totals (see [Cost totals](#cost-totals)). ```ts theme={"theme":"github-dark"} { execId: string status: string // the run's final status, such as 'succeeded', 'failed' or 'cancelled' costUsd?: number | null inputTokens?: number outputTokens?: number error?: string | null } ``` On task-runner runs, `error` holds a code when Clawboo stopped or refused the run: | `error` | `status` | Meaning | | - | - | - | | `workspace_unavailable` | `failed` | A file-changing task had no repository, or its git worktree could not be created, so the run was refused | | `budget_paused:<scope>` | `cancelled` | A hard-cap budget stopped the run; `<scope>` is `agent`, `mission`, `team`, or `node` (a routine's per-run cost cap) | | `circuit_broken:<reason>` | `cancelled` | A circuit breaker stopped the run; see the reasons below | | Anything else | `failed` or `cancelled` | The run's own error summary | Breaker reasons, also posted on the task as a comment that begins `[stopped: <reason>]`: | Reason | Trips when | | - | - | | `iteration-cap` | The run made too many tool calls | | `repeat-failure` | The same tool call failed several times in a row | | `no-progress` | Tool calls kept failing without any new successful result | | `token-velocity` | Tokens per minute (input plus output) passed the ceiling | | `repeat-policy-denied` | Guardrails refused the run's calls twice in a row | Budgets and breakers are explained in [Governance](/concepts/governance); their defaults are in [Limits and defaults](/reference/limits-and-defaults). ### tool\_call An agent called a tool. `toolCallId` matches the call's `tool_result`. Secrets in `input` are scrubbed. ```ts theme={"theme":"github-dark"} { toolCallId: string; name: string; input?: unknown; grantId?: string | null; connectorId?: string | null } ``` `grantId` and `connectorId` are declared for attribution, but Clawboo's own writers leave them out. Grant attribution for connector calls is in [`grant_decision`](#grant_decision) events and the tool audit. ### tool\_result A tool returned. `isError` feeds the tool error rate in the trace metrics. ```ts theme={"theme":"github-dark"} { toolCallId: string; name: string; isError: boolean; output?: string } ``` ### cost Spend and tokens reported during a run. Several can arrive for one run; see [Cost totals](#cost-totals). ```ts theme={"theme":"github-dark"} { costUsd?: number | null inputTokens?: number outputTokens?: number model?: string | null estimated?: boolean // task-runner runs: Clawboo priced the figure itself local?: true // task-runner runs: a model the user's own Ollama served, recorded at $0 } ``` Team chat runs write no `cost` events; their spend is charged to budgets directly. See [Budgets and spend](/using/budgets-and-spend) for which runtimes report real cost and which are estimated. ### session\_rotated A task-runner run reached its turn limit or filled its context, and continued in a fresh session. The new session starts from a short handoff note, not the old transcript. ```ts theme={"theme":"github-dark"} { from: string // the previous session's key to: string // the new session's key reason: 'max_turns' | 'context_watermark' tokensUsed?: number // tokens the previous session used rotationIndex?: number // 1 for the first rotation in the task's run chain } ``` ## Approval events ### approval\_requested Declared, but nothing writes it. Pending tool approvals live in the `tool_call_approvals` table and appear under **Needs you** on the Board (see [Approvals](/using/approvals)). ```ts theme={"theme":"github-dark"} { approvalId?: string; scopeKey?: string; kind?: string } ``` ### approval\_resolved Written only when the approval reaper expires an approval nobody answered. The reaper runs hourly and expires pending approvals older than 24 hours (`CLAWBOO_APPROVAL_TTL_MS`). A person's decision is stored on the approval row itself, not as an event. ```ts theme={"theme":"github-dark"} { approvalId?: string; decision?: string } // decision is 'expired' ``` ## Error events ### error A failure during a run, with its [error class](#error-classes). ```ts theme={"theme":"github-dark"} { code?: string | null message: string errorClass: string // an error class, or 'PolicyDenied' harnessBug: boolean // true when errorClass is 'Unknown' fatal?: boolean } ``` * The task runner writes one for each error a runtime reports, and one more (with `code: null` and `fatal: true`) when a run ends in an error. * The team engine writes one for each error a runtime reports during team chat. * Classification joins `code` and `message`, and the rules are tried in the order of the [classes table](#error-classes), first match wins. `ContextOverflow` is tried before `InvalidArgs`, so an oversized prompt that a provider rejects with a 400 counts as a context overflow. ## Trace events ### span\_start The task runner opens one span per run, named `run:<taskId>`, with `spanKind: 'task'`. A child task's run nests under its parent task's run. ```ts theme={"theme":"github-dark"} { name: string; spanKind?: 'task' | 'tool' | 'run' } ``` ### span\_end The run's span closed. ```ts theme={"theme":"github-dark"} { name: string; status?: 'ok' | 'error'; durationMs?: number } ``` Team chat runs, OpenClaw routine fires and board API events carry no trace, so they never appear in the **Traces** list. When OpenTelemetry export is set up, task-runner spans also go to your collector; see [Observability](/concepts/observability). ## Routine events Routine events carry the routine's ledger row in `data.scheduledRunId` (a `scheduled_runs` row). See [Routines](/using/routines). ### routine\_fired A due routine was picked up for firing. ```ts theme={"theme":"github-dark"} { scheduledRunId: string cronSpec: string scheduledBy: string } // scheduledBy is 'clawboo' for the Routines engine ``` ### routine\_dispatched The fire was handed to its target. ```ts theme={"theme":"github-dark"} { scheduledRunId: string taskId: string | null // null for a team routine, which creates no task runtime: string | null dispatchPath: 'one-shot' | 'connected' | 'human' | 'team-chat' targetAgentId?: string | null // team routines: the agent the message went to } ``` `dispatchPath` is `one-shot` when the task runner ran it, `connected` when it went to the OpenClaw Gateway, and `team-chat` when a team routine posted its message to the team chat. The type also allows `human`, which nothing writes. ### routine\_completed The fire succeeded and the routine re-armed. ```ts theme={"theme":"github-dark"} { scheduledRunId: string; taskId?: string | null; status: string; nextRunAt?: number | null } ``` `status` is `idle`. `nextRunAt` is the next fire time, or `null` when nothing is left to fire (a spent one-time routine). ### routine\_error The fire failed. The routine stops in an error state until someone resumes it. ```ts theme={"theme":"github-dark"} { scheduledRunId: string; code?: string | null; message: string } // code is declared but not written ``` | `message`, for example | Meaning | | - | - | | `dispatch refused: <reason>` | The task runner refused the run: `workspace_unavailable`, `budget_paused`, `too_deep`, `connected_substrate` or `not_found` | | `run <reason>: <summary>` | The run ended without success; `<reason>` is `max_turns`, `aborted` or `error` | | `The agent "<name>" was removed.` | The routine's agent is archived | | `agent <id> not found` | The routine's agent no longer exists | | `The team "<name>" is archived.` | A team routine's team is archived | | `The team this routine posts to no longer exists.` | A team routine's team was deleted | | `invalid task template` | The stored routine could not be read | | `dispatch failed` | The dispatch failed without a message | ## Peer room events These concern the agents' peer room (the `team_chat` table), which the dashboard does not show. See [Peer chat](/concepts/peer-chat). ### team\_chat\_post A bound agent posted through the [TeamChat MCP server](/reference/mcp-tools#teamchat-server) over HTTP, so this covers Claude Code, Codex and Hermes agents. Posts that Clawboo Native agents make in-process are not logged. ```ts theme={"theme":"github-dark"} { roomId: string seq: number authorAgentId: string postKind: 'peer' | 'system' | 'user' } ``` `seq` is the post's position in its room. `authorAgentId` comes from the session's signed binding, never from tool arguments. ### speaker\_selected The agents' exchange (`POST /api/team-chat/exchange`) picked the next agent to speak. ```ts theme={"theme":"github-dark"} { roomId: string speakerAgentId: string policy: 'leader-nominated' | 'round-robin' exchangeTurn: number } // exchangeTurn starts at 1 ``` ### turn\_bound\_hit An agents' exchange ended. ```ts theme={"theme":"github-dark"} { roomId: string reason: string maxExchangeTurns: number turnsTaken: number } ``` `reason` is `max_turns` (the turn cap was reached with replies still owed), `no_pending_obligation` (nobody owed a reply), `budget_paused` (a paused hard-cap budget refused a turn, or a turn's spend paused one) or `aborted` (the caller disconnected). The typed shape lists only the first two. ## Connector events ### grant\_decision The tool broker checked a connector tool call against the caller's grants. Builtin tools are not governed by grants and write none. It is its own kind because a refused call produces no `tool_call`. ```ts theme={"theme":"github-dark"} { decision: string // 'allow' | 'require_approval' | 'deny' reason?: string | null // null when allowed grantId?: string | null connectorId?: string | null toolName: string ruleId?: string | null // set when a remembered decision ("Always") decided it } ``` The **Activity** feed shows it as `<decision>: <reason>` under a **grant** badge. Reasons, in the order the check applies them: | `reason` | `decision` | Meaning | | - | - | - | | `no-grant` | `deny` | No grant gives this agent, its team, or every agent the connector | | `grant-revoked` | `deny` | The grant was revoked | | `grant-suspended` | `deny` | The grant is suspended | | `grant-proposed` | `deny` | The grant was proposed but not approved | | `grant-expired` | `deny` | The grant has expired | | `spec-drift` | `deny` | The connector's definition or tool list changed since the grant was made | | `tool-not-in-scope` | `deny` | The grant's tool allow and deny lists exclude this tool | | `mode-insufficient` | `deny` | The tool needs a higher grant mode (`read`, then `write`, then `admin` for destructive tools) | | `rate-limited` | `deny` | The grant's hourly call limit was reached | | `lethal-trifecta` | `require_approval` | The tool can read private data, take in untrusted content and send data out. Never remembered | | `tainted-run` | `require_approval` | Defined, but nothing marks a run tainted, so it does not occur | | `standing-deny` | `deny` | A remembered "deny" rule matched | | `never-remembered` | `require_approval` | This tool always asks, even with a remembered "Always" on file | | `policy-always` | `require_approval` | The grant asks for approval on every call, even with a remembered "Always" on file. Never remembered | | `policy-writes` | `require_approval` | The grant asks for approval on every call that changes something | | `risk-destructive` | `require_approval` | The tool is destructive | | `risk-external` | `require_approval` | The tool acts outside Clawboo and changes something | A call the grant check allows can still ask for approval later in the broker, for example a destructive tool. See [Connectors](/using/connectors) and [Approvals](/using/approvals). ### connector\_health A connector's health changed. ```ts theme={"theme":"github-dark"} { connectorId: string; health: string; detail?: string | null; failures?: number } ``` `health` is one of `unknown`, `ok`, `needs-auth`, `degraded`, `error` or `drift`. The server writes this event with `health: 'error'` and `detail: 'the connector process exited'` when a connector's process stops. The other values can arrive through `POST /api/obs/ingest`. `drift` means the connector's tools no longer match what was approved, so review what changed before trusting it again. ## Cost totals `cost` events add up during a run, and `execution_completed` carries the run's total. Some runtimes report cost only when the run ends. So the trace metrics and the graph take, for each run (keyed by `taskId`), the larger of the summed `cost` events and the completed total. Nothing is counted twice, and a run with no `cost` events still shows its total. ## Fleet health The **Fleet health** list in the **Observability** panel gives each agent a status from its recent events: | Status | Rule | | - | - | | `working` | Has an open run (an `execution_started` without its `execution_completed`) and an event in the last 5 minutes | | `stalled` | Has an open run and no event for 5 minutes | | `zombie` | Has an open run and no event for 30 minutes; its process is probably gone | | `idle` | Has no open run | The panel reads the latest 5,000 events, and a single trace returns at most 5,000 events. ## Writing events from outside `POST /api/obs/ingest` lets a process on this machine mirror runtime events into the log. The body is `{ "events": [ ... ] }`, and each event may set `kind`, `ts`, `teamId`, `taskId`, `agentId`, `runtime` and `data`. * Only `tool_call`, `tool_result`, `error`, `grant_decision` and `connector_health` are accepted, so board, cost, approval and routine events cannot be forged this way. Other kinds are skipped. * At most 200 events per request are read. The answer is `{ "ok": true, "count": <stored> }`. * A `ts` more than 60 seconds ahead of the server's clock, or more than 24 hours behind it, is replaced with the server's time. A missing `runtime` is stored as `openclaw`. * For `grant_decision` and `connector_health`, unknown fields are dropped, text is redacted and cut to 200 characters (ids to 120), an unknown `health` becomes `unknown`, and an unknown `decision` becomes `deny`. The route sits behind the access gate like every `/api/` route. See the [Observability API](/reference/rest-api/observability). ## Structured log lines Alerts such as the harness-bug alert are written to the server log as structured entries in this shape, with secrets scrubbed: ```ts theme={"theme":"github-dark"} { ts: number level: 'debug' | 'info' | 'warn' | 'error' component: string action: string durationMs?: number input?: unknown output?: unknown error?: string correlationId: string traceId?: string | null spanId?: string | null taskId?: string | null agentId?: string | null runtime?: string | null } ``` The harness-bug alert is an `error` entry with `component: 'runtime'`, `action: 'harness_bug'`, `error: '[<class>] <message>'` and the run's execution id in `correlationId`. Only task-runner runs write it. Where the server log lives is in the [CLI reference](/reference/cli). ## Related * [Observability](/concepts/observability): what Clawboo records and how to read it. * [Observability API](/reference/rest-api/observability): the routes that serve the log, traces, errors and fleet health. * [Governance](/concepts/governance): budgets, caps and circuit breakers. * [MCP tools](/reference/mcp-tools): the tools whose calls and refusals appear here. * [Database schema](/reference/database-schema#orchestration_events): the table that stores the log. # Reference Source: https://docs.claw.boo/reference/index Exact facts about Clawboo, checked against the code: commands, configuration, environment variables, limits, shortcuts, MCP tools, events, the database and the REST API. Reference pages state exact facts: names, defaults, limits and formats, checked against the code. Come here to look something up. For step-by-step help, start at [Use Clawboo](/using/index); for how the pieces fit together, see [Key concepts](/concepts/index). ## Pages in this section | Page | Look here for | | - | - | | [CLI](/reference/cli) | Every `clawboo` command and flag, how it finds and stops the server, the server log, exit codes, and the MCP stdio bins | | [Configuration](/reference/configuration) | Where each setting lives: `settings.json`, the database, your browser, the files in `~/.clawboo`, and what Clawboo writes in `~/.openclaw` | | [Environment variables](/reference/environment-variables) | Every variable Clawboo reads or sets, with its type, default, effect and an example | | [Limits and defaults](/reference/limits-and-defaults) | Every built-in number: delegation caps, watchdogs, breakers, approval windows, rate limits, ports and timeouts | | [Keyboard shortcuts](/reference/keyboard-shortcuts) | Every dashboard shortcut, the chat slash commands, and Settings search | | [MCP tools](/reference/mcp-tools) | The four MCP servers (Tasks, Memory, Tools, TeamChat) and every tool's inputs | | [Events and errors](/reference/events-and-errors) | The 25 kinds of event in the event log and the error classes a run can end with | | [Database schema](/reference/database-schema) | The 35 tables in `clawboo.db` and their columns | | [Marketplace catalog](/reference/marketplace-catalog) | The 19 packs behind the marketplace's 436 agents and 85 teams, and how Clawboo fetches and checks them | ## REST API and MCP The REST API has its own group in this section. Start at the [REST overview](/reference/rest-api/index) for the address, authentication, rate limits and errors, and an index of all 199 routes; each resource has its own page from there. MCP has two pages: [MCP tools](/reference/mcp-tools) lists every tool and its inputs, and [MCP servers](/operating/mcp-servers), under self-hosting, shows how to attach an agent you run yourself. ## Packages Clawboo's code is split into 33 workspace packages under `packages/`: 27 at the top level and 6 runtime adapters, one of which is a test runtime. They are documented for contributors in the Contribute tab, starting at the [Packages overview](/reference/packages/index). ## Related * [Use Clawboo](/using/index): how-to guides for every part of the dashboard * [Self-hosting](/operating/index): deployment, security, and data and state * [Help](/help/troubleshooting): fixes for errors you may see * [Glossary](/appendices/glossary): what each term means, including old names # Keyboard shortcuts Source: https://docs.claw.boo/reference/keyboard-shortcuts Every keyboard shortcut in the Clawboo dashboard, by surface, plus the chat slash commands and how Settings search finds a panel. Every shortcut the dashboard handles, grouped by where it works. On macOS use `Cmd`; on Windows and Linux use `Ctrl`. The tables write `Cmd/Ctrl` for both. | Surface | Shortcuts | | - | - | | [Anywhere](#anywhere) | Jump to a view, open Settings, show or hide the sidebar, Escape | | [Chat](#chat) | Send, new line, mentions, steering a task, room names | | [Slash commands](#slash-commands) | `/reset`, `/new`, `/rule` | | [Board](#board) | New task, views, today, previous and next, Find, the task drawer, the date picker | | [Org chart and team graph](#org-chart-and-team-graph) | Expand a Boo, its menu, remove a connection, the add picker | | [Settings](#settings) | Search, close | | [Editors and dialogs](#editors-and-dialogs) | Save, confirm, cancel, onboarding | ## Anywhere | Shortcut | What it does | | - | - | | `Cmd/Ctrl+1` | Open the Org chart | | `Cmd/Ctrl+2` | Open the Board | | `Cmd/Ctrl+3` | Open Connectors | | `Cmd/Ctrl+4` | Open Memory | | `Cmd/Ctrl+,` | Open Settings | | `Cmd/Ctrl+\` | Show or hide the sidebar | | `Escape` | Close the topmost menu, popover or dialog. With none open, close Settings; with Settings closed, leave an agent, Boo Zero or team chat for the **Welcome to Clawboo** screen | `Cmd/Ctrl+1` to `4`, `Cmd/Ctrl+,` and the view-leaving `Escape` do nothing while you type in a text field or while a dialog has the keyboard. `Cmd/Ctrl+\` works everywhere, even while you type. The sidebar's resize handle (**Resize sidebar**) takes the keyboard too: focus it, then use the left and right arrow keys to make the sidebar narrower or wider. Left at the narrowest width, or `Enter`, collapses it. Double-click the handle to reset its width. ## Chat These work in 1:1 chats, team chat and rooms. | Shortcut | What it does | | - | - | | `Enter` | Send the message | | `Shift+Enter` | Start a new line | | `@` | Open the mention list: teammates in team chat, teams in Boo Zero's chat | | `Up` / `Down` | Move through the mention list while it is open | | `Enter` or `Tab` | Insert the highlighted mention | | `Escape` | Close the mention list | In a team's **Tasks** card, the **Comment or steer this task** box sends with `Enter` (`Shift+Enter` for a new line), and `Escape` inside an open task returns to the task list. When you rename a room, `Enter` saves the name and `Escape` cancels. In **Search agents**, `Escape` clears the search and closes it. ## Slash commands Type these as the whole message. | Command | Where | What it does | | - | - | - | | `/reset` or `/new` | Any chat | The agent stops carrying the conversation so far. Your messages stay, and a divider marks the point. Not case-sensitive. | | `/rule <text>` | Team chat | Adds `<text>` to the team's rules. It needs a space and some text after `/rule`. | [Team chat](/using/team-chat) covers starting fresh and clearing a chat; [Teams](/using/teams) covers team rules. ## Board These work while the board has focus, and not while you type, drag a card, or have a task, the new-task form, a Routine dialog or Settings open. | Shortcut | What it does | | - | - | | `C` | Open **New task** | | `K` | Switch to **Kanban** (`A` also works) | | `D` | Switch to **Today** | | `W` | Switch to **Week** | | `M` | Switch to **Month** | | `T` | Go to today (in **Today**, **Week** and **Month**) | | `Left` / `Right` | Previous or next week or month (in **Week** and **Month**) | | `/` | Open **Find** | | `Escape` | Clear a picked date | While focus is inside the calendar grid, the left and right arrow keys scroll it instead. `Cmd/Ctrl+K` also opens **Find**, from anywhere on the Board page and even while you type in a field. It does nothing while a dialog, a menu or the task drawer is open. In the task drawer, `J` opens the next task in the card's column and `K` the previous one, the same as the up and down arrow buttons at the top of the drawer. They do nothing while focus is in a field or an open list. `Escape` closes the drawer. In **Find**, focus stays in the search field: `Up` and `Down` move through the results, `Enter` shows the highlighted task on the board, and `Shift+Enter` opens it. A Routine always opens. In the date picker (click the date range title, **Choose a date**), the arrow keys move a day or a week, `Page Up` and `Page Down` move a month, `Home` and `End` go to the start and end of the week, and `Enter` picks the day. When you mark a task done or cancel it, `Enter` confirms the dialog and the arrow keys move between the day choices. ## Org chart and team graph The same keys work on the Org chart and on a team's graph card. Use `Tab` to focus a Boo first. | Shortcut | What it does | | - | - | | `Enter` or `Space` on a Boo | Expand or collapse the tiles around it | | `Menu` key, `Shift+F10` or `Alt+Enter` on a Boo | Open its menu (**Open agent**, **Delete**). On a Mac, `Option+Enter`. | | Arrow keys on a selected Boo | Move it, unless the canvas is locked | | `Backspace` or `Delete` with a connection selected | Remove that connection: a routing link, an added skill or a shared connector | A connection that came with the agent or its runtime cannot be removed; the graph says why instead. When you drag a thread from a Boo to empty space, the picker that opens takes `Up` and `Down` to move, `Enter` to pick, `Right` to open a category, `Left` or `Backspace` (in an empty search) to go back, and `Tab` to close. ## Settings When **Settings** opens, the **Search settings** field has focus. Type to narrow the list to panels whose name or keywords contain what you typed. For example, `cron` finds Routines, `api key` finds Providers and System, `budget` finds Tokens Used and Governance, and `traces` finds Observability. | Shortcut | What it does | | - | - | | `Cmd/Ctrl+,` | Open Settings | | `Tab` / `Shift+Tab` | Move between controls; focus stays in Settings | | `Escape` | Close Settings (an open picker inside it first) | ## Editors and dialogs | Where | Shortcut | What it does | | - | - | - | | An agent file editor (**Agent Files**, or the full-screen editor) | `Cmd/Ctrl+S` | Save the file | | The full-screen agent file editor | `Escape` | Save any unsaved files and close | | **Personality**, with **Use Custom Instructions** on | `Cmd/Ctrl+S` | Save the custom instructions (they also save when you leave the field) | | A confirmation dialog | `Enter` | Confirm | | A confirmation dialog | `Escape` | Cancel | | Onboarding | `Escape` | Go back one step (onboarding cannot be dismissed) | | A field for an API key or Gateway address | `Enter` | Save or connect, the same as the button next to it | The file editors also take the usual undo and redo keys. > [accordion] Under the hood > > Source of truth: `apps/web/src/features/layout/ContentArea.tsx` (view shortcuts and `Escape`), `features/layout/AppSidebar.tsx` (sidebar), `features/chat/chatComponents.tsx` (composer), `features/chat/resetConversation.ts` and `lib/teamRules.ts` (slash commands), `features/board/BoardPanel.tsx`, `features/board/TaskDetailDrawer.tsx` and `features/board/calendar/` (board), `features/graph/graphKeyAction.ts` and `features/graph/ThreadPicker.tsx` (graph), `features/settings/SettingsModal.tsx` (Settings search and its keywords), `features/shared/useDismissableLayer.ts` (one `Escape` closes one layer). ## Related * [Dashboard tour](/getting-started/dashboard-tour): where each view lives * [Board](/using/board): views, the toolbar, Find and the task drawer * [Org chart](/using/org-chart): what the tiles and connections mean * [Team chat](/using/team-chat): rooms, mentions, `/reset` and Clear chat # Limits and defaults Source: https://docs.claw.boo/reference/limits-and-defaults Every built-in number in Clawboo: delegation caps, watchdogs, breakers, approval windows, rate limits, ports and timeouts, with the variable to change it and its source. This page lists every number Clawboo builds in that changes what you see: caps, timeouts, windows, retention and ports. Each row names the variable that changes it, if any, and the line of code that sets it, so maintainers can check it. Some limits apply to only one of the two ways work runs. **Team chat** covers every chat turn, delegated task and team Routine. **Task runner** covers agent Routines on Clawboo Native, Claude Code, Codex and Hermes, and runs started with `POST /api/runtimes/:id/run`. [How a team works](/intro/how-it-works) explains both. | Area | What it covers | | - | - | | [Delegation](#delegation) | Depth, fan-out, subtask caps, watchdogs | | [Runs and safety stops](#runs-and-safety-stops) | Silence timeout, turn limit, context rotation, circuit breakers | | [Verification](#verification) | Attempts, the check command, when a reviewer runs | | [Board and Routines](#board-and-routines) | Heartbeats, stale tasks, auto-refire, worktree cleanup | | [What reaches an agent](#what-reaches-an-agent) | Catch-up, inbox digest, tool results, memory injection | | [Memory](#memory) | Search limits, ranking, embedding checks | | [Approvals and budgets](#approvals-and-budgets) | Approval windows, remembered answers, budget warnings | | [Agent browsers](#agent-browsers) | Browsers at once, idle close, saved screenshots | | [Network and HTTP](#network-and-http) | Ports, rate limits, request sizes | | [Server and CLI](#server-and-cli) | Start, stop, logs, health checks, the database | | [Updates and monitoring](#updates-and-monitoring) | Catalog and update checks, Fleet health thresholds | ## Delegation | Limit | Value | Change it | Source | | - | - | - | - | | Delegation depth: a task with this many parent tasks cannot delegate or get subtasks | 2 | No | `packages/governance/src/caps/predicates.ts:12` | | Delegations one turn can start (team chat); the rest are skipped with a note | 8 | No | `apps/web/server/lib/teamChat/teamOrchestrator.ts:56` | | Subtasks under one parent task, finished ones included (Tasks MCP) | 24 | No | `packages/governance/src/caps/predicates.ts:19` | | New top-level tasks an agent can create (Tasks MCP) | 30 per 5 minutes | No | `packages/governance/src/caps/predicates.ts:28-29` | | Failures in a row of the same agent on the same task before re-delegation is refused | 3 | No | `packages/team-orchestration/src/boardOrchestration.ts:104` | | Silence before a delegated task is failed and the leader told (team chat) | 8 minutes | No | `packages/team-orchestration/src/boardOrchestration.ts:62` | | The same, while inside one tool call or waiting to start | 24 minutes | No | `packages/team-orchestration/src/boardOrchestration.ts:89,96` | | How often that watchdog checks | 30 seconds | No | `apps/web/server/lib/teamChat/teamOrchestrator.ts:57` | | Window for batching task updates to the leader | 3 seconds | No | `packages/team-orchestration/src/boardOrchestration.ts:52` | The board's own REST API (`POST /api/board`) is not subject to the Tasks MCP caps. [Governance](/concepts/governance) explains the caps and when each applies. ## Runs and safety stops | Limit | Value | Change it | Source | | - | - | - | - | | Silence before a run is stopped (team chat, task runner, one-to-one chat) | 30 minutes, then 5 seconds for it to wind down | `CLAWBOO_RUN_SILENT_TIMEOUT_MS` | `packages/executor/src/idleTimeout.ts:18,22` | | Wait for the same agent's previous run to finish | 10 minutes | `CLAWBOO_HOME_MUTEX_ACQUIRE_MS` | `apps/web/server/lib/executorRunner.ts:142` | | Model turns per Clawboo Native run (one model call plus its tool calls each) | 16 | Per agent config, not in the dashboard | `packages/adapters/native/src/agentConfig.ts:97` | | Context rotation: start a fresh session at this share of the context window (task runner) | 85% | No | `packages/executor/src/session-rotation.ts:147` | | Rotations per task (task runner) | 3 | Per run (`maxRotations` on `POST /api/runtimes/:id/run`) | `packages/executor/src/session-rotation.ts:148` | | Breaker: tool calls in one run (task runner) | Stops past 30 | Per run (`breakerConfig`) | `packages/governance/src/breaker/policy.ts:30` | | Breaker: the same tool call failing with the same input, in a row | 3 | Per run (`breakerConfig`) | `packages/governance/src/breaker/policy.ts:31` | | Breaker: tool results in a row that add no new output | 6 | Per run (`breakerConfig`) | `packages/governance/src/breaker/policy.ts:32` | | Breaker: token rate, measured once the run spans 15 seconds | Over 200,000 tokens per minute | Per run (`breakerConfig`) | `packages/governance/src/breaker/policy.ts:33-34` | | Breaker: the same policy denial in a row | 2 | Per run (`breakerConfig`) | `packages/governance/src/breaker/policy.ts:35` | Circuit breakers, context rotation and the per-run settings exist only on the task runner. Team chat runs have the silence timeout, the delegation watchdog and, on Clawboo Native, the turn limit. ## Verification Checks run only for task-runner work in a git worktree with changes; [How work is checked](/concepts/verification) explains when. | Limit | Value | Change it | Source | | - | - | - | - | | Attempts before a failing task goes to **Needs you** | 2 (one fix cycle) | `CLAWBOO_MAX_FIX_CYCLES` | `apps/web/server/lib/verification/index.ts:101` | | Time limit for the verify command | 10 minutes | No | `apps/web/server/lib/verification/deterministicGate.ts:25` | | Command output kept as evidence | The last 4,000 characters | No | `apps/web/server/lib/verification/deterministicGate.ts:26` | | A reviewer also checks the work when | It was delegated, or changes over 5 files or over 300 lines | No | `packages/governance/src/verify/criticTrigger.ts:21-30` | ## Board and Routines | Limit | Value | Change it | Source | | - | - | - | - | | Heartbeat from a running task | Every 30 seconds | No | `packages/db/src/board/repository.ts:427` | | Missed heartbeats before an **In progress** task returns to **To do** | 3 minutes | `CLAWBOO_BOARD_STALE_TTL_MS` | `apps/web/server/index.ts:526` | | How often that check runs | Every minute, and once at startup | `CLAWBOO_BOARD_STALE_SWEEP_MS` | `apps/web/server/index.ts:527` | | Waking delegated work that is ready to run | 10 seconds after startup, then every minute | `CLAWBOO_DISPATCH_PUMP_MS` (the interval) | `apps/web/server/index.ts:553,589` | | Failed or timed-out runs in a row before a task stops being retried on its own | 3 (a Stop is never retried) | No | `packages/board-core/src/dispatch.ts:33` | | Text of your comment passed to the agent working on the task | The first 1,000 characters | No | `apps/web/server/lib/teamChat/taskSteer.ts:27` | | Replies to a waiting task that start one run between them | Sent within 15 seconds of each other | No | `apps/web/server/lib/teamChat/personReply.ts:53` | | Wait for a reply to reach a running task before it reads as queued | 2.5 seconds | No | `apps/web/server/lib/teamChat/personReply.ts:81` | | Room name length | 80 characters | No | `packages/db/src/board/rooms.ts:24` | | Worktree cleanup at startup | Older than 72 hours, or beyond the newest 25 | No | `packages/worktrees/src/lifecycle.ts:60-61` | | Time limit for each git command | 60 seconds | No | `packages/worktrees/src/git.ts:16` | | Routines: how far ahead the scheduler sleeps at most | 60 seconds | No | `apps/web/server/lib/routines/ticker.ts:32` | | Routines: wait for an OpenClaw agent's run | 10 minutes | `CLAWBOO_ROUTINE_OPENCLAW_TIMEOUT_MS` | `apps/web/server/lib/routines/openclawDispatch.ts:46` | Cleanup commits a worktree's changes before removing it and keeps its branch. ## What reaches an agent | Limit | Value | Change it | Source | | - | - | - | - | | Teammates' new peer-room posts added to a turn | 4,000 characters, at most 800 per post | No | `apps/web/server/lib/teamChat/peerCatchup.ts:25,27` | | Inbox notices added to a turn | 4,000 characters, at most 400 per notice | No | `packages/db/src/inbox.ts:102,98` | | Undelivered inbox notices kept | 30 days | No | `packages/db/src/inbox.ts:176` | | One tool result in an agent's context | 16 KiB; the rest is cut and kept for the agent to read | No | `packages/mcp/src/ceiling.ts:35` | | Stored copy of a large tool result | Up to 8 MiB | No | `packages/db/src/tools/resultStore.ts:45` | | How long stored tool results are kept | 7 days | `CLAWBOO_TOOL_RESULT_TTL_MS` | `apps/web/server/lib/approvalReaper.ts:29` | | Memory facts added at the start of a run (task runner) | Top 5, within 1,500 characters | Per run (`disableMemoryAutoInject` turns it off) | `apps/web/server/lib/defaults.ts:53,56` | | Related facts added alongside them | Up to 2, at similarity 0.8 or more | No | `apps/web/server/lib/memoryInjection.ts:81-82` | [What an agent sees](/concepts/agent-context) explains what goes into each turn. ## Memory | Limit | Value | Change it | Source | | - | - | - | - | | Search results | 10 by default, at most 100 | Per search | `packages/db/src/memory/store.ts:54`, `packages/mcp/src/memory/server.ts:183` | | Facts per browse | At most 200 | Per call | `packages/mcp/src/memory/server.ts:205` | | Hybrid ranking | 0.6 meaning match plus 0.4 keyword match | No | `packages/db/src/memory/store.ts:588` | | Half-life of feedback on a fact | 30 days | No | `packages/db/src/memory/learning.ts:44` | | Re-check of the embedding provider | Every 10 minutes while a local Ollama works, every 30 seconds otherwise | `CLAWBOO_DISABLE_EMBEDDINGS` turns embeddings off | `apps/web/server/lib/memoryEmbedding.ts:49,53` | | Filling in facts that have no embedding yet | Every 5 minutes (10 checks, 30 seconds apart) | No | `apps/web/server/lib/memoryEmbedding.ts:57-58` | | Re-check in the memory stdio bin | Every 60 seconds | No | `packages/mcp/src/memory/stdioEmbedSource.ts:30` | ## Approvals and budgets | Limit | Value | Change it | Source | | - | - | - | - | | Approval prompt for a tool call or a risky delegation | 5 minutes, then it expires | No | `packages/db/src/tools/persistence.ts:27,34` | | **Always** on a connector tool | 30 days | No | `packages/db/src/tools/persistence.ts:230` | | **Always** on a risky delegation | Does not expire | No | `packages/db/src/governance/approvalScope.ts:11` | | Clawboo Native command prompt | 10 minutes | No | `apps/web/server/lib/runtimes/native/execTool.ts:47` | | Commands a Clawboo Native agent can ask to run in one run | 10 | No | `apps/web/server/lib/runtimes/native/execTool.ts:59` | | OpenClaw command prompt | The Gateway's deadline, else 30 minutes | No | `apps/web/server/lib/agentSource/execApprovalSurface.ts:45` | | Age at which a forgotten pending approval is expired | 24 hours | `CLAWBOO_APPROVAL_TTL_MS` | `apps/web/server/lib/approvalReaper.ts:26` | | How often that cleanup runs | Every hour, and once at startup | `CLAWBOO_APPROVAL_REAPER_INTERVAL_MS` | `apps/web/server/lib/approvalReaper.ts:30` | | Budget warning threshold | 80% of the limit | No | `packages/governance/src/budget/math.ts:9` | | Time a task-runner run gets to end once a hard cap trips | 5 seconds; more work stops it sooner | No | `apps/web/server/lib/executorRunner.ts:154` | A budget set to hard cap pauses work at 100% of its limit; the default mode only warns. Which runs a cap can stop, and how quickly, depends on the runtime; [Budgets and spend](/using/budgets-and-spend) explains. ## Agent browsers | Limit | Value | Change it | Source | | - | - | - | - | | Agent browsers open at once | 4 | `CLAWBOO_MAX_AGENT_BROWSERS` | `apps/web/server/lib/connectors/agentBrowsers.ts:59` | | Idle time before an agent browser closes | 10 minutes | `CLAWBOO_AGENT_BROWSER_IDLE_MS` | `apps/web/server/lib/connectors/agentBrowsers.ts:62` | | Wait for a free browser when all are busy | 60 seconds | No | `apps/web/server/lib/connectors/agentBrowsers.ts:65` | | Saved browser screenshots kept in memory | 24 MiB in total; the oldest go first | No | `apps/web/server/lib/screenshotBus.ts:49` | ## Network and HTTP | Limit | Value | Change it | Source | | - | - | - | - | | Dashboard port | 18790, else the next free of 20 (to 18809) | `CLAWBOO_API_PORT`, `CLAWBOO_API_PORT_START` | `apps/web/server/lib/portUtils.ts:26,29` | | OpenClaw Gateway address | `ws://localhost:18789` | `settings.json` | `packages/config/src/index.ts:31` | | Development UI port (`pnpm dev`) | 5173 | No | `packages/gateway-proxy/src/origin-guard.ts:116` | | Ollama | `http://localhost:11434` | `OLLAMA_BASE_URL` (not for memory) | `apps/web/server/lib/runtimes/native/providers/openai.ts:55`, `packages/db/src/memory/embedding.ts:109` | | OpenClaw browser debugging port | 18800 (Gateway port plus 11) | `CLAWBOO_OPENCLAW_CDP_PORT` | `apps/web/server/lib/openclawTabCapture.ts:46` | | API requests per client address | 3,000 per minute | No | `apps/web/server/lib/rateLimit.ts:24,29` | | Requests to the 26 sensitive routes (installs, sign-ins, connector and runtime setup) | 60 per minute in total, on top of the above | No | `apps/web/server/lib/rateLimit.ts:38` | | Requests for the dashboard's page and files | 3,000 per minute, counted separately | No | `apps/web/server/lib/rateLimit.ts:47` | | JSON request body | 2 MB | No | `apps/web/server/index.ts:322` | | File shown in a task's workspace preview | 8 MiB | No | `apps/web/server/lib/workspacePreview.ts:43` | Over a rate limit the server answers 429 with a `Retry-After` header. The [REST API overview](/reference/rest-api/index) lists the sensitive routes. ## Server and CLI | Limit | Value | Change it | Source | | - | - | - | - | | Node.js version | 22.12 or newer | No | `apps/cli/src/node-version.ts:14-15` | | CLI wait for a new server to answer | About 45 seconds (90 checks, 500 ms apart) | No | `apps/cli/src/lifecycle.ts:528` | | CLI version check against a running server | 2 seconds | No | `apps/cli/src/lifecycle.ts:312` | | `clawboo stop`: wait after SIGTERM, then after SIGKILL | 4 seconds, then 2 seconds | No | `apps/cli/src/lifecycle.ts:574,576` | | `server.log` size before it rotates to `server.log.1` | 5 MiB, checked at each launch | No | `apps/cli/src/server-log.ts:19` | | Log lines shown after a start timeout | The last 12, from the final 8 KiB | No | `apps/cli/src/server-log.ts:60` | | A restarted server's wait for its old port | 15 seconds | No | `apps/web/server/index.ts:152` | | Shutdown wait for runtime processes to exit | 5 seconds | No | `apps/web/server/lib/runtimes/subprocess.ts:151` | | Gateway check at startup | 1.5 seconds | No | `apps/web/server/lib/defaults.ts:42` | | Health check of the server's own MCP servers | Every minute; a failed one is retried after 30 seconds, doubling to 5 minutes | `CLAWBOO_MCP_PROBE_MS` (the interval) | `apps/web/server/lib/mcpSupervisor.ts:30-32` | | Wait for a busy database | 250 ms | No | `packages/db/src/db.ts:31` | | Retry time for one busy database write | 1.5 seconds | `CLAWBOO_DB_WRITE_BUDGET_MS` | `packages/db/src/board/contention.ts:48` | ## Updates and monitoring | Limit | Value | Change it | Source | | - | - | - | - | | Marketplace catalog refresh | Cached 6 hours; 5-second fetch timeout | No | `apps/web/server/lib/catalogIndex.ts:65,71` | | Check for a newer Clawboo on npm | Cached 6 hours; 5-second timeout | No | `apps/web/server/lib/updateCheck.ts:107,111` | | How often the dashboard asks the server about updates | At most once an hour | No | `apps/web/src/features/promo/useUpdateCheck.ts:30` | | Fleet refresh | Every 8 seconds while the view is visible | No | `apps/web/src/features/fleet/FleetHealth.tsx:135` | | Quiet time before an agent with an open run shows as stalled | 5 minutes | No | `packages/obs/src/project/graph.ts:227` | | Quiet time before it shows as a zombie (process likely gone) | 30 minutes | No | `packages/obs/src/project/graph.ts:228` | | Events folded into the Fleet and Observability views | The newest 5,000 | No | `apps/web/server/api/obs.ts:96` | | GitHub star count shown in the dashboard | Fetched by your browser, cached 10 minutes | No | `apps/web/src/features/promo/GitHubStarButton.tsx:29` | ## Related * [Environment variables](/reference/environment-variables): how to set each variable named here * [Governance](/concepts/governance): what the caps and breakers are for * [Production defaults](/operating/production-defaults): the default posture of a fresh install * [CLI reference](/reference/cli): the start, stop and log behavior behind the CLI timings # Marketplace catalog reference Source: https://docs.claw.boo/reference/marketplace-catalog What the marketplace catalog contains, where Clawboo gets it and how often, all 19 packs with their licenses, and the pack format. The marketplace catalog is the set of ready-made teams, agents and skills behind the **Marketplace**. This page lists what it contains, where Clawboo gets it and how often, the packs it is made of, and the pack format for anyone who wants to read or write a pack. To browse and deploy, see [Browse and deploy from the Marketplace](/using/marketplace). ## At a glance | Item | Value | | - | - | | Packs | 19: two written for Clawboo, and 17 adapted from open-source repositories (16 MIT, 1 Apache-2.0) | | Teams | 85 in the full catalog; 5 when only the built-in pack is available | | Agents | 436 in the full catalog; 15 when only the built-in pack is available | | Skills | 44 built into Clawboo, listed on the **Skills** tab; 7 packs declare 45 more that their agents reference | | Comes from | The Clawboo repository on GitHub, downloaded by your Clawboo server | | Refreshed | At most every 6 hours | | Offline fallback | The built-in pack, compiled into Clawboo | | Format | Pack schema version 1, published as JSON Schema in `catalog/schema/` | The counts describe the catalog, not your fleet, and they grow as packs are added. The **Teams** and **Agents** tab counts in the Marketplace show what your server has loaded. ## What the catalog contains * **Pack.** A bundle of agents, teams and skills from one source, usually one open-source repository, with one license and one provenance record (repository, pinned commit, authors). * **Agent.** One specialist: a card (name, role, emoji, color, description, category, tags and skills) and its texts. `IDENTITY.md` holds its full instructions, `SOUL.md` a shorter persona, and `TOOLS.md` tool notes. The 15 built-in agents also carry an `AGENTS.md`. * **Team.** A roster of at least two agents, plus routing: for each member, the `AGENTS.md` text that says whom it hands work to. A team can also carry a workflow description. * **Skill.** A named capability an agent's card lists. The 44 built-in skills appear on the **Skills** tab. A pack may declare its own skills for its agents, but may not redeclare a built-in one. When you deploy, Clawboo saves an agent's three texts on the new agent and writes its `AGENTS.md` from the team's routing. Only OpenClaw agents follow `IDENTITY.md`; on every other runtime the agent runs on `SOUL.md` plus its personality settings, and `IDENTITY.md` is kept for reference. The per-runtime table is in [What each runtime runs](/using/marketplace#what-each-runtime-runs). ## Where the catalog comes from The catalog is not part of the npm package. Only the built-in pack ships inside Clawboo; your Clawboo server downloads the rest: 1. The first time the dashboard asks for the catalog, the server downloads the catalog index from the Clawboo repository on GitHub, waiting up to 5 seconds. 2. For each pack the index lists, it reads a copy saved in `~/.clawboo/catalog/`, or downloads one. It checks the pack's SHA-256 digest before reading it and skips a pack that does not match. Saved packs are keyed by their digest, so an unchanged pack is downloaded once. 3. It merges every pack that passed with the built-in pack, which is always included, so the catalog is never empty. 4. It keeps the result in memory for 6 hours. After that, the next request asks GitHub whether the index changed and rebuilds the catalog. A pack published upstream can therefore take up to 6 hours, or a server restart, to appear. The request carries no account or usage data; it only asks for the published files. > [!NOTE] > > The 6-hour hold also applies to a download that failed. If GitHub could not be reached when the catalog was first needed, the Marketplace shows only the built-in pack (5 teams, 15 agents) until the next refresh. Restart the server (`clawboo restart`) to try again sooner. * The index URL is `https://raw.githubusercontent.com/clawboo/clawboo/refs/heads/main/catalog/dist/v1/index.json`. Set `CLAWBOO_CATALOG_INDEX_URL` to serve the same files from a mirror. * When the server runs from a repository checkout that has `catalog/dist/v1/`, it reads the catalog from those files instead of GitHub. * An index in a newer format than your Clawboo can read shows only the built-in pack. A single pack it cannot read is left out, and the rest still show. ## Packs | Pack | `id` | Entry prefix | Agents | Teams | Skills | License | | - | - | - | - | - | - | - | | Agency Agents | `agency-agents` | `agency-` | 116 | 5 | 0 | MIT | | Engineering Agents | `wshobson-agents` | `wshobson-` | 97 | 24 | 0 | MIT | | Clawboo Life and Home | `clawboo-home` | `home-` | 35 | 10 | 0 | MIT | | Growth Marketing | `coreyhaines-growth-marketing` | `growth-` | 26 | 5 | 9 | MIT | | Research and Orchestration | `voltagent-subagents` | `voltagent-` | 24 | 5 | 0 | MIT | | Business and Compliance Desk | `alirezarezvani-business-desk` | `bizdesk-` | 18 | 4 | 0 | MIT | | Clawboo (built-in) | `clawboo` | `clawboo-` | 15 | 5 | 0 | MIT | | Founder Sprint | `clawboo-founder-sprint` | `sprint-` | 14 | 4 | 0 | MIT | | Creator and Founder Operations | `craighewitt-creator-ops` | `creatorops-` | 14 | 4 | 0 | MIT | | Generative Media Production | `calesthio-generative-media` | `genmedia-` | 12 | 4 | 9 | MIT | | Creator Studio | `charliehills-creator-studio` | `creatorkit-` | 12 | 3 | 0 | MIT | | Engineering Craft | `mattpocock-craft` | `craft-` | 12 | 3 | 7 | MIT | | Product Craft | `phuryn-product-craft` | `pmcraft-` | 12 | 3 | 0 | MIT | | Content Repurposing | `agricidaniel-repurpose` | `repurpose-` | 8 | 2 | 0 | MIT | | Ads and Analytics | `google-ads-analytics` | `adsapi-` | 7 | 1 | 0 | Apache-2.0 | | Lifecycle Commerce | `thatrebeccarae-lifecycle-commerce` | `lifecycle-` | 6 | 2 | 7 | MIT | | Storefront Catalog Ops | `kgelster-storefront-catalog` | `shopcat-` | 5 | 1 | 6 | MIT | | Image and Shot Direction | `blackforestlabs-visual-direction` | `visdir-` | 2 | 0 | 4 | MIT | | Presenter Video Studio | `heygen-presenter-video` | `presenter-` | 1 | 0 | 3 | MIT | | **Total** | | | **436** | **85** | **45** | | The pack names are the pack filters in the Marketplace. Every agent and team id is the pack's prefix plus a slug, for example `agency-engineering-frontend-developer`, and is unique across the catalog. Every pack is at content version `1.0.0`. ### Sources Clawboo Life and Home and the built-in pack are written for Clawboo. The other 17 are adapted from these repositories, each pinned to the commit it was taken from: | Pack | Upstream repository | Pinned commit | | - | - | - | | Agency Agents | `github.com/msitarzewski/agency-agents` | `64eee9f8e04f69b04e78e150d771a443c64720be` | | Engineering Agents | `github.com/wshobson/agents` | `d82998e7df393c671ede2387a8435075f0b633f5` | | Growth Marketing | `github.com/coreyhaines31/marketingskills` | `b1aaa3619e747f4a836c61e03084c4a531de1262` | | Research and Orchestration | `github.com/VoltAgent/awesome-claude-code-subagents` | `c9e51ec0b3d43f5dcdd0b558a6cd28ba6ada97c1` | | Business and Compliance Desk | `github.com/alirezarezvani/claude-skills` | `19392f7a08264ed00486a251f5b2098321771f94` | | Founder Sprint | `github.com/garrytan/gstack` | `394db326f2d3aaccd4804fe846b82aaa7d189dee` | | Creator and Founder Operations | `github.com/TheCraigHewitt/skills` | `fdbf39b61fbc8cb7cea67949bfb5e8fc567bbc51` | | Generative Media Production | `github.com/calesthio/generative-media-skills` | `8c85352d5d75d4dcbe58480bd138e37b9742bab1` | | Creator Studio | `github.com/charlie947/social-media-skills` | `d2e948719eafc8ed9e2436357ad18489bb371a81` | | Engineering Craft | `github.com/mattpocock/skills` | `6654f6b60cd9d5be8b54c6fafe44346dabeb3b76` | | Product Craft | `github.com/phuryn/pm-skills` | `18468a95b427e70e258b51389796367c6f684e7d` | | Content Repurposing | `github.com/AgriciDaniel/claude-repurpose` | `669187e2b69ef3c854d149657b7fb2483263dab4` | | Ads and Analytics | `github.com/google/skills` | `0dad3f947e45a736060e524bbefa3eab692809f9` | | Lifecycle Commerce | `github.com/thatrebeccarae/claude-marketing` | `a8a63ec1341f05ec9c1e9cb52b4edeb14e3bdcba` | | Storefront Catalog Ops | `github.com/kgelster/awesome-ecom-skills` | `0b6f9e51b4a14b030ab52a2f1ff8a320bdc50070` | | Image and Shot Direction | `github.com/black-forest-labs/skills` | `8907d515b0ac270a988ec7a239add81ee13d6cba` | | Presenter Video Studio | `github.com/heygen-com/skills` | `1bd5e4d33a028dfed3abf504c5e3dd644fb9ea8a` | Every pack ships a `NOTICE.md`, and attribution is collected in `THIRD_PARTY_NOTICES.md` (see [License and notices](/appendices/license-and-notices)). The 140 entries from Agency Agents (116) and Research and Orchestration (24) also link to the exact upstream file they came from (`origin.url`); the other packs record provenance once, at pack level. A pin records where content came from; nothing fetches the upstream repositories again. The 17 packs are marked `adaptation: "adapted"`, and their bodies have the upstream YAML frontmatter removed. ## Pack format A pack is a folder of JSON files. The public specification is JSON Schema 2020-12 in the repository: `catalog/schema/pack.schema.json` describes a pack, and `catalog/schema/index.schema.json` the browse index. You can write a pack against those two files without installing anything. Inside the repository, `packages/pack-format` holds the same format in TypeScript. ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} catalog/packs/<publisher>/<slug>/ pack.json the manifest, plus the agent, team and skill listings agents/<slug>.json one agent's texts teams/<slug>.json one team's routing and workflow NOTICE.md required when provenance.repo is set ``` ### The manifest ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} interface PackManifest { schemaVersion: 1 // first key; an integer id: string // the pack id, for example 'agency-agents' idPrefix?: string // entry-id prefix; defaults to id name: string description: string version: string // exact semver of the content; ranges such as '^1.2.3' are rejected provenance: Provenance counts: { agents: number; teams: number; skills: number } newCategories?: string[] // categories this pack introduces renames?: Record<string, string | null> // former entry id -> current id, or null if removed } ``` * `provenance` is recorded once per pack: `sourceId`, `label`, `color` (`#RRGGBB`), `repo`, `ref` (the pinned commit), `license` (an SPDX id), `authors`, `adaptation` (`verbatim`, `adapted` or `original`) and `importedAt` (an ISO time). An entry from a different upstream can override it with an `origin` block (`url`, `adaptation`, `authors`). * `renames` is entry bookkeeping, not a schema migration. The built-in pack maps its five old team ids, such as `dev`, to the current ones, such as `clawboo-dev`. * `category` values are open: 19 are known (for example `engineering`, `marketing`, `research`, `support`), and a pack that uses another must list it in `newCategories`. ### Listings and bodies A listing is what a card shows; a body is what the detail view and a deploy need. They are separate files because the agent texts are most of the bytes and no card reads them. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} interface AgentListing { id: string // `${idPrefix}-${slug}`, flat kebab-case, unique across the catalog packId: string slug: string name: string role: string emoji: string color: string // #RRGGBB description: string category: string tags: string[] skillIds: string[] // built-in skills or this pack's own body: string // relative path to the agent's body file origin?: { url?: string; adaptation?: string; authors?: string[] } suggestedRuntime?: string } interface TeamListing { id: string packId: string slug: string name: string emoji: string color: string description: string category: string tags: string[] members: { agentId: string; name: string; role: string }[] // so a card needs no body fetch body: string origin?: { url?: string; adaptation?: string; authors?: string[] } defaultRuntime?: string } interface AgentBody { id: string files: Record<string, string> // must include non-empty SOUL.md and IDENTITY.md } interface TeamBody { id: string workflowNarrative?: string routing?: Record<string, string> // each member's AGENTS.md text, keyed by agent id } interface PackSkill { id: string name: string description: string category: 'code' | 'web' | 'data' | 'comm' | 'file' | 'other' tags: string[] } ``` * `files` is a map, so a pack can carry another agent file without a format change. * At deploy, Clawboo writes `SOUL.md` with the default personality added, `IDENTITY.md` with the agent's final name (a number is added when the name is taken), `TOOLS.md`, an `AGENTS.md` built from the team's routing and Clawboo's team instructions, and a `CLAWBOO.md` help file. * `suggestedRuntime` and `defaultRuntime` are optional hints the team dialog uses to preselect a runtime. No pack sets them. ### Versions `schemaVersion` is required, must be an integer, and is never assumed. Version 1 is the only one, so this Clawboo reads exactly version 1. A pack that fails is rejected on its own, and the rest of the catalog still loads. | Rejection | Cause | | - | - | | `missing-schema-version` | No `schemaVersion` | | `schema-version-not-an-integer` | A value such as `"1"` or `1.0.0` | | `schema-version-too-new` | Newer than this Clawboo reads; upgrade Clawboo | | `schema-version-too-old` | Older than this Clawboo reads; republish the pack | | `invalid-at-declared-version` | The pack does not validate against its own version | | `upgrade-produced-invalid-document` | Upgrading the pack to the current version produced invalid data | ## Catalog routes The dashboard reads the catalog from its own server: | Route | Returns | | - | - | | `GET /api/catalog/index` | `{ schemaVersion, counts, agents, teams, packs }`: browse rows for the built-in pack and every pack that verified | | `GET /api/catalog/agents/:id` | `{ id, files, sourceUrl? }`, or 404 `{ "error": "Unknown catalog agent" }` | | `GET /api/catalog/teams/:id` | `{ id, workflowNarrative?, routing?, sourceUrl? }`, or 404 `{ "error": "Unknown catalog team" }` | `sourceUrl` is the entry's `origin.url`, when it has one. Full shapes and examples are in the [Catalog API](/reference/rest-api/catalog). ## For contributors > [accordion] Repository layout > > ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} > catalog/ > catalog.config.json which packs exist, and which one is the built-in seed > schema/ pack.schema.json and index.schema.json (the public spec) > packs/<publisher>/<slug>/ > dist/v1/index.json generated and committed: the browse index > dist/v1/packs/<publisher>/<slug>/<version>.json generated and committed: one bundle per pack > ``` > > `catalog/` is not a pnpm workspace member and is in no turbo task: a pack is content, not code. `catalog/dist/` is committed, which is what lets the default index URL (a raw GitHub URL on `main`) serve the catalog with no deploy step. > [accordion] Add or change a pack > > Edit the files under `catalog/packs/<publisher>/<slug>/`, then run: > > ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} > pnpm catalog:build > pnpm catalog:verify > ``` > > `catalog:build` regenerates `catalog/dist/` and the compiled built-in pack. `catalog:verify` runs every content rule, then checks that `catalog/dist/` and the compiled built-in pack are byte-for-byte what a rebuild would write. Commit the regenerated files with your change. > [accordion] What catalog:verify checks > > * The pack validates against the schema (strictly, with stale versions treated as errors), and every listing's body file exists and validates. A body file no listing points to is an error. > * A pack with `provenance.repo` ships a non-empty `NOTICE.md`. > * Every `skillIds` entry resolves to a built-in skill or one of the pack's own, and a pack does not redeclare a built-in skill. > * A category the dashboard has no label for is listed in `newCategories`, and `newCategories` lists only new, used categories. > * No two packs emit the same id prefix, and no two packs claim the same id. > * Descriptions are at least 20 characters and do not end in `...`. An `IDENTITY.md` does not open with YAML frontmatter (`---`) and is at most 50,000 characters. No entry repeats a tag. > * A team has at least two members, routes only to its members, and has routing for every member. > * No string names a competing registry, installer or chat invite. > * No text trips the prompt-injection scan, except findings reviewed in `scripts/catalog/injection-allowlist.ts`. > > `node scripts/catalog/budget.mjs` separately keeps the compiled built-in pack under 128 KB and the index under 512 KB. > [accordion] Digests and canonical bytes > > Each pack row in `index.json` names one bundle by version and carries `integrity`, a `sha256-<base64>` digest of the bundle's exact bytes. The server recomputes the digest and compares it before parsing the bundle, so bytes that do not match are never parsed. > > Bundles and the index are written as canonical bytes: object keys sorted recursively, no insignificant whitespace, LF line endings and no trailing newline. Those exact bytes are written, served and hashed, which is why Prettier never touches `catalog/dist/` (its `**/dist/` ignore entry). Pack sources under `catalog/packs/` are ordinary Prettier-formatted JSON. > [accordion] The built-in pack > > The built-in pack (`catalog/packs/clawboo/builtin`) is compiled into Clawboo, because first-run onboarding offers no start-from-scratch path and needs teams to pick from. `pnpm catalog:build` generates it into two files, one per build target, and `catalog:verify` fails if either drifts from the pack: > > | Generated file | Used by | > | - | - | > | `apps/web/src/features/marketplace/seed/` | The dashboard | > | `apps/web/server/lib/catalogSeed.ts` | The server | > > Never edit them by hand. > [accordion] CI for content pull requests > > A pull request that changes only `catalog/` skips the full CI matrix and runs `.github/workflows/catalog-ci.yml`: the content rules, the injection scan and the size budgets, offline. A change under `catalog/packs/clawboo/` gets the full matrix, because that folder holds the built-in pack, which ships inside Clawboo. > > Never make `catalog-ci.yml` a required check. It is path-filtered, so it never reports on other pull requests, and GitHub would keep them waiting. The `catalog-verify` job in `ci.yml` runs the same checks on every code pull request; require that one. Publishing a release runs `pnpm catalog:verify` too. ## Related * [Browse and deploy from the Marketplace](/using/marketplace): search, details, deploy, and what each runtime runs. * [Catalog API](/reference/rest-api/catalog): the three catalog routes in full. * [License and notices](/appendices/license-and-notices): licenses and attribution for the adapted packs. * [Agents](/using/agents): the agent files a deployed agent gets, per runtime. * [Configuration](/reference/configuration): `~/.clawboo/catalog/` and the other files Clawboo keeps. # MCP tools reference Source: https://docs.claw.boo/reference/mcp-tools Every tool on the Tasks, Memory, Tools and TeamChat MCP servers, with arguments, results, errors and what each kind of caller is served. Clawboo hosts four MCP servers. Agents on every runtime use them to read and update the shared board, save and recall memory, call brokered tools, and talk in their peer room. This page lists every tool with its arguments, results and errors, and explains which tools each kind of caller is served and what it can see. To attach an agent of your own, follow [Attaching Clawboo's MCP servers](/operating/mcp-servers). For the HTTP routes behind the servers, see the [Tools and MCP API](/reference/rest-api/tools-and-mcp). ## Servers | Server | Name in `tools/list` | Tools | What it fronts | | - | - | - | - | | [Tasks](#tasks-server) | `clawboo-tasks` | 12 | The [board](/concepts/the-board) | | [Memory](#memory-server) | `clawboo-memory` | 4 | Shared [memory](/concepts/memory) | | [Tools](#tools-server) | `clawboo-tools` | Visible builtins, granted connector tools, and `read_tool_result` | The tool broker (approvals and audit) | | [TeamChat](#teamchat-server) | `clawboo-teamchat` | 2, plus `team_delegate` on orchestrated team runs | The agents' peer room | Every server reports version `0.1.0` and declares the `tools.listChanged` capability. Only the Tools server sends list-changed notifications, when a connector connects or disconnects. ## Tool index | Tool | Server | What it does | | - | - | - | | [`list_tasks`](#list_tasks) | Tasks | List tasks, or only the ones ready to claim | | [`get_task`](#get_task) | Tasks | Read one task with its comments and parent chain | | [`create_task`](#create_task) | Tasks | Create a root task or a subtask | | [`create_subtask`](#create_subtask) | Tasks | Create a subtask under a parent | | [`claim_task`](#claim_task) | Tasks | Atomically claim a `todo` task | | [`assign_task`](#assign_task) | Tasks | Atomically hand a `todo` task to an agent | | [`release_task`](#release_task) | Tasks | Put an `in_progress` task back to `todo` | | [`update_task_status`](#update_task_status) | Tasks | Move a task to another status | | [`block_task`](#block_task) | Tasks | Move a task to `blocked` | | [`unblock_task`](#unblock_task) | Tasks | Move a task back to `todo` | | [`add_comment`](#add_comment) | Tasks | Comment on a task | | [`link_task`](#link_task) | Tasks | Make one task wait for another | | [`memory_save`](#memory_save) | Memory | Save a fact or a new version of a procedure | | [`memory_search`](#memory_search) | Memory | Search facts by keyword, meaning, or both | | [`memory_browse`](#memory_browse) | Memory | List the most recently updated facts | | [`memory_feedback`](#memory_feedback) | Memory | Report whether a recalled fact helped | | [`echo`, `note`, `web_search`, `delete_path`](#builtin-tools) | Tools | Demonstration builtins that exercise approvals and audit | | [Connector tools](#connector-tools) | Tools | Tools from connectors the agent has been granted | | [`read_tool_result`](#read_tool_result) | Tools | Page through a result that was too large to return whole | | [`team_chat_post`](#team_chat_post) | TeamChat | Post to the peer room as a named teammate | | [`team_chat_subscribe`](#team_chat_subscribe) | TeamChat | Read new peer-room posts since a cursor | | [`team_delegate`](#team_delegate) | TeamChat | Hand work to a teammate by name (orchestrated runs only) | ## Callers and scoping A caller's identity decides which tools it is served and what those tools can see. Over HTTP, a session reads its identity once, when it sends `initialize`, from query parameters on its attach URL. Clawboo signs those parameters with a secret kept in its own database, so an identity counts only when its signature verifies. There are four kinds of caller: * **Bound caller.** A run Clawboo started with a signed attach URL. Claude Code, Codex and Hermes runs are bound to their team and agent this way. Clawboo Native agents are bound in-process. * **Unverified HTTP caller.** Any HTTP session without a valid signature. Every OpenClaw agent is one, because the OpenClaw Gateway uses one shared configuration for all of its agents. So is an external agent attached with an unscoped URL, or with parameters edited by hand. * **Operator (stdio bin).** Someone ran a `clawboo-mcp-*` bin directly. The operator is trusted, so scope and identity come from the tool arguments. * **In-process (Clawboo Native).** The servers run inside the dashboard server and are bound to the run's team and agent. | Server | Bound caller | Unverified HTTP caller | Operator (stdio bin) | | - | - | - | - | | Tasks | 12 tools. When the run has a team, reads and writes are limited to its tasks and new tasks land in it. Mailbox notices ride along on results | 10 tools (no `claim_task` or `assign_task`). Board-wide. Comments post as a plain `agent` comment | 12 tools, board-wide | | Memory | Saves are shared with the team. Reads see team facts, this agent's own facts and facts that belong to no team. Provenance is recorded | Saves and reads use only facts that belong to no team. No reporter or provenance is recorded | Scope comes from `scopeTeamId` and `scopeAgentId` | | Tools | Builtins plus the connector tools this agent's grants allow | Builtins and `read_tool_result`, plus any connector tools a `global` grant allows. Agent and team grants cannot be found | Builtins and `read_tool_result`; no connector tools | | TeamChat | 2 tools, posting as the bound agent in its peer room. `team_delegate` too on orchestrated runs | No tools | 2 tools; author and team come from the arguments | > [!WARNING] > > An attach URL you assemble by hand binds nothing. Unsigned or edited scope parameters are ignored and the session is served as an unverified caller. To give an external agent an identity, copy a signed URL from `GET /api/mcp/config` with `scopeTeamId` and `scopeAgentId` set. A scoped session for an existing agent also consumes that agent's mailbox notices. ### What each runtime attaches | Runtime | Servers | Caller kind | | - | - | - | | Clawboo Native | Tasks (read-only by default), Memory, Tools, and TeamChat on team runs | In-process, bound to the run's team and agent | | Claude Code, Codex, Hermes | All four, over HTTP | Bound, with a signed team and agent scope | | OpenClaw | Tasks, Memory and Tools, over HTTP (never TeamChat) | Unverified | | An agent you attach | Whatever you configure | Unverified unless you use a signed, scoped URL | A Clawboo Native agent's tool settings choose which servers it gets. The default is the read-only Tasks surface (`list_tasks` and `get_task`), Memory, Tools and TeamChat, because the team engine owns board writes on team runs. An OpenClaw agent takes part in the peer room through the server instead of the TeamChat tools. The critic run that [verification](/concepts/verification) can start attaches with no scope, so it is an unverified caller too. ### Attach URL parameters | Server | Identity parameters | Signed by | | - | - | - | | Tasks | `scopeTeamId` limits reads and writes to the team; `scopeAgentId` turns on mailbox delivery | `scopeSig` | | Memory | `scopeTeamId`, `scopeAgentId`, `scopeTenantId`; provenance stamps `provRuntime`, `provTaskId`, `provSessionKey` | `scopeSig`; the provenance stamps by `provSig` | | Tools | `scopeTeamId`, `scopeAgentId` (decide which connector grants apply) | `scopeSig` | | TeamChat | `roomTeamId` (the room is `team:<roomTeamId>`), `postAuthorAgentId`, and `delegate=1` on orchestrated runs | `scopeSig`, which also covers `delegate` | * `scopeSig` is an HMAC-SHA256 over the team, agent, tenant and delegate fields under a per-install secret stored in the `settings` table (`mcp-attach-scope-secret`). A spawned runtime only ever sees the derived signature. * Provenance stamps record which run saved a fact or reported an outcome. They never widen what a session can see, and they are dropped when `provSig` does not verify. * `delegate=1` is inside the signature, so a run cannot add it to its own URL. Clawboo writes it only for runs driven by the team engine, which is watching for the `team_delegate` call. * Every URL Clawboo writes also carries an `attachToken`. When the access gate is on, a cookieless MCP request needs it (see [Transports](#transports)). * The request log keeps attach URLs but masks `attachToken`, `scopeSig` and `provSig`. ## Tasks server The Tasks server is the board over MCP. Any runtime can list, create, claim and update tasks on the same board the dashboard shows. Board rules (statuses, legal moves, the verification check before `done`) are described in [The board](/concepts/the-board). Statuses in the schemas below are `TaskStatus`: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} type TaskStatus = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'blocked' | 'done' | 'cancelled' ``` Rules that apply to every Tasks tool: * **Team-bound sessions.** When the session is bound to a team, every tool refuses a task from another team with the same `not found: <taskId>` a missing task gets, so a bound run cannot probe other teams. `list_tasks` lists only the team's tasks, and new tasks are created in the team. * **Refusals are tool errors.** A refused call returns `isError: true` with the text shown under each tool, not a protocol error. * **Never retry a lost claim.** `claim failed: conflict` means another agent won the claim. Retrying the same call cannot succeed. * **Mailbox delivery.** On a session bound to an agent, each successful result can carry an extra text block headed `[team updates` with up to five undelivered mailbox notices for that agent. Each notice is delivered once, on whichever channel reaches the agent first. ### Creation caps Creates through these tools are capped so a looping agent cannot flood the board. The caps count every task on the board, including tasks created through the dashboard, the API or the team engine, which are not capped themselves. | Cap | Limit | Tool error | `_meta.denied` | | - | - | - | - | | Children per parent | 24 live (not dropped) subtasks; `done` and `cancelled` ones count | `subtask rejected: parent <id> already has <n> children (max 24); drop one or attach the new task elsewhere` | `child_cap` | | Nesting depth | 2 levels below a root task (root, child, grandchild) | `subtask rejected: parent <id> is at the maximum nesting depth (2); attach the new task higher in the tree` | `depth_cap` | | Root tasks | 30 new root tasks per rolling 5 minutes | `task rejected: <n> root tasks already created in the last 5 min (max 30); attach this work to an existing task with parentTaskId instead of opening another` | `root_rate_cap` | | Missing parent | The parent must exist | `parent not found: <id>` | `parent_not_found` | The check and the insert run in one write transaction, so two runtimes racing for the last slot cannot both get it. An unchanged retry gets the same answer; dropping a stale child frees a slot, and the root window rolls. A refusal also carries its reason in the result's `_meta.denied` field. Clawboo Native turns that field into a `policy_denied` error, and repeated identical refusals in a task-runner run trip the circuit breaker (see [Governance](/concepts/governance)). ### list\_tasks List tasks, most recently updated first. Dropped (soft-deleted) tasks are left out. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { teamId?: string; status?: TaskStatus; ready?: boolean } ``` * `ready: true` lists only tasks ready to claim: `todo`, not dropped, and every dependency `done`, highest `priority` first. `status` is ignored then. * On a team-bound session the session's team is used and `teamId` is ignored. Otherwise a call with no `teamId` lists every team's tasks. * Returns a JSON array of task rows (the [`tasks` table](/reference/database-schema#tasks)). ### get\_task Read one task with its comments and its ancestor chain. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string } ``` * Returns `{ task, comments, ancestors }`. * Error: `not found: <taskId>` for a missing task or one outside the session's team. ### create\_task Create a task. With `parentTaskId` it is a subtask and the per-parent caps apply. Without one it is a root task and the root rate cap applies (see [Creation caps](#creation-caps)). ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { title: string description?: string status?: TaskStatus // default 'todo' priority?: number // integer, default 0 teamId?: string parentTaskId?: string // non-empty when present assigneeRuntime?: string } ``` * A subtask takes its parent's team and chat room. * On a team-bound session the task is created in the session's team and `teamId` is ignored. A parent outside the team is refused with `not found: <parentTaskId>`. * An empty `parentTaskId` fails argument validation (`invalid args: ...`); it is never treated as a root task. * Returns the new task row, or one of the cap errors. ### create\_subtask Create a subtask under a parent. It takes the parent's team and chat room, and starts in `todo`. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { parentTaskId: string; title: string; description?: string } ``` * Same caps and errors as a `create_task` with `parentTaskId`, including the team-bound parent check. * Returns the new task row. ### claim\_task Claim a `todo` task for an agent. The claim is atomic: when two agents race, exactly one wins. The claimed task moves to `in_progress` with the assignee set. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string; assigneeAgentId: string; assigneeRuntime?: string } ``` * Succeeds only for a task that is `todo`, has no assignee and is not dropped. * Returns the claimed task row. * Errors: `claim failed: conflict` (the task exists but is not claimable, for example another agent won; do not retry), `claim failed: not_found`, and `not found: <taskId>` for a task outside the session's team. * Not served to an unverified HTTP caller, because `assigneeAgentId` would let it act as any agent. ### assign\_task Hand a `todo` task to an agent. It is the same atomic claim as `claim_task`, with the same arguments, results and errors; `conflict` means the task is already taken. Not served to an unverified HTTP caller. ### release\_task Put an `in_progress` task back to `todo` and clear its assignee and its verification verdict, so another agent can claim it. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string } ``` * Returns the text `released: <taskId>`. A task that is not `in_progress` is left unchanged, and the same text is returned. ### update\_task\_status Move a task to another status. Legal moves follow the board's state machine; `done` and `cancelled` are final, and setting the current status again is a harmless no-op. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string status: TaskStatus } ``` * Moving a task to `todo` also clears its assignee and its verification verdict. * Returns the updated task row. * Errors: `status change failed: illegal_transition`, `status change failed: not_found`, and `status change failed: verification_required` when the task holds a failing verification verdict (only a person's override, from the board or the board API, can finish it). See [When work is checked](/concepts/verification). * Refused when a person moved the card on the board or in the task drawer after the task's latest run started (or the task has not run since): that run was overruled, and the status stays where the person put it. The tool error reads `A person moved this task to <column> after your run on it started, so its status is theirs to set. Leave it as it is and do not retry this call.` A run started after the move writes as usual. ### block\_task Move a task to `blocked`. It is `update_task_status` with `status: 'blocked'`. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string } ``` * Returns the updated task row. Error: `block failed: <reason>`, with the same reasons, and the same refusal as `update_task_status` after a person moved the card. ### unblock\_task Move a task to `todo` (from `blocked`, or any status that may move to `todo`). It clears the assignee and the verification verdict, like any move to `todo`. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string } ``` * Returns the updated task row. Error: `unblock failed: <reason>`, with the same reasons, and the same refusal as `update_task_status` after a person moved the card. ### add\_comment Add a comment to a task, such as a progress report or a note for the person. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string body: string authorAgentId?: string authorType?: 'agent' | 'user' | 'system' // default 'agent' } ``` * For an unverified HTTP caller both `authorAgentId` and `authorType` are dropped, and the comment posts as a plain `agent` comment with no author. A caller that cannot prove who it is cannot sign as someone else, or as the person. * Returns the new comment row. Error: `not found: <taskId>` for a task outside the session's team. ### link\_task Make `taskId` wait for `dependsOnTaskId`: it is not ready to claim until that task is `done`. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { taskId: string dependsOnTaskId: string } ``` * Returns the text `linked: <taskId> depends on <dependsOnTaskId>`. Linking an edge that already exists changes nothing. * A link to the task itself, or one that would close a loop (directly or through other tasks), is refused: `linking <taskId> to <dependsOnTaskId> would create a dependency cycle`. A loop could never finish, because every task in it waits on another. * On a team-bound session both tasks must belong to the team, otherwise `not found: <id>`. ## Memory server The Memory server saves and recalls the team's shared memory: declarative facts ("the client prefers short emails") and versioned procedures. See [Memory](/concepts/memory) for the tiers and how memory reaches an agent's prompt. Where a call reads and writes depends on the caller: | Caller | Saves go to | Searches and browses see | Feedback is recorded as | | - | - | - | - | | Bound | The bound team, shared with every teammate on any runtime | Team facts, this agent's own facts, and facts that belong to no team; never another team's | The bound agent, team and task | | Unverified HTTP caller | Facts that belong to no team | Only facts that belong to no team | No one | | Operator (stdio bin) | The `scopeTeamId` and `scopeAgentId` arguments | The `scopeTeamId` and `scopeAgentId` arguments | The `scopeAgentId` and `scopeTeamId` arguments | On a bound session the `scopeTeamId` and `scopeAgentId` arguments are ignored. A bound save also records provenance (the agent, runtime, task and session that saved it), which never changes who can see the fact. ### memory\_save Save a fact (`title` and `content`), or a new version of a procedure (set `procedureName`). ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { content: string title?: string // required for a fact tags?: string[] procedureName?: string // save a procedure instead of a fact scopeTeamId?: string scopeAgentId?: string } ``` * Secrets in the content are replaced with `[REDACTED]` before storage. * Returns `{ saved: 'fact', fact }` or `{ saved: 'procedure', procedure }`. Saving a procedure under an existing name and scope stores the next version. * Errors: `nothing to save: content was entirely redacted secrets`, and `a fact requires a title (or set procedureName for a procedure)`. ### memory\_search Search facts by keyword, by meaning, or both. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { query: string mode?: 'fts' | 'vector' | 'hybrid' limit?: number // integer, 1 to 100, default 10 scopeTeamId?: string scopeAgentId?: string } ``` * With no `mode`, search is `hybrid` (meaning plus keyword) when an embedding provider is available and `fts` (keyword) when not. `vector` and `hybrid` also fall back to keyword search when there is no provider or the query cannot be embedded. * Returns an array of facts, best first. Each carries `score`, `matchedVia` (the mode that found it) and `learning`. * `learning` is `null` for a fact nothing has used or rated yet. Otherwise its `status` is `preferred` (two or more different agent and task pairs found it useful), `tentative` (one did), `contested` (mixed reports), `dead_end` (only negative reports), or `null` (it has only been used, not rated). * The tool's own description says `fts` is the default; the rule above is what the server does. ### memory\_browse List the most recently updated facts the caller can see. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { limit?: number; scopeTeamId?: string; scopeAgentId?: string } // limit: integer, 1 to 200, default 10 ``` * Returns an array of facts, each with `learning` as in `memory_search`. ### memory\_feedback Report whether a recalled fact helped. Reports feed the `learning` status that search results carry. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { factId: string // the full id, or a unique prefix of 8 or more hex characters outcome: 'useful' | 'dead_end' | 'corrected' note?: string // required for 'corrected' scopeTeamId?: string scopeAgentId?: string } ``` * `useful` means the fact was right and helped, `dead_end` that it misled or wasted effort, and `corrected` that it was wrong (put the correction in `note`; secrets in it are scrubbed). * Cite the id from `memory_search`, or the id prefix shown in the memory block of the agent's prompt. * A fact the caller cannot see gets the same error as a missing one, so feedback cannot reveal another team's facts. That error begins `unknown fact id (or ambiguous prefix)`. The other error is `corrected requires a note with the correction`. * Returns `{ recorded, learning }`. * Each reporter (an agent and task pair) counts once per direction, at the weight of its newest report, so repeating a report adds nothing. An unverified caller is recorded as no one, so it cannot promote a fact on its own. ## Tools server The Tools server is Clawboo's tool broker. Every call is checked, may wait for a person's approval, then runs and is recorded in the tool audit. Approval requests appear under **Needs you** on the Board and in the Governance approval queue (see [Approvals](/using/approvals)). `tools/list` contains: * The [builtin tools](#builtin-tools) whose requirements are met. * The tools of every live [connector](/using/connectors) that the calling agent's grants allow. A connector tool the agent has not been granted is not listed at all. The list is recomputed for every list and every call, and the server sends a list-changed notification when a connector connects or disconnects. * [`read_tool_result`](#read_tool_result), always. Grants, availability and connector tools never hide or replace it. A call goes through these checks in order: 1. The tool must exist, be enabled and be available, and its arguments must validate. 2. A connector tool must pass the grant check for the calling agent and team. 3. A security scan of the arguments can refuse the call. Numeric `limit`, `max`, `count`, `maxResults`, `top_k` and `topK` arguments above 1,000 are lowered to 1,000. 4. A destructive tool, an external tool that is not read-only, or a grant that asks for approval waits up to 5 minutes for a person to answer. 5. The call runs, and its arguments and result are recorded in the audit with secrets scrubbed. Results: * A refused call returns the tool error `denied: <reason>`, and the same reason in `_meta.denied`. Reasons start with `unknown-tool:`, `disabled:`, `unavailable:`, `provenance:`, `invalid-args:`, `grant:` (for example `grant:no-grant`; see the [grant decision reasons](/reference/events-and-errors#grant_decision)), `security:` or `scope:`. * A call whose approval is refused, expires or times out returns `approval deny`, `approval expired` or `approval timeout`. * A text result larger than 16 KiB is stored whole (up to 8 MiB) and the model gets a trimmed view: the start and the end of the result, with a notice between them that carries a `tr_` handle and the exact `read_tool_result` call for the rest. Only the Tools server trims results; the other three servers bound theirs with `limit` arguments. Stored results are deleted after 7 days (`CLAWBOO_TOOL_RESULT_TTL_MS`). ### Builtin tools These four exist to exercise the broker (approvals, audit and denials). None of them has a side effect. | Tool | Arguments | Risk | Listed | Returns | | - | - | - | - | - | | `echo` | `{ message: string }` | safe | Always | The message | | `note` | `{ note: string }` (non-empty) | safe | Always | `noted: <note>`; nothing is stored | | `web_search` | `{ query: string; limit?: number }` (query non-empty) | external, asks for approval | Only with a search provider | `searched: <query>`; no search runs | | `delete_path` | `{ path: string }` (non-empty) | destructive, asks for approval | Always | `would delete: <path>`; nothing is deleted | A search provider means the `TAVILY_API_KEY` environment variable, or an authorized `tavily` provider. With no search provider and no grant that covers every agent (a `global` grant), an unverified caller is served four tools: `echo`, `note`, `delete_path` and `read_tool_result`. ### Connector tools * A connector tool is named `mcp__<connector-slug>__<tool-name>`, at most 128 characters. Its description is the connector's own, unchanged. * A brokered connector (one service that fronts many apps, such as Composio) gets one line appended to its app-facing tools' descriptions: `Apps this agent may use: ...` for a bound agent, or `Apps reachable here, each subject to the operator approving the first call: ...` for an unverified caller. * The agent's grants decide which connector tools it is offered and whether a call is allowed, asks for approval, or is denied. See [Connectors](/using/connectors). ### read\_tool\_result Read back a result that was too large to return whole. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { handle: string // the tr_ handle from the trimmed result offset?: number // integer, at least 0, in bytes limit?: number // integer, at least 1, in bytes; capped at 16 KiB search?: string } ``` * Returns the requested bytes, then a line with the total size and the exact call for the next page, or `This is the end of the result.` When the stored copy itself was cut at 8 MiB, that line says how many bytes were kept. * With `search`, returns only the lines that contain the text (case-insensitive), each prefixed with its byte offset as `[byte <n>]` so the next call can seek to it. * An unknown or expired handle is an error that begins `No stored result for handle` and tells the model to re-run the original call with narrower arguments. * Its own result is never trimmed. Handles are random and work from any session, so a handle is as sensitive as the result it points to. ## TeamChat server The TeamChat server is the agents' peer room. Teammates on any runtime post as named peers and read each other's posts. This room is separate from the team chat you see in the dashboard (General and its rooms); see [Peer chat](/concepts/peer-chat). A post never changes the board. * On a bound session the author and room come from the binding, and any `authorAgentId`, `teamId` or `roomId` argument is ignored, so a runtime cannot post as a teammate it is not. The room is `team:<teamId>`. * On the stdio bin, pass `authorAgentId` and `teamId`; `roomId` defaults to `team:<teamId>`. * An unverified HTTP caller is served no tools, because both tools need an author and a room. * On a bound session, `team_chat_post` and `team_delegate` results can carry mailbox notices like the Tasks tools. `team_chat_subscribe` results never do. ### team\_chat\_post Post a message to the peer room. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { text: string; authorAgentId?: string; teamId?: string; roomId?: string } ``` * Returns `{ posted: { seq, roomId, authorAgentId } }`. `seq` goes up by one with each post in the room. * Errors: `a post requires non-empty text`, and on the stdio bin `no bound identity: pass authorAgentId + teamId (unbound mode)`. * A bound post over HTTP also writes a `team_chat_post` event to the [event log](/reference/events-and-errors). ### team\_chat\_subscribe Read new posts since a cursor. The caller's own posts are never returned. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { sinceSeq?: number // integer, at least 0, default 0 limit?: number // integer, 1 to 500; up to 1,000 posts when omitted authorAgentId?: string teamId?: string roomId?: string } ``` * Returns `{ posts, nextSeq }`, oldest post first. Each post is `{ seq, authorAgentId, kind, wrapped }`. * `wrapped` presents the post as evidence, not as an instruction. It is a header line, `[Inter-session message · from=<agent> · kind=<kind> · seq=<n> · isUser=false]`, then the body with every line prefixed `| `. Text in the body that imitates such a header becomes `[peer-quoted header]`. A teammate's post is context to weigh, never an order from the person. * `nextSeq` is the room's newest `seq`, never lower than `sinceSeq`. It moves to the newest post even when `limit` cut the page short, so posts beyond the limit are skipped by the next read. * Error (stdio bin only): `no bound identity: pass authorAgentId + teamId (unbound mode)`. ### team\_delegate Hand a self-contained piece of work to a teammate by name. It is served only on runs the team engine drives (a signed `delegate=1`), and it is how Claude Code, Codex and Hermes team leaders delegate. Clawboo Native leaders use their own built-in `delegate` tool instead. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { assignee: string task: string } // assignee is the teammate's name, for example "Coder" ``` * The tool writes nothing itself. The team engine sees the call and creates the board task for that teammate, who reports back when done. See [Delegation and orchestration](/concepts/delegation-and-orchestration). * Returns `Delegated to <assignee>: <task>. They'll pick it up and report back when done.` whenever both fields are present. A name that matches no teammate, or the caller itself, creates no task. * Error: `team_delegate requires both an assignee (teammate name) and a task`. ## Transports Every server is built from the same code and served three ways. ### stdio bins The `clawboo` npm package installs one command per server. The client that spawns a bin owns its process. | Command | Server | | - | - | | `clawboo-mcp-tasks` | Tasks | | `clawboo-mcp-memory` | Memory | | `clawboo-mcp-tools` | Tools | | `clawboo-mcp-teamchat` | TeamChat | * The bins open `CLAWBOO_DB_PATH`, or `~/.openclaw/clawboo/clawboo.db` when it is unset. That is a different file from the dashboard's database (`~/.clawboo/clawboo.db`), on purpose. To work on the dashboard's board and memory, set `CLAWBOO_DB_PATH` to the dashboard's file; the stdio snippets from `GET /api/mcp/config` already do. * The Memory bin picks an embedding provider and checks again once a minute: a local Ollama with `nomic-embed-text` at `http://localhost:11434` first, then `OPENAI_API_KEY` from its environment, unless the store holds local vectors and you have not allowed remote embeddings. With neither, search is keyword-only. `CLAWBOO_DISABLE_EMBEDDINGS=1` turns embeddings off. * The Tools bin serves the builtins and `read_tool_result`; it has no connector tools. * The TeamChat bin is unbound, so author and team come from the tool arguments. ### Streamable HTTP The dashboard server mounts each server at `/api/mcp/<server>`, where `<server>` is `tasks`, `memory`, `tools` or `teamchat`. | Method | Path | Use | | - | - | - | | `POST` | `/api/mcp/<server>` | JSON-RPC requests: `initialize`, `tools/list`, `tools/call` | | `GET` | `/api/mcp/<server>` | The event stream of an open session | | `DELETE` | `/api/mcp/<server>` | Close a session | * A session starts with `initialize`. Its response carries an `mcp-session-id` header that every later request must send. Sessions live in the server's memory, so after a restart a client must initialize again. * A `POST` must send `Accept: application/json, text/event-stream`, or it gets HTTP 406; a `GET` needs `Accept: text/event-stream`. A body that is not JSON gets HTTP 415. * `No valid session; send an initialize request first.` (HTTP 400) answers a `POST` that is not `initialize` and has no known session. `Invalid or missing MCP session id.` (HTTP 400) answers a `GET` or `DELETE` without one. Both are JSON-RPC errors with code `-32000` and `id: null`. A server failure before the response starts returns HTTP 500 with `{ "error": "..." }`. * The MCP routes share the general API rate limit; see [Limits and defaults](/reference/limits-and-defaults). When the access gate is on (`STUDIO_ACCESS_TOKEN` is set), every `/api/` request needs the access cookie. A cookieless request to one of the four MCP paths is let through only when it carries a valid `attachToken`, arrives on a loopback connection, and has none of the `Forwarded`, `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Proto` or `X-Real-IP` headers. A request through a reverse proxy therefore always needs the cookie. `GET /api/mcp/config` always needs the cookie when the gate is on, because it signs identities. See [Security](/operating/security). ### In-process Clawboo Native agents use the same servers inside the dashboard server, through an in-memory MCP client. There is no URL and no token, and the run's team and agent are bound directly. ### Attach snippets `GET /api/mcp/config` returns a ready-to-paste attach snippet. Copy URLs from it rather than building them, because only it can sign them. | Parameter | Values | Default | | - | - | - | | `server` | `tasks`, `memory`, `tools`, `teamchat` | `tasks` | | `runtime` | `claude-code`, `codex`, `openclaw` | `claude-code` | | `transport` | `http`, `stdio` (any other value means `stdio`) | `http` | | `scopeTeamId`, `scopeAgentId` | Ids to bind the session to | Unscoped | * Returns `{ ok: true, config: { id, transport, snippet, structured } }`. An unknown `server` is HTTP 400 `unknown server: <name>`. An unknown `runtime` returns `{ ok: true }` with no `config`. * HTTP URLs use the server's own loopback address and port, never the request's `Host` header. They always carry `attachToken`, and a `scopeSig` when scoped. * A Claude Code HTTP snippet looks like `claude mcp add --transport http clawboo-tasks 'http://127.0.0.1:<port>/api/mcp/tasks?attachToken=<hex>'`. The URL is single-quoted because it carries a query string. The full route reference is in the [Tools and MCP API](/reference/rest-api/tools-and-mcp). ## Errors on every server | Error text | Meaning | | - | - | | `unknown tool: <name>` | The tool is not on this session's list, for example a hidden or ungranted tool | | `invalid args: <details>` | The arguments failed the tool's schema; `<details>` lists the problems | Both are tool results with `isError: true`. Errors specific to a tool are listed under that tool. ## Related * [Attaching Clawboo's MCP servers](/operating/mcp-servers): attach an agent of your own, step by step. * [Tools and MCP API](/reference/rest-api/tools-and-mcp): approvals, audit and the attach-config route. * [The board](/concepts/the-board): statuses, legal moves and claiming. * [Memory](/concepts/memory): memory tiers, recall and learning. * [Connectors](/using/connectors): connect services and grant them to agents. * [Events and errors](/reference/events-and-errors): the events these tools write. # Runtime adapters Source: https://docs.claw.boo/reference/packages/adapters The six runtime adapter packages compared: how each one drives its runtime, what it reports, how it steers, and where its state lives. Each runtime Clawboo drives has an adapter package under `packages/adapters/`. Every adapter implements `RuntimeAdapter` from `@clawboo/executor`, and only the server imports them; they reach users inside the bundled server of the `clawboo` package. Use this page when you change an adapter, need to know what a runtime really reports, or want the fault-injection runtime for a test. The runtimes as users see them are on [Choose a runtime](/runtimes/index), and the steps for a new one are on [Write a runtime adapter](/reference/packages/executor). ## At a glance | | Claude Code | Codex | Hermes | Clawboo Native | OpenClaw | Mock | | - | - | - | - | - | - | - | | Package | `adapter-claude-code` | `adapter-codex` | `adapter-hermes` | `adapter-native` | `adapter-openclaw` | `adapter-mock` | | Runtime id | `claude-code` | `codex` | `hermes` | `clawboo-native` | `openclaw` | `clawboo-mock` | | What runs | Claude Agent SDK `query()` | `codex app-server`, else `codex exec` | `hermes chat -q`, one process per run | Clawboo's own loop over the provider API | the server's Gateway connection | nothing | | Healthy when | `claude` is found | `codex` is found | `hermes` is found | a provider key or a set-up Ollama is connected | `agents.list()` answers in 2 s | always | | Steering | live | live (not on `codex exec`) | stop and resume | live | live | none | | Model change mid-run | no | no | no | yes, for later turns | yes | no | | Cost the adapter reports | provider USD | tokens, no price | nothing | Clawboo's list price, or tokens | nothing | nothing | | Agent state | your own home folder | a managed `CODEX_HOME` per agent | a managed `HERMES_HOME` per agent | a managed folder per agent | the Gateway's own folders | none | | Clawboo MCP servers | HTTP, in the SDK options | the home's `config.toml` | the home's `mcp.json` | in-process | the Gateway's `mcp.servers` | none | | Approval prompts in Clawboo runs | off | off | off | asked per shell command | Gateway exec approvals | none | "Found" means the binary is on `PATH` or in a per-user install folder (for Hermes, the pip and pipx user folders). A managed home lives at `<clawboo home>/runtimes/<runtime id>/<agent id>`. A Codex or Hermes run with no agent identity gets a throwaway temporary home instead. ## What every adapter shares * **Construction.** The Claude Code, Codex, Hermes and Native adapters take `(driverFactory, healthCheck?)` and create one driver per run. The OpenClaw adapter takes the Gateway client. The mock takes nothing. * **Health.** `health()` races the check against two seconds. A timeout or a throw reads as unhealthy. * **Runs.** `start()` returns `runId: null` (the mock returns `mock-<session key>`). `events()` subscribes when called, binds the run id from the first frame that carries one (falling back to the session key), and buffers through `createAsyncQueue`. * **Control.** `abort()`, `setModel()` and `writeContext()` go to the run's driver. The Claude Code, Codex and Hermes drivers ignore `setModel()`, because the model is fixed when the run starts, and their `writeContext()` writes a file into the run's working folder (nothing when the run has none). * **Steering.** The per-run adapters steer through `steerThroughDriver`. Their `sessionCodec` serializes the session key and the last native session id. * **Server wiring.** `adapterFactoryFor(id)` in `apps/web/server/lib/runtimes/index.ts` builds the non-OpenClaw adapters, and gives the CLI drivers the attach secret so their MCP URLs pass the access gate. OpenClaw adapters come from the server's shared Gateway connection (`buildOpenClawServerAdapter`). ## Approvals and isolation All five real adapters declare `toolApproval: true`, which describes the runtime and not Clawboo's runs: * Claude Code runs with `permissionMode: 'bypassPermissions'`. * Codex runs with approval policy `never` and sandbox `danger-full-access`; its `codex exec` fallback passes `--dangerously-bypass-approvals-and-sandbox`. * Hermes runs with `--yolo` and `--accept-hooks`. * Clawboo Native asks a person before every shell command, and its shell tool is off unless an agent's config turns it on. * OpenClaw decides which commands ask first through its own exec policy; Clawboo shows those requests and relays your answer to the Gateway. A per-task git worktree keeps file changes apart. It is not a sandbox: the process runs with your user's permissions. See [What agents can do](/concepts/permissions). ## What each adapter reports as cost The adapter reports what the runtime gives it; the host fills the gaps with estimates. The task runner serves `POST /api/runtimes/<id>/run` and agent routines, and team chat covers team turns and delegations. | Runtime | The adapter emits | Task runner records | Team chat records | | - | - | - | - | | Claude Code | a `cost` event with the SDK's `total_cost_usd` | the reported USD | the reported USD | | Codex | token usage with `costUsd: null` | an estimate from the tokens | an estimate from the tokens | | Hermes | nothing | an estimate from characters, plus a warning | an estimate from characters | | Clawboo Native, priced model | per-turn USD from Clawboo's price list | the reported USD | the reported USD | | Clawboo Native, other model | token usage with `costUsd: null` | an estimate from the tokens | an estimate from the tokens | | Clawboo Native, local Ollama | per-turn `costUsd: 0` with `local: true` | \$0, recorded as `local` | \$0, recorded as `local` | | OpenClaw | nothing | not run (the task runner refuses OpenClaw) | an estimate from characters | On both paths, a run that ends without a cost report (a Claude Code run that fails first, say) is estimated from characters; on the task runner it also logs a `spend_unreported` warning unless it was aborted. An OpenClaw routine fire skips the task runner. Every fire that runs records an estimate from characters, whether it succeeded, failed, timed out or was stopped, priced at the routine's model or else the agent's model in `openclaw.json`. A token estimate prices the tokens at the model's rate. A model Clawboo has no price for gets the shared default rate of $3 per million input tokens and $15 per million output tokens. A model the user's own Ollama serves is free: a Clawboo Native turn on the Ollama provider, or an `ollama/` model id, is recorded at \$0, unless its tag marks one of Ollama's cloud models (`-cloud` or `:cloud`). The run ledger records each figure as `reported`, `local`, `token-estimate` or `char-estimate`. What users see is described in [Budgets and spend](/using/budgets-and-spend). ## Claude Code The driver (`createClaudeCodeDriver`) loads the Claude Agent SDK on demand. The published `clawboo` package does not ship the SDK, so a run on an install without it fails with a message that says how to install it alongside Clawboo. The SDK's own `total_cost_usd` is passed through unchanged, and its `error_max_turns` result becomes `done` with reason `max_turns`. A steer goes in as a user message with priority `next`, which the CLI reads at its next turn boundary. Clawboo provisions no home for Claude Code: the SDK runs as your user, with your own home folder and Claude login. The connected Anthropic key, when there is one, is passed in the run's environment. ## Codex The driver hosts each run in `codex app-server` and falls back to `codex exec` for a Codex without it. Only the app-server path can be steered (`turn/steer`); on the fallback a steer is refused as `rejected`. A thread resumes through `thread/resume`, or `codex exec resume <id>` on the fallback. Each agent gets a managed `CODEX_HOME`, so session files survive between turns. The driver copies your `codex login` credentials (`~/.codex/auth.json`) into it and never writes them back. A managed home with no usable login and no API key fails fast with `no Codex credentials provisioned for managed home <path>` and asks the user to run `codex login`. The adapter's `models` list is informational; nothing in the host reads it. ## Hermes The driver runs `hermes chat -q <prompt> -Q` once per run. In that quiet mode, response lines become message frames, the `session_id:` line on stderr becomes the resume handle, and the final result is built from the exit code. Hermes reports no tool calls and no token usage, so a Hermes run shows no tool activity and emits no `cost` event. Steering is stop and resume (`interrupt-resume`): the driver stops the process and resumes the same session with the message as the next input, and the host still sees one run. A passive steer is refused, because it must never stop a run. Each agent gets a managed `HERMES_HOME` where its skills and memory persist, and the driver writes all four Clawboo MCP servers into that home's `mcp.json`. Hermes ships its own scheduler, but Clawboo never runs it for teammates. Hermes works one task at a time from Clawboo's board: its own task board is not synced, and Clawboo never starts `hermes gateway`, so Hermes's chat channels stay off. The home is seeded once from your `~/.hermes/config.yaml`; with no config, the driver picks `--provider` from whichever key is connected (OpenRouter, Anthropic or OpenAI). ## Clawboo Native The package holds the adapter, the event mapper and the `AgentConfig` schema; the conversation loop, provider clients and MCP bridge live in `apps/web/server/lib/runtimes/native/`. Each agent's config is stored in the `settings` table under `native-agent-config:<agent id>` and validated with `agentConfigSchema` on every load, so a corrupt value falls back to `DEFAULT_AGENT_CONFIG` instead of crashing a run. * **Fields.** `systemPrompt`, `primaryProvider`, `primaryModel`, `fallbacks`, `envVar` (the vault slot's name, never the key), `tools` (`memory`, `tools`, `tasks` as `true`, `false` or `'read'`, `teamchat`, an optional `shell` that is off when absent, `custom`), `maxTurns`, and `budgetUsd`. A `budgetUsd` value creates an agent-scope hard-cap budget when the agent is created; `null` keeps the track-and-warn default. * **Providers.** `KNOWN_PROVIDERS` lists `anthropic`, `openai`, `openrouter` and `ollama`, plus the OpenAI-compatible `google`, `xai`, `groq`, `mistral`, `together`, `cerebras` and `moonshot`. The schema accepts any provider string, but only these route: an unknown primary provider fails with `unknown provider: <id>`. `envVarForProvider` returns each provider's key variable, and `null` for keyless Ollama. * **Defaults.** An agent created without a provider takes the user's recorded default native provider, else the first connected provider in `KNOWN_PROVIDERS` order. `DEFAULT_AGENT_CONFIG` (Anthropic, `claude-haiku-4-5`, read-only Tasks, no shell) only fills in for an agent with no stored config. * **Cost.** Each provider response produces a `turn` frame with that response's token delta. Clawboo prices it from a short list of pinned model ids. A turn the Ollama provider served costs an exact \$0 and is flagged `local` (Ollama's cloud models excepted); any other model gets `costUsd: null` and is estimated by the host. * **Context window.** `contextWindowTokens` is a deliberate over-estimate, because one number cannot fit every provider. A run that overflows a smaller window ends with a typed error instead. ## OpenClaw The adapter drives agents over the server's live OpenClaw Gateway connection. It takes a structural `OpenClawGatewayClient`, so tests pass an in-memory double. Team chat builds one per delivery through `buildOpenClawServerAdapter()` (which returns `null` while the Gateway is down), and the Routines wake bridge builds one for OpenClaw routine fires. * **Start.** `start()` sends `chat.send` with `deliver: false` and a fresh idempotency key, the context placed above the message. An acknowledgement that did not start a run (`accepted: false`, or a no-run status) throws, so the caller can release the task instead of waiting for frames that never come. * **Events.** `events()` subscribes at once and keeps observing the session across runs, rebinding the run id from each frame. * **Control.** `abort()` sends `chat.abort` for the bound run and always `sessions.abort` as a backstop. `setModel()` is `sessions.patch`. `writeContext()` sets an agent file, which a running session does not re-read. * **Steering.** A steer is `chat.send` with `queueMode: 'steer'`, delivered when the Gateway confirms the steer's own run. Frames for a steer's run id are kept out of `events()`. A steer still unread when the run ends is cancelled and reported as `run-ended`, and so is a steer for a session this adapter has no live run on. * **No worktrees, no task runner.** `runtimeClass: 'connected-substrate'` makes the task runner refuse OpenClaw (`connected_substrate`) before it claims anything, and `worktrees: false` means OpenClaw works in its own Gateway-owned folders. * **No MCP attach.** `mcp: false` means the adapter attaches nothing. The OpenClaw agent source registers Clawboo's Memory, Tasks and Tools servers in the Gateway config's `mcp.servers`, without an agent identity, so the MCP servers treat OpenClaw sessions as unverified callers. See [@clawboo/mcp](/reference/packages/mcp). * **No cost events.** The Gateway sends no token usage, so Clawboo estimates OpenClaw spend from characters. `mapFrameToRuntimeEvents` is pure. The server's session activity watcher also uses it on its own, to show tool activity from OpenClaw runs Clawboo did not start (an OpenClaw cron job, a message from a chat channel). | Gateway frame | Events | | - | - | | `chat` delta | assistant and reasoning `text-delta`, plus any tool calls and results in the message | | `chat` final | tool calls and results, then `done` with reason `success` | | `chat` aborted | `done` with reason `aborted` | | `chat` error | a fatal `error`, then `done` with reason `error` | | `agent` lifecycle start, end, error | `status` running, `status` turn-complete, a fatal `error` | | `agent` tool stream | `tool-call` on start and `tool-result` on result; updates are dropped | | `agent` other streams | `text-delta` (reasoning when the stream is a reasoning stream) | | `session.message` | `tool-call` and `tool-result` only, never text or `done` | | anything else | nothing | Tool results from the `agent` tool stream and from `session.message` frames are cut at 4,000 characters and end with a note of how many were not recorded. ## Mock (fault injection) `@clawboo/adapter-mock` is a real runtime, `clawboo-mock`, not a test double. It reproduces silence, crashes, open tool calls and slow starts through the same server paths a real runtime uses. It executes nothing: every tool call is a synthesized event. The server offers it only when `CLAWBOO_ENABLE_MOCK_RUNTIME` is exactly `1`. Then team turns, delegations and agent routines can route to an agent whose runtime is `clawboo-mock`, and `GET /api/runtimes` lists it under `runtimes` (never under `available`). The dashboard draws runtime cards from its own catalog, so it never shows the mock, and a test seeds that agent row directly (as the wake bridge tests do). The run's message and context carry the faults as directives, one per line: a `!word` at the start of a line, optionally indented. Other lines are ignored, and a message with no directive ends in `done: success` with the summary `mock run complete`. | Directive | Effect | | - | - | | `!ok <text>` | emits the text (default `ok`), which also becomes the run's summary | | `!silent <ms>` | emits nothing for that long (default 1 second) | | `!toolcall <name>` | a tool call that never gets a result (default name `build`) | | `!error <message>` | one fatal `error` with code `mock`, then the stream ends with no `done` | | `!abort` | holds the run open until it is aborted, then `done: aborted` | | `!loop <n>` | n identical tool call and result pairs (default 3, at most 50) | | `!slowstart <ms>` | delays `start()` (default half a second) | | `!crash` | throws from `events()` | Delays are capped at ten minutes. The mock declares no worktrees, no resume and no steering, and `setModel()` and `writeContext()` do nothing. ## Related * [Write a runtime adapter](/reference/packages/executor): build and test a new adapter. * [The RuntimeAdapter trait](/internals/runtime-adapter): the interface and the server wiring checklist. * [Choose a runtime](/runtimes/index): the runtimes and their capabilities as users see them. * [What agents can do](/concepts/permissions): the safety posture of each runtime. * [Budgets and spend](/using/budgets-and-spend): how real and estimated costs reach the dashboard. # @clawboo/board-core Source: https://docs.claw.boo/reference/packages/board-core The task rules every layer shares: the seven statuses and their legal moves, the auto-fire policy, the Needs you reasons, and a task's run cost. `@clawboo/board-core` declares the board's rules once: which statuses a task can have, which moves between them are legal, when the board fires a task again on its own, why a task is waiting for a person, and what its runs cost. Every layer imports this one copy, so a newly legal move reaches the server, the team engine and the board view together, and a mismatch is a compile error. Use this page when you change a task rule. How the database enforces the rules is on [Board internals](/internals/board-internals), and the board as users see it is on [The board](/concepts/the-board). ## Who uses it * **`@clawboo/db`** enforces the transition table inside `updateStatus` and wraps the other rules for whole boards (`attentionForTasks`, `runCostForTasks`, `isLedgerAutoFireable`). * **`@clawboo/team-orchestration`** uses the fire policy and the person-assignment markers when it dispatches work. * **The server** attaches `attention` and `runCost` to every task the board API returns. * **The SPA** builds the status editor from `legalTargets`, routes tasks into the **Needs you** column, and shows run cost on cards. The package has no dependencies at all and imports nothing outside its own files, so it can ship to the browser. `apps/web/src/__tests__/browserBundlePurity.test.ts` checks that the built files declare no bare import and that `state-machine.ts` imports nothing; the package's own tsconfig pins `types: []`, so a Node global fails the type check. ## Statuses and legal moves `TASK_STATUSES` lists the seven statuses in lifecycle order. `done` and `cancelled` are terminal. | From | Legal targets | | - | - | | `backlog` | `todo`, `blocked`, `cancelled` | | `todo` | `in_progress`, `blocked`, `backlog`, `cancelled` | | `in_progress` | `in_review`, `done`, `blocked`, `todo`, `cancelled` | | `in_review` | `done`, `in_progress`, `blocked`, `cancelled` | | `blocked` | `todo`, `in_progress`, `backlog`, `cancelled` | | `done` | none | | `cancelled` | none | * `canTransition(from, to)` answers one move; a move to the same status is a harmless no-op and returns `true`. * `legalTargets(from)` returns the targets in table order as a fresh array, empty for a terminal status. * `isLocked(status)` is `true` for `in_progress` and `in_review`: a task someone is working on keeps its assignee. * `isTerminal(status)` is `true` for `done` and `cancelled`. The server is the authority: `updateStatus` re-checks the table against the freshly read row, and an illegal move through the board API returns `409`. The one writer outside the table is the orphan reaper (`reconcileOrphans`), which releases a stale `in_progress` or `in_review` task straight to `todo`. The board view shows **Needs you** and then five status columns (**Backlog**, **To do**, **In progress**, **Done**, **Cancelled**). An `in_review` task sits in **In progress** under a **Verifying** badge, and every `blocked` task appears in **Needs you**. ## When a task fires again on its own `ledgerAllowsAutoFire(execs)` reads a task's run ledger, oldest run first, and decides whether the ready-pump may start it again. The team engine's pump and the server's scan both call it, so they agree on what will run. | The ledger | Result | | - | - | | empty (never delivered) | fire | | last run still `running` | leave it, someone owns it | | last run `cancelled` (a person pressed Stop) | never fire again on its own | | `MAX_AUTO_FIRES` unsuccessful runs in a row at the end | park it for a person | | anything else (timed out, failed, orphaned) | fire again | `trailingFailedRuns` counts the runs at the end of the ledger that ended neither `succeeded` nor `cancelled`. A task's `sourceDelegationId` records who it is bound to: `delegationTargetOf` reads the agent out of it, and `encodeHumanAssignment` writes the marker for a task a person assigned, which `isHumanAssignment` detects. A person-assigned task reports its result on its card instead of to the team's leader. ## Why a task needs a person `taskAttention` returns the reason a task waits on a person, or `null` when the board will move it on its own. The board view puts every task with a reason in **Needs you**. | Status | Reason | | - | - | | `blocked` | always one: `needs_review` when the verdict blocks, else `timed_out` or `failed` from the last run, else `blocked` | | `todo` | `unassigned` with no bound agent, `stopped` when the last run was cancelled, `failed` or `timed_out` once parked | | `in_progress` | `unstarted` when no agent is assigned and nothing is running (a card moved there by hand starts nothing) | | any other | none | The result carries the reason, the number of trailing failed runs, and a `detail` taken from the last run's error (trimmed to a short excerpt). ## Run cost `summarizeRunCost(runs, taskCostUsd)` adds up what a task's runs cost, for the card and the drawer: * A run counts only when its `costUsd` is a finite number, so a run stopped before it priced itself adds nothing. * The total is marked as an estimate unless every counted run's `costSource` is `reported` or `local`; a missing or unknown source counts as an estimate. * When every counted run is `local`, the result also carries `local: true`: the task ran only on a model the user's own Ollama served, and the board shows its cost as `free`. * With no counted run, a positive cost stored on the task row itself is returned as an estimate; otherwise the cost is `null`. `RUN_COST_SOURCES` lists `reported`, `local`, `token-estimate` and `char-estimate`, most trustworthy first. `local` is a run on a model the user's own Ollama served, so its \$0 is exact. ## Test it Run `pnpm --filter @clawboo/board-core test`, and `pnpm --filter @clawboo/board-core typecheck`, which checks the source and the tests against separate tsconfigs. A rule change usually needs a matching case in the board tests of `@clawboo/db` and in the cascade contract of [@clawboo/team-orchestration](/reference/packages/team-orchestration). ## Related * [Board internals](/internals/board-internals): how the database enforces these rules, claims and reconciles. * [The board](/concepts/the-board): statuses and columns as users see them. * [Track work on the board](/using/board): working the board day to day. * [@clawboo/db](/reference/packages/db): the repository that applies the rules. # Add a connector Source: https://docs.claw.boo/reference/packages/connector-catalog Add or change an entry in @clawboo/connector-catalog, the offline connector directory, and pass the checks CI runs on it. `@clawboo/connector-catalog` is the directory the Connectors view shows: the curated connectors Clawboo has checked itself, a committed snapshot of the official MCP registry (the community entries), and the apps reachable through a broker. It is plain data plus lookups, with no runtime dependencies and no network access, so the directory renders with no loading state even offline. Use this page when you add or change a curated connector, refresh the community snapshot, or add a brokered app. How people connect one and give it to an agent is on [Connect tools to your agents](/using/connectors). ## What lives where | Path (`packages/connector-catalog/`) | Holds | | - | - | | `src/types.ts` | `ConnectorDefinition`, the shape of one entry | | `src/sources/dev.ts`, `data.ts`, `productivity.ts` | the curated entries, grouped by area | | `src/catalog.ts` | the merged list and its lookups: `connectorBySlug`, `connectorsByCategory`, `searchConnectors`, `connectorCounts` | | `src/connectable.ts` | `connectRefusal` and `isConnectable`, the one rule the server enforces and the browser renders | | `src/snippet.ts` | `connectorSnippet`, a paste-ready config block for Claude Code, Codex or VS Code | | `src/generated/community.ts` | the community snapshot, generated and loaded only through the `./community` entry point | | `src/brokeredApps.ts` | `BROKERED_APPS`, the apps a broker can reach; display rows, not connectors | An entry is a definition, not an install. Connecting one creates a `connectors` row in the database, its tools show up in the capability inventory, and no agent can call them until someone grants the connector to that agent (see [@clawboo/governance](/reference/packages/governance)). ## Add a curated connector **1. Write the entry** Add a `ConnectorDefinition` to the source file for its area, with `provenance: 'curated'` and a kebab-case `slug` (the catalog's key, also used for its logo and its snippets). Give it a `displayName`, write `description` in plain verbs ("Read and write issues"), pick a `category`, add search `tags`, and set `popular: true` only for a name people arrive looking for. **2. Pin the launch** The `launch` field says how to start the server. For a local server, set `transport: 'stdio'`, the `command`, and `args` with an exact version baked in (for example `npx -y <package>@<version>`), and put the same version in `pinnedVersion`. A bare package name resolves to the latest release on every spawn, so unpinned entries fail the checks. For a remote server, set `transport: 'streamable-http'` and an `https` `url`. **3. Describe the credentials** Pick `auth.kind`: `none`, `api-key`, `bearer` or `oauth`. In `auth.inputs` (empty for `none`, at least one for `api-key`), list one input per credential with its `key` (the variable or header name), a `label` in the vendor's words, a one-line `description`, a `docsUrl` where a person makes it, `required`, and `secret: true` for anything secret, so it lives only in the vault. Never put a value in the entry. For OAuth, pin the `scopes` and explain them in `scopesRationale`. Set `needsPreregisteredApp` when the provider offers no dynamic client registration, because Clawboo cannot sign itself in there. **4. State what it can reach** `egressAllow` lists the hosts it talks to. `trifecta` says, at its most permissive, whether it reads private data, ingests untrusted content and can send data out; the grant rules use the union of these across a run. If the server will not start without a path or value only a person can supply, set `requiresUserArgument` and describe it in `userArgument` (with `replacesArg` when the args hold a placeholder to replace). A browser connector also declares `perAgentProfileFlag` in its `launch`, so each agent gets its own browser profile. **5. Run the checks** Try the server against its real package or endpoint before you commit; every curated entry was checked that way. Then run: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} pnpm --filter @clawboo/connector-catalog test pnpm verify:connectors ``` The package tests validate the shape and the honesty rules (pinned versions, `https` endpoints, no secret values, secret inputs marked, declared egress, browser profiles). `pnpm verify:connectors` is the offline gate CI runs, and `pnpm verify:connectors --live` also re-resolves every pinned version and dials every remote endpoint; it needs the network, so run it by hand. **6. Add the logo** Run `pnpm generate:brand-marks`. It rebuilds `apps/web/src/features/connectors/brandMarks.ts` from simple-icons and attaches a mark only when the connector's display name, the first word of it, or its slug (lower-cased, punctuation removed) matches a simple-icons title or slug, or through an entry you add to `ALIASES` in `scripts/generate-brand-marks.ts`. A connector with no match shows no logo, which is better than the wrong one. **7. Check it in the dashboard** Start the dev server and open **Connectors**. You should see the new entry in the curated list, with the button that `COST_COPY` in `src/cost.ts` gives its state: **Turn on**, **Connect**, **Add key** or **Choose folder**. ## Refresh the community snapshot `pnpm ingest:connectors` snapshots the official MCP registry into `src/generated/community.ts` and updates the count file beside it. Treat the result as a reviewed change: it is a diff a person reads, and it never runs on the release path, so the community list cannot change under users without a release. `pnpm verify:connectors` checks the committed snapshot against its recorded digest. Three rules keep the community entries honest: * **They cannot be connected with one click.** `connectRefusal` returns `community-unsandboxed` for them: a community server would run as you, unsandboxed, and nobody at Clawboo has read it. A person who trusts one can add the same server as a custom connector. * **They load late.** The SPA imports `@clawboo/connector-catalog/community` only when someone scrolls to them or a curated search finds nothing, so the snapshot stays out of the first page load. * **Counts stay split.** `connectorCounts()` returns curated and community numbers separately, and the UI never shows one combined total. ## Add a brokered app `BROKERED_APPS` lists the apps a broker (Composio) can reach. Each row has Clawboo's `slug` (also its logo key), a `name`, a `category`, a `description`, and the broker's own `toolkit` name. Copy the toolkit name from the broker's toolkit page: it often differs from the slug (`googlecalendar` against `google-calendar`). These rows are never launched or connected one by one, and whether an app is connected comes from the broker. ## Who reads the catalog * **The server**, in the connector routes and `apps/web/server/lib/connectors/`, which launch connectors through the [@clawboo/mcp](/reference/packages/mcp) connector client with `resolveLaunchArgs` and an allowlisted environment. * **The SPA**, in the Connectors view and the connector request card an agent's reply can carry (`ask.ts`). * **Root scripts**: `scripts/verify-connectors.ts`, `scripts/ingest-connector-registry.ts` and `scripts/generate-brand-marks.ts`, which import the package source by relative path. Because `connectRefusal` is shared, the server refuses exactly what the browser greys out, so change it in one place and both follow. ## Related * [Connect tools to your agents](/using/connectors): connecting a connector and giving it to agents. * [@clawboo/governance](/reference/packages/governance): the grant rules that decide each connector tool call. * [@clawboo/mcp](/reference/packages/mcp): the client that launches and talks to connectors, and its OAuth flow. * [Connectors API](/reference/rest-api/connectors): the routes behind the Connectors view. # @clawboo/db Source: https://docs.claw.boo/reference/packages/db The SQLite data layer: who owns the schema, how a database opens and upgrades, the two default file paths, the repository modules, and the rules for writers. `@clawboo/db` is the only package that reads or writes Clawboo's SQLite database. It declares the schema, opens and upgrades the file, and holds every repository: the board, chat rooms, memory, the tool broker, connector grants, budgets, the event log and more. Use this page when you add a table or a column, write to the database, or need to know which module owns a piece of state. Each table is documented on [Database schema](/reference/database-schema). ## Who uses it * **The server** (`apps/web/server`), through one shared connection. * **`@clawboo/mcp`**, whose servers run inside the server and whose stdio bins open their own connection. * **`@clawboo/evals`**, which builds a throwaway database for every trial. * **The SPA**, for types only. Its entry point pulls in `better-sqlite3`, so `apps/web/src/__tests__/browserBundlePurity.test.ts` fails on any value import of `@clawboo/db` in the browser. ## Who owns the schema `SCHEMA_DDL` in `src/schemaBootstrap.ts` is the only place tables, indexes and triggers are created, and every statement in it is `IF NOT EXISTS`. `src/schema.ts` is the Drizzle type layer over the same tables: queries use it, nothing applies it as a migration, and `schemaSource.test.ts` keeps the two in step. The package entry point re-exports a Drizzle table object and its row types for almost every table. Two stay internal behind their accessors: `agent_inbox` (use `enqueueInbox` and the other inbox functions) and `tool_result_blobs` (use `putToolResult` and `readToolResult`). ## Opening a database | Function | Use it for | What it does | | - | - | - | | `openDb(path)` | a long-lived process | Creates the folder, opens the file, applies the pragmas (WAL, foreign keys, a short busy timeout) and limits the file and folder to your user. Runs no DDL. | | `ensureSchema(db)` | right after `openDb` | Upgrades the file to the schema in one `BEGIN IMMEDIATE` transaction, then returns `{ added }`, the columns it added. | | `createDb(path)` | short-lived processes | `openDb` plus `ensureSchema`. The stdio bins and the eval harness use it. | Server code never calls these. It calls `getDb()` from `apps/web/server/lib/db.ts`, which opens one connection per process, runs `ensureSchema` once at boot, and is closed only by `closeDb()` on exit. `sharedConnection.test.ts` fails if server code opens its own connection. better-sqlite3 is synchronous, so one connection is not a bottleneck. ## Where the file lives The server and the stdio bins default to different files, on purpose: * **The server** keeps its database at `~/.clawboo/clawboo.db`, which moves with `CLAWBOO_HOME`. * **`defaultDbPath()`**, which the stdio bins use, returns `CLAWBOO_DB_PATH` when it is set, else `~/.openclaw/clawboo/clawboo.db`. A bin you start by hand therefore opens a separate, empty board. Set `CLAWBOO_DB_PATH` to the server's file, or use the attach snippet from `GET /api/mcp/config`, which sets it for you. Clawboo's own Claude Code, Codex and Hermes runs reach the MCP servers over HTTP inside the server process, so they always use the server's file. WAL mode makes the shared file safe for several processes. See [Data and state](/operating/data-and-state) and [MCP servers](/operating/mcp-servers). ## Upgrading the schema There is no migration ladder: no numbered migration files, no `user_version`, no migrate script. `ensureSchema` runs two phases in one transaction. First the reconciler reads the column list back out of `SCHEMA_DDL` and adds any column an existing table is missing. Then the DDL batch creates whatever tables, indexes and triggers are absent. A failure anywhere leaves the file as it was, and the next start tries again. A new column on an existing table must be one SQLite can add to a table that already has rows: no `PRIMARY KEY` or `UNIQUE`, no stored generated column, a literal default, a default whenever it is `NOT NULL`, and no non-null default on a `REFERENCES` column. `schemaReconcile.test.ts` checks every new column against the frozen snapshot in `schemaBaseline.ts`, so a column that breaks the rules fails the build instead of a user's upgrade. A column that still cannot be added at boot throws `SchemaUpgradeError`, which the server treats as fatal. These changes are not in-place upgrades: changing an existing column's type, `NOT NULL` or default, removing a column, adding a table constraint, redefining an index or trigger (they match by name), and any change to the FTS5 table. Plan those separately. The upgrade rules are also covered in [Database schema](/reference/database-schema). To add a table, add its `CREATE TABLE IF NOT EXISTS` statement to `SCHEMA_DDL`, its declaration to `src/schema.ts`, its exports to `src/index.ts` and repository functions to the module that owns it, then document it on the schema page. `pnpm --filter @clawboo/db test` runs the schema guards. ## The repository modules Everything is exported from the package entry point; the source folders below are where to look and where to add. | Module (`src/`) | Owns | Main entry points | More | | - | - | - | - | | `board/` | tasks, dependencies, comments, execution runs, the board's lifecycle events | `createTask`, `createCappedSubtask`, `claimTask`, `updateStatus`, `releaseTask`, `linkDep`, `getReadyTasks`, `startTaskHeartbeat`, `reconcileOrphans`, `onBoardLifecycle` | [Board internals](/internals/board-internals) | | `board/rooms.ts` | a team's chat rooms | `createRoom`, `listRooms`, `setRoomMembers`, `archiveRoom`, `activeRoomMemberIds` | [Work with your team in chat](/using/team-chat) | | `memory/` | facts, procedures, outcome reports, embeddings, the memory graph | `SqliteMemoryStore` (`saveFact`, `searchMemory`, `browseMemory`, `recordOutcome`, `getMemoryGraph`), `computeLearningOverlay`, `projectMemoryGraph`, `resolveEmbeddingProvider` | [Memory](/concepts/memory) | | `tools/` | the tool registry, the brokered call pipeline, approvals and audit, large tool results | `executeBrokeredCall`, `createBuiltinRegistry`, `runInspectors`, `evaluateInjection`, `putToolResult`, `readToolResult`, `scrubSecrets` | [Tools and MCP API](/reference/rest-api/tools-and-mcp) | | `grants/` | connectors, capability grants, standing approval rules, per-grant rate windows | `upsertConnector`, `upsertGrant`, `listCandidateGrants`, `revokeGrant`, `mintStandingRule`, `chargeCall`, `previewGrant` | [governance](/reference/packages/governance) | | `capabilities/` | the stored capability inventory | `upsertCapabilities`, `listCapabilities`, `getCapability` | [Capabilities](/concepts/capabilities) | | `governance/` | budgets and the governance audit log | `recordSpend`, `setBudgetLimit`, `getBudget`, `resumeBudget`, `appendAudit`, `priorAllowAlways` | [Budgets and guardrails](/concepts/governance) | | `events/` | the append-only orchestration event log | `appendEvent`, `listEvents` | [Events and errors](/reference/events-and-errors) | | `sessions/` | session rotation lineage | `recordRotation`, `getSessionLineage` | [The executor runner](/internals/executor-runner) | | `routines/` | the routines ledger and fire history | `registerScheduledRun`, `queueDueRuns`, `claimScheduledRun`, `recordRunOutcome`, `listRoutineFires` | [scheduler](/reference/packages/scheduler) | | `teamChat/` | the agents' peer channel (`team_chat`), which TeamChat MCP tools post to | `postToRoom`, `readRoom`, `resolveRoomForTeam` | [How agents talk to each other](/concepts/peer-chat) | | `inbox.ts` | each agent's mailbox of updates, alerts and signals | `enqueueInbox`, `listUndeliveredInbox`, `packInboxRows`, `markInboxDelivered`, `reapOrphanInbox` | [The executor runner](/internals/executor-runner) | | `chat/` | the dashboard's chat transcript (`chat_messages`) | `listRecentChatMessages`, `listChatMessagesSince`, `latestChatMessageId` | [Work with your team in chat](/using/team-chat) | A few modules carry rules worth knowing before you change them: * **Rooms.** A team's own chat is its default room and has no row; its room id is `null`. A room's live roster is its stored members intersected with the team's active agents, so an archived or moved agent drops out without a sweep. * **Grants.** `grants/` is the stored half of connector permissions: the rows, their uniqueness keys, the spec digests that drift is measured against, and the hourly call counter. The decision itself is the pure `decideGrant` in `@clawboo/governance`, which the broker's grant gate calls for every connector tool call. * **The broker.** `executeBrokeredCall` runs one pipeline for the Tools MCP server and REST callers: availability, provenance (off by default), argument validation, the grant gate (connector tools only), the inspector chain, approval when required, execution, then compaction of the result. Every path writes audit rows with secrets scrubbed. Inspectors return `allow`, `deny`, `require_approval`, `rewrite` or `observe` (allow, but record what a stricter reading would have refused). * **Memory.** `searchMemory` runs hybrid search when an embedding provider is available and falls back to keyword search when there is none. Outcome reports (`useful`, `dead_end`, `corrected`, `cited`) feed the learning overlay, which never rewrites a fact. The graph projection is pure and deterministic, so the same data always draws the same graph. * **Embeddings.** The provider order is an explicit provider, then a local Ollama that has the embedding model, then OpenAI when a key allows it. A store that was ever indexed locally needs the user's consent before it embeds remotely, and `CLAWBOO_DISABLE_EMBEDDINGS=1` turns embeddings off. ## Rules for writers * **Write through the contention helpers.** Use `immediateWrite` (a `BEGIN IMMEDIATE` transaction) or `withWriteRetry`. They retry lock errors with jitter inside a wall-clock budget (`CLAWBOO_DB_WRITE_BUDGET_MS`), then throw `WriteBudgetExhaustedError`, which still reads as busy. The recipe is on [Board internals](/internals/board-internals). * **Never retry a lost claim.** `claimTask` returns `conflict` when another worker won. That is an answer, not an error. * **Change status through `updateStatus`.** It enforces the `@clawboo/board-core` transition table inside the write transaction and refuses `done` with `verification_required` for a task whose verdict does not allow it, unless the caller passes `humanOverride`. * **Publish after commit.** Board lifecycle events reach `onBoardLifecycle` subscribers only after the transaction commits, so a subscriber never sees a write that rolled back. * **Mark only what was delivered.** After `packInboxRows`, mark delivered only the row ids it reported as included. * **Scrub before storing.** Tool arguments, results and event payloads pass through `scrubSecrets`, which replaces credential-looking values with `[REDACTED]`. * **Budgets default to warn.** `setBudgetLimit` creates a budget in `mode: 'warn'` unless the caller passes `mode: 'cap'`. Updating a budget without `mode` keeps the mode it has, so re-setting a hard cap's limit keeps it a hard cap. `pnpm --filter @clawboo/db db:studio` opens Drizzle Studio on the development file `packages/db/dev.db`, never on your real database. ## Related * [Database schema](/reference/database-schema): every table, column and index. * [Board internals](/internals/board-internals): the state machine, the atomic claim and the contention recipe. * [Data and state](/operating/data-and-state): where the files live, backups and resets. * [@clawboo/governance](/reference/packages/governance): the pure decisions these tables record. * [@clawboo/mcp](/reference/packages/mcp): the MCP servers built on these repositories. # Write a runtime adapter Source: https://docs.claw.boo/reference/packages/executor Build a runtime adapter on @clawboo/executor: declare honest capabilities, map native frames to RuntimeEvent, and pass the shared contract suite. `@clawboo/executor` is the package every runtime adapter builds on. An adapter starts a run on one runtime (a coding CLI, an SDK, a long-lived connection) and turns that runtime's own output into the shared `RuntimeEvent` stream that the task runner, the team engine and the dashboard read. Use this page when you write the adapter package for a new runtime or change an existing one. The interface is explained member by member in [The RuntimeAdapter trait](/internals/runtime-adapter). Wiring a finished adapter into the server is the [Add a runtime](/internals/runtime-adapter#add-a-runtime) checklist. ## What you import | Entry point | What an adapter author uses from it | | - | - | | `@clawboo/executor` | `RuntimeAdapter`, `Capabilities` and the run types; the `RuntimeEvent` union; `createAsyncQueue`; `steerThroughDriver`; `withIdleTimeout`; the session-rotation helpers; `resolveRuntimeIntegration` | | `@clawboo/executor/contract` | `runAdapterContract` and the `AdapterTestHarness` type. It imports vitest, so use it from tests only. | | `@clawboo/executor/tiers` | `assembleTiers`, `sortToolDefs` and `dateStamp`, which keep a prompt in cache-friendly order | The package has no dependencies and imports nothing from `node:*`. Keep your adapter the same way: it never spawns a process or opens a socket. A driver on the server does that, and the adapter receives it as an injected factory, or as a connected client in OpenClaw's case. ## Before you begin * Know how the runtime runs without a person at the keyboard (an SDK call, a CLI with machine-readable output, a long-lived connection) and what it reports: text deltas, tool calls, token usage, cost. * Read [Runtime adapters](/reference/packages/adapters) to see how the six existing adapters differ, and pick the closest one to copy. For a CLI or an SDK that runs once per task, that is Claude Code, Codex or Hermes. ## Write the adapter **1. Create the package** Make `packages/adapters/<id>/` with `@clawboo/executor` as its only workspace dependency and vitest as a dev dependency (the [Packages](/reference/packages/index) page has the full recipe). Export the adapter class, a type for the runtime's native frames, and a pure function that maps one native frame to `RuntimeEvent[]`. The per-run pattern keeps the adapter small. The constructor takes `(driverFactory, healthCheck?)`. `start()` calls the factory once per run, the driver exposes `start()`, `onEvent(handler)` (returning an unsubscribe function), `abort()`, `setModel()`, `writeContext()` and an optional `steer()`, and the adapter delegates to it. **2. Declare only true capabilities** `capabilities()` tells the host what the runtime can do: `streaming`, `mcp`, `worktrees`, `resume`, `toolApproval`, `models`, and optionally `steering`, `contextWindowTokens` and the integration fields (`runtimeClass`, `nativeHome`, `nativeSkills`, `nativeMemory`, `nativeChannels`, `nativeScheduler`). The host routes on these through `resolveRuntimeIntegration` and never on the runtime id. A missing claim is read conservatively: no `steering` means `none`, and no `runtimeClass` means a one-shot run. > [!WARNING] > > `toolApproval: true` says the runtime can ask before it uses a tool. It does not mean Clawboo's runs are gated: > Clawboo drives Claude Code, Codex and Hermes with their own approval prompts and sandboxes turned off. See > [What agents can do](/concepts/permissions) before you describe a runtime's safety anywhere. **3. Map native frames to RuntimeEvent** There are seven event kinds: `text-delta` (with `channel` set to `assistant` or `reasoning`), `tool-call`, `tool-result`, `status`, `cost`, `done` (reason `success`, `max_turns`, `aborted` or `error`) and `error` (with a `fatal` flag). Every event carries `runId`, `sessionId`, `ts` and a strictly increasing `seq`. Follow these rules: * `start()` returns `runId: null`. Bind the run id from the first native frame that carries one, and fall back to the session key. * A runtime with no incremental text emits one `text-delta` that carries the whole message. * Put provider USD in `costUsd` only when the runtime reports it. If it reports tokens but no price, send `costUsd: null`, `estimated: true` and the usage. If it reports neither, emit no `cost` event. * Drop frames you do not recognise instead of throwing. * End every run with `done`, or with an `error` whose `fatal` is `true`. `createAsyncQueue` turns the driver's callback into the async iterable `events()` returns. It drops the oldest event when full, and closing the iterator closes the queue, which is how `events()` releases its subscription. **4. Implement the control members** * `abort()` cancels the run. The stream must still end with `done` (reason `aborted`); the contract suite checks it, and the host treats that event as the end of the run. * `setModel()` and `writeContext()` reach the driver. A runtime that fixes its model at launch may treat `setModel()` as a no-op, as the Claude Code, Codex and Hermes drivers do. * If you declare `steering`, implement `steer()` with `steerThroughDriver(driver, text, mode, opts)`. It returns `no-run` with no driver, `unsupported` when the driver has no `steer` or a passive message meets an `interrupt-resume` runtime, `run-ended` when the driver answers `false`, and `rejected` when it throws. * If the runtime can resume a session, expose a `sessionCodec` that serializes the session id. **5. Pass the contract suite** In the adapter's tests, build an `AdapterTestHarness` over a fake driver and call `runAdapterContract`. The harness says how to make the adapter, start a run, emit a native frame, and list the side effects the adapter issued. ```ts theme={"theme":"github-dark"} import { runAdapterContract, type AdapterTestHarness } from '@clawboo/executor/contract' function makeHarness(): AdapterTestHarness { let driver = new FakeDriver() return { label: 'my-runtime', makeAdapter: () => { driver = new FakeDriver() return new MyRuntimeAdapter(() => driver) }, start: (adapter) => adapter.start({}, { agentId: 'a1', sessionKey: 'agent:a1:main', message: 'hi' }), emit: (frame) => driver.emit(frame as MyNativeFrame), frames: { delta: (text) => ({ type: 'text', text }), toolCall: (name, input) => ({ type: 'tool-call', id: 'tc-1', name, input }), final: (summary) => ({ type: 'result', ok: true, summary }), aborted: () => ({ type: 'result', ok: false, aborted: true, summary: '' }), error: (message) => ({ type: 'result', ok: false, errorMessage: message, summary: '' }), }, recordedCalls: () => driver.calls, } } runAdapterContract(makeHarness()) ``` The suite checks a stable id, a well-formed `capabilities()`, a `health()` that answers within two seconds, a late-bound `runId`, increasing `seq`, a run that ends in `done: success` or `done: aborted`, and that `abort`, `setModel` and `writeContext` reach the driver. Run it with `pnpm --filter @clawboo/adapter-<id> test`; you should see a `RuntimeAdapter contract` block pass. Put runtime-specific tests in the same file. **6. Wire it into the server** Follow the [Add a runtime](/internals/runtime-adapter#add-a-runtime) checklist. In short: a driver under `apps/web/server/lib/runtimes/`, a descriptor entry, a case in `adapterFactoryFor()` with a health check, a capability source, an agent source, an `AGENT_FILE_RUNTIME_SUPPORT` row, and the dashboard's runtime catalog. **7. Check it end to end** Run `pnpm build` and `pnpm test`, start the dev server, and connect the runtime from the Runtimes panel. Create a board task, then run it with `POST /api/runtimes/<id>/run` and a body of `taskId` plus `"kind": "research"`. A `code` task (the default kind) also needs a `repoPath` for its worktree, or the run is refused with `workspace_unavailable`. The request stays open until the run ends (see [Runtimes API](/reference/rest-api/runtimes)). You should get a `200` response with `"ok": true` and a `doneReason`. ## How the host treats your adapter * **A fresh adapter per run.** The server builds one through `adapterFactoryFor(id)` for each run, so keep no cross-run state in the adapter. `RuntimeRegistry` is exported but the server does not use it. * **Silence ends a run.** The task runner and the team engine wrap your stream in `withIdleTimeout`. Past the silence ceiling (`CLAWBOO_RUN_SILENT_TIMEOUT_MS`) the host calls `abort()` and waits a short grace period for your terminal event, then ends the stream itself. * **Rotation needs a window.** With `contextWindowTokens` set, the task runner starts a successor session before the window fills. A `done` with reason `max_turns` triggers rotation too. * **Missing cost is estimated.** A `cost` event with `costUsd: null` and usage is priced from the tokens and marked as an estimate. A run that reports no usage at all is estimated from the characters it was sent and produced, on task-runner runs and in team chat alike. A `cost` event with `local: true` marks a model the user's own Ollama served, and the host records it at \$0. See [Budgets and spend](/using/budgets-and-spend) for what users see. * **Some claims change the path.** A runtime that declares `runtimeClass: 'connected-substrate'` is refused by the task runner, and one that declares `worktrees: false` gets no task worktree. * **Prompt order.** The task runner builds every prompt with `assembleTiers`, frozen content first. No Clawboo code reads the `cacheBreakpoints` it returns. ## Troubleshooting > [accordion] The contract block does not appear in the test output > > The suite registers its tests on the vitest the adapter package resolves, because the package keeps vitest > external. Add `vitest` to the adapter's `devDependencies`, run `pnpm install`, and call `runAdapterContract` from a > file that vitest collects (`src/__tests__/*.test.ts`). > [accordion] A stopped run takes a long time to end > > After `abort()`, the stream must still yield `done` with reason `aborted`. Without it, the run ends only when the > silence ceiling closes the stream. If the driver drops its listeners on abort, synthesize that event in the > adapter. > [accordion] Spend shows nothing for the new runtime > > The mapper emitted no `cost` event. Emit one with the token usage, even when the runtime reports no price. ## Related * [The RuntimeAdapter trait](/internals/runtime-adapter): every member and event kind explained. * [Runtime adapters](/reference/packages/adapters): how the six existing adapters differ. * [The executor runner](/internals/executor-runner): how a task run claims, isolates, drives and verifies. * [What agents can do](/concepts/permissions): the safety posture of each runtime. * [Choose a runtime](/runtimes/index): the runtimes as users see them. # Gateway client and proxy Source: https://docs.claw.boo/reference/packages/gateway-client The OpenClaw Gateway client, the server-side proxy and device identity, the optional access gate, and the always-on Origin and Host guard. Two packages carry Clawboo's connection to an OpenClaw Gateway and guard the dashboard's own front door. `@clawboo/gateway-client` is the WebSocket client the SPA and the server use to talk to a Gateway. `@clawboo/gateway-proxy` is its server half: the same-origin WebSocket proxy the browser connects through, the device identity that signs the Gateway handshake, the optional access gate and the always-on Origin and Host guard. Use this page when you change how Clawboo talks to OpenClaw or touch dashboard access control. The operator's view of the same controls is on [Security model](/operating/security). > [!NOTE] > > The Ed25519 device identity only proves this Clawboo install to the OpenClaw Gateway. Who may open the dashboard > is decided by the access gate (off unless `STUDIO_ACCESS_TOKEN` is set) and the Origin and Host guard. ## The Gateway client `GatewayClient` connects to a Gateway, correlates requests and responses, delivers event frames to `onEvent` subscribers, and reconnects with backoff when a connection that was open drops. `call(method, params)` sends any RPC, and typed groups cover the common ones: `agents` (list, create, files), `sessions`, `config` and `chat`. `connect(url, options)` negotiates OpenClaw's protocol range and takes these options: * `token` or `password` for the Gateway's own auth. * `caps`, the capabilities the connection declares. The default, `GATEWAY_BROWSER_CAPS`, declares exec approvals. The server's own connection declares `tool-events` and `exec-approvals`. * `signConnect`, for a caller that signs the connect frame with its own identity, and `webSocketImpl` and `origin`, for a caller outside the browser. The server passes the `ws` package's constructor and the Gateway host as the origin. * `disableDeviceAuth`. The client can sign the connect frame itself in a secure browser context, with an Ed25519 key kept in `localStorage`, but the dashboard turns that off because the proxy signs for it. `GatewayResponseError` carries the Gateway's error code (the pairing flow keys off `NOT_PAIRED`), and `isAuthConnectError` and `authRetryAfterMs` tell a caller whether and when to retry an auth failure. The package is browser-safe: it uses the global `WebSocket` and never imports `ws`, although its manifest still lists it. The SPA uses it for the live connection, events and the pairing flow. On the server it backs the shared Gateway connection in `apps/web/server/lib/agentSource/registry.ts`, the OpenClaw capability source, and the Permissions tab routes. `@clawboo/events` and `@clawboo/adapter-openclaw` import its types. ## Exec approvals The Gateway keeps every agent's exec posture and its standing command grants in one fleet-wide document, reachable only through `exec.approvals.get` and `exec.approvals.set`. The package's exec helpers follow four rules, and any new code that touches the document must too: * **Treat `exec.approvals.get` as a write.** The Gateway re-saves the document when it answers, and an unparseable stored document would be replaced with a fail-closed default for every agent. Code that only reads grants reads OpenClaw's stored row directly and read-only, as `openclawExecAllowlistRead.ts` does. * **Write under compare-and-swap.** `mutateExecApprovalsDoc(client, mutate)` reads the document, applies `mutate`, and writes it back against the base hash, re-running `mutate` on a fresh read after a conflict. `upsertExecApprovalPolicy` uses it to set one agent's posture while keeping its grants. * **Filter rows, never rebuild them.** A grant that loses its `argPattern` in a round trip stops matching the command it was made for. `revokeExecAllowlistEntries` removes rows by content fingerprint (`execAllowlistEntryKey`), refuses a row that also lives in the wildcard bucket, and checks the Gateway's post-write reply before it reports `revoked`. * **Patch sessions with `execHost` only.** OpenClaw refuses the retired `execSecurity` and `execAsk` session fields, so `resolveExecPatchParams()` returns only `execHost`. `classifyExecAllowlistEntry`, `isEffectiveExecGrant` and `affectsEnforcement` say what one stored row means. The user side is [Review and revoke command permissions](/using/command-permissions). ## The proxy and device identity `createGatewayProxy({ loadUpstreamSettings })` serves the WebSocket at `/api/gateway/ws`. The browser opens a same-origin socket, and the proxy opens the upstream Gateway connection for it, fills in the Gateway token and a device signature on the `connect` frame, and then forwards frames both ways with a bounded buffer and a keepalive. The browser never sees the Gateway token. `loadOrCreateProxyDeviceIdentity()` keeps one Ed25519 keypair in `proxy-device-identity.json` in Clawboo's state folder, with the folder limited to your user and the file re-tightened on every load. `signConnectParams` signs a connect frame with it. The server's own Gateway connection reuses the same identity through the client's `signConnect` hook, so the dashboard and the server count as one paired device. ## The access gate `createAccessGate({ token, basePath, authorizeRuntime })` is off unless `STUDIO_ACCESS_TOKEN` is set. When it is on: * Opening the dashboard with `?access_token=<token>` sets an HttpOnly `clawboo_access` cookie, scoped to the base path and marked `Secure` over TLS, then redirects to remove the token from the address bar. * Every `/api/*` request and every Gateway WebSocket upgrade needs that cookie. The comparison is constant-time, and the path check ignores case. * The one exception is an `/api/mcp/*` call from a runtime the server started. `authorizeRuntime` admits it only when it carries a valid attach token, came straight to the loopback port, and was not forwarded. `isForwardedRequest` looks for `Forwarded`, `X-Forwarded-*` and `X-Real-IP`, and those headers can only take access away. A token with a character outside `A-Z a-z 0-9 . _ ~ -` would break the cookie, so `createAccessGate` throws on one. The server checks first with `findUnsupportedTokenChar` (the allowed set is `ACCESS_TOKEN_CHARSET`): it logs `SECURITY: refusing to start` and exits 1 on every bind, and `CLAWBOO_ALLOW_INSECURE=1` does not skip it. A non-loopback bind with no token also refuses to start, unless `CLAWBOO_ALLOW_INSECURE=1`. Generate the token with `openssl rand -hex 24`. When a forwarded request reaches a server with no token, the server logs a one-time warning (`proxyExposureWarning.ts`). ## The Origin and Host guard `createOriginGuard({ port, bindHost, allowedOrigins, allowedHosts })` runs on every `/api/*` request and WebSocket upgrade, whether or not the access gate is on. It closes cross-site WebSocket hijacking, DNS rebinding and cross-site requests on a default install with no configuration. A request passes when: * its `Host` is a loopback name, the bind host, or listed in `CLAWBOO_ALLOWED_HOSTS`; * its `Origin`, when present, is a loopback origin on the API port, the bind host, the Vite dev server in development, or listed in `CLAWBOO_ALLOWED_ORIGINS`; * its `Sec-Fetch-Site`, when present, is `same-origin`, `same-site` or `none`. Anything else gets `403` with `Cross-origin request blocked.` The two environment lists only widen the allowed set, so a reverse proxy or a LAN address must be listed in them. The guard is not authentication: a non-browser client can send any headers it likes, which is why a non-loopback bind needs the access gate. ## Test it Run `pnpm --filter @clawboo/gateway-client test` and `pnpm --filter @clawboo/gateway-proxy test`. The dashboard's end-to-end suite runs against a mock Gateway; see [Testing strategy](/internals/testing). ## Related * [Security model](/operating/security): the access model, tokens and safe exposure. * [Reach Clawboo from another machine](/guides/self-host-securely): tunnels, proxies and the environment lists in practice. * [Gateway connection internals](/internals/gateway-and-events): how Gateway frames flow into the dashboard. * [OpenClaw](/runtimes/openclaw): pairing, the Gateway connection and OpenClaw setup. * [@clawboo/mcp](/reference/packages/mcp): the attach token the access gate checks. # @clawboo/governance Source: https://docs.claw.boo/reference/packages/governance The pure decision rules behind verification, budgets, delegation caps, circuit breakers and connector grants, and where each one is enforced. `@clawboo/governance` holds Clawboo's decision rules as pure functions and zod schemas, with no database, network or `node:*` access. The board, the server and the SPA all import the same rules, so a badge on screen and the check that enforces it cannot disagree. The state the rules decide over lives in [@clawboo/db](/reference/packages/db), and the server applies the decisions. Use this page when you change how Clawboo decides whether work passed verification, spend is over a budget, a delegation goes too deep, a run is looping, or an agent may call a connector tool. ## What it decides | Group (`src/`) | Decides | Main entry points | Applied by | | - | - | - | - | | `verify/` | whether a task's work passed, judged by someone other than its builder | `verificationStatusFor`, `classifySeverity`, `nextCycleDecision`, `isVerdictPromotable`, `shouldRunCritic`, `parseVerifyCommand`, the verdict schemas | the server's verification gate and `updateStatus` in `@clawboo/db` | | `budget/` | a budget's status after spend | `statusForSpend`, `budgetStatusAfter`, `softThresholdCents`, the cent conversions | `recordSpend` in `@clawboo/db` | | `caps/` | how deep and wide delegation may go, and a per-run cost ceiling | `checkDepthCap`, `checkFanoutCap`, `checkCostCap` | the board's capped create path, the team engine, the task runner | | `breaker/` | when a run is looping or burning tokens | `createBreakerState`, `stepBreaker`, `toolSignature`, `breakerConfigSchema` | the task runner | | `grants/` | whether an agent may call a connector tool | `decideGrant`, `requiredMode`, `isLethalTrifecta`, `isToolInScope`, `canonicalizeSpec` | the tool broker's grant gate in `@clawboo/db` | ## Budgets: warn is the default The budget functions compute a status from a limit and the spend so far: `active`, `soft_capped` at the soft threshold (`SOFT_CAP_PERCENT` of the limit), and `paused` at the limit. They do not decide whether `paused` stops anything. That depends on the budget's mode, stored by `@clawboo/db`: * **`warn`, the default.** Spend is tracked and warnings fire at the soft threshold and at 100%. A warn budget is stored as `soft_capped` even past its limit, so it never reads `paused` and never stops work. * **`cap`, opt-in.** The budget pauses at 100%, and the task runner, team chat and one-to-one chat refuse to start work for a paused scope (`budget_paused`). Spend is kept in micro-cents (`MICRO_CENTS_PER_CENT`), so sub-cent turns still add up. Which run paths a hard cap can actually stop, and when, is explained in [Budgets and guardrails](/concepts/governance); the user's view is [Budgets and spend](/using/budgets-and-spend). ## Caps | Rule | Default | Where it applies | | - | - | - | | `checkDepthCap` | `DEFAULT_MAX_DEPTH` = 2 | refuses a delegation once the ancestor depth reaches the limit; the board, the team engine and the task runner share this one constant | | `checkFanoutCap` | `DEFAULT_MAX_CHILDREN` = 24 | live children of one parent on the board's capped create path; the team engine applies it per turn with its own, smaller limit | | root-task rate | `DEFAULT_MAX_ROOT_CREATES` = 30 per `DEFAULT_ROOT_CREATE_WINDOW_MS` (5 minutes) | new tasks with no parent, so a looping agent trips quickly and the limit clears itself | | `checkCostCap` | none | a per-run cent ceiling the task runner applies only when the run input sets `maxNodeCents` | The caps are enforced in code below the model, whatever the prompt asks for. The full list of numbers is on [Limits and defaults](/reference/limits-and-defaults). ## Circuit breakers `createBreakerState(config)` makes a run-local accumulator, and `stepBreaker(state, signal)` feeds it one signal taken from a typed `RuntimeEvent` (a tool call, a tool result, a cost, a policy denial) and returns the first trip it sees. A trip names its reason: `iteration-cap`, `repeat-failure`, `no-progress`, `token-velocity` or `repeat-policy-denied`. `toolSignature(name, input)` identifies a repeated call from its name and a hash of its input, never from prose. `BREAKER_DEFAULTS` holds the thresholds, and `POST /api/runtimes/<id>/run` accepts a partial `breakerConfig` that `breakerConfigSchema` validates. Breakers run only on the task runner path, not in team chat. ## Verification These rules judge one attempt at a task and decide what happens next. Which runs are verified at all (only task-runner runs in a git worktree with changes to check) is explained in [How work is checked](/concepts/verification). * **One attempt.** `verificationStatusFor(deterministic, critic)` returns `fail` when the verify command failed, `fail` when it passed but the critic found a blocking problem, and `pass` otherwise. * **Blocking findings.** `classifySeverity` blocks on `security`, `crash`, `data_loss`, `wrong_algorithm` and `missing_ac`; every other finding is recorded as debt. * **Retries.** `nextCycleDecision` retries a failed attempt until the attempts run out, then marks the work as completed with debt, which sends the task to a person. The server's attempt budget is `CLAWBOO_MAX_FIX_CYCLES` plus one (two attempts by default); the package's `DEFAULT_MAX_FIX_CYCLES` is not used by the server. * **Moving to done.** `isVerdictPromotable` is the rule `updateStatus` applies to a stored verdict on the way to `done`: `pass` may move, and `completed_with_debt` may move only when the latest verify command passed. Any other verdict is refused unless a person overrides it. A task with no stored verdict was never checked, and it moves to `done` normally. * **The critic.** `shouldRunCritic` runs the read-only reviewer for delegated work, a risk flag (no caller sets one), or a diff over the size threshold (`DEFAULT_CRITIC_THRESHOLD`). * **The verify command.** `parseVerifyCommand` reads `VERIFY_CMD` from a task's `init.sh` and returns `null` for a missing value or the placeholder Clawboo writes. ## Connector grants A grant says that an agent (or a team) may use one connector, in a mode, under an approval policy. It is both the permission the broker checks and the edge the Org chart draws: the graph asks `decideGrant` the same question through `previewGrant`, so the picture and the gate cannot disagree. | Term | Values | | - | - | | Grant state | `proposed`, `active`, `suspended`, `revoked`, `expired`; only `active` authorizes | | Mode | `read`, `write`, `admin`, ranked in that order | | Approval policy | `never`, `risk`, `writes`, `always` | | Tool risk | `safe`, `external`, `destructive` | | Decision | `allow`, `require_approval` or `deny`, each with a reason | `decideGrant` checks a call in a fixed order, and the order matters: **1. Find the grant and check its state** No matching grant denies with `no-grant`. A revoked, suspended, proposed or expired grant denies, and expiry is checked against the clock as well as the stored state. **2. Check for drift** If the connector's spec or tool list changed since the grant pinned them, the call is denied (`spec-drift`). This runs before the scope check, so a renamed or redefined tool is caught as drift. **3. Check scope, mode and rate** The tool must match the grant's allow and deny patterns, the tool's required mode must not exceed the grant's mode, and the grant's hourly call ceiling must not be reached. **4. Check the run's risk** If the run's combined reach reads private data, ingests untrusted content and can send data out (the lethal trifecta), or the run is tainted and can send data out, the call needs a person's approval. These approvals are never remembered, and they run before the approval policy, so a policy of `never` cannot switch them off. **5. Apply standing rules and the policy** A standing deny rule denies. A never-remember tool always asks, whatever the policy. A policy of `always` asks next, even for a tool a standing allow rule covers, and marks the request never-remember. Otherwise a standing allow rule allows, and then the policy decides: `writes` asks for a mutating tool, `never` allows, and `risk` asks for a destructive tool or a mutating tool with external effects. The broker's grant gate in `@clawboo/db` calls `decideGrant` for every call to a connector tool, and charges the hourly window in the same synchronous step as the decision. Clawboo's built-in tools are never grant-governed. Every decision is recorded as a `grant_decision` event. How users attach a connector to an agent is on [Connect tools to your agents](/using/connectors). ## Test it Run `pnpm --filter @clawboo/governance test`. Each group has its own suite, and `grants/` tests the decision order case by case. A change to `decideGrant` usually needs a matching case in the grant gate tests in `@clawboo/db`. ## Related * [Budgets and guardrails](/concepts/governance): budgets, caps and breakers as a model, and which run paths have them. * [How work is checked](/concepts/verification): when work is checked and what a verdict means. * [@clawboo/db](/reference/packages/db): where budgets, grants and verdicts are stored. * [Connect tools to your agents](/using/connectors): connecting a connector and granting it to agents. * [Budgets and spend](/using/budgets-and-spend): warn only and hard cap as users set them. # Packages Source: https://docs.claw.boo/reference/packages/index Every private @clawboo/* workspace package: what it is for, who uses it, how the packages depend on each other, and the rules they follow. Use this page to find which workspace package owns a piece of Clawboo, what it may import, and where its deeper docs live. It is written for contributors. Every package is private, so nobody outside this repository installs one. ## At a glance | What | Count | | - | - | | Packages under `packages/` | 27 | | Runtime adapters under `packages/adapters/` | 6 | | Workspace packages published to npm | 0 | | Workspace packages `apps/web` depends on directly | 31 (all but `boo-avatar`, which arrives through `ui`, and `tsconfig`, a dev dependency) | ## How packages reach users Every `@clawboo/*` package is `private: true`. The only npm package is `clawboo`, which `pnpm assemble` builds from three pieces: * the CLI from `apps/cli`, which imports only `@clawboo/config` and `@clawboo/process-lookup`; * the dashboard server (`server.js`) and the browser app (`ui/`), both built by `apps/web`; * the four MCP stdio bins (`clawboo-mcp-tasks`, `clawboo-mcp-memory`, `clawboo-mcp-tools`, `clawboo-mcp-teamchat`), built from `@clawboo/mcp`. The server and the bins inline every `@clawboo/*` package they import. A package change therefore reaches users only inside those bundles, in the next `clawboo` release. See [Release process](/internals/release-process) for how a release is cut. ## All packages The **Kind** column says what a package may run in. **pure** has no runtime dependencies and no `node:*` imports. **browser-safe** has dependencies but imports nothing from `node:*`. **server-only** imports `node:*` and must stay out of the browser app. **config** ships configuration files only. **Used by** names areas: the server is `apps/web/server`, the SPA is `apps/web/src`, and "(types)" means type-only imports. | Package | Kind | Purpose | Used by | Documented in | | - | - | - | - | - | | `@clawboo/agent-registry` | pure | The `AgentSource` interface, agent, team and session records, and the registry of sources | server; SPA and `control-client` (types) | [AgentSource and the agent registry](/internals/agent-source), [notes](#agent-registry) | | `@clawboo/board-core` | pure | Task statuses, legal moves, dispatch rules, Needs you reasons, run-cost sums | server, SPA, `db`, `team-orchestration` | [board-core](/reference/packages/board-core) | | `@clawboo/boo-avatar` | pure | The generated per-agent Boo SVG and the fixed Codex, Claude and Hermes Boos | `ui` | [notes](#boo-avatar-and-ui) | | `@clawboo/capability-registry` | pure | The capability record and source interface behind the Capabilities panel and the Org chart | server, SPA | [Capability and schedule sources](/internals/seams), [notes](#capability-registry) | | `@clawboo/compaction` | pure | Shrinks long tool output without dropping error lines | server, `db` | [notes](#compaction) | | `@clawboo/config` | server-only | State folders, `settings.json`, and the `CLAWBOO_BASE_PATH` rules | server, CLI, `gateway-proxy` | [notes](#config) | | `@clawboo/connector-catalog` | pure | The offline connector directory: curated entries, the community snapshot, brokered apps | server, SPA, root scripts | [Add a connector](/reference/packages/connector-catalog) | | `@clawboo/control-client` | browser-safe | The SPA's REST and SSE client (`apiFetch`, `apiUrl`, typed route wrappers) | SPA | [notes](#control-client) | | `@clawboo/db` | server-only | The SQLite schema and every repository: board, rooms, memory, tools, grants, budgets | server, `mcp`, `evals`; SPA (types) | [db](/reference/packages/db) | | `@clawboo/evals` | server-only | The eval harness that grades Clawboo's own orchestration guarantees | server | [Testing strategy](/internals/testing), [notes](#evals) | | `@clawboo/events` | browser-safe | Turns OpenClaw Gateway frames into typed intents (Bridge, Policy, Handler) | server, SPA, `adapter-openclaw` | [The event pipeline](/internals/event-pipeline), [notes](#events) | | `@clawboo/executor` | pure | The `RuntimeAdapter` interface, the `RuntimeEvent` union, the adapter contract suite | server, every adapter; `team-orchestration` and `evals` (types) | [Write a runtime adapter](/reference/packages/executor) | | `@clawboo/gateway-client` | browser-safe | The WebSocket client for the OpenClaw Gateway, plus exec-approval helpers | server, SPA; `events` and `adapter-openclaw` (types) | [gateway-client](/reference/packages/gateway-client) | | `@clawboo/gateway-proxy` | server-only | The dashboard's Gateway WebSocket proxy, the access gate, the Origin and Host guard | server | [gateway-client](/reference/packages/gateway-client) | | `@clawboo/governance` | browser-safe | Pure decision rules: verification verdicts, budget math, caps, breakers, connector grants | server, `db`, `team-orchestration` | [governance](/reference/packages/governance) | | `@clawboo/logger` | browser-safe | The pino logger, with secret masking on every record | server, `gateway-client`; `events` (types) | [notes](#logger) | | `@clawboo/mcp` | server-only | Clawboo's four MCP servers, the client for third-party connectors, attach signing | server; the four stdio bins | [mcp](/reference/packages/mcp) | | `@clawboo/model-catalog` | pure | The static provider and model list behind the model pickers | server, SPA | [notes](#model-catalog) | | `@clawboo/obs` | browser-safe | Orchestration event kinds, the error taxonomy, pure projections over the event log | server, `db`, `evals` | [Events and errors](/reference/events-and-errors), [notes](#obs) | | `@clawboo/pack-format` | browser-safe | The marketplace pack schema and its version ladder | server, root catalog scripts; SPA (types) | [Marketplace catalog](/reference/marketplace-catalog), [notes](#pack-format) | | `@clawboo/process-lookup` | server-only | Finds the process listening on a port, through `lsof` or `netstat` | server, CLI | [notes](#process-lookup) | | `@clawboo/protocol` | pure | Gateway message parsing, transcript types, agent-file definitions, the OpenClaw install pin | server, SPA, `events`, `adapter-openclaw` | [notes](#protocol) | | `@clawboo/scheduler` | browser-safe | Routine spec parsing, next-run math, the calendar projection, schedule sources | server, SPA | [scheduler](/reference/packages/scheduler) | | `@clawboo/team-orchestration` | browser-safe | The team chat engine: delegation, the board cascade, session keys | server, SPA | [team-orchestration](/reference/packages/team-orchestration) | | `@clawboo/tsconfig` | config | Shared TypeScript presets: `base.json`, `react.json`, `node.json` | every package and both apps (`extends`) | [notes](#tsconfig) | | `@clawboo/ui` | browser-safe | `BooAvatar`, `BooVariantAvatar`, and the `cn` and `cva` class helpers | SPA | [Design system](/internals/design-system), [notes](#boo-avatar-and-ui) | | `@clawboo/worktrees` | server-only | Per-task git worktrees, the task's working files, the handoff record | server | [worktrees](/reference/packages/worktrees) | | `@clawboo/adapter-claude-code` | browser-safe | The Claude Code runtime adapter | server | [Runtime adapters](/reference/packages/adapters) | | `@clawboo/adapter-codex` | browser-safe | The Codex runtime adapter | server | [Runtime adapters](/reference/packages/adapters) | | `@clawboo/adapter-hermes` | browser-safe | The Hermes runtime adapter | server | [Runtime adapters](/reference/packages/adapters) | | `@clawboo/adapter-mock` | browser-safe | A fault-injection runtime, offered only with `CLAWBOO_ENABLE_MOCK_RUNTIME=1` | server | [Runtime adapters](/reference/packages/adapters) | | `@clawboo/adapter-native` | browser-safe | The Clawboo Native runtime adapter and its `AgentConfig` schema | server | [Runtime adapters](/reference/packages/adapters) | | `@clawboo/adapter-openclaw` | browser-safe | The OpenClaw runtime adapter over the Gateway client | server | [Runtime adapters](/reference/packages/adapters) | ## Dependency layers Each package depends only on packages in a lower layer, counting runtime `dependencies` in its `package.json`. Turbo derives the build order from those same edges (`"dependsOn": ["^build"]` in `turbo.json`), so nobody keeps a build order by hand. `@clawboo/tsconfig` is a dev dependency of everything and sits outside the layers. | Layer | Package | Workspace dependencies | | - | - | - | | 0 | `agent-registry`, `board-core`, `boo-avatar`, `capability-registry`, `compaction`, `config`, `connector-catalog`, `executor`, `governance`, `logger`, `model-catalog`, `obs`, `pack-format`, `process-lookup`, `protocol`, `scheduler`, `worktrees` | none | | 1 | `adapter-claude-code`, `adapter-codex`, `adapter-hermes`, `adapter-mock`, `adapter-native` | `executor` | | 1 | `control-client` | `agent-registry` | | 1 | `db` | `board-core`, `compaction`, `governance`, `obs` | | 1 | `gateway-client` | `logger` | | 1 | `gateway-proxy` | `config` | | 1 | `team-orchestration` | `board-core`, `executor`, `governance` | | 1 | `ui` | `boo-avatar` | | 2 | `evals` | `db`, `executor`, `governance`, `obs` | | 2 | `events` | `gateway-client`, `logger`, `protocol` | | 2 | `mcp` | `db` | | 3 | `adapter-openclaw` | `events`, `executor`, `gateway-client`, `logger`, `protocol` | | app | `apps/cli` | `config`, `process-lookup` (dev dependencies, inlined at build time) | | app | `apps/web` | every package except `boo-avatar` and `tsconfig` | ## Rules * **Dependencies flow one way.** Apps depend on packages and packages depend on packages. A package never imports from `apps/`, and the Express server (`apps/web/server`) and the SPA (`apps/web/src`) never import each other. Code both halves need goes in a package, which is why `model-catalog` exists. ESLint enforces all three boundaries for static imports, and the server and SPA boundaries for dynamic `import()` too; see [Monorepo and build](/internals/monorepo-and-build). * **The browser imports `@clawboo/db` for types only.** Its entry point pulls in `better-sqlite3`, Drizzle and `node:*` modules. `apps/web/src/__tests__/browserBundlePurity.test.ts` fails on any value import, re-export or dynamic import of `@clawboo/db` under `apps/web/src`. Keep the other server-only packages out of the SPA as well; no test checks those, so check for it in review. * **`board-core` keeps zero dependencies.** The same test checks that its built files declare no bare import and that `state-machine.ts` imports nothing, so the transition table can ship to the browser. * **Re-export a workspace dependency by name.** tsup keeps workspace dependencies external, and an `export *` from an external module leaves the server's esbuild bundle unable to resolve the names. `@clawboo/db` re-exports the `board-core` symbols by name, and `@clawboo/ui` does the same for `boo-avatar`. A star re-export of a module inside the same package is fine. * **Test helpers get their own entry point.** `@clawboo/executor/contract` and `@clawboo/team-orchestration/contract` import vitest and keep it external, so they bind to the caller's vitest and the main entry points never pull in a test runner. * **Builds and type checks are separate.** Every package except `tsconfig` builds with tsup to CommonJS, ESM and `.d.ts` files. Its `typecheck` script runs `tsc --noEmit` against one of the `@clawboo/tsconfig` presets. ## Add a package **1. Create the folder** Make `packages/<name>/` (or `packages/adapters/<name>/` for a runtime adapter). The workspace globs in `pnpm-workspace.yaml` already include both locations. **2. Write the manifest and configs** Copy a sibling with the same kind. The `package.json` needs `"name": "@clawboo/<name>"`, `"private": true`, the `build`, `lint`, `typecheck` and `test` scripts, and `"@clawboo/tsconfig": "workspace:*"` as a dev dependency. Add a `tsconfig.json` that extends the matching preset and a `tsup.config.ts` with `format: ['cjs', 'esm']` and `dts: true`. **3. Wire the consumers** Add `"@clawboo/<name>": "workspace:*"` to the `dependencies` of every package or app that imports it (usually `apps/web/package.json`), then run `pnpm install`. **4. List it here** Add a row to the table above and to the layer table, and check it with `pnpm build` and `pnpm test`. You should see the new package build before the packages that depend on it. ## Notes on the smaller packages These packages have no page of their own. Each note names the main entry points and what to know before you change one. ### agent-registry The `AgentSource` interface, the agent, team and session record types, and `AgentRegistry`, which holds one source per runtime. A source answers who exists; how an agent runs is the `RuntimeAdapter`'s job in `@clawboo/executor`, and the two never mix. The server registers five sources: `OpenClawAgentSource`, `ClawbooNativeAgentSource`, and a `RuntimeAgentSource` each for Claude Code, Codex and Hermes. Every source reads its records from SQLite. Only the OpenClaw source needs a live upstream: creating an OpenClaw agent, its agent files and its session list go through the Gateway and fail while it is disconnected, while the other sources write SQLite directly. Details live on [AgentSource and the agent registry](/internals/agent-source). ### boo-avatar and ui `@clawboo/boo-avatar` draws the generated Boo: `generateBooAvatar(params)` returns the same SVG string for the same parameters, and `resolveBooTint(seed)` picks a tint from `TINTS`, whose first entry is reserved for Boo Zero. It also holds the fixed Boo drawings for three runtimes: `variantIdForRuntime` maps `codex`, `claude-code` and `hermes` to their drawing and every other runtime to `null` (keep the generated Boo). The drawings sit behind `loadBooVariant` and their own entry points (`./variants/codex`, `./variants/claude`, `./variants/hermes-light`, `./variants/hermes-dark`), so they stay out of the main bundle. `scopeSvgIds` gives each inlined copy its own gradient and clip ids. `@clawboo/ui` wraps both as React components: `BooAvatar` (ids scoped per mounted copy, memoized on all props) and `BooVariantAvatar` (loads the drawing on demand, so its first render can be empty for a moment). In the app, `AgentBooAvatar` picks between them by runtime. The package also exports the `cn` and `cva` class helpers and a `tokens` constant that the app does not read; the live theme is the CSS custom properties described in [Design system](/internals/design-system). `@clawboo/ui` is the only importer of `@clawboo/boo-avatar`. ### capability-registry The `CapabilityRecord` shape, the `CapabilitySource` interface, and `CapabilityMultiplexer`, which merges every source into the one list the Capabilities panel and the Org chart read. There are six sources: `native`, `hermes`, `claude-code`, `codex` and `openclaw` (what each runtime's agents have), plus `connector` (the connectors Clawboo itself connects). Capability ids are `<sourceId>:<key>`, and a write goes to the source that owns the id. Records also carry connector grant, health and risk fields, which the Org chart draws as grant edges and badges. A new runtime needs its id in `CapabilitySourceId` and `SOURCE_IDS` plus a source under `apps/web/server/lib/capabilitySource/`; the pattern is described on [Capability and schedule sources](/internals/seams). ### compaction `compactToolOutput(toolName, output)` shrinks long tool output before it re-enters a prompt, and `compactToolResultMarkdown` applies it to every tool-result block inside a transcript. It makes two promises: small outputs and low-savings results come back unchanged, and if a rule would drop an error line, the original comes back instead. Built-in rules handle git status, test output and HTML, with a dedupe-and-elide fallback; pass extra rules in `opts.rules` and they run first. All callers are on the server: the tool broker in `@clawboo/db`, the task runner, the team orchestrator and the Clawboo Native conversation loop. ### config `resolveClawbooDir()` returns Clawboo's own state folder (`CLAWBOO_HOME`, else `~/.clawboo`), and `resolveStateDir()` returns OpenClaw's (`OPENCLAW_STATE_DIR`, else the first existing folder among `~/.openclaw`, `~/.clawdbot` and `~/.moltbot`, falling back to `~/.openclaw`). `loadSettings()` and `saveSettings()` read and write `settings.json` in Clawboo's folder. `writePrivateFileSync()` writes a file owner-only (`0600`), narrowing an existing file first, and `mkdirPrivateSync()` creates missing folders `0700`; `saveSettings()` and the server's OpenClaw `.env` and `auth-profiles.json` writers use them. `resolveBasePath()` validates `CLAWBOO_BASE_PATH`: it accepts `/clawboo`-style paths and rejects whitespace, `%`, `?`, `#`, backslashes, empty or dot segments and a first segment of `api`, and the server exits on an invalid value. The CLI uses `resolveBasePathOrRoot()`, which falls back to the root. See [Configuration](/reference/configuration). ### control-client The browser's API client. `apiFetch(path)` and `apiUrl(path)` add the base path, and typed modules wrap the agent, runtime, provider and onboarding routes. At startup the SPA calls `setApiBase` with the path prefix the server injects, so a dashboard served under `CLAWBOO_BASE_PATH` keeps working. ESLint rejects a root-absolute `/api/...` `fetch` or `EventSource` in `apps/web/src`, so always go through these helpers. `setRequestHeaderProvider` exists for a client on another origin; the dashboard never calls it. ### evals The harness that checks Clawboo's own orchestration guarantees against throwaway boards: regression tasks (the atomic claim is never retried, the dependency gate, report-up, the state machine) and capability tasks (cross-runtime resume, verification catching a bug, delegation fan-out). Each task runs K trials in a fresh environment. `passAt1` is the share of trials that passed, and `passPowK` is that share raised to the k-th power, the estimated chance that k trials in a row all pass. The smoke subset runs in `pnpm test`, from the dashboard through `POST /api/eval/smoke`, and in the manual `evals.yml` workflow. Tasks use code graders only, so no suite needs a provider key. Add a task to `packages/evals/src/tasks/regression.ts` or `capability.ts`. See [Testing strategy](/internals/testing). ### events The browser runs every OpenClaw Gateway frame through three steps: `classifyEvent` sorts it (Bridge), `derivePolicy` decides what should change (Policy), and `createEventHandler` applies those intents to the SPA's stores (Handler). The server reuses the parsers, `parseSessionMessagePayload` in particular, to show activity from OpenClaw sessions Clawboo did not start. The full walk-through is on [The event pipeline](/internals/event-pipeline). ### logger `createLogger(module)` returns a child of one pino logger. The level comes from `LOG_LEVEL` (default `info`), and output is pretty-printed unless `NODE_ENV=production`. Every record goes through `redactObject`, which masks credential-looking keys and values with `••••` while token counts and costs survive. The server's API routes apply the same redactor to event payloads and audit rows before they send them. Stored data is scrubbed separately by `@clawboo/db`, which writes `[REDACTED]`. The package reads `process.env` behind a guard, so it is safe in the browser bundle. ### model-catalog `MODEL_GROUPS` is the static list of OpenClaw providers and models behind the model pickers, with ids in OpenClaw's `provider/model-id` routing shape. Clawboo Native keeps its own catalog with provider-native ids (`apps/web/src/lib/nativeModelCatalog.ts`). `findModelLabel`, `findProviderForModel` and `formatProviderName` read the list, and `providerSlug` is the one comparison key for provider names (so `Hugging Face` matches `huggingface`). The list does not decide which providers are set up: the server's `/api/system/models` route reads the live list from the OpenClaw CLI, uses this one for casing, and falls back to it for a known provider the CLI does not list (or for every provider when the CLI is unavailable). It is a package so that route and the SPA can share it without crossing the server and SPA boundary; the SPA imports it through `apps/web/src/lib/modelCatalog.ts`. ### obs Pure observability building blocks: the orchestration event kinds and their data schemas, the runtime error taxonomy (`classifyError`), projections that fold the event log into a task graph and fleet health (`projectGraph`, `projectFleetHealth`), and the structured-output judge that the verification critic and the eval model grader share. It never imports OpenTelemetry; the server loads that only when an OTLP endpoint is set. To add an event kind, add it to `ORCHESTRATION_EVENT_KINDS` with a data schema in `packages/obs/src/events/schema.ts` and document it on [Events and errors](/reference/events-and-errors). An unknown kind reaching `appendEvent` in `@clawboo/db` is stored as `error`. ### pack-format The versioned schema for marketplace packs. The published contract is the JSON Schema in `catalog/schema/`; this package is its TypeScript mirror and stays private. `parseAgentPack` validates a pack and runs it up the version ladder, bounded by `MIN_SUPPORTED_SCHEMA_VERSION` and `CURRENT_SCHEMA_VERSION`. The server's catalog index and the `scripts/catalog/*` tools use it (the scripts import its source by relative path), and the SPA imports only its types. The format is documented in [Marketplace catalog](/reference/marketplace-catalog). ### process-lookup `findListenerPid(port)` returns the process listening on a TCP port, or `null`, and never throws. The CLI's `stop` and `restart` commands and the server's managed-Gateway control both pass its answer to `process.kill`, so a wrong answer kills an innocent process. Three details keep it right: * On macOS and Linux it runs `lsof -nP -iTCP:<port> -sTCP:LISTEN -t`. Without `-sTCP:LISTEN`, a browser tab connected to the dashboard also matches, and `-n -P` keep a slow DNS resolver from blocking the call. * On Windows it parses `netstat -ano` with a raised output buffer, because a busy host overflows Node's default. * `netstat` translates its state column on non-English Windows, so when no `LISTENING` row matches it accepts a row whose local address is the port and whose foreign address is all zeros. The parsers (`parseLsofPids`, `parseNetstatPid`) are pure; keep their recorded-output tests when you change them. ### protocol Pure helpers for OpenClaw Gateway messages (`extractText`, `extractThinking`, `extractToolCalls`, `parseMessage`), the transcript entry types the chat views render, and the agent-file definitions. `AGENT_FILE_RUNTIME_SUPPORT` decides which file tabs the agent editor shows on each runtime (`agentFilesForRuntime`), and a runtime missing from it shows all seven files, so add a row when you add a runtime. The user-facing table is on [Create and edit agents](/using/agents). The package also pins the OpenClaw version Clawboo installs (`OPENCLAW_INSTALL_SPEC`, with the Node versions it accepts) and holds the web-browsing guidance appended to a new OpenClaw agent's `AGENTS.md`. ### tsconfig Three TypeScript presets. `base.json` is the strict baseline, and every package not named below extends it. `react.json` adds JSX and the DOM libraries, for `apps/web` and `packages/ui`. `node.json` adds Node types and Node-style resolution, for `apps/cli`, `config`, `db`, `gateway-proxy`, `logger`, `mcp`, `process-lookup` and `worktrees`. The presets drive type checking only, and tsup decides the build output. `board-core` and `pack-format` also pin `types: []` in their main `tsconfig.json`, so a Node global cannot slip into browser-safe source, and type-check their tests separately with a second `tsconfig.test.json` that drops the pin. See [Monorepo and build](/internals/monorepo-and-build). ## Related * [Monorepo and build](/internals/monorepo-and-build): the workspace layout, the lint boundaries and the build pipeline. * [Write a runtime adapter](/reference/packages/executor): the extension point for a new runtime. * [Runtime adapters](/reference/packages/adapters): the six adapters compared. * [@clawboo/db](/reference/packages/db): the schema owner and every repository. * [Testing strategy](/internals/testing): the test projects, the e2e suite and the standing guards. # @clawboo/mcp Source: https://docs.claw.boo/reference/packages/mcp Clawboo MCP servers and their transports, the client for third-party connectors with its OAuth flow, attach-URL signing, and the stdio bins. `@clawboo/mcp` has two halves. The server half gives agents Clawboo's board, memory, tools and peer channel as four MCP servers. The client half is how Clawboo itself connects to the third-party MCP servers you add as connectors. Between them sits attach signing, which stops an agent from claiming an identity Clawboo did not give it. Use this page when you add or change an MCP tool, change how runtimes attach, or work on connectors. Each tool's inputs are listed on [MCP tools](/reference/mcp-tools), and attaching an outside agent is covered in [MCP servers](/operating/mcp-servers). This is the only package that depends on `@modelcontextprotocol/sdk`, which is why the connector client lives here too. ## The four servers | Server | Built by | Tools | | - | - | - | | Tasks (`clawboo-tasks`) | `createTasksServer(db, opts)` | `list_tasks`, `get_task`, `create_task`, `create_subtask`, `claim_task`, `assign_task`, `release_task`, `update_task_status`, `block_task`, `unblock_task`, `add_comment`, `link_task` | | Memory (`clawboo-memory`) | `createMemoryServer(db, embed, opts)` | `memory_save`, `memory_search`, `memory_browse`, `memory_feedback` | | Tools (`clawboo-tools`) | `createToolsServer(db, opts)` | the built-ins (`echo`, `note`, `web_search`, `delete_path`), plus the tools of each connected connector and the app toolkits an agent has been granted | | TeamChat (`clawboo-teamchat`) | `createTeamChatServer(db, opts)` | `team_chat_post`, `team_chat_subscribe`, and `team_delegate` when the binding grants delegation | Each server is a thin layer over `@clawboo/db`: the handlers call its repository functions. A few behaviors matter to anyone changing them: * **Tasks.** `readOnly` serves only `list_tasks` and `get_task`; Clawboo Native uses it for an agent whose Tasks access is set to read. A bound team scope forces `list_tasks` to that team and makes `get_task` refuse another team's task. A lost claim, a dependency cycle in `link_task` and a refused subtask (too many children, too deep) come back as tool errors the model should not retry. * **Tools.** Every call runs through the `@clawboo/db` broker, so a connector tool is checked against the agent's grants. A connector tool is listed only to an agent whose grant would allow it. * **TeamChat.** `team_delegate` only signals: the team engine sees the tool call and creates the board task. * **Mailbox delivery.** For a bound agent, its undelivered mailbox rows ride along on the next Tasks or TeamChat tool response as a second content block. ## Transports * **HTTP.** `createStreamableHttpHandlers` serves each server inside the dashboard server at `/api/mcp/<server>`. Clawboo's own Claude Code, Codex and Hermes runs and the OpenClaw Gateway connect here. * **In-process.** `connectInMemoryClient` connects Clawboo Native's MCP bridge to the servers without a network hop. `probeServer` checks that a server answers, for the boot probe and the MCP supervisor. * **stdio.** `runStdioServer` backs the four bins `clawboo-mcp-tasks`, `clawboo-mcp-memory`, `clawboo-mcp-tools` and `clawboo-mcp-teamchat`, for runtimes you attach yourself. The bins are built from `packages/mcp/src/bin/*.ts` by `apps/web/tsup.mcp-bins.config.ts`, with every `@clawboo/*` package inlined, and `scripts/assemble-cli.sh` copies them into the published `clawboo` package. Each bin opens `createDb(defaultDbPath())`: the file named by `CLAWBOO_DB_PATH`, else `~/.openclaw/clawboo/clawboo.db`. That is a different file from the server's `~/.clawboo/clawboo.db`, on purpose, so set `CLAWBOO_DB_PATH` to the server's file or use the snippet from `GET /api/mcp/config`, which embeds it. The Memory bin re-resolves its embedding provider once a minute and falls back to keyword search when there is none. ## Attach URLs and signing `mcpHttpUrl(baseUrl, server, scope)` writes the URL a runtime attaches with, and `buildAttachConfig` wraps it in a runtime's own config shape (a `claude mcp add` line, a Codex `config.toml` entry, an `openclaw.json` entry). A URL Clawboo mints with the install's attach secret carries up to three signatures. An unscoped URL, such as the one registered with a local OpenClaw Gateway, carries the token alone. * **`attachToken`**, on every such URL: proof that the request comes from a runtime this server started. The access gate admits a request without the dashboard cookie only when it carries a valid token, targets one of the four server mounts, arrives on the loopback port directly and was not forwarded by a proxy. `GET /api/mcp/config` mints signed URLs, so it always needs the cookie when the gate is on. * **`scopeSig`**, when the URL claims a team, agent or tenant: an HMAC over those and the delegation privilege. * **`provSig`**, on Memory URLs that carry provenance stamps (runtime, task, session): it signs the stamps recorded with saves and feedback. `signAttachAccess`, `signAttachScope` and `signAttachProvenance` mint these from the per-install secret, and their `verify` partners check them in constant time. The secret never leaves the server. ## Unverified callers A scope whose signature is missing or wrong counts as no scope at all. Over HTTP that makes the caller unverified, and each server narrows what it serves: | Server | What an unverified HTTP caller gets | | - | - | | Tasks | no `claim_task` or `assign_task`, and `add_comment` drops its author fields; other edits still work | | Memory | global memory only for reads and saves, and no provenance recorded | | TeamChat | no tools at all | | Tools | no agent or team identity, so no grant matches and connector tools stay hidden | A stdio caller with no scope is the operator running a bin and is served without these limits. OpenClaw agents are always unverified: the Gateway registers Memory, Tasks and Tools once for all its agents, with no per-run identity, and gets no TeamChat server. The server-side flags are `unverifiedCaller` on the Tasks, Memory and TeamChat options. ## The connector client When you connect a connector, Clawboo is the MCP client. The server's connector supervisor and OAuth flow (`apps/web/server/lib/connectors/`) call into this half; the connector definitions themselves come from [@clawboo/connector-catalog](/reference/packages/connector-catalog). * **Sessions.** `connectStdioConnector(spec)` spawns a local server and `connectHttpConnector(spec)` dials a remote one. Both return a `ConnectorSession` that lists tools and calls them, and both throw `ConnectorHandshakeError` when the server never completes the MCP handshake. * **Time budgets.** The handshake, each tool-list page and each tool call have separate timeouts (`DEFAULT_HANDSHAKE_TIMEOUT_MS`, `DEFAULT_LIST_TIMEOUT_MS`, `DEFAULT_CALL_TIMEOUT_MS`). The handshake budget is the generous one, because a cold `npx` install happens inside it. Discovery stops at `MAX_CONNECTOR_TOOLS` tools. * **Results.** `flattenContent` turns an MCP result into text for the model, and images ride along separately. * **Environment.** `connectorChildEnv` builds a stdio connector's environment from an empty set, the few variables a process needs to start (`CONNECTOR_ENV_ALLOWLIST`) and the credentials you gave that connector, so it never inherits your provider keys. This closes the environment only. It is not a sandbox: the connector still runs as your user and can read your files. Remote connectors sign in with OAuth 2.1, discovered from the server rather than configured per provider: **1. Discover** An unauthenticated request returns `401` with a `WWW-Authenticate` header. `resourceMetadataUrl` reads it, `discoverResourceMetadata` fetches the protected-resource metadata (RFC 9728), and `discoverAuthServer` fetches the authorization server's metadata (RFC 8414). **2. Register** `registerClient` registers this install as a client through dynamic client registration (RFC 7591), with the loopback redirect it is about to listen on. Clawboo ships no client secret, so an authorization server without a registration endpoint cannot be used; the catalog marks such providers with `needsPreregisteredApp`. **3. Authorize and exchange** `createPkce` makes an S256 challenge, `buildAuthorizeUrl` builds the sign-in URL, and `exchangeCode` and `refreshToken` talk to the token endpoint. Every endpoint must be `https` unless it is on this machine, and a host that resolves to a private or link-local address is refused, so a connector cannot point Clawboo at your local network. `isLoopbackUrl` lets the flow tell an authorization server you run yourself from one a remote server named. ## Add or change a tool **1. Add the tool definition** Each server builds its tools as plain definitions (`name`, `description`, a zod `inputSchema`, a `handler`) and passes them to `buildServer` in `src/shared.ts`. `buildServer` validates arguments with zod and converts the schema to JSON Schema itself. The servers use the SDK's low-level `Server` API, because the high-level per-tool type inference exhausts the TypeScript compiler's memory once a server has about a dozen tools. **2. Keep data access in the database package** Put the query or write in a `@clawboo/db` repository function and call it from the handler, so the REST routes and the other servers can reuse it. **3. Decide who may call it** If an argument names an agent, a team or a room, take it from the bound scope instead of trusting the model, and decide what an unverified caller gets. Tasks lists its identity tools in `IDENTITY_TOOL_NAMES` for this reason. Return a refusal the model must not retry as an error result (`textResult(message, true)`). **4. Test and document it** Run `pnpm --filter @clawboo/mcp test`; the suite includes the shared server contract and the unverified-caller cases. Then add the tool to [MCP tools](/reference/mcp-tools). You should see it in a client's `tools/list`, for example from the stdio bin with `CLAWBOO_DB_PATH` set. ## Related * [MCP tools](/reference/mcp-tools): every tool's inputs, outputs and limits. * [MCP servers](/operating/mcp-servers): attach an outside agent, and the attach token in practice. * [@clawboo/db](/reference/packages/db): the repositories behind every handler. * [Add a connector](/reference/packages/connector-catalog): the definitions the connector client launches. * [@clawboo/gateway-client](/reference/packages/gateway-client): the access gate that checks the attach token. # @clawboo/scheduler Source: https://docs.claw.boo/reference/packages/scheduler Routine spec parsing and next-run math, the calendar projection, the task template, and the schedule sources behind the Routines view. `@clawboo/scheduler` holds the pure scheduling rules behind Routines: how a schedule is written, when it fires next, how its runs land on the board calendar, what a fire creates, and the schedule-source layer that merges Clawboo's routines with an OpenClaw Gateway's own cron jobs. It is browser-safe, and it is the only place `croner` is imported, so the Routines view does next-run math in the browser with the same code the server fires on. Use this page when you change how routines are scheduled or listed. The engine that fires them is on [How routines run](/concepts/scheduling), the REST surface on [Schedules API](/reference/rest-api/schedules), and the how-to on [Run work on a schedule with routines](/using/routines). ## Two spec forms | Where | Form | Read with | | - | - | - | | A Clawboo routine | a cron expression (five or six fields) or a one-shot `once@<ISO-8601>` | `parseCronSpec`, `probeCronSpec`, `nextOccurrence`, `isOnceSpec` | | A `ScheduleRecord` from any source | the routine forms, plus the Gateway's `every:<ms>` (optionally `@anchor:<ms>`), `at:<iso>`, and a cron expression with an `@tz:<zone>` suffix | `encodeCronSpec`, `decodeCronSpec` | `nextOccurrence(spec, fromMs)` returns the next run strictly after `fromMs`, or `null` when the spec will never fire again (a spent one-shot). `probeCronSpec` validates a spec when it is registered, and both throw `InvalidCronSpecError` for a malformed one. Every other part of the system (the routines ledger, the ticker, the REST layer) stores precomputed epoch-millisecond times, so a change of cron library touches one file, `src/occurrence.ts`. ## The calendar projection `occurrencesInWindows(spec, bounds, { after, first, countCap, timesCap })` spreads a spec's future runs over consecutive windows, where window `i` runs from `bounds[i]` up to `bounds[i + 1]`. The board calendar sends the viewer's local midnights as the bounds, which keeps daylight-saving days right, and `GET /api/schedules/occurrences` serves the result. Each window reports a capped `count`, a `more` flag when the cap was hit, and the first few run `times`. Work is bounded by the windows and the cap, not by how often the spec fires. Bounds that are not at least two finite, strictly ascending numbers throw `RangeError`. ## What a fire creates `TaskTemplate` (validated by `taskTemplateSchema`, parsed with `parseTaskTemplate`, which returns `null` for an invalid one) is what each fire materializes: * `target` is `team` (the instructions go to the team's chat, where its lead picks them up) or `agent` (a board task run through the task runner). A template with no target is an agent routine (`routineTargetOf`). * `title`, `description`, `kind` (default `code`), `priority` (default `0`), `repoPath` and `model` describe the task. With a `repoPath`, a `code` fire gets its own worktree; without one, the wake bridge runs the fire as a `research` task with no checkout (`runKindFor` in `apps/web/server/lib/routines/wakeBridge.ts`). * `maxNodeCents` sets a per-run cost ceiling, and `teamTaskId` binds the routine to an existing board task instead of creating one per fire. A bound routine must be a one-shot (`BoundRecurringScheduleError`). ## Schedule sources The Routines view lists two kinds of schedule through one interface, `ScheduleSource`, each normalized to a `ScheduleRecord`: | Source id | What it lists | Manageability | | - | - | - | | `clawboo-routine` | Clawboo's own routines, which put work on the team's board | `managed`: Clawboo creates, edits, pauses and runs them | | `openclaw-gateway-cron` | the OpenClaw Gateway's own cron jobs for its agents | `external-write`: Clawboo edits them through the Gateway, which owns them | `ScheduleMultiplexer` merges every source's `read()`. A source that fails reports a degraded status instead of failing the whole list. Writes go to the source that owns the id (ids are `<source>:<raw id>`, built by `makeScheduleId` and split by `parseScheduleId`), after three checks in order: an unknown source throws `UnknownScheduleError`, an `observe-only` source throws `UnsupportedScheduleWriteError`, and creating a `team-task` schedule on a `runtime-own-life` source (the Gateway's) throws `TeamTaskDomainViolationError`. The pattern it shares with the capability sources is described on [Capability and schedule sources](/internals/seams). Every error class carries a `code` for callers to branch on. The REST layer maps them to statuses; note that a malformed spec comes back as a `400` whose body code is `invalid_body`, not `invalid_cron_spec`. The full mapping is on [Schedules API](/reference/rest-api/schedules). ## Who uses it Only `apps/web`. On the server: the routines ticker and wake bridge, the OpenClaw routine dispatcher, the two schedule sources, and the `/api/schedules` routes. In the SPA: the Routines view, which imports the spec helpers as well as the types, and the board calendar. `@clawboo/db` does not depend on this package; its routines ledger stores precomputed times. ## Test it Run `pnpm --filter @clawboo/scheduler test`. The spec, window, record, template and multiplexer cases each have their own file under `src/__tests__/`; pass explicit `fromMs` and `after` values in a new case so it does not depend on the current time. ## Related * [How routines run](/concepts/scheduling): the ledger, the ticker and the one-firing-owner rule. * [Run work on a schedule with routines](/using/routines): creating and running routines in the dashboard. * [Schedules API](/reference/rest-api/schedules): the routes and their error codes. * [Capability and schedule sources](/internals/seams): the source-plus-multiplexer pattern. # @clawboo/team-orchestration Source: https://docs.claw.boo/reference/packages/team-orchestration The team chat engine: how delegation signals become board tasks, the dependencies a host provides, session keys, turn origins, and the cascade contract. `@clawboo/team-orchestration` is the engine behind team chat. It watches each agent's run events, turns delegation signals into board tasks, delivers each task to the agent it names, and reports results back to whoever delegated them. It is pure and browser-safe: the board, delivery, narration and every other side effect arrive as injected functions, so the same engine runs on the server against SQLite and in tests against an in-memory board. Use this page when you change how a team delegates, reports back or recovers. The model is on [How work is delegated](/concepts/delegation-and-orchestration), and how a message reaches the engine is on [How a team works](/intro/how-it-works). ## How the server runs it `apps/web/server/lib/teamChat/teamOrchestrator.ts` builds one long-lived engine per team chat room: the team's own chat is its default room, and each extra room gets its own engine. Each engine is created with `createBoardOrchestrator(deps)`, wired to the database through `serverBoardClient.ts` and to the runtimes through `serverDeliver.ts`. The SPA uses only the helpers (session keys, the `[Task Update]` builder, control-token checks) and talks to the server over REST and server-sent events. | Dependency | Required | What the host provides | | - | - | - | | `board` | yes | a `BoardClient` over the durable board | | `deliver` | yes | starts an agent's turn with a message and a turn origin | | `teamId`, `known`, `leaderAgentId` | yes | the team, the room's roster and its lead | | `sessionKeyForAgent`, `agentIdForSession` | yes | the session-key mapping for this room | | `stopGen` | yes | the host's stop generation, so work delivered before a Stop starts nothing new | | `isSessionBusy`, `isSessionOccupied` | no | whether an agent is mid-run, so a message waits instead of interrupting | | `onBoardChange`, `narrate` | no | push board changes and engine notes (`reflect`, `alert`) to the dashboard | | `compact` | no | shrink tool output before it is quoted back | | `caps`, `onCapHit` | no | the per-turn fan-out limit and a callback when it trips | | `isRiskyDelegation`, `requestDelegationApproval` | no | make a risky delegation wait for a person's approval | | `abortSession` | no | stop a run when a person moves its card | > [!WARNING] > > `deliver` must not claim or complete the task. The engine owns the board lifecycle: it creates the task, claims it, > opens the execution row and then calls `deliver`, and it marks the task done itself. A `deliver` that claimed again > would lose to its own claim, and one that completed the task would complete it twice. ## The rules it enforces * **Typed signals only.** `extractSignals` reads a `sessions_send` or `delegate` tool call (including TeamChat's `team_delegate`), or a `<delegate to="@Name">` or `<plan>` block in a finished turn. It never infers delegation from prose. The tag parsers tolerate a missing opening `<`, and stripping removes the whole tag from what the chat shows. * **One task per session.** A session works one task at a time. A message for a busy session waits in the nudge queue (`createNudgeQueue`) and goes out at its next turn boundary. A queued message whose run never starts frees the session for the next one. * **A refused delivery says why.** When `deliver` rejects, the engine fails the task at once instead of waiting for the watchdog. A host that decided not to start the run, such as the web server when a hard-cap budget is paused, rejects with a `DeliveryRefusedError`, and its message becomes the task's failure comment and what the delegator is told. Any other rejection is reported as a message the agent did not receive. * **Bounded trees.** Delegation depth is capped by `MAX_SPAWN_DEPTH`, the same constant the board and the task runner use, and fan-out per turn by `caps.maxFanout`. A lost claim is never retried. After `MAX_DELEGATION_FAILURES` failures in a row of the same task by the same agent, the engine refuses to delegate it to that agent again and tells the delegator to handle it another way. * **Silence fails a delegation.** `sweepStaleSessions` fails a delegated run that sends no event for `DELEGATION_IDLE_TIMEOUT_MS`, with longer allowances while a tool call is open or a run waits to start, and tells the delegator. The values are on [Limits and defaults](/reference/limits-and-defaults). * **Results are batched.** Finished tasks are gathered for `REFLECT_WINDOW_MS` and delivered to the delegator as one `[Task Update]` (`buildTaskUpdateMessage`), which also names work still outstanding. * **A person's move is final.** `personMovedTask` stops the run working on the card, closes its execution as cancelled, and tells the delegator when the person marked it done or cancelled it. A run that ends after that changes nothing. `markStopped` makes a Stop durable at once, so the ready-pump never re-fires stopped work. * **A person can ask again.** `dispatchTask` runs one `todo` task now because a person asked, skipping the automatic fire policy. A person-assigned task reports on its card, not to the team's lead. ## Session keys A team session key is `agent:<agentId>:team:<teamId>`, with `:room:<roomId>` appended for a room other than the team's own chat. `buildTeamSessionKey(agentId, teamId, roomId?)` writes one, and `parseTeamSessionKey`, `agentIdFromSessionKey`, `teamIdFromSessionKey` and `roomIdFromSessionKey` read one back; `isTeamSessionKey` is true with or without a room. A person's 1:1 chat with a Clawboo Native agent uses `nativeChatSessionKey(agentId)`, which is `agent:<agentId>:native`; the web server keys a Claude Code, Codex or Hermes agent's 1:1 chat `agent:<agentId>:<runtime>`. Build and parse keys only through these helpers, so the room suffix stays readable. ## Turn origins Whoever asks for a turn stamps it with a `TurnOrigin`, passed as `deliver`'s fourth argument, because the engine's own view of a session goes stale as soon as a task completes. | Origin | Meaning | Reply shown in team chat | | - | - | - | | `human` | a person typed into team chat | yes | | `schedule` | a routine posted into team chat for the person | yes | | `system` | Clawboo itself, such as a batched `[Task Update]` | yes, unless it is a follow-up on a closed task | | `delegation` | a teammate delegated this task (`fromAgentId` is who to report to) | no | | `assignment` | a person assigned this board task to the agent | no, the result goes on the card | `classifyTurn` turns an origin into the framing the prompt needs (worker, leader, user-facing), and `isChatVisible` answers the last column. Never derive visibility from the framing: a person's @mention to a busy worker is framed as a worker turn but still gets an answer in the chat. ## Other helpers * `buildTurnEnvelope({ ambient, addressed })` frames waiting context in two parts: items addressed to this agent, first, and ambient evidence that carries no authority to change the task. Membership comes from where an item came from, never from its text. * `shouldDropAssistantTurn` keeps control tokens (`RESUME_ACK_TOKEN`, `SKIP_ACK_TOKEN`) and short refusals out of the transcript. ## Test it `@clawboo/team-orchestration/contract` exports `runCascadeContract(harness)`, the shared suite of cascade scenarios: stop and release, the idle watchdog and its allowances, one task per session, reflect batching, the fan-out cap, plan steps cancelled after a failed dependency, the loop breakers, claims never retried, the ready-pump's fire policy, follow-ups on closed tasks, and work delivered before a Stop. It runs twice: against the package's in-memory board (`pnpm --filter @clawboo/team-orchestration test`), and against the real server board client over SQLite in `apps/web/server/lib/teamChat/__tests__/boardOrchestration.contract.test.ts`, so the fake and the real board cannot drift apart. Add a scenario to `src/contract.ts` when you add a rule. ## Related * [How work is delegated](/concepts/delegation-and-orchestration): how a team delegates and reports back. * [@clawboo/board-core](/reference/packages/board-core): the task rules the engine dispatches by. * [Board internals](/internals/board-internals): claims, heartbeats and reconciliation underneath the engine. * [Work with your team in chat](/using/team-chat): rooms, General and the chat a person sees. # @clawboo/worktrees Source: https://docs.claw.boo/reference/packages/worktrees Per-task git worktrees for the task runner: the lifecycle, the task's working files and handoff record, path safety, cleanup, and the keyed mutex. `@clawboo/worktrees` gives a file-changing task its own git worktree and branch, writes the working files any runtime can resume the task from, and records a structured handoff when a run ends. It is server-only, because it runs `git` and writes files. The board decides what runs; this package is only the git and file mechanism, and the database bookkeeping (the task's `workspaces` row) lives in `apps/web/server/lib/worktrees.ts`. Use this page when you change how task runs are isolated, paused, resumed or cleaned up. The model behind it is on [Worktrees and handoff](/concepts/worktrees-and-handoff). A worktree keeps parallel teammates' file changes apart, but it is not a sandbox: see [What agents can do](/concepts/permissions). ## When a task gets a worktree `isolationForTask(kind)` returns `none` for `research` and `review` tasks and `worktree` for `code` and any unknown kind; it never returns `container`. Worktrees are made by the task runner (a run of `POST /api/runtimes/<id>/run`, or an agent routine that names a repository) and by `POST /api/board/<taskId>/workspace`; team chat never makes one. The task runner refuses a file-changing task with no repository path (`workspace_unavailable`) rather than run it without isolation; see [The executor runner](/internals/executor-runner). The package's default root is `<repo>/.clawboo/worktrees`, but the server always passes its own: task worktrees live at `<clawboo home>/worktrees/<hash of the repo path>/<task id>` and review checkouts under `<clawboo home>/reviews/`, so nothing is created inside your repository. Each task gets the branch `clawboo/task-<task id>`. ## The lifecycle | Function | What it does | | - | - | | `provisionWorktree` | Branches from a commit (never the dirty working tree), writes the working files and commits them as the baseline. It clears leftovers of an earlier attempt, reuses a branch that already exists, checks that git registered the folder, and retries once. | | `resumeWorktree` | Re-attaches a paused task's branch at its tip, keeping earlier commits. Throws when the branch is gone. | | `loadWorktree` | Rebuilds the handle for a task that already has a worktree. | | `pauseWorktree` | Commits any uncommitted work, removes the worktree folder and keeps the branch. | | `commitWorktreeWork` | Commits without removing anything; the critic's checkpoint before a review. | | `completeWorktree` | Diffs against the baseline, ignoring the working files. With no change it removes the worktree and the branch; otherwise the branch stays for a person to merge. | | `gcWorktrees` | Reaps worktrees by age and by count (defaults: 72 hours, 25 worktrees), commits before it drops one, skips active tasks, and keeps going past a failure. It touches only the worktree root. | | `provisionReviewWorktree` | A detached checkout at one commit for the read-only reviewer, so there is no branch to push. It replaces any earlier checkout at the same path. | Clawboo never merges a task branch; finished work waits on `clawboo/task-<task id>`. ## The working files `SOR_FILES` names the system-of-record files, which any runtime (or a person) reads to pick a task up cold: | File | Holds | | - | - | | `TASK.md` | what and why, acceptance criteria, known gotchas | | `task-progress.md` | the running log of what was done | | `DECISIONS.json` | a structured decision log (`clawboo/decisions@1`) | | `init.sh` | `INSTALL_CMD`, `VERIFY_CMD` and `START_CMD`, as an executable script | | `VERIFICATION.md` | the evidence the verification gate reads and appends to | | `AGENT_HANDOFF.json` | written at the end of a run by `writeHandoff`, validated by `agentHandoffSchema` | `writeScaffold` writes the first five. `reconstructState(path)` rebuilds what is done, what is broken and what comes next from the handoff (falling back to `task-progress.md`) and `init.sh`, with no access to chat or the board, which is what lets a different runtime resume the task. The scaffold's `VERIFY_CMD` is a placeholder: until the agent sets a real command, the verification gate reports that no verify command is configured and the attempt fails. ## Path safety These functions remove folders recursively and run commands, so every path they act on is checked first: * **Task ids are one path segment.** `assertSafeTaskId` accepts only `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$` and throws for anything else rather than rewriting it. `branchNameForTask`, `worktreePathFor` and the lifecycle functions all go through it. * **Task folders stay under the root.** `worktreePathFor(repo, taskId, rootDir)` resolves the task's folder and throws if it would land anywhere but one segment below the root. * **Removal rebuilds the path.** Before their recursive remove, `pauseWorktree` and `completeWorktree` rebuild the folder from the root and the task id, and throw if the stored record points somewhere else. * **Review checkouts stay in the review folder.** `removeReviewWorktree(repo, worktree, rootDir)` throws before it removes anything unless the record's path is exactly one segment below the review root (`rootDir`, else `<repo>/.clawboo/reviews`). Pass the `rootDir` the checkout was provisioned with. `provisionReviewWorktree` checks its own path the same way. * **Verify commands run in Clawboo's folder only.** The server's verification gate refuses a worktree path outside Clawboo's state folder before it runs `VERIFY_CMD`. ## Helpers you will reach for * **`KeyedMutex`.** `mutex.run(key, fn, { acquireTimeoutMs })` serializes work per key and throws `MutexAcquireTimeoutError` when the wait runs out. The lifecycle functions lock per worktree path, the task runner locks each agent's persistent home (the wait is `CLAWBOO_HOME_MUTEX_ACQUIRE_MS`), and the OpenClaw routine dispatcher locks per agent. * **`execGit(cwd, args)`.** Runs `git` with a timeout and an output cap and throws `GitError` with the command's `stderr` and `args`. The Workspace tab's git status and file diffs use it. * **Diffs.** `diffStat` and `worktreeDiff` compare a worktree with its baseline, and `isDetached` confirms a review checkout has no branch. ## Who uses it Only the server: the task runner, the board's workspace routes, the verification gate and critic, the Routines dispatchers, and the Workspace tab's file view. Agent chat and team chat import only `MutexAcquireTimeoutError`, to recognise a busy agent home. ## Test it Run `pnpm --filter @clawboo/worktrees test`. The suite creates real git repositories in temporary folders, so it needs `git` on your `PATH`; CI also runs it on Windows and macOS. ## Related * [Worktrees and handoff](/concepts/worktrees-and-handoff): why tasks get worktrees and how handoff works. * [Hand a task from one runtime to another](/guides/cross-runtime-handoff): the handoff from start to finish. * [The executor runner](/internals/executor-runner): when the runner provisions, pauses and completes a worktree. * [How work is checked](/concepts/verification): how the verify command and the critic judge the work. # Agent settings API Source: https://docs.claw.boo/reference/rest-api/agent-settings REST reference for per-agent personality values, skills, OpenClaw command-execution settings, and the standing exec grants an OpenClaw agent holds. Per-agent settings that live outside the agent's files: personality slider values, installed skills, and, for OpenClaw agents, command-execution settings and standing exec grants. Use this page when you script agent setup or audit what an OpenClaw agent may run without asking. Examples use the default port `18790` and no access token. With `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each `curl`. Every route is on the general rate limit; the revoke also counts against the stricter sensitive limit (see the [REST API overview](/reference/rest-api/index)). Errors use `{ "error": "<message>" }` unless a section shows otherwise. A Clawboo Native agent's command access is a separate switch, `/api/agents/:agentId/shell`, documented in the [Agents API](/reference/rest-api/agents). ## Routes | Method | Path | Summary | Limit | | - | - | - | - | | GET | `/api/personality` | Read an agent's personality values | general | | POST | `/api/personality` | Save an agent's personality values | general | | GET | `/api/skills` | List installed skills, optionally for one agent | general | | POST | `/api/skills` | Install a skill on an agent, after a safety scan | general | | DELETE | `/api/skills` | Remove a skill from an agent | general | | GET | `/api/exec-settings` | Read an agent's command-execution setting | general | | GET | `/api/exec-settings/all` | Read every agent's `execAsk` value | general | | POST | `/api/exec-settings` | Save the setting, and apply it to OpenClaw for an OpenClaw agent | general | | GET | `/api/exec-allowlist` | List the commands an OpenClaw agent may run without asking | general | | POST | `/api/exec-allowlist/revoke` | Take standing exec grants back | sensitive | ## Personality The six personality sliders and an optional custom text, stored on the agent row as `{ values, customText }`. The server does not read these values when it builds a prompt. The dashboard turns them into a personality section in the agent's `SOUL.md` and saves that file separately, through `PUT /api/agents/:agentId/files/SOUL.md`. Calling this route alone does not change how an agent behaves. | Key | Slider in the dashboard | Default | | - | - | - | | `bite` | **Bite** | 45 | | `spine` | **Spine** | 50 | | `glass` | **Glass Box** | 45 | | `elbows` | **Elbows** | 45 | | `receipts` | **Receipts** | 60 | | `hobbyhorse` | **Hobbyhorse** | 25 | The dashboard uses values from 0 to 100. The server stores any `values` object without checking its keys. When the dashboard reads older values, it maps `verbosity` to `glass`, `humor` to `bite` and `caution` to `spine`, and drops `formality` and `speed_cost`. ### `GET /api/personality` * **Query params**: `agentId` (required). #### Responses **`200 OK`**: the stored values, or `null`s when nothing is stored or the stored value cannot be read: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { values: Record<string, number> | null, customText: string | null } ``` **`400 Bad Request`**: `{ "error": "agentId required" }`. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl "http://localhost:18790/api/personality?agentId=<agent-id>" ``` ### `POST /api/personality` Saves the values. An `agentId` with no agent row gets a placeholder row (named after the id). * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { agentId: string // required values: Record<string, number> // required; any truthy value is stored as sent, its shape is not checked. For example { "bite": 60, "spine": 50 } customText?: string | null // trimmed; blank is stored as null } ``` #### Responses **`200 OK`**: `{ "ok": true }`. **`400 Bad Request`**: `{ "error": "invalid JSON" }` when the body is not a JSON object, or `{ "error": "agentId and values required" }`. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/personality \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>","values":{"bite":60,"spine":50,"glass":45,"elbows":45,"receipts":60,"hobbyhorse":25},"customText":"Concise, dry."}' ``` ## Skills A skill row records that one or more agents have a skill. The skill then shows on those agents in the **Capabilities** panel and on the graphs (it is a `skill` record in `GET /api/capabilities`). This route only records the skill: it downloads and runs nothing. The **Marketplace** installs skills through it with `source: "curated"`. Each row lists its holders in `metadata.agentIds`, so one row can serve several agents: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} interface SkillRow { id: string name: string source: string // free text, for example "curated" category: string | null trustScore: number | null // not set by this route installedAt: number | null // epoch ms metadata: string | null // JSON text: { "agentIds": string[] } } ``` Skill routes answer errors as `{ "ok": false, "error": "<message>" }`. ### `GET /api/skills` Lists skills, newest first. * **Query params**: `agentId` (optional): only skills this agent holds. #### Responses **`200 OK`**: `{ ok: true, skills: SkillRow[] }`. **`500 Internal Server Error`**: `{ ok: false, error: string, skills: [] }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl "http://localhost:18790/api/skills?agentId=<agent-id>" ``` ### `POST /api/skills` Installs a skill on one agent. Before anything is stored, Clawboo's prompt-injection scan checks the name, source, category and the whole request body, at its strictest setting, where every finding blocks the install except a bidirectional-text isolate character, which is let through. A blocked install answers `422`; blocked and clean installs both write an audit row. An existing skill gains the agent as a holder; otherwise a new row is created. * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { id: string // required name: string // required source: string // required agentId: string // required category?: string | null } ``` #### Responses **`200 OK`**: `{ ok: true, skill: SkillRow | null }`. **`400 Bad Request`**: `{ "ok": false, "error": "Invalid JSON body" }`, or `{ "ok": false, "error": "id, name, source, and agentId are required" }` when a required field is not a non-empty string or `category` is not a string. **`422 Unprocessable Entity`**: the scan blocked the install. The audit row keeps only `pattern`, `line` and `fingerprint` for each finding: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: false error: 'skill blocked: injection / supply-chain finding' findings: Array<{ severity: 'exfil' | 'injection' | 'destructive' | 'supply-chain' pattern: string excerpt: string intent: 'language' | 'machine' // aimed at the model, or at the machine message: string line: number fingerprint: string }> } ``` **`500 Internal Server Error`**: `{ ok: false, error: string }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/skills \ -H 'Content-Type: application/json' \ -d '{"id":"web-search","name":"Web Search","source":"curated","agentId":"<agent-id>","category":"web"}' ``` ### `DELETE /api/skills` Removes one agent from a skill. When no holder is left, the row is deleted. * **Query params**: `id` (the skill id) and `agentId`, both required. #### Responses **`200 OK`**, one of: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true, "deleted": false, "reason": "skill not found" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true, "deleted": true, "removedRow": true } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true, "deleted": true, "removedRow": false } ``` `removedRow: false` means other agents still hold the skill. `deleted: true` does not confirm that this agent held it. **`400 Bad Request`**: `{ "ok": false, "error": "id and agentId query params are required" }`. **`500 Internal Server Error`**: `{ ok: false, error: string }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X DELETE "http://localhost:18790/api/skills?id=web-search&agentId=<agent-id>" ``` ## Exec settings Each agent's command-execution choice, stored on the agent row (`exec_config`) as `{ execAsk, execSecurity? }`. Only OpenClaw enforces it: for an OpenClaw agent, `POST` also writes the choice into the OpenClaw Gateway's exec-approval policy, the copy OpenClaw checks before it runs a command. The dashboard shows this control, **Command Execution** on the agent's **Permissions** tab, for OpenClaw agents only. On every other runtime the value is stored and nothing enforces it. Claude Code, Codex and Hermes run with their own approval prompts turned off; see [What agents can do](/concepts/permissions). | `execAsk` | Dashboard label | What `POST` writes into OpenClaw's policy for this agent | | - | - | - | | `off` | **Run Freely** | Removes the agent's own `ask` and `security`, so OpenClaw falls back to the fleet-wide (`*`) entry and then to the policy defaults. Standing grants are kept. | | `on-miss` | **Ask for Unknown** | `security: "allowlist"` and `ask: "on-miss"`. Standing grants are kept. | | `always` | **Always Ask** | `security: "allowlist"` and `ask: "always"`. Standing grants are kept. | `execSecurity` is stored but never sent to OpenClaw. > [!WARNING] > > `POST /api/exec-settings` replaces the whole stored value. A Claude Code, Codex or Hermes agent keeps the model and provider picked for it in that same field. Posting here for one of those agents drops that choice, and the agent falls back to its default model. ### `GET /api/exec-settings` Reads Clawboo's stored copy, not OpenClaw's policy. To see what OpenClaw holds, read `storedAsk` from `GET /api/exec-allowlist`. * **Query params**: `agentId` (required). #### Responses **`200 OK`**: the stored value, or `null` when nothing is stored. Other fields stored in the same place come back too: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { values: { execAsk: string; execSecurity?: string } | null } ``` **`400 Bad Request`**: `{ "error": "agentId required" }`. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl "http://localhost:18790/api/exec-settings?agentId=<agent-id>" ``` ### `GET /api/exec-settings/all` Every agent's stored `execAsk`, keyed by agent id. Agents with nothing stored, or whose stored value has no string `execAsk`, are left out. #### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { configs: Record<string, { execAsk: string }> } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/exec-settings/all ``` ### `POST /api/exec-settings` Stores the value first, then, for an OpenClaw agent, writes it into OpenClaw's policy under OpenClaw's own id for the agent. A refusal from the Gateway never loses what you sent. An `agentId` with no agent row gets a placeholder row (named after the id). * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { agentId: string // required values: { execAsk: string; execSecurity?: string } // required } ``` #### Responses **`200 OK`**: stored, and OpenClaw accepted the setting: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true, "gateway": "applied" } ``` **`200 OK`**: stored, with no OpenClaw policy to write. This is the answer for every other runtime, and for an OpenClaw agent row with no OpenClaw id: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true, "gateway": "not-applicable" } ``` **`400 Bad Request`**: `{ "error": "invalid JSON" }` when the body is not a JSON object, or `{ "error": "agentId and values required" }`. **`400 Bad Request`**: an OpenClaw agent with an `execAsk` other than `off`, `on-miss` or `always`. The value is already stored by then; on other runtimes any string is stored and answered `not-applicable`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "unknown execAsk: <value>" } ``` **`502 Bad Gateway`**: stored in Clawboo, but the Gateway refused it, so what the agent is held to did not change. `ok` is `false` and `savedLocally` is `true`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": false, "savedLocally": true, "error": "saved in clawboo, but the Gateway did not accept it: <reason>" } ``` After a `502`, `GET /api/exec-settings` returns the value you sent, not the one in force. Retry until the `POST` answers `200`, or read `storedAsk` from `GET /api/exec-allowlist` to see what OpenClaw holds. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/exec-settings \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>","values":{"execAsk":"always"}}' ``` ## Standing exec grants The commands an OpenClaw agent may already run without asking, and the way to take them back. Answering **Always** to a command prompt creates such a grant. These two routes back the **Commands this Boo can run without asking** card on the agent's **Permissions** tab. Only OpenClaw keeps standing exec grants, so both routes are OpenClaw-only. The read never calls the Gateway, because OpenClaw's read call (`exec.approvals.get`) also writes the policy back. Instead it opens OpenClaw's state database read-only at `<state dir>/state/openclaw.sqlite`. The state dir is `OPENCLAW_STATE_DIR` (or the older `MOLTBOT_STATE_DIR` or `CLAWDBOT_STATE_DIR`) when set, otherwise `~/.openclaw`, or an existing `~/.clawdbot` or `~/.moltbot` when `~/.openclaw` does not exist. The policy's internal socket token is never returned. Only the revoke talks to the Gateway. > [!NOTE] > > An empty list is a safety claim, so these routes never produce one by accident. "No grants", "no policy on this machine" and "a policy that could not be read" are three different answers with three different `state` values. Never show `unreadable` as an empty list: those grants are still on disk and still enforced. ### `GET /api/exec-allowlist` Reads one agent's grants from OpenClaw's stored policy, plus the fleet-wide `*` entry. OpenClaw applies the fleet-wide rows to this agent too, but they are not this agent's to revoke, so the two lists are returned separately. * **Query params**: `agentId` (required): Clawboo's id for the agent. The route looks up OpenClaw's id itself. #### Responses **`200 OK`**, `state: 'ok'`: the policy was read. `entries` is this agent's own list, the only rows a revoke can remove: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { state: 'ok' sourceAgentId: string // OpenClaw's id for this agent entries: ExecAllowlistRow[] // this agent's own grants wildcard: ExecAllowlistRow[] // the fleet-wide '*' entry: applies here, not revocable here duplicatedInWildcard: string[] // keys that appear in both lists storedSecurity: string | null // what OpenClaw holds for this agent storedAsk: string | null defaultSecurity: string | null // the policy defaults a missing field falls back to defaultAsk: string | null updatedAtMs: number } interface ExecAllowlistRow { key: string // a fingerprint of the row's content; pass it back to revoke pattern: string argPattern: string | null source: string | null classification: ExecAllowlistClass // see the table below bucket: 'agent' | 'wildcard' lastUsedCommand: string | null // never set for a grant made with Always lastResolvedPath: string | null lastUsedAt: number | null } ``` | `classification` | What the row allows | | - | - | | `bound-grant` | One exact command, in one folder (what **Always** creates) | | `exact-command-grant` | One exact command, in any folder | | `path-rule` | One program, with any arguments, in any folder | | `arg-rule` | One program, with matching arguments | | `catch-all` | Any command at all | | `node-marker` | Nothing on its own, but the grant it came with stops working without it | | `inert-allow-always` | Nothing: an **Always** row that OpenClaw skips | | `inert` | Nothing: a malformed row, or one that can never match | **`200 OK`**, `state: 'absent'`: there is no policy on this machine, so nothing is granted to any agent: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "state": "absent", "sourceAgentId": "<openclaw-agent-id>" } ``` **`200 OK`**, `state: 'not-applicable'`: the agent is not on OpenClaw, or its row has no OpenClaw id: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "state": "not-applicable", "runtime": "clawboo-native" } ``` **`502 Bad Gateway`**, `state: 'unreadable'`: the policy exists and could not be read. The counts come from the last successful save and cover every agent in the policy, not only this one (both are `0` when the database itself could not be opened). This response has **no `entries` key**, so a client cannot mistake it for an empty list: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { state: 'unreadable' sourceAgentId: string error: string // redacted reason knownAgentCount: number knownAllowlistCount: number } ``` **`400 Bad Request`**: `{ "error": "agentId required" }`. **`404 Not Found`**: `{ "error": "agent not found" }`. **`500 Internal Server Error`**: any other failure, redacted, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl "http://localhost:18790/api/exec-allowlist?agentId=<agent-id>" ``` ### `POST /api/exec-allowlist/revoke` Removes rows from one agent's own list. The Gateway has no call that removes one row, so the route rewrites OpenClaw's whole policy through the Gateway, with a check that nothing else changed it in between. Every other agent's policy is carried over unchanged; this agent keeps its own `security` and `ask`; an emptied list is kept rather than removed. Success is checked against the policy the Gateway returns after the write. On the sensitive rate limit as well as the general one. * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { agentId: string // Clawboo's id for the agent keys: string[] // 1 to 200 keys, each a `key` from the GET } ``` Pass `key` values back exactly as the GET returned them: a key is a fingerprint of the row's `pattern`, `argPattern` and `source`, not an id. Revoke a grant made with **Always** together with its `node-marker` row, in the same call. #### Responses **`200 OK`**, outcome `revoked`: the rows are gone, confirmed against the Gateway's reply. `remaining` counts the rows this agent still has: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "outcome": "revoked", "removed": 2, "remaining": 3 } ``` **`409 Conflict`**: nothing was written. One of: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "outcome": "already-absent", "error": "those grants were already gone" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "outcome": "blocked-wildcard", "keys": ["<key>"], "error": "that grant is set for every Boo, not just this one, so it cannot be removed here" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "outcome": "no-such-agent", "error": "this Boo has no policy to change" } ``` `blocked-wildcard` means a key is also in the fleet-wide `*` entry, so removing this agent's copy would leave the command allowed. One such key refuses the whole call; `keys` names them. **`502 Bad Gateway`**, outcome `not-verified`: the write went through, but the Gateway's reply still lists those rows, or came back with no policy to check: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "outcome": "not-verified", "keys": ["<key>"], "error": "the Gateway accepted the change and still lists those grants" } ``` > [!WARNING] > > `not-verified` is the one outcome that is not a no-op: the write landed and could not be confirmed. Read `GET /api/exec-allowlist` again before you act, and do not report the grant as gone. **`502 Bad Gateway`**: the Gateway call failed (for example, no Gateway connection). Its message is passed through, redacted: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "state": "failed", "error": "<message>" } ``` **`400 Bad Request`**: no `agentId`, or `keys` is missing, empty, longer than 200, or holds a non-string or empty entry: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "agentId and a non-empty keys array are required" } ``` **`400 Bad Request`**: the agent is not on OpenClaw, or its row has no OpenClaw id: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "this runtime keeps no standing exec grants" } ``` **`404 Not Found`**: `{ "error": "agent not found" }`. **`429 Too Many Requests`**: the sensitive limit: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Too many requests for this operation. Wait a moment and retry." } ``` #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/exec-allowlist/revoke \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>","keys":["<key-from-the-GET>"]}' ``` **Source of truth:** `apps/web/server/api/personality.ts`, `skills.ts`, `execSettings.ts` and `execAllowlist.ts`; the OpenClaw policy read is `apps/web/server/lib/agentSource/openclawExecAllowlistRead.ts`, the policy write is `packages/gateway-client/src/execApprovals.ts`, the revoke `execRevoke.ts`, and the row classes `execAllowlist.ts`. ## Related * [Review and revoke command permissions](/using/command-permissions): the Permissions tab over these routes * [What agents can do](/concepts/permissions): what each runtime may do on its own, and why a worktree is not a sandbox * [Agents API](/reference/rest-api/agents): agent files (where `SOUL.md` lives) and the Clawboo Native shell switch * [Capabilities API](/reference/rest-api/capabilities): where installed skills show up * [Tools and MCP API](/reference/rest-api/tools-and-mcp): resolving an approval that can create a standing grant # Agents API Source: https://docs.claw.boo/reference/rest-api/agents REST reference for agents: list, create, sync, delete, agent files, sessions, model, workspaces, browser screenshots, the native shell switch and 1:1 chat. These routes manage agents (each one appears as a Boo in the dashboard). SQLite is the source of truth for which agents exist. Every agent belongs to one source, named by its `sourceId`: `openclaw` agents mirror agents in the OpenClaw Gateway, while `clawboo-native`, `claude-code`, `codex` and `hermes` agents live only in SQLite. The agent list and single-agent reads serve SQLite, so they answer with the Gateway down (the list then reports `stale: true`). For an OpenClaw agent, creating it, a sync without a body, reading or writing its files, and listing its sessions need the Gateway, and answer `503 { "error": "gateway_disconnected" }` without it. Agents on the other runtimes need no Gateway. > [!NOTE] > > These routes return Clawboo's `AgentRecord` shape, not OpenClaw protocol shapes. See [Agent model](/concepts/agent-model) for the concept and [Agent sources](/internals/agent-source) for how the Gateway is synced in. A per-agent route goes to the source that owns the agent; an unknown id goes to the OpenClaw source, which answers its usual `404`. Every route here is on the general rate tier except `POST /api/agents/:agentId/shell`, which is on the sensitive tier (see [rate limits](/reference/rest-api/index#rate-limits)). ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/agents` | List every agent from SQLite | No | | POST | `/api/agents` | Create an agent through its source | No | | POST | `/api/agents/sync` | Sync OpenClaw agents into SQLite (server-side or from a list) | No | | GET | `/api/agents/registry/health` | The server's OpenClaw Gateway connection state | No | | POST | `/api/agents/cleanup-ghosts` | Delete local OpenClaw rows the Gateway no longer has | No | | GET | `/api/agents/:agentId` | One agent record | No | | DELETE | `/api/agents/:agentId` | Delete an agent and its local rows | No | | GET | `/api/agents/:agentId/files/:name` | Read one agent file | No | | PUT | `/api/agents/:agentId/files/:name` | Write one agent file | No | | GET | `/api/agents/:agentId/sessions` | List the agent's sessions | No | | GET | `/api/agents/:agentId/workspaces` | The agent's task worktrees and its team folder | No | | GET | `/api/agents/:agentId/team-folder/tree` | One directory level of the agent's team folder | No | | GET | `/api/agents/:agentId/team-folder/file` | One file from the agent's team folder | No | | GET | `/api/agents/:agentId/screenshot` | The newest browser screenshot for this agent | No | | POST | `/api/agents/:agentId/screenshot/capture` | Take a fresh screenshot of a browser already open for it | No | | PATCH | `/api/agents/:agentId/model` | Change a native or Hermes agent's model (and provider) | No | | POST | `/api/agents/:agentId/chat` | Start one 1:1 chat turn, any runtime but OpenClaw (`202`) | No | | POST | `/api/agents/:agentId/chat/stop` | Stop the agent's running 1:1 turn | No | | GET | `/api/agents/:agentId/chat/stream` | Live tail of the agent's 1:1 chat | SSE | | GET | `/api/agents/:agentId/shell` | Whether a native agent may ask to run commands | No | | POST | `/api/agents/:agentId/shell` | Turn that switch on or off (sensitive tier) | No | The `AgentRecord` shape (returned by `GET /api/agents`, `GET /api/agents/:agentId`, and inside the create `201`): ```ts theme={"theme":"github-dark"} interface AgentRecord { // Identity id: string // Clawboo's id for the agent sourceId: 'openclaw' | 'clawboo-native' | 'claude-code' | 'codex' | 'hermes' // owning source sourceAgentId: string // the id inside the source (the OpenClaw agent id for OpenClaw) // Display (merged) displayName: string // Boo-Zero override ▸ identity.name ▸ name ▸ id emoji: string | null avatarUrl: string | null avatarSeed: string | null // Live state (for OpenClaw agents, synced from the Gateway; may be stale while it is down) status: 'idle' | 'running' | 'error' | 'sleeping' | 'archived' sessionKey: string | null isDefault: boolean // true for the source's default agent // Clawboo's own settings for the agent (kept across re-syncs) teamId: string | null personalityConfig: unknown | null execConfig: unknown | null model?: string | null // native agents: the configured model; absent where the model lives elsewhere providerReady?: boolean | null // native agents: a provider key (or keyless Ollama) can run it; absent otherwise // Classification participantKind: 'agent' | 'human' runtime: 'openclaw' | 'clawboo-native' | 'claude-code' | 'codex' | 'hermes' capabilities: unknown | null tenantId: string | null // Lifecycle archivedAt: number | null // epoch ms when the agent was archived; null = live createdAt: number // epoch ms updatedAt: number // epoch ms } ``` `runtime` matches `sourceId` for every agent. `participantKind` and `tenantId` are reserved: agents Clawboo creates carry `'agent'` and `null`. *** ## `GET /api/agents` Lists the agents of all five sources from SQLite, so the list answers even when the Gateway connection is down. `defaultId` is Boo Zero, resolved in this order: the override set with `POST /api/boo-zero/override`, then the native Boo Zero Clawboo creates, then the OpenClaw default agent. `mainKey`, `stale` and `lastSyncedAt` describe the OpenClaw connection; `stale` is `true` whenever the server's OpenClaw connection is not `connected`. * **Path params**: none. * **Query params**: | Param | Type | Default | Effect | | - | - | - | - | | `includeArchived` | `'true'` | off | Include soft-deleted (archived) records | | `teamId` | string | none (all teams) | Filter to one team | * **Request body**: none. ### Responses **`200 OK`**: the merged agent list: ```ts theme={"theme":"github-dark"} { defaultId: string // the runtime-neutral Boo Zero (resolveBooZero); '' if unset mainKey: string // OpenClaw main session key; 'main' if unset agents: AgentRecord[] stale: boolean // true when the server-side OpenClaw connection !== 'connected' lastSyncedAt: number | null pendingDeleteIds: string[] // Gateway ids of OpenClaw agents deleted while the Gateway was down } ``` An agent in `pendingDeleteIds` is already gone from `agents`, but the Gateway still lists it until the server's next sync deletes it there. A client that reads the Gateway's own `agents.list()` leaves those ids out. **`500 Internal Server Error`**: any failure listing or probing health: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/agents curl 'http://localhost:18790/api/agents?teamId=<team-uuid>&includeArchived=true' ``` *** ## `POST /api/agents` Creates an agent through a source. `sourceId` picks it: `openclaw` (the default), `clawboo-native`, `claude-code`, `codex` or `hermes`. An OpenClaw create makes the agent in the Gateway, writes its files there and mirrors it into SQLite, so it answers **503** while the Gateway connection is down. The other four write only to SQLite and work offline. `name` and every entry in `files` are scanned for prompt injection before anything is written. Instruction-override phrasing and hidden Unicode characters block the create with **422** and an `install` audit row. Text aimed at machines rather than the model (a `DROP TABLE` inside a code reviewer's worked example) passes and comes back as `findings`. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { name: string // required; trimmed; must be non-empty teamId?: string | null personalityConfig?: unknown execConfig?: unknown // for native agents: the agent's config (model, provider, tools, budgetUsd, ...) avatarSeed?: string | null files?: Record<string, string> // agent files written at create (e.g. SOUL.md, AGENTS.md) sourceId?: 'openclaw' | 'clawboo-native' | 'claude-code' | 'codex' | 'hermes' // default 'openclaw' } ``` Side effects of a successful create: * The new agent gets its own grants to the browser connectors. * Creating a `clawboo-native` agent in a team also creates the native Boo Zero, when none exists yet and a native provider is connected (best-effort). * A `clawboo-native` agent whose `execConfig.budgetUsd` is above 0 gets an agent budget in `cap` mode (a hard cap; see [Governance](/reference/rest-api/governance)). * An OpenClaw agent's `AGENTS.md` gets a short section on how to open web pages, added once. ### Responses **`201 Created`**: the agent was created. `findings` is present only when the scan produced review-level findings: ```ts theme={"theme":"github-dark"} { agent: AgentRecord, findings?: InjectionFinding[] } ``` **`422 Unprocessable Entity`**: `name` or a file carries a blocking prompt-injection finding. Nothing is written: ```ts theme={"theme":"github-dark"} { error: 'agent content blocked: prompt-injection finding', findings: InjectionFinding[] // pattern, severity, intent, message, line, excerpt, fingerprint } ``` **`400 Bad Request`**: missing or blank `name`: ```json theme={"theme":"github-dark"} { "error": "name is required" } ``` **`400 Bad Request`**: `sourceId` does not name a registered source: ```json theme={"theme":"github-dark"} { "error": "unknown sourceId '<sourceId>'" } ``` **`503 Service Unavailable`**: an OpenClaw create while the Gateway connection is down: ```json theme={"theme":"github-dark"} { "error": "gateway_disconnected" } ``` **`500 Internal Server Error`**: any other failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} # OpenClaw agent (the default source): needs a live Gateway curl -X POST http://localhost:18790/api/agents \ -H 'Content-Type: application/json' \ -d '{"name":"Research Boo","teamId":"<team-uuid>"}' # Native agent: SQLite only, works offline curl -X POST http://localhost:18790/api/agents \ -H 'Content-Type: application/json' \ -d '{"name":"Native Boo","sourceId":"clawboo-native"}' ``` *** ## `POST /api/agents/sync` Brings the OpenClaw agent list into SQLite. With a body that carries an `agents` array, it stores that list (the browser's own `agents.list` result, sent while the server has no Gateway connection of its own). With no body, the server reads the list from the Gateway itself, which needs the connection and answers **503** while it is down. * **Path/query params**: none. * **Request body** (optional; a body with an `agents` array stores that list): ```ts theme={"theme":"github-dark"} { defaultId?: string mainKey?: string // trimmed; defaults to 'main' scope?: string agents?: Array<{ id: string; name?: string; identity?: Record<string, unknown> }> } ``` Without an `agents` array the body is ignored and the server syncs from the Gateway. Storing a list touches only the fields that come from the Gateway; Clawboo's own fields (`teamId`, `personalityConfig`, `execConfig`, `avatarSeed` and so on) are kept. ### Responses **`200 OK`**: a list was stored (body with an `agents` array): ```ts theme={"theme":"github-dark"} { result: { upserted: number, archived: number, durationMs: 0, at: number } } ``` **`200 OK`**: server-side sync (no body): ```ts theme={"theme":"github-dark"} { result: { upserted: number, archived: number, durationMs: number, at: number } } ``` `archived` counts agents present in SQLite but gone upstream (their `archivedAt` is set; the tombstone is reversible and cleared if the agent reappears). **`503 Service Unavailable`**: a sync without a list while the Gateway connection is down: ```json theme={"theme":"github-dark"} { "error": "gateway_disconnected" } ``` **`500 Internal Server Error`**: any other failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} # Server-side sync curl -X POST http://localhost:18790/api/agents/sync # Browser-fallback upsert curl -X POST http://localhost:18790/api/agents/sync \ -H 'Content-Type: application/json' \ -d '{"defaultId":"a1","mainKey":"main","agents":[{"id":"a1","name":"Boo"}]}' ``` *** ## `GET /api/agents/registry/health` Reports the state of the server's own OpenClaw Gateway connection. It answers **200** whether or not the Gateway is connected. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the OpenClaw source's `HealthResult`: ```ts theme={"theme":"github-dark"} { ok: boolean connection: 'connected' | 'connecting' | 'reconnecting' | 'disconnected' lastSyncedAt: number | null message?: string } ``` **`500 Internal Server Error`**: a failure reading health: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/agents/registry/health ``` *** ## `POST /api/agents/cleanup-ghosts` A one-shot cleanup the dashboard runs after it loads the Gateway's agent list. Send the ids of every agent the Gateway has; the route deletes each local OpenClaw agent row that is not in the list, with its cost and approval-history rows and its per-agent settings. Running it again changes nothing. Only rows with `sourceId = 'openclaw'` are compared, so agents on other runtimes are never removed. * **Path params**: none. * **Query params**: | Param | Type | Effect | | - | - | - | | `allowEmpty` | `'true'` | Permit an empty `liveAgentIds` (a guard against wiping every row after a Gateway hiccup) | * **Request body**: ```ts theme={"theme":"github-dark"} { liveAgentIds: string[] } // required; the IDs alive in the Gateway ``` ### Responses **`400 Bad Request`**: `liveAgentIds` is missing or not an array: ```json theme={"theme":"github-dark"} { "error": "liveAgentIds array required" } ``` **`400 Bad Request`**: `liveAgentIds` is empty and `?allowEmpty=true` was not passed (the message contains an em dash, written here as its JSON escape): ```json theme={"theme":"github-dark"} { "error": "empty liveAgentIds list \u2014 pass ?allowEmpty=true to confirm" } ``` **`200 OK`**: no local rows were stale: ```json theme={"theme":"github-dark"} { "ok": true, "deleted": 0 } ``` **`200 OK`**: stale rows were swept (`remaining` is the post-sweep agent count, or `null`): ```ts theme={"theme":"github-dark"} { ok: true, deleted: number, remaining: number | null } ``` **`500 Internal Server Error`**: a failure during the sweep: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/agents/cleanup-ghosts \ -H 'Content-Type: application/json' \ -d '{"liveAgentIds":["a1","a2"]}' ``` *** ## `GET /api/agents/:agentId` Returns one agent record, routed to the source that owns the row. * **Path params**: `agentId`. * **Request body**: none. ### Responses **`200 OK`**: the record: ```ts theme={"theme":"github-dark"} { agent: AgentRecord } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`404 Not Found`**: no agent with that id in its source: ```json theme={"theme":"github-dark"} { "error": "agent not found" } ``` **`500 Internal Server Error`**: a failure reading the record: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/agents/<agent-id> ``` *** ## `DELETE /api/agents/:agentId` Deletes the agent. The owning source deletes it upstream first (for OpenClaw, the Gateway's `agents.delete`), then removes the local agent row along with the `cost_records` and `approval_history` rows that reference it. If the Gateway connection is down, the local rows are removed anyway. > [!NOTE] > > An OpenClaw agent deleted while the Gateway is down is recorded as a pending delete. On the next sync the server sends the Gateway's `agents.delete` before it reads the list, and until that lands no sync re-creates the agent's row. If the Gateway refuses the delete outright (it will not delete its only agent, or one that owns shared credentials), the pending delete is dropped and the agent comes back on that same sync. A connection failure is retried on the next sync. * **Path params**: `agentId`. * **Request body**: none. ### Responses **`200 OK`**: deleted upstream and locally. Agents on runtimes other than OpenClaw have nothing upstream, and also answer `upstreamDeleted: true`: ```json theme={"theme":"github-dark"} { "ok": true, "upstreamDeleted": true } ``` **`200 OK`**: the Gateway was disconnected, so the local rows were cleaned and, for an OpenClaw agent, the Gateway delete was recorded for the next sync (`upstreamDeletePending: true`). It is `false` when there was no OpenClaw row to delete: ```json theme={"theme":"github-dark"} { "ok": true, "upstreamDeleted": false, "upstreamDeletePending": true } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`500 Internal Server Error`**: any failure other than a lost Gateway connection: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X DELETE http://localhost:18790/api/agents/<agent-id> ``` *** ## `GET /api/agents/:agentId/files/:name` Reads one agent file through the owning source. An OpenClaw agent's files are read live from the Gateway (**503** while it is down); the other runtimes keep their files in SQLite. The `:name` segment must be one of the seven agent file names; any other name is rejected before the read. * **Path params**: `agentId`, `name`. `:name` is one of `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `TOOLS.md`, `HEARTBEAT.md`, `MEMORY.md`. The route stores any of them on any agent, but which files actually shape an agent depends on its runtime: see [Agents](/using/agents). OpenClaw 2026.9 and later does not load `TOOLS.md`, so put tool guidance in `AGENTS.md`. * **Request body**: none. ### Responses **`200 OK`**: the file content: ```ts theme={"theme":"github-dark"} { name: string, content: string } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`400 Bad Request`**: `:name` is not a recognized agent file: ```json theme={"theme":"github-dark"} { "error": "invalid file name" } ``` **`503 Service Unavailable`**: an OpenClaw agent, and the Gateway connection is down: ```json theme={"theme":"github-dark"} { "error": "gateway_disconnected" } ``` **`500 Internal Server Error`**: any other failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/agents/<agent-id>/files/SOUL.md ``` *** ## `PUT /api/agents/:agentId/files/:name` Writes one agent file through the owning source. Same `:name` allowlist as the read; the body must carry a string `content`. `content` is scanned for prompt injection before the write, exactly as `POST /api/agents` scans its `files`. A blocking finding refuses the write with **422** and an `install` audit row; review-level findings come back on the `200`. * **Path params**: `agentId`, `name` (same allowlist as the GET). * **Request body**: ```ts theme={"theme":"github-dark"} { content: string } // required; must be a string ``` ### Responses **`200 OK`**: the file was written (echoes what was stored). `findings` is present only when the scan produced review-level findings: ```ts theme={"theme":"github-dark"} { name: string, content: string, findings?: InjectionFinding[] } ``` **`422 Unprocessable Entity`**: `content` carries a blocking prompt-injection finding. Nothing is written: ```ts theme={"theme":"github-dark"} { error: 'agent content blocked: prompt-injection finding', findings: InjectionFinding[] } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`400 Bad Request`**: `:name` is not a recognized agent file: ```json theme={"theme":"github-dark"} { "error": "invalid file name" } ``` **`400 Bad Request`**: `content` is missing or not a string: ```json theme={"theme":"github-dark"} { "error": "content (string) required" } ``` **`503 Service Unavailable`**: an OpenClaw agent, and the Gateway connection is down: ```json theme={"theme":"github-dark"} { "error": "gateway_disconnected" } ``` **`500 Internal Server Error`**: any other failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PUT http://localhost:18790/api/agents/<agent-id>/files/AGENTS.md \ -H 'Content-Type: application/json' \ -d '{"content":"# How you work\n\n- Reply in short bullet points.\n"}' ``` *** ## `GET /api/agents/:agentId/sessions` Lists the agent's sessions. For an OpenClaw agent the list is read live from the Gateway, so the route answers **503** while the connection is down. The other runtimes list the sessions stored in SQLite. * **Path params**: `agentId`. * **Request body**: none. ### Responses **`200 OK`**: the session list: ```ts theme={"theme":"github-dark"} { sessions: Array<{ id: string sourceId: 'openclaw' | 'clawboo-native' | 'claude-code' | 'codex' | 'hermes' sourceSessionId: string // the session key inside the source agentId: string teamId: string | null status: 'active' | 'idle' | 'closed' createdAt: number | null updatedAt: number | null }> } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`503 Service Unavailable`**: an OpenClaw agent, and the Gateway connection is down: ```json theme={"theme":"github-dark"} { "error": "gateway_disconnected" } ``` **`500 Internal Server Error`**: any other failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/agents/<agent-id>/sessions ``` *** ## `GET /api/agents/:agentId/workspaces` The task worktrees of this agent's assigned tasks, newest first, and the agent's team folder. The agent's **Workspace** tab uses it to pick what to show, and to say so when the agent has nothing. Archived workspaces are left out. `onDisk: false` marks a checkout that was paused or cleaned up: the row and the branch remain, but the files are gone, and the tab says so rather than showing an empty tree. The files themselves come from the `/api/board/:taskId/workspace/*` routes on the [Board API](/reference/rest-api/board). * **Path params**: `agentId`. * **Request body**: none. ### Responses **`200 OK`**: ```json theme={"theme":"github-dark"} { "ok": true, "workspaces": [ { "taskId": "t_8f2c", "title": "Fix the flaky board round-trip test", "taskStatus": "in_progress", "branch": "clawboo/task-t_8f2c", "repoPath": "/Users/you/code/clawboo", "worktreePath": "/Users/you/.clawboo/worktrees/ab12cd34ef56/t_8f2c", "workspaceStatus": "active", "onDisk": true, "updatedAt": 1756000000000 } ], "teamFolder": { "teamId": "4f1e2b9a-7c3d-4e8f-9a1b-2c3d4e5f6a7b", "path": "/Users/you/.clawboo/workspaces/4f1e2b9a-7c3d-4e8f-9a1b-2c3d4e5f6a7b", "onDisk": true } } ``` `teamFolder` is the folder the agent's runs start in when they have no worktree: its team's folder, or `workspaces/_no-team` with `teamId: null` for an agent with no team. `onDisk` is `false` until a run creates it. It is `null` for an OpenClaw agent. An agent with no worktree-backed tasks returns an empty `workspaces` list. **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/agents/<agent-id>/workspaces ``` *** ## `GET /api/agents/:agentId/team-folder/tree` and `GET /api/agents/:agentId/team-folder/file` Read-only views of the agent's team folder, behind the **Team folder** choice on its **Workspace** tab. The folder comes from the agent's row, never from the request. Both routes take the same `dir` and `path` query values, keep every path inside the folder the same way, and answer with the same shapes as the task worktree routes in [The workspace file view](/reference/rest-api/board#the-workspace-file-view). There is no diff or git status, because the folder is not a git checkout. * **Path params**: `agentId`. * **`404`** `{ "ok": false, "error": "workspace not found", "reason": "not_found" }`: an unknown agent, or one whose runtime gets no team folder (OpenClaw). * **`404`** with `"reason": "gone"`: the folder does not exist yet, because no run has created it. ### Example ```bash theme={"theme":"github-dark"} curl 'http://localhost:18790/api/agents/<agent-id>/team-folder/tree?dir=' curl 'http://localhost:18790/api/agents/<agent-id>/team-folder/file?path=notes.md' ``` *** ## `GET /api/agents/:agentId/screenshot` The newest browser screenshot for this agent, shown on its **Browser** card. A frame is kept whenever one of the agent's browser tools returns an image, and when `POST /api/agents/:agentId/screenshot/capture` takes one. The route serves only this agent's own frame, never another agent's. Frames are held in memory, one per agent: a new frame replaces the old one, and past 24 MB in total the oldest frames are dropped. They are also saved under `<clawboo home>/frames/`, so a restart brings them back, marked `restored: true`. A restored frame can be hours old. * **Path params**: `agentId`. * **Query params**: `meta` (any value) returns the frame's details as JSON instead of the image. * **Request body**: none. ### Responses **`200 OK`** with `?meta`: the frame's details. `restored` is present only for a frame reloaded from disk: ```json theme={"theme":"github-dark"} { "ok": true, "mimeType": "image/png", "toolName": "mcp__playwright__browser_take_screenshot", "ts": 1756000000000, "restored": true } ``` **`200 OK`** without `?meta`: the image bytes. `Content-Type` is `image/png`, `image/jpeg`, `image/gif` or `image/webp` (a frame that states any other type is served as `image/png`), with `Content-Length`, `Cache-Control: no-store` and `X-Content-Type-Options: nosniff`. **`404 Not Found`**: this agent has no frame: ```json theme={"theme":"github-dark"} { "ok": false, "error": "no screenshot for this agent" } ``` **`500 Internal Server Error`**: the stored frame could not be decoded: ```json theme={"theme":"github-dark"} { "ok": false, "error": "frame is not decodable" } ``` ### Example ```bash theme={"theme":"github-dark"} curl 'http://localhost:18790/api/agents/<agent-id>/screenshot?meta=1' curl -o frame.png http://localhost:18790/api/agents/<agent-id>/screenshot ``` *** ## `POST /api/agents/:agentId/screenshot/capture` Takes a fresh screenshot of a browser that is already open for this agent, so the **Browser** card can refresh without waiting for the agent's next browser action. It never opens a browser and never navigates: with no browser open it fails, and the card stays as it was. The route tries each browser connector Clawboo runs (Playwright's `browser_take_screenshot`, Chrome DevTools' `take_screenshot`), and only where this agent's own grant allows that tool and the agent's browser is running. If none of those has a browser open and the agent runs on OpenClaw, it captures the newest browser tab OpenClaw tracks for that agent's session (stored with `toolName: "openclaw__browser"`). Read the new frame with `GET /api/agents/:agentId/screenshot`. * **Path params**: `agentId`. * **Request body**: none. ### Responses **`200 OK`**: a new frame was stored: ```json theme={"theme":"github-dark"} { "ok": true, "ts": 1756000000000 } ``` **`409 Conflict`**: no browser is open for this agent: ```json theme={"theme":"github-dark"} { "ok": false, "error": "this Boo has no browser open right now" } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "ok": false, "error": "agentId is required" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/agents/<agent-id>/screenshot/capture ``` *** ## `PATCH /api/agents/:agentId/model` Changes the model of a `clawboo-native` or `hermes` agent, and optionally its provider. It needs no Gateway. * **Native agent:** the stored agent config is rewritten, and the next run uses it. With a `provider`, the provider, model and the key variable it reads (`envVar`) change together, so a model picked from another provider's list never runs against the old provider. Without a `provider`, only the model changes, which is also how you set a custom model id for the current provider. The server checks the provider name but not the model string, so custom ids work. * **Hermes agent:** Hermes reaches every model through OpenRouter, so the model is stored as `{ provider: 'openrouter', model }` and any other `provider` is a `400`. * **Other runtimes** answer `404`. An OpenClaw agent's model is set through `PATCH /api/system/openclaw-config` (see [System API](/reference/rest-api/system)). * **Path params**: `agentId`. * **Request body**: `{ model: string, provider?: string }`. `provider` must be one of `anthropic`, `openai`, `openrouter`, `ollama`, `google`, `xai`, `groq`, `mistral`, `together`, `cerebras`, `moonshot`. With `ollama`, which needs no key, the stored `envVar` is `OLLAMA_BASE_URL`. ### Responses **`200 OK`**: the model (and the provider, when sent) was saved. `provider` is echoed only when the request carried one: ```json theme={"theme":"github-dark"} { "ok": true, "model": "anthropic/claude-haiku-4.5", "provider": "openrouter" } ``` **`400 Bad Request`**: one of these messages: | `error` | Cause | | - | - | | `model (non-empty string) required` | `model` is missing or blank | | `unknown provider '<provider>'` | `provider` is not in the list above | | `hermes routes every model via OpenRouter; provider must be 'openrouter' or omitted` | A Hermes agent with another provider | | `agentId required` | Missing `agentId` segment | **`404 Not Found`**: one of these messages: | `error` | Cause | | - | - | | `agent not found` | No agent with that id | | `native agent config not found` | A native agent with no stored config | | `model change via this route is native/hermes-only` | An agent on any other runtime | **`500 Internal Server Error`**: any other failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PATCH http://localhost:18790/api/agents/<agent-id>/model \ -H 'Content-Type: application/json' \ -d '{ "model": "anthropic/claude-haiku-4.5", "provider": "openrouter" }' ``` *** ## `POST /api/agents/:agentId/chat` Starts one turn of the 1:1 chat of a `clawboo-native`, `claude-code`, `codex` or `hermes` agent, Boo Zero's personal chat included. The route stores your message under the agent's 1:1 session key, the `sessionKey` in its record: `agent:<agentId>:native` for a native agent and `agent:<agentId>:<runtime>` for the others. It starts the reply in the background and answers **202** at once. The reply streams over `GET /api/agents/:agentId/chat/stream` and is saved by the server, so closing the connection does not stop it. An OpenClaw agent answers **404**; its 1:1 chat goes through the Gateway connection instead. Each turn resumes the session the agent's previous 1:1 turn saved, so the conversation carries over. The pointer is one setting per agent, `native-chat-session:<agentId>`, whatever the runtime. `/reset` in the chat clears it, and so does a failed Claude Code, Codex or Hermes turn that tried to resume, so the next turn starts a fresh session. One agent's turns run one at a time: a second message waits for the turn in front of it. On Clawboo Native, Codex and Hermes it also waits for that agent's team-chat runs and routines, and a turn that cannot start within 10 minutes leaves the notice "This agent was busy with another run and did not pick the message up in time. Send it again." Claude Code, Codex and Hermes get the agent's `SOUL.md` as a persona block on every turn. Every turn starts in the agent's team folder, `<CLAWBOO_HOME>/workspaces/<teamId>` (`_no-team` for an agent on no team), created `0700` before the run. If the folder cannot be created, the turn ends with a notice instead of starting anywhere else. A native agent gets its file tools there, and `run_command` when its shell switch is on. A turn that fails before it writes any reply leaves a notice with the reason, including a run ended after `CLAWBOO_RUN_SILENT_TIMEOUT_MS` (30 minutes by default) with no events. A turn that fails partway keeps the text it had written. * **Path params**: `agentId`. * **Request body**: ```ts theme={"theme":"github-dark"} { message: string // required; trimmed; must be non-empty. The context-injected // text delivered to the model. displayText?: string // what the transcript shows; defaults to `message`. The two // differ when the client prepends a rules block / @team brief. entryId?: string // idempotency key for the persisted user turn; defaults to // `user-<Date.now()>`. Re-posting one inserts nothing (the // optimistic client bubble and the SSE replay share the id). } ``` ### Responses **`202 Accepted`**: the user turn was persisted and the reply run started: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`400 Bad Request`**: missing or blank `message`: ```json theme={"theme":"github-dark"} { "error": "message required" } ``` **`404 Not Found`**: the agent is unknown, or its `runtime` is `openclaw`: ```json theme={"theme":"github-dark"} { "error": "agent does not chat through the server" } ``` **`500 Internal Server Error`**: any other failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/agents/<agent-id>/chat \ -H 'content-type: application/json' \ -d '{ "message": "summarize the board", "entryId": "user-1718450000000" }' ``` *** ## `POST /api/agents/:agentId/chat/stop` Stops the agent's running 1:1 turn (the composer's **Stop** button). An agent has at most one running 1:1 turn; with none running the call changes nothing and still answers **200**. * **Path params**: `agentId`. * **Request body**: none. ### Responses **`200 OK`**: the run was aborted, or there was nothing to abort: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`400 Bad Request`**: missing `agentId` segment: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`500 Internal Server Error`**: a failure during abort: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/agents/<agent-id>/chat/stop ``` *** ## `GET /api/agents/:agentId/chat/stream` A Server-Sent Events tail of the agent's 1:1 chat (`agent:<agentId>:native`, or `agent:<agentId>:<runtime>` for a Claude Code, Codex or Hermes agent). It only reads; it never starts a turn. It sends two kinds of frame: stored `chat_messages` rows, polled every 750 ms past a row-id cursor (each with an `id:` line, so they replay on resume), and live frames for streaming text and run state (no `id:` line, never replayed). **Where it starts.** With no `Last-Event-ID` header and no `since`, the stream starts at the newest stored message and sends only what follows. Load the history first with `GET /api/chat-history` (see [Chat history API](/reference/rest-api/chat-history)). A supplied cursor resumes after that row id; `Last-Event-ID` wins over `since`, and an invalid or negative value resumes from `0` (the whole chat). * **Path params**: `agentId`. * **Query params**: | Param | Type | Notes | | - | - | - | | `since` | number | Resume after this `chat_messages` row id. `Last-Event-ID` takes priority. | * **Request body**: none. > [!NOTE] > > This is an SSE route: there is no JSON response body, and the catalog below describes the frames. A missing `agentId` ends the response with a bare **400** and no body. Each poll reads up to 500 rows past the cursor. ### Connection On open, the handler writes `HTTP/1.1 200` with: ```http theme={"theme":"github-dark"} Content-Type: text/event-stream; charset=utf-8 Cache-Control: no-cache, no-transform Connection: keep-alive X-Accel-Buffering: no ``` then sends a `: connected` comment, any stored rows already past the cursor, and the last `status` published for the chat, so a client that reconnects learns about a turn that ended while no stream was open. ### Event catalog | Frame | When | Shape | | - | - | - | | `: connected` | Immediately on open | SSE comment (ignored by `EventSource`) | | entry data | Per new stored row (every 750 ms) | `id: <row id>` line + `data: <json>` line (the stored chat entry) | | `event: delta` | Per streamed piece of the reply | `{ sessionKey, runId, text }`; `text` is the whole reply so far | | `event: status` | When a turn starts or ends, and on open | `{ agentId, status: 'running' \| 'idle' \| 'error' }` | | `: keepalive` | Every 20 s while open | SSE comment (keeps the connection warm) | `delta` and `status` frames carry no `id:` line, so they never move the resume cursor. The stored rows are the record; the deltas only let a reply appear as it is written. ### Example ```bash theme={"theme":"github-dark"} curl -N http://localhost:18790/api/agents/<agent-id>/chat/stream curl -N 'http://localhost:18790/api/agents/<agent-id>/chat/stream?since=4187' ``` *** ## `GET /api/agents/:agentId/shell` Reports whether a `clawboo-native` agent may ask to run commands. The value is the `tools.shell` field of the agent's stored config, and it decides whether the `run_command` tool is offered to the model at all. It does not let a command run on its own: `run_command` puts every command in front of a person, remembers nothing and has no allowlist, so a command allowed once asks again the next time. A missing field reads as off, and the native defaults leave it out, so a new native agent starts with the shell off. This switch is separate from `/api/exec-settings` (see [Agent settings API](/reference/rest-api/agent-settings)), which writes the OpenClaw Gateway's exec policy. Nothing reads that policy for a native agent, so this route refuses every other runtime by name. * **Path params**: `agentId`. * **Request body**: none. ### Responses **`200 OK`**: ```json theme={"theme":"github-dark"} { "ok": true, "enabled": false } ``` **`400 Bad Request`**: the agent is not on the `clawboo-native` runtime. The response names the runtime it found: ```json theme={"theme":"github-dark"} { "error": "only a clawboo-native Boo has this switch", "runtime": "openclaw" } ``` **`404 Not Found`**: no agent with that id, or a native agent with no stored config: ```json theme={"theme":"github-dark"} { "error": "agent not found" } ``` ```json theme={"theme":"github-dark"} { "error": "native agent config not found" } ``` **`500 Internal Server Error`**: any other failure, redacted: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/agents/<agent-id>/shell ``` *** ## `POST /api/agents/:agentId/shell` Turns the switch on or off. **Rate tier: sensitive.** Changing what an agent may ask to run is a permissions change, so this route shares the 60-requests-a-minute sensitive budget (see [rate limits](/reference/rest-api/index#rate-limits)). The route writes the whole `tools` record back with only `shell` changed, so the agent's other tool flags (memory, tasks, teamchat and the reserved custom list) are kept. It then reads the config back and answers with what is stored, not with what you sent. * **Path params**: `agentId`. * **Request body**: ```ts theme={"theme":"github-dark"} { enabled: boolean // required; anything other than a boolean is a 400 } ``` > [!NOTE] > > The confirmation the dashboard shows before switching this on exists only in the browser. This route accepts `{ "enabled": true }` directly, and `POST /api/agents` with `sourceId: 'clawboo-native'` can set `execConfig.tools.shell` at creation. Native agents created from the dashboard start with the shell off. Switching this on is necessary for `run_command` to exist, but not enough. The tool is also left out of any run without a working directory. Team chat, 1:1 chat, delegated turns and task runs all carry one: the team's folder, or the task's worktree. It is never offered on Windows. ### Responses **`200 OK`**: the stored value after the write: ```json theme={"theme":"github-dark"} { "ok": true, "enabled": true } ``` **`400 Bad Request`**: `enabled` is missing or is not a boolean: ```json theme={"theme":"github-dark"} { "error": "enabled must be true or false" } ``` **`400 Bad Request`**: the agent is not on the `clawboo-native` runtime: ```json theme={"theme":"github-dark"} { "error": "only a clawboo-native Boo has this switch", "runtime": "openclaw" } ``` **`404 Not Found`**: no agent with that id, or a native agent with no stored config: ```json theme={"theme":"github-dark"} { "error": "agent not found" } ``` **`429 Too Many Requests`**: the sensitive ceiling: ```json theme={"theme":"github-dark"} { "error": "Too many requests for this operation. Wait a moment and retry." } ``` **`500 Internal Server Error`**: any other failure, redacted: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/agents/<agent-id>/shell \ -H 'Content-Type: application/json' \ -d '{"enabled":true}' ``` *** ## Error envelope Most error bodies are `{ "error": "<message>" }`. The exceptions: * The two `422` responses (create, and the file `PUT`) add `findings`, the injection findings that blocked the write: each has `pattern`, `severity`, `intent`, `message`, `line`, `excerpt` and `fingerprint`. * The two screenshot routes answer `{ "ok": false, "error": "..." }`. * The two shell routes add `runtime` to their wrong-runtime `400`, naming the runtime they found. * `GET /api/agents/:agentId/chat/stream` has no JSON error: its only failure before the stream opens is a bare `400` with no body. A `503 { "error": "gateway_disconnected" }` means the route needed the OpenClaw Gateway for an OpenClaw agent and the server's connection is down. The list and single-agent reads never answer it: they serve SQLite. ## Related * [Agent model](/concepts/agent-model): what an agent is, Boo Zero, and how agents relate to teams. * [Agents](/using/agents): the agent files each runtime reads, and the dashboard screens behind these routes. * [Agent settings API](/reference/rest-api/agent-settings): personality, OpenClaw exec settings and skills. * [Teams API](/reference/rest-api/teams): add an agent to a team, and team chat. * [Runtimes API](/reference/rest-api/runtimes): run a board task on a runtime. * [Agent sources](/internals/agent-source): how OpenClaw agents are synced into SQLite. # Board API Source: https://docs.claw.boo/reference/rest-api/board REST reference for the board: tasks, claims, status changes, comments, retry, assign, the run ledger, dependencies, worktrees, workspace files and preview. These routes read and change the [board](/concepts/the-board), where every team's tasks live. They create and list tasks, claim a task for one agent, move tasks through the status rules (including the check that guards `done`), record each run in the run ledger, link dependencies, and manage a task's git worktree: create it, read it, pause or complete it, browse its files and preview what the agent built. Several routes also post a short system line to the team's peer room, the agents' shared room behind the TeamChat tools (see [Peer chat](/concepts/peer-chat)). The dashboard's team chat does not show those lines, and a failed post never fails the request. Every POST and PATCH body is validated. An invalid body returns **400** `{ "error": "invalid body", "details": <zod flatten> }`; the handoff route returns `{ "error": "invalid handoff", "details": ... }` instead. Every route here is on the general rate tier (see [rate limits](/reference/rest-api/index#rate-limits)). > [!NOTE] > > A claim is how agents share work without doing it twice: `POST /api/board/:taskId/claim` answers **409** when another worker already holds the task. **A 409 is an answer, not a transient error; do not retry it.** The task is simply taken. The same applies to `409 illegal_transition` on `PATCH /api/board/:taskId`. ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/board` | List tasks (or the ready-to-work ones) | No | | POST | `/api/board` | Create a task (optionally for one agent, started at once) | No | | POST | `/api/board/:taskId/retry` | Run a stuck task again, straight to its agent | No | | POST | `/api/board/:taskId/assign` | Give an unowned task to one team member and start it | No | | GET | `/api/board/:taskId` | One task with its comments and parent chain | No | | POST | `/api/board/:taskId/claim` | Claim a `todo` task for one agent (409 if taken) | No | | PATCH | `/api/board/:taskId` | Change status (checked) and edit title, description, priority | No | | POST | `/api/board/:taskId/comments` | Add a comment (a person's reply reaches the task's agent) | No | | POST | `/api/board/:taskId/executions` | Open a run in the ledger (after a claim) | No | | PATCH | `/api/board/executions/:execId` | Close a run with its outcome, tokens and cost | No | | GET | `/api/board/:taskId/executions` | The task's run ledger | No | | POST | `/api/board/:taskId/deps` | Make one task wait for another | No | | POST | `/api/board/:taskId/cancel-dependents` | Cancel the waiting tasks behind a failed one | No | | POST | `/api/board/:taskId/workspace` | Create the task's worktree, branch and task files | No | | GET | `/api/board/:taskId/workspace` | Read the workspace and the state to resume from | No | | PATCH | `/api/board/:taskId/workspace` | `pause` or `complete` the worktree | No | | POST | `/api/board/:taskId/workspace/handoff` | Write `AGENT_HANDOFF.json` for the next runtime or person | No | | GET | `/api/board/:taskId/workspace/detail` | Task file contents and the diff (the task drawer view) | No | | GET | `/api/board/:taskId/workspace/tree` | One directory level of the worktree | No | | GET | `/api/board/:taskId/workspace/file` | One file, size-capped | No | | GET | `/api/board/:taskId/workspace/file-diff` | One file's diff against the starting commit | No | | GET | `/api/board/:taskId/workspace/status` | `git status` for the worktree | No | | GET | `/api/board/:taskId/preview` | Serve the worktree's `index.html` (redirects to `preview/`) | No | | GET | `/api/board/:taskId/preview/<path>` | Serve one file from the worktree, for the preview frame | No | ## Task statuses A task is in one of seven statuses: `backlog`, `todo`, `in_progress`, `in_review`, `blocked`, `done`, `cancelled`. `done` and `cancelled` are final. Writing the status a task already has is allowed and changes nothing. Any move not in this table is refused with **409** `illegal_transition`: | From | Can move to | | - | - | | `backlog` | `todo`, `blocked`, `cancelled` | | `todo` | `in_progress`, `blocked`, `backlog`, `cancelled` | | `in_progress` | `in_review`, `done`, `blocked`, `todo`, `cancelled` | | `in_review` | `done`, `in_progress`, `blocked`, `cancelled` | | `blocked` | `todo`, `in_progress`, `backlog`, `cancelled` | | `done`, `cancelled` | nothing | Moving a task to `todo` clears its assignee and its check result, so it can be claimed again. *** ## `GET /api/board` Lists tasks, for one team or all of them, or only the tasks that are ready to work. With `ready=true` the list holds `todo` tasks that are not dropped and whose dependencies are all `done`. Otherwise it is filtered by `teamId`, `status` and `includeDropped`. Dropped (soft-deleted) tasks are left out unless `includeDropped=true`. * **Query params**: | Param | Type | Notes | | - | - | - | | `teamId` | string | Scope to one team. Omit for all teams. | | `status` | task status | Filter by one status (one of the 7). Ignored when `ready=true`. | | `ready` | `'true'` | Return only ready-to-work tasks (deps satisfied). Overrides `status`/`includeDropped`. | | `includeDropped` | `'true'` | Include soft-deleted tasks. | * **Request body**: none. ### Responses **`200 OK`**: the task list, most recently updated first (the `ready=true` list puts the highest `priority` first, then the most recently updated). The `ready=true` rows carry none of `attention`, `runCost` and `runningSince`. Other sections call one of these rows `DbTask`: ```ts theme={"theme":"github-dark"} { tasks: Array<{ id: string title: string description: string | null status: 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'blocked' | 'done' | 'cancelled' priority: number teamId: string | null roomId: string | null // the chat room it was delegated in; null = the team's General chat assigneeAgentId: string | null assigneeRuntime: string | null parentTaskId: string | null sourceDelegationId: string | null worktreeRef: string | null branchRef: string | null costUsd: number // the sum of the task's recorded run costs (0 before any) parentSessionId: string | null dropped: number // 0 | 1 (soft-delete flag) tenantId: string | null // always null scheduledBy: string // 'manual' | 'clawboo' | runtime id verification: string | null // the check's result as JSON; null until a check runs createdAt: number updatedAt: number completedAt: number | null personMovedAt: number | null // when a person last moved the card (see PATCH below) // Why the task is waiting on a person; null when the board will move it on // its own. Not returned by the `ready=true` variant. attention: null | { reason: 'failed' | 'timed_out' | 'stopped' | 'needs_review' | 'blocked' | 'unassigned' | 'unstarted' failedRuns: number // consecutive unsuccessful runs at the end of the ledger detail?: string // the last run's error, trimmed, for failed / timed_out } // What the task's runs cost, summed from its execution ledger. Not returned by // the `ready=true` variant. runCost: { usd: number | null // null when no run recorded a cost estimated: boolean // true when any part of usd is an estimate local?: true // present when every counted run ran on a local model, so the task cost nothing } // When the current run started (epoch ms); null when no run is going. Not // returned by the `ready=true` variant. runningSince: number | null }> } ``` `attention` comes from the task's status, its bound agent (the `:agent:` segment of `sourceDelegationId`), its run ledger and its check result. A `blocked` task always has one (`needs_review` when its verdict cannot be promoted, else `timed_out` / `failed` from its last run, else `blocked`). A `todo` task has one only when nothing will fire it: no bound agent (`unassigned`), a last run the user stopped (`stopped`), or three failed runs in a row (`failed` / `timed_out`). An `in_progress` task with no assignee and no running run is `unstarted`: it was moved there by hand, which starts nothing. The board renders every task with an `attention` in its **Needs you** column. `runCost` sums the `costUsd` of the task's run ledger rows. A row with no cost (a run stopped before it priced itself) adds nothing, and `usd` stays null until some run records one. `estimated` is true unless every counted row has `costSource` `'reported'` or `'local'`; a row with a null source counts as an estimate. `local: true` appears only when every counted row is `'local'`, a run on a model the user's own Ollama served, and the board then shows the cost as `free`. A task whose rows carry no cost but whose own `costUsd` is positive predates per-run costs, so that figure is returned with `estimated: true`. **`500 Internal Server Error`**: a database failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} # Ready-to-work tasks for a team curl 'http://localhost:18790/api/board?teamId=<team-id>&ready=true' # All in-progress tasks for a team curl 'http://localhost:18790/api/board?teamId=<team-id>&status=in_progress' ``` *** ## `POST /api/board` Creates a task. `status` defaults to `todo` (ready to be claimed); pass `backlog` to park it. On success the route writes a `task_created` event to the [event log](/reference/rest-api/observability); a failed event write never fails the request. Set `parentTaskId` to make a subtask. This route applies no child-count or depth limit (the Tasks MCP server's `create_subtask` does; see [MCP tools](/reference/mcp-tools)), and it does not copy the parent's team or room. A `parentTaskId` that does not exist fails the database's foreign key and answers **500**. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { title: string // required, 1 to 500 chars description?: string // up to 20000 chars status?: 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'blocked' | 'done' | 'cancelled' priority?: number // integer teamId?: string // non-empty if present assigneeRuntime?: string parentTaskId?: string sourceDelegationId?: string tenantId?: string // non-empty if present assigneeAgentId?: string // create the task FOR this agent (see below) } ``` **With `assigneeAgentId`** (the board's New task dialog), the task is created for one agent: `teamId` is required, the agent must be an active member of that team, and `status` may only be `todo` (the default) or `backlog`. The server writes a `sourceDelegationId` that binds the task to the agent and marks it as assigned by a person, then, for a `todo` task, hands it to the team's engine to run now (the response does not wait for the run to start). The agent receives the title and description together. Its result is recorded on the task (the report comment) and is **not** delivered to the team lead as a task update: a person-assigned task reports to the person, on its card. If the agent is busy, the task waits in `todo` and runs when the agent is free. ### Responses **`400 Bad Request`**: body failed validation (`{ "error": "invalid body", ... }`), or, with `assigneeAgentId`: `team_required`, `agent_not_in_team` (unknown, archived, or on another team), or `invalid_status`: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`200 OK`**: the created task, shaped as a `GET /api/board` row with `attention` and `runCost` (no `runningSince`): ```ts theme={"theme":"github-dark"} { task: { id: string, title: string, status: 'todo' /* ...the rest of the row */ } } ``` **`500 Internal Server Error`**: a database failure, including an unknown `parentTaskId`: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board \ -H 'Content-Type: application/json' \ -d '{"title":"Implement /api/foo","teamId":"<team-id>","priority":1}' # Give one agent a task; it starts right away curl -X POST http://localhost:18790/api/board \ -H 'Content-Type: application/json' \ -d '{"title":"Draft the launch post","description":"Under 200 words.","teamId":"<team-id>","assigneeAgentId":"<agent-id>"}' ``` *** ## `POST /api/board/:taskId/retry` Runs a stuck task again because a person asked: one that failed, timed out, was stopped, or was parked after failing three times in a row. A `blocked`, `backlog` or stranded `in_progress` task (no assignee and no run going) is moved back to `todo` first, which clears its assignee and check result. The route adds a `user` comment `Retry requested.` to the task and hands it straight to its bound agent. The board's automatic limit of three re-runs does not apply to a retry a person asks for. The response does not wait for the run to start. * **Path params**: `taskId`. * **Request body**: none. ### Responses **`202 Accepted`**: the task as it now stands, in `todo`: ```ts theme={"theme":"github-dark"} { ok: true, task: DbTask } ``` **`404 Not Found`**: `{ "ok": false, "error": "task not found" }`. **`409 Conflict`**: `{ "ok": false, "error": "<reason>" }`, where the reason is `not_retryable` (the task is running, finished or cancelled), `no_team`, or `unassigned` (no agent is bound to it; use `assign`). **`500 Internal Server Error`**: `{ "ok": false, "error": "<message>" }`. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<task-id>/retry ``` *** ## `POST /api/board/:taskId/assign` Gives a task that nobody is working on to one member of its team, and starts it. Use it for a card no agent will pick up (created without one, or by an agent's own `create_task`), or to hand a failed task to someone else. The task is bound to the agent as a person's assignment (see `POST /api/board`), moved back to `todo` if it was `blocked`, `backlog` or stranded `in_progress` (no assignee and no run going), noted on its trail (`Assigned to <name>.`) and started. * **Path params**: `taskId`. * **Request body**: `{ agentId: string }`. ### Responses **`200 OK`**: `{ "ok": true, "task": DbTask }`. **`400 Bad Request`**: `{ "ok": false, "error": "agentId required" }`, or `agent_not_in_team` (the agent is unknown, archived or on another team). **`404 Not Found`**: `{ "ok": false, "error": "task not found" }`. **`409 Conflict`**: `no_team`, or `not_assignable` (the task is being worked on, finished, cancelled or dropped). **`500 Internal Server Error`**: `{ "ok": false, "error": "<message>" }`. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<task-id>/assign \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>"}' ``` *** ## `GET /api/board/:taskId` Returns one task with its comments (oldest first) and its ancestors (the chain of parent tasks). * **Path params**: `taskId`. * **Request body**: none. ### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { task: DbTask & { attention; runCost; runningSince } // shape as in GET /api/board comments: Array<{ id: string taskId: string authorAgentId: string | null authorType: 'agent' | 'user' | 'system' body: string tenantId: string | null createdAt: number }> ancestors: Array<{ id: string parent_task_id: string | null title: string status: string }> } ``` **`404 Not Found`**: no such task: ```json theme={"theme":"github-dark"} { "error": "task not found" } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/board/<task-id> ``` *** ## `POST /api/board/:taskId/claim` Claims a `todo` task for one assignee. The claim succeeds only while the task is `todo`, unassigned and not dropped, so at most one caller wins: the task moves to `in_progress`, and every other caller gets **409**. A won claim writes a `task_claimed` event and posts a line to the team's peer room. A **409** means another worker holds the task: do not retry it. A task left `in_progress` by a run that died is released back to `todo` by the server's stale-run cleanup, and a normal claim can then take it. * **Path params**: `taskId`. * **Request body**: ```ts theme={"theme":"github-dark"} { assigneeAgentId: string // required, non-empty assigneeRuntime?: string // e.g. 'claude-code', 'clawboo-native' } ``` ### Responses **`400 Bad Request`**: body failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`200 OK`**: the claim won; the task is now `in_progress` and assigned: ```ts theme={"theme":"github-dark"} { ok: true, task: DbTask } ``` **`404 Not Found`**: the task does not exist (`reason: 'not_found'`): ```json theme={"theme":"github-dark"} { "ok": false, "error": "not_found" } ``` **`409 Conflict`**: the task was not in a claimable state (already claimed, dropped, or not `todo`): ```json theme={"theme":"github-dark"} { "ok": false, "error": "conflict" } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<task-id>/claim \ -H 'Content-Type: application/json' \ -d '{"assigneeAgentId":"native-leader-abc","assigneeRuntime":"clawboo-native"}' ``` *** ## `PATCH /api/board/:taskId` Changes a task's status, edits its `priority`, `title` or `description`, or both. At least one field is required. The status change follows the [status rules](#task-statuses) (an illegal move is **409** `illegal_transition`). A move to `done` is also refused with **409** `verification_required` when the task's stored check result is `fail`, or `completed_with_debt` with a failing last check. The only way past that is `humanOverride: true`, and a `done` with the override writes a `verification` row to the [audit log](/reference/rest-api/governance). This is the route a **person** uses (the board and the task drawer), and a person's move is the final word on the task: * A move to `done` or `cancelled` may carry `completedAt`, the day it really finished. It must fall between the task's creation and now (up to 5 minutes ahead of the server clock is allowed), else **400** `invalid_completed_at`. Without it the finish time is now. Writing the same finished status again never moves the finish time. * A move to `cancelled` also cancels the task's pending (`todo` / `backlog`) dependents, which could otherwise never run, and returns them as `cancelledDependents`. * A move to `backlog`, `todo`, `blocked`, `done` or `cancelled` stops the run working on the task, whether team chat, the task runner or an OpenClaw Routine fire started it, and closes its ledger row as cancelled. A move to `in_progress` or `in_review` stops nothing. A task moved back to `todo` while it was running is started again for its bound agent. * Whoever delegated the task (the `:reflectTo:` agent in its `sourceDelegationId`) is told a person marked it done or cancelled it: through a `[Task Update]` when the team's engine is running, else as a mailbox notice read at its next turn. A task a person assigned reports to nobody. * The move is stamped on the task as `personMovedAt`. Until a run starts after it, the agent's own status writes through the [tasks MCP tools](/reference/mcp-tools) (`update_task_status`, `block_task`, `unblock_task`) are refused with a tool error that names the column the person chose and tells the agent not to retry, so the run the person overruled cannot put the card back. A run that starts after the move writes as usual. > [!NOTE] > > A task with no stored check result is unchecked, not failing, and moves to `done` normally. Most tasks never get a check: only task-runner runs in a git worktree with a non-empty diff are checked (see [Verification](/concepts/verification)). Moving a task to `todo` clears its assignee and check result so it can be claimed again. A status change writes a `status_changed` event and posts `A person moved "<title>" to <Status>.` to the team's peer room. * **Path params**: `taskId`. * **Request body** (at least one field required): ```ts theme={"theme":"github-dark"} { status?: 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'blocked' | 'done' | 'cancelled' priority?: number // integer title?: string // 1 to 500 chars description?: string // up to 20000 chars humanOverride?: boolean // move to done despite a failed check (audited); only with status 'done' completedAt?: number // epoch ms; only with status 'done' or 'cancelled' } ``` ### Responses **`400 Bad Request`**: empty body or failed validation (including `completedAt` without a move to `done` or `cancelled`), or `{ ok: false, error: 'invalid_completed_at' }` for a finish time outside the task's life: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": ["at least one field required"], "fieldErrors": {} } } ``` **`200 OK`**: applied; the updated task, and on a move to `cancelled` the dependents cancelled with it: ```ts theme={"theme":"github-dark"} { ok: true, task: DbTask, cancelledDependents?: Array<{ id: string; title: string }> } ``` **`404 Not Found`**: the task does not exist (`reason: 'not_found'`, or the field update found no row): ```json theme={"theme":"github-dark"} { "ok": false, "error": "not_found" } ``` (or, when only fields were edited and the row vanished:) ```json theme={"theme":"github-dark"} { "error": "task not found" } ``` **`409 Conflict`**: the status transition is illegal or the verification gate blocked `→done`: ```ts theme={"theme":"github-dark"} { ok: false, error: 'illegal_transition' | 'verification_required' } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} # Bump priority + retitle curl -X PATCH http://localhost:18790/api/board/<task-id> \ -H 'Content-Type: application/json' \ -d '{"priority":5,"title":"Updated title"}' # Ship despite a non-promotable verdict (audited) curl -X PATCH http://localhost:18790/api/board/<task-id> \ -H 'Content-Type: application/json' \ -d '{"status":"done","humanOverride":true}' ``` *** ## `POST /api/board/:taskId/comments` Adds a comment to a task (discussion or a system note). `authorType` defaults to `agent`. The route writes a `comment_added` event and posts the first 160 characters to the team's peer room. An unknown `taskId` is not checked first: the insert fails the database's foreign key and the route answers **500** (`FOREIGN KEY constraint failed`). A `user` comment is a person's reply. After the comment is saved, the route delivers it to the agent the task belongs to: the assignee, or else the agent named in the task's `sourceDelegationId` (a task moved back to `todo` keeps that binding). Nothing is delivered when there is no such agent, or when the task is `done`, `cancelled` or dropped. 1. **The mailbox**, always first. The agent's inbox gets `A person commented on your task "<title>": ...` with the comment's first 1000 characters. Every run reads the inbox when it starts. 2. **Then by status:** * `in_progress` with a run working on it: a steer into that run. How it gets in depends on the runtime (see [Choose a runtime](/runtimes/index)). The route waits up to 2.5 seconds for the steer to land, and the steer carries on in the background after that. When it lands, the mailbox row is marked delivered so the agent does not read the comment twice. * `blocked`, `todo`, or `in_progress` with no assignee and no run going: the task is started again, as the retry route above does. A task that is not `todo` moves there first (with a `status_changed` event whose reason is `reply`). If every task it depends on is `done`, the route adds the `system` comment `Started again for a person’s reply.` and dispatches the task to its agent; otherwise the reply waits in the mailbox. A `todo` task that a reply started less than 15 seconds ago is not dispatched again. * `in_review` and `backlog`: the reply waits in the mailbox. A task with no team, or one whose move back to `todo` is refused, is not started again: the reply waits in the mailbox for its next run. * **Path params**: `taskId`. * **Request body**: ```ts theme={"theme":"github-dark"} { body: string // required, 1 to 20000 chars authorAgentId?: string authorType?: 'agent' | 'user' | 'system' // default 'agent' } ``` ### Responses **`400 Bad Request`**: body failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`200 OK`**: the created comment: ```ts theme={"theme":"github-dark"} { ok: true comment: { id: string taskId: string authorAgentId: string | null authorType: 'agent' | 'user' | 'system' body: string tenantId: string | null createdAt: number } forwarded: boolean // true when `delivery` is present and its status is not 'not-delivered' delivery?: // present for a `user` comment only | { status: 'steered'; agentId: string } | { status: 'woke-agent'; agentId: string } | { status: 'queued'; agentId: string; reason: 'next-run' | 'in-review' | 'waiting' } | { status: 'not-delivered' agentId: string | null reason: 'finished' | 'unassigned' | 'failed' } } ``` `delivery` says where a person's reply went. `agentId` is the agent the task belongs to. | `status` | `reason` | Meaning | | - | - | - | | `steered` | | The reply got into the run working on the task | | `woke-agent` | | The task was started again for the reply, or a reply less than 15 seconds earlier already started it | | `queued` | `next-run` | The reply is in the mailbox for the agent's next run, for example on a `backlog` task, or on an `in_progress` task whose steer did not land within 2.5 seconds | | `queued` | `in-review` | The task is `in_review`; the agent reads the reply after the check | | `queued` | `waiting` | The task waits on a dependency that is not `done`, so it cannot start yet | | `not-delivered` | `finished` | The task is `done`, `cancelled` or dropped. The comment is saved and nothing is sent | | `not-delivered` | `unassigned` | No agent has the task, and `agentId` is `null` | | `not-delivered` | `failed` | Writing to the mailbox or starting the task failed. The comment is saved, and the route still answers `200` so the reply is not sent twice | For a `finished` reply, the dashboard offers **Send as a follow-up task**, which calls `POST /api/board` (above) with `title: "Follow up: <title>"`, the reply as `description`, the same `teamId` and `assigneeAgentId`, and the finished task as `parentTaskId`. **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} # An agent's note curl -X POST http://localhost:18790/api/board/<task-id>/comments \ -H 'Content-Type: application/json' \ -d '{"body":"Blocked on the upstream schema.","authorType":"agent","authorAgentId":"native-leader-abc"}' # A person's reply, which answers with `delivery` curl -X POST http://localhost:18790/api/board/<task-id>/comments \ -H 'Content-Type: application/json' \ -d '{"body":"Use the v2 schema; it landed this morning.","authorType":"user"}' ``` *** ## `POST /api/board/:taskId/executions` Opens a row in the task's run ledger, after a successful claim. The server reads the ledger after a restart to find runs that died: a worker that starts work must open a row here and close it with the `PATCH` below. A row left open by a crash is marked `failed` and its task released. A new row writes an `execution_started` event. * **Path params**: `taskId`. * **Request body**: ```ts theme={"theme":"github-dark"} { executorType: string // required, 1 to 100 chars (the runtime id) workspaceId?: string runReason?: string // up to 2000 chars beforeCommit?: string // up to 200 chars (git checkpoint) } ``` ### Responses **`400 Bad Request`**: body failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`404 Not Found`**: the task does not exist: ```json theme={"theme":"github-dark"} { "error": "task not found" } ``` **`200 OK`**: the created execution row (starts in `status: 'running'`): ```ts theme={"theme":"github-dark"} { ok: true execution: { id: string taskId: string workspaceId: string | null executorType: string // The runtime that actually ran, when executorType names an owner instead: the // team engine records every run it starts as 'openclaw'. Null otherwise. runtime: string | null status: 'queued' | 'running' | 'succeeded' | 'failed' | 'timed_out' | 'cancelled' claimedAt: number | null startedAt: number | null completedAt: number | null beforeCommit: string | null afterCommit: string | null inputTokens: number | null outputTokens: number | null cacheRead: number | null cacheWrite: number | null costUsd: number | null // How costUsd was obtained; null when no cost is recorded or the row predates // the column (the board then treats a recorded cost as an estimate). costSource: 'reported' | 'local' | 'token-estimate' | 'char-estimate' | null summary: string | null runReason: string | null error: string | null recoveryTombstone: number // 0 | 1 tenantId: string | null createdAt: number } } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<task-id>/executions \ -H 'Content-Type: application/json' \ -d '{"executorType":"claude-code","beforeCommit":"<sha>"}' ``` *** ## `PATCH /api/board/executions/:execId` Closes a run in the ledger with its outcome, and optionally its tokens and cost. The route writes an `execution_completed` event carrying the run's `taskId`, `teamId`, `agentId` and `runtime`, so it pairs with the `execution_started` event. A closed row never changes again, so only a `running` row can be closed: an unknown `execId`, or one already closed elsewhere, closes nothing and answers `404` without an event. * **Path params**: `execId`. * **Request body**: ```ts theme={"theme":"github-dark"} { status: 'succeeded' | 'failed' | 'timed_out' | 'cancelled' // required summary?: string // up to 20000 chars error?: string // up to 20000 chars afterCommit?: string // up to 200 chars inputTokens?: number // integer outputTokens?: number // integer cacheRead?: number // integer cacheWrite?: number // integer costUsd?: number costSource?: 'reported' | 'local' | 'token-estimate' | 'char-estimate' } ``` A token or cost field the body omits leaves the row's value as it is, so closing a row never erases a cost already recorded on it. These fields take a number or are left out; a `null` fails validation with `400`. A `costUsd` sent without a `costSource` is stored with a null source, which the board counts as an estimate. A close that records a cost also refreshes the task's `costUsd`, the sum of its rows. ### Responses **`400 Bad Request`**: body failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`200 OK`**: the execution was closed: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`404 Not Found`**: no still-`running` execution row matched `execId`, either because the id is unknown or because the row is already terminal (most often the stale sweep closed it as `timed_out`). Nothing was closed and no event was appended, and this 404 is data rather than a transient error to retry: ```json theme={"theme":"github-dark"} { "error": "execution not found" } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PATCH http://localhost:18790/api/board/executions/<exec-id> \ -H 'Content-Type: application/json' \ -d '{"status":"succeeded","inputTokens":1200,"outputTokens":340,"costUsd":0.012}' ``` *** ## `GET /api/board/:taskId/executions` Lists every run in the task's ledger, oldest first. * **Path params**: `taskId`. * **Request body**: none. ### Responses **`200 OK`**: the ledger (`executions[]` is `DbExecutionProcess[]`, shape as in `POST .../executions`): ```ts theme={"theme":"github-dark"} { ok: true, executions: DbExecutionProcess[] } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/board/<task-id>/executions ``` *** ## `POST /api/board/:taskId/deps` Makes `taskId` wait for `dependsOnTaskId`: it is not ready until the other task is `done`. A plan is a chain of these links, and the team's engine starts the next step when the one before it finishes. Both tasks must exist. A new link writes a `dep_linked` event and posts a line to the team's peer room. * **Path params**: `taskId` (the dependent). * **Request body**: ```ts theme={"theme":"github-dark"} { dependsOnTaskId: string // required, non-empty (the blocker) } ``` ### Responses **`400 Bad Request`**: body failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`404 Not Found`**: either the task or the blocker does not exist: ```json theme={"theme":"github-dark"} { "error": "task not found" } ``` **`200 OK`**: the edge was linked (duplicate edges are ignored): ```json theme={"theme":"github-dark"} { "ok": true } ``` **`409 Conflict`**: the edge would close a direct or transitive **dependency cycle**. A cycle can never resolve, so the plan is unworkable rather than the server being at fault: ```json theme={"theme":"github-dark"} { "ok": false, "error": "task_dependency_cycle" } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<step-2-id>/deps \ -H 'Content-Type: application/json' \ -d '{"dependsOnTaskId":"<step-1-id>"}' ``` *** ## `POST /api/board/:taskId/cancel-dependents` Cancels the tasks still waiting (`todo` or `backlog`) behind a failed task, directly or further down the chain. A failed task never becomes `done`, so those tasks could never run; cancelling them shows the stall instead of leaving cards that never become ready. Tasks in any other status (`in_progress`, `in_review`, `blocked`, `done` or `cancelled`) are left alone. Each cancelled task writes a `status_changed` event with `reason: 'blocker_failed'`, and the response lists them so the caller can tell the team lead. * **Path params**: `taskId` (the failed blocker). * **Request body**: none. ### Responses **`404 Not Found`**: the task does not exist: ```json theme={"theme":"github-dark"} { "error": "task not found" } ``` **`200 OK`**: the cancelled dependents (id + title only; may be an empty array): ```ts theme={"theme":"github-dark"} { cancelled: Array<{ id: string; title: string }> } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<failed-task-id>/cancel-dependents ``` *** ## `POST /api/board/:taskId/workspace` Creates a git worktree and a `clawboo/task-<id>` branch for a task that changes files, writes the task files into it, and records the worktree and branch on the task. The task files are `TASK.md`, `task-progress.md`, `DECISIONS.json`, `init.sh` and `VERIFICATION.md` (plus `AGENT_HANDOFF.json` once someone writes a handoff); the other workspace routes read them. The worktree lives outside your repo, at `<clawboo home>/worktrees/<repo hash>/<taskId>`, so it never shows up in the repo's own `git status`. A worktree keeps file changes apart; it is not a sandbox (see [What agents can do](/concepts/permissions)). The task `kind` decides whether a worktree is made: `code` (the default) and any unknown kind get one; `research` and `review` are refused with **422**. Asking again for a task that already has a live worktree reuses it. * **Path params**: `taskId`. * **Request body**: ```ts theme={"theme":"github-dark"} { repoPath: string // required, 1 to 4000 chars: the git repo to branch from baseSha?: string // up to 200 chars: pins the branch point baseRef?: string // up to 400 chars: the branch point (default HEAD) when no baseSha kind?: string // up to 100 chars: the task kind, which decides isolation; default 'code' } ``` ### Responses **`400 Bad Request`**: body failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`404 Not Found`**: the task does not exist (`reason: 'not_found'`): ```json theme={"theme":"github-dark"} { "ok": false, "error": "task not found" } ``` **`422 Unprocessable Entity`**: the task kind gets no worktree (`research` or `review`): ```ts theme={"theme":"github-dark"} { ok: false, error: 'no_isolation', isolation: 'none' } ``` **`200 OK`**: the worktree was provisioned (or an existing one reused): ```ts theme={"theme":"github-dark"} { ok: true worktree: { taskId: string worktreePath: string // absolute path to the checkout branch: string // 'clawboo/task-<id>' baseCommit: string // the commit holding the task files: the diff baseline detached: boolean // false for a task worktree } workspaceId: string isolation: 'worktree' } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<task-id>/workspace \ -H 'Content-Type: application/json' \ -d '{"repoPath":"/path/to/repo","kind":"code"}' ``` *** ## `GET /api/board/:taskId/workspace` Reads the task's workspace row and the state to resume from, rebuilt only from the worktree's task files (`AGENT_HANDOFF.json`, else `task-progress.md` and `init.sh`): no chat history and no board state. A different runtime, or a person, can pick the task up from this alone. * **Path params**: `taskId`. * **Request body**: none. ### Responses **`404 Not Found`**: no workspace row for the task (or it has no recorded worktree path): ```json theme={"theme":"github-dark"} { "error": "workspace not found" } ``` **`200 OK`**: the workspace, the resume state and the parsed handoff (`resume` and `handoff` are `null` when the worktree is paused or no handoff was written): ```ts theme={"theme":"github-dark"} { ok: true workspace: { id: string taskId: string repoPath: string branch: string | null worktreePath: string | null status: 'active' | 'archived' | 'stale' tenantId: string | null createdAt: number lastUsedAt: number | null } resume: { hasHandoff: boolean done: string[] // completed subtasks broken: string[] // broken / unverified items next: string | null // the single next best step whyBlocked?: string | null commands: { init: string; verify: string; start: string } warnings: string[] lastRuntime?: string | null // who wrote the last handoff (may be 'human') nativeSessionId?: string | null } | null handoff: AgentHandoff | null // the parsed AGENT_HANDOFF.json (shape below) } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/board/<task-id>/workspace ``` *** ## `PATCH /api/board/:taskId/workspace` Pauses or completes a task's worktree. * **`pause`**: commits any uncommitted work, removes the worktree and keeps the branch. The workspace stays `active` and can be resumed. * **`complete`** with an empty diff: removes the worktree and branch and moves the task to `done`. There is nothing to check, so the check is skipped. The task files do not count toward the diff, so a run that only updated its own progress or handoff is still empty. * **`complete`** with a non-empty diff: moves the task to `in_review` and runs the check. Only a `pass` moves it to `done`. A `fail` with attempts left moves it back to `in_progress`. A `fail` on the last attempt, or `completed_with_debt`, moves it to `blocked` with a system comment and a mailbox notice to whoever delegated it. A repeat `complete` on a `blocked` task does not run the check again; it returns the current status with `verified: 'fail'`. Through this route the check is the task's own verify command only; the separate reviewer pass never runs. The command is `VERIFY_CMD` in the worktree's `init.sh` (else the `Verify:` line in `VERIFICATION.md`). The task files start with a placeholder command, which counts as no command, so the check fails until a real one is set. If a person moves the task to `done` or `cancelled` while the check runs, their move stands and the result is only recorded. See [Verification](/concepts/verification). * **Path params**: `taskId`. * **Request body**: ```ts theme={"theme":"github-dark"} { action: 'pause' | 'complete' // required } ``` ### Responses **`400 Bad Request`**: body failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`404 Not Found`**: no workspace row (or it has no worktree path / branch): ```json theme={"theme":"github-dark"} { "ok": false, "error": "workspace not found" } ``` **`200 OK` (`pause`)**: committed-or-not + the resulting HEAD sha: ```ts theme={"theme":"github-dark"} { ok: true, action: 'pause', pause: { committed: boolean, head: string } } ``` **`200 OK` (`complete`)**: the diff outcome, the resulting task status, and (when the gate ran on a dirty diff) the verification verdict: ```ts theme={"theme":"github-dark"} { ok: true action: 'complete' complete: { dirty: boolean // any change vs baseline diffStat: { filesChanged: number, insertions: number, deletions: number, dirty: boolean } cleaned: boolean // empty-diff worktree auto-removed } taskStatus: string // 'done' | 'in_review' | 'in_progress' | 'blocked' | … verified?: 'pass' | 'fail' | 'completed_with_debt' // present only on a dirty-diff complete } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} # Pause (resumable) curl -X PATCH http://localhost:18790/api/board/<task-id>/workspace \ -H 'Content-Type: application/json' \ -d '{"action":"pause"}' # Complete (runs the check when the diff is not empty) curl -X PATCH http://localhost:18790/api/board/<task-id>/workspace \ -H 'Content-Type: application/json' \ -d '{"action":"complete"}' ``` *** ## `POST /api/board/:taskId/workspace/handoff` Writes `AGENT_HANDOFF.json` into the task's worktree: structured notes on what was done, what is broken and what to do next, so a different runtime or a person can pick the task up. When `timestamp` is left out, the server fills in the current ISO-8601 time. * **Path params**: `taskId`. * **Request body** (`timestamp` defaulted server-side): ```ts theme={"theme":"github-dark"} { handoffFrom: string // required, non-empty: who is handing off runtime: string // required, non-empty: the runtime that produced it (may be 'human') timestamp?: string // ISO-8601; defaulted server-side when omitted completedSubtasks?: string[] // default [] brokenOrUnverified?: string[] // default [] nextBestStep?: string // default '' whyBlocked?: string | null commands?: { init?: string; verify?: string; start?: string } // defaults: init './init.sh', verify '', start '' evidence?: { testResults?: string | null; lintResults?: string | null } warnings?: string[] // default [] nativeSessionId?: string | null // same-runtime resume handle roomCursor?: { roomId: string; lastSeenSeq: number } | null } ``` ### Responses **`400 Bad Request`**: the handoff failed validation: ```json theme={"theme":"github-dark"} { "error": "invalid handoff", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`404 Not Found`**: no workspace row (or it has no worktree path): ```json theme={"theme":"github-dark"} { "ok": false, "error": "workspace not found" } ``` **`200 OK`**: the handoff was written: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/board/<task-id>/workspace/handoff \ -H 'Content-Type: application/json' \ -d '{ "handoffFrom":"native-specialist-abc", "runtime":"clawboo-native", "completedSubtasks":["wired the route"], "brokenOrUnverified":["the e2e test is flaky"], "nextBestStep":"stabilize the flaky test" }' ``` *** ## `GET /api/board/:taskId/workspace/detail` The task drawer's workspace view: the contents of the task files that exist (`TASK.md`, `task-progress.md`, `DECISIONS.json`, `init.sh`, `VERIFICATION.md`, `AGENT_HANDOFF.json`), plus the unified diff and diff stats against the starting commit, with the task files left out of the diff. * **Path params**: `taskId`. * **Request body**: none. ### Responses **`404 Not Found`**: no workspace row (or it has no worktree path): ```json theme={"theme":"github-dark"} { "error": "workspace not found" } ``` **`200 OK`**: the task files and the diff (`diffStat` and `diff` are empty when the worktree is paused or git is unavailable): ```ts theme={"theme":"github-dark"} { ok: true workspace: DbWorkspace // shape as in GET .../workspace sorFiles: Record<string, string> // task file contents, keyed by file name diffStat: { filesChanged: number, insertions: number, deletions: number, dirty: boolean } | null diff: string // unified diff without the task files; '' when unavailable } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/board/<task-id>/workspace/detail ``` *** ## The workspace file view Four read-only routes behind an agent's **Workspace** tab. They serve a task's worktree: one directory level, one file, one file's diff, and the git status behind the change badges. Nothing here writes. The worktree path comes from the stored workspace row, never from the request. Every relative path is kept inside the worktree in three ways: absolute paths and `..` segments are refused; the resolved path must sit inside the root (so a sibling such as `<root>-evil` cannot pass as `<root>`); and symlinks are refused outright, with the path checked again after resolving any symlinked parent folder. Backslashes, control characters and paths over 1024 characters are refused too. A refused path answers **400** `{ "ok": false, "error": "<what is wrong>" }`. When the workspace itself cannot be read, all four answer `{ "ok": false, "error": "<message>", "reason": "<reason>" }`: | Status | `error` | `reason` | Meaning | | - | - | - | - | | `404` | `workspace not found` | `not_found` | No workspace row, or the row has no worktree path | | `404` | `workspace not found` | `gone` | The row exists but the checkout is not on disk (paused or cleaned up; the branch remains) | | `409` | `workspace unreadable` | `unreadable` | The checkout is on disk but git refuses it (moved repo, corrupt index) | `unreadable` is not a 404 on purpose: a client that read a git failure as "no workspace" would show a changed worktree as clean. ### `GET /api/board/:taskId/workspace/tree` One directory level, for a tree that expands on demand. `.git`, symlinks and special files are left out; folders sort before files, then by name, ignoring case. * **Query**: `dir` (relative to the worktree; empty or left out lists the root). * **`200 OK`**: `{ ok, dir, entries: [{ name, kind: "file" | "dir", size? }], truncated }`. Only files carry `size`. At most 500 entries; `truncated` says the limit was hit. ### `GET /api/board/:taskId/workspace/file` One file's contents, up to 256 KB. * **Query**: `path` (relative to the worktree). * **`200 OK`** (text): `{ ok, path, binary: false, size, content, truncated }`. A cut falls on a character boundary, so a truncated file never ends in a broken character. * **`200 OK`** (binary): `{ ok, path, binary: true, size }`. A NUL byte in the first 8 KB marks the file as binary, and no bytes are returned. ### `GET /api/board/:taskId/workspace/file-diff` The unified diff for one file against the task's starting commit. * **Query**: `path` (relative to the worktree). A folder is refused with **400** `Not a file.`, since a git path match would otherwise return everything under it. * **`200 OK`**: `{ ok, path, diff, truncated }`, capped at 1 MB. The diff is empty for an untracked file; show the file's contents instead. ### `GET /api/board/:taskId/workspace/status` `git status --porcelain -z --untracked-files=all` for the worktree, parsed. * **`200 OK`**: `{ ok, branch, entries: [{ path, x, y, origPath? }], truncated }`. `x` is the index column and `y` the worktree column; `origPath` is the source of a rename or copy. At most 2000 entries. ### Example ```bash theme={"theme":"github-dark"} curl 'http://localhost:18790/api/board/<task-id>/workspace/tree?dir=src' curl 'http://localhost:18790/api/board/<task-id>/workspace/file?path=src/index.ts' ``` *** ## `GET /api/board/:taskId/preview` and `GET /api/board/:taskId/preview/<path>` Serves the files in a task's worktree as a static website, so the task drawer's **Preview** can show the page an agent built next to the diff. It only serves files: it never runs a build or a dev server. The drawer checks for a preview every 10 seconds while it is open. * `GET /api/board/:taskId/preview` and `GET /api/board/:taskId/preview/<path>` serve the same tree. A folder (including the bare route) serves its `index.html`. A folder URL without a trailing slash answers `302` to the same URL with a `/` added (the base path and query are kept), so the page's relative links resolve. * Paths follow the same rules as the [workspace file view](#the-workspace-file-view): no absolute paths, `..`, symlinks, backslashes or control characters, and at most 1024 characters. * `Content-Type` comes from a fixed list: `html`/`htm`, `css`, `js`/`mjs`, `json`/`map`, `txt` and `md` (as `text/plain`), `svg`, `png`, `jpg`/`jpeg`, `gif`, `webp`, `avif`, `ico`, `woff`/`woff2`, `ttf`, `otf`, `mp4`, `webm`, `wasm`. Anything else is sent as `application/octet-stream`, which a browser downloads rather than runs. * Every file is sent with `Content-Length`, `Cache-Control: no-store` and `X-Content-Type-Options: nosniff`, and streamed. * The routes sit under `/api`, so the same-origin guard and the access gate apply, and the dashboard's access cookie reaches the preview frame. The drawer shows the page in a frame that runs no scripts, so a script-driven page appears as its static shell. The **Open** link shows the same URL in a new tab without that restriction: there the page's scripts run in the dashboard's origin and can call this API, so open it only for work you trust. ### Responses **`200 OK`**: the file's bytes. **`302 Found`**: a folder requested without a trailing slash. **`400 Bad Request`**: a refused path, or a missing file. The message names the problem, for example: ```json theme={"theme":"github-dark"} { "ok": false, "error": "No such file in the workspace." } ``` **`404 Not Found`**: `{ "ok": false, "error": "<message>" }`, where the message is `workspace not_found` or `workspace gone` (no usable worktree), `No index.html in that directory.`, or `Not a file.` **`413 Payload Too Large`**: a file over 8 MB: `{ "ok": false, "error": "That file is too large to preview." }` **`500 Internal Server Error`**: `{ "error": "<message>" }` (or `preview read failed` when the file cannot be read). ### Example ```bash theme={"theme":"github-dark"} # The worktree's index.html (follow the redirect to preview/) curl -L http://localhost:18790/api/board/<task-id>/preview # One asset curl -I http://localhost:18790/api/board/<task-id>/preview/dist/app.css ``` *** ## Error envelope Most error bodies are `{ "error": "<message>" }`. The exceptions: * Validation `400`s add `details` (the zod `flatten()` output): `{ "error": "invalid body", "details": {...} }`, or `invalid handoff` on the handoff route. * `{ "ok": false, "error": "<reason>" }` comes from: the claim (`not_found`, `conflict`); a status change on `PATCH /api/board/:taskId` (`not_found`, `illegal_transition`, `verification_required`, and `invalid_completed_at` as a `400`); every retry and assign failure (`task not found`, `agentId required`, `not_retryable`, `no_team`, `unassigned`, `agent_not_in_team`, `not_assignable`); the dependency cycle (`409 task_dependency_cycle`); the provision route (`task not found`, and `no_isolation` with `isolation`); and the pause, complete and handoff `404`s (`workspace not found`). * The workspace file view adds `reason` to its misses and answers a refused path with `{ "ok": false, "error": "<message>" }`. * The preview routes answer `{ "ok": false, "error": "<message>" }` on `400`, `404` and `413`. * `GET /api/board/:taskId/workspace` and `/workspace/detail` answer a missing workspace with a bare `{ "error": "workspace not found" }`. An unknown `taskId` on a comment, or an unknown `parentTaskId` on a create, is a `500` from the database's foreign key, not a `404`. ## Related * [The board](/concepts/the-board): statuses, claims, dependencies and how stuck tasks are recovered. * [Worktrees and handoff](/concepts/worktrees-and-handoff): the task files and `AGENT_HANDOFF.json` across runtimes. * [Verification](/concepts/verification): which tasks are checked, and what `completed_with_debt` means. * [Runtimes API](/reference/rest-api/runtimes): `POST /api/runtimes/:id/run` claims one of these tasks and runs it. * [Database schema](/reference/database-schema): the `tasks`, `task_deps`, `task_comments`, `workspaces` and `execution_processes` tables. # Boo Zero API Source: https://docs.claw.boo/reference/rest-api/boo-zero REST reference for Boo Zero's team briefs, global brief and display name, and the override that picks which agent is Boo Zero. Boo Zero is your team leader: one agent that coordinates every team. These routes store the briefs written for it, a display name the dashboard shows for it, and the override that decides which agent is Boo Zero. Use this page when you script team setup or move the leader role to another agent. Examples use the default port `18790` and no access token. With `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each `curl`. All nine routes are on the general rate limit described in the [REST API overview](/reference/rest-api/index). Errors use `{ "error": "<message>" }`. ## Routes | Method | Path | Summary | | - | - | - | | GET | `/api/boo-zero/team-briefs/:teamId` | Read a team's brief | | PUT | `/api/boo-zero/team-briefs/:teamId` | Save a team's brief | | DELETE | `/api/boo-zero/team-briefs/:teamId` | Remove a team's brief | | GET | `/api/boo-zero/global-brief` | Read the global brief | | PUT | `/api/boo-zero/global-brief` | Save the global brief | | GET | `/api/boo-zero/display-name/:agentId` | Read the display name saved for an agent | | PUT | `/api/boo-zero/display-name/:agentId` | Save or clear that display name | | GET | `/api/boo-zero/override` | Read the leader override and the agent that is Boo Zero now | | POST | `/api/boo-zero/override` | Make any agent Boo Zero, or clear the override | ## Briefs A brief is markdown. A missing brief reads as `content: null`, not as a `404`; the dashboard then shows a generated default. | Brief | Stored in | Where it reaches a model | | - | - | - | | Team | The `boo_zero_team_briefs` table, deleted with the team | Only in Boo Zero's 1:1 chat, when a message starts with `@TeamName`: the brief rides in that team's summary | | Global | The `settings` table, key `boo-zero:global-brief` | Nowhere: it is stored and shown in its editor, and no chat or task sends it to a model | The dashboard edits the team brief from the team chat's settings sheet and the global brief from Boo Zero's own page; see [Boo Zero](/using/boo-zero). ### `GET /api/boo-zero/team-briefs/:teamId` #### Responses **`200 OK`**: the stored brief, or `null`s when there is none: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { content: string | null, updatedAt: number | null } ``` **`400 Bad Request`**: `{ "error": "teamId required" }`. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/boo-zero/team-briefs/<team-id> ``` ### `PUT /api/boo-zero/team-briefs/:teamId` Creates or replaces the team's brief. * **Request body**: `{ "content": string }` (required). #### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { content: string, updatedAt: number } ``` **`400 Bad Request`**: `{ "error": "teamId required" }`, or `{ "error": "content (string) required" }` when `content` is missing or not a string. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. A `teamId` that names no team fails here too, because a brief must belong to a team. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X PUT http://localhost:18790/api/boo-zero/team-briefs/<team-id> \ -H 'Content-Type: application/json' \ -d '{"content":"# Growth team\n\nShip the launch post by Friday."}' ``` ### `DELETE /api/boo-zero/team-briefs/:teamId` Removes the team's brief. Removing a brief that does not exist still answers `200`. Deleting the team removes its brief too. #### Responses **`200 OK`**: `{ "ok": true }`. **`400 Bad Request`**: `{ "error": "teamId required" }`. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X DELETE http://localhost:18790/api/boo-zero/team-briefs/<team-id> ``` ### `GET /api/boo-zero/global-brief` #### Responses **`200 OK`**: the stored brief, or `null` content when unset. `updatedAt` is always `null` on this route: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { content: string | null, updatedAt: null } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/boo-zero/global-brief ``` ### `PUT /api/boo-zero/global-brief` Creates or replaces the global brief. * **Request body**: `{ "content": string }` (required). #### Responses **`200 OK`**: `updatedAt` is the time of the save: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { content: string, updatedAt: number } ``` **`400 Bad Request`**: `{ "error": "content (string) required" }`. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X PUT http://localhost:18790/api/boo-zero/global-brief \ -H 'Content-Type: application/json' \ -d '{"content":"# Global brief\n\nKeep every team focused on the launch."}' ``` ## Display name A name the dashboard shows for Boo Zero instead of the agent's own name, saved per agent id in the `settings` table (key `boo-zero:display-name:<agentId>`). It is removed when the agent is deleted. ### `GET /api/boo-zero/display-name/:agentId` #### Responses **`200 OK`**: the saved name, or `null` when none was ever saved. A cleared name reads as `""`; treat an empty name as unset: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { name: string | null } ``` **`400 Bad Request`**: `{ "error": "agentId required" }`. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/boo-zero/display-name/<agent-id> ``` ### `PUT /api/boo-zero/display-name/:agentId` Saves the name, trimmed and cut to 80 characters. An empty string clears it. * **Request body**: `{ "name": string }` (required). #### Responses **`200 OK`**: the value actually stored: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { name: string } ``` **`400 Bad Request`**: `{ "error": "agentId required" }`, or `{ "error": "name (string) required" }` when `name` is missing or not a string. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X PUT http://localhost:18790/api/boo-zero/display-name/<agent-id> \ -H 'Content-Type: application/json' \ -d '{"name":"Boo Zero"}' ``` ## Leader override Clawboo picks Boo Zero in this order: 1. **The override**: an agent you named with `POST /api/boo-zero/override`, on any runtime, if it still exists and is not archived. 2. **Clawboo Native's Boo Zero**: the team-less Boo Zero agent Clawboo creates on Clawboo Native once a team has a Clawboo Native member. 3. **The OpenClaw default**: among OpenClaw agents with no team, the Gateway's default agent if it is one of them, else the first of them. The dashboard has no control for the override. It sets one in a single case: when you deploy a team on Codex and neither an override nor a Clawboo Native Boo Zero exists, the team's lead becomes the override. ### `GET /api/boo-zero/override` #### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { overrideAgentId: string | null // null when unset or cleared effective: { id: string; name: string } | null // the agent that is Boo Zero now tier: 'override' | 'native' | 'openclaw' | null // which rule picked it } ``` `tier` reads `override` whenever an override is saved, even when that agent cannot be resolved (it was deleted or archived) and `effective` has fallen through to the next rule. **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/boo-zero/override ``` ### `POST /api/boo-zero/override` Sets or clears the override. Any runtime is allowed. The agent must exist and not be archived; clearing restores the order above. * **Request body**: `{ "agentId": string | null }` (required: a non-empty string to set, `null` to clear). #### Responses **`200 OK`**: saved, with the agent that is Boo Zero after the change: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true overrideAgentId: string | null effective: { id: string; name: string } | null } ``` **`400 Bad Request`**: the body has no `agentId` key: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "agentId is required (string to set, null to clear)" } ``` **`400 Bad Request`**: `agentId` is neither `null` nor a non-empty string: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "agentId must be a non-empty string or null" } ``` **`404 Not Found`**: no live agent has that id: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "agent not found (or archived)" } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Make an agent on any runtime Boo Zero curl -X POST http://localhost:18790/api/boo-zero/override \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>"}' # Clear the override curl -X POST http://localhost:18790/api/boo-zero/override \ -H 'Content-Type: application/json' \ -d '{"agentId":null}' ``` **Source of truth:** `apps/web/server/api/booZero.ts`, `apps/web/server/api/booZeroOverride.ts`, and the resolution order in `apps/web/server/lib/teamChat/booZero.ts`. ## Related * [Boo Zero](/using/boo-zero): where the dashboard edits the briefs and the display name * [Teams API](/reference/rest-api/teams): team rules, rooms, and the team activity summary * [Agents API](/reference/rest-api/agents): `defaultId` in `GET /api/agents` is the agent this page calls Boo Zero * [Agent model](/concepts/agent-model): Boo Zero and a team's own Leader # Capabilities API Source: https://docs.claw.boo/reference/rest-api/capabilities REST reference for the capability inventory: list every runtime's skills, tools and connectors, install or toggle manageable ones, and answer approvals. These two routes serve the [capability inventory](/concepts/capabilities): one merged list of every skill, tool and connector across the five runtimes and Clawboo's own connectors. `GET /api/capabilities` is the read behind the Org chart and the **Capabilities** panel. `POST /api/capabilities/:action` installs a curated skill or connector, turns a manageable capability on or off, or answers a pending tool-call approval. The read asks six sources (`native`, `hermes`, `claude-code`, `codex`, `openclaw`, and `connector` for the MCP connectors Clawboo runs itself) and merges their answers. A source that answers has its rows saved; a source that fails is served from its last saved rows, so a dropped Gateway connection never empties the inventory. The response also says how each source read went. > [!NOTE] > > What you can do to a capability depends only on its `manageability` tier. `managed` and `external-write` rows are writable; `observe-only` rows (runtime built-ins, outside command-line tools) are read-only and refuse `enable` and `disable` with **422**. A `runtime-of-record` row can also be read-only when its source marks it `writable: false`. See [Capabilities](/concepts/capabilities) for the tiers. Both routes are on the general rate tier (see [rate limits](/reference/rest-api/index#rate-limits)). ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/capabilities` | List the merged capability inventory (filterable) | No | | POST | `/api/capabilities/:action` | `install` \| `enable` \| `disable` \| `approve` | No | *** ## `GET /api/capabilities` Returns the merged records and a read status for every source. When two records share an `id`, a fresh one wins over a saved one. Filters apply after the merge, so an unknown filter value returns an empty `records` list, not an error. This read also writes. It saves every source that answered, and it stamps grant details onto connector records: it creates the owner grant for a connector an agent's own runtime already attaches (create-only, so a grant you revoked stays revoked), and it adds a read-only copy of a connector record for each other agent that holds a grant on it (`synthetic: true`). * **Path params**: none. * **Query params**: | Param | Type | Effect | | - | - | - | | `runtime` | string | Keep records whose `runtime` matches (e.g. `clawboo-native`, `hermes`, `openclaw`). | | `kind` | string | Keep records whose `kind` matches (`skill` \| `tool` \| `connector`). | | `scope` | string | Keep records whose `scope` matches (`team` \| `agent` \| `global`). | | `agentId` | string | Keep records whose `agentId` matches. | Each param is applied only when present and non-empty; omit all four for the full inventory. * **Request body**: none. ### Responses **`200 OK`**: the merged view: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { records: Array<{ id: string // source-namespaced `${sourceId}:${rawKey}` sourceKey: string // natural id inside the owning store kind: 'skill' | 'tool' | 'connector' runtime: 'openclaw' | 'clawboo-native' | 'claude-code' | 'codex' | 'hermes' | 'human' scope: 'team' | 'agent' | 'global' agentId: string | null // null for team/global scope source: | 'brokered-mcp' | 'curated-skill' | 'filesystem-skill-md' | 'mcp-connector' | 'runtime-builtin' | 'openclaw-extension' | 'external-vendor-cli' manageability: 'managed' | 'external-write' | 'runtime-of-record' | 'observe-only' name: string description: string availability: | { auth: string } | { config: string } | { env: string } | { plugin: string } | { allOf: unknown[] } | { anyOf: unknown[] } | null // null when always-available available: boolean // the requirement is met; false renders the row greyed out diagnostics: string[] // why unavailable (e.g. ['auth-missing:openai']) provenance: { signerId?: string; signature?: string; signedAt?: number } | null status: 'ready' | 'disabled' | 'manageable-but-pending-auth' | 'unavailable' writable?: boolean // false: no Enable or Disable action; defaults to true hint?: string // a hint from the source, e.g. the command that signs it in tenantId: string | null // always null syncedAt: string // ISO timestamp of the read // Connector and grant fields (present on connector records, absent elsewhere) connectorId?: string | null // the connector this record came from grantId?: string | null // the grant that lets this agent use it grantState?: 'proposed' | 'active' | 'suspended' | 'revoked' | 'expired' grantMode?: 'read' | 'write' | 'admin' grantCount?: number // how many agents hold a grant on it health?: 'unknown' | 'ok' | 'needs-auth' | 'degraded' | 'error' | 'drift' healthDetail?: string | null // one line on a health other than ok lastUsedAt?: string | null // ISO timestamp of the last successful use pendingApprovals?: number // calls under this grant waiting for a person trifecta?: { readsPrivateData: boolean; ingestsUntrustedContent: boolean; canEgress: boolean } synthetic?: true // a copy for an agent that holds a grant; it is not a stored row }> sources: Array<{ sourceId: 'native' | 'hermes' | 'claude-code' | 'codex' | 'openclaw' | 'connector' ok: boolean degraded: boolean reason?: string // e.g. 'gateway_disconnected' | 'home_missing' at: number // epoch ms }> } ``` `sources` reports how each source read went. A source with `ok: false, degraded: true` did not answer; its records are the last saved rows, served unchanged so the inventory stays filled through a disconnect. `health: 'drift'` means the connector's tool list no longer matches the one that was approved: re-check what changed before trusting it again. `trifecta` marks which of three risks a connector brings: reading private data, taking in untrusted content, and sending data out. All three together is the combination to watch. **`500 Internal Server Error`**: any failure loading the inventory (the message is redacted): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Full inventory curl http://localhost:18790/api/capabilities # Only Hermes connectors curl 'http://localhost:18790/api/capabilities?runtime=hermes&kind=connector' ``` *** ## `POST /api/capabilities/:action` One route for four actions, chosen by the `:action` segment. `install` adds a curated skill or connector to an agent; `enable` and `disable` turn an existing manageable capability on or off by its `id`; `approve` answers a pending tool-call approval. `install`, `enable` and `disable` go to the source that owns the capability, which also writes the audit record. `approve` updates the approval row directly. * **Path params**: `action` (one of `install` | `enable` | `disable` | `approve`; any other value returns **400**). * **Request body**: shape depends on `:action` (below). ### Action: `install` Installs a skill or connector on an agent. Send the install spec as the body, or nested under `spec`. The agent must exist (an unknown `agentId` is a **404**). The server takes the agent's runtime from its own record, so the `runtime` you send is replaced before the write. **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { // accepted as the top-level body OR nested under `spec` via: 'native' | 'hermes' | 'claude-code' | 'codex' | 'openclaw' | 'connector' // the source that does the write; 'connector' always answers 422 agentId: string runtime: string // required, but replaced with the agent's own runtime kind: 'skill' | 'tool' | 'connector' name: string mcpServer?: { name: string; transport: 'stdio' | 'http'; command?: string; args?: string[]; env?: Record<string,string>; url?: string } skillContent?: string // SKILL.md content tenantId?: string | null } ``` With `via: 'native'`, `name` and `skillContent` are scanned for prompt injection, and any `mcpServer` command, arguments and environment are scanned more strictly. A blocked install writes an `install` audit row and answers **500** with `Error: capability blocked: injection / supply-chain finding`. **`400 Bad Request`**: the body is not a valid install spec (`via`, `agentId`, `runtime`, `kind` or `name` missing or not a string): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "install requires a valid spec { via, agentId, runtime, kind, name }" } ``` **`404 Not Found`**: no agent with that `agentId`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "agent not found" } ``` **`200 OK`**: the capability was installed; the fresh record is returned (or `null` for an acknowledgement that yields no new row): ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true, record: CapabilityRecord | null } ``` **`422 Unprocessable Entity`**: the owning source does not take this write for its tier (for example `via: 'connector'`, or an `observe-only` source): ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { error: "<message>", manageability: 'managed' | 'external-write' | 'runtime-of-record' | 'observe-only' } ``` **`503 Service Unavailable`**: an install through OpenClaw while the Gateway connection is down: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gateway_disconnected" } ``` ### Action: `enable` / `disable` Turns an existing capability on or off by its `id`. The route looks the row up first and applies the same tier the panel shows: an `observe-only` capability, or one its source marked `writable: false`, is refused with **422** before anything is written. **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { id: string } // the capability id, e.g. "native:tool_registry:echo" ``` **`400 Bad Request`**: missing `id`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "enable requires { id }" } ``` (For `disable`, the message reads `disable requires { id }`.) **`404 Not Found`**: no capability row with that `id`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "capability not found" } ``` **`422 Unprocessable Entity`**: the capability cannot be modified (`observe-only`, or `writable === false`): ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { error: 'capability cannot be modified', manageability: 'managed' | 'external-write' | 'runtime-of-record' | 'observe-only', writable: boolean } ``` **`503 Service Unavailable`**: the tier allowed the write, but the owning source is OpenClaw and the Gateway connection is down: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gateway_disconnected" } ``` **`200 OK`**: the toggle was applied; the updated record is returned (or `null`): ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true, record: CapabilityRecord | null } ``` ### Action: `approve` Answers a pending tool-call approval: the same `tool_call_approvals` rows the approval cards on the board and in chat answer. Answering a row that is already resolved changes nothing and returns the row as it is. An `allow_always` on an approval tied to a connector grant also saves a standing rule that allows that tool, with any arguments, for 30 days, unless the prompt marked the call as one that cannot be remembered (`neverRemember`). This action only updates the row. An approval of `kind: 'exec'` (an OpenClaw shell command the Gateway is holding) is not released by it; answer those with `POST /api/tools/approvals/:id/resolve` (see [Tools and MCP](/reference/rest-api/tools-and-mcp)), which also tells the Gateway. **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { id: string // approval id decision: 'allow_once' | 'allow_always' | 'deny' } ``` **`400 Bad Request`**: missing `id` or a `decision` outside the allowed set: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "approve requires { id, decision }" } ``` **`404 Not Found`**: no approval row with that `id`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "approval not found" } ``` **`200 OK`**: the approval was resolved; the row is returned: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true, approval: { id: string toolName: string agentId: string | null argsSummary: string | null // scrubbed JSON reason: string | null status: 'pending' | 'allow_once' | 'allow_always' | 'deny' | 'expired' taskId: string | null tenantId: string | null grantId: string | null // the connector grant the call ran under connectorId: string | null neverRemember: 0 | 1 // 1: an "Always" answer saves no standing rule ruleReason: string | null // why the call needed approval, e.g. 'lethal-trifecta' toolClass: string | null // 'read' | 'write' | 'destructive' toolSummary: string | null // the tool's own one-line description kind: 'tool' | 'exec' // exec = an OpenClaw shell command held by the Gateway createdAt: number // epoch ms expiresAt: number // epoch ms resolvedAt: number | null // epoch ms } } ``` ### Catch-all **`400 Bad Request`**: `:action` is not one of the four known actions: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "unknown action: <action>" } ``` **`404 Not Found`**: the write named an unknown source or a capability id that cannot be parsed: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` **`422 Unprocessable Entity`**: the owning source does not take this write for its tier: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { error: "<message>", manageability: 'managed' | 'external-write' | 'runtime-of-record' | 'observe-only' } ``` **`503 Service Unavailable`**: the write needed the OpenClaw Gateway and its connection is down: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gateway_disconnected" } ``` **`500 Internal Server Error`**: any other failure (the message is redacted): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Examples ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Install a curated skill on a native agent (the server replaces `runtime` with the agent's own) curl -X POST http://localhost:18790/api/capabilities/install \ -H 'Content-Type: application/json' \ -d '{"via":"native","agentId":"native-leader-ab12cd","runtime":"clawboo-native","kind":"skill","name":"web_search"}' # Disable a manageable capability curl -X POST http://localhost:18790/api/capabilities/disable \ -H 'Content-Type: application/json' \ -d '{"id":"native:tool_registry:delete_path"}' # Answer a pending tool-call approval curl -X POST http://localhost:18790/api/capabilities/approve \ -H 'Content-Type: application/json' \ -d '{"id":"<approval-uuid>","decision":"allow_once"}' ``` *** ## Error envelope Most error bodies are `{ "error": "<message>" }`. The two tier `422`s add `manageability`, and the `enable` and `disable` check also adds `writable`, so a client can show the read-only state without a second lookup. A `503` means the tier allowed the write but the owning source could not reach its runtime: retry after the Gateway reconnects. A `500` message is redacted before it is sent. ## Related * [Capabilities](/concepts/capabilities): the inventory and the manageability tiers. * [Capabilities panel](/using/capabilities): the dashboard view over these routes. * [Connectors API](/reference/rest-api/connectors): connect and disconnect connectors, and manage grants. * [Tools and MCP API](/reference/rest-api/tools-and-mcp): the brokered tools, and the approval queue `approve` answers. * [Governance API](/reference/rest-api/governance): delegation approvals use the same `tool_call_approvals` table. * [@clawboo/capability-registry](/reference/packages/index#capability-registry): the `CapabilityRecord` type and the source interface. # Catalog API Source: https://docs.claw.boo/reference/rest-api/catalog REST reference for the marketplace catalog the dashboard browses: the index of agent and team templates, and the files behind each entry. The agent and team templates the **Marketplace** browses, served as an index plus one document set per entry. Use this page when you build a catalog view of your own or check what a template deploys. These routes read files, not the database, and they never return an empty catalog. Examples use the default port `18790` and no access token. With `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each `curl`. All three routes are on the general rate limit described in the [REST API overview](/reference/rest-api/index). ## Routes | Method | Path | Summary | | - | - | - | | GET | `/api/catalog/index` | Browse rows for every agent, team and pack | | GET | `/api/catalog/agents/:id` | One agent template's files | | GET | `/api/catalog/teams/:id` | One team template's workflow and routing | ## Where the catalog comes from The server merges a built-in pack with the packs it can fetch: 1. **The built-in pack** (15 agents, 5 teams) is compiled into the server and always included, so first-run onboarding works offline. 2. **The other packs** come from the first source that exists: a local `catalog/dist/v1/` folder (a repo checkout), then the URL in `CLAWBOO_CATALOG_INDEX_URL`, then `catalog/dist/v1/index.json` on the `main` branch of the `clawboo/clawboo` repository on GitHub. Each pack's bytes are checked against the `sha256` digest the index lists before they are parsed, and a pack that fails the check is skipped. A pack this build cannot read is dropped, and an index newer than this build understands (`schemaVersion` above 1) is ignored. Fetched packs are cached on disk under `<clawboo home>/catalog/` and checked again each time they are read. The merged result is cached in memory for 6 hours. If the remote cannot be reached, the index holds only the built-in pack, and that result is cached for 6 hours too; restart the server to try again sooner. For the pack format and how content is published, see the [Marketplace catalog reference](/reference/marketplace-catalog). ## `GET /api/catalog/index` Browse rows only: names, descriptions and tags, never an agent's files. ### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { schemaVersion: number counts: { agents: number teams: number } agents: Array<{ id: string packId: string source: string name: string role: string emoji: string color: string description: string category: string tags: string[] skillIds: string[] suggestedRuntime?: string }> teams: Array<{ id: string packId: string source: string name: string emoji: string color: string description: string category: string tags: string[] agentIds: string[] defaultRuntime?: string }> packs: Array<{ publisher: string slug: string id: string version: string offline?: true // only on the compiled-in built-in pack }> } ``` A fetched copy of the built-in pack, once verified, replaces the compiled-in row, so `offline: true` means the server is using the copy it shipped with. ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/catalog/index ``` A shortened response: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "schemaVersion": 1, "counts": { "agents": 436, "teams": 85 }, "agents": [ { "id": "clawboo-dev-code-reviewer-boo", "packId": "clawboo", "source": "clawboo", "name": "Code Reviewer Boo", "role": "Code Reviewer", "emoji": "👾", "color": "#34D399", "description": "Code Reviewer on the Dev Team.", "category": "engineering", "tags": ["clawboo", "dev", "code review"], "skillIds": ["code-search"] } ], "teams": [ { "id": "clawboo-dev", "packId": "clawboo", "source": "clawboo", "name": "Dev Team", "emoji": "👾", "color": "#34D399", "description": "Code review, bug hunting, and documentation.", "category": "engineering", "tags": ["code review", "debugging"], "agentIds": ["clawboo-dev-code-reviewer-boo", "clawboo-dev-bug-fixer-boo"] } ], "packs": [ { "publisher": "clawboo", "slug": "builtin", "id": "clawboo", "version": "1.0.0" }, { "publisher": "agency", "slug": "agents", "id": "agency-agents", "version": "1.0.0" } ] } ``` ## `GET /api/catalog/agents/:id` One agent template's files, keyed by file name. ### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { id: string files: Record<string, string> // SOUL.md, IDENTITY.md and TOOLS.md; AGENTS.md when the entry has its own routing sourceUrl?: string // link to the upstream source, when the entry has one } ``` A team deploy from the dashboard does not pass this map through as it stands. It renames the agent in `IDENTITY.md` and `SOUL.md`, adds the personality section to `SOUL.md`, and builds `AGENTS.md` and `CLAWBOO.md` from the team, using the entry's own `AGENTS.md` routing as input. **`404 Not Found`**: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Unknown catalog agent" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/catalog/agents/clawboo-dev-code-reviewer-boo ``` ## `GET /api/catalog/teams/:id` One team template's workflow narrative and per-member routing. ### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { id: string workflowNarrative?: string routing?: Record<string, string> // agent id -> that member's AGENTS.md routing text sourceUrl?: string } ``` **`404 Not Found`**: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Unknown catalog team" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/catalog/teams/clawboo-dev ``` **Source of truth:** `apps/web/server/api/catalog.ts` and `apps/web/server/lib/catalogIndex.ts`; the built-in pack is `apps/web/server/lib/catalogSeed.ts`, generated from `catalog/packs/clawboo/builtin`. ## Related * [Browse and deploy from the Marketplace](/using/marketplace): the dashboard view over these routes * [Marketplace catalog reference](/reference/marketplace-catalog): the pack format, publishing and integrity checks * [Agents API](/reference/rest-api/agents): `POST /api/agents`, the route a deploy creates each agent through * [Agent settings API](/reference/rest-api/agent-settings): installing a skill on an agent # Chat history API Source: https://docs.claw.boo/reference/rest-api/chat-history REST reference for stored chat transcripts: read a session page by page, save entries, reset an agent's context, and delete a session. Stored chat transcripts, one session at a time. The server stores most chat turns here itself as they happen, the dashboard also saves entries through `POST`, and it reads a chat back a page at a time. Use this page when you read or archive conversations outside the dashboard. Examples use the default port `18790` and no access token. With `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each `curl`. All four routes are on the general rate limit described in the [REST API overview](/reference/rest-api/index). Errors use `{ "error": "<message>" }`. ## Routes | Method | Path | Summary | | - | - | - | | GET | `/api/chat-history` | Read the newest page of a session, with a cursor for older | | POST | `/api/chat-history` | Save transcript entries (repeats are ignored) | | POST | `/api/chat-history/reset-context` | End the agent's conversation and add a divider | | DELETE | `/api/chat-history` | Delete every message of a session | ## Sessions and entries Each stored row belongs to one **session key**. The keys the server reads for itself: | Session key | Conversation | | - | - | | `agent:<agentId>:native` | A Clawboo Native agent's 1:1 chat | | `agent:<agentId>:<runtime>` | A Claude Code, Codex or Hermes agent's 1:1 chat | | `agent:<agentId>:team:<teamId>` | One teammate's session in a team's General chat | | `agent:<agentId>:team:<teamId>:room:<roomId>` | The same, in one of the team's rooms | Other chats, such as an OpenClaw agent's 1:1 chat, use the session key the dashboard already holds for them. Each row stores one transcript entry as JSON: ```ts theme={"theme":"github-dark"} interface TranscriptEntry { entryId: string // unique; a repeat is ignored on save role: 'user' | 'assistant' | 'tool' | 'system' | 'other' kind: 'meta' | 'user' | 'assistant' | 'thinking' | 'tool' text: string sessionKey: string runId: string | null source: 'local-send' | 'runtime-chat' | 'runtime-agent' | 'history' | 'legacy' timestampMs: number | null sequenceKey: number confirmed: boolean fingerprint: string // an opaque id, not a content hash origin?: { kind: 'routine'; routineId: string; routineName: string } // posted by a routine } ``` ### Three ways a conversation ends | Action | Route | Messages | | - | - | - | | **Reset context** (`/reset` or `/new` in chat) | `POST /api/chat-history/reset-context` | Kept, with a divider at the reset point | | **Clear chat** in a team's chat | `POST /api/teams/:id/chat/clear` | Set aside: re-keyed with a `#cleared:<ts>` suffix, so no read here returns them | | **Delete** (when an agent is deleted) | `DELETE /api/chat-history` | Deleted | All three also clear the saved resume points of the sessions involved. A resume point is the saved session an agent continues from on its next turn. Clawboo Native, Claude Code, Codex and Hermes agents keep one for a 1:1 chat, and Clawboo Native and Codex team leaders keep one per team chat. Clear chat is documented in the [Teams API](/reference/rest-api/teams). ## `GET /api/chat-history` Returns the **newest** page of a session, oldest first within the page. Pass `nextBefore` back as `before` to walk further back. Rows whose stored JSON cannot be parsed are left out. * **Query params**: | Param | Type | Default | Notes | | - | - | - | - | | `sessionKey` | string | required | The session to read | | `limit` | number | `200` | At most `1000`; a non-numeric value falls back to `200` | | `before` | number | none | A row id, normally `nextBefore` from an earlier response; a value that is not an integer is ignored | ### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { entries: TranscriptEntry[] hasMore: boolean // older entries exist nextBefore: number | null // pass back as `before`; null at the start of the session } ``` **`400 Bad Request`**: no `sessionKey`: ```json theme={"theme":"github-dark"} { "error": "sessionKey required" } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. ### Example ```bash theme={"theme":"github-dark"} curl "http://localhost:18790/api/chat-history?sessionKey=agent:my-boo:native&limit=500" ``` ## `POST /api/chat-history` Saves transcript entries for one session. Saving is idempotent: an entry whose `entryId` is already stored is ignored, and an entry with no `entryId` is skipped. * **Request body**: ```ts theme={"theme":"github-dark"} { sessionKey: string // required gatewayUrl?: string // stored on each row; defaults to '' entries: TranscriptEntry[] // required, at least one } ``` Each entry is stored exactly as sent. When it has no `timestampMs`, only the row's own timestamp column gets the time it arrived; the entry that `GET` returns is unchanged. ### Responses **`200 OK`**: `saved` counts the entries received, not the rows written: ```ts theme={"theme":"github-dark"} { ok: true, saved: number } ``` **`400 Bad Request`**: the body is not a JSON object: ```json theme={"theme":"github-dark"} { "error": "invalid JSON" } ``` **`400 Bad Request`**: no `sessionKey`, or `entries` is not a non-empty array: ```json theme={"theme":"github-dark"} { "error": "sessionKey and entries[] required" } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/chat-history \ -H 'Content-Type: application/json' \ -d '{"sessionKey":"agent:my-boo:native","entries":[{"entryId":"e1","role":"user","kind":"user","text":"Hi","sessionKey":"agent:my-boo:native","runId":null,"source":"local-send","timestampMs":1761650000000,"sequenceKey":1761650000000,"confirmed":true,"fingerprint":"e1"}]}' ``` ## `POST /api/chat-history/reset-context` Writes one divider entry and clears the saved resume point of every listed session, so an agent that resumes from one (see above) starts its next turn fresh. **No message is moved, re-keyed or deleted.** This is what `/reset` and `/new` call. The route does not reach the OpenClaw Gateway: for an OpenClaw agent, the dashboard also sends `/reset` to the agent's Gateway session. A team chat passes every teammate's session key plus one `noticeSessionKey`, so the person sees one divider in the merged timeline rather than one per teammate. * **Request body**: ```ts theme={"theme":"github-dark"} { sessionKeys: string[] // required; empty strings are ignored noticeSessionKey?: string // must be one of sessionKeys; defaults to the first } ``` ### Responses **`200 OK`**: the divider that was written, ready to show: ```ts theme={"theme":"github-dark"} { ok: true, entry: TranscriptEntry } // kind 'meta', role 'system' ``` **`400 Bad Request`**: no usable session key: ```json theme={"theme":"github-dark"} { "error": "sessionKeys[] required" } ``` **`400 Bad Request`**: `noticeSessionKey` is not in the list: ```json theme={"theme":"github-dark"} { "error": "noticeSessionKey must be one of sessionKeys" } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/chat-history/reset-context \ -H 'Content-Type: application/json' \ -d '{"sessionKeys":["agent:my-boo:native"]}' ``` ## `DELETE /api/chat-history` Deletes every message of one session and clears its resume point. The dashboard calls this when it deletes an agent. To end a conversation and keep the messages, use reset-context instead. * **Query params**: `sessionKey` (required). ### Responses **`200 OK`**: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`400 Bad Request`**: no `sessionKey`: ```json theme={"theme":"github-dark"} { "error": "sessionKey required" } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. ### Example ```bash theme={"theme":"github-dark"} curl -X DELETE "http://localhost:18790/api/chat-history?sessionKey=agent:my-boo:native" ``` **Source of truth:** `apps/web/server/api/chatHistory.ts`; the entry type is `TranscriptEntry` in `packages/protocol/src/index.ts`. ## Related * [Team chat](/using/team-chat): General, rooms, Clear chat and `/reset` in the dashboard * [Teams API](/reference/rest-api/teams): Clear chat, stop, and the team chat stream * [Agents API](/reference/rest-api/agents): 1:1 chat with a Clawboo Native, Claude Code, Codex or Hermes agent and its live stream * [Database schema](/reference/database-schema): the `chat_messages` table # Connectors API Source: https://docs.claw.boo/reference/rest-api/connectors REST reference for connectors (MCP servers Clawboo runs or signs in to), the Composio broker, custom connectors, and the grants that govern them. A connector is an MCP server that Clawboo runs on this machine (a local process) or reaches over HTTP (a remote server). You supply its credentials: an API key, an OAuth sign-in, or a launch argument such as the folder a filesystem server may see. Connectors come from Clawboo's built-in catalog or from custom entries you add, and grants decide which agents may use their tools. Use this page when you script connector setup or audit who can use what. Examples use the default port `18790` and no access token. With `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each `curl`. Every route is on the general rate limit; the routes marked **sensitive** also count against the stricter sensitive limit (see the [REST API overview](/reference/rest-api/index)). Errors use `{ "error": "<message>" }`; a `400` for a body that fails validation adds `details`, the validator's form and field errors. Local connectors run as child processes of the Clawboo server, as your user, with no sandbox. The server does not tell callers apart: any request that passes the access gate can connect a connector or create a grant. See [Security](/operating/security) before you open Clawboo to other machines. ## Routes | Method | Path | Summary | Limit | | - | - | - | - | | GET | `/api/connectors` | List the connectors running now | general | | GET | `/api/connectors/configured` | Which connectors have everything they need | general | | GET | `/api/connectors/path-suggestions` | Real folders or files to offer for a path argument | general | | POST | `/api/connectors/connect` | Start a connector and list its tools | sensitive | | POST | `/api/connectors/:slug/disconnect` | Stop a connector and keep it stopped | sensitive | | POST | `/api/connectors/:slug/authorize` | Start an OAuth sign-in for a remote connector | sensitive | | POST | `/api/connectors/:slug/authorize/await` | Wait for that sign-in to finish | general | | DELETE | `/api/connectors/:slug/authorize` | Sign a connector out and forget its tokens | sensitive | | GET | `/api/connectors/:slug/config` | What a connector needs, and what is stored | general | | PUT | `/api/connectors/:slug/config` | Store credentials and the launch argument | sensitive | | GET | `/api/connectors/composio` | Whether a Composio key is stored, and which apps are linked | general | | PUT | `/api/connectors/composio/key` | Store a Composio API key | sensitive | | DELETE | `/api/connectors/composio/key` | Forget the Composio key | sensitive | | POST | `/api/connectors/composio/apps/:slug/authorize` | Start linking one app through Composio | sensitive | | GET | `/api/connectors/custom` | List your custom connectors | general | | POST | `/api/connectors/custom` | Add or replace a custom connector | sensitive | | DELETE | `/api/connectors/custom/:slug` | Remove a custom connector | sensitive | | GET | `/api/grants` | List grants | general | | POST | `/api/grants` | Create or update a grant | general | | POST | `/api/grants/:id/revoke` | Revoke a grant | general | | POST | `/api/grants/:id/resume` | Undo a revoke within 15 seconds | general | ## Connectors The routes that look a connector up by slug (connect, authorize and the two config routes) search the built-in catalog first, then your custom connectors. `path-suggestions` reads the catalog only. **What is stored where.** A credential goes to Clawboo's encrypted vault under `connector:<slug>:<KEY>` and is never returned. A launch argument is a plain setting, `connector:<slug>:argument`, so it can be shown back to you. An OAuth sign-in keeps its client registration and tokens in the vault under `connector-oauth-client:<slug>` and `connector-oauth-tokens:<slug>`. **At startup**, the server reconnects every connector you left connected, as long as it still has what it needs (the same check `POST /api/connectors/connect` makes). A Disconnect keeps a connector down across restarts. On a machine with no saved choice for them, the server also starts the Playwright and Chrome DevTools browser connectors. ### `GET /api/connectors` Lists the connectors running now. A connector that is defined but not connected is not listed. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true connectors: Array<{ connectorId: string slug: string toolCount: number tools: string[] // tool names, each prefixed with the slug skipped: Array<{ name: string; reason: string }> specHash: string toolsHash: string }> } ``` `skipped` lists the tools Clawboo could not offer, each with a reason, such as `duplicate-name`. An entry named `(inventory)` with the reason `tool-list-truncated` means the server's tool list could not be read to the end. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/connectors ``` ### `GET /api/connectors/configured` One request for the whole shelf: which connectors, built-in and custom, already have what they ask for. Presence only; no value or per-connector detail comes back. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true; slugs: string[]; supplied: string[] } ``` `slugs` lists every connector whose configuration is complete: required credentials stored, the launch argument present when one is required, and, for a remote connector that signs in with OAuth, a sign-in on file. `supplied` lists every connector you actually gave something to, meaning a stored credential or a stored argument. A connector that asks for nothing is in `slugs` but not in `supplied`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/connectors/configured ``` ### `GET /api/connectors/path-suggestions` Real paths to offer as one-click choices for a connector that takes a path argument. Each one is checked on disk before it is offered, so the list can be empty. * **Query params**: `slug` (required): a built-in connector that takes a path, such as `filesystem` or `sqlite`. For `sqlite`, the server searches up to two folders deep from the folder you started `clawboo` in, skipping hidden entries and `node_modules` and never following links. It stops looking once it has 25 `.db`, `.sqlite` or `.sqlite3` files, sorts them, and offers the first five, each labelled with its file name. For any other slug it offers, in order, **Where you started clawboo**, then `Documents`, `Desktop` and `Downloads` in your home folder, keeping those that exist. Either way the list holds at most five paths, with no repeats. The folder you started `clawboo` in comes from `CLAWBOO_LAUNCH_DIR`, which the CLI sets; a development server uses its own working directory. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true suggestions: Array<{ label: string; path: string }> } ``` **`404 Not Found`**: the slug is missing, is not a built-in connector, or names one that takes no path: ```json theme={"theme":"github-dark"} { "error": "that connector does not take a path" } ``` **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl "http://localhost:18790/api/connectors/path-suggestions?slug=filesystem" ``` ### `POST /api/connectors/connect` **Sensitive.** Starts one connector: it spawns the local process, or opens the session to a remote server, and returns the tools the connector offers. Connecting a connector that is already running returns that session. A request that arrives while a connect for the same slug is under way waits for it instead of starting a second process. Before it starts anything, the server checks, against the vault and the stored settings, that the connector has what it needs. Anything missing answers `422` with a `reason`: | `reason` | Why the connector was refused | | - | - | | `community-unsandboxed` | It is a community entry, which nobody has vetted. Add it as a custom connector if you trust it. | | `remote-needs-registered-app` | The remote provider needs a pre-registered OAuth app, so Clawboo cannot sign itself in. | | `remote-needs-oauth` | A remote connector that signs in with OAuth has no usable sign-in yet. | | `needs-credential` | A required credential is not stored, or a remote connector that takes a token (`bearer`) has none. Optional credentials never block. | | `needs-user-supplied-argument` | It needs a launch argument, such as a folder or a database file, and none is stored. | When several apply, the response names one. For an OAuth connector, an expired token that can be refreshed is refreshed during this check. * **Request body**: ```ts theme={"theme":"github-dark"} { slug: string // 1 to 48 characters, lowercase letters, digits and dashes, starting with a letter or digit } ``` #### Responses **`200 OK`**: the connector is up: ```ts theme={"theme":"github-dark"} { ok: true connectorId: string command: string // the command that actually ran, or the URL of a remote connector tools: string[] skipped: Array<{ name: string; reason: string }> } ``` **`400 Bad Request`**: `{ "error": "invalid body", "details": { ... } }`. **`404 Not Found`**: no built-in or custom connector has that slug: ```json theme={"theme":"github-dark"} { "error": "no catalog connector named <slug>" } ``` **`422 Unprocessable Entity`**: refused, with the reason from the table and a sentence for people: ```json theme={"theme":"github-dark"} { "error": "Sign in to this provider to connect it.", "reason": "remote-needs-oauth" } ``` **`502 Bad Gateway`**: the process or the handshake failed. A failure that never reached the other end is retried first, up to three attempts with a doubling delay; a refusal from the other end is not. `error` names the connector and the likely cause, or reads `<Name> did not start.` when none matches. Likely causes include a missing `npx` or `uvx`, a Node.js that is too old, a package or version that is not published, a rejected key, a missing scope, a timeout, an unreachable host and a missing file. `detail` is the original message, redacted: ```ts theme={"theme":"github-dark"} { error: string detail: string } ``` Every failure past the checks above is a `502`, never a `500`. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/connectors/connect \ -H 'Content-Type: application/json' \ -d '{"slug":"filesystem"}' ``` ### `POST /api/connectors/:slug/disconnect` **Sensitive.** Stops a running connector and records that you want it stopped, so it stays down after a restart. The intent is recorded first, so even a `404` has marked an existing connector as disconnected. If a connect for the same slug is under way, the route waits for it to finish before it decides. #### Responses **`200 OK`**: `{ "ok": true }`. **`404 Not Found`**: nothing with that slug was running: ```json theme={"theme":"github-dark"} { "error": "connector is not connected" } ``` **`500 Internal Server Error`**: stopping it failed, `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/connectors/filesystem/disconnect ``` ### `POST /api/connectors/:slug/authorize` **Sensitive.** Starts an OAuth sign-in for a remote connector and returns the URL to open. The server opens nothing itself, because it may not be on the machine with the browser. No credential of yours is involved: Clawboo finds the provider's authorization server and registers itself with it. The provider redirects back to a short-lived listener on `http://127.0.0.1:<port>/callback`, never to a route under `/api`. The port is any free port, except that the port of an earlier registration for this connector is tried first, so that registration can be reused. Starting a sign-in cancels any earlier pending one for the same connector, so at most one is pending per connector. The discovery and registration steps are tried up to three times in all, with a delay that starts at 300 ms and doubles, but only for failures that never reached the provider. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true authorizeUrl: string } ``` **`400 Bad Request`**: a local connector has nothing to sign in to: ```json theme={"theme":"github-dark"} { "error": "only remote connectors sign in" } ``` **`404 Not Found`**: `{ "error": "no such connector" }`. **`502 Bad Gateway`**: every other failure, which is almost always at the provider. Both messages name the connector: ```ts theme={"theme":"github-dark"} // the request never left this machine { error: 'Could not reach <Name>. The request did not leave this machine, so this is network rather than the connector.' } // anything else, for example a provider without dynamic client registration { error: '<Name> could not start sign-in. <detail>' } // detail redacted, at most 200 characters ``` #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/connectors/linear/authorize ``` ### `POST /api/connectors/:slug/authorize/await` Waits until the sign-in started by `POST /api/connectors/:slug/authorize` finishes, so the browser can open the URL first and wait afterwards. The listener gives up 5 minutes after the sign-in started, so this request returns within about 5 minutes. Two callers waiting on the same sign-in get the same answer. `200` means the provider's code was exchanged and the tokens are stored, not only that the redirect arrived. Everything else is a `400`. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true authorized: true } ``` **`400 Bad Request`**: the sign-in did not complete. The message keeps its `Error: ` prefix: | `error` | Cause | | - | - | | `Error: no sign-in is in progress for this connector` | No pending sign-in: `authorize` was never called, or the sign-in already ended | | `Error: timed out waiting for the sign-in to finish` | No callback within 5 minutes | | `Error: authorization failed: <code>` | The provider redirected back with an `error` parameter | | `Error: callback missing code or state` | The callback came without an authorization code | | `Error: state mismatch, authorization refused` | The callback's `state` did not match this sign-in | A failure in the token exchange comes back here too, with that exchange's own message. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/connectors/linear/authorize/await ``` ### `DELETE /api/connectors/:slug/authorize` **Sensitive.** Signs a connector out, in this order: 1. Cancels a sign-in that is still pending, so it cannot finish and store new tokens. 2. Stops the connector if it is running, and marks it disconnected. 3. Deletes the stored client registration and tokens. It looks nothing up, so it answers `200` for any slug, whether or not anything was stored or running. #### Responses **`200 OK`**: `{ "ok": true }`. **`500 Internal Server Error`**: stopping the connector or deleting the tokens failed, `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X DELETE http://localhost:18790/api/connectors/linear/authorize ``` ### `GET /api/connectors/:slug/config` Everything a connector needs before it can run, and whether you have supplied it. Credentials come back as a `present` flag only; the launch argument comes back in full, so you can check which folder or file a connector was given. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true credentials: Array<{ key: string // the variable name, for example NOTION_TOKEN label?: string description: string required: boolean secret: boolean present: boolean // a value is stored; the value is never returned docsUrl?: string }> argument: string | null // the stored launch argument; null when none or empty argumentSpec: { label: string description: string example: string replacesArg?: string } | null // null when the connector takes no launch argument authorized: boolean satisfied: boolean } ``` `authorized` is the OAuth question. Only a remote connector that signs in with OAuth can answer `false`; every other connector reports `true`. `satisfied` is `true` when every required credential is stored, a required launch argument is present, and `authorized` is `true`. A connector that declares an argument without requiring it is satisfied without one. **`404 Not Found`**: `{ "error": "no such connector" }`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/connectors/filesystem/config ``` ### `PUT /api/connectors/:slug/config` **Sensitive.** Stores the credentials and the launch argument for one connector. These are your own credentials for the connector itself, entered once; a Composio key goes through `PUT /api/connectors/composio/key` instead. The slug is checked before the body, so an unknown slug is a `404` even when the body is also invalid. The whole body is then checked before anything is written, so a rejected request stores nothing. * **Request body**: both fields are optional, and an empty object returns the current state unchanged. | Field | Type | Notes | | - | - | - | | `argument` | string, up to 4096 chars | The launch argument, for example a folder or a database file. Trimmed before it is stored; an empty string clears it. | | `values` | object of strings | Credential values by variable name. Each key must match `^[A-Z][A-Z0-9_]*$` and be declared by this connector; each value is at most 8192 chars. | Each credential value is cleaned the way the dashboard's field cleans a paste. Surrounding whitespace is trimmed, a leading `Bearer`, `Token` or `Basic` is removed, and matching quotes or backticks around the value are unwrapped, repeatedly. A value that cleans down to nothing clears that credential. #### Responses **`200 OK`**: the state after the write, in the same shape `GET` returns, so a secret sent here cannot be read back. **`400 Bad Request`**: `{ "error": "invalid body", "details": ... }` for a body that fails validation, including a badly formed key or a value over the limit. **`400 Bad Request`**: a well-formed key that this connector does not declare: ```json theme={"theme":"github-dark"} { "error": "<KEY> is not declared by <slug>" } ``` **`404 Not Found`**: `{ "error": "no such connector" }`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X PUT http://localhost:18790/api/connectors/filesystem/config \ -H 'Content-Type: application/json' \ -d '{"argument":"/Users/me/projects"}' curl -X PUT http://localhost:18790/api/connectors/notion/config \ -H 'Content-Type: application/json' \ -d '{"values":{"NOTION_TOKEN":"ntn_..."}}' ``` ## Composio Composio is the broker Clawboo uses for apps it cannot sign in to directly, such as Gmail or Slack. These routes are about your Composio account and which of its apps you have linked. The apps' own tokens are exchanged at Composio and never reach Clawboo. The credential is a Composio project API key, the one that starts with `ak_` on your Composio project settings page. Clawboo keeps it in the encrypted vault under `COMPOSIO_API_KEY`, and no response returns it: the only fact any route reports about it is `hasKey`. One install is one Composio user (the fixed id `clawboo-local`). ### `GET /api/connectors/composio` Whether a key is stored, and which brokered apps are linked. With a key stored, the server asks Composio for the linked apps when its cached answer is more than 60 seconds old. #### Responses **`200 OK`**, no key stored: ```json theme={"theme":"github-dark"} { "ok": true, "hasKey": false, "known": true, "connected": [] } ``` **`200 OK`**, a key is stored: ```ts theme={"theme":"github-dark"} { ok: true; hasKey: true; known: boolean; connected: string[]; keyRejected: boolean } ``` `connected` holds Clawboo's slugs for the linked apps. `known` is `false` until Composio has answered at least once, so an empty `connected` with `known: false` means "not known yet", not "nothing linked". `keyRejected` is `true` when Composio refused the stored key (`401` or `403`) on its last use. **`502 Bad Gateway`**: reading the linked apps failed; the message is redacted and cut to 200 characters: ```json theme={"theme":"github-dark"} { "error": "Could not read connected apps. <message>" } ``` #### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/connectors/composio ``` ### `PUT /api/connectors/composio/key` **Sensitive.** Stores your Composio API key. The paste is unwrapped first, so `COMPOSIO_API_KEY=ak_...`, an `export` line and a quoted value all work. The key is then tried against Composio: a key Composio refuses is not stored, and a key that could not be checked because Composio was unreachable is stored with `verified: false`. Storing a key clears the cached list of linked apps. * **Request body**: `{ "apiKey": string }`, at most 512 characters. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true hasKey: true verified: boolean } ``` **`400 Bad Request`**: the body has no usable `apiKey` string: `{ "error": "invalid body" }`. **`400 Bad Request`**: the paste is not a project key (`ak_` followed by at least ten more letters, digits, `_` or `-`). The message is one of: * `Paste your Composio key.` (an empty value) * `That is a Composio login key. The one needed here starts with ak_ and is on your project settings page.` (a `uak_` key) * `That does not look like a Composio key. It starts with ak_.` (anything else) **`400 Bad Request`**: Composio refused the key, so nothing was stored: ```json theme={"theme":"github-dark"} { "error": "Composio rejected that key. Copy it again from your project settings." } ``` **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted and cut to 200 characters. #### Example ```bash theme={"theme":"github-dark"} curl -X PUT http://localhost:18790/api/connectors/composio/key \ -H 'Content-Type: application/json' \ -d '{"apiKey":"ak_xxxxxxxxxxxx"}' ``` ### `DELETE /api/connectors/composio/key` **Sensitive.** Forgets the stored key and clears the cached list of linked apps. The app links stay at Composio, but Clawboo cannot reach them without a key. Answers `200` whether or not a key was stored. #### Responses **`200 OK`**: `{ "ok": true, "hasKey": false }`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted and cut to 200 characters. #### Example ```bash theme={"theme":"github-dark"} curl -X DELETE http://localhost:18790/api/connectors/composio/key ``` ### `POST /api/connectors/composio/apps/:slug/authorize` **Sensitive.** Starts linking one app through Composio and returns Composio's consent URL for the browser to open. The stored key is checked before the slug, so an unknown app with no key stored answers `409`, not `404`. The route refreshes the linked-app list first, so an app that is already linked returns without starting a second consent. * **Path params**: `slug`, Clawboo's slug for the app, for example `gmail`, `slack` or `google-calendar` (not Composio's own toolkit name, which for the last one is `googlecalendar`). #### Responses **`200 OK`**, already linked: `{ "ok": true, "alreadyConnected": true }`. **`200 OK`**, a consent was started; open `url` in a browser: ```ts theme={"theme":"github-dark"} { ok: true url: string } ``` **`409 Conflict`**: no Composio key is stored: ```json theme={"theme":"github-dark"} { "error": "Add a Composio key first.", "reason": "no-key" } ``` **`404 Not Found`**: `{ "error": "no brokered app named <slug>" }`. **`502 Bad Gateway`**: Composio refused or failed the request, or answered without a consent URL (then the trailing detail is absent): ```json theme={"theme":"github-dark"} { "error": "<App name> could not be connected. <detail>" } ``` **`500 Internal Server Error`**: `{ "error": "Could not connect this app. <message>" }`, redacted and cut to 200 characters. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/connectors/composio/apps/gmail/authorize ``` ## Custom connectors A custom connector is a local MCP server you point Clawboo at yourself: a command and its arguments. Definitions are stored in the `connectors:custom` setting, not in the built-in catalog, and come back in the same shape as a catalog entry with `provenance: 'custom'`. Clawboo has not run or reviewed them, so it treats them at their riskiest. Every declared input is a secret, outbound access is unrestricted (`egressAllow: ['*']`), and all three risk flags are set: reads private data, takes in untrusted content, can send data out. Credential values never go through these routes: declare the variable names here, then store the values with `PUT /api/connectors/:slug/config`. ### `GET /api/connectors/custom` Lists your custom connectors in storage order. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true connectors: Array<{ slug: string displayName: string description: string // 'A connector you added.' when none was given category: 'dev' provenance: 'custom' launch: { transport: 'stdio' command: string args: string[] pinnedVersion: string // the version you were shown, else 'user-supplied' } auth: | { kind: 'api-key' inputs: Array<{ key: string; description: string; required: boolean; secret: true }> } | { kind: 'none'; inputs: [] } // when no authInputs were declared egressAllow: ['*'] trifecta: { readsPrivateData: true; ingestsUntrustedContent: true; canEgress: true } // the three risk flags tags: ['custom'] catalogId?: string }> } ``` An input saved with an empty description comes back as `'Required by this server.'`. Stored data that cannot be read never causes an error. A missing or malformed list reads as empty, and an entry without a string `slug`, a string `command` or an `args` array is left out. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/connectors/custom ``` ### `POST /api/connectors/custom` **Sensitive**, because the `command` and `args` it stores become a real process when you connect it. Saving a slug that is already a custom connector replaces it and moves it to the end of the list. * **Request body**: | Field | Type | Default | Notes | | - | - | - | - | | `slug` | string | required | 1 to 48 characters: lowercase letters, digits and dashes, starting with a letter or digit. It names the tools and grants. | | `displayName` | string | required | 1 to 80 characters | | `description` | string | none | Up to 300 characters | | `command` | string | required | 1 to 1024 characters | | `args` | string array | `[]` | Up to 64 entries of up to 2048 characters. An array, so nothing is passed through a shell. | | `authInputs` | `Array<{ key, description, required }>` | `[]` | Up to 16. `key` is a variable name (`^[A-Za-z_][A-Za-z0-9_]*$`, up to 64 chars), but `PUT /api/connectors/:slug/config` stores only keys matching `^[A-Z][A-Z0-9_]*$`, so use upper case; `description` up to 300 chars, default `''`; `required` default `true`. | | `catalogId` | string | none | Up to 200 characters: the registry id, when the entry came from the community list | | `pinnedVersion` | string | none | Up to 64 characters: the exact version you were shown before you approved it | #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true slug: string } ``` **`400 Bad Request`**: `{ "error": "invalid body", "details": ... }`. **`409 Conflict`**: the slug belongs to a built-in connector, which a custom entry may not replace: ```json theme={"theme":"github-dark"} { "error": "<slug> is already a catalog connector" } ``` **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/connectors/custom \ -H 'Content-Type: application/json' \ -d '{ "slug": "my-notes", "displayName": "My Notes", "command": "node", "args": ["/Users/me/servers/notes/index.js"], "authInputs": [{ "key": "NOTES_API_KEY", "description": "Key for the notes server", "required": true }] }' ``` ### `DELETE /api/connectors/custom/:slug` **Sensitive.** Removes a custom connector. It stops the connector first, if it is running, so no process is left behind with nothing that knows how to stop it. The stop runs on every call, so a `404` means only that no stored definition had that slug. #### Responses **`200 OK`**: `{ "ok": true }`. **`404 Not Found`**: `{ "error": "no such custom connector" }`. **`500 Internal Server Error`**: stopping it or saving the list failed, `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X DELETE http://localhost:18790/api/connectors/custom/my-notes ``` ## Grants A grant says that a subject (one agent, one team, or every agent) may use a capability (a connector, a tool or a skill) in a given mode, under a given approval policy. Clawboo's connector gate reads grants before a connector tool runs, and the graphs draw your grants as edges. A grant's `origin` is `owner` when it records what an agent's own runtime already attaches, and `operator` when a person shared the capability. Only `operator` grants are drawn with a **Detach** control. These routes read and write the local database only, and need no credential. ```ts theme={"theme":"github-dark"} interface GrantRow { id: string subjectKind: 'agent' | 'team' | 'global' subjectId: string | null // null for 'global' capabilityKind: 'connector' | 'tool' | 'skill' connectorId: string | null capabilityId: string | null toolAllow: string[] // tool-name patterns; ['*'] is every tool, [] is none toolDeny: string[] // checked first; a denied name is out of scope even if allowed mode: 'read' | 'write' | 'admin' approvalPolicy: 'never' | 'risk' | 'writes' | 'always' state: 'proposed' | 'active' | 'suspended' | 'revoked' | 'expired' expiresAt: number | null specHashPin: string | null // when set, calls are denied once the connector's spec differs from it toolsHashPin: string | null // the same, for the connector's tool list callCeilingPerHour: number | null origin: 'owner' | 'operator' grantedBy: string | null grantedAt: number revokedAt: number | null revokedReason: string | null } ``` | `approvalPolicy` | When a tool call asks a person first | | - | - | | `always` | Every call | | `writes` | Calls to tools that change something | | `risk` (default) | Calls to destructive tools, and to outside tools that change something | | `never` | The policy never asks | Some calls ask whatever the policy says, for example in a run that combines private data, untrusted content and a way to send data out. Under `always`, a standing rule from an earlier **Always** answer does not skip the prompt, and the prompt cannot be remembered (`neverRemember`). Under the other policies a standing allow rule runs the tool without asking; revoking the grant deletes those rules. A tool whose required mode ranks above the grant's `mode` is denied. A stored value that cannot be read falls back to its safest reading: `mode` reads as `read`, `state` as `suspended`, `approvalPolicy` as `always`, an unreadable `toolAllow` as `[]` and an unreadable `toolDeny` as `['*']`. ### `GET /api/grants` Lists grants, newest `grantedAt` first, with no limit. * **Query params**: `subjectId` (optional): only grants whose subject id equals it, whatever the `subjectKind`. A `global` grant has no subject id, so it never matches. An empty or repeated `subjectId` is ignored and the full list returned. #### Responses **`200 OK`**: `{ ok: true, grants: GrantRow[] }`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl "http://localhost:18790/api/grants?subjectId=<agent-id>" ``` ### `POST /api/grants` Creates a grant, or updates the existing grant for the same subject and capability. An update sets `state` back to `active`, clears `revokedAt` and `revokedReason`, and moves `grantedAt` to now. Grants made here have `origin: 'operator'`, and updating an `owner` grant turns it into an `operator` one. The server has no caller identity, so a new grant records `grantedBy: null`. * **Request body**: | Field | Type | Default on create | Notes | | - | - | - | - | | `subjectKind` | `'agent' \| 'team' \| 'global'` | required | Who the grant is for | | `subjectId` | `string \| null` | `null` | Required unless `subjectKind` is `global` | | `capabilityKind` | `'connector' \| 'tool' \| 'skill'` | required | | | `connectorId` | `string \| null` | `null` | Give `connectorId` or `capabilityId`. With `connectorId`, the stored `capabilityId` is `null`. | | `capabilityId` | `string \| null` | `null` | Used only when there is no `connectorId` | | `mode` | `'read' \| 'write' \| 'admin'` | `'read'` | Left as is on an update when omitted | | `approvalPolicy` | `'never' \| 'risk' \| 'writes' \| 'always'` | `'risk'` | Left as is on an update when omitted | | `toolAllow` | `string[]` | `['*']` | `[]` means no tools, which differs from leaving the field out | | `toolDeny` | `string[]` | `[]` | | | `expiresAt` | `number \| null` | none | Positive whole epoch ms, or `null`. Only applied when present, so leaving it out never clears a time limit. | | `callCeilingPerHour` | `number \| null` | none | Positive whole number, or `null`. Only applied when present. | #### Responses **`200 OK`**: `{ ok: true, grant: GrantRow }`. **`400 Bad Request`**: `{ "error": "invalid body", "details": ... }`. The two cross-field rules ("one of connectorId or capabilityId is required" and "subjectId is required unless subjectKind is global") appear in `details.formErrors`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/grants \ -H 'Content-Type: application/json' \ -d '{"subjectKind":"agent","subjectId":"<agent-id>","capabilityKind":"connector","connectorId":"github","mode":"read","approvalPolicy":"risk"}' ``` ### `POST /api/grants/:id/revoke` Revokes a grant and deletes the standing rules recorded against it, so a remembered **Always** cannot outlive the grant. Only an `active` grant changes state; for a grant in any other state the route still answers `200` with the grant as it stands. The standing rules are deleted whatever the state. * **Request body**: optional, `{ "reason": string | null }` (up to 200 characters). A missing or invalid body is not an error: `reason` is then `null`. The dashboard's **Detach** sends no body. #### Responses **`200 OK`**: the grant as it now stands, `{ ok: true, grant: GrantRow }`. **`404 Not Found`**: `{ "error": "grant not found" }`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/grants/<grant-id>/revoke \ -H 'Content-Type: application/json' \ -d '{"reason":"detached from the graph"}' ``` ### `POST /api/grants/:id/resume` Undoes a revoke within 15 seconds of `revokedAt`, the window behind the dashboard's **Undo**. It sets the grant back to `active` and clears `revokedAt` and `revokedReason`, but the standing rules the revoke deleted stay deleted. An `active` grant comes back unchanged. Takes no body. #### Responses **`200 OK`**: the resumed, or already active, grant: `{ ok: true, grant: GrantRow }`. **`409 Conflict`**: the grant exists but cannot be resumed: the 15 seconds have passed, it has no `revokedAt`, or it is `proposed`, `suspended` or `expired`: ```json theme={"theme":"github-dark"} { "error": "the undo window has closed" } ``` **`404 Not Found`**: `{ "error": "grant not found" }`. **`500 Internal Server Error`**: `{ "error": "<message>" }`, redacted. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/grants/<grant-id>/resume ``` **Source of truth:** `apps/web/server/api/connectors.ts`, `composio.ts` and `grants.ts`; request schemas in `packages/db/src/grants/connectorSchemas.ts` and `packages/db/src/grants/schemas.ts`; the refusal reasons in `packages/connector-catalog/src/connectable.ts`; the grant decision in `packages/governance/src/grants/decide.ts`. ## Related * [Connect an MCP server](/using/connectors): the Connectors panel over these routes * [Capabilities API](/reference/rest-api/capabilities): connectors and grants as capability records * [Tools and MCP API](/reference/rest-api/tools-and-mcp): tool approvals and the audit trail * [What agents can do](/concepts/permissions): what agents can reach, and what is not sandboxed * [Security](/operating/security): the access token and exposing Clawboo beyond this machine # Costs and fleet API Source: https://docs.claw.boo/reference/rest-api/costs REST reference for token cost records, the 30-day summary behind Tokens Used, and the fleet summary behind the Fleet panel. Read and write token cost records, the 30-day summary the **Tokens Used** panel shows, and the fleet overview the **Fleet** panel shows. Use this page when you build your own spend report or health dashboard. Examples use the default port `18790` and no access token. With `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each `curl`. All four routes are on the general rate limit described in the [REST API overview](/reference/rest-api/index). Errors use `{ "error": "<message>" }`. ## Routes | Method | Path | Summary | | - | - | - | | GET | `/api/cost-records` | List cost records, newest first | | POST | `/api/cost-records` | Record one run's token usage with an estimated cost | | GET | `/api/cost-records/summary` | Totals, per-agent totals and a daily series for the last 30 days | | GET | `/api/fleet/summary` | Agents per runtime, 24-hour task and verification counts, spend, budgets | ## Cost records A cost record is one run's token counts plus a price. The server records each OpenClaw Gateway turn itself, and those rows carry a `sessionKey`. Rows posted through `POST /api/cost-records` (by your own script, or by the dashboard for a Gateway turn of an agent it does not list as OpenClaw) have `sessionKey: null`. The server also writes one record for each Clawboo Native, Claude Code, Codex or Hermes run when it settles the run's cost, on the task runner (agent routines and API runs) and in team chat. Those rows carry a `sessionKey` too, and `runId` is the run's execution id when it has one. When the run reported no token counts, they are guessed from the text at about 4 characters per token. One-to-one chats with these agents write no records, and runs from before these records were added are not backfilled. A cost record is a report only. Writing one does not charge a budget; budget spend is tracked separately (see the [Governance API](/reference/rest-api/governance)). Deleting an agent deletes its cost records. > [!NOTE] > > On rows the server records for OpenClaw turns, and on rows posted through `POST /api/cost-records`, `costUsd` is an estimate from a built-in price list, never a figure a provider reported. The list covers Anthropic Claude models plus `gpt-4o` and `gpt-4o-mini`. A model your own Ollama serves is priced at `$0`: an `ollama/` model id, or an OpenClaw turn the Gateway says Ollama served, unless its tag marks one of Ollama's cloud models (`-cloud` or `:cloud`). Any other model is priced at `$3` per million input tokens and `$15` per million output tokens. A row for another runtime's run carries the cost that run recorded: Claude Code's reported figure, or Clawboo's estimate. For which runtimes report real provider cost, see [Budgets and spend](/using/budgets-and-spend). The record shape, returned by both cost-record routes: ```ts theme={"theme":"github-dark"} interface CostRecord { id: number agentId: string model: string inputTokens: number outputTokens: number costUsd: number // see the note above runId: string | null sessionKey: string | null // set on rows the server records itself; null on rows from POST createdAt: number // epoch ms } ``` ### `GET /api/cost-records` Lists cost records, newest first, at most 500. * **Query params**: | Param | Type | Default | Notes | | - | - | - | - | | `period` | `'today' \| 'week' \| 'month'` | none | `today` starts at midnight, server time; `week` is the last 7 days and `month` the last 30 days | | `agentId` | string | none | Only this agent's records | Any other `period` value applies no time filter. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { records: CostRecord[] } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":"github-dark"} curl "http://localhost:18790/api/cost-records?period=week&agentId=<agent-id>" ``` ### `POST /api/cost-records` Records one run's token usage. The server prices it with the built-in list and stores the result. An `agentId` with no agent row gets a placeholder row (named after the id) so the record can be stored. * **Request body**: ```ts theme={"theme":"github-dark"} { agentId: string // required model: string // required; picks the price inputTokens: number // required; 0 is allowed outputTokens: number // required; 0 is allowed runId?: string | null } ``` Model ids match the price list exactly first, then by substring with dots read as dashes, so `anthropic/claude-haiku-4.5` is priced as `claude-haiku-4-5`. #### Responses **`200 OK`**: the stored record: ```ts theme={"theme":"github-dark"} { ok: true, record: CostRecord } ``` **`400 Bad Request`**: the body is not a JSON object: ```json theme={"theme":"github-dark"} { "error": "invalid JSON" } ``` **`400 Bad Request`**: `agentId` or `model` is empty, or a token count is missing: ```json theme={"theme":"github-dark"} { "error": "agentId, model, inputTokens, outputTokens required" } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/cost-records \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>","model":"claude-sonnet-4-6","inputTokens":1200,"outputTokens":340}' ``` ### `GET /api/cost-records/summary` Totals for the **Tokens Used** panel, built from the records of the last 30 days. Every window starts at midnight in the server's time zone: today, 7 days ago, and 30 days ago. Takes no parameters. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { totalToday: number // USD, see the note above totalWeek: number totalMonth: number tokensToday: number // input + output tokens tokensWeek: number tokensMonth: number byAgent: Array<{ agentId: string agentName: string // the agent's name, or its id when no agent row exists totalCost: number totalTokens: number inputTokens: number outputTokens: number messageCount: number // number of records }> // most tokens first timeSeries: Array<{ date: string // en-US short date, for example "Oct 28" cost: number tokens: number }> // 30 entries, oldest first, from 29 days ago through today; days with no records are 0. Records from 30 days ago count in the month totals but have no entry here. } ``` **`500 Internal Server Error`**: a database failure, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/cost-records/summary ``` ## Fleet summary ### `GET /api/fleet/summary` A read-only overview built from data Clawboo already stores: agents per runtime, board tasks and verification verdicts from the last 24 hours, recorded task cost, and budgets. It backs the **Fleet** panel in Settings. Takes no parameters. #### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { generatedAt: number // epoch ms tenantId: null // always null totalAgents: number // agents that are not archived runtimes: Array<{ runtime: string runtimeClass: 'connected-substrate' | 'wrapped-oneshot' | 'native' healthOk: boolean | null agentCount: number healthy: number // agents with status idle or running degraded: number // agents with status error down: number // agents with any other status }> tasks24h: { total: number done: number cancelled: number inProgress: number // in_progress or in_review passRate: number | null } verification24h: { total: number pass: number fail: number debt: number // completed with debt passRate: number | null } spend24hUsd: number budgets: { count: number paused: number } } ``` | Field | Meaning | | - | - | | `runtimes` | One row per runtime that has agents or reports health. `openclaw`, `claude-code`, `codex`, `hermes` and `clawboo-native` are always listed, with zero counts when they have no agents. Sorted by `agentCount`, then by name. | | `runtimeClass` | `connected-substrate` is OpenClaw, a Gateway that runs on its own and Clawboo connects to. `wrapped-oneshot` is a runtime Clawboo starts for each run (Claude Code, Codex, Hermes). `native` is Clawboo Native, which runs inside the server. | | `healthOk` | OpenClaw: the server's Gateway connection is up. Claude Code, Codex, Hermes: the `claude`, `codex` or `hermes` command was found on this machine. Clawboo Native: a provider key is connected or local Ollama is set up. `null` when nothing reports. | | `tasks24h` | Board tasks **updated** in the last 24 hours. `passRate` is `done / (done + cancelled)`, or `null` when none finished. | | `verification24h` | Verdicts on those same tasks. `passRate` is `pass / total`, or `null` when there are no verdicts. | | `spend24hUsd` | The total recorded cost of the tasks updated in the last 24 hours. A long task touched today counts its whole cost, so this is not the money spent in the last 24 hours. | | `budgets` | How many budgets exist, and how many are paused. | **`500 Internal Server Error`**: building the summary failed, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/fleet/summary ``` **Source of truth:** `apps/web/server/api/costRecords.ts`, `costRecordsSummary.ts`, `fleet.ts`, and the price list in `apps/web/server/lib/costUtils.ts`. ## Related * [Budgets and spend](/using/budgets-and-spend): which runtimes report real cost, and the Tokens Used panel * [Governance API](/reference/rest-api/governance): budgets, caps and the audit log * [Observability API](/reference/rest-api/observability): the event log and fleet health triage * [Board API](/reference/rest-api/board): task cost and the execution ledger behind `spend24hUsd` * [Database schema](/reference/database-schema): the `cost_records` table # Governance API Source: https://docs.claw.boo/reference/rest-api/governance REST reference for budgets (spend tracking, optional hard caps, resume), the audit log, delegation approvals, and the approval-decision history. These routes cover [governance](/concepts/governance): spend budgets, the audit log, the approval a team lead gives before a risky delegation runs, and a history of approval decisions. For the dashboard side, see [Budgets and spend](/using/budgets-and-spend). How budgets behave: * A scope is tracked only once it has a budget row. A scope is an `agent`, a `team`, a `mission` (a top-level task and every task delegated under it, keyed by the top-level task's id) or a `tenant`. You create rows with `POST /api/governance/budgets`. Clawboo also creates one when a native agent is created with `execConfig.budgetUsd` above 0: an agent budget in `cap` mode. * A budget is in `warn` mode unless you send `mode: 'cap'`. A `warn` budget tracks spend and moves to `soft_capped` from 80% of its limit, but it never pauses anything. Only a `cap` budget reaches `paused` at 100% and stops runs. * Nothing charges a `tenant` budget: the route accepts one, but no run records spend against it. * Spend is recorded by the server as runs report it, not through these routes. Which runs a hard cap can stop before they spend more depends on the runtime and on how the run started; see [Governance](/concepts/governance). The audit log is always written. Every route here is on the general rate tier (see [rate limits](/reference/rest-api/index#rate-limits)). ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/governance/budgets` | List every budget | No | | POST | `/api/governance/budgets` | Create a budget, or change its limit and mode | No | | POST | `/api/governance/budgets/:scope/:scopeId/resume` | Resume a paused budget, optionally with extra headroom | No | | GET | `/api/governance/audit` | Read the audit log (filterable) | No | | POST | `/api/governance/delegation-approval` | Ask a team lead to approve a risky delegation (waits up to 5 minutes) | No | | GET | `/api/approvals` | List stored approval decisions | No | | POST | `/api/approvals` | Store an approval decision | No | > [!NOTE] > > Two separate approval records live here. `POST /api/governance/delegation-approval` opens a live approval in the `tool_call_approvals` table and waits for the answer. `/api/approvals` reads and writes `approval_history`, a plain log of past allow and deny decisions. The two never write the same table. *** ## `GET /api/governance/budgets` Lists every budget, most recently updated first. The route has no filters. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { budgets: Array<{ id: string scope: 'agent' | 'mission' | 'team' | 'tenant' scopeId: string limitUsdCents: number spentUsdCents: number // whole cents: floor(spentMicroCents / 10000) spentMicroCents: number // the exact spend, in millionths of a dollar (10,000 per cent) status: 'active' | 'soft_capped' | 'paused' mode: 'cap' | 'warn' tenantId: string | null createdAt: number // epoch ms updatedAt: number // epoch ms }> } ``` `status` is `active` below 80% of the limit and `soft_capped` from 80%. Only a `cap` budget becomes `paused`, at 100%, and stays paused until you resume it or raise its limit. A `warn` budget stops at `soft_capped` however far spend goes. ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/governance/budgets ``` *** ## `POST /api/governance/budgets` Creates a budget for a scope, or changes the limit and mode of an existing one. A new budget starts at 0 spent and `active`. Changing the limit recomputes `status` from the spend so far, so raising the limit above the current spend un-pauses a `cap` budget. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { scope: 'agent' | 'mission' | 'team' | 'tenant' // required scopeId: string // required, non-empty limitUsdCents: number // required, a positive integer (0 is refused) mode?: 'cap' | 'warn' // omitted: 'warn' for a new budget, unchanged for an existing one tenantId?: string | null } ``` Sending `mode` sets it, on create and on update. Leaving it out keeps an existing budget's mode, so re-sending a `cap` budget with only a new `limitUsdCents` keeps the hard stop. To turn a hard cap into a warning, send `mode: 'warn'`. A limit of `0` is refused: a budget is a positive limit, and an untracked scope is one with no row. There is no delete route; to stop a cap, change it to `warn` or raise its limit. ### Responses **`200 OK`**: the budget as stored: ```ts theme={"theme":"github-dark"} { budget: { id: string scope: 'agent' | 'mission' | 'team' | 'tenant' scopeId: string limitUsdCents: number spentUsdCents: number spentMicroCents: number status: 'active' | 'soft_capped' | 'paused' mode: 'cap' | 'warn' tenantId: string | null createdAt: number updatedAt: number } } ``` **`400 Bad Request`**: the body failed validation (an unknown scope, a missing `scopeId`, a limit that is not a positive integer). `details` is the zod `flatten()` output: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` ### Example ```bash theme={"theme":"github-dark"} # A $5.00 hard cap on a team: runs for this team stop when it is spent curl -X POST http://localhost:18790/api/governance/budgets \ -H 'Content-Type: application/json' \ -d '{"scope":"team","scopeId":"<team-id>","limitUsdCents":500,"mode":"cap"}' ``` *** ## `POST /api/governance/budgets/:scope/:scopeId/resume` Sets a paused budget back to `active`. If the spend has already reached the limit, the next recorded cost pauses it again; send `graceUsdCents` to raise the limit to the current spend plus that amount, so work can continue. A resume with grace keeps the budget's mode. The response carries `willRepause: true` when you resume a budget at or over its limit without grace. * **Path params**: | Param | Type | Notes | | - | - | - | | `scope` | `'agent' \| 'mission' \| 'team' \| 'tenant'` | Any other value is a **400** | | `scopeId` | string | The scope's id; matched together with `scope` | * **Request body** (optional): ```ts theme={"theme":"github-dark"} { graceUsdCents?: number // a positive integer: headroom when spend is at or over the limit } ``` ### Responses **`200 OK`**: the resumed budget and the re-pause warning. `willRepause` is `true` when `spentUsdCents >= limitUsdCents`: ```ts theme={"theme":"github-dark"} { budget: { /* the same shape as POST /api/governance/budgets */ } willRepause: boolean } ``` **`400 Bad Request`**: `:scope` is not one of the four scopes: ```json theme={"theme":"github-dark"} { "error": "invalid scope" } ``` **`400 Bad Request`**: the body failed validation (for example a `graceUsdCents` that is not a positive integer): ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": {} } } ``` **`404 Not Found`**: no budget exists for that `scope` and `scopeId`: ```json theme={"theme":"github-dark"} { "error": "budget not found" } ``` ### Example ```bash theme={"theme":"github-dark"} # Resume a paused team budget with $1.00 (100 cents) of headroom curl -X POST http://localhost:18790/api/governance/budgets/team/<team-id>/resume \ -H 'Content-Type: application/json' \ -d '{"graceUsdCents":100}' ``` *** ## `GET /api/governance/audit` Reads the append-only audit log, newest first: installs, approvals, tool calls, budget events, cap hits, check results and circuit breaks. There is no write route; the server writes these rows itself. Each row's `summary` was scrubbed of secrets when it was written, and is masked again before it is sent. The 80% and 100% budget warnings (`eventType: 'budget'`) are written by task-runner runs and OpenClaw Routine fires. Team chat and 1:1 chat update the budget row without writing a warning row. A task-runner run also writes a `budget` row when a hard cap stops it (`summary.reason` is `'auto_pause'`), or when its final cost report crossed a cap after the work was done (`'cap_reached_at_end'`). * **Query params**: | Param | Type | Default | Notes | | - | - | - | - | | `agentId` | string | none | Only this agent's rows | | `eventType` | `'install' \| 'approval' \| 'tool_call' \| 'budget' \| 'cap_hit' \| 'verification' \| 'circuit_break'` | none | Any other value is ignored | | `since` | number | none | `createdAt` lower bound (epoch ms); used only when above 0 | | `limit` | number | `200` | At most 1000; used only when above 0 | * **Request body**: none. ### Responses **`200 OK`**: the matching rows (an empty list when nothing matches): ```ts theme={"theme":"github-dark"} { audit: Array<{ id: string eventType: 'install' | 'approval' | 'tool_call' | 'budget' | 'cap_hit' | 'verification' | 'circuit_break' agentId: string | null taskId: string | null teamId: string | null tenantId: string | null summary: string // JSON text, scrubbed and masked createdAt: number // epoch ms }> } ``` ### Example ```bash theme={"theme":"github-dark"} # The last 50 budget events for one agent curl "http://localhost:18790/api/governance/audit?agentId=<agent-id>&eventType=budget&limit=50" ``` *** ## `POST /api/governance/delegation-approval` Asks a team lead to approve a risky delegation before it runs. It opens a pending approval in `tool_call_approvals` (it appears in the [approval cards](/using/approvals)) and holds the request open until the lead answers or the approval expires. The approval's key is `delegate:<kind>`, and it is marked `neverRemember`: every request asks, whatever the lead answered before. The wait is fixed at 5 minutes: the approval expires 5 minutes after it is created, and the request gives up at the same time. `CLAWBOO_APPROVAL_TTL_MS` (24 hours by default) does not change this wait. It sets how old an abandoned pending approval must be before the hourly cleanup expires it and unblocks any task it was holding. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { leaderAgentId: string // required kind?: string // the delegation kind; the key is 'delegate:<kind>'; default 'code' targetAgentName?: string // the agent the work goes to (shown in the prompt) task?: string // what is being delegated taskId?: string // the board task this approval gates, so the cleanup can unblock it } ``` ### Responses **`200 OK`**: the outcome. Each one writes an `approval` audit row, except a `timeout` caused by a server error: ```ts theme={"theme":"github-dark"} { resolution: 'allow_once' | 'allow_always' | 'deny' | 'expired' | 'timeout' } ``` `expired` means the 5 minutes ran out. `timeout` means the approval row disappeared, or the server hit an error while waiting: the route never answers `500`, it answers `timeout`. Treat `deny`, `expired` and `timeout` alike: do not run the delegation. An `allow_always` answer allows this delegation only, exactly like `allow_once`. The next request opens a new approval. **`400 Bad Request`**: missing `leaderAgentId`: ```json theme={"theme":"github-dark"} { "error": "leaderAgentId required" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/governance/delegation-approval \ -H 'Content-Type: application/json' \ -d '{"leaderAgentId":"<leader-id>","kind":"code","targetAgentName":"Reviewer Boo","task":"run the destructive migration"}' ``` *** ## `GET /api/approvals` Lists stored approval decisions from `approval_history`, newest first. This is a plain log, separate from the live approvals above. * **Query params**: | Param | Type | Default | Notes | | - | - | - | - | | `agentId` | string | none | Only this agent's decisions | | `limit` | number | `50` | Kept between 1 and 200. Send a number: a non-numeric value becomes `NaN`, and the query then runs with no limit | * **Request body**: none. ### Responses **`200 OK`**: ```ts theme={"theme":"github-dark"} { ok: true records: Array<{ id: number agentId: string action: string // 'allow-once' | 'allow-always' | 'deny', as stored by POST toolName: string details: string | null // JSON text createdAt: number // epoch ms }> } ``` **`500 Internal Server Error`**: a database error, with an empty list: ```json theme={"theme":"github-dark"} { "ok": false, "error": "<message>", "records": [] } ``` ### Example ```bash theme={"theme":"github-dark"} curl "http://localhost:18790/api/approvals?agentId=<agent-id>&limit=50" ``` *** ## `POST /api/approvals` Stores one approval decision in `approval_history` and returns the stored row. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { agentId: string // required action: 'allow-once' | 'allow-always' | 'deny' // required toolName: string // required details?: Record<string, unknown> | null // stored as JSON text } ``` ### Responses **`200 OK`**: the stored row (`null` if the insert returned nothing): ```ts theme={"theme":"github-dark"} { ok: true record: { id: number agentId: string action: string toolName: string details: string | null createdAt: number } | null } ``` **`400 Bad Request`**: one of: ```json theme={"theme":"github-dark"} { "ok": false, "error": "Invalid JSON body" } ``` ```json theme={"theme":"github-dark"} { "ok": false, "error": "agentId, action, and toolName are required" } ``` ```json theme={"theme":"github-dark"} { "ok": false, "error": "Invalid action" } ``` **`500 Internal Server Error`**: the insert failed: ```json theme={"theme":"github-dark"} { "ok": false, "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/approvals \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>","action":"allow-always","toolName":"web_search"}' ``` *** ## Error envelope The budget, audit and delegation-approval routes answer validation errors as `{ "error": "<message>" }`, adding a zod `details` object on the budget routes. The budget and audit routes do not catch database errors: one reaches Express's default handler and comes back as an HTML `500`, not JSON. The delegation approval never answers `500`; it answers `timeout`. The `/api/approvals` routes answer `{ "ok": false, "error": "<message>" }`, and the `GET` adds `records: []` on its `500`. On the default loopback bind no token is needed for these routes, though the same-origin guard still refuses cross-origin browser requests. A non-loopback bind without `STUDIO_ACCESS_TOKEN` stops the server at startup unless `CLAWBOO_ALLOW_INSECURE=1` is set. With the token set, the access gate covers every route here. See the [REST API overview](/reference/rest-api/index) and [Security](/operating/security). ## Related * [Governance](/concepts/governance): budgets, hard caps, circuit breakers and approvals, and which runs each one covers. * [Budgets and spend](/using/budgets-and-spend): set a budget in the dashboard, and how spend is counted per runtime. * [Approvals](/using/approvals): where a team lead answers a delegation approval. * [Verification](/concepts/verification): what writes `verification` audit rows. * [Tools and MCP API](/reference/rest-api/tools-and-mcp): the tool approval queue in the same `tool_call_approvals` table. * [Environment variables](/reference/environment-variables): `CLAWBOO_APPROVAL_TTL_MS` and the cleanup interval. # REST API overview Source: https://docs.claw.boo/reference/rest-api/index Call the Clawboo server API: base URL, authentication, the same-origin guard, rate limits, errors, streaming, and an index of all 200 routes. The Clawboo server answers JSON requests under `/api/` on the same port as the dashboard. The dashboard itself runs on this API, so you can script anything you do in the app. Use this page for the rules every route shares; each resource page lists its routes with parameters, bodies, responses and an example. ## At a glance | Topic | Default | | - | - | | Base URL | `http://localhost:18790` (the port can differ; see [Base URL](#base-url)) | | Request bodies | JSON, up to 2 MB | | Authentication | None on the default loopback bind; the `clawboo_access` cookie when a token is set | | Cross-origin | Browser requests from other origins are refused with `403` unless you allowlist them | | Rate limits | 3,000 requests a minute per client; 60 a minute across the sensitive routes | | Streaming | 9 routes answer with Server-Sent Events; one WebSocket route for the OpenClaw Gateway | | Routes | 199: 89 `GET`, 79 `POST`, 15 `DELETE`, 9 `PATCH`, 7 `PUT` ([index](#route-index)) | Check that the server is up (the response carries `"ok": true` when no boot check failed): ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/health ``` The examples on these pages assume the default port and no access token. With a token set, add the cookie shown under [Authentication](#authentication). ## Base URL The server picks its port at startup. It tries `18790` first and scans up to `18809` (20 ports), binding the first free one. Three environment variables change that: | Variable | Effect | | - | - | | `CLAWBOO_API_PORT` | Use exactly this port. The server fails to start if it is taken. Wins over the other two. | | `PORT` | Outside `--dev`, use exactly this port (fails if taken). Checked before the scan, so it beats the next one. | | `CLAWBOO_API_PORT_START` | Start the scan here instead of `18790`. | After binding, the server writes the port to `<clawboo home>/api-port.txt` (`~/.clawboo/api-port.txt` unless `CLAWBOO_HOME` is set). The CLI reads that file to find a running server; see [CLI](/reference/cli). ### Serving under a path prefix Set `CLAWBOO_BASE_PATH` (for example `/clawboo`) to serve the app under a prefix, typically behind a reverse proxy. The API then answers at `/clawboo/api/...`, and it still answers at the unprefixed `/api/...`, which the CLI, agent MCP connections and the update check use on the loopback port. The access cookie's `Path` follows the prefix. The value must be one or more segments of `A-Z a-z 0-9 . _ ~ -` and must not start with `api`. A malformed value stops the server at startup with a `SECURITY/CONFIG` error. ## Host binding The server listens on loopback (`127.0.0.1`) by default, so other machines cannot reach it. Set `HOST` to listen on another interface (`HOSTNAME` is ignored). A non-loopback bind without an access token makes the server refuse to start (`SECURITY: refusing to start`, exit code 1). Set `STUDIO_ACCESS_TOKEN`, or set `CLAWBOO_ALLOW_INSECURE=1` to run open on purpose. [Self-host securely](/guides/self-host-securely) walks through a safe remote setup. ## Authentication Clawboo has no user accounts. The only credential is an optional access token, read from the `STUDIO_ACCESS_TOKEN` environment variable at startup. * **No token (the default):** every route answers without credentials. This relies on the loopback bind; the [same-origin guard](#same-origin-guard) still refuses cross-origin browser requests. * **Token set:** every `/api/*` request and the WebSocket need a `clawboo_access` cookie whose value is the token. A browser gets the cookie by opening `http://localhost:18790/?access_token=<token>` once. The server answers `302`, sets the cookie (`HttpOnly`, `SameSite=Lax`, `Path=/` or the base path, plus `Secure` when the request arrived with `X-Forwarded-Proto: https`) and redirects to the same URL without the token. A script sends the cookie itself. Add `-b` to any example on these pages: ```bash theme={"theme":"github-dark"} curl -b "clawboo_access=$STUDIO_ACCESS_TOKEN" http://localhost:18790/api/agents ``` Keep the token out of API URLs: any request whose URL carries `access_token` gets that `302` answer (or a `401` when the token is wrong) instead of the route's response. | Request | Status | Body | | - | - | - | | Wrong token in `?access_token=` | `401` | `{ "error": "Invalid Clawboo access token." }` | | `/api/*` without a valid cookie | `401` | `{ "error": "Clawboo access token required. Open /?access_token=<token> once to set a cookie." }` | | WebSocket upgrade without a valid cookie | none | The socket is closed | The token may contain only `A-Z a-z 0-9 . _ ~ -`. With any other character the server does not start: it logs `SECURITY: refusing to start` and exits with code 1 on every bind, even with `CLAWBOO_ALLOW_INSECURE=1`. `openssl rand -hex 24` makes a suitable token. The comparison is constant-time, and the `/api/` prefix test ignores letter case. **Agent MCP connections.** An agent runtime that Clawboo starts reaches the four MCP endpoints (`/api/mcp/tasks`, `/api/mcp/memory`, `/api/mcp/tools`, `/api/mcp/teamchat`) without a cookie. It passes only with the `attachToken` Clawboo put in its attach URL, and only when it connects straight to the loopback port with no proxy forwarding headers. `/api/mcp/config` and `POST /api/mcp/attach-secret/rotate` always need the cookie. See [Tools and MCP](/reference/rest-api/tools-and-mcp). ## Same-origin guard Every `/api/*` request passes an origin check before the token check, whether or not a token is set. The guard answers `403` with `{ "error": "Cross-origin request blocked." }` unless all three headers pass: | Header | Allowed values | | - | - | | `Host` | `localhost`, any `127.x.x.x`, `::1`, the bind host, or a name in `CLAWBOO_ALLOWED_HOSTS` | | `Origin` (when sent) | `http` or `https` on `localhost`, `127.0.0.1` or `[::1]` at the API port; the bind host at the API port; port `5173` in dev; or an origin in `CLAWBOO_ALLOWED_ORIGINS` | | `Sec-Fetch-Site` (when sent) | `same-origin`, `same-site` or `none` | curl and other non-browser clients send no `Origin` or `Sec-Fetch-Site`, so a local script passes. A refused WebSocket upgrade gets `403 Forbidden` with no body. The guard is not authentication: a non-browser client can send any headers it likes. Use the access token for that. When clients reach the server by another name (a LAN address, or a reverse proxy that passes its own host name), add that name to `CLAWBOO_ALLOWED_HOSTS`. For a browser on that origin, also add the full origin (for example `https://clawboo.example.com`) to `CLAWBOO_ALLOWED_ORIGINS`. Both take comma-separated lists, and both only widen the allowlist. **CORS.** A built app serves the dashboard and the API from one origin, so it sends no CORS headers. In dev (`--dev`, with Vite on port 5173), the server answers credentialed CORS requests only from origins the guard allows; it never reflects an arbitrary origin. ## Requests and responses * **Bodies** are JSON (`Content-Type: application/json`), parsed once for every route with a 2 MB limit. Malformed JSON gets `400` and a larger body gets `413`. Both come from the body parser before any route runs, with an HTML error page rather than a JSON body. * **Paths are case-sensitive.** `/api/agents` matches; `/API/agents` does not. * **Unknown paths.** In a built app, a `GET` to an unknown `/api/` path returns the dashboard page (HTML, `200`), because the page fallback answers every unmatched `GET`. Other methods get `404`. When a call returns HTML, check the path. * **Long requests.** Some routes hold the connection open until work finishes, for example `POST /api/runtimes/:id/run` and `POST /api/governance/delegation-approval`. Their pages say so. ## Rate limits Two ceilings apply. Both count requests in memory, per client address, over a 60-second window. | Tier | Limit | Applies to | `429` body | | - | - | - | - | | General | 3,000 requests a minute | Every `/api/*` request | `{ "error": "Too many requests. Wait a moment and retry." }` | | Sensitive | 60 requests a minute, one budget shared by all 26 routes | The routes listed below, on top of the general tier | `{ "error": "Too many requests for this operation. Wait a moment and retry." }` | Responses carry `RateLimit-Policy`, `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (IETF draft 6). A `429` also carries `Retry-After` in seconds. On a sensitive route these headers describe the sensitive budget. The dashboard's page and asset files have their own ceiling and do not count against the API. The client address is the TCP peer. `X-Forwarded-For` is ignored, so behind a reverse proxy every client shares the proxy's budget. The sensitive tier covers the routes that install software, rewrite runtime configuration, start sign-ins or grant access: | Page | Sensitive routes | | - | - | | [Agents](/reference/rest-api/agents) | `POST /api/agents/:agentId/shell` | | [Memory](/reference/rest-api/memory) | `POST /api/memory/embedding/reindex`, `POST /api/memory/embedding/install` | | [Connectors](/reference/rest-api/connectors) | `POST /api/connectors/connect`; `PUT` and `DELETE /api/connectors/composio/key`; `POST /api/connectors/composio/apps/:slug/authorize`; `POST /api/connectors/custom`; `DELETE /api/connectors/custom/:slug`; `POST /api/connectors/:slug/disconnect`; `POST` and `DELETE /api/connectors/:slug/authorize`; `PUT /api/connectors/:slug/config` | | [Runtimes](/reference/rest-api/runtimes) | `POST /api/auth/cli-login/:tool`; `POST /api/runtimes/:id/install`, `/connect`, `/disconnect`, `/logout` | | [System](/reference/rest-api/system) | `POST /api/system/install-openclaw`, `/configure-openclaw`, `/auto-configure-openclaw`, `/gateway`, `/approve-device`, `/self-update`; `PATCH /api/system/openclaw-config` | | [Agent settings](/reference/rest-api/agent-settings) | `POST /api/exec-allowlist/revoke` | Every other route is on the general tier, including routes that start work, such as `POST /api/runtimes/:id/run`, `POST /api/teams/:id/chat` and the MCP transports. For what to do when the dashboard shows "Too many requests", see [Troubleshooting](/help/troubleshooting). ## Errors A JSON error body carries a human-readable `error` string. Many routes add fields (zod `details` on a validation failure, `findings`, `reason`, `code`), and some answer `{ "ok": false, "error": "..." }`. Each resource page lists its shapes. Three kinds of failure are not this JSON: body-parser errors (HTML, see above), the MCP transports (JSON-RPC error objects), and the SSE routes, which report a failure as a frame inside the stream once it has opened. | Status | Meaning | | - | - | | `400` | The body, query or path failed validation, or the JSON was malformed | | `401` | A token is set and the cookie is missing or wrong | | `403` | The same-origin guard refused the request, or a schedule's source does not allow that change | | `404` | Unknown resource or id | | `409` | A conflict that retrying will not fix: a task someone else claimed, an illegal status change, a failed check blocking `done`, a dependency cycle, an unreadable worktree. Treat it as an answer, not a transient error. | | `413` | A body over 2 MB, or a previewed file over 8 MB | | `422` | Well-formed but refused: blocked prompt-injection content, a capability whose tier forbids the change, a connector that cannot run here, a worktree for a task kind that gets none, or a run the task runner refuses (`budget_paused`, `too_deep`, `workspace_unavailable`) | | `429` | A rate limit | | `500` | An unexpected failure inside the handler | | `502` | An upstream refused the request: the OpenClaw Gateway, Composio or a connector | | `503` | The server's connection to the OpenClaw Gateway is down (`{ "error": "gateway_disconnected" }`) | ## Streaming routes Nine routes answer with Server-Sent Events (`Content-Type: text/event-stream`) and write `data: <json>` frames. Each page documents its frames. | Routes | Page | | - | - | | `POST /api/system/install-openclaw`, `POST /api/system/gateway` (start and restart), `POST /api/system/self-update` | [System](/reference/rest-api/system) | | `POST /api/runtimes/:id/install`, `POST /api/auth/cli-login/:tool` | [Runtimes](/reference/rest-api/runtimes) | | `POST /api/memory/embedding/install` | [Memory](/reference/rest-api/memory) | | `GET /api/agents/:agentId/chat/stream` | [Agents](/reference/rest-api/agents) | | `GET /api/teams/:id/chat/stream` | [Teams](/reference/rest-api/teams) | | `GET /api/obs/stream` | [Observability](/reference/rest-api/observability) | The three tail routes (the two chat streams and the event stream) put an `id:` line on every stored row, so an `EventSource` resumes from `Last-Event-ID` after a reconnect. They start in different places when you send no cursor: the chat streams start at the newest message and send only what follows, while the event stream replays the whole log for its scope first. A streaming route can still answer with a JSON error before the stream opens (for example `409` from the model install). The `GET` routes of the MCP transports are also event streams, but they carry MCP JSON-RPC, not Clawboo frames. ## WebSocket: the Gateway proxy `/api/gateway/ws` is the one WebSocket route. It carries the dashboard's connection to the OpenClaw Gateway through the server, which adds the Gateway token and Clawboo's signed device identity to the upstream connect frame, so the browser never sees the Gateway token. The route also answers at `<base path>/api/gateway/ws`; any other upgrade is closed. The same-origin guard runs on the upgrade first (`403 Forbidden` on failure), then the access gate (with a token set, a missing cookie closes the socket). The frames are the Gateway's own protocol; see [Gateway and events](/internals/gateway-and-events). ## Route index The router registers 200 routes: 89 `GET`, 80 `POST`, 15 `DELETE`, 9 `PATCH`, 7 `PUT`. Each belongs to one page: | Page | Routes | What it covers | | - | - | - | | [Agents](/reference/rest-api/agents) | 19 | `/api/agents*`: list, create, sync, delete, model, agent files, sessions, workspaces, browser screenshots, the native shell switch, 1:1 agent chats | | [Teams](/reference/rest-api/teams) | 21 | `/api/teams*` (members, rooms, onboarding, activity summary, team chat), `/api/team-rules/:teamId`, `/api/team-chat*` | | [Board](/reference/rest-api/board) | 24 | `/api/board*`: tasks, claim, status changes, comments, retry, assign, run ledger, dependencies, worktrees, the workspace file view and preview | | [Memory](/reference/rest-api/memory) | 9 | `/api/memory*`: search, save, browse, graph, feedback, outcomes, embedding status, reindex, model install | | [Capabilities](/reference/rest-api/capabilities) | 2 | `/api/capabilities*`: the merged skills, tools and connectors inventory, and its actions | | [Connectors](/reference/rest-api/connectors) | 21 | `/api/connectors*` (including Composio and custom connectors), `/api/grants*` | | [Boo Zero](/reference/rest-api/boo-zero) | 9 | `/api/boo-zero/*`: team briefs, the global brief, display names, the leader override | | [Runtimes](/reference/rest-api/runtimes) | 17 | `/api/runtimes*`, `/api/providers*`, `/api/onboarding/*`, `/api/auth/cli-login/:tool` | | [Schedules](/reference/rest-api/schedules) | 7 | `/api/schedules*`: Routines and OpenClaw Gateway cron jobs | | [Governance](/reference/rest-api/governance) | 7 | `/api/governance/*` (budgets, audit log, delegation approval), `/api/approvals` | | [Costs](/reference/rest-api/costs) | 4 | `/api/cost-records*`, `/api/fleet/summary` | | [Observability](/reference/rest-api/observability) | 8 | `/api/obs/*`, `/api/eval/smoke` | | [Tools and MCP](/reference/rest-api/tools-and-mcp) | 18 | `/api/tools*`, `/api/mcp/config`, `/api/mcp/attach-secret/rotate`, the four MCP transports under `/api/mcp/*` | | [Agent settings](/reference/rest-api/agent-settings) | 10 | `/api/personality`, `/api/exec-settings*`, `/api/exec-allowlist*`, `/api/skills` | | [Chat history](/reference/rest-api/chat-history) | 4 | `/api/chat-history*` | | [Settings and health](/reference/rest-api/settings) | 6 | `/api/settings`, `/api/health*`, `/api/graph-layout` | | [System](/reference/rest-api/system) | 11 | `/api/system/*`: OpenClaw install, setup and Gateway control, models, device pairing, Clawboo's own version and update | | [Catalog](/reference/rest-api/catalog) | 3 | `/api/catalog/*`: the marketplace's agent and team templates | Source of truth for maintainers: the router `apps/web/server/api/index.ts`, the middleware order in `apps/web/server/index.ts`, `apps/web/server/lib/rateLimit.ts`, and `packages/gateway-proxy/src/access-gate.ts` and `origin-guard.ts`. ## Related * [Self-host securely](/guides/self-host-securely): reach Clawboo from another machine with a token and the allowlists. * [Security](/operating/security): the access gate, the same-origin guard and what each protects. * [Environment variables](/reference/environment-variables): `STUDIO_ACCESS_TOKEN`, `HOST`, the port and allowlist variables. * [Limits and defaults](/reference/limits-and-defaults): every limit and timeout in one table. * [MCP tools](/reference/mcp-tools): the tools agents call over the MCP transports. # Memory API Source: https://docs.claw.boo/reference/rest-api/memory REST reference for shared memory: search, save, browse, the memory graph, feedback, outcomes, and the embeddings behind vector search. These routes read and write the shared [memory](/concepts/memory). Memory holds two kinds of entry: **facts** (short statements, such as "Ship on Thursdays") and **procedures** (named, versioned how-to text). The routes search, save and browse entries, read the memory graph, record how a fact worked out, and inspect or repair the embeddings that power vector search and similarity links. Agents reach the same store through the Memory MCP server (see [MCP tools](/reference/mcp-tools)), so a fact saved here is searchable by agents, and the reverse. > [!NOTE] > > Memory is always on. Search uses full-text matching, plus vector matching when an embedding provider is available; without one, `vector` and `hybrid` searches fall back to full text. `GET /api/memory/provider` shows which provider is in use and why. The `GET` routes check their query string as strictly as the `POST` routes check their bodies, so an out-of-range `limit` or an empty `query` is a **400**. Every route is on the general rate tier except `POST /api/memory/embedding/reindex` and `POST /api/memory/embedding/install`, which are on the sensitive tier (see [rate limits](/reference/rest-api/index#rate-limits)). ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/memory` | Search facts (full text, vector or hybrid), optionally scoped | No | | POST | `/api/memory` | Save a fact (the default) or a procedure | No | | GET | `/api/memory/browse` | List recent facts and procedures, optionally scoped | No | | GET | `/api/memory/graph` | The graph: nodes, edges and communities (`?limit=` 1 to 500, scoped) | No | | POST | `/api/memory/feedback` | Record an outcome on a fact: `useful`, `dead_end` or `corrected` | No | | GET | `/api/memory/outcomes` | A fact's outcome trail (`?factId=&limit=` up to 200) | No | | GET | `/api/memory/provider` | The embedding provider, and why it is or is not usable | No | | POST | `/api/memory/embedding/reindex` | Re-check the provider and index facts it has no vector for | No | | POST | `/api/memory/embedding/install` | Install the embedding model through the local Ollama | SSE | > [!NOTE] > > Saving runs a fact's `title` and `content`, and a procedure's `content`, through a secret scrubber before anything is embedded or stored. Text that looks like a credential (API keys, tokens, `NAME_KEY=value` pairs, PEM blocks) is replaced with `[REDACTED]`. The scrubber matches patterns, so a secret in an unusual shape can still get through: keep secrets out of memory. *** ## `GET /api/memory` Searches stored facts. The query string is checked with the same rules as the agents' `memory_search` tool. Each result is a fact with a `score` from 0 to 1 and a `matchedVia` field saying how it matched. * **Query params** | Param | Type | Required | Notes | | - | - | - | - | | `query` | string | Yes | 1 to 2000 chars (a missing or blank query is a 400) | | `mode` | `'fts' \| 'vector' \| 'hybrid'` | No | Default: `hybrid` if an embedding provider resolved, else `fts`. `vector`/`hybrid` degrade to `fts` when no provider is reachable | | `limit` | integer | No | 1 to 100; default `10` | | `teamId` | string | No | Scope filter (see scope note below) | | `agentId` | string | No | Scope filter (see scope note below) | * **Request body**: none. > [!NOTE] > > **Scope is inclusive.** A query scoped by `teamId` or `agentId` also sees global facts (those saved with no scope), so global memory is visible to every scoped search. ### Responses **`200 OK`**: the (possibly empty) result list: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true results: Array<{ id: string title: string content: string tags: string[] scopeAgentId: string | null scopeTeamId: string | null tenantId: string | null createdByAgentId: string | null // provenance: who saved it, when recorded createdByRuntime: string | null sourceTaskId: string | null sourceSessionKey: string | null createdAt: number // epoch ms updatedAt: number // epoch ms score: number // 0..1, higher = more relevant matchedVia: 'fts' | 'vector' | 'hybrid' }> learning: Record<string, LearningEntry> // by fact id: how each result has fared in runs } ``` `matchedVia` reflects the mode that actually ran, not the mode requested: a `vector`/`hybrid` request with no embedding provider runs as `fts` and reports `matchedVia: 'fts'`. In `hybrid` mode the score blends the cosine similarity (60%) and an FTS-hit signal (40%). **`400 Bad Request`**: the query failed validation (for example a blank `query`, a `limit` outside 1 to 100, or an unknown `mode`): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "invalid query", "details": { "...": "zod flatten output" } } ``` **`500 Internal Server Error`**: any failure running the search: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl 'http://localhost:18790/api/memory?query=deploy%20checklist&mode=hybrid&teamId=<team-uuid>&limit=5' ``` *** ## `POST /api/memory` Saves a memory entry: a **fact** (the default, when `kind` is left out or is `"fact"`) or a **procedure** (`kind: "procedure"`). A fact is a lasting statement ("The client prefers short answers"); a procedure is named how-to text that keeps every version. Saving a procedure under a name and scope that already exist adds a new version (the highest version plus 1) instead of overwriting. Entries saved here record `createdByRuntime: 'user'` and no agent, so you can tell them apart from what agents save. * **Path/query params**: none. * **Request body**: one of: **Fact (default):** ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { kind?: 'fact' // optional; absence means fact title: string // 1 to 500 chars content: string // 1 to 50,000 chars tags?: string[] // up to 50 tags, each up to 100 chars scope?: { agentId?: string | null teamId?: string | null tenantId?: string | null } } ``` **Procedure:** ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { kind: 'procedure' // required discriminant name: string // 1 to 200 chars content: string // 1 to 100,000 chars scope?: { agentId?: string | null teamId?: string | null tenantId?: string | null } } ``` ### Responses **`200 OK`**: a fact was saved. It is embedded when a provider is available; a failed embedding never blocks the save: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true fact: { id: string title: string // scrubbed content: string // scrubbed tags: string[] scopeAgentId: string | null scopeTeamId: string | null tenantId: string | null createdByAgentId: string | null // null for a save through this route createdByRuntime: string | null // 'user' for a save through this route sourceTaskId: string | null sourceSessionKey: string | null createdAt: number // epoch ms updatedAt: number // epoch ms } } ``` **`200 OK`**: a procedure was saved (`kind: 'procedure'`): ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true procedure: { id: string name: string version: number // the previous highest version + 1 content: string // scrubbed scopeAgentId: string | null scopeTeamId: string | null tenantId: string | null createdByAgentId: string | null // null for a save through this route createdByRuntime: string | null // 'user' for a save through this route sourceTaskId: string | null sourceSessionKey: string | null createdAt: number // epoch ms } } ``` **`400 Bad Request`**: the body failed validation (for example a `title` or `content` that is too long, too many tags, or a procedure with no `name`): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "invalid body", "details": { "...": "zod flatten output" } } ``` **`500 Internal Server Error`**: any failure writing the entry: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Save a team-scoped fact curl -X POST http://localhost:18790/api/memory \ -H 'Content-Type: application/json' \ -d '{"title":"Release cadence","content":"Ship on Thursdays.","tags":["process"],"scope":{"teamId":"<team-uuid>"}}' # Save a procedure (a repeat name and scope adds a version) curl -X POST http://localhost:18790/api/memory \ -H 'Content-Type: application/json' \ -d '{"kind":"procedure","name":"deploy-runbook","content":"1. Run the tests. 2. Tag the release."}' ``` *** ## `GET /api/memory/browse` Lists the most recent facts (newest `updatedAt` first) and procedures (newest first, one row per version). Facts are scoped the same inclusive way as search. Procedures are filtered by `teamId` only: `agentId` does not narrow them. * **Query params** | Param | Type | Required | Notes | | - | - | - | - | | `limit` | integer | No | 1 to 200; default `10` (applied to facts and to procedures separately) | | `teamId` | string | No | Scope filter (inclusive of global rows) | | `agentId` | string | No | Scope filter (inclusive of global rows) | * **Request body**: none. ### Responses **`200 OK`**: facts and procedures side by side: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true facts: Array<{ id: string title: string content: string tags: string[] scopeAgentId: string | null scopeTeamId: string | null tenantId: string | null createdByAgentId: string | null createdByRuntime: string | null sourceTaskId: string | null sourceSessionKey: string | null createdAt: number updatedAt: number }> procedures: Array<{ id: string name: string version: number content: string scopeAgentId: string | null scopeTeamId: string | null tenantId: string | null createdByAgentId: string | null createdByRuntime: string | null sourceTaskId: string | null sourceSessionKey: string | null createdAt: number }> learning: Record<string, LearningEntry> // by fact id } ``` **`400 Bad Request`**: `limit` out of the `1..200` range: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "invalid query", "details": { "...": "zod flatten output" } } ``` **`500 Internal Server Error`**: any failure reading the store: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl 'http://localhost:18790/api/memory/browse?teamId=<team-uuid>&limit=50' ``` *** ## `GET /api/memory/graph` The store as a graph: facts and procedures are nodes, joined by similarity, shared-tag and version edges and grouped into communities. Every edge comes from something stored. * **Query params**: `limit` (1 to 500 facts, newest first), `teamId`, `agentId` (scope, as for search). **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { ok: true graph: { nodes: MemoryGraphNode[] // facts and procedures; a procedure is one node however many versions. // A fact with hasEmbedding false carries embedSkipped: true when the provider turned it down, // so a client can tell "not indexed yet" from "never will be". edges: Array<{ id: string; source: string; target: string; kind: 'similarity' | 'tag' | 'version'; weight: number; sharedTags: string[] }> communities: Array<{ id: number; label: string; size: number }> // id is positional: not stable across payloads totalFacts: number totalProcedures: number truncated: boolean similarityAvailable: boolean // at least two facts carry vectors from the same model } provider: { id: string; dimensions: number } | null } ``` **`400 Bad Request`**: `{ "error": "invalid query", "details": { ... } }`. *** ## `POST /api/memory/feedback` Records how a fact worked out. Outcomes feed the `learning` entry returned with each fact by search and browse. `factId` can be the full id, or a prefix of at least 8 characters that matches exactly one fact. * **Body**: `{ factId: string; outcome: 'useful' | 'dead_end' | 'corrected'; note?: string; scope?: { teamId?, agentId? } }`. `corrected` requires a `note`. **`200 OK`**: `{ ok: true, outcome: MemoryOutcome, learning: LearningEntry }`, the recorded outcome and the fact's updated learning entry. **`400 Bad Request`**: `{ "error": "invalid body", "details": { ... } }`, or `{ "error": "corrected requires a note" }`. **`404 Not Found`**: `{ "error": "unknown fact" }`. *** ## `GET /api/memory/outcomes` Every outcome recorded for one fact, newest first. `factId` can be a prefix, as for feedback. * **Query params**: `factId` (required), `limit` (1 to 200). **`200 OK`**: `{ ok: true, factId: string, outcomes: MemoryOutcome[] }`, where each outcome is `{ id, factId, outcome, note, agentId, teamId, taskId, runtime, createdAt }`. **`400 Bad Request`**: `{ "error": "invalid query", "details": { ... } }`. **`404 Not Found`**: `{ "error": "unknown fact" }`. *** ## `GET /api/memory/provider` Reports the embedding provider behind vector and hybrid search and the graph's similarity links, and why it is the one in use. Providers are tried in this order: 1. Ollama at `http://localhost:11434` with the `nomic-embed-text` model installed. This address is fixed: `OLLAMA_BASE_URL`, which Clawboo Native honors, is not used for embeddings. 2. OpenAI, with `OPENAI_API_KEY` from the server's environment or a key saved under **Providers**. OpenClaw's `~/.openclaw/.env` is not read. 3. None: search uses full text only. Two rules apply on top of that order: * **Ollama without the model cannot embed.** With no OpenAI key this is reported as `ollama-model-missing`. With a key, OpenAI serves, and `missingModel` names the model whose install would move embeddings onto this machine. * **Local first.** Once any fact holds an Ollama vector, an OpenAI key is not used on its own, and an Ollama that stops answering is reported as `ollama-unreachable` instead of being replaced quietly. To use OpenAI during such an outage, send `POST /api/memory/embedding/reindex` with `allowRemote: true`. That choice lasts until one of these happens: * any Clawboo process (the server, or the stdio Memory server) finds Ollama serving again; * the OpenAI key is disconnected under **Providers** or **Runtimes**; * a check finds no OpenAI key at all (a key store that failed to read does not count). A pending `reembedAll` is dropped along with it. `CLAWBOO_DISABLE_EMBEDDINGS=1` turns embeddings off: `provider` is `null` and `status.state` is `disabled`. The server re-checks the provider on its own, whether or not anything reads this route: | Situation | Next check | | - | - | | No provider can serve, or OpenAI is standing in | Every 30 seconds | | Ollama is serving | Every 10 minutes | | An embedding call failed, or a key was connected or disconnected | Straight away | A disconnected key stops being used at once, including by Memory MCP sessions already open, which ask for the current provider on every call. Reading this route while facts wait to be indexed also starts indexing them, unless the last attempt failed within the past minute. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: `provider` keeps its original shape (and is `null` whenever `status.state` is not `ready`); `status` explains it: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { provider: { id: string; dimensions: number } | null // e.g. 'ollama:nomic-embed-text', 'openai:text-embedding-3-small' status: { state: 'ready' | 'ollama-model-missing' | 'ollama-unreachable' | 'none' | 'disabled' provider: { id: string; dimensions: number } | null remote: boolean // the provider sends fact text off this machine missingModel: string | null // a model whose install is a fix: none can embed without it, or it would move embeddings local remoteAvailable: boolean // an OpenAI key exists that local-first is deliberately not using pending: number | null // facts the running pass (else an automatic one) would index; null with no provider factCount: number // every fact in the store: the scale of what switching to OpenAI sends localFirst: boolean // the store has had local vectors; a remote provider serving is the user's choice vectorsWritten: number // vectors written by indexing since the server started; only grows skipped: number // facts the provider turned down (they still match by keyword) indexing: boolean // a background indexing pass is running installing: boolean // a model install is running lastError: string | null // the most recent embedding or indexing failure } } ``` With a remote provider, `pending` counts only facts that have no vector at all: an automatic pass never re-uploads facts another provider already indexed. The exception is a switch the user asked for (`reembedAll`) that has not finished: until it has, `pending` also counts the facts still carrying another provider's vectors, and automatic retries carry on with them. **`500 Internal Server Error`**: an unexpected failure: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/memory/provider ``` *** ## `POST /api/memory/embedding/reindex` Re-checks the provider straight away and starts indexing every fact that has no vector, including facts the provider turned down before. In the background it sends the provider one short fixed string (never fact text), so a provider that has recovered stops reporting its last failure. Indexing runs in the background, newest facts first, and never changes a fact's `updatedAt`. **Rate tier: sensitive** (60 requests a minute, shared with the other sensitive routes). * **Request body** (optional): `{ allowRemote?: boolean; reembedAll?: boolean }`. * `allowRemote: true` records the choice to use an OpenAI key although the store was indexed locally (see `GET /api/memory/provider` for when the choice ends). * `reembedAll: true` also replaces vectors another provider made, which an automatic pass never does for a remote provider. The request stays pending for the provider it found until every fact is re-indexed: it survives a pass that fails partway and a server restart, and is dropped once another provider takes over. ### Responses **`202 Accepted`**: indexing started, or there was nothing to do. Poll `GET /api/memory/provider` for progress: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { status: EmbeddingStatus } // the same shape as GET /api/memory/provider's `status` ``` **`500 Internal Server Error`**: the re-check or the status read failed: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/memory/embedding/reindex ``` *** ## `POST /api/memory/embedding/install` Asks the local Ollama to download the embedding model (through Ollama's own `/api/pull`) and streams the progress as Server-Sent Events. The model is always `nomic-embed-text`; any request body is ignored. On success the provider is re-checked, which starts indexing the store. Closing the connection cancels the download; Ollama keeps what it already fetched. **Rate tier: sensitive** (60 requests a minute, shared with the other sensitive routes), because it downloads over the network. * **Request body**: none. ### Responses **`409 Conflict`** (JSON, not a stream): there is nothing to install, because `status.missingModel` is `null` (Ollama is not running, or already has the model). `state` is the current `status.state`, or `unknown` when the status check itself failed: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "nothing to install", "detail": "Ollama is not running, or already has the embedding model", "state": "ready" } ``` **`200 OK`** (`text/event-stream`): one `data:` frame per event: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { type: 'progress'; message: string; completed?: number; total?: number } // bytes of the model layer only { type: 'complete'; status: EmbeddingStatus } // installed and re-checked { type: 'error'; code: 'PULL_FAILED' | 'IN_PROGRESS' | 'CANCELLED'; message: string } ``` `message` is a plain phrase (`Preparing the download`, `Downloading`, `Verifying`, `Finishing`). Byte counts follow the largest layer only, so the percentage does not restart for each small file after the model. A stream that ends without Ollama reporting success is an `error`, never a `complete`. `IN_PROGRESS` means another install is already running. ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -N -X POST http://localhost:18790/api/memory/embedding/install ``` ## Error envelope Most error bodies are `{ "error": "<message>" }`. The validating routes (search, save, browse, graph, feedback, outcomes) add `details`, the zod `flatten()` output, on a `400`: `{ "error": "invalid query", "details": { ... } }`. The install route's `409` carries `detail` and `state` instead, since nothing in the request was wrong. ## Related * [Memory](/concepts/memory): what facts and procedures are, scopes, and how agents use them. * [Memory panel](/using/memory): search, save and browse from the dashboard. * [MCP tools](/reference/mcp-tools): the Memory tools agents call against the same store. * [Tools and MCP API](/reference/rest-api/tools-and-mcp): attach the Memory MCP server to an agent. * [@clawboo/db](/reference/packages/db): `SqliteMemoryStore` and the memory schemas. # Observability API Source: https://docs.claw.boo/reference/rest-api/observability REST reference for the event log: the feed, traces, errors, agent health, the task graph, a live stream, event ingest, and the eval smoke run. These routes read the server's event log, the durable record of what happened on the board and in runs: tasks created and claimed, status changes, runs, tool calls, costs, approvals and errors. They return the raw feed, one trace with its totals, the error feed, each agent's health, the task-delegation graph, and a live stream; one route accepts events from outside processes. `POST /api/eval/smoke` runs the built-in eval suite. The fleet summary (`GET /api/fleet/summary`) is on the [Costs API](/reference/rest-api/costs) page. Observability is always on: there is no switch, and every route here answers. A trace is all events that share a `traceId`; the graph, metrics, error list and agent health are all worked out from the same rows. Every route is on the general rate tier (see [rate limits](/reference/rest-api/index#rate-limits)). > [!NOTE] > > Every response that carries event payloads masks credential-shaped keys and values in the event's `data` with `••••` before sending it, on top of the secret scrub applied when the event was written. Numbers such as token counts and cost are kept. `GET /api/obs/stream` is a Server-Sent Events stream, documented below with its frames rather than a JSON body. ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/obs/events` | Query the orchestration event feed | No | | GET | `/api/obs/traces/:traceId` | One reconstructed trace + aggregate metrics | No | | GET | `/api/obs/errors` | The error feed (filter to likely Clawboo bugs) | No | | GET | `/api/obs/health` | Each agent's state: working, idle, stalled or zombie | No | | GET | `/api/obs/graph` | The task and delegation graph, built from the log | No | | GET | `/api/obs/stream` | SSE live-tail of the log, scoped by team/task/agent | SSE | | POST | `/api/obs/ingest` | Add events an outside process observed to the log | No | | POST | `/api/eval/smoke` | Run the built-in eval smoke suite (no model calls) | No | *** ## `GET /api/obs/events` Returns events from the log that match the query, ordered by `seq`, a number that only ever grows. The default order is `asc` (oldest first, the order things happened); pass `order=desc` for newest first. * **Path params**: none. * **Query params**: | Param | Type | Notes | | - | - | - | | `teamId` | string | Match events for one team. | | `taskId` | string | Match events for one board task. | | `agentId` | string | Match events for one agent (the per-agent activity scope). | | `traceId` | string | Match events sharing a trace. | | `kinds` | string | Comma-separated list of event kinds; empty/blank values dropped. | | `since` | number | Only events with `ts` at or after this time (epoch ms). | | `afterSeq` | number | Only events with a larger `seq` (a resume cursor). | | `limit` | number | Row cap, clamped to 1-5000 (defaults to 500 when omitted). | | `order` | `asc` \| `desc` | `desc` only when literally `"desc"`; anything else is `asc`. | * **Request body**: none. ### Responses **`200 OK`**: the matched events. Each row is the full stored shape with its `data` field redacted (still a JSON string after masking): ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { events: Array<{ seq: number id: string ts: number kind: OrchestrationEventKind teamId: string | null taskId: string | null agentId: string | null runtime: string | null traceId: string | null spanId: string | null parentSpanId: string | null correlationId: string | null data: string // JSON string, credential keys/values masked with •••• tenantId: string | null createdAt: number }> } ``` `OrchestrationEventKind` is one of 25 kinds: `task_created`, `task_claimed`, `status_changed`, `comment_added`, `dep_linked`, `execution_started`, `execution_completed`, `tool_call`, `tool_result`, `cost`, `approval_requested`, `approval_resolved`, `error`, `span_start`, `span_end`, `session_rotated`, `routine_fired`, `routine_dispatched`, `routine_completed`, `routine_error`, `team_chat_post`, `speaker_selected`, `turn_bound_hit`, `grant_decision`, `connector_health`. See [Events and errors](/reference/events-and-errors) for each kind's `data`. **`500 Internal Server Error`**: any read failure: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Last 100 cost + error events for a team, recent first curl "http://localhost:18790/api/obs/events?teamId=<team-uuid>&kinds=cost,error&order=desc&limit=100" ``` *** ## `GET /api/obs/traces/:traceId` Returns one trace: every event that shares the `traceId`, oldest first. A whole multi-agent task (the lead, the agents it delegated to, their tool calls) reads from this. The totals in `metrics` are worked out before each event's `data` is masked, so cost and token figures are exact. * **Path params**: `traceId` (string). * **Request body**: none. ### Responses **`200 OK`**: the trace events plus the aggregate metrics: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { traceId: string events: Array<{/* same redacted row shape as GET /api/obs/events */}> metrics: { totalCostUsd: number inputTokens: number outputTokens: number toolErrorRate: number // failed tool results / total tool results (0 when none) toolCalls: number toolErrors: number eventCounts: Partial<Record<OrchestrationEventKind, number>> activeAgents: number tokensPerMinute: number // output tokens/min over the window (0 when window < 1s) } } ``` The trace read is capped at 5000 events. An unknown `traceId` returns `200` with an empty `events` array and zeroed `metrics`. **`500 Internal Server Error`**: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/obs/traces/<trace-id> ``` *** ## `GET /api/obs/errors` The error feed: `error` events, newest first, in a compact shape. Each error carries the class Clawboo sorted it into; an error it could not sort (class `Unknown`) is flagged `harnessBug: true`, a likely bug in Clawboo itself. Pass `harnessBug=true` to see only those. * **Path params**: none. * **Query params**: | Param | Type | Notes | | - | - | - | | `harnessBug` | string | `"true"` returns only errors flagged `harnessBug`; any other value returns all. | | `since` | number | Only errors with `ts` at or after this time (epoch ms). | * **Request body**: none. The route always reads the 500 newest `error` events by `ts`, then applies the `harnessBug` filter. Older errors are not reachable here; use `GET /api/obs/events?kinds=error` with `afterSeq` to page through them. ### Responses **`200 OK`**: the errors and the count of flagged ones. The whole response is masked before it is sent: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { errors: Array<{ seq: number ts: number taskId: string | null agentId: string | null runtime: string | null errorClass: string // from event data; defaults to 'Unknown' harnessBug: boolean // from event data message: string // from event data; defaults to '' }> harnessBugCount: number // flagged errors among the 500 read, before the filter } ``` `harnessBugCount` ignores the `harnessBug` filter, so a client can show the count while listing every error. **`500 Internal Server Error`**: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Errors flagged as likely Clawboo bugs in the last hour curl "http://localhost:18790/api/obs/errors?harnessBug=true&since=$(($(date +%s%3N) - 3600000))" ``` *** ## `GET /api/obs/health` Each agent's current state, worked out from the event log against the current time. An agent with an open run is `working` while events keep arriving, `stalled` after 5 quiet minutes and `zombie` after 30 (its process has almost certainly died; the server's stale-run cleanup closes such runs). An agent with no open run is `idle`. * **Path params**: none. * **Query params**: `teamId` (string), scope to one team. * **Request body**: none. Only the **most recent 5000 events** in scope are read. The event log only grows (nothing prunes it), so this works from a recent window, not the whole history: an agent whose activity has scrolled out of the window stops appearing. ### Responses **`200 OK`**: one entry per agent that appears in the window: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { agents: Array<{ agentId: string status: 'working' | 'idle' | 'stalled' | 'zombie' lastEventTs: number activeTaskId: string | null openExecutions: number costUsd: number }> } ``` **`500 Internal Server Error`**: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl "http://localhost:18790/api/obs/health?teamId=<team-uuid>" ``` *** ## `GET /api/obs/graph` Builds the task graph from the event log: tasks with their status and cost, the delegation and dependency edges between them, and the agent-to-agent delegations they imply. The same events always give the same graph. * **Path params**: none. * **Query params**: `teamId` (string), scope to one team. * **Request body**: none. Only the **most recent 5000 events** in scope are read, as for health. A task whose events have all left that window stops appearing, and a task partly inside it is drawn from the events that remain. ### Responses **`200 OK`**: the graph. It carries no raw event `data`, so nothing in it is masked: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { tasks: Array<{ id: string title: string | null status: string assigneeAgentId: string | null parentTaskId: string | null runtime: string | null teamId: string | null costUsd: number }> taskEdges: Array<{ id: string source: string target: string kind: 'delegation' | 'dependency' }> agents: Array<{ id: string costUsd: number taskIds: string[] }> agentEdges: Array<{ id: string source: string target: string kind: 'delegation' | 'dependency' // edges between agents are always 'delegation' }> } ``` **`500 Internal Server Error`**: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl "http://localhost:18790/api/obs/graph?teamId=<team-uuid>" ``` *** ## `GET /api/obs/stream` A Server-Sent Events tail of the event log, optionally scoped to a team, task or agent. The route polls the log every 750 ms past a `seq` cursor and sends any new events. It reads the database, so it also sees events that another process writes to the same database file. Resume with the `EventSource` `Last-Event-ID` header or `?since=<seq>`. **Where it starts.** With no `Last-Event-ID` and no `since`, the cursor is `0`: the stream first replays the whole log for its scope, 500 events per poll, and only then follows new events. This is the opposite of the chat streams, which start at the newest message. To watch only new events, pass the newest `seq` as `since` (read it with `GET /api/obs/events?order=desc&limit=1`). * **Path params**: none. * **Query params**: | Param | Type | Notes | | - | - | - | | `teamId` | string | Scope the tail to one team. | | `taskId` | string | Scope the tail to one board task. | | `agentId` | string | Scope the tail to one agent. | | `since` | number | Resume after this `seq`. An invalid or negative value means `0`. `Last-Event-ID` takes priority. | * **Request body**: none. > [!NOTE] > > This is an SSE route: there is no JSON response body, and the catalog below describes the frames. Each poll reads up to 500 events past the cursor, oldest first. The cursor only moves forward, so resuming from an old `since` or `Last-Event-ID` replays everything after it. ### Connection On open, the handler writes `HTTP/1.1 200` with: ```http theme={"theme":{"light":"github-dark","dark":"github-dark"}} Content-Type: text/event-stream; charset=utf-8 Cache-Control: no-cache, no-transform Connection: keep-alive X-Accel-Buffering: no ``` then emits a `: connected` comment frame and flushes any rows already past the cursor. ### Event catalog | Frame | When | Shape | | - | - | - | | `: connected` | Immediately on open | SSE comment (ignored by `EventSource`) | | event data | Per new log row (poll every 750 ms) | `id: <seq>` line + `data: <json>` line (the full event row, `data` field redacted) | | `: keepalive` | Every 20 s while open | SSE comment (keeps the connection warm) | The `data:` payload is one stored event row, identical in shape to a `GET /api/obs/events` row, with its inner JSON `data` field masked (`••••`). The `id:` value is the row's `seq`; the browser sends it back as `Last-Event-ID` on auto-reconnect, so the tail resumes without gaps. Example frames: ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} : connected id: 4187 data: {"seq":4187,"id":"...","ts":1718450000000,"kind":"tool_call","taskId":"...","agentId":"...","runtime":"claude-code","traceId":"...","data":"{\"toolName\":\"read_file\"}","tenantId":null,"createdAt":1718450000000} : keepalive ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -N "http://localhost:18790/api/obs/stream?taskId=<task-uuid>" # Resume from a known seq curl -N "http://localhost:18790/api/obs/stream?taskId=<task-uuid>&since=4187" ``` *** ## `POST /api/obs/ingest` Adds events that a process outside the server observed to the log. Nothing in the dashboard calls it: the server records its own runs' tool calls, results and errors itself. Because any process that can reach the API may call it, only five kinds are accepted: `tool_call`, `tool_result`, `error`, `grant_decision` and `connector_health`. Board events (`task_created`, `status_changed`, `execution_started` and the rest) come only from the server and are never accepted here. A malformed event is skipped without failing the batch. The first three kinds store `data` as sent (it is masked when read). For `connector_health` and `grant_decision` the server rebuilds `data` from a fixed shape and drops every other field: | Kind | Kept fields | | - | - | | `connector_health` | `connectorId`, `health` (`unknown`, `ok`, `needs-auth`, `degraded`, `error` or `drift`; anything else becomes `unknown`), `detail`, `failures` | | `grant_decision` | `decision` (`allow`, `require_approval` or `deny`; anything else becomes `deny`), `reason`, `grantId`, `connectorId`, `toolName`, `ruleId` | In those two kinds, `detail` is masked for secrets and cut to 200 characters, and the other text fields are masked and cut to 120. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { events?: Array<{ kind: 'tool_call' | 'tool_result' | 'error' | 'grant_decision' | 'connector_health' // others are skipped ts?: number // must be within [now - 24h, now + 60s], else server time is used teamId?: string | null taskId?: string | null agentId?: string | null runtime?: string | null // defaults to 'openclaw' when omitted data?: Record<string, unknown> // a non-object is stored as {}; for tool_call, tool_result and error an array is stored as sent }> } ``` A missing or non-array `events` counts as empty. At most 200 events are taken per call; the rest are ignored. An event whose `kind` is missing or not one of the five is skipped. A `ts` more than 60 seconds ahead of the server clock, or more than 24 hours behind it, is replaced with the server's time. Agent health is worked out from `ts`, so a future timestamp would keep an agent `working` and hide a real `zombie`. ### Responses **`200 OK`**: how many events were added: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true, "count": 3 } ``` **`400 Bad Request`**: an unexpected failure while adding events (the message is masked): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/obs/ingest \ -H 'Content-Type: application/json' \ -d '{"events":[{"kind":"tool_call","taskId":"<task-uuid>","agentId":"<agent-id>","data":{"toolName":"web_search"}}]}' ``` *** ## `POST /api/eval/smoke` Runs the smoke subset of Clawboo's built-in eval suite (`SMOKE_TASKS`) and returns its report. It calls no model, needs no provider key, starts no runtime and uses no network. Each trial runs on its own throwaway board in a temporary SQLite file, never your `clawboo.db`, and the temporary files are removed afterwards. The ablation self-test runs only in the repository's manual `evals.yml` workflow, never from this route. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { trials?: number // 1 to 3; missing or not a number means 1 k?: number // 1 to 3; missing or not a number means the trials value } ``` Both values are rounded down and kept between 1 and 3, so the run stays small. ### Responses **`200 OK`**: the suite report: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { tasks: Array<{ taskId: string suite: 'capability' | 'regression' kind: 'coding' | 'research' | 'coordination' trials: Array<{ taskId: string passed: boolean score: number graders: Array<{/* GraderResult */}> }> passAt1: number // per-trial success rate = empirical pass@1 passPowK: number // probability all k trials pass = passAt1^k meanScore: number // mean partial-credit score across trials }> passAt1: number // macro-averaged pass@1 across tasks passPowK: number // macro-averaged pass^k across tasks k: number } ``` **`500 Internal Server Error`**: the run failed (the temporary files are still removed): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/eval/smoke \ -H 'Content-Type: application/json' \ -d '{"trials":3,"k":2}' ``` *** ## Error envelope Most error bodies are `{ "error": "<message>" }`; on the `/api/obs/*` routes the message is masked before it is sent. Two success shapes differ from the rest: `POST /api/obs/ingest` answers `{ "ok": true, "count": n }`, and `POST /api/eval/smoke` answers the bare suite report. ## Related * [Observability](/concepts/observability): the event log, traces and agent health. * [Observability panel](/using/observability): the dashboard views over these routes. * [Events and errors](/reference/events-and-errors): each event kind's `data` and the error classes. * [Costs API](/reference/rest-api/costs): cost records and the fleet summary. * [Board API](/reference/rest-api/board): the board routes that write task events. # Runtimes API Source: https://docs.claw.boo/reference/rest-api/runtimes REST reference for runtimes and provider keys: list, install, connect, disconnect, check a key, run a board task, onboarding and CLI sign-in. Use these routes to manage the runtimes Clawboo runs agents on, other than OpenClaw: Clawboo Native (built in), Claude Code, Codex and Hermes. They list each runtime's state, install a runtime's CLI, connect or disconnect its credentials, check a provider key, and run a board task on a runtime. The page also covers the provider-key routes behind **Settings > Providers**, the first-run onboarding routes, and the in-app ChatGPT sign-in. > [!NOTE] > > OpenClaw runs as its own Gateway process that Clawboo starts and talks to, so these routes do not cover it and answer **404** for `openclaw`. Its install, setup and Gateway control are in the [System API](/reference/rest-api/system). An unknown `:id` answers **404** `{ "error": "unknown runtime '<id>'" }`. Request bodies are JSON (up to 2 MB). Every route counts against the general [rate limit](/reference/rest-api/index#rate-limits) of 3,000 requests a minute per client address; routes marked **sensitive** below also count against a limit of 60 a minute. The `curl` examples assume the access gate is off (the default on a loopback install); with `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each call (see [Authentication](/reference/rest-api/index#authentication)). ## Routes | Method | Path | Summary | Rate tier | Stream? | | - | - | - | - | - | | GET | `/api/runtimes` | List runtimes with capabilities, health and connection state | general | No | | POST | `/api/runtimes/:id/install` | Install the runtime's CLI | sensitive | SSE | | POST | `/api/runtimes/:id/connect` | Save a key in the vault, reuse one, or record the Ollama choice | sensitive | No | | POST | `/api/runtimes/:id/disconnect` | Delete every vault key the runtime reads, or remove Ollama only | sensitive | No | | POST | `/api/runtimes/:id/logout` | Sign Codex out with its own CLI | sensitive | No | | POST | `/api/runtimes/:id/healthcheck` | Check a provider key or local Ollama (native only, stores none) | general | No | | POST | `/api/runtimes/:id/run` | Run a board task on the runtime and wait for the result | general | No | | GET | `/api/runtimes/openrouter/models` | The live OpenRouter catalog | general | No | | POST | `/api/onboarding/native-leader-model` | Record the default native provider and model | general | No | | GET | `/api/onboarding/native-leader-model` | The recorded pick and what a new native agent would get | general | No | | GET | `/api/onboarding/state` | First-run signals in one call | general | No | | GET | `/api/providers` | The 14 providers and whether each has a key | general | No | | POST | `/api/providers/:id/connect` | Save a key in the vault and in OpenClaw's key files | general | No | | POST | `/api/providers/:id/disconnect` | Remove a key from the vault and from OpenClaw's key files | general | No | | GET | `/api/providers/:id/models` | Live model list with the stored key (Ollama: its pulled models) | general | No | | POST | `/api/providers/:id/models` | Live model list with a pasted, unsaved key | general | No | | POST | `/api/auth/cli-login/:tool` | Run a CLI's own ChatGPT sign-in and relay its output | sensitive | SSE | > [!NOTE] > > `POST /api/runtimes/:id/install` and `POST /api/auth/cli-login/:tool` stream **Server-Sent Events** instead of returning a JSON body, so each is documented with an event catalog (event `type` and payload). Install answers a plain JSON **400** for the built-in Clawboo Native before any stream opens; cli-login answers a plain JSON **404** for an unknown `:tool`. *** ## `GET /api/runtimes` Lists every runtime with its capabilities, live health, and install and credential status. `available[]` describes the full catalog, so a client can show "available to add" cards for runtimes you have not connected yet. No key value is ever returned; the credential fields report presence only. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the runtime list plus the catalog: ```ts theme={"theme":"github-dark"} { runtimes: Array<{ id: 'claude-code' | 'codex' | 'hermes' | 'clawboo-native' participantKind: 'agent' | 'human' capabilities: { streaming: boolean mcp: boolean worktrees: boolean resume: boolean toolApproval: boolean // what the runtime can do, not whether Clawboo gates its tools steering?: 'live' | 'interrupt-resume' | 'none' // how a comment reaches a running task; absent = none models: string[] contextWindowTokens?: number runtimeClass?: 'wrapped-oneshot' | 'connected-substrate' | 'native' nativeHome?: { scope: 'per-identity' | 'per-run'; persist: boolean } nativeSkills?: 'preserve' | 'none' nativeMemory?: 'preserve' | 'none' nativeChannels?: 'gateway' | 'none' nativeScheduler?: boolean } health: { ok: boolean; message?: string } name: string installed: boolean binPath: string | null builtIn: boolean authKind: 'api-key' | 'oauth' | 'none' envVar: string | null hasCredential: boolean hasVaultCredential: boolean loggedIn: boolean // Codex only codexAuth: boolean // Hermes only ollamaConnected: boolean // Clawboo Native only installCommand: string | null docsUrl: string connectionState: 'not-installed' | 'needs-auth' | 'needs-login' | 'ready' | 'unknown' // this route never sends 'unknown' }> available: Array<{ id: string name: string healthBin: string | null packageManager: 'npm' | 'pip' | null installCommand: string | null builtIn: boolean authKind: 'api-key' | 'oauth' | 'none' envVar: string | null docsUrl: string headlessAuth: boolean }> } ``` `steering` is `live` for Claude Code, Codex and Clawboo Native (a comment joins the running turn) and `interrupt-resume` for Hermes (the run is stopped and resumed with the comment). `toolApproval: true` does not mean Clawboo asks before a tool runs: the coding runtimes run with their own approval prompts off (see [What agents can do](/concepts/permissions)). The status fields: | Field | Runtimes | Meaning | | - | - | - | | `installed` | all | Always `true` for the built-in Clawboo Native. Otherwise, whether the CLI resolves on `PATH` or in a known user install folder (where `pip --user` and `pipx` put Hermes). | | `binPath` | CLI runtimes | The resolved CLI path, or `null`. Always `null` for Clawboo Native. | | `envVar` | key runtimes | The vault slot a connect writes by default: `ANTHROPIC_API_KEY` for Claude Code and Clawboo Native, `OPENROUTER_API_KEY` for Hermes, `null` for Codex. | | `hasCredential` | key runtimes | A key resolves for any variable the runtime reads (listed under disconnect below), from the server's environment, the vault, or `~/.openclaw/.env`. After a disconnect only the vault counts. Hermes also counts a ChatGPT sign-in. | | `hasVaultCredential` | key runtimes | Like `hasCredential`, but counts the vault only, so a key that exists only in the server's shell does not look like a key you connected. The first-run check reads this one. | | `loggedIn` | Codex | Codex is installed and `codex login status` reports a ChatGPT sign-in. | | `codexAuth` | Hermes | Hermes holds a usable ChatGPT sign-in in its own `auth.json` (in `~/.hermes`, or `HERMES_HOME` when set). | | `ollamaConnected` | Clawboo Native | Local Ollama is set up: you picked it (a connect with `provider: "ollama"`), or no choice is recorded, `OLLAMA_BASE_URL` is set, and you have not disconnected the runtime. | `connectionState` is derived like this: | Condition | `connectionState` | | - | - | | The CLI is not installed | `not-installed` | | `authKind: 'none'` | `ready` | | `authKind: 'oauth'` (Codex) | `ready` when `loggedIn`, else `needs-login` | | `authKind: 'api-key'` (the rest) | `ready` when `hasCredential` (Clawboo Native: or `ollamaConnected`), else `needs-auth` | With `CLAWBOO_ENABLE_MOCK_RUNTIME=1`, `runtimes[]` also lists a `clawboo-mock` fault-injection runtime for development. It is not in `available[]`, and the `:id` routes answer **404** for it. **`500 Internal Server Error`**: a failure building an adapter or probing health: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/runtimes ``` *** ## `POST /api/runtimes/:id/install` Installs the runtime's CLI, pinned to a major version: * Claude Code: `npm install -g @anthropic-ai/claude-code@2`. * Codex: `npm install -g @openai/codex@0`. * Hermes: `pipx install 'hermes-agent[anthropic]<1'`, with pipx pointed at a Python 3.11 or newer when one is found. Without pipx it runs `python -m pip install --user` with such a Python, and retries once with `--break-system-packages` when pip reports an externally managed environment. Clawboo Native is built in, so it answers **400** before any stream opens. If the client closes the connection, the install process is killed. * **Path params**: `id`. * **Request body**: none. ### Responses **`400 Bad Request`**: the runtime is built in (no stream opens): ```json theme={"theme":"github-dark"} { "error": "Clawboo Native is built in \u2014 nothing to install" } ``` **`200 OK` + `text/event-stream`**: for an installable runtime the stream sends `data: <json>\n\n` frames. #### Event catalog | `type` | When | Payload fields | | - | - | - | | `progress` | Install start, the pipx path, and the externally managed retry | `step` (`'installing'` \| `'retrying'`), `message` | | `output` | Each non-empty stdout or stderr line | `line` | | `error` | Tooling missing, permission denied, spawn failure, non-zero exit | `code`, `message` | | `complete` | The process exits 0 | `success: true`, optional `warning` | The `error` codes: | `code` | Meaning | | - | - | | `NPM_MISSING` | `npm` not found (Claude Code, Codex) | | `PYTHON_MISSING` | No Python with pip or pipx found (Hermes) | | `PYTHON_TOO_OLD` | The newest Python found is older than 3.11 (Hermes) | | `EACCES` | The npm or pipx error output mentioned `EACCES` or "permission denied" | | `SPAWN_THROW` | The spawn call threw | | `SPAWN_ERROR` | The child process emitted an error | | `EXIT_<code>` | The process exited with a non-zero code (for example `EXIT_1`) | An `EACCES` frame does not end the stream: the exit code still decides the outcome, and its message suggests the same command with `sudo`. When the install succeeds but the CLI still cannot be found, `complete` carries a `warning` that suggests restarting the server. Example frames: ```text theme={"theme":"github-dark"} data: {"type":"progress","step":"installing","message":"Installing Claude Code…"} data: {"type":"output","line":"added 1 package in 3s"} data: {"type":"complete","success":true} ``` ### Example ```bash theme={"theme":"github-dark"} curl -N -X POST http://localhost:18790/api/runtimes/claude-code/install ``` *** ## `POST /api/runtimes/:id/connect` Connects a runtime. What it does depends on the runtime: * **Codex** takes no key. The route checks for an existing `codex login`, reports the state, and returns the command to sign in. To sign in from the dashboard, use `POST /api/auth/cli-login/codex`. * **Claude Code** stores `apiKey` under `ANTHROPIC_API_KEY`. * **Hermes** stores `apiKey` under `OPENROUTER_API_KEY`. Hermes also runs on an `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` already stored, or on a ChatGPT sign-in. * **Clawboo Native** stores `apiKey` in the slot for `provider` (table below). With `provider: "ollama"` it records that you picked local Ollama and stores nothing. With a `provider` and no `apiKey`, it reuses a key that already exists for that provider. The key goes into Clawboo's encrypted vault only; it is not copied to OpenClaw's files (`POST /api/providers/:id/connect` does that). Every successful connect clears this runtime's disconnect flag, so keys in the server's environment and in `~/.openclaw/.env` count for it again. The key is never echoed back. * **Path params**: `id`. * **Request body**: ```ts theme={"theme":"github-dark"} { apiKey?: string // required, except for Codex, provider 'ollama', or a Clawboo Native key reuse provider?: string // Clawboo Native: which provider the key is for (see the table) } ``` How Clawboo Native maps `provider` to a vault slot: | `provider` | Vault slot | | - | - | | omitted, anthropic | `ANTHROPIC_API_KEY` | | `openai` | `OPENAI_API_KEY` | | `openrouter` | `OPENROUTER_API_KEY` | | `google` | `GEMINI_API_KEY` | | `xai` | `XAI_API_KEY` | | `groq` | `GROQ_API_KEY` | | `mistral` | `MISTRAL_API_KEY` | | `together` | `TOGETHER_API_KEY` | | `cerebras` | `CEREBRAS_API_KEY` | | `moonshot` | `MOONSHOT_API_KEY` | | `ollama` | none (keyless) | Any other value falls back to `ANTHROPIC_API_KEY`. That includes `minimax`, `nvidia`, `huggingface` and `venice`, which the Providers routes accept but Clawboo Native cannot run on, so check the spelling before you send a key. **Key reuse.** A Clawboo Native request with a `provider` and no `apiKey` reconnects with a key that already resolves for that provider, from the server's environment, the vault or `~/.openclaw/.env`. Nothing is stored. The dashboard's **Use** button on a provider row (in **Settings > Runtimes**, under Clawboo Native's **Manage**) sends this request after it checks the stored key with `POST /api/runtimes/clawboo-native/healthcheck`. ### Responses **`200 OK`** (Codex): the current state and the sign-in command: ```ts theme={"theme":"github-dark"} { ok: true connectionState: 'ready' | 'needs-login' | 'not-installed' // ready = an existing `codex login` was found loginCommand: 'codex login' } ``` **`200 OK`** (every other case): the key was stored, reused, or the Ollama choice recorded, and the state re-derived (normally `ready`): ```ts theme={"theme":"github-dark"} { ok: true, connectionState: 'not-installed' | 'needs-auth' | 'needs-login' | 'ready' } ``` **`400 Bad Request`**: `apiKey` is missing or blank and no `provider` was sent: ```json theme={"theme":"github-dark"} { "error": "apiKey is required" } ``` **`400 Bad Request`**: a `provider` was sent with no `apiKey`, and Clawboo Native has no existing key for it. Claude Code and Hermes always answer this when `provider` comes without a key. The server's message contains an em dash, shown here as the JSON escape `\u2014`: ```json theme={"theme":"github-dark"} { "error": "no existing key found for openai \u2014 paste one to connect" } ``` ### Example ```bash theme={"theme":"github-dark"} # Claude Code curl -X POST http://localhost:18790/api/runtimes/claude-code/connect \ -H 'Content-Type: application/json' \ -d '{"apiKey":"sk-ant-..."}' # Clawboo Native on OpenRouter curl -X POST http://localhost:18790/api/runtimes/clawboo-native/connect \ -H 'Content-Type: application/json' \ -d '{"apiKey":"sk-or-...","provider":"openrouter"}' # Clawboo Native: reuse an OpenAI key that is already stored curl -X POST http://localhost:18790/api/runtimes/clawboo-native/connect \ -H 'Content-Type: application/json' \ -d '{"provider":"openai"}' # Clawboo Native on local Ollama (no key) curl -X POST http://localhost:18790/api/runtimes/clawboo-native/connect \ -H 'Content-Type: application/json' \ -d '{"provider":"ollama"}' ``` *** ## `POST /api/runtimes/:id/disconnect` Disconnects a runtime. With no body this is a **full disconnect**: it deletes every vault key the runtime reads and stops the runtime from picking keys up anywhere else. The CLI stays installed. Clawboo Native also takes `{ "provider": "ollama" }`, which removes only the local Ollama choice. > [!WARNING] > > A full disconnect deletes every vault key this runtime reads, not one, and the vault holds a single copy of each key. Other runtimes that use the same key lose it too: disconnecting Hermes deletes the Anthropic key that Claude Code and Clawboo Native may run on. To remove one provider's key, use `POST /api/providers/:id/disconnect` instead. What a full disconnect deletes: | Runtime | Vault keys deleted | Also | | - | - | - | | `claude-code` | `ANTHROPIC_API_KEY` | | | `hermes` | `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` | A ChatGPT sign-in in Hermes's own `auth.json` is kept, so Hermes can still read `ready`. | | `clawboo-native` | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`, `TOGETHER_API_KEY`, `CEREBRAS_API_KEY`, `MOONSHOT_API_KEY` | Records local Ollama as removed. | | `codex` | none (Codex keeps no key in the vault) | Its state comes from `codex login status`, so a signed-in Codex stays `ready` in `GET /api/runtimes` (the disconnect answer itself reads `needs-login`). Sign out with `POST /api/runtimes/codex/logout`. | A full disconnect also: * Sets the runtime's disconnect flag. Until you connect this runtime again, it reads keys from the vault only, so a key in the server's environment or in `~/.openclaw/.env` no longer counts for it. * Leaves the copies in `~/.openclaw/.env` and in each OpenClaw agent's `auth-profiles.json` alone. OpenClaw keeps those keys, and other runtimes that are not disconnected still find them there. * Stops memory embeddings from using a removed key. For Clawboo Native, neither a later key connect nor `OLLAMA_BASE_URL` turns Ollama back on after a full disconnect; only picking it again (a connect with `provider: "ollama"`) does. **`{ "provider": "ollama" }`** removes only the Ollama choice and keeps every key. It also stops a configured `OLLAMA_BASE_URL` from counting as set up until Ollama is picked again. While Ollama is removed, no run uses it: an agent whose only provider is Ollama fails and reads `providerReady: false`, and an agent with a keyed fallback runs on the fallback. In the dashboard, a runtime's **Manage** > **Disconnect** sends the full form. Under Clawboo Native's **Manage**, **Disconnect Ollama** sends `{ "provider": "ollama" }`, and the disconnect button on any other provider row (for example **Disconnect OpenAI**) calls `POST /api/providers/:id/disconnect`. * **Path params**: `id`. * **Request body**: none, or `{ "provider": "ollama" }` for `clawboo-native`. ### Responses **`200 OK`**: done; the state re-derived: ```ts theme={"theme":"github-dark"} { ok: true, connectionState: 'needs-auth' | 'needs-login' | 'ready' | 'not-installed' } ``` **`400 Bad Request`**: `provider` was sent but is empty or not a string (it is never treated as a full disconnect): ```json theme={"theme":"github-dark"} { "error": "provider must be a non-empty string" } ``` **`400 Bad Request`**: a `provider` other than `ollama`, or `ollama` on a runtime other than `clawboo-native`: ```json theme={"theme":"github-dark"} { "error": "only the native runtime's keyless 'ollama' disconnects by provider here; remove a provider key via /api/providers/:id/disconnect" } ``` ### Example ```bash theme={"theme":"github-dark"} # Full disconnect (see the table for what it deletes) curl -X POST http://localhost:18790/api/runtimes/hermes/disconnect # Clawboo Native: stop using local Ollama, keep every key curl -X POST http://localhost:18790/api/runtimes/clawboo-native/disconnect \ -H 'Content-Type: application/json' \ -d '{"provider":"ollama"}' ``` *** ## `POST /api/runtimes/:id/logout` Signs Codex out by running the CLI's own `codex logout`; Clawboo never deletes the vendor's tokens itself. The answer comes from a fresh `codex login status` check, not from the command's exit code. A runtime that authenticates with a key has no sign-out; use the disconnect route. > [!WARNING] > > This signs out the ChatGPT sign-in that every tool on the machine shares, not only Clawboo's use of it. * **Path params**: `id`. * **Request body**: none. ### Responses **`404 Not Found`**: the runtime does not use a sign-in: ```json theme={"theme":"github-dark"} { "error": "runtime '<id>' has no sign-out" } ``` **`400 Bad Request`**: the CLI is not installed: ```json theme={"theme":"github-dark"} { "error": "Codex is not installed" } ``` **`200 OK`**: the fresh check. `ok` is `true` only when the sign-out took: ```ts theme={"theme":"github-dark"} { ok: true, connectionState: 'needs-login' } // signed out { ok: false, connectionState: 'ready' } // still signed in ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/runtimes/codex/logout ``` *** ## `POST /api/runtimes/:id/healthcheck` Checks a provider credential with one authenticated request to the provider's model list, before anything relies on it. The key is used for that one request only: it is never stored, logged or echoed. A bad key or an unreachable provider comes back as `{ ok: false, error }` with status **200**. The dashboard calls this before it saves or reuses a key: on the onboarding connect step, in **Providers**, on a runtime's connect card, and for **Use** on a provider row. > [!NOTE] > > Only `clawboo-native` takes this route; any other valid `:id` answers **400**. It checks a **provider**, not a runtime, so a key meant for another runtime is checked by naming its provider here. * **Path params**: `id` (must be `clawboo-native`). * **Request body**: ```ts theme={"theme":"github-dark"} { provider?: string // 'anthropic' | 'openai' | 'openrouter' | 'ollama' | 'google' | 'xai' | // 'groq' | 'mistral' | 'together' | 'cerebras' | 'moonshot' apiKey?: string // omit to check the key that already resolves for `provider` // (server environment, vault, or ~/.openclaw/.env); never needed for ollama model?: string // ollama only: also check that this model is pulled } ``` Where each provider is checked: `anthropic` at `https://api.anthropic.com/v1/models`, `openai` at `https://api.openai.com/v1/models`, `openrouter` at `https://openrouter.ai/api/v1/models`, the OpenAI-compatible providers (`google`, `xai`, `groq`, `mistral`, `together`, `cerebras`, `moonshot`) at their own `<base URL>/models`, and `ollama` at `/api/tags` on `OLLAMA_BASE_URL` (a trailing `/v1` removed), else `http://localhost:11434`. The request times out after 8 seconds and refuses redirects. ### Responses **`400 Bad Request`**: `:id` is not `clawboo-native`: ```json theme={"theme":"github-dark"} { "ok": false, "error": "healthcheck is only supported for the native runtime" } ``` **`400 Bad Request`**: unknown provider: ```json theme={"theme":"github-dark"} { "ok": false, "error": "unknown provider '<provider>'" } ``` **`400 Bad Request`**: a keyed provider with no `apiKey` and no key that resolves for it: ```json theme={"theme":"github-dark"} { "ok": false, "error": "apiKey is required" } ``` **`200 OK`**: the provider answered and accepted the key: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`200 OK`**: the check failed (the failure is in the body): ```ts theme={"theme":"github-dark"} { ok: false, error: 'Invalid API key.' } // the provider answered 401 or 403 { ok: false, error: 'Provider returned <status>.' } // any other non-2xx { ok: false, error: 'Could not reach <provider> (timed out).' } // no answer within 8 s { ok: false, error: 'Could not reach <provider>.' } // network error or a redirect // ollama with `model`: Ollama answered, but has not pulled the model { ok: false, error: 'Ollama is running, but it doesn\'t have llama3.2 yet. Pull it with "ollama pull llama3.2".', missingModel: 'llama3.2' } ``` For `ollama`, `model` matches the way Ollama resolves names: an untagged `llama3.2` is met by `llama3.2:latest`, a tagged `qwen2.5:7b` only by itself. An answer that is not a tags list proves nothing either way, so it passes. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/runtimes/clawboo-native/healthcheck \ -H 'Content-Type: application/json' \ -d '{"provider":"anthropic","apiKey":"sk-ant-..."}' ``` *** ## `POST /api/runtimes/:id/run` Runs one board task on the runtime from start to finish: claim the task, prepare its workspace, run the agent, and report the result. The request stays open for the whole run. If the client disconnects first, the run and its process are stopped and the task goes back to `todo`. The runtime's MCP clients attach to this server's own `/api/mcp/*` routes on its loopback port, never to the request's `Host`. The runtime's keys are read the way its card reads them: from the server's environment, the vault, then `~/.openclaw/.env`, or from the vault only after a disconnect. Codex gets no key; it uses its own sign-in. This route is one of the two ways work runs through Clawboo's task runner (the other is an agent Routine), so it gets the runner's extras: a git worktree when the task needs one, verification of the worktree's changes, memory injection and circuit breakers. Team chat does not run through it; see [How a team works](/intro/how-it-works) and [When work is checked](/concepts/verification). * **Path params**: `id`. * **Request body**: ```ts theme={"theme":"github-dark"} { taskId: string // required assigneeAgentId?: string // default: the runtime id; must match /^[A-Za-z0-9_-]+$/ repoPath?: string | null // the git repository to branch the task's worktree from kind?: string // 'code' (default) needs a worktree; 'research' and 'review' run without one model?: string | null // model override for this run keepForResume?: boolean // keep the worktree and release the task, for another runtime to resume disableMemoryAutoInject?: boolean // skip putting relevant memory into the run's prompt maxRotations?: number // cap on fresh sessions when the context fills breakerConfig?: object // circuit-breaker overrides; ignored when invalid parentTraceparent?: string | null // W3C traceparent to nest this run's trace under } ``` A `code` task (or any kind other than `research` and `review`) changes files, so it needs a worktree. A task that already has a worktree reuses it. Otherwise, with no `repoPath`, or when the worktree cannot be created, the run is refused with `workspace_unavailable`: the task gets a `[blocked: no isolation]` comment and goes back to `todo`. A `research` or `review` task gets no worktree and starts in its team's folder, `<CLAWBOO_HOME>/workspaces/<teamId>` (`workspaces/_no-team` for a task with no team); if that folder cannot be created, the run is refused with `workspace_unavailable` too. A worktree keeps file changes apart; neither it nor the team's folder is a sandbox (see [What agents can do](/concepts/permissions)). ### Responses A refused or finished run answers with the runner's result. A refusal (`ok: false`) maps to a status by its `reason`: `not_found` is **404**, `conflict` is **409**, and every other reason is **422**. **`400 Bad Request`**: missing `taskId`: ```json theme={"theme":"github-dark"} { "error": "taskId is required" } ``` **`400 Bad Request`**: `assigneeAgentId` is not a bare identifier: ```json theme={"theme":"github-dark"} { "error": "invalid assigneeAgentId" } ``` **`200 OK`**: the task ran: ```ts theme={"theme":"github-dark"} { ok: true runtimeId: string execId: string doneReason: 'success' | 'max_turns' | 'aborted' | 'error' status: string // the task's status after the run summary: string costUsd: number | null // reported, or estimated when the runtime reports none; 0 on a local Ollama model usedWorktree: boolean degradations: string[] needsVerifyFix?: true // verification failed and the task is still in_progress, waiting for a fix stoppedByPerson?: true // a person moved the task's card, which stopped the run (doneReason 'aborted') } ``` When verification fails, the route runs the task again with the failure in its prompt, inside the same request, before it answers. It allows one fix run by default (`CLAWBOO_MAX_FIX_CYCLES` changes the number), so the answer describes the last run. How each runtime's cost is reported or estimated is in [Budgets and spend](/using/budgets-and-spend). **`404 Not Found`**: no task with that id: ```json theme={"theme":"github-dark"} { "ok": false, "reason": "not_found" } ``` **`409 Conflict`**: the task could not be claimed (someone else holds it). Treat it as data and do not retry: ```json theme={"theme":"github-dark"} { "ok": false, "reason": "conflict" } ``` **`422 Unprocessable Entity`**: the run was refused before it started: ```ts theme={"theme":"github-dark"} { ok: false, reason: 'too_deep' | 'connected_substrate' | 'budget_paused' | 'workspace_unavailable' } ``` | `reason` | Meaning | | - | - | | `too_deep` | The task sits deeper in its delegation tree than the depth limit allows. | | `connected_substrate` | The runtime runs work over its own live connection (OpenClaw), so this route cannot run it. | | `budget_paused` | A hard-cap budget covering this agent, this delegation tree or this team is paused. Warn-only budgets never block. | | `workspace_unavailable` | The task needs a worktree and none could be prepared (no `repoPath`, or the worktree failed), or the team's folder for a read-only task could not be created. | **`500 Internal Server Error`**: an unexpected error inside the run: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/runtimes/claude-code/run \ -H 'Content-Type: application/json' \ -d '{"taskId":"<task-uuid>","repoPath":"/path/to/repo","kind":"code"}' ``` *** ## `GET /api/runtimes/openrouter/models` The live OpenRouter catalog behind the OpenRouter model pickers. It reads OpenRouter's public model list (no key), keeps text models, sorts them by label, and caches the list for 30 minutes, with an 8-second fetch timeout. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the catalog. This route does not fail: a failed or empty fetch falls back to the last good list, and an empty array tells the client to use its own built-in list: ```ts theme={"theme":"github-dark"} { models: Array<{ id: string; label: string }> } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/runtimes/openrouter/models ``` *** ## Onboarding The three `/api/onboarding/*` routes take no `:id`. They are grouped here because they are Clawboo Native's first-run routes. Onboarding creates no team of its own: you pick one in the marketplace and deploy it through the [Teams API](/reference/rest-api/teams). ### `POST /api/onboarding/native-leader-model` Records the provider and model you picked when you connected a native provider (a key, or keyless Ollama), or later with **Make default** in Clawboo Native's provider list. Boo Zero runs on it when Clawboo creates it (if that provider can still run), and an existing native Boo Zero is switched to it. New native agents created without a model pick follow it while its provider stays set up: a leader runs the recorded model, and a specialist runs that provider's cheaper default (on Ollama, the recorded model too). See [Clawboo Native](/runtimes/native). Body `{ provider, model }`. * **`200 OK`**: `{ "ok": true }`. * **`400 Bad Request`**: `{ "error": "unknown provider '<provider>'" }` when `provider` is not one of the eleven native providers (the table under connect, including `ollama`), or `{ "error": "model is required" }` when `model` is missing or blank. * **`500 Internal Server Error`**: `{ "error": "<message>" }` when the setting cannot be written. ### `GET /api/onboarding/native-leader-model` The recorded pick, plus what a native agent created without a model pick would get right now, per tier. The native provider manager reads it for its **Default** tag and model menu, and the team-deploy dialog labels each native member left on **Default** with it. ```ts theme={"theme":"github-dark"} { provider: string | null // the recorded pick; null when none is recorded or it cannot be read model: string | null resolved: { leader: { provider: string; model: string } specialist: { provider: string; model: string } } | null // null when no native provider is set up } ``` `resolved` comes from the same logic agent creation uses, including the check that an Ollama model is pulled. A recorded value that cannot be read counts as no pick. This route does not return an error status. ### `GET /api/onboarding/state` The first-run signals in one call, so a client can decide between the setup wizard and the dashboard without several requests: ```ts theme={"theme":"github-dark"} { configured: boolean // OpenClaw is installed and its openclaw.json and .env both exist hasNative: boolean // at least one Clawboo Native agent exists hasTeam: boolean // at least one team exists hasConnectedRuntime: boolean // see below } ``` `hasConnectedRuntime` is `true` when any of the four runtimes has one of its keys in the vault, when Codex is signed in, or when Hermes holds a ChatGPT sign-in. A key that exists only in the server's environment does not count, and neither does a keyless Ollama choice. **500** `{ "error": "<message>" }` on an unexpected error. *** ## Providers The `/api/providers*` routes back **Settings > Providers**. A provider is a model vendor (`anthropic`, `openai`, ...), not a runtime: one Anthropic key can serve Clawboo Native, Claude Code, Hermes and OpenClaw at once. These routes keep the key in two places, so that every runtime finds it: * Clawboo's encrypted vault (read by Clawboo Native, Claude Code and Hermes). * OpenClaw's own key files, in plain text and owner-only (mode `0600`): `~/.openclaw/.env` and every existing OpenClaw agent's `auth-profiles.json` (as the `<provider>:default` profile). The folder is created (mode `0700`) if it does not exist, even when you do not use OpenClaw. For where keys are stored and how to connect them in the dashboard, see [Providers and runtimes](/using/providers-and-runtimes). The 14 providers and their key variables: `anthropic` (`ANTHROPIC_API_KEY`), `openai` (`OPENAI_API_KEY`), `google` (`GEMINI_API_KEY`), `openrouter` (`OPENROUTER_API_KEY`), `xai` (`XAI_API_KEY`), `groq` (`GROQ_API_KEY`), `mistral` (`MISTRAL_API_KEY`), `moonshot` (`MOONSHOT_API_KEY`), `minimax` (`MINIMAX_API_KEY`), `together` (`TOGETHER_API_KEY`), `nvidia` (`NVIDIA_API_KEY`), `huggingface` (`HF_TOKEN`), `cerebras` (`CEREBRAS_API_KEY`), `venice` (`VENICE_API_KEY`). `ollama` and `openai-codex` (the ChatGPT sign-in) are not providers here; `connect` answers **400** for them. ### `GET /api/providers` Every provider and whether it has a key: ```ts theme={"theme":"github-dark"} { providers: Array<{ id: string // one of the 14 ids above, in that order connected: boolean // a key is in the vault or in ~/.openclaw/.env (a shell variable alone does not count) poweredRuntimes: string[] // a fixed display list per provider, e.g. ["Clawboo Native", "Claude Code", "OpenClaw"] for anthropic }> } ``` `poweredRuntimes` is a fixed label list, not a live check: `Clawboo Native` for `anthropic`, `openai` and `openrouter`, `Claude Code` for `anthropic`, `Hermes` for `openrouter`, and `OpenClaw` for every provider. Clawboo Native also runs on the other keyed providers in the connect table, and Hermes also reads an Anthropic or OpenAI key, even though the list leaves them out. ### `POST /api/providers/:id/connect` Body `{ apiKey }`. Stores the key in the vault and writes it to `~/.openclaw/.env` and every OpenClaw agent's `auth-profiles.json`, replacing an earlier key for the same provider. Memory embeddings may switch to the new key. Returns `{ ok: true, providers }` with the refreshed list. The dashboard's **Providers** page and the OpenClaw setup's key prompt call this route. * **`400 Bad Request`**: `{ "error": "unknown provider '<id>'" }`, or `{ "error": "apiKey is required" }` when the key is missing or blank. ### `POST /api/providers/:id/disconnect` Removes the provider's key from the vault, from `~/.openclaw/.env`, and from every OpenClaw agent's `auth-profiles.json`. A key exported in the server's environment is not touched: runtimes can still use it, though the list shows the provider as not connected. Returns `{ ok: true, providers }`. * **`400 Bad Request`**: `{ "error": "unknown provider '<id>'" }`. ### `GET /api/providers/:id/models` The provider's live model list, fetched with the key that resolves for it (server environment, vault, then `~/.openclaw/.env`). Returns `{ "models": [] }` when there is no key or the provider has no live list here, so a client can fall back to its built-in list. Lists are cached for 30 minutes per provider and key, with an 8-second timeout; a failed fetch serves the last good list for the same key. | Provider | Live list | | - | - | | `anthropic`, `openai` | Yes (chat models only, newest first) | | `google`, `xai`, `groq`, `mistral`, `together`, `cerebras`, `moonshot` | Yes (each provider's own `/models`, non-chat models left out) | | `openrouter` | No: always `[]`. The OpenRouter catalog is `GET /api/runtimes/openrouter/models`. | | `minimax`, `nvidia`, `huggingface`, `venice` | No: always `[]` | | `ollama` | The models your local Ollama has pulled (below) | `ollama` needs no key and answers from the local Ollama instead: its chat models (embedding-only models left out, models without tool support labelled `(can't use tools)`, newest first), whether Ollama answered, and `recommendedModel`. That is `llama3.2` when it is pulled; otherwise the newest pulled model that can use tools, then the newest whose tool support Ollama does not report, then the newest that cannot use tools; and `llama3.2` itself when Ollama lists no chat model or does not answer. It does not read your recorded default. It is read fresh on every call, so a model you just pulled shows up. ```json theme={"theme":"github-dark"} { "models": [ { "id": "qwen2.5:7b", "label": "qwen2.5:7b" }, { "id": "gemma2", "label": "gemma2 (can't use tools)" } ], "reachable": true, "recommendedModel": "qwen2.5:7b" } ``` ### `POST /api/providers/:id/models` Body `{ apiKey }`. The live model list using a pasted key that is not saved; the onboarding step calls it before any key is stored. The key is used for one fetch and is never logged, stored or echoed. Returns `{ "models": [] }` when the provider has no live list (see the table; `ollama` included) or the key is blank. *** ## `POST /api/auth/cli-login/:tool` The dashboard's **Sign in with ChatGPT**. Runs the official CLI's own sign-in command on this machine and relays what it prints (a device code, a sign-in URL) over SSE. The sign-in itself stays inside the vendor's CLI: you approve it in a browser, and Clawboo never handles the tokens. Only one sign-in runs per tool; a new request stops the previous one first. A sign-in is stopped after 16 minutes, since the CLIs' own device codes expire after 15. | `:tool` | Command it runs | | - | - | | `codex` | `codex login` | | `hermes` | `hermes auth add openai-codex --type oauth` | | `openclaw` | `openclaw models auth login --provider openai-codex` | * **Path params**: `tool`. * **Request body**: none. ### Responses **`404 Not Found`**: unknown `:tool`, as plain JSON before any stream opens: ```json theme={"theme":"github-dark"} { "error": "unknown login tool: <tool>" } ``` **`200 OK` + `text/event-stream`**: otherwise the stream sends `data: <json>\n\n` frames. A sign-in that cannot start (the CLI is not installed, or the OpenClaw sign-in on Windows) is an `error` frame inside the stream, not an HTTP error, so the dashboard can fall back to showing the command to copy. #### Event catalog | `type` | When | Payload fields | | - | - | - | | `progress` | The command is about to start | `step: 'starting'`, `message` | | `output` | Each non-empty output line (terminal colors removed) | `line` | | `device-code` | A device code appeared in the output | `url`, `code` | | `auth-url` | A browser sign-in URL appeared in the output | `url` | | `error` | Cannot start, spawn failure, time-out, or cancelled | `code`, `message` | | `complete` | The sign-in finished, successfully or not | `success`, `loggedIn`, optional `message` | The `error` codes: | `code` | Meaning | | - | - | | `NOT_INSTALLED` | The tool's CLI is not installed | | `UNSUPPORTED_PLATFORM` | The OpenClaw sign-in needs a real terminal (on Windows) | | `SPAWN_THROW` | The spawn call threw | | `SPAWN_ERROR` | The child process emitted an error | | `TIMEOUT` | The 16-minute limit passed | | `CANCELLED` | The command was stopped or exited with code 130 | Completion comes from checking where the tool stores its sign-in (every 3 seconds), not from the exit code alone, because a browser sign-in can leave the CLI waiting at a prompt after the credential has landed. As soon as the credential appears, the stream sends `complete` with `success: true, loggedIn: true` and stops the command. If the command exits without a usable credential, `complete` carries `success: false, loggedIn: false` and a `message` naming the command to run in a terminal. Cancelling in the dashboard closes the connection, which stops the command and everything it started. ### Example ```bash theme={"theme":"github-dark"} curl -N -X POST http://localhost:18790/api/auth/cli-login/codex ``` *** ## Error envelope Errors use the standard `{ "error": string }` body, with three exceptions: `healthcheck` answers `{ "ok": false, "error": string }` (its success body is `{ "ok": true }`), a refused `run` answers `{ "ok": false, "reason": string }`, and the two SSE routes report failures as `error` frames once the stream is open. Going over a rate limit answers **429** with `{ "error": "Too many requests. Wait a moment and retry." }` (general) or `{ "error": "Too many requests for this operation. Wait a moment and retry." }` (sensitive), plus a `Retry-After` header. **Source of truth:** `apps/web/server/api/runtimes.ts`, `providers.ts`, `onboardingSeed.ts`, `onboardingState.ts` and `cliLogin.ts`; key storage in `apps/web/server/lib/secretsVault.ts`, `providerKeys.ts` and `openclawEnv.ts`; runtime definitions in `apps/web/server/lib/runtimes/descriptor.ts`. ## Related * [Providers and runtimes](/using/providers-and-runtimes): connect and disconnect from the dashboard, and where keys are stored. * [Choose a runtime](/runtimes/index): what each runtime supports. * [System API](/reference/rest-api/system): OpenClaw install, setup and the Gateway. * [Board API](/reference/rest-api/board): create the task you pass to the run route. * [Tools and MCP API](/reference/rest-api/tools-and-mcp): the `/api/mcp/*` servers a run attaches to. * [REST API overview](/reference/rest-api/index): authentication, rate limits and conventions. # Schedules API Source: https://docs.claw.boo/reference/rest-api/schedules REST reference for Routines and OpenClaw cron jobs: list, create, change, pause, resume, delete and run schedules, and read run history and upcoming runs. Use these routes to read and change everything that runs on a schedule. One list covers two sources: Clawboo's own [Routines](/using/routines), which schedule team work, and the OpenClaw Gateway's cron jobs, which run an OpenClaw agent's own work outside any team. The schedule list always answers and reports a source that is down as data. A write goes to the source that owns the schedule, and its errors come back as precise status codes with a stable `code`. > [!NOTE] > > The two sources stay separate. A Routine schedules team work on any runtime: a message to a team's lead (a team routine) or a board task for one agent (an agent routine). A Gateway cron job schedules an OpenClaw agent's own work. Clawboo never puts team work into the Gateway's cron and never creates Gateway cron jobs by itself; the Routines view shows them and lets you change them. Every schedule has a **composite `id`** of the form `<source>:<rawId>`: `clawboo-routine:<routine-id>` or `openclaw-gateway-cron:<gateway-job-id>`. The `:id` path segment is URL-decoded and sent to the source it names; an id that matches no source answers **404**. Request bodies are JSON (up to 2 MB). Every route here is on the general [rate limit](/reference/rest-api/index#rate-limits) (3,000 requests a minute per client address). The `curl` examples assume the access gate is off; with `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` (see [Authentication](/reference/rest-api/index#authentication)). ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/schedules` | Every schedule from both sources (a source that is down is reported) | No | | POST | `/api/schedules` | Create a schedule in the source named by `source` | No | | PATCH | `/api/schedules/:id` | Pause, resume, or change the cron spec, label, template, payload, target | No | | DELETE | `/api/schedules/:id` | Remove a schedule | No | | POST | `/api/schedules/:id/run` | Run it now (queued, answered with 202) | No | | GET | `/api/schedules/:id/runs` | A Routine's recent runs, newest first | No | | GET | `/api/schedules/occurrences` | Upcoming Routine runs, counted per time window you name | No | *** ## The `ScheduleRecord` shape Both sources describe their schedules in one shape. It is the element type of `schedules[]` on `GET`, and the `schedule` returned by a create, change, pause or resume. ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { id: string // composite <source>:<rawId>; treat it as opaque sourceScheduleId: string // the raw id inside the owning source runtime: string // the runtime a run uses ('openclaw', 'clawboo-native', ...); 'unknown' when there is // no agent to read it from (see the note on team routines below) owner: string // which engine runs it ('clawboo' or 'openclaw') source: 'clawboo-routine' | 'openclaw-gateway-cron' agentId: string // the agent a run uses; '' for a team routine target?: 'team' | 'agent' // Routines: who a run goes to teamId?: string | null // Routines: the team whose chat or board receives a run teamName?: string // display names, filled in by GET /api/schedules and the occurrences agentName?: string // route only; for a team routine, agentName (and runtime) are the lead's teamTaskId?: string // only for a Routine bound to an existing board task label?: string description?: string // Routines: the instructions each run carries cronSpec: string // a cron expression, `once@<iso>`, `every:<ms>[@anchor:<ms>]` or `at:<iso>`; // a Gateway cron expression with a time zone reads `<expr>@tz:<zone>` nextRunAt: number | null // epoch ms of the next run; null when none is scheduled. A paused Routine // keeps the value it had when paused, but does not run until resumed lastRunAt?: number lastError?: string status: 'queued' | 'claimed' | 'running' | 'idle' | 'paused' | 'error' manageability: 'managed' | 'external-write' | 'observe-only' domain: 'team-task' | 'runtime-own-life' tenantId: string | null // always null } ``` `domain` says what a schedule is for: `team-task` is team work that Clawboo schedules (Routines), and `runtime-own-life` is an OpenClaw agent's own job in the Gateway's cron. `manageability` says who owns it: `managed` means Clawboo owns and runs it, `external-write` means the Gateway owns and runs it and Clawboo changes it through the Gateway, and `observe-only` (no source uses it today) would be read-only. A team routine stores no agent (`agentId` is `''`). `GET /api/schedules` and the occurrences route fill in `teamName` and `agentName`, and for a team routine they take `agentName` and `runtime` from the team's current lead. The `schedule` in a create, change, pause or resume answer has no names, and a team routine there reads `runtime: 'unknown'`. An agent routine whose agent is gone reads `runtime: 'unknown'` everywhere. | `source` | `domain` | `manageability` | `owner` | Backed by | | - | - | - | - | - | | `clawboo-routine` | `team-task` | `managed` | `clawboo` | Clawboo's database (the `scheduled_runs` table) | | `openclaw-gateway-cron` | `runtime-own-life` | `external-write` | `openclaw` | The Gateway's cron, through Clawboo's own Gateway connection | > [!NOTE] > > There is no third source. Claude Code, Codex, Hermes and Clawboo Native have no scheduler of their own that Clawboo uses; scheduling any of them is a Routine. *** ## `GET /api/schedules` Every schedule from both sources, with each one's `teamName` and `agentName` filled in. A team routine stores no agent, so it reports the team's current lead (the agent its next run would reach) as `agentName` and `runtime`. A source that fails or is disconnected does not fail the request: it adds a degraded entry to `sources[]`. A Gateway cron list read earlier is served from its cache; with no cache, its jobs are absent until the Gateway reconnects. This route always answers **200**. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the schedules plus one status entry per source: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { schedules: ScheduleRecord[] sources: Array<{ sourceId: 'clawboo-routine' | 'openclaw-gateway-cron' ok: boolean degraded: boolean reason?: string // 'gateway_disconnected', 'stale_cache', or an error message at: number // epoch ms of this read }> } ``` The Routines source reports `{ ok: true, degraded: false }`. When its Gateway connection is down, the Gateway cron source reports `{ ok: false, degraded: true, reason: 'gateway_disconnected' }` with no cache, or `reason: 'stale_cache'` when it serves cached jobs. ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/schedules ``` *** ## `POST /api/schedules` Creates a schedule in the source named by `source`. Before the source is touched, two checks run in order: a read-only (`observe-only`) source answers **403**, and a `domain: 'team-task'` create aimed at the Gateway cron answers **422** (the Gateway cron source refuses it too). The source then runs its own checks. For a `clawboo-routine` create, the cron spec is checked (a spec that does not parse is a **400**), and the Routine's task template is built from `label`, `taskTemplate` and `target`. The template's `title` defaults to `label` (or `Scheduled team task`), and its `description` is the instructions each run carries. The target is checked too: * **A team routine** (`target: 'team'`) needs a `teamId` naming a live, unarchived team. Its runs are posted into that team's chat for the team's lead, so it stores no agent (`agentId` is ignored and reads back as `''`), and it cannot bind a `teamTaskId`. * **An agent routine** (`target: 'agent'`, the default) needs an `agentId` naming a live, unarchived agent, and is filed on that agent's team. A `teamId` is optional; when sent, it must match the agent's team (`null` for an agent on no team). A target that fails these checks is a **400** with `code: "invalid_routine_target"`. A recurring spec bound to an existing `teamTaskId` is refused too: a bound task can be claimed only once, so a recurring run would park the Routine in `error`. Bind only one-shot `once@<iso>` specs. Binding a task that another engine already schedules is the **409** `duplicate_firing_owner` refusal. For an `openclaw-gateway-cron` create, Clawboo calls the Gateway's `cron.add` with the job's `payload` (default `{ kind: 'agentTurn', message: label }`, with `'Scheduled wake'` when there is no `label`) and the session target that payload needs: `main` for a `systemEvent`, `isolated` for anything else. The Gateway accepts a mismatched pair but then skips every run, so Clawboo always picks the pair itself. * **Path/query params**: none. * **Request body**: a `ScheduleCreateSpec`. `source`, `domain` and `cronSpec` are required, plus a `teamId` for a team routine or an `agentId` for anything else: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { source: 'clawboo-routine' | 'openclaw-gateway-cron' // required domain: 'team-task' | 'runtime-own-life' // required cronSpec: string // required target?: 'team' | 'agent' // Routines: who a run goes to (default 'agent') agentId?: string // required unless target is 'team' teamId?: string | null // required for a team routine; must match the agent's team otherwise label?: string teamTaskId?: string | null // agent routines: bind to an existing board task (one-shot specs only) taskTemplate?: unknown // Routines: the task template, for example { description } payload?: unknown // Gateway jobs: the cron payload, for example { kind: 'agentTurn', message } tenantId?: string | null // unused } ``` ### Responses **`201 Created`**: the schedule was created: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { schedule: ScheduleRecord } ``` **`400 Bad Request`**: a required field is missing, `source`, `domain` or `target` is unknown, or a source check failed. `code` is `invalid_body` for the shape check, a cron spec that does not parse, or an invalid task template; `invalid_routine_target` for a target that does not check out; and `bound_recurring_schedule` for a recurring spec bound to an existing task. Messages on this page that contain an em dash show it as the JSON escape `\u2014`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "source, domain, cronSpec, and an agentId (or a teamId for a team routine) are required", "code": "invalid_body" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Invalid cron spec \"* * *\": <detail>", "code": "invalid_body" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "invalid task template", "code": "invalid_body" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "\"Ada\" is not on that team.", "code": "invalid_routine_target" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "A recurring schedule (\"0 9 * * *\") cannot bind to existing team task <id> \u2014 a bound task is claimable once, so use a one-shot (once@<iso>) spec", "code": "bound_recurring_schedule" } ``` **`403 Forbidden`**: the target source is `observe-only` (no source is today, but the check exists): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Schedule source \"<id>\" (observe-only) does not support \"create\"", "code": "unsupported_schedule_write" } ``` **`404 Not Found`**: the `teamTaskId` you bound names no board task: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Unknown schedule \"<teamTaskId>\"", "code": "unknown_schedule" } ``` **`409 Conflict`**: the bound `teamTaskId` is already scheduled by another engine. Treat it as data and do not retry: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Already scheduled by \"<owner>\" (team task <id>) \u2014 never retry this refusal", "code": "duplicate_firing_owner" } ``` **`422 Unprocessable Entity`**: a `domain: 'team-task'` create was aimed at the Gateway cron: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "A team-task schedule cannot be registered into \"openclaw-gateway-cron\" \u2014 team-task cadence belongs to the Routines ledger", "code": "team_task_domain_violation" } ``` **`503 Service Unavailable`**: the target is the Gateway cron and Clawboo's Gateway connection is down: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gateway_disconnected", "code": "schedule_source_unavailable" } ``` **`500 Internal Server Error`**: any other error: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # A team routine: post to the team chat every weekday at 9am curl -X POST http://localhost:18790/api/schedules \ -H 'Content-Type: application/json' \ -d '{ "source": "clawboo-routine", "domain": "team-task", "target": "team", "teamId": "<team-id>", "cronSpec": "0 9 * * 1-5", "label": "Daily standup digest", "taskTemplate": { "description": "Summarize what the team finished yesterday." } }' # An agent routine: a daily task for one agent curl -X POST http://localhost:18790/api/schedules \ -H 'Content-Type: application/json' \ -d '{ "source": "clawboo-routine", "domain": "team-task", "target": "agent", "agentId": "<agent-id>", "cronSpec": "0 9 * * *", "label": "Inbox sweep", "taskTemplate": { "description": "Check the support inbox and draft replies to anything urgent." } }' ``` *** ## `PATCH /api/schedules/:id` Pauses or resumes a schedule, or changes its cron spec, label, task template, payload or target. The body takes one of two shapes; a body with neither is a **400**, and a body with both runs `action` and ignores `patch`. The change goes to the source named in `:id`. For a Routine: * **Pause and resume** follow the table below. A paused Routine does not run until you resume it. * **A cron spec change** recomputes `nextRunAt` for an `idle` Routine and sets it to `null` for any other. A paused or errored Routine gets a new `nextRunAt` when you resume it. * **A change to `target`, `agentId` or `teamId`** points the Routine somewhere else, and the new target is checked exactly as on create. A `target` inside `taskTemplate` counts as one too. * **A `teamTaskId` inside a template change** is ignored: a Routine binds to a board task only when it is created, where the check for another engine runs. | Action | Allowed when the Routine is | Result | | - | - | - | | `pause` | `idle`, `queued`, `error` or already `paused` | `paused`; it does not run until resumed | | `resume` | `paused`, `error` or already `idle` | `idle`, a newly computed `nextRunAt`, `lastError` cleared | | either | `claimed` or `running` | **409**: the run in progress settles it | Pausing a paused Routine changes nothing, and resuming an idle one only recomputes its `nextRunAt`; both answer **200**. Resuming a `queued` Routine is a **409**. For a Gateway cron job: * **Pause and resume** become the Gateway's `cron.update { id, patch: { enabled } }` (the Gateway has no separate enable method). * **A change** becomes `cron.update { id, patch }` with the changed `label` (as the job's name), `cronSpec` and `payload`; the Routine-only fields are ignored. A changed `payload` carries the session target it needs, as on create. * **The job id is not checked by Clawboo.** It goes to the Gateway as is, and a Gateway error (for example an unknown job) answers **500** with the Gateway's message, not **404**. The same holds for delete and run. * **Path params**: `id` (composite schedule id). * **Request body**: one of: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { action: 'pause' | 'resume' } ``` ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { patch: { cronSpec?: string label?: string taskTemplate?: unknown // Routines, e.g. { description } for new instructions target?: 'team' | 'agent' // Routines: point the Routine somewhere else agentId?: string | null // Routines teamId?: string | null // Routines payload?: unknown // Gateway jobs } } ``` ### Responses **`200 OK`**: the schedule was changed; the updated record is returned. A Gateway job can come back as `schedule: null` when it cannot be read back right after the change: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { schedule: ScheduleRecord | null } ``` **`400 Bad Request`**: the body is neither a valid `action` nor a `patch` object, a changed `cronSpec` does not parse, the template is invalid, or a changed target does not check out (`invalid_routine_target`): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "body needs { action: 'pause' | 'resume' } or { patch }", "code": "invalid_body" } ``` ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Invalid cron spec \"0 99 * * *\": <detail>", "code": "invalid_body" } ``` **`403 Forbidden`**: the target source is `observe-only`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Schedule source \"<id>\" (observe-only) does not support \"pause\"", "code": "unsupported_schedule_write" } ``` **`404 Not Found`**: `:id` matches no source, or names a Routine that does not exist (an unknown Gateway job id is a **500**, see above): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Unknown schedule \"<id>\"", "code": "unknown_schedule" } ``` **`409 Conflict`**: the pause or resume is not allowed from the Routine's current status: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Illegal schedule transition running → paused", "code": "illegal_schedule_transition" } ``` **`503 Service Unavailable`**: the target is a Gateway job and Clawboo's Gateway connection is down: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gateway_disconnected", "code": "schedule_source_unavailable" } ``` **`500 Internal Server Error`**: any other error: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Pause a Routine curl -X PATCH http://localhost:18790/api/schedules/clawboo-routine:<routine-id> \ -H 'Content-Type: application/json' \ -d '{"action":"pause"}' # Change its cron spec curl -X PATCH http://localhost:18790/api/schedules/clawboo-routine:<routine-id> \ -H 'Content-Type: application/json' \ -d '{"patch":{"cronSpec":"0 8 * * 1-5"}}' # Turn it into a team routine for another team curl -X PATCH http://localhost:18790/api/schedules/clawboo-routine:<routine-id> \ -H 'Content-Type: application/json' \ -d '{"patch":{"target":"team","teamId":"<team-id>","agentId":null}}' ``` *** ## `DELETE /api/schedules/:id` Removes a schedule: a Routine is deleted from Clawboo's database, and a Gateway job is removed with the Gateway's `cron.remove`. * **Path params**: `id` (composite schedule id). * **Request body**: none. ### Responses **`200 OK`**: the schedule was removed: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true } ``` **`403 Forbidden`**: the target source is `observe-only`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Schedule source \"<id>\" (observe-only) does not support \"remove\"", "code": "unsupported_schedule_write" } ``` **`404 Not Found`**: `:id` matches no source, or names a Routine that does not exist (an unknown Gateway job id is a **500**, see PATCH): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Unknown schedule \"<id>\"", "code": "unknown_schedule" } ``` **`503 Service Unavailable`**: the target is a Gateway job and Clawboo's Gateway connection is down: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gateway_disconnected", "code": "schedule_source_unavailable" } ``` **`500 Internal Server Error`**: any other error: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X DELETE http://localhost:18790/api/schedules/openclaw-gateway-cron:<job-id> ``` *** ## `POST /api/schedules/:id/run` Runs a schedule now. The answer only says the run was queued; it does not wait for the run. A Routine moves to `queued` and Clawboo's scheduler picks it up at once; a Gateway job gets the Gateway's `cron.run { id, mode: 'force' }`. Only an `idle` Routine can be queued: a paused, errored, queued, claimed or running one answers **409**. Follow the result in the Routine's run history (`GET /api/schedules/:id/runs`), the event log, or OpenClaw's own cron history. * **Path params**: `id` (composite schedule id). * **Request body**: none. ### Responses **`202 Accepted`**: the run was queued: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": true } ``` **`403 Forbidden`**: the target source is `observe-only`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Schedule source \"<id>\" (observe-only) does not support \"run\"", "code": "unsupported_schedule_write" } ``` **`404 Not Found`**: `:id` matches no source, or names a Routine that does not exist (an unknown Gateway job id is a **500**, see PATCH): ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Unknown schedule \"<id>\"", "code": "unknown_schedule" } ``` **`409 Conflict`**: the Routine is not `idle`: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Illegal schedule transition paused → queued", "code": "illegal_schedule_transition" } ``` **`503 Service Unavailable`**: the target is a Gateway job and Clawboo's Gateway connection is down: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gateway_disconnected", "code": "schedule_source_unavailable" } ``` **`500 Internal Server Error`**: any other error: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/schedules/clawboo-routine:<routine-id>/run ``` *** ## `GET /api/schedules/:id/runs` A Routine's recent runs, newest first, built from the Routine's events in the event log. An agent routine's run carries the board task it created, with that task's current title and status. The Gateway keeps its own run history, so a Gateway job id returns an empty list. * **Path params**: `id` (composite schedule id). * **Query params**: `limit` (optional, default `10`, kept between 1 and 50). * **Request body**: none. ### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { runs: Array<{ firedAt: number // epoch ms when the run was claimed finishedAt: number | null // when its outcome was recorded status: 'running' | 'succeeded' | 'failed' | 'interrupted' error: string | null // why a failed run failed taskId: string | null // the board task an agent routine's run created; null for a team routine dispatchPath: string | null // 'team-chat' | 'one-shot' | 'connected' targetAgentId: string | null // a team routine's recipient: the lead the message went to task: { id: string; title: string; status: string } | null }> } ``` A run reads `interrupted` when it never recorded an outcome: the next run began without one, or the Routine is no longer queued, claimed or running. Either way the server stopped during that run. A team routine's run `succeeded` once the lead's turn started; the team's work then continues in the chat. **`404 Not Found`**: `:id` is not a composite schedule id, or names no Routine: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "Unknown schedule \"<id>\"", "code": "unknown_schedule" } ``` **`500 Internal Server Error`**: any other error: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl 'http://localhost:18790/api/schedules/clawboo-routine:<routine-id>/runs?limit=5' ``` *** ## `GET /api/schedules/occurrences` The upcoming runs of Clawboo's own Routines, counted per time window you name. The board's calendar sends the viewer's local midnights, so each window is one day; that keeps a 23- or 25-hour day around a clock change correct. The count is worked out on the server because the scheduler reads cron in the server's time zone, so these are the times it will actually run. Only runs after now are counted, and only for Routines that will run. These are left out: * a paused Routine, one stopped in `error` after a failed run, and a spent one-shot (`idle` with no next run); * a Routine whose cron spec cannot be read; * a team routine whose team is gone, archived, opted out of server orchestration, or has no active member; * an agent routine whose agent is gone, archived, or a person; * every OpenClaw Gateway cron job (those are an OpenClaw agent's own work, not team work). For an `idle` Routine, the first run is its stored `nextRunAt`, the time the scheduler will actually use, and the cron spec gives the runs after it. A result is reused for up to a minute, and never past its first run, so boards that poll this cost little. * **Query params**: * `bounds` (required): 2 to 64 comma-separated epoch-millisecond integers in strictly ascending order, spanning at most 62 days. Window `i` is `[bounds[i], bounds[i + 1])`. * `teamId` (optional): only Routines whose `teamId` matches. A Routine for an agent on no team appears only without this filter. * **Request body**: none. ### Responses **`200 OK`**: only Routines with at least one run in some window are listed, each with one entry per window, in window order: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { now: number // the time the count started from timeZone: string | null // the IANA time zone the scheduler reads cron in routines: Array<{ schedule: ScheduleRecord // with teamName and agentName filled in, as on GET /api/schedules windows: Array<{ count: number // runs in the window, counted up to 96 more: boolean // true when the window holds more runs than `count` times: number[] // the window's first runs, ascending, at most 8 }> }> } ``` Counting stops at 96 (every fifteen minutes for a whole day), so a very frequent Routine costs a bounded amount of work; `more` says the window holds more than that. `timeZone` lets a caller in another time zone tell which zone a Routine's cron spec is read in. **`400 Bad Request`**: `bounds` is missing, has fewer than 2 or more than 64 values, is not strictly ascending integers, or spans more than 62 days: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "bounds must be 2 to 64 ascending epoch-ms integers spanning at most 62 days", "code": "invalid_bounds" } ``` **`500 Internal Server Error`**: the Routines could not be read: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "<message>" } ``` ### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Three local days starting at a midnight curl 'http://localhost:18790/api/schedules/occurrences?bounds=1791777600000,1791864000000,1791950400000,1792036800000' ``` *** ## Error envelope Every error here is `{ "error": string, "code"?: string }`. Branch on `code`, never on the message: | `code` | Status | Meaning | | - | - | - | | `invalid_body` | 400 | The body failed the shape check, the cron spec does not parse, or the template is invalid | | `bound_recurring_schedule` | 400 | A recurring spec was bound to an existing board task (one-shot specs only) | | `invalid_routine_target` | 400 | A Routine's team or agent is missing, archived, or does not match | | `invalid_bounds` | 400 | The occurrence windows are missing, out of order, too many, or too wide | | `unsupported_schedule_write` | 403 | The source is read-only (`observe-only`) for that action | | `unknown_schedule` | 404 | The composite id matched no source or no Routine, or a bound task does not exist | | `duplicate_firing_owner` | 409 | The bound board task is already scheduled by another engine; do not retry | | `illegal_schedule_transition` | 409 | The pause, resume or run is not allowed from the current status | | `team_task_domain_violation` | 422 | A `team-task` create was aimed at the Gateway cron | | `schedule_source_unavailable` | 503 | A write hit the Gateway cron while Clawboo's Gateway connection is down | | *(none)* | 500 | Any other error | A cron spec that does not parse answers `code: "invalid_body"` with a message that starts `Invalid cron spec`. A rate-limited request answers **429** `{ "error": "Too many requests. Wait a moment and retry." }` with a `Retry-After` header. **Source of truth:** `apps/web/server/api/schedules.ts`; the two sources in `apps/web/server/lib/scheduleSource/`; error classes in `packages/scheduler/src/errors.ts`; Routine states in `packages/db/src/routines/state-machine.ts`. ## Related * [Routines](/using/routines): create and manage Routines in the dashboard. * [Scheduling](/concepts/scheduling): how Routines and OpenClaw cron jobs differ, and how a run reaches a team. * [The board](/concepts/the-board): bound tasks and the one-claim rule. * [System API](/reference/rest-api/system): the OpenClaw Gateway the cron source talks to. * [@clawboo/scheduler](/reference/packages/scheduler): the shared record type, sources and errors. * [REST API overview](/reference/rest-api/index): authentication, rate limits and conventions. # Settings, health and layout API Source: https://docs.claw.boo/reference/rest-api/settings REST reference for the OpenClaw Gateway connection settings, the boot report behind System Health, and the saved positions of graph nodes. Three small surfaces: the OpenClaw Gateway connection settings, the boot report the **System Health** panel shows, and the node positions the dashboard saves for its graphs. Use this page when you point Clawboo at a Gateway from a script, or check an install's health from a monitor. Examples use the default port `18790` and no access token. With `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` to each `curl`. All six routes are on the general rate limit described in the [REST API overview](/reference/rest-api/index). ## Routes | Method | Path | Summary | | - | - | - | | GET | `/api/settings` | The Gateway URL, whether a token resolves, the first-run dismiss | | POST | `/api/settings` | Save the Gateway URL, token or first-run dismiss | | GET | `/api/health` | The latest boot report | | POST | `/api/health/recheck` | Run the boot probe again | | GET | `/api/graph-layout` | Saved node positions for one graph | | POST | `/api/graph-layout` | Save node positions for one graph | ## Settings Clawboo keeps the Gateway URL, the Gateway token and the time the first-run notice was dismissed in `<clawboo home>/settings.json` (`~/.clawboo/settings.json` unless `CLAWBOO_HOME` is set). Most other dashboard settings live in the SQLite `settings` table and are not served here. No route returns the Gateway token. The server adds it itself when it connects to the Gateway, so the browser never needs it. Errors use `{ "error": "<message>" }`. ### `GET /api/settings` #### Responses **`200 OK`**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { gatewayUrl: string // never empty, see below hasToken: boolean firstRunDismissedAt: number | null // epoch ms, or null } ``` `gatewayUrl` is the first of: 1. the URL saved in `settings.json`; 2. `ws://localhost:<port>`, when no token is saved and OpenClaw's `openclaw.json` has both a Gateway token and a port; 3. `ws://localhost:18789`. `hasToken` is `true` when a Gateway token resolves: the token saved in `settings.json` (a `${VAR}` value is read from the environment, then from OpenClaw's `.env`), or else the `gateway.auth.token` in OpenClaw's `openclaw.json`. So it can be `true` with nothing saved in Clawboo. OpenClaw's files are in its state directory, `~/.openclaw` by default. **`500 Internal Server Error`**: the settings could not be read, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/settings ``` ### `POST /api/settings` Saves only the fields you send; the rest keep their values, so a dismiss-only request leaves the Gateway URL alone. A field of the wrong type is ignored rather than rejected. * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { gatewayUrl?: string // ws:// or wss:// on any host; '' clears the saved URL gatewayToken?: string // '' clears the saved token firstRunDismissedAt?: number // epoch ms } ``` The URL is checked after trimming, and only a `ws:` or `wss:` URL is accepted, because the server later connects to it. After you clear it, `GET` returns the fallback above. Changing `gatewayUrl` or `gatewayToken` makes the server reconnect to the Gateway in the background. #### Responses **`200 OK`**: `{ "ok": true }`. **`400 Bad Request`**: the body is missing or not a JSON object: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "JSON body required" } ``` **`400 Bad Request`**: a non-empty `gatewayUrl` that is not a `ws://` or `wss://` URL: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "error": "gatewayUrl must be a ws:// or wss:// URL" } ``` **`500 Internal Server Error`**: saving failed, `{ "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} # Point Clawboo at a Gateway curl -X POST http://localhost:18790/api/settings \ -H 'Content-Type: application/json' \ -d '{"gatewayUrl":"ws://localhost:18789","gatewayToken":"<token>"}' # Record the first-run dismiss only curl -X POST http://localhost:18790/api/settings \ -H 'Content-Type: application/json' \ -d '{"firstRunDismissedAt":1761650000000}' ``` ## Health The server runs a boot probe at startup and keeps the result: one pass or fail per check, plus what the install resolved (folders, port, vault). The **System Health** panel shows it. These routes answer with the Gateway down, and `ok` (`true` when no check is fatal) is the one field a monitor needs. Responses pass through the same redaction as the logs, so a credential-shaped string inside a check's message is masked. On failure the health routes answer `{ "ok": false, "error": "<message>" }`. ### `GET /api/health` Returns the latest boot report. A request that arrives before the first report exists computes one. #### Responses **`200 OK`**: the boot report (see [The boot report](#the-boot-report) below), with `ok` added. Dates are ISO strings. **`500 Internal Server Error`**: computing a first report failed, `{ "ok": false, "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl http://localhost:18790/api/health ``` ### `POST /api/health/recheck` Runs the boot probe again: the **Re-run probe** button in System Health. When the Gateway cannot be reached, the answer waits out the Gateway check, about 1.5 seconds. Takes no body. #### Responses **`200 OK`**: the new boot report, the same shape as `GET /api/health`. **`500 Internal Server Error`**: `{ "ok": false, "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/health/recheck ``` ### The boot report ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} interface BootReport { ok: boolean // true when fatal is empty startedAt: string // ISO date finishedAt: string checks: Array<{ id: string ok: boolean message: string // short status detail?: string // longer detail, when there is any durationMs: number }> degraded: string[] // ids of failed checks the server runs without fatal: string[] // ids of failed checks nothing works without config: { logLevel: string budgetPosture: string budgetHardCapUsdCents: number | null budgetWarnSoftPct: number otelEnabledByDefault: boolean otelActive: boolean } resolved: { clawbooHome: string dbPath: string apiPort: number | null stateDir: string // OpenClaw's state directory vaultPresent: boolean // the encrypted vault file, secrets/runtime-keys.json, exists masterKeyOk: boolean } } ``` Most failures only degrade: the server keeps running and the panel shows a warning, so a degraded install still answers `ok: true` with a non-empty `degraded`. Three failures are fatal: `clawbooHomeWritable`, `databaseIntegrity` and `databaseSchema`. Each failed check's `detail` says what to do; resetting the Clawboo home folder is the last resort (see [Data and state](/operating/data-and-state)). The checks, in the order they run: | `id` | What it checks | Fatal | | - | - | - | | `clawbooHomeWritable` | The Clawboo home folder exists (it is created if missing) and is writable | **Yes** | | `vaultPerms` | `secrets/`, the master key and `proxy-device-identity.json` give no access to group or others. Passes on Windows, and before any of them exist | No | | `masterKeyBootSentinel` | A value encrypted on first boot still decrypts, so the master key has not changed. When it fails, saved runtime keys cannot be read | No | | `databaseIntegrity` | SQLite's `PRAGMA integrity_check` returns `ok` | **Yes** | | `databaseSchema` | The core tables exist and no column the schema declares is missing | **Yes** | | `apiPortFileMatches` | The api-port file holds the port the server listens on. Passes without checking when the probe runs before the server has a port | No | | `mcpServersHealthy` | Each built-in MCP server (Tasks, Memory, Tools, TeamChat) can be built and lists its tools | No | | `openclawGatewayReachable` | The server's Gateway connection comes up within 1.5 seconds. It always runs, because a Gateway URL always resolves, so an install without OpenClaw lists it in `degraded` while `ok` stays `true`; the server then serves the agents last synced to SQLite | No | | `otelExporterReachable` | With an OTLP endpoint set, the OpenTelemetry exporter started. With none set, it passes | No | `config` reports the defaults this build ships, not live overrides: `logLevel` reads `info` even when `LOG_LEVEL` is set. Only `otelActive` reflects this boot. | Field | Shipped value | Meaning | | - | - | - | | `logLevel` | `"info"` | The default log level | | `budgetPosture` | `"track-and-warn"` | Spend is tracked and warned about; nothing pauses unless you set a budget in hard-cap mode | | `budgetHardCapUsdCents` | `null` | No install-wide hard cap | | `budgetWarnSoftPct` | `80` | A budget warns at 80% of its limit | | `otelEnabledByDefault` | `false` | OpenTelemetry export is opt-in | | `otelActive` | this boot | `true` when `OTEL_EXPORTER_OTLP_ENDPOINT` or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is set | ## Graph layout The node positions the dashboard saves when you drag nodes on a graph, stored in the SQLite `graph_layouts` table under a pair of keys: a layout `name` and a Gateway URL. The URL is only part of the storage key: when the dashboard runs without a Gateway, it saves under `''`. Errors on `POST` use `{ "ok": false, "error": "<message>" }`. | `name` | Graph | | - | - | | `atlas-radial` | The Org chart in its radial layout (`atlas` is the Org chart's internal name) | | `atlas-top-down` | The Org chart in its tree layout; each layout keeps its own positions | | `team-<teamId>` | A team's **Team graph** | | `team-<teamId>-room-<roomId>` | A room's **Room graph**, kept apart from the team's | | `default` | The team graph when no team is selected | | `loose-<canvas>` | Skills and connectors placed on a graph with **+** and not yet attached to an agent: `loose-atlas` on the Org chart, `loose-team-<teamId>` on a team graph | In a `loose-` layout the position keys are node ids such as `loose:skill:web-search`. ### `GET /api/graph-layout` The query parameter for the Gateway URL is `url`, not `gatewayUrl`. * **Query params**: | Param | Type | Default | Notes | | - | - | - | - | | `name` | string | `'default'` | The layout name | | `url` | string | `''` | The Gateway URL the layout was saved under | #### Responses **`200 OK`**: the saved layout, or no positions when nothing is saved: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { positions: Record<string, { x: number; y: number }> } ``` This route never answers an error: a database failure also returns `{ "positions": {} }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl "http://localhost:18790/api/graph-layout?name=team-<team-id>&url=" ``` ### `POST /api/graph-layout` Creates or replaces the positions for one layout. The positions are stored as sent. * **Request body**: ```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}} { name?: string // default 'default' positions: Record<string, { x: number; y: number }> gatewayUrl: string // required; '' is allowed } ``` #### Responses **`200 OK`**: `{ "ok": true }`. **`400 Bad Request`**: the body is not a JSON object: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": false, "error": "invalid JSON" } ``` **`400 Bad Request`**: `gatewayUrl` is missing or not a string: ```json theme={"theme":{"light":"github-dark","dark":"github-dark"}} { "ok": false, "error": "gatewayUrl required" } ``` **`500 Internal Server Error`**: `{ "ok": false, "error": "<message>" }`. #### Example ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} curl -X POST http://localhost:18790/api/graph-layout \ -H 'Content-Type: application/json' \ -d '{"name":"team-<team-id>","gatewayUrl":"","positions":{"boo-a1":{"x":120,"y":40}}}' ``` **Source of truth:** `apps/web/server/api/settings.ts` with `packages/config/src/index.ts` (settings resolution), `apps/web/server/api/health.ts` with `apps/web/server/lib/bootProbe.ts` and `lib/defaults.ts`, and `apps/web/server/api/graphLayout.ts`. ## Related * [System Health](/using/system-health): the panel over the boot report * [Configuration](/reference/configuration): `settings.json` and the other files in the Clawboo home folder * [Environment variables](/reference/environment-variables): `CLAWBOO_HOME`, `LOG_LEVEL` and the OpenTelemetry variables * [System API](/reference/rest-api/system): installing, configuring and starting the OpenClaw Gateway * [Org chart](/using/org-chart): the graphs whose positions these routes save # System API Source: https://docs.claw.boo/reference/rest-api/system REST reference for /api/system: OpenClaw status, install, setup, Gateway control, config, device pairing and models, plus Clawboo version and self-update. Use these routes to manage the host itself. Most of them drive OpenClaw: detect what is installed, install the OpenClaw CLI, write its config, control the Gateway process, read or patch `openclaw.json`, approve a device-pairing request, and list the model catalog. The dashboard's OpenClaw setup and **Manage** section in **Settings > Runtimes**, the **System** panel and the update chip call them. The last two routes, `self-version` and `self-update`, are about Clawboo itself: which release this server runs, and installing a newer one. > [!NOTE] > > Every route here except the two `self-*` routes is for OpenClaw. OpenClaw runs as its own Gateway process that Clawboo starts and talks to, so its lifecycle lives here. [`/api/runtimes`](/reference/rest-api/runtimes) covers Clawboo Native (built in), Claude Code, Codex and Hermes, and the agent registry is in the [Agents API](/reference/rest-api/agents). Three routes stream **Server-Sent Events** instead of returning a JSON body: `POST /api/system/install-openclaw` and `POST /api/system/self-update` always, and `POST /api/system/gateway` for `start` and `restart`. Each is documented with an event catalog (event `type` and payload). Request bodies are JSON (up to 2 MB). Every route counts against the general [rate limit](/reference/rest-api/index#rate-limits) of 3,000 requests a minute per client address; routes marked **sensitive** also count against a limit of 60 a minute. The `curl` examples assume the access gate is off; with `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` (see [Authentication](/reference/rest-api/index#authentication)). ## Routes | Method | Path | Summary | Rate tier | Stream? | | - | - | - | - | - | | GET | `/api/system/status` | Node, OpenClaw and Gateway status | general | No | | POST | `/api/system/install-openclaw` | Install the OpenClaw CLI with `npm install -g` | sensitive | SSE | | POST | `/api/system/configure-openclaw` | Replace `openclaw.json` and `.env` for one provider (API only) | sensitive | No | | POST | `/api/system/auto-configure-openclaw` | Set up OpenClaw from a credential you already connected | sensitive | No | | POST | `/api/system/gateway` | Gateway control: `start`, `stop`, `restart`, `status` | sensitive | SSE for `start`/`restart` | | GET | `/api/system/openclaw-config` | Read `openclaw.json`, key flags and the ChatGPT sign-in state | general | No | | PATCH | `/api/system/openclaw-config` | Change parts of `openclaw.json` (model, port, keys, exec, a2a) | sensitive | No | | POST | `/api/system/approve-device` | Approve the latest pending device-pairing request | sensitive | No | | GET | `/api/system/models` | Model catalog (from the OpenClaw CLI, else the built-in list) | general | No | | GET | `/api/system/self-version` | Which Clawboo this server runs, and whether a newer one exists | general | No | | POST | `/api/system/self-update` | Install `clawboo@latest` globally, then restart into it | sensitive | SSE | *** ## `GET /api/system/status` Returns one snapshot of the Node runtime, the OpenClaw install and the Gateway process. The dashboard reads it at startup to decide what to show, and the OpenClaw setup and the **System** panel read it for the Gateway's state. When the Gateway is running and `.env` exists, the call also copies the Gateway token from `.env` into Clawboo's settings, so Clawboo never connects with a stale token. The OpenClaw check is cached per binary path and gives up on `openclaw --version` after 6 seconds, so a slow first start never holds up the server. `installed` says whether the binary resolves on `PATH`; `version` is `null` when the version check timed out or failed (the binary still reads installed). * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the status snapshot: ```ts theme={"theme":"github-dark"} { node: { version: string // process.version, e.g. "v22.3.0" major: number // parsed major version sufficient: boolean // whether this Node satisfies OpenClaw's engines, NOT a bare major check required: string // the range OpenClaw requires, e.g. ">=24.22.1 <25 || >=26.12.1" path: string // process.execPath } openclaw: { installed: boolean version: string | null // null when the version probe timed out / failed path: string | null // resolved binary path, or null when not installed stateDir: string // resolved OpenClaw state dir (~/.openclaw) configExists: boolean // openclaw.json present envExists: boolean // .env present } gateway: { running: boolean // PID alive OR port reachable port: number // PID-file port → openclaw.json gateway.port → 18789 pid: number | null // null unless a Clawboo-managed PID is alive managedByClawboo: boolean // a live PID file written by this server uptimeMs: number | null // since the managed start, else null } } ``` **`500 Internal Server Error`**: any failure assembling the snapshot: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/system/status ``` *** ## `POST /api/system/install-openclaw` Installs the OpenClaw CLI by running `npm install -g openclaw@~2026.9`. The pin is `~2026.9` (not `@latest`, and not `^2026.9`, which would allow any 2026.x) so a new install gets an OpenClaw whose config format matches what Clawboo writes. On success the installed version is read again and reported. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK` + `text/event-stream`**: the stream sends `data: <json>\n\n` frames with `Cache-Control: no-cache`. The install process is killed if the client closes the connection. #### Event catalog | `type` | When | Payload fields | | - | - | - | | `progress` | Install start | `step: 'installing'`, `message` | | `output` | Each non-empty stdout or stderr line | `line` | | `error` | Permission denied, spawn failure, or a non-zero exit | `code`, `message` | | `complete` | Process exits 0 | `success: true`, `version` | The `error` codes: | `code` | Meaning | | - | - | | `EACCES` | stderr mentioned `EACCES` or "permission denied" (a global install that needs elevation) | | `SPAWN_THROW` | The spawn call threw | | `SPAWN_ERROR` | The child process emitted an error | | `EXIT_<code>` | The process exited with a non-zero code (for example `EXIT_1`) | On a clean exit, `complete` carries the newly installed version (`'unknown'` when the version check could not read it). Example frames: ```text theme={"theme":"github-dark"} data: {"type":"progress","step":"installing","message":"Installing OpenClaw..."} data: {"type":"output","line":"added 1 package in 4s"} data: {"type":"complete","success":true,"version":"2026.9.1"} ``` An `EACCES` frame does not end the stream: the exit code still decides the outcome (`complete` on 0, `EXIT_<code>` otherwise), and its message suggests running the install with `sudo`. ### Example ```bash theme={"theme":"github-dark"} curl -N -X POST http://localhost:18790/api/system/install-openclaw ``` *** ## `POST /api/system/configure-openclaw` Writes a fresh `openclaw.json` and `.env` for one provider, makes a new Gateway token, and saves the Gateway URL and token in Clawboo's settings. No dashboard screen calls this route; it is for scripts and manual setup. The dashboard's OpenClaw setup (in **Settings > Runtimes** and onboarding's **Add more runtimes** step) uses `POST /api/system/auto-configure-openclaw` instead, and when that answers `needsKey` it saves the key you paste with `POST /api/providers/:id/connect` and calls auto-configure again. > [!WARNING] > > This route replaces `~/.openclaw/openclaw.json` and `~/.openclaw/.env` entirely, and makes a new Gateway token on every call. On an existing install that drops the agent roster (`agents.entries`), channels, plugins, the exec policy and every other key in `.env`, including keys saved from **Providers**. Use it only on a fresh install. To keep existing settings, use `auto-configure-openclaw` or `PATCH /api/system/openclaw-config`. Restart the Gateway afterwards so it runs with the new config and token. The written `openclaw.json` holds only: `gateway` (`mode: 'local'`, the port, token auth read from `GATEWAY_AUTH_TOKEN`), the default model under `agents.defaults.model.primary`, agent-to-agent coordination (`tools.agentToAgent.enabled: true`, `tools.sessions.visibility: 'all'`), and for Ollama an optional `models.providers.ollama` entry. The written `.env` holds `GATEWAY_AUTH_TOKEN=<64 hex characters>` plus one key line, and only its owner can read it (mode `0600`). The route also creates `~/.openclaw/workspace`. The token is kept on the server and never returned. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { provider: string // required: 'anthropic', 'openai', 'ollama', 'openai-codex', ... apiKey?: string // required except for the keyless 'ollama' and 'openai-codex' model?: string // default: the provider's default model (below) gatewayPort?: number // default 18789 } ``` Default models when `model` is omitted: `anthropic/claude-sonnet-4-5`, `openai/gpt-5.4`, `google/gemini-2.0-flash`, `openrouter/anthropic/claude-sonnet-4-5`, `xai/grok-3`, `groq/llama-3.3-70b-versatile`, `mistral/mistral-large-latest`, `moonshot/kimi-k2.5`, `minimax/MiniMax-M2.5`, `together/meta-llama/Llama-3.3-70B-Instruct-Turbo`, `nvidia/llama-3.1-nemotron-70b-instruct`, `huggingface/deepseek-ai/DeepSeek-R1`, `cerebras/zai-glm-4.7`, `venice/llama-3.3-70b`, `openai-codex/gpt-5.5`, and for `ollama` a pulled model (below). Any other provider gets `<provider>/default`. The key line in `.env` uses the provider's own variable (for example `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` for `google`, `HF_TOKEN` for `huggingface`). A provider with no known variable gets `CUSTOM_API_KEY`. `openai-codex` (the ChatGPT sign-in) writes no key line: OpenClaw uses its own sign-in profile, created with `openclaw models auth login --provider openai-codex`. `ollama` works differently, because OpenClaw runs only a model the local Ollama has pulled and loads its Ollama provider only when it has a credential: * The model is checked against Ollama's `/api/tags`. The requested model (or `llama3.2` when none is sent) is kept when it is pulled; otherwise the route writes the most recently pulled model, preferring one that can use tools, the same pick a new [Clawboo Native](/runtimes/native) agent gets. With no chat model pulled, or Ollama not answering, the request stands. It is always written as an `ollama/<model>` ref. * `.env` gets `OLLAMA_API_KEY=ollama-local`, the marker OpenClaw accepts for a local Ollama. It is not a secret. * OpenClaw calls `http://127.0.0.1:11434` and does not read `OLLAMA_BASE_URL`. When that variable is set, the route writes its address (without `/v1`) to `models.providers.ollama.baseUrl`, so OpenClaw uses the same Ollama whose models were checked. ### Responses **`400 Bad Request`**: the body is missing or not an object: ```json theme={"theme":"github-dark"} { "error": "JSON body required" } ``` **`400 Bad Request`**: `provider` is missing or empty: ```json theme={"theme":"github-dark"} { "error": "provider is required" } ``` **`400 Bad Request`**: a keyed provider with a missing or empty `apiKey`: ```json theme={"theme":"github-dark"} { "error": "apiKey is required for non-keyless providers" } ``` **`200 OK`**: both files written, the token made and saved: ```ts theme={"theme":"github-dark"} { ok: true, gatewayUrl: string, model: string } // gatewayUrl is ws://localhost:<port> ``` `model` is the default written to `openclaw.json`. For `ollama` it can differ from the model you sent, when Ollama has not pulled that one. **`500 Internal Server Error`**: a file or token failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/system/configure-openclaw \ -H 'Content-Type: application/json' \ -d '{"provider":"anthropic","apiKey":"sk-ant-...","gatewayPort":18789}' ``` *** ## `POST /api/system/auto-configure-openclaw` Sets up OpenClaw from a credential you have already connected, so the OpenClaw setup never asks for a key it could find itself. The dashboard's OpenClaw setup calls it first and shows the key prompt only when this route finds nothing to use. Unlike `configure-openclaw`, it keeps what is already in `openclaw.json` and `.env` (see the list after the responses). * **Path/query params**: none. * **Request body**: none (ignored). It looks for a credential in five steps, in order, and stops at the first that matches: | Step | Condition | Outcome | | - | - | - | | 1 | A provider key is connected (in the vault or in `~/.openclaw/.env`) | The key is written to `.env` and every OpenClaw agent's `auth-profiles.json`, config written, `ok: true` | | 2 | OpenClaw already holds an `openai-codex` (ChatGPT) sign-in profile | Config written, no key written (OpenClaw uses its own profile), `ok: true` | | 3 | The `codex` CLI is signed in, but OpenClaw has no profile of its own | Nothing written; `needsCodexAuth: true` and `loginCommand`, so the dashboard offers a ChatGPT sign-in | | 4 | Local Ollama is set up for Clawboo Native | Ollama's marker in `.env`, a pulled default model, config written, `ok: true` | | 5 | Nothing is connected | Nothing written; `needsKey: true`, so the dashboard shows the key prompt | In step 1, with several keys connected, the route takes the first in this fixed order, not your default native provider: anthropic, openai, google, openrouter, xai, groq, mistral, moonshot, minimax, together, nvidia, huggingface, cerebras, venice. A provider counts as connected when its key is in the vault or in `~/.openclaw/.env` (a variable only in the server's shell does not count), but the value written is the one that resolves first: the server's environment, then the vault, then `.env`. Step 3 exists because a `codex login` in your terminal is a ChatGPT subscription that OpenClaw cannot use until it holds a sign-in profile of its own. `loginCommand` is `openclaw models auth login --provider openai-codex`. The dashboard runs it for you through `POST /api/auth/cli-login/openclaw` and shows the command only if that fails. Step 4 uses the same Ollama setup Clawboo Native reads: you picked **Ollama** in onboarding or pressed **Use** on it in Clawboo Native's **Manage**, or no choice is recorded and `OLLAMA_BASE_URL` is set. **Disconnect Ollama**, or a full disconnect of Clawboo Native, turns it off again. It is the last resort before the key prompt. It writes what `configure-openclaw` writes for `ollama`: `OLLAMA_API_KEY=ollama-local` in `.env`, a default model your Ollama has pulled (when `llama3.2` is not pulled, the most recently pulled model, preferring one that can use tools, always as `ollama/<model>`), and, when `OLLAMA_BASE_URL` is set, its address as `models.providers.ollama.baseUrl`. ### Responses **`200 OK`** (steps 1, 2 and 4): `openclaw.json` was written and the Gateway URL and token saved in Clawboo's settings. `provider` is the provider that matched (`'openai-codex'` for step 2, `'ollama'` for step 4): ```ts theme={"theme":"github-dark"} { ok: true gatewayUrl: string // e.g. ws://localhost:18789 provider: string // step 4 only, when the default model is an ollama/ model: model?: string // the default in openclaw.json, kept or just written modelPulled?: boolean // false when Ollama does not list it, or is not answering pullCommand?: string // with modelPulled false, e.g. `ollama pull llama3.2` } ``` An Ollama default that is not pulled fails every OpenClaw turn, so the dashboard's setup stops on `pullCommand` before it starts the Gateway, with **Check again** (which calls this route again) and **Continue anyway**. **`200 OK`** (steps 3 and 5): nothing was written; the body says which prompt to show: ```ts theme={"theme":"github-dark"} { ok: false, needsCodexAuth: true, loginCommand: string } // or { ok: false, needsKey: true } ``` **`500 Internal Server Error`**: a file or token failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` What this route keeps, which makes it safe on an existing install: * **The Gateway token.** `GATEWAY_AUTH_TOKEN` in `.env` is reused; a new token is made only when there is none. * **Your `openclaw.json` settings.** Only `gateway.mode` is set (to `'local'`). `gateway.port`, `gateway.auth`, the default model, `tools.agentToAgent`, `tools.sessions` and, in step 4, `models.providers.ollama` are filled in only when missing. * **An existing `OLLAMA_API_KEY`.** Step 4 keeps one that has a value, since any value lets OpenClaw load its Ollama provider. * **The port.** There is no `gatewayPort` input. A missing `gateway.port` becomes `18789`; an existing one is left alone. The returned and saved `gatewayUrl` uses the port the Gateway route will use: the port in Clawboo's Gateway PID file, then `gateway.port` when it is a positive number, then `18789`. So a Gateway you moved to another port is the one Clawboo connects to. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/system/auto-configure-openclaw ``` *** ## `POST /api/system/gateway` Starts, stops, restarts or reports on the Gateway process. `action` picks the operation. `status` and `stop` return JSON; `start` and `restart` stream SSE, because they wait for the port to open and report progress. The port comes from Clawboo's Gateway PID file, then `gateway.port` in `openclaw.json`, then `18789`. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { action: 'start' | 'stop' | 'restart' | 'status' } ``` ### Responses **`400 Bad Request`**: body is missing or not an object: ```json theme={"theme":"github-dark"} { "error": "JSON body required" } ``` **`400 Bad Request`**: `action` is not one of the four values: ```json theme={"theme":"github-dark"} { "error": "action must be 'start', 'stop', 'restart', or 'status'" } ``` **`200 OK` (`action: 'status'`)**: current process state (a stale PID file is cleaned up as a side effect): ```ts theme={"theme":"github-dark"} { running: boolean // PID alive OR port reachable pid: number | null // null unless a managed PID is alive port: number uptimeMs: number | null } ``` **`200 OK` (`action: 'stop'`)**: the stop result. `stopped: true` means a process was signalled (SIGTERM, then SIGKILL after a 2-second grace). When none was found, the body is `{ ok: false, message }` instead: ```ts theme={"theme":"github-dark"} { ok: true, stopped: true } // or, when no gateway process was found: { ok: false, message: 'No gateway process found' } ``` **`200 OK` + `text/event-stream` (`action: 'start'` or `'restart'`)**: the stream sends `data: <json>\n\n` frames. `restart` first stops the running process. The route starts `openclaw gateway --port <port> --allow-unconfigured` as a detached process that keeps running when the client disconnects or Clawboo restarts. The Gateway's output goes to `~/.clawboo/gateway.log` (under `CLAWBOO_HOME` when set), and the stream relays new lines from that file as `output` frames. #### Event catalog (`start` / `restart`) | `type` | When | Payload fields | | - | - | - | | `progress` | `restart` stop phase | `step: 'stopping'`, `message` | | `progress` | Spawn start, or joining an in-flight launch | `step: 'starting'`, `message` | | `progress` | Each poll while waiting for the port to bind | `step: 'waiting'`, `message` (`Waiting for gateway... (n/120)`) | | `progress` | The Gateway asked for `openclaw doctor --fix` and the repair is running, or another start is already running it | `step: 'repairing'`, `message` | | `output` | Each non-empty line of Gateway output (read from `gateway.log`) and of the repair | `line` | | `complete` | The port opened, or the Gateway was already running | `success: true`, `port`, and `pid` or `message` | | `error` | OpenClaw missing, a spawn failure, the Gateway exits with a stated reason, the repair fails, an unexpected error, or the 60 s wait runs out | `code`, `message` | Terminal `error` `code` values: | `code` | Meaning | | - | - | | `NOT_INSTALLED` | OpenClaw binary not found | | `SPAWN_THROW` | The spawn call threw | | `SPAWN_ERROR` | The child process emitted an error | | `TIMEOUT` | The port did not become reachable within \~60s (120 × 500ms polls); the message carries the Gateway's stated reason when its log has one | | `GATEWAY_EXITED` | The Gateway exited before binding and its log says why (`Gateway failed to start: ...`); reported at once rather than after the poll | | `DOCTOR_FIX_FAILED` | The Gateway asked for `openclaw doctor --fix` and running it failed; the message carries doctor's last line | | `DOCTOR_FIX_REQUIRED` | The Gateway still asked for `openclaw doctor --fix` after the repair ran once | | `INTERNAL` | An unexpected error after the stream opened | **Automatic repair.** After an OpenClaw upgrade the Gateway can refuse to start until its data is migrated (for example `Legacy workspace setup state requires migration for ~/.openclaw/workspace; run openclaw doctor --fix`). When a launch exits with that request, the route runs `openclaw doctor --fix --non-interactive` once, with `OPENCLAW_SERVICE_REPAIR_POLICY=external` so doctor never installs, loads or restarts a launchd or systemd Gateway service, streams its output, and launches again. Only the failure line of that launch counts, so a passing mention of doctor elsewhere in the log never triggers it. A second `start` or `restart` that arrives while the repair runs waits for it instead of launching against data that is mid-migration. The repair has a 5-minute limit. Every launch, and the repair, runs with an explicit working directory that exists: the OpenClaw CLI refuses to start in a deleted one. When the port opens, the route copies the `.env` token into Clawboo's settings, starts reconnecting Clawboo's own Gateway connection (which then syncs the agent list), and sends `complete` without waiting for that reconnect. A `start` that finds the port already open sends `complete` at once with `message: 'Gateway already running'`. A `start` that finds a Gateway Clawboo launched still starting up waits for that launch instead of starting a second one. Example frames: ```text theme={"theme":"github-dark"} data: {"type":"progress","step":"starting","message":"Starting gateway on port 18789..."} data: {"type":"progress","step":"waiting","message":"Waiting for gateway... (3/120)"} data: {"type":"complete","success":true,"pid":48211,"port":18789} ``` ### Example ```bash theme={"theme":"github-dark"} # Stop (JSON) curl -X POST http://localhost:18790/api/system/gateway \ -H 'Content-Type: application/json' \ -d '{"action":"stop"}' # Start (SSE) curl -N -X POST http://localhost:18790/api/system/gateway \ -H 'Content-Type: application/json' \ -d '{"action":"start"}' ``` *** ## `GET /api/system/openclaw-config` Reads `openclaw.json`, flags for which provider keys OpenClaw holds (from `.env` and the agents' `auth-profiles.json`), the exec policy, and the state of OpenClaw's ChatGPT sign-in. The flags report presence only; no key value is returned. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the config and flags: ```ts theme={"theme":"github-dark"} { config: Record<string, unknown> | null // the parsed openclaw.json, or null when missing or unreadable env: { hasAnthropicKey: boolean hasOpenAIKey: boolean hasGoogleKey: boolean hasOpenRouterKey: boolean hasXaiKey: boolean hasGroqKey: boolean hasMistralKey: boolean hasMoonshotKey: boolean hasMiniMaxKey: boolean hasTogetherKey: boolean hasNvidiaKey: boolean hasHuggingFaceKey: boolean hasCerebrasKey: boolean hasVeniceKey: boolean hasGatewayToken: boolean } version: string | null // OpenClaw version; null when the check failed execAsk: unknown | null // tools.exec.ask from openclaw.json execSecurity: unknown | null // tools.exec.security codexAuth: { profile: boolean // OpenClaw holds its own openai-codex (ChatGPT) sign-in profile codexCli: boolean // a `codex login` exists on this machine bootstrapTrusted: boolean // currently false: Clawboo does not rely on OpenClaw making a profile from a codex login loginCommand: string // 'openclaw models auth login --provider openai-codex' } } ``` A provider flag is `true` when its variable has a value in `.env`, or an agent's `auth-profiles.json` holds a key for that provider. Only one agent's file counts: the scan stops at the first agent whose `auth-profiles.json` holds any key. `hasGoogleKey` matches `GEMINI_API_KEY` or `GOOGLE_API_KEY`; `hasHuggingFaceKey` matches `HF_TOKEN` or `HUGGINGFACE_HUB_TOKEN`. **`500 Internal Server Error`**: a read failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/system/openclaw-config ``` *** ## `PATCH /api/system/openclaw-config` Changes parts of `openclaw.json` and, when keys are sent, writes them to `.env` and every OpenClaw agent's `auth-profiles.json`. Every field is optional and only the fields you send are touched. A missing or unreadable `openclaw.json` is treated as empty. Keys sent here go to OpenClaw's files only, not to Clawboo's vault. * **Path/query params**: none. * **Request body** (all fields optional): ```ts theme={"theme":"github-dark"} { model?: string // agents.defaults.model.primary (and agents.defaults.contextTokens, below) fallbacks?: string[] // agents.defaults.model.fallbacks (non-strings dropped) gatewayPort?: number // gateway.port, and Clawboo's saved Gateway URL apiKeys?: Array<{ provider: string; key: string }> // .env and every agent's auth-profiles.json agentModel?: { agentId: string; model: string | null } // agents.entries.<agentId>.model exec?: { ask?: string; security?: string } // tools.exec.ask, tools.exec.security agentToAgent?: { enabled: boolean } // tools.agentToAgent.enabled } ``` What each field writes: * **`model`** sets the default model. It also sets `agents.defaults.contextTokens` to the context window the OpenClaw CLI's model catalog reports for that exact model; when the catalog has no entry, `contextTokens` is not written. * **`agentModel`** sets one agent's model override in OpenClaw's roster, `agents.entries.<agentId>.model`. A legacy `agents.list` array is folded into `agents.entries` and removed first, because OpenClaw rejects a config that has both. A `model` of `null` or `''` deletes only the override and keeps the agent's entry, since the entry is the agent's place in the roster. When the roster then holds more than one agent and has no `agents.ownership`, it is set to `'explicit'`. * **`gatewayPort`** sets `gateway.port` and saves `ws://localhost:<port>` as Clawboo's Gateway URL. * **`apiKeys`** upserts each key under its provider's variable (a provider with no known variable gets `<PROVIDER>_API_KEY`) and as the `<provider>:default` profile in each agent's `auth-profiles.json`. * **`agentToAgent`** with `enabled: true` also sets `tools.sessions.visibility: 'all'`. ### Responses **`400 Bad Request`**: the body is missing or not an object: ```json theme={"theme":"github-dark"} { "error": "JSON body required" } ``` **`400 Bad Request`**: `agentModel.agentId` is `__proto__`, `constructor` or `prototype`. Nothing is written: ```json theme={"theme":"github-dark"} { "error": "invalid agentId" } ``` **`200 OK`**: written. `hotReloadHint` is present only when the Gateway is reachable, as a sign that a client can reload its config: ```ts theme={"theme":"github-dark"} { ok: true } // or, when the Gateway is running: { ok: true, hotReloadHint: true } ``` **`500 Internal Server Error`**: a file failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PATCH http://localhost:18790/api/system/openclaw-config \ -H 'Content-Type: application/json' \ -d '{"model":"anthropic/claude-sonnet-4-5","agentToAgent":{"enabled":true}}' # Give one agent its own model curl -X PATCH http://localhost:18790/api/system/openclaw-config \ -H 'Content-Type: application/json' \ -d '{"agentModel":{"agentId":"<agent-id>","model":"openai/gpt-5.4"}}' ``` *** ## `POST /api/system/approve-device` Approves the most recent pending OpenClaw device-pairing request. OpenClaw does not pair a new device on its own: the device waits until a person approves it, and until then its Gateway connection fails with `NOT_PAIRED` ("pairing required: device is not approved yet"). The dashboard's pairing card (**Approve this device**) calls this route. It runs two commands. First `openclaw devices approve --latest`, which only previews: it prints the pending request's UUID (in a line like `Approve this exact request with: openclaw devices approve <UUID>`) and exits non-zero. The route reads the UUID from that output, then runs `openclaw devices approve <UUID>` to approve it. The first command gets 5 seconds and the second 10. * **Path/query params**: none. * **Request body**: none. ### Responses **`400 Bad Request`**: OpenClaw is not installed: ```json theme={"theme":"github-dark"} { "error": "OpenClaw not installed" } ``` **`500 Internal Server Error`**: the preview command could not run (not found, or timed out): ```json theme={"theme":"github-dark"} { "error": "Failed to run openclaw: <message>" } ``` **`404 Not Found`**: the preview output named no pending request. `details` is its first 500 characters: ```json theme={"theme":"github-dark"} { "error": "No pending device pairing requests found", "details": "<preview output>" } ``` **`200 OK`**: the device was approved. `output` is the command's output, up to 1,000 characters: ```ts theme={"theme":"github-dark"} { ok: true, requestId: string, output: string } ``` **`500 Internal Server Error`**: the approval command failed. The message carries its error output, up to 500 characters: ```json theme={"theme":"github-dark"} { "error": "Failed to approve device: <message>", "requestId": "<uuid>" } ``` > [!NOTE] > > Finding the UUID depends on the wording of OpenClaw's output (the pattern `/openclaw devices approve\s+([a-f0-9-]{36})/i`). If an OpenClaw release changes that line, this route answers 404. The pairing card then shows the error, and its footer always lists the manual `openclaw devices approve` commands. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/system/approve-device ``` *** ## `GET /api/system/models` The model catalog behind the OpenClaw model pickers. It comes from `openclaw models list --all --json` (cached for 5 minutes); without the CLI, the built-in catalog is used instead. The result keeps only the 14 key providers, `ollama` and `openai-codex`, with provider names in the built-in catalog's display spelling. A known provider the CLI does not list is filled in from the built-in catalog. `configuredProviders` lists the providers OpenClaw can use: those with a key in `.env` or in an agent's `auth-profiles.json`, those with a sign-in profile (for example `openai-codex` after a ChatGPT sign-in), and always `ollama`. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the grouped catalog: ```ts theme={"theme":"github-dark"} { groups: Array<{ provider: string models: Array<{ id: string; label: string }> }> | null // null only after an unexpected error configuredProviders: string[] // always includes 'ollama' } ``` > [!NOTE] > > This route never answers with an error status. An unexpected error comes back as `{ "groups": null, "configuredProviders": [] }` with status **200**, so a picker can fall back to its own list. ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/system/models ``` *** ## `GET /api/system/self-version` Which Clawboo this server runs, how it was installed, and whether a newer release is published. The dashboard's **Update available** chip reads it, and so does the `clawboo` command when it finds a server already running (see the [CLI reference](/reference/cli)). ### Query parameters | Param | Values | Effect | | - | - | - | | `local` | `1` | Skip the npm-registry probe. `latest` and `checkedAt` come back `null` and `updateAvailable` `false`, i.e. "not checked". | `?local=1` is for the CLI, which reads this route each time it attaches to a running server only to compare versions. The registry check is capped at 5 seconds and a failed check is not cached, so without it an offline machine would wait on every launch. ### Responses Always `200`. An unreachable registry is a `null` `latest`, never an error status. | Field | Type | Notes | | - | - | - | | `current` | `string` | The running version. `CLAWBOO_VERSION` if set (the CLI injects it), else the shipped `package.json`, else `0.0.0-dev`. | | `latest` | `string \| null` | npm's `latest` for `clawboo`. `null` when unreachable or skipped. Cached \~6 h. | | `updateAvailable` | `boolean` | True only for a real (non-`0.0.0*`) `current` with a strictly greater `latest`. | | `updateCommand` | `string` | `npx clawboo@latest` for an npx run, `npm install -g clawboo@latest` otherwise. | | `installMethod` | `'global' \| 'npx' \| 'dev'` | Derived from the running entry path. | | `applyable` | `boolean` | Whether `POST /api/system/self-update` can succeed here, i.e. `installMethod === 'global'`. | | `isDeprecated` | `boolean` | `current` is on the npm-deprecated `0.1.x` line. | | `checkedAt` | `number \| null` | Epoch ms of the last registry fetch, `null` when `latest` is `null`. | > [!NOTE] > > A dev checkout reports `current: "0.0.0-dev"` and is never told an update is available. The chip and the CLI both treat a `0.0.0*` version as "don't nag", and the CLI never offers to restart such a server, so a contributor's `pnpm dev` process is never a target. ### Example ```bash theme={"theme":"github-dark"} # Full payload, including the registry check curl http://localhost:18790/api/system/self-version # Just this server's identity, no outbound network call curl 'http://localhost:18790/api/system/self-version?local=1' ``` *** ## `POST /api/system/self-update` Installs `clawboo@latest` globally and restarts the server into it. Streams **Server-Sent Events**; takes no request body. Only a `global` install can update itself. An `npx` run lives in npm's `_npx` cache, so a global install lands somewhere the running copy cannot reach, and a dev checkout never updates itself. Both get an `unsupported` event with the command to run instead (the chip hides its button in those cases anyway). ### Event stream | `type` | When | Payload fields | | - | - | - | | `unsupported` | `installMethod` is not `global` | `method`, `command`, `message` | | `progress` | Install start | `step: 'installing'`, `message` | | `output` | Each non-empty line of npm output | `line` | | `error` | A spawn failure, or npm exits non-zero; at most one per stream | `code`, `message`, and on a non-zero exit `exitCode` and `detail` | | `installed-elsewhere` | npm exited 0 but the on-disk version did not change, or the API port is unknown | `version`, `message` | | `installed` | npm exited 0 and the on-disk version changed | `version` | | `restarting` | Just before the new server starts | `port`, `version` | | `code` | Meaning | | - | - | | `EACCES` | npm's output named `EACCES`, `EPERM` or "permission denied": the install needs elevation | | npm's code | npm printed its own code (`ETARGET`, `ENOTFOUND`, ...) | | `EXIT_<code>` | A non-zero npm exit that printed no code of its own (e.g. `EXIT_7`) | | `SPAWN_THROW` | The spawn call threw | | `SPAWN_ERROR` | The child process emitted an error | On a non-zero exit, `detail` is npm's own reason in one line (the `Error:` line, or the first line of its error block, or the name of a package whose install script failed), and `message` leads with it, so the chip shows why rather than only an exit code. `detail` is `null` when npm's output named no reason. npm runs in a working directory that exists, because npm exits with code 7 when its working directory has been deleted. After `restarting`, the server starts a new server on the same port (`CLAWBOO_API_PORT`), told to wait until that port is free (`CLAWBOO_AWAIT_PORT`), and then exits so the new one can bind. The new server starts in a working directory that exists, and a server that does start in a deleted directory moves into the Clawboo directory before it loads anything. `CLAWBOO_VERSION` is removed from its environment, so it reads its version from the newly installed package. The browser keeps polling the same address and reloads when the new server answers. > [!NOTE] > > `installed-elsewhere` means the global install landed somewhere this running copy does not point at, so a restart would run the old code again. The server stays up and asks you to restart it yourself. From a terminal, run the `updateCommand` that `self-version` reports, then `clawboo restart` (see the [CLI reference](/reference/cli)). ### Example ```bash theme={"theme":"github-dark"} curl -N -X POST http://localhost:18790/api/system/self-update ``` *** ## Error envelope Errors use the standard `{ "error": string }` body. `approve-device` adds `details` on its 404 and `requestId` on the 500 from the approval command. `GET /api/system/models` never returns an error status; it falls back to a `200` with a `null` catalog. The SSE routes report failures as `error` frames once the stream is open. Going over a rate limit answers **429** with `{ "error": "Too many requests. Wait a moment and retry." }` (general) or `{ "error": "Too many requests for this operation. Wait a moment and retry." }` (sensitive), plus a `Retry-After` header. **Source of truth:** `apps/web/server/api/system.ts` and `systemUpdate.ts`; the version check in `apps/web/server/lib/updateCheck.ts`; key files in `apps/web/server/lib/openclawEnv.ts`. ## Related * [OpenClaw](/runtimes/openclaw): set up OpenClaw, the Gateway and device pairing in the dashboard. * [System](/using/system): Gateway control and OpenClaw settings in the **System** panel. * [System Health](/using/system-health): the start-up checks behind `/api/health`. * [Settings API](/reference/rest-api/settings): the saved Gateway URL and token these routes write. * [Runtimes API](/reference/rest-api/runtimes): Clawboo Native, Claude Code, Codex and Hermes. * [REST API overview](/reference/rest-api/index): authentication, rate limits and conventions. # Teams API Source: https://docs.claw.boo/reference/rest-api/teams REST reference for teams: teams and members, chat rooms, team chat (send, stop, clear, live stream), the intro gate, team rules and peer chat. Use these routes to manage teams and everything scoped to one: creating and changing teams, which agents belong to them, the team's chat rooms, the per-team **Know Your Team** intro flags, the team rules, the team chat (send a message, stop, clear, and the live stream) with its activity summary, and the agents' peer-chat room (read it, or start one exchange). Teams are stored in the SQLite `teams` table. An agent's team is the nullable `agents.teamId` column: a team has many agents, and an agent belongs to at most one team. A team's chat rooms are the `rooms` and `room_members` tables. The intro flags and team rules are not tables; they are JSON values in the `settings` table under `team-onboarding:<teamId>` and `team-rules:<teamId>`. The peer-chat room is the `team_chat` table, the one the [TeamChat MCP server](/reference/rest-api/tools-and-mcp) writes. > [!NOTE] > > `DELETE /api/teams/:id` keeps the team's agents: it sets their `teamId` to `null` and removes the team's rules, intro flags, General clear time and resume pointers, and the team's folder with its files. The dashboard's **Delete with agents** first deletes each agent with `DELETE /api/agents/:agentId` (see the [Agents API](/reference/rest-api/agents)) and then deletes the team, so a REST caller that wants the same result deletes the agents itself. > [!NOTE] > > Two different things on this page are called rooms. A team's **chat rooms** (`/api/teams/:id/rooms`, and the `roomId` the chat routes take) are conversations inside the team chat, each with some of the team's members. The team's main chat, **General**, is the default: it has no room row, and leaving `roomId` out means General. The **peer-chat room** (`/api/team-chat`, whose `roomId` is `team:<teamId>`) is where the TeamChat MCP server's posts go. Its id is never a chat room id, and the dashboard does not show it. Request bodies are JSON (up to 2 MB). Every route here is on the general [rate limit](/reference/rest-api/index#rate-limits) (3,000 requests a minute per client address), including the ones that start agent work. The `curl` examples assume the access gate is off; with `STUDIO_ACCESS_TOKEN` set, add `-b 'clawboo_access=<token>'` (see [Authentication](/reference/rest-api/index#authentication)). Most routes name the team `:id` (the single-room routes add `:roomId`); the team-rules routes name it `:teamId`. Both are the team id. ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/teams` | List teams with `agentCount`, plus agent→team assignments | No | | GET | `/api/teams/member-roles` | Each team member's one-line role, for New task suggestions | No | | POST | `/api/teams` | Create a team (optional client-provided UUID) | No | | PATCH | `/api/teams/:id` | Update a team (partial) | No | | DELETE | `/api/teams/:id` | Delete a team, orphan its agents, clean team-scoped settings | No | | POST | `/api/teams/:id/agents` | Assign (upsert) an agent into the team | No | | DELETE | `/api/teams/:id/agents/:agentId` | Remove an agent from a team | No | | GET | `/api/teams/:id/rooms` | List the team's chat rooms | No | | POST | `/api/teams/:id/rooms` | Create a chat room from a subset of the team's members (201) | No | | PATCH | `/api/teams/:id/rooms/:roomId` | Rename a room, replace its members, or pin it | No | | DELETE | `/api/teams/:id/rooms/:roomId` | Archive a room: stop its work, keep its conversation | No | | GET | `/api/teams/:id/onboarding` | Read the per-team onboarding flags + user-intro text | No | | PATCH | `/api/teams/:id/onboarding` | Merge-update the onboarding state | No | | GET | `/api/teams/:id/activity-summary` | Short summary of the team's brief, board, memory and chat | No | | POST | `/api/teams/:id/chat` | Send a message to a chat; the reply runs in the background | No | | POST | `/api/teams/:id/chat/stop` | Stop one chat's running turns and release their tasks | No | | POST | `/api/teams/:id/chat/clear` | Clear General or one room's chat (messages set aside) | No | | GET | `/api/teams/:id/chat/stream` | Live tail of one chat (General or a room) | SSE | | GET | `/api/team-rules/:teamId` | Read the durable team-rules text | No | | PUT | `/api/team-rules/:teamId` | Replace the team-rules text (4000-char cap) | No | | GET | `/api/team-chat` | Read the team's peer-chat room from a cursor | No | | POST | `/api/team-chat/exchange` | Start one bounded peer-chat exchange | No | *** ## `GET /api/teams` Lists every team with a computed `agentCount` (a `COUNT(*)` subquery over `agents.team_id`). Also returns the full agent→team assignment list so a client can patch its fleet store after hydration. Archived teams are excluded unless `includeArchived=true`. Each team also says whether the server orchestrates it (`serverOrchestrated`) and when its General chat was last cleared (`chatClearedAt`); a room's clear time is on the room (see `GET /api/teams/:id/rooms`). * **Path params**: none. * **Query params**: | Param | Type | Default | Notes | | - | - | - | - | | `includeArchived` | string | `false` | `"true"` includes archived teams (`isArchived = 1`) | * **Request body**: none. ### Responses **`200 OK`**: the team list plus assignments: ```ts theme={"theme":"github-dark"} { teams: Array<{ id: string name: string icon: string color: string colorCollectionId: string | null templateId: string | null leaderAgentId: string | null isArchived: number // 0 | 1 createdAt: number updatedAt: number agentCount: number // COUNT(*) of agents with this team_id serverOrchestrated: boolean // true unless the team opted out (see POST /api/teams/:id/chat) chatClearedAt: number | null // when General's chat was last cleared; null = never }> assignments: Array<{ agentId: string; teamId: string }> // agents with a non-null teamId } ``` **`500 Internal Server Error`**: any DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/teams curl 'http://localhost:18790/api/teams?includeArchived=true' ``` *** ## `GET /api/teams/member-roles` The one-line role each live team member states in its SOUL.md, for the board's New task dialog to suggest who fits a sentence (see [Giving an agent a task](/using/board#give-an-agent-a-task)). Roles are read with the same reader as the team lead's roster: a `- **Role**:` bullet, else the first Role or Mission section, else the intro paragraph. An agent with no role there is left out: OpenClaw agents (their files live on the Gateway), agents with no persona or the shipped default prompt, teamless agents and archived agents. * **Path params**: none. * **Request body**: none. ### Responses **`200 OK`**: roles keyed by agent id: ```ts theme={"theme":"github-dark"} { roles: Record<string, string> // agentId -> one-line role; agents without one are absent } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/teams/member-roles ``` *** ## `POST /api/teams` Creates a team. The server makes a random UUID for the id unless the body sends a valid UUID `id`; the create-team UI seeds a client-side id so the Boo color-palette preview matches the deployed team (per-team color rotation is seeded off the team id). `name`, `icon`, and `color` are required; everything else is optional and stored as supplied (`null` when omitted). * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { name: string // required icon: string // required (emoji) color: string // required (hex) colorCollectionId?: string // null when omitted templateId?: string // null when omitted leaderAgentId?: string // null when omitted id?: string // honored only if it matches the UUID regex; else server-minted serverOrchestrated?: boolean // true also writes team-server-orchestrated:<id> = 'true' } ``` ### Responses **`400 Bad Request`**: body is missing or not an object: ```json theme={"theme":"github-dark"} { "error": "invalid JSON" } ``` **`400 Bad Request`**: a required field is missing: ```json theme={"theme":"github-dark"} { "error": "name, icon, and color are required" } ``` **`200 OK`**: the created team (note: **200**, not 201): ```ts theme={"theme":"github-dark"} { team: { id: string name: string icon: string color: string colorCollectionId: string | null templateId: string | null leaderAgentId: string | null isArchived: 0 agentCount: 0 serverOrchestrated: boolean createdAt: number updatedAt: number } } ``` **`500 Internal Server Error`**: the insert failed: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/teams \ -H 'Content-Type: application/json' \ -d '{"name":"Research Squad","icon":"🔬","color":"#34d399"}' ``` *** ## `PATCH /api/teams/:id` Partially updates a team. Only the fields present in the body are written; `updatedAt` is always refreshed. `isArchived` is coerced to `0` or `1` from the body's truthiness. Returns the full updated row (including a recomputed `agentCount`). * **Path params**: `id` (team id). * **Request body** (all optional): ```ts theme={"theme":"github-dark"} { name?: string icon?: string color?: string colorCollectionId?: string | null isArchived?: number // truthy → 1, falsy → 0 leaderAgentId?: string | null } ``` ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`400 Bad Request`**: the body is missing or not an object: ```json theme={"theme":"github-dark"} { "error": "invalid JSON" } ``` **`404 Not Found`**: no team with that id: ```json theme={"theme":"github-dark"} { "error": "team not found" } ``` **`200 OK`**: the updated team: ```ts theme={"theme":"github-dark"} { team: { id: string name: string icon: string color: string colorCollectionId: string | null templateId: string | null leaderAgentId: string | null isArchived: number createdAt: number updatedAt: number agentCount: number } } ``` **`500 Internal Server Error`**: the update failed: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PATCH http://localhost:18790/api/teams/<team-id> \ -H 'Content-Type: application/json' \ -d '{"name":"Renamed","leaderAgentId":"<agent-id>"}' ``` *** ## `DELETE /api/teams/:id` Deletes the team and keeps its agents. In one call it (1) sets `teamId = null` on every agent in the team, (2) deletes these team-scoped `settings` rows: `team-rules:<teamId>`, `team-onboarding:<teamId>`, General's clear time `team-chat-cleared:<teamId>`, and every member's native resume pointer for the team and for each of its rooms, (3) deletes the team row, and (4) when a team row was deleted, removes the team's folder, `<CLAWBOO_HOME>/workspaces/<teamId>`, with every file in it. The `boo_zero_team_briefs` and `rooms` tables FK-cascade on the team delete (room memberships go with their rooms), so per-team briefs and chat rooms clean themselves. The team's board tasks, chat messages and peer-chat posts are not deleted, and neither are the `team-server-orchestrated:<teamId>` setting and each room's clear time. * **Path params**: `id` (team id). * **Request body**: none. ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`200 OK`**: the team was deleted and its agents orphaned. `folderRemoved` is `false` when the folder could not be removed, or when no team had that id: ```json theme={"theme":"github-dark"} { "ok": true, "folderRemoved": true } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` > [!NOTE] > > The route does not answer 404 for an unknown team id: the updates and the delete match nothing, so it still returns `{ ok: true, folderRemoved: false }` and touches no folder. ### Example ```bash theme={"theme":"github-dark"} curl -X DELETE http://localhost:18790/api/teams/<team-id> ``` *** ## `POST /api/teams/:id/agents` Assigns an agent to the team. This is an **upsert**: if the agent row does not exist it is created (with `status: 'idle'` and `gatewayId = agentId`); if it exists, only its `teamId` and `updatedAt` are set. The optional `agentName` is used as the display name when creating a new row (it defaults to the `agentId`). Two more behaviors: * An OpenClaw agent that was deleted while its Gateway was down, and is not yet deleted in the Gateway, cannot be assigned: the route answers **409**, because the upsert would bring its row back. * When the agent runs on Clawboo Native, the route creates the default native Boo Zero before it answers, if none exists yet and a native provider is set up. So Boo Zero exists by the time the response arrives. * **Path params**: `id` (team id). * **Request body**: ```ts theme={"theme":"github-dark"} { agentId: string // required agentName?: string // display name when creating a new row; defaults to agentId } ``` ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`400 Bad Request`**: the body is missing/non-object or has no `agentId`: ```json theme={"theme":"github-dark"} { "error": "agentId is required" } ``` **`409 Conflict`**: the agent is an OpenClaw agent whose delete is still pending (it was deleted while the Gateway was down): ```json theme={"theme":"github-dark"} { "error": "agent is being deleted" } ``` **`200 OK`**: the agent was assigned (created or updated): ```json theme={"theme":"github-dark"} { "ok": true } ``` **`500 Internal Server Error`**: the upsert failed. The route does not check the team first, so an unknown team id lands here (a `FOREIGN KEY constraint failed` message), not on a 404: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/teams/<team-id>/agents \ -H 'Content-Type: application/json' \ -d '{"agentId":"<agent-id>"}' ``` *** ## `DELETE /api/teams/:id/agents/:agentId` Removes an agent from a team by setting its `teamId` to `null`; the `:id` (team) segment is not used in the predicate. The agent row is not deleted. * **Path params**: `id` (team id, unused in the query), `agentId` (the agent to orphan). * **Request body**: none. ### Responses **`400 Bad Request`**: the `:agentId` segment is missing: ```json theme={"theme":"github-dark"} { "error": "agentId required" } ``` **`200 OK`**: the agent's `teamId` was cleared: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`500 Internal Server Error`**: the update failed: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X DELETE http://localhost:18790/api/teams/<team-id>/agents/<agent-id> ``` *** ## `GET /api/teams/:id/rooms` Lists the team's chat rooms in creation order, oldest first. Archived rooms (the ones deleted in the dashboard) are not listed, and neither is General: the team's main chat is the default and has no row. An unknown team id is not a 404; it returns an empty list. The sidebar applies its own order (pinned rooms first, most recently pinned on top, then newest first). * **Path params**: `id` (team id). * **Request body**: none. ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`200 OK`**: the team's active rooms: ```ts theme={"theme":"github-dark"} { rooms: Array<{ id: string teamId: string name: string memberIds: string[] // stored members, oldest membership first; the teamless Boo Zero is never one pinnedAt: number | null // when it was pinned to the top of the room list; null = not pinned chatClearedAt: number | null // when its chat was last cleared; null = never createdAt: number updatedAt: number }> } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` > [!NOTE] > > `memberIds` is the stored membership. The room's live roster, the agents its chat lists and routes to, is that list minus any agent that has since been archived or moved to another team (an agent row that is deleted leaves the list by itself), plus Boo Zero, which presides over every room without being stored as a member. ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/teams/<team-id>/rooms ``` *** ## `POST /api/teams/:id/rooms` Creates a chat room from a name and a subset of the team's members, and returns it with **201**. The name has its whitespace collapsed, is trimmed, and is cut to 80 characters. Duplicate member ids count once. Every member must be an active (not archived) agent of this team. Boo Zero needs no membership, because it presides over every room; the default Boo Zero has no team, so its id is refused as a member like any other agent outside the team. A new room starts unpinned and never cleared. * **Path params**: `id` (team id). * **Request body**: ```ts theme={"theme":"github-dark"} { name: string // required; whitespace collapsed, trimmed, cut to 80 characters memberIds: string[] // required; one or more active agents of this team } ``` ### Responses The checks run in this order: **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`400 Bad Request`**: `name` is missing, not a string, or empty once its whitespace is collapsed: ```json theme={"theme":"github-dark"} { "error": "name required" } ``` **`404 Not Found`**: no team with that id: ```json theme={"theme":"github-dark"} { "error": "team not found" } ``` **`400 Bad Request`**: `memberIds` is missing, not an array, or holds anything other than non-empty strings: ```json theme={"theme":"github-dark"} { "error": "memberIds must be an array of agent ids" } ``` **`400 Bad Request`**: `memberIds` is an empty array: ```json theme={"theme":"github-dark"} { "error": "a room needs at least one member" } ``` **`400 Bad Request`**: one or more ids are not active agents of this team (the message lists them, comma-separated): ```json theme={"theme":"github-dark"} { "error": "not active members of this team: <agent-id>, <agent-id>" } ``` **`201 Created`**: the new room: ```ts theme={"theme":"github-dark"} { room: { id: string teamId: string name: string memberIds: string[] // in the order sent, duplicates dropped pinnedAt: null chatClearedAt: null createdAt: number updatedAt: number } } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/teams/<team-id>/rooms \ -H 'Content-Type: application/json' \ -d '{"name":"Launch prep","memberIds":["<agent-id>","<agent-id>"]}' ``` *** ## `PATCH /api/teams/:id/rooms/:roomId` Changes a room: rename it, replace or add to its members, or pin or unpin it, in any combination. Every field you send is validated before anything is written. `memberIds` replaces the whole member set; the members you keep keep their place in the roster order. `addMemberIds` adds agents and leaves the current members in place, so two adds sent at the same time both land; the dashboard uses it when an agent made on a room's graph joins that room. Send one or the other, not both. A member you remove finishes the turn it is already running, and the room's next turns no longer list or route to it (the roster is read fresh on every turn). Pinning stamps `pinnedAt` with the current time, even on a room that is already pinned, which moves it to the top of the pinned rooms; unpinning clears it. * **Path params**: `id` (team id), `roomId` (the room). * **Request body** (at least one field): ```ts theme={"theme":"github-dark"} { name?: string // whitespace collapsed, trimmed, cut to 80 characters memberIds?: string[] // replaces the member set; same rules as on create addMemberIds?: string[] // adds to the member set; same checks; not with memberIds pinned?: boolean // true pins (pinnedAt = now), false unpins (pinnedAt = null) } ``` ### Responses The checks run in this order: **`400 Bad Request`**: a path segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id and room id required" } ``` **`400 Bad Request`**: `pinned` is present but not a boolean: ```json theme={"theme":"github-dark"} { "error": "pinned must be true or false" } ``` **`400 Bad Request`**: both `memberIds` and `addMemberIds` are present: ```json theme={"theme":"github-dark"} { "error": "send memberIds or addMemberIds, not both" } ``` **`400 Bad Request`**: the body has none of `name`, `memberIds`, `addMemberIds`, or `pinned`: ```json theme={"theme":"github-dark"} { "error": "nothing to change" } ``` **`404 Not Found`**: no active room with that id in this team (an archived room, or another team's room, is not found): ```json theme={"theme":"github-dark"} { "error": "room not found" } ``` **`400 Bad Request`**: `name` is present but not a string, or empty once its whitespace is collapsed: ```json theme={"theme":"github-dark"} { "error": "name must not be empty" } ``` **`400 Bad Request`**: `memberIds` is present but invalid, with the same three errors as `POST /api/teams/:id/rooms`: `memberIds must be an array of agent ids`, `a room needs at least one member`, or `not active members of this team: <ids>`. **`400 Bad Request`**: `addMemberIds` is present but invalid: `addMemberIds must be an array of agent ids`, `addMemberIds must name at least one agent`, or `not active members of this team: <ids>`. **`200 OK`**: the updated room: ```ts theme={"theme":"github-dark"} { room: { id: string teamId: string name: string memberIds: string[] pinnedAt: number | null chatClearedAt: number | null createdAt: number updatedAt: number } } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PATCH http://localhost:18790/api/teams/<team-id>/rooms/<room-id> \ -H 'Content-Type: application/json' \ -d '{"name":"Launch week","pinned":true}' ``` *** ## `DELETE /api/teams/:id/rooms/:roomId` Archives the room; nothing is deleted. In one call it stops the room's work (a clean Stop, as `POST /api/teams/:id/chat/stop` does for this `roomId`: claimed tasks are released to `todo` and nothing is reported as a failure), drops the room's resume pointers (`native-team-session:<agentId>:<teamId>:room:<roomId>`) so no agent resumes the archived conversation, and sets the room's `archived_at`. The conversation stays in `chat_messages` under the room's session keys, the room leaves `GET /api/teams/:id/rooms`, and its tasks stay on the board, where a retry or a reassignment runs them from General. No route removes a room row; deleting the team does (the `rooms` table cascades). * **Path params**: `id` (team id), `roomId` (the room). * **Request body**: none. ### Responses **`400 Bad Request`**: a path segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id and room id required" } ``` **`404 Not Found`**: no active room with that id in this team (including one already archived): ```json theme={"theme":"github-dark"} { "error": "room not found" } ``` **`200 OK`**: the room was archived: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X DELETE http://localhost:18790/api/teams/<team-id>/rooms/<room-id> ``` *** ## `GET /api/teams/:id/onboarding` Reads the team's **Know Your Team** intro state. Both `agentsIntroduced` and `userIntroduced` must be `true` before the team chat's normal message box opens. `userIntroText` is your self-introduction, which is added to the team context of every team chat message. With nothing stored, the defaults (`false`, `false`, `''`) come back. A team that already has messages in General reads as introduced: the route answers both flags `true` without changing what is stored, so a team you have used is not gated again. Messages in rooms, and messages set aside by a clear, do not count. * **Path params**: `id` (team id). * **Request body**: none. ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`200 OK`**: the onboarding state (defaults when unset): ```ts theme={"theme":"github-dark"} { agentsIntroduced: boolean // default false userIntroduced: boolean // default false userIntroText: string // default '' } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/teams/<team-id>/onboarding ``` *** ## `PATCH /api/teams/:id/onboarding` Merge-updates the onboarding state and returns the full updated state. Only fields with the correct type are applied; a non-boolean `agentsIntroduced`/`userIntroduced` or non-string `userIntroText` is ignored and the current value is kept. `userIntroText` is truncated to 4000 characters. * **Path params**: `id` (team id). * **Request body** (all optional, merged with current state): ```ts theme={"theme":"github-dark"} { agentsIntroduced?: boolean userIntroduced?: boolean userIntroText?: string // sliced to 4000 chars server-side } ``` ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`400 Bad Request`**: the body is missing or not an object: ```json theme={"theme":"github-dark"} { "error": "invalid JSON" } ``` **`200 OK`**: the merged, updated state: ```ts theme={"theme":"github-dark"} { agentsIntroduced: boolean userIntroduced: boolean userIntroText: string } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PATCH http://localhost:18790/api/teams/<team-id>/onboarding \ -H 'Content-Type: application/json' \ -d '{"agentsIntroduced":true,"userIntroduced":true,"userIntroText":"I run a small SaaS."}' ``` *** ## `GET /api/teams/:id/activity-summary` Builds a compact, on-demand snapshot of what a team has been doing, for injection into Boo Zero's **personal** chat when the user `@`-mentions that team. Composed from durable server state in order (brief, board, saved memory, recent chat), so it works regardless of what the browser has loaded. The sections are sized against a 2500-character budget: the Boo-Zero brief is clipped to 900, the board summary and saved memory are kept whole, and the recent-chat section (up to 30 turns, oldest dropped first) gets whatever budget remains. The recent chat is drawn from General and every active room, since the summary is of the team's work wherever it was discussed. * **Path params**: `id` (team id). * **Request body**: none. ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`200 OK`**: the summary block, or `null` when the team has nothing to report: ```ts theme={"theme":"github-dark"} { content: string | null // null = no brief, no board tasks, no saved memory, no meaningful chat } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` > [!NOTE] > > An unknown team id is not a 404: it has no brief, board, memory, or chat, so it returns `{ "content": null }`. Saved memory is best-effort (a memory-store failure is swallowed and the section omitted) rather than a 500. ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/teams/<team-id>/activity-summary ``` *** ## `POST /api/teams/:id/chat` Sends your message to one chat, General by default or the room named by `roomId`, and answers **202** at once. The server runs the reply and any delegated work in the background, and closing the connection does not stop it. Each chat runs on its own, so work started in a room runs beside the other chats, and the tasks it delegates are filed with that room's id. Target resolution happens within that chat's roster (General: the team's active members plus Boo Zero; a room: its live members plus Boo Zero), by priority: an explicit `targetAgentId` that is in the roster, then a **leading** `@`-mention (the message must *start* with `@<agent name>`, and the name must be followed by whitespace or the end of the message; a mention anywhere else in the text is ignored), then the chat's lead (Boo Zero, else the team's own lead if it is in the roster, else the first member). The user message is persisted under the target's session key for that chat (`agent:<id>:team:<teamId>` in General, `agent:<id>:team:<teamId>:room:<roomId>` in a room) before that agent's turn runs. A team whose `team-server-orchestrated:<teamId>` setting is `'false'` gets a **404**. Nothing in Clawboo writes that value, so in practice every team passes. * **Path params**: `id` (team id). * **Request body**: ```ts theme={"theme":"github-dark"} { message: string // required; trimmed, must be non-empty targetAgentId?: string // honored only when it matches a member of the chat's roster entryId?: string // client-minted id for the persisted user entry (SSE dedup) roomId?: string // the chat room to post into; absent or empty = General } ``` ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`400 Bad Request`**: `message` is absent, not a string, or empty after trimming: ```json theme={"theme":"github-dark"} { "error": "message required" } ``` **`404 Not Found`**: the team opted out of server orchestration: ```json theme={"theme":"github-dark"} { "error": "team is not server-orchestrated" } ``` **`404 Not Found`**: `roomId` names no active room of this team (an unknown or archived room, or another team's): ```json theme={"theme":"github-dark"} { "error": "room not found" } ``` **`202 Accepted`**: the message was enqueued (the run has NOT finished): ```json theme={"theme":"github-dark"} { "ok": true } ``` **`500 Internal Server Error`**: an unexpected throw before the enqueue: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` > [!NOTE] > > The 202 means "accepted", not "succeeded". Watch the reply on `GET /api/teams/:id/chat/stream`. A failed delivery is not returned here; it is saved in the transcript as a `role: 'system'`, `kind: 'meta'` entry. When the cause is a dropped OpenClaw Gateway connection, the server first reconnects and retries the turn once, and saves the entry only if that also fails. > > A team with no active members, or an unknown team id, also gets the 202, but nothing is stored and no turn runs. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/teams/<team-id>/chat \ -H 'Content-Type: application/json' \ -d '{"message":"Plan the launch checklist."}' # Post into a room instead of General curl -X POST http://localhost:18790/api/teams/<team-id>/chat \ -H 'Content-Type: application/json' \ -d '{"message":"@Writer Draft the announcement.","roomId":"<room-id>"}' ``` *** ## `POST /api/teams/:id/chat/stop` Stops one chat, General by default or the room named by `roomId`, the way the **Stop** button does. Other chats keep going. A Stop: * takes effect at once, before any running turn has finished; * stops every turn running in that chat; * releases each task those turns had claimed back to `todo`, as a clean stop: no `blocked` status, no cancelled dependents, and no failure reported to the agent that delegated it; * drops messages waiting to be delivered, and a delivery whose run had not started never starts; * records a `cancelled` run for every tracked turn and every delegation that was ready but not started, so the board does not start the stopped work again on its own. The same `team-server-orchestrated` check as the send route applies. * **Path params**: `id` (team id). * **Request body** (optional): ```ts theme={"theme":"github-dark"} { roomId?: string } // the chat to stop; absent or empty = General ``` ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`404 Not Found`**: the team opted out of server orchestration: ```json theme={"theme":"github-dark"} { "error": "team is not server-orchestrated" } ``` **`200 OK`**: the stop was applied, or there was nothing to stop: ```json theme={"theme":"github-dark"} { "ok": true } ``` **`500 Internal Server Error`**: an unexpected throw: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` > [!NOTE] > > Stopping a chat with nothing running (no work has run in it since the server started, or it has been idle for 30 minutes) changes nothing and still returns `{ "ok": true }`. That includes a `roomId` that names no room at all: unlike the ingest, Stop never answers `room not found`. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/teams/<team-id>/chat/stop # Stop one room; General and the other rooms keep going curl -X POST http://localhost:18790/api/teams/<team-id>/chat/stop \ -H 'Content-Type: application/json' \ -d '{"roomId":"<room-id>"}' ``` *** ## `POST /api/teams/:id/chat/clear` Clears one chat: General by default, or the room named by `roomId`. Nothing is deleted. Each of the chat's `chat_messages` rows is set aside by appending `#cleared:<ts>` to its `session_key` and `entry_id`, so no reader matches it again. The route also drops the resume pointers Clawboo keeps for the chat (the `native-team-session:<agentId>:<teamId>` keys, with `:room:<roomId>` in a room), so native and Codex agents start the conversation fresh on their next turn. It does **not** reset an OpenClaw teammate's session: that conversation lives in the Gateway, not in Clawboo. The clear time is stored in `settings` (`team-chat-cleared:<teamId>` for General, `team-chat-cleared:<teamId>:room:<roomId>` for a room); that is what `chatClearedAt` reports on `GET /api/teams` and `GET /api/teams/:id/rooms`. Other chats are not touched. This route has no server-orchestration gate. The rows set aside are the ones under the chat's session keys at the moment of the clear: each team member's key in General, each live member's room key in a room, plus Boo Zero's key in either. > [!NOTE] > > The UI's **Clear chat** does two things this route does not. First, after the call it sends `/reset` to each of the chat's OpenClaw teammates over the Gateway, exactly as typing `/reset` in the chat does, so in the product every agent starts the conversation fresh. It sends the Gateway's `chat.send` with `message: '/reset'` on each teammate's chat session key (`agent:<id>:team:<teamId>`, with `:room:<roomId>` in a room), only while a Gateway connection is up, skipping any teammate it cannot reach. A direct REST caller gets only the pointer drop. Second, only the chat's timeline stops showing the task cards made before the clear (a task whose `createdAt` is earlier than `chatClearedAt`); the board and the chat's **Tasks** card keep them. * **Path params**: `id` (team id). * **Request body** (optional): ```ts theme={"theme":"github-dark"} { roomId?: string } // the room to clear; absent or empty = General ``` ### Responses **`400 Bad Request`**: the `:id` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`404 Not Found`**: no team with that id: ```json theme={"theme":"github-dark"} { "error": "team not found" } ``` **`404 Not Found`**: `roomId` names no active room of this team: ```json theme={"theme":"github-dark"} { "error": "room not found" } ``` **`200 OK`**: the chat was cleared: ```ts theme={"theme":"github-dark"} { ok: true clearedAt: number // epoch ms; also the <ts> in the #cleared:<ts> suffix cleared: number // how many chat_messages rows were set aside } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} # Clear General curl -X POST http://localhost:18790/api/teams/<team-id>/chat/clear # Clear one room curl -X POST http://localhost:18790/api/teams/<team-id>/chat/clear \ -H 'Content-Type: application/json' \ -d '{"roomId":"<room-id>"}' ``` *** ## `GET /api/teams/:id/chat/stream` Server-Sent Events live-tail of one chat's transcript: General by default, or the room named by `roomId`. It runs in two tiers. **Tier 1** polls the durable `chat_messages` rows for that chat's session keys every 750 ms on the monotonic `id` cursor (up to 500 rows per batch); each row's stored `data` is already a serialized `TranscriptEntry` and goes straight to the wire. **Tier 2** forwards ephemeral in-memory signals (assistant token deltas, board-projection changes, agent status) as *named* frames with no `id:` line, so they never advance the resume cursor. The stream only reads: it never starts work, and the team's work runs to the end with no client connected. It has no `team-server-orchestrated` check, since reading is safe for any team. The session-key set is resolved **once at connect**: in General, `agent:<id>:team:<teamId>` for each team member plus Boo Zero's team-scoped key; in a room, `agent:<id>:team:<teamId>:room:<roomId>` for each live member plus Boo Zero's. A member added mid-stream is picked up on the next reconnect. * **Path params**: `id` (team id). * **Query params**: | Param | Type | Notes | | - | - | - | | `roomId` | string | The chat room to tail; absent or empty = General. A value that is not one of the team's active rooms gets a `404` instead of a stream. | | `since` | number | Resume cursor over the `chat_messages` id; `Last-Event-ID` wins over it. With neither, the stream starts at the newest row and sends only what comes next. A negative or non-numeric value means `0`. | * **Request body**: none. > [!NOTE] > > This is an SSE route, not request/response. Once the stream opens there is no JSON response body; the catalog below describes the wire frames. The one exception comes before it opens: a `roomId` that names no active room of this team (an unknown or archived room, or another team's) gets `404` with `{ "error": "room not found" }`. An unknown team id with no `roomId` is not a 404: no session keys match, so the stream opens and stays open emitting only keepalives. Resume replays **committed rows only**: deltas, board changes, and status frames carry no id and are never replayed. ### Connection On open (after the `roomId` check), the route answers `HTTP/1.1 200` with: ```http theme={"theme":"github-dark"} Content-Type: text/event-stream; charset=utf-8 Cache-Control: no-cache, no-transform Connection: keep-alive X-Accel-Buffering: no ``` then emits a `: connected` comment frame, flushes any rows already past the cursor, and replays the team's last-known agent status per agent. ### Event catalog | Frame | When | Shape | | - | - | - | | `: connected` | Immediately on open | SSE comment (ignored by `EventSource`) | | *(unnamed)* | Per committed transcript row (poll every 750 ms) | `id: <row id>` line + `data: <TranscriptEntry json>` line | | `event: delta` | Per live assistant-text delta in this chat | `{ sessionKey, runId, text }`; `text` is the FULL running text so far (replace, not append) | | `event: board` | Per board change on this team, from any source (team chat, the task runner, a Tasks MCP tool, the stale-task sweep, a REST call) | `{ id, title?, status?, assigneeAgentId?, parentTaskId?, createdAt?, updatedAt?, summary?, attention?, roomId? }` | | `event: status` | Per run boundary, plus a snapshot on connect | `{ agentId, status: 'running' \| 'idle' \| 'error', roomId? }` | | `: keepalive` | Every 20 s while open | SSE comment (keeps the connection warm) | Only the unnamed frames carry an `id:`, so only they move the resume cursor. A client reconciles any board change it missed across a reconnect with a `GET /api/board` reload. Deltas belong to this chat alone: a stream never forwards another chat's deltas, so General's stream skips every room's, and a room's stream skips General's and the other rooms'. Board and status frames are team-wide, so every stream of the team receives them, each tagged with the chat it came from so a client can keep each chat's task cards and busy state to its own. A board frame's `roomId` is the task's room (`null` = General; left out if the task could not be read), and it carries `attention` (why the task now needs a person, or `null`) only when the change moved the task's status. A run-boundary status frame's `roomId` is the room the run belongs to (`null` = General); the snapshot replayed on connect carries none. Example frames: ```text theme={"theme":"github-dark"} : connected id: 8121 data: {"entryId":"...","role":"user","kind":"user","text":"Plan the launch checklist.","sessionKey":"agent:<id>:team:<team-id>","runId":null,"source":"local-send","timestampMs":1718450000000,"sequenceKey":1,"confirmed":true,"fingerprint":"..."} event: status data: {"agentId":"<agent-id>","status":"running"} event: delta data: {"sessionKey":"agent:<id>:team:<team-id>","runId":"<run-id>","text":"Here is the checklist so far"} : keepalive ``` ### Example ```bash theme={"theme":"github-dark"} curl -N http://localhost:18790/api/teams/<team-id>/chat/stream # Resume from a known transcript row id curl -N 'http://localhost:18790/api/teams/<team-id>/chat/stream?since=8121' # Tail one room instead of General curl -N 'http://localhost:18790/api/teams/<team-id>/chat/stream?roomId=<room-id>' ``` *** ## `GET /api/team-rules/:teamId` Reads the team's rules. You set them in the team's settings (the gear icon in the team header) or with `/rule <text>` in the team chat's message box; both write here. The text is added to every team agent's message context, so your corrections last across sessions. With nothing stored, `{ "content": "" }` comes back. > [!NOTE] > > This route uses the param name `:teamId` (not `:id` like the routes above). The value is still the team id. * **Path params**: `teamId` (team id). * **Request body**: none. ### Responses **`400 Bad Request`**: the `:teamId` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`200 OK`**: the rules text (empty when unset): ```ts theme={"theme":"github-dark"} { content: string } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/team-rules/<team-id> ``` *** ## `PUT /api/team-rules/:teamId` Replaces the team-rules text. `content` is required and must be a string; it is rejected (not truncated) when it exceeds 4000 characters. Returns the stored value. * **Path params**: `teamId` (team id). * **Request body**: ```ts theme={"theme":"github-dark"} { content?: string } // required string; max 4000 chars ``` ### Responses **`400 Bad Request`**: the `:teamId` segment is missing: ```json theme={"theme":"github-dark"} { "error": "team id required" } ``` **`400 Bad Request`**: the body is missing or not an object: ```json theme={"theme":"github-dark"} { "error": "invalid JSON" } ``` **`400 Bad Request`**: `content` is absent or not a string: ```json theme={"theme":"github-dark"} { "error": "content (string) required" } ``` **`400 Bad Request`**: `content` exceeds the 4000-character cap: ```json theme={"theme":"github-dark"} { "error": "content exceeds 4000 characters" } ``` **`200 OK`**: the stored rules: ```ts theme={"theme":"github-dark"} { content: string } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X PUT http://localhost:18790/api/team-rules/<team-id> \ -H 'Content-Type: application/json' \ -d '{"content":"Reply in British English. Ask before you delete any file."}' ``` *** ## `GET /api/team-chat` Reads the team's peer-chat room from a cursor: the `team_chat` table, where each agent posts as a named peer through the [TeamChat MCP server](/reference/rest-api/tools-and-mcp) at `/api/mcp/teamchat`. The dashboard does not read this route. Pass `teamId` (the room resolves to `team:<teamId>`) or a `roomId` directly; one of the two is required. `sinceSeq` is the cursor (posts with `seq > sinceSeq`). * **Path params**: none. * **Query params**: | Param | Type | Default | Notes | | - | - | - | - | | `teamId` | string | n/a | Resolves the room id to `team:<teamId>` | | `roomId` | string | n/a | An explicit room id (overrides `teamId`) | | `sinceSeq` | string (number) | `0` | Return posts with `seq > sinceSeq`; non-finite falls back to `0` | | `limit` | string (number) | `1000` | Cap the batch; ignored unless a positive number | * **Request body**: none. ### Responses **`400 Bad Request`**: neither `teamId` nor `roomId` was supplied: ```json theme={"theme":"github-dark"} { "error": "teamId or roomId is required" } ``` **`200 OK`**: the room posts in ascending `seq` order plus the next cursor: ```ts theme={"theme":"github-dark"} { roomId: string posts: Array<{ id: string roomId: string teamId: string authorAgentId: string body: string kind: 'peer' | 'system' | 'user' // 'peer' teammate post · 'system' board-mutation narration · 'user' createdAt: number seq: number // per-room monotonic }> nextSeq: number // the last post's seq, or the requested sinceSeq when empty } ``` **`500 Internal Server Error`**: a DB failure: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl 'http://localhost:18790/api/team-chat?teamId=<team-id>&sinceSeq=0' ``` *** ## `POST /api/team-chat/exchange` Starts one bounded peer-chat exchange for a team in the peer-chat room. The first speaker (the team's lead, or the agents in `firstSpeakers`) takes a turn, then each active member a turn delegates or reports to, until nobody is owed a turn or the turn limit is reached. Each speaker choice (`speaker_selected`) and the stop reason (`turn_bound_hit`) are recorded in the event log. Nothing starts an exchange by itself; only this route does, and the dashboard does not call it. The agents' MCP clients attach to this server's own `/api/mcp/*` routes on its loopback port, never to the request's `Host`. The exchange stops between turns if the request's connection closes, and a second exchange for the same room is refused while one runs. A paused hard-cap budget on the team or a first speaker refuses the exchange with `422`; one that pauses later, or covers a later speaker, refuses that turn, adds a notice to the team chat and ends the exchange with `endedReason: 'budget_paused'`. * **Path/query params**: none. * **Request body**: ```ts theme={"theme":"github-dark"} { teamId: string // required stimulus?: string // the initiating message for the first speaker's turn firstSpeakers?: string[] // active members seeded to speak first; defaults to the team's lead // (leaderAgentId); with neither, no turn runs maxExchangeTurns?: number // total turn limit; default 5; at least 1, at most 5 x active members } ``` ### Responses **`400 Bad Request`**: missing `teamId`: ```json theme={"theme":"github-dark"} { "error": "teamId is required" } ``` **`409 Conflict`**: an exchange is already running for this room (the per-room lock refuses an overlapping kickoff): ```json theme={"theme":"github-dark"} { "ok": false, "error": "an exchange is already running for this room" } ``` **`200 OK`**: the exchange ran to a bound and returned its result: ```ts theme={"theme":"github-dark"} { ok: true roomId: string result: { turnsTaken: number endedReason: 'max_turns' | 'no_pending_obligation' | 'budget_paused' | 'aborted' speakers: string[] // ordered agent ids who spoke this exchange } } ``` **`404 Not Found`**: the team does not exist (the exchange returned `error: 'team not found'`): ```json theme={"theme":"github-dark"} { "ok": false, "error": "team not found" } ``` **`422 Unprocessable Entity`**: the exchange was refused for any other reason (e.g. `team has no agents`, `unknown first speaker: <id>`, `budget_paused:<scope>`): ```json theme={"theme":"github-dark"} { "ok": false, "error": "<reason>" } ``` **`500 Internal Server Error`**: an unexpected throw: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` > [!NOTE] > > The board stays canonical: a peer-chat post never changes the board. Decisions land as board changes (see [the board](/concepts/the-board)). Claims, status moves, comments and dependencies made through the [Board API](/reference/rest-api/board) are narrated into the room as `kind: 'system'` lines with the author `clawboo`. ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/team-chat/exchange \ -H 'Content-Type: application/json' \ -d '{"teamId":"<team-id>","stimulus":"Plan the launch checklist."}' ``` *** ## Error envelope Errors use the standard `{ "error": string }` body. On `/api/team-chat/exchange`, the **409** for a running exchange and the **404** and **422** refusals use `{ "ok": false, "error": string }` (success is `{ ok: true, roomId, result }`). The stream route answers a `roomId` that names no active room with a **404** and the standard body before any stream opens; after that it has no error body: it opens a `200` event stream and keeps the connection alive through brief read errors. A rate-limited request answers **429** `{ "error": "Too many requests. Wait a moment and retry." }` with a `Retry-After` header. **Source of truth:** `apps/web/server/api/teams.ts`, `rooms.ts`, `teamChatStream.ts`, `teamChat.ts`, `teamRules.ts` and `teamOnboarding.ts`. ## Related * [Teams](/using/teams): create and manage teams, leaders and team rules in the dashboard. * [Team chat](/using/team-chat): General, rooms, Clear chat and the Know Your Team intro. * [Peer chat](/concepts/peer-chat): how agents post to each other in the peer-chat room. * [Agents API](/reference/rest-api/agents): the agents these membership routes refer to, and deleting them. * [Tools and MCP API](/reference/rest-api/tools-and-mcp): the TeamChat MCP server that writes the peer-chat room. * [Database schema](/reference/database-schema): the `teams`, `agents`, `rooms`, `room_members`, `chat_messages`, `settings` and `team_chat` tables. # Tools and MCP API Source: https://docs.claw.boo/reference/rest-api/tools-and-mcp REST reference for the tools broker (tools, approvals, audit) and the MCP transport: attach snippets, the attach token, session binding and /api/mcp. Use these routes for two related things. The tools broker routes (`/api/tools*`) list Clawboo's built-in tools, show and answer the tool-approval queue, and read the tool-call audit log. The MCP routes (`/api/mcp/*`) carry Clawboo's four MCP servers (`tasks`, `memory`, `tools`, `teamchat`) over Streamable HTTP, which the runtimes Clawboo starts attach to. `GET /api/mcp/config` builds an attach snippet for an agent you connect yourself. > [!NOTE] > > The MCP routes speak the Model Context Protocol (JSON-RPC over Streamable HTTP), not a Clawboo request and response format, so they are documented by their transport rules and status codes. The tools each server offers, and their inputs, are in the [MCP tools reference](/reference/mcp-tools). To attach an outside agent step by step, see [MCP servers](/operating/mcp-servers). Every route here is on the general [rate limit](/reference/rest-api/index#rate-limits) (3,000 requests a minute per client address), including the approval resolve and the MCP transport; none is on the sensitive tier. Request bodies are JSON (up to 2 MB). Argument and result summaries are scrubbed when they are written; the approval queue and the audit log mask them again before they leave the server, and the `/api/tools*` routes mask their error messages. The `curl` examples assume the access gate is off. With `STUDIO_ACCESS_TOKEN` set, the `/api/tools*` routes, `GET /api/mcp/config` and `POST /api/mcp/attach-secret/rotate` need the access cookie (`-b 'clawboo_access=<token>'`, see [Authentication](/reference/rest-api/index#authentication)), and the MCP transport takes the `attachToken` described below. ## Routes | Method | Path | Summary | Stream? | | - | - | - | - | | GET | `/api/tools` | List the built-in tools and whether each is available | No | | GET | `/api/tools/approvals` | The pending tool-approval queue | No | | POST | `/api/tools/approvals/:id/resolve` | Answer a pending approval (allow once, always, or deny) | No | | GET | `/api/tools/audit` | The tool-call audit log (before and after each call) | No | | GET | `/api/mcp/config` | Build an attach snippet for one runtime and one server | No | | POST | `/api/mcp/attach-secret/rotate` | Replace the secret behind every attach URL | No | | POST | `/api/mcp/tasks` | MCP JSON-RPC requests (Tasks server) | Streamable HTTP | | GET | `/api/mcp/tasks` | MCP event stream for a Tasks session | SSE | | DELETE | `/api/mcp/tasks` | End a Tasks session | No | | POST | `/api/mcp/memory` | MCP JSON-RPC requests (Memory server) | Streamable HTTP | | GET | `/api/mcp/memory` | MCP event stream for a Memory session | SSE | | DELETE | `/api/mcp/memory` | End a Memory session | No | | POST | `/api/mcp/tools` | MCP JSON-RPC requests (Tools server) | Streamable HTTP | | GET | `/api/mcp/tools` | MCP event stream for a Tools session | SSE | | DELETE | `/api/mcp/tools` | End a Tools session | No | | POST | `/api/mcp/teamchat` | MCP JSON-RPC requests (TeamChat server) | Streamable HTTP | | GET | `/api/mcp/teamchat` | MCP event stream for a TeamChat session | SSE | | DELETE | `/api/mcp/teamchat` | End a TeamChat session | No | *** ## `GET /api/tools` Lists every built-in tool with an availability verdict worked out from the server's environment. A tool that needs something not set up reads `available: false`, with `diagnostics` saying why; the dashboard greys it out, and the Tools MCP server leaves it out of `tools/list`. The four built-in tools are `echo` and `note` (risk `safe`), `web_search` (`external`, hidden until `TAVILY_API_KEY` is set or a Tavily sign-in exists) and `delete_path` (`destructive`). * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the tools and their availability: ```ts theme={"theme":"github-dark"} { ok: true tools: Array<{ name: string description: string owner: 'core' | 'plugin' | 'channel' | 'mcp' // 'core' when not set risk: 'safe' | 'destructive' | 'external' // 'safe' when not set available: boolean diagnostics: string[] // why it is hidden, when it is }> } ``` **`500 Internal Server Error`**: the tool list could not be built: ```json theme={"theme":"github-dark"} { "error": "<masked message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/tools ``` *** ## `GET /api/tools/approvals` The pending tool-approval queue: rows whose `status` is `pending` and whose `expiresAt` has not passed, newest first. Each row's `argsSummary` (scrubbed when it was written) is masked again here. The dashboard's approval cards read this queue, on the board's **Needs you** column and in team chat. A broker waiting on an approval, in this server or in an MCP stdio process, checks the same rows for the answer. The queue holds two kinds of row, told apart by `kind`: * **`tool`**: Clawboo itself holds the call. Its tool broker is waiting for the answer, a native agent's `run_command` is waiting on the row it wrote, or a delegation is waiting for its approval. * **`exec`**: a shell command an OpenClaw agent asked to run. The OpenClaw Gateway holds the command; Clawboo copies the request into a row so the card survives a closed tab, a refresh, or a tab opened partway through. Such a row has `toolName: 'exec'`, `toolClass: 'destructive'`, and the command's first 200 characters as `toolSummary`. * **Path/query params**: none. * **Request body**: none. ### Responses **`200 OK`**: the pending approvals, each the whole stored row: ```ts theme={"theme":"github-dark"} { ok: true approvals: Array<{ id: string kind: 'tool' | 'exec' // who holds the call: Clawboo, or the OpenClaw Gateway toolName: string agentId: string | null argsSummary: string | null // scrubbed JSON of the arguments, masked again here reason: string | null status: 'pending' // this route returns pending rows only taskId: string | null // the board task this approval holds up, when known tenantId: string | null // unused grantId: string | null // the connector grant the call runs under connectorId: string | null neverRemember: 0 | 1 // 1: an "always" answer is not remembered for this call ruleReason: string | null // the reason code behind the prompt toolClass: string | null // Clawboo's reading of the tool: 'read' | 'write' | 'destructive' toolSummary: string | null // a one-line description for the card createdAt: number expiresAt: number resolvedAt: number | null }> } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<masked message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl http://localhost:18790/api/tools/approvals ``` *** ## `POST /api/tools/approvals/:id/resolve` Answers an approval. An unknown `decision` is a **400**. The route then loads the row and acts on its `kind`, because the two kinds are held by different things and released in different ways. The **404** means only that no row with that id exists; an approval that was already answered is not a 404. **A `kind: 'tool'` row** is answered here. The answer is written only while the row is still `pending`; any other row (already answered, or marked `expired`) comes back unchanged with **200**, so check `status` in the response. A waiting broker sees the answer the next time it checks. For a `tool` row, `allow_always` also creates a standing allow rule tied to the row's grant: later calls of the same tool under that grant run without asking, whatever their arguments, for 30 days. No rule is made when the row has no `grantId`, when it has `neverRemember: 1`, or when this call did not change the row (it was already answered). **A `kind: 'exec'` row** is held by the OpenClaw Gateway, and answering Clawboo's copy alone would not release the command. So the answer goes to the Gateway as `exec.approval.resolve`, in OpenClaw's own words (`allow_once` becomes `allow-once`, `allow_always` becomes `allow-always`, `deny` stays `deny`), and Clawboo marks its copy only after the Gateway takes it. This branch answers a different **200** body and is the only one that can answer **502**. > [!NOTE] > > Clawboo makes no rule of its own for an `exec` row. An `allow_always` is passed on as `allow-always`, and any standing permission it creates lives in OpenClaw's own allowlist, which you can read and revoke through `/api/exec-allowlist` (see the [Agent settings API](/reference/rest-api/agent-settings)). * **Path params**: `id` (approval id). * **Request body**: ```ts theme={"theme":"github-dark"} { decision: 'allow_once' | 'allow_always' | 'deny' // required } ``` ### Responses **`400 Bad Request`**: `decision` is missing or not one of the three values. `details` is the validation error: ```json theme={"theme":"github-dark"} { "error": "invalid body", "details": { "formErrors": [], "fieldErrors": { "decision": ["..."] } } } ``` **`404 Not Found`**: no approval with that id exists: ```json theme={"theme":"github-dark"} { "error": "approval not found" } ``` **`200 OK`** (`kind: 'tool'`): the whole row after the answer, with the same fields as the queue. `argsSummary` is the stored, scrubbed value; this route does not mask it again: ```ts theme={"theme":"github-dark"} { ok: true approval: { id: string kind: 'tool' status: 'allow_once' | 'allow_always' | 'deny' | 'expired' resolvedAt: number | null // when the row was answered, which may be earlier than this call // ...and every other field shown under GET /api/tools/approvals } } ``` **`200 OK`** (`kind: 'exec'`): the Gateway took the answer. There is **no `approval` object** here: ```ts theme={"theme":"github-dark"} { ok: true kind: 'exec' alreadyResolved: boolean } ``` `alreadyResolved: true` means the Gateway reports the command as already answered. That is a normal race, not a failure: the same card can be open in several browser tabs, each with its own Gateway connection. Clawboo then leaves its copy as it is and settles it from the Gateway's `exec.approval.resolved` broadcast, which is also how a card answered elsewhere clears from a tab. **`502 Bad Gateway`** (`kind: 'exec'` only): the Gateway did not take the answer, so the command is still waiting and the card stays open: ```json theme={"theme":"github-dark"} { "error": "the Gateway did not accept that decision" } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<masked message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -X POST http://localhost:18790/api/tools/approvals/<approval-id>/resolve \ -H 'Content-Type: application/json' \ -d '{"decision":"allow_once"}' ``` *** ## `GET /api/tools/audit` The tool-call audit log: one row before and one after each brokered call, newest first. `argsSummary` and `resultSummary` are masked again before they are returned. * **Query params**: | Param | Type | Default | Meaning | | - | - | - | - | | `toolName` | string | (all tools) | Only this tool's rows | | `limit` | number | `100` | The most rows to return | * **Request body**: none. ### Responses **`200 OK`**: the audit rows: ```ts theme={"theme":"github-dark"} { ok: true audit: Array<{ id: string toolName: string agentId: string | null phase: 'before' | 'after' decision: string | null // before rows: allow | deny | require_approval | rewrite | observe (allowed, but an inspector flagged it) argsSummary: string | null // scrubbed JSON, masked again here resultSummary: string | null // after rows: scrubbed, shortened, masked again here isError: number // 0 | 1 tenantId: string | null grantId: string | null // the grant the call ran under; null for a built-in tool connectorId: string | null ruleId: string | null // the standing rule that allowed it, if any createdAt: number }> } ``` **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<masked message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl 'http://localhost:18790/api/tools/audit?toolName=web_search&limit=20' ``` *** ## MCP transport: `/api/mcp/{tasks,memory,tools,teamchat}` Twelve routes, `POST`, `GET` and `DELETE` on each of `/api/mcp/tasks`, `/api/mcp/memory`, `/api/mcp/tools` and `/api/mcp/teamchat`, carry Clawboo's four MCP servers over Streamable HTTP. The servers run inside the Clawboo server. Each connection is a stateful session named by the `mcp-session-id` header. | Method | Role | | - | - | | `POST` | JSON-RPC requests. The first must be `initialize`, sent without a session id; the response's `mcp-session-id` header names the new session, and later requests send it back. | | `GET` | The session's event stream from the server (notifications and streamed results). One per session. | | `DELETE` | Ends the session. | Headers a client sends: * Every `POST`: `Content-Type: application/json` and `Accept: application/json, text/event-stream`. * A `GET`: `Accept: text/event-stream`. * After `initialize`: the `mcp-session-id` it returned, and optionally `mcp-protocol-version`. Each session gets its own server instance, made at `initialize` and dropped when the session closes. The servers bind the session to a caller when it starts (see the binding sections below). Sessions live in memory, so a restart of the Clawboo server ends them all and clients must send `initialize` again. Errors from the transport are JSON-RPC error bodies, `{ "jsonrpc": "2.0", "error": { "code": -32000, "message": "<message>" }, "id": null }`, except where the table says otherwise: | Status | When | | - | - | | 400 | A `POST` with no session id that is not `initialize`, or with a session id that has no live session: `No valid session; send an initialize request first.` | | 400 | A `GET` or `DELETE` with a missing or unknown session id: `Invalid or missing MCP session id.` | | 400 | An `mcp-protocol-version` header the server does not support | | 401 | The access gate is on and the request has neither the cookie nor a valid `attachToken` (a plain `{ "error": ... }` body, below) | | 406 | A `POST` whose `Accept` lacks `application/json` or `text/event-stream`, or a `GET` whose `Accept` lacks `text/event-stream` | | 409 | A second `GET` stream for a session that already has one | | 415 | A `POST` to a live session whose `Content-Type` is not `application/json` (an `initialize` sent that way gets the first 400) | | 500 | An error before the response started: `{ "error": "<message>" }` | ### Access: the `attachToken` With the access gate on (`STUDIO_ACCESS_TOKEN` set), a request to one of the twelve transport routes needs either the access cookie or an `attachToken` query parameter. The token is an HMAC-SHA256 the server derives from a per-install secret, and it is the same on every attach URL of an install. Clawboo writes it into every attach URL it builds: for the runtimes it starts, and in the `GET /api/mcp/config` snippet. It is checked on every request (`initialize` and the session's later `POST`, `GET` and `DELETE` alike), which is why it rides in the URL. The token counts only on a request that reached the loopback port directly. It is refused from another machine, and on any request that carries a proxy header (`Forwarded`, `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Proto` or `X-Real-IP`). It opens these twelve routes only: `GET /api/mcp/config` always needs the cookie while the gate is on, because it signs attach URLs. With the gate off, neither is needed. The server log masks `attachToken`, `scopeSig` and `provSig` values. Without either credential the gate answers **401**: ```json theme={"theme":"github-dark"} { "error": "Clawboo access token required. Open /?access_token=<token> once to set a cookie." } ``` Clawboo 0.5.0, the latest npm release, does not have this rule: on 0.5.0 any loopback request reaches every `/api/mcp/*` route, `/api/mcp/config` included, without the cookie. See [Security](/operating/security). ### Session binding: who is calling Every server reads who is calling from the attach URL when the session starts, at `initialize`. A binding counts only when its `scopeSig` signature verifies. Clawboo signs the URLs it writes for the runtimes it starts, and `GET /api/mcp/config` signs the ones it builds for you. A parameter added or edited by hand, without a matching signature, is ignored, so neither the model nor the runtime can widen what a session sees by changing tool arguments or its own config. | Server | URL parameters | What a verified binding does | | - | - | - | | `tasks` | `scopeTeamId`, `scopeAgentId`, `scopeSig` | Board reads and writes are limited to that team; a task from another team reads as not found. `scopeAgentId` also delivers the agent's undelivered inbox messages on its tool responses. | | `memory` | `scopeTeamId`, `scopeAgentId`, `scopeTenantId`, `scopeSig`; `provRuntime`, `provTaskId`, `provSessionKey`, `provSig` | Saves are tagged with the team, so every teammate can recall them. Reads see that team, this agent's private facts and the global tier. The `prov*` stamps record which runtime, task and session saved a fact. | | `tools` | `scopeTeamId`, `scopeAgentId`, `scopeSig` | Connector grants scoped to that agent or team apply to its tool calls. | | `teamchat` | `roomTeamId`, `postAuthorAgentId`, optional `delegate=1`, `scopeSig` | Posts go to that team's peer-chat room as that agent; both parameters are required. `delegate=1` adds the `team_delegate` tool; Clawboo sets it only for runs the team chat starts. | The provenance stamps have their own signature, `provSig`. They are dropped when it does not verify, and they never widen what a session can see. `delegate=1` is covered by `scopeSig`, so it cannot be added to a signed URL. ### What an unverified caller gets An HTTP session with no binding, or with a signature that does not verify, is an **unverified caller**: | Server | Unverified HTTP caller | | - | - | | `tasks` | No `claim_task` or `assign_task`. `add_comment` posts as a plain agent comment with no author id. Reads and writes are not limited to one team. | | `memory` | Reads and saves use the global tier only; scope arguments in tool calls are ignored, and no provenance is recorded. | | `tools` | No agent identity, so a grant scoped to an agent or a team never matches its calls. | | `teamchat` | No tools at all. | **OpenClaw agents are always unverified callers.** The Gateway's MCP config is one entry for the whole Gateway, so it cannot carry a per-agent binding. Clawboo registers `tasks`, `memory` and `tools` for OpenClaw with no binding, and never registers `teamchat`. The URL carries the `attachToken` only when the Gateway runs on this machine; a Gateway on another machine gets the bare URL. The stdio servers (the MCP bins) are different: there you, the operator, run the server, so scope comes from the tool arguments. ### What each server offers Tasks serves board tools (12, including `list_tasks`, `create_task`, `claim_task` and `add_comment`), Memory serves `memory_save`, `memory_search`, `memory_browse` and `memory_feedback`, Tools serves the available built-in tools, the connector tools the caller's grants allow, and `read_tool_result` for a result too large to return at once, and TeamChat serves `team_chat_post`, `team_chat_subscribe` and, on a `delegate=1` session, `team_delegate`. The full list, with each tool's inputs, is in the [MCP tools reference](/reference/mcp-tools). The `GET` stream carries MCP notifications and streamed results, not Clawboo-shaped events. ### Example (a raw `initialize`) ```bash theme={"theme":"github-dark"} # With the access gate on, add ?attachToken=<token> (copy it from GET /api/mcp/config) # to the URL in both calls. # 1. Initialize. The response carries the mcp-session-id header. curl -i -X POST http://localhost:18790/api/mcp/tasks \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' # 2. Send the session id back on every later call. curl -X POST http://localhost:18790/api/mcp/tasks \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'mcp-session-id: <id-from-step-1>' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' ``` Without a binding this session is an unverified caller, so its `tools/list` leaves out `claim_task` and `assign_task`. In practice an MCP client library, or the runtime's own MCP support, speaks the protocol for you; get its attach snippet from `GET /api/mcp/config`. *** ## `GET /api/mcp/config` Builds a copy-and-paste attach snippet for one runtime and one MCP server. The HTTP address uses the port this server is bound to (`http://127.0.0.1:<port>`), never the request's `Host` header, so a forged `Host` cannot send a runtime's MCP traffic elsewhere. If that port is unknown, it falls back to `CLAWBOO_API_PORT`, else `18790`. Every `http` URL it returns carries the `attachToken`. Send `scopeTeamId` or `scopeAgentId` and the URL also carries those parameters with a matching `scopeSig`, so the binding counts. Because the route signs the identity you ask for, it always needs the access cookie while the gate is on; the `attachToken` does not open it. * **Query params**: | Param | Type | Default | Values | | - | - | - | - | | `server` | string | `tasks` | `tasks`, `memory`, `tools`, `teamchat` (anything else is a **400**) | | `runtime` | string | `claude-code` | `claude-code`, `codex`, `openclaw` | | `transport` | string | `http` | `http`; any other value is treated as `stdio` | | `scopeTeamId` | string | none | A team to bind, covered by `scopeSig`. It must exist | | `scopeAgentId` | string | none | An agent to bind, covered by `scopeSig` | `scopeTeamId` must name a team in the `teams` table, archived or not. A `scopeAgentId` that is a Clawboo agent must be on that team; an id no Clawboo agent uses is signed as given, for an outside agent's own identity. `runtime` and `transport` are not checked: an unknown `runtime` answers `200` with `{ "ok": true }` and no `config`. > [!WARNING] > > Binding `scopeAgentId` to an existing agent makes your outside agent act as that agent: it uses that agent's grants, and the Tasks server delivers that agent's inbox messages to it, so the real agent no longer receives them. Use `scopeAgentId` only with an agent id that nothing else runs as. * **Request body**: none. ### Responses **`400 Bad Request`**: `server` is not one of the four servers, `scopeTeamId` names no team, or `scopeAgentId` is a Clawboo agent on another team: ```json theme={"theme":"github-dark"} { "error": "unknown server: <server>" } { "error": "unknown team: <scopeTeamId>" } { "error": "agent <scopeAgentId> is not on team <scopeTeamId>" } ``` **`200 OK`**: the attach config: ```ts theme={"theme":"github-dark"} { ok: true config?: { id: string // 'clawboo-<server>' transport: 'http' | 'stdio' snippet: string // a command or config block to copy structured: Record<string, unknown> // the same thing as data, e.g. Claude Code's inline mcpServers entry } // absent for an unknown runtime } ``` What `snippet` holds: | `runtime` | `http` | `stdio` | | - | - | - | | `claude-code` | `claude mcp add --transport http clawboo-<server> '<url>'` (the URL is quoted for the shell) | `claude mcp add clawboo-<server> -e CLAWBOO_DB_PATH=<db> -- node <bin>` | | `codex` | a `[mcp_servers.clawboo-<server>]` block for `~/.codex/config.toml` with `url = "<url>"` | a `[mcp_servers.clawboo-<server>]` block with `command`, `args` and `env` | | `openclaw` | an entry for `mcp.servers` in `openclaw.json` with `"transport": "streamable-http"` | an entry for `mcp.servers` with `command`, `args` and `env` | An `http` URL looks like `http://127.0.0.1:18790/api/mcp/tasks?attachToken=<64 hex characters>`, with the scope parameters before the token when you ask for them. A `stdio` snippet runs `node <CLAWBOO_MCP_BIN_DIR>/<server>.js` (a `<path-to>/dist/bin/<server>.js` placeholder when `CLAWBOO_MCP_BIN_DIR` is not set) and sets `CLAWBOO_DB_PATH` to this server's database, because the stdio bins otherwise open a different default database (see [Data and state](/operating/data-and-state)). **`500 Internal Server Error`**: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl 'http://localhost:18790/api/mcp/config?runtime=claude-code&server=tasks&transport=http' # A Memory attach bound to one team curl 'http://localhost:18790/api/mcp/config?runtime=claude-code&server=memory&scopeTeamId=<team-id>' ``` *** ## `POST /api/mcp/attach-secret/rotate` Replaces the per-install secret that every `attachToken`, `scopeSig` and `provSig` is derived from. Every attach URL minted before stops verifying at once, in every Clawboo process that uses this database: the ones you copied, the ones a local OpenClaw Gateway holds, and those of runs already in progress. Clawboo's own runtimes get new URLs on their next run. When this server is connected to an OpenClaw Gateway, it re-registers the Gateway's URLs and waits up to 5 seconds for the write before it answers. Use it when an attach URL leaks, then copy new URLs from `GET /api/mcp/config`. Like `GET /api/mcp/config`, it always needs the access cookie while the gate is on; the `attachToken` does not open it. * **Request body**: none. ### Responses **`200 OK`**: the secret is replaced. `gateway` says what became of the OpenClaw Gateway's registered URLs: ```ts theme={"theme":"github-dark"} { ok: true gateway: 'updated' | 'retrying' | 'not-connected' } ``` | `gateway` | Meaning | | - | - | | `updated` | The Gateway holds the new URLs | | `retrying` | The Gateway refused the config write, or did not answer within 5 seconds. Clawboo tries again after waits of 15, 30, 60, 120 and 240 seconds, then leaves it to its next connection to the Gateway | | `not-connected` | No Gateway connection. The Gateway gets the new URLs when Clawboo next connects to it | **`500 Internal Server Error`**: the new secret could not be stored: ```json theme={"theme":"github-dark"} { "error": "<message>" } ``` ### Example ```bash theme={"theme":"github-dark"} curl -s -X POST http://localhost:18790/api/mcp/attach-secret/rotate ``` *** ## Error envelope The `/api/tools*` routes, `GET /api/mcp/config` and `POST /api/mcp/attach-secret/rotate` use the standard `{ "error": string }` body; on the `/api/tools*` routes the message is masked. The resolve route's validation `400` adds `details`. The MCP transport answers with the JSON-RPC error bodies in its table, except for the gate's `401` and a `500`, which use the standard body. A rate-limited request answers **429** `{ "error": "Too many requests. Wait a moment and retry." }` with a `Retry-After` header. **Source of truth:** `apps/web/server/api/tools.ts` and `mcp.ts`; the servers in `packages/mcp/src/` (`http.ts`, `config.ts`, `tasks/`, `memory/`, `tools/`, `teamchat/`); the gate in `packages/gateway-proxy/src/access-gate.ts`; approvals in `packages/db/src/tools/persistence.ts`. ## Related * [MCP tools reference](/reference/mcp-tools): every tool the four servers offer, and its inputs. * [MCP servers](/operating/mcp-servers): attach an outside agent, transports and scoping in practice. * [Security](/operating/security): the access gate, the attach token and reverse proxies. * [Approvals](/using/approvals): answering approval cards in the dashboard. * [Runtimes API](/reference/rest-api/runtimes): a task run attaches to these servers. * [REST API overview](/reference/rest-api/index): authentication, rate limits and conventions. # Claude Code runtime Source: https://docs.claw.boo/runtimes/claude-code Run agents on Anthropic's Claude Code: install it, connect an Anthropic API key, and see what it can do on your computer and what it costs. Claude Code is Anthropic's coding agent. Clawboo runs it through the Claude Agent SDK, so a Claude Code agent works with Claude Code's own tools: it reads and edits files, runs shell commands and fetches web pages. Clawboo records the cost Claude Code reports for each run. Use this page to set it up, see what Clawboo controls on it, and understand what it can do on your computer. | Topic | Claude Code | | - | - | | Best for | Code changes and other file work on Anthropic models | | You need | An Anthropic API key, plus the Claude Agent SDK on an npm install of Clawboo | | Who bills you | Anthropic, per token, on your API key | | Cost in Clawboo | The real cost Claude Code reports | | Steering | Your comments reach a running task | To compare it with the other runtimes, see [Choose a runtime](/runtimes/index). ## Before you begin * Clawboo installed with npm (`npm install -g clawboo`) or running from source. A one-off `npx clawboo@latest` run cannot load the Agent SDK that Claude Code needs. * Node.js 22.12 or newer, with npm. * An Anthropic API key from [console.anthropic.com](https://console.anthropic.com/settings/keys). A `claude` CLI signed in with a Claude subscription is not enough: Clawboo shows Claude Code as connected, and offers it for your agents, only when an Anthropic API key is available. ## Set up Claude Code **1. Install the Claude Agent SDK** Skip this step if you run Clawboo from source. On an npm install, install Clawboo and the SDK together: ```bash theme={"theme":"github-dark"} npm install -g clawboo @anthropic-ai/claude-agent-sdk ``` The SDK has to sit next to Clawboo, which is why both install globally. Clawboo does not ship it, because its Claude Code binary adds about 210 MB to every install. If Clawboo is already running, restart it with `clawboo restart`. **2. Install Claude Code** Open **Settings** (at the bottom of the left sidebar, or press Cmd+, on macOS and Ctrl+, on Windows or Linux), then **Runtimes**. On the **Claude Code** row, click **Install**, then **Install** in the panel that opens. Clawboo runs this and shows its output: ```bash theme={"theme":"github-dark"} npm install -g @anthropic-ai/claude-code@2 ``` You should see the row change to **Needs key**, or to **Connected** if an Anthropic key is already saved. If Claude Code is already installed on this computer, the row shows that state straight away. **3. Connect your Anthropic key** Click **Connect** on the row, paste your key into the `ANTHROPIC_API_KEY` field, and click **Connect**. Clawboo checks the key with Anthropic before it saves it. If the check fails, the panel shows the reason; fix the key, or click **Connect anyway** when Anthropic is only unreachable from this computer. You should see the row read **Connected**. If you already saved an Anthropic key under **Settings** > **Providers**, Claude Code reads **Connected** without this step. **4. Put a Claude Code agent on a team** Create a team, and in **Customize team** set a member's runtime to **Claude Code**, then deploy the team. You can pick Claude Code there only while its row reads **Connected**; until then it is greyed out with "Connect in Runtimes". See [Teams](/using/teams) for the full flow. ## What Clawboo controls on Claude Code | What | On Claude Code | | - | - | | Persona | The agent's `SOUL.md` (which the Personality sliders write into) is added to every prompt as a persona block, up to 4,000 characters. | | `AGENTS.md` | Draws the agent's links on the team graph. Claude Code never reads it. | | Other agent files | Not used. `IDENTITY.md`, `TOOLS.md` and the rest are OpenClaw files; see [Agents](/using/agents). | | Model | Team setup has no model picker for Claude Code. A run that names no model gets Claude Code's default, or `ANTHROPIC_MODEL` when it is set in the environment Clawboo runs with. | | Clawboo tools | Every run gets Clawboo's Tasks, Memory, Tools and TeamChat servers (MCP), so the agent can read the board, use shared memory and connectors, and talk to its team. | | Leading a team | A Claude Code leader hands work to teammates with the `team_delegate` tool. | | Worktree | Only a task-runner run that names a repository gets its own git worktree. Team chat and the tasks a team hands out run without one. See [Worktrees and handoff](/concepts/worktrees-and-handoff). | | Steering | Live. A comment on a running task joins the run at its next step, after the tool call in progress. See [Board](/using/board). | | Sessions | Each team chat turn starts fresh from the context Clawboo builds. Its one-to-one chat resumes one session until `/reset`. See [What an agent sees](/concepts/agent-context). | | Memory | Clawboo's shared memory, through the Memory tools, and the `CLAUDE.md` of the folder it works in. Your own Claude Code memory is left out (below). | | Cost | The real cost each run reports. See [Costs](#costs). | ### Your own Claude Code setup stays out Clawboo starts Claude Code without your own Claude Code setup. A Claude Code agent does not load: * your user settings in `~/.claude/settings.json`: plugins, hooks, `env`, permissions, model and effort level * the MCP servers you added to your own Claude Code * your `~/.claude/CLAUDE.md` and `~/.claude/rules`, or a `CLAUDE.md` in any folder above the one it works in * Claude Code's auto memory, so it neither reads nor writes the notes Claude Code keeps for a repository It still loads the Claude Code files of the folder it works in. In a task's worktree that is the repository's `CLAUDE.md`, `.claude/settings.json` (hooks included) and `.mcp.json`, as anyone who clones the repository gets them. This does not change how Claude Code signs in. Clawboo passes your Anthropic key in Claude Code's environment, and the `claude` command's own sign-in is not kept in a settings file. If your Claude Code reaches Anthropic through `~/.claude/settings.json`, for example an `env` block that sets `ANTHROPIC_BASE_URL` or an `apiKeyHelper`, that no longer applies to agents: set the same variables in the environment you start Clawboo with. Leaving your setup out is not a sandbox. Claude Code can still read any file your user account can, `~/.claude` included. ## Permissions and safety > [!WARNING] > > Clawboo starts Claude Code with its permission prompts turned off (`permissionMode: 'bypassPermissions'`). Nothing asks you before it runs a command, edits or deletes a file, or reaches the network. It runs as your user account and can read and change any file that account can. * **A worktree is not a sandbox.** Only a task-runner run of a task that changes files, with a repository named, works in its own git worktree, which keeps its edits off your main checkout. Everywhere else there is no worktree: in team chat, in the agent's own chat, and on research and review runs (which includes every agent Routine you create in the app), Claude Code starts in its team's folder, `~/.clawboo/workspaces/<team id>`, which every member of the team shares (a task or an agent with no team uses `workspaces/_no-team`). That folder only sets where it starts: Claude Code can still read and change any file your user account can, including Clawboo's database and key vault. * **Clawboo's approvals do not cover Claude Code's own tools.** Calls the agent makes through Clawboo's Tools server (connectors, for example) go through Clawboo's checks and can ask you first. Claude Code's built-in shell, file and web tools never do. * **Clawboo hides some secrets from it, by name only.** Before a run, Clawboo removes its own secrets (the OpenClaw Gateway token, the dashboard access token and the vault master key) from the environment Claude Code inherits. It also removes a fixed list of common credentials, such as GitHub, npm, cloud, Stripe, Slack and database variables. Other variables pass through, and credential files on disk stay readable. For work you do not trust, run Clawboo under a separate user account or in a virtual machine. [What agents can do](/concepts/permissions) compares every runtime. ## Costs Claude Code reports the dollar cost of each run, and Clawboo records that figure as reported, not as an estimate. It counts toward your budgets, and Anthropic bills the same usage to your API key. If a run in team chat or in the agent's own chat ends without reporting a cost (it failed early, say), Clawboo records an estimate from the length of the prompt and reply instead. The cost arrives when a run finishes, so a hard cap cannot stop a Claude Code run partway through. When that last report takes a hard cap past its limit, the finished result is kept and posted, and the next run is refused. A routine or API run that ends without reporting a cost is estimated the same way as in team chat. See [Budgets and spend](/using/budgets-and-spend) for how each runtime's cost reaches your budgets. ## Disconnect Claude Code In **Settings** > **Runtimes**, click **Manage** on the **Claude Code** row, then **Disconnect**. This deletes the Anthropic key from Clawboo's vault. The vault keeps one copy of each key for every runtime, so Clawboo Native and Hermes lose that key too unless they find it elsewhere. Until you connect Claude Code again, it also ignores an Anthropic key in your shell or in `~/.openclaw/.env`. To remove one key on purpose, or keep it for other runtimes, see [Disconnect a key or a runtime](/using/providers-and-runtimes#disconnect-a-key-or-a-runtime). ## Limits * Team setup has no model picker for Claude Code members. * It needs an Anthropic API key to read **Connected**; a Claude subscription sign-in alone doesn't count. * A one-off `npx` run of Clawboo cannot load the Agent SDK. * A hard cap counts Claude Code's cost only when each run ends, so it refuses the next run rather than stopping the current one. ## Troubleshooting > [accordion] The run failed: The Claude Code runtime needs @anthropic-ai/claude-agent-sdk > > The full message continues "which this clawboo install does not ship". The row reads **Connected** because that only checks the `claude` command and your key; the Agent SDK is a separate package. > > Install it next to Clawboo with `npm install -g clawboo @anthropic-ai/claude-agent-sdk`, run `clawboo restart`, then send the message or start the task again. If you started Clawboo with `npx`, switch to the global install first. > [accordion] Claude Code reads Needs key, but my claude CLI is signed in > > Clawboo needs an Anthropic API key for Claude Code; the CLI's own sign-in does not count. Connect a key on the row, or save one under **Settings** > **Providers**. An `ANTHROPIC_API_KEY` set in the environment Clawboo runs with also counts. > [accordion] Claude Code is greyed out in Customize team (Connect in Runtimes) > > The runtime is not connected. Finish [Set up Claude Code](#set-up-claude-code) until the row reads **Connected**, then open **Customize team** again. > [accordion] Invalid API key. > > Anthropic rejected the key. Copy it again from [console.anthropic.com](https://console.anthropic.com/settings/keys), or create a new one, and connect it. > [accordion] Could not reach anthropic (timed out). > > The key check could not reach Anthropic from this computer. Check your network or proxy. If you are sure the key is right, click **Connect anyway** to save it without the check. > [accordion] Permission denied. A global install may require elevated permissions. > > npm could not write to its global folder. The message suggests `sudo npm install -g @anthropic-ai/claude-code@2`. A Node version manager such as nvm or fnm avoids the problem without `sudo`; install Node through one, then click **Install** again. > [accordion] npm not found. Install Node.js (which bundles npm) and retry. > > The Clawboo server cannot find npm. Install Node.js 22.12 or newer, restart Clawboo with `clawboo restart`, then click **Install** again. ## Check that it works 1. In **Settings** > **Runtimes**, the **Claude Code** row reads **Connected**. 2. Open the team chat of a team with a Claude Code member. Type `@`, pick that agent, and ask: "Say hello in one sentence." 3. You should see a reply under the agent's name. If the run fails, the chat shows "The run failed:" and the reason; look it up in [Troubleshooting](#troubleshooting). > [accordion] Under the hood > > * Clawboo calls the SDK's `query()` with `permissionMode: 'bypassPermissions'` and `allowDangerouslySkipPermissions: true`. It also passes the run's working folder (the task's worktree, or else the team's folder), the model when a run names one, a session to resume, Clawboo's four MCP servers over HTTP, and the scrubbed environment plus the connected key. It sets `settingSources: ['project']`, passes `settings` with `autoMemoryEnabled: false` and a `claudeMdExcludes` list that covers every folder above the working folder and your home folder, and sets `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` in the environment, over any value Clawboo itself runs with. > * The SDK starts Claude Code from its own platform package. The `claude` command that **Install** adds is what the Runtimes row checks for. A source build of Clawboo uses SDK version 0.3.268. > * Run a board task on Claude Code directly with `POST /api/runtimes/claude-code/run`; see the [Runtimes API](/reference/rest-api/runtimes). Such a run falls back to the `claude` CLI's own sign-in when no Anthropic key is available. > * A run that hits Claude Code's turn limit ends cleanly, and the task runner continues the task in a fresh session with a handoff note. ## Related * [Choose a runtime](/runtimes/index): how Claude Code compares with the other runtimes. * [Connect AI providers and runtimes](/using/providers-and-runtimes): keys, sign-ins and what Disconnect removes. * [What agents can do](/concepts/permissions): permissions and safety for every runtime. * [Budgets and spend](/using/budgets-and-spend): how cost reaches your budgets. * [Worktrees and handoff](/concepts/worktrees-and-handoff): when a run gets its own git worktree. # Codex runtime Source: https://docs.claw.boo/runtimes/codex Run agents on OpenAI Codex with your ChatGPT plan: install it, sign in, and see what it can do on your computer and how Clawboo estimates its cost. Codex is OpenAI's coding agent. You sign in with your ChatGPT account instead of pasting an API key, so Codex is how you run agents on a ChatGPT plan. A Codex agent works with Codex's own tools: it runs shell commands, edits files and searches the web. Use this page to set it up, see what Clawboo controls on it, and understand what it can do on your computer. | Topic | Codex | | - | - | | Best for | Running a team on your ChatGPT plan, with no API key | | You need | A ChatGPT account whose plan includes Codex | | Who bills you | OpenAI, through your ChatGPT plan | | Cost in Clawboo | An estimate from the tokens Codex reports | | Steering | Your comments reach a running task | If you chose **Sign in with ChatGPT** during onboarding, you already installed Codex, signed in and deployed your first team on it. To compare Codex with the other runtimes, see [Choose a runtime](/runtimes/index). ## Before you begin * Clawboo running, and Node.js with npm (Clawboo installs Codex with npm). * A ChatGPT account whose plan includes Codex. Clawboo never sees your password: Codex's own sign-in opens OpenAI's page in your browser. ## Set up Codex **1. Install Codex** Open **Settings** (at the bottom of the left sidebar, or press Cmd+, on macOS and Ctrl+, on Windows or Linux), then **Runtimes**. On the **Codex** row, click **Install**, then **Install** in the panel that opens. Clawboo runs this and shows its output: ```bash theme={"theme":"github-dark"} npm install -g @openai/codex@0 ``` You should see the row change to **Needs login**. If Codex is already installed and signed in on this computer, the row reads **Connected** straight away. **2. Sign in with ChatGPT** Click **Sign in** on the row, then **Sign in with ChatGPT**. Clawboo runs Codex's own `codex login`, which opens OpenAI's sign-in page in your browser. Approve there; there is no code to type. If no browser window appears, click **Open sign-in page**. You should see the row read **Connected**. The same sign-in is on **Settings** > **Providers** > **ChatGPT subscription** > **Connect**. **3. Put Codex agents on a team** Create a team, and in **Customize team** set a member's runtime to **Codex**, then deploy the team. You can pick Codex there only while its row reads **Connected**; until then it is greyed out with "Connect in Runtimes". See [Teams](/using/teams) for the full flow. If the in-app sign-in fails, the panel shows the reason and the command to run yourself. Run `codex login` in a terminal, finish in the browser, then click **Re-check** in the Codex panel. ## What Clawboo controls on Codex | What | On Codex | | - | - | | Persona | The agent's `SOUL.md` (which the Personality sliders write into) is added to every prompt as a persona block, up to 4,000 characters. | | `AGENTS.md` | Draws the agent's links on the team graph. Codex never reads it. | | Other agent files | Not used. See [Agents](/using/agents). | | Model | Team setup has no model picker for Codex. It runs Codex's default model. | | Codex home | Each Codex agent has its own Codex folder (below). Your own `~/.codex` settings are not used. | | Clawboo tools | Every run gets Clawboo's Tasks, Memory, Tools and TeamChat servers (MCP). | | Leading a team | A Codex leader hands work to teammates with the `team_delegate` tool. | | Worktree | Only a task-runner run that names a repository gets its own git worktree. Team chat and the tasks a team hands out run without one. See [Worktrees and handoff](/concepts/worktrees-and-handoff). | | Steering | Live. A comment on a running task joins the turn in progress; Codex reads it at its next model call, after the command that is running. See [Board](/using/board). | | Sessions | A Codex agent leading a chat continues the same Codex thread from one turn to the next, separately for each chat room. Its one-to-one chat continues one session until `/reset`. | | Memory | Clawboo's shared memory, through the Memory tools, plus the thread history in the agent's Codex folder. | | Cost | An estimate from the tokens Codex reports. See [Costs](#costs). | ### Each agent's Codex folder Each Codex agent gets its own Codex home at `~/.clawboo/runtimes/codex/<agent id>`, created so only your user can open it. Codex keeps the agent's sessions there, which is how a leader, or the agent's own chat, continues its thread. Before each run, Clawboo: * copies your ChatGPT sign-in (`~/.codex/auth.json`) into the folder when the copy there is missing, unusable or older than yours. Codex refreshes the copy itself, so Clawboo never overwrites a newer one. * writes the folder's `config.toml`, which lists Clawboo's MCP servers. Your own `~/.codex/config.toml` is not read, and Clawboo never writes to `~/.codex`. ### Leading a team on Codex When onboarding deploys your first team on Codex (the **Sign in with ChatGPT** path), it gives the team a lead: the member with a leader role, or else its first agent. It also promotes that agent to Boo Zero, the leader that coordinates every team. It skips that promotion when you already chose a Boo Zero or have a Clawboo Native one; an OpenClaw Gateway's default agent is replaced. See [Boo Zero](/using/boo-zero). ## Permissions and safety > [!WARNING] > > Clawboo runs Codex with its own sandbox and approval prompts turned off (approval policy `never` and sandbox `danger-full-access`, or `--dangerously-bypass-approvals-and-sandbox` when a run falls back to `codex exec`). Nothing asks you before it runs a command, edits or deletes a file, or reaches the network. It runs as your user account and can read and change any file that account can. * **A worktree is not a sandbox.** Only a task-runner run of a task that changes files, with a repository named, works in its own git worktree, which keeps its edits off your main checkout. Everywhere else there is no worktree: in team chat, in the agent's own chat, and on research and review runs (which includes every agent Routine you create in the app), Codex starts in its team's folder, `~/.clawboo/workspaces/<team id>`, which every member of the team shares (a task or an agent with no team uses `workspaces/_no-team`). That folder only sets where it starts: Codex can still read and change any file your user account can, including Clawboo's database and key vault. * **Clawboo's approvals do not cover Codex's own tools.** Calls the agent makes through Clawboo's Tools server (connectors, for example) go through Clawboo's checks and can ask you first. Codex's shell commands, file edits and web searches never do. If Codex asks for an approval or an answer mid-run, Clawboo declines, so the run never waits for a person who is not there. * **Clawboo hides some secrets from it, by name only.** Before a run, Clawboo removes its own secrets and a fixed list of common credentials (GitHub, npm, cloud, Stripe, Slack and database variables) from the environment Codex inherits. Other variables pass through, and credential files on disk stay readable. For work you do not trust, run Clawboo under a separate user account or in a virtual machine. [What agents can do](/concepts/permissions) compares every runtime. ## Costs Codex reports the tokens a run used but no dollar figure, and Clawboo has no price list for Codex models. Clawboo prices the tokens at its default estimate rate ($3 per million input tokens, $15 per million output tokens), records the result as an estimate, and counts it toward your budgets. Codex reports once, as the run ends, so a hard cap that report crosses keeps the finished result and refuses the next run. On a ChatGPT plan you are not billed per token: the estimate is a guide for your budgets, not your bill. See [Budgets and spend](/using/budgets-and-spend) for how each runtime's cost reaches your budgets. ## Sign out In **Settings** > **Runtimes**, click **Manage** on the **Codex** row, then **Sign out** and confirm. Clawboo runs Codex's own `codex logout`, and the row then reads **Needs login**. Codex keeps no key in Clawboo's vault, so there is nothing else for Clawboo to delete. ## Limits * Team setup has no model picker for Codex members. * Your own `~/.codex` settings do not apply to Codex agents. * A Codex too old to run `codex app-server` falls back to `codex exec`, which cannot take comments mid-run. * Its cost in Clawboo is always an estimate. ## Troubleshooting > [accordion] Codex reads Needs login after I signed in from a terminal > > Click **Sign in** on the **Codex** row to open its panel, then **Re-check**. Clawboo asks Codex (`codex login status`) whether you are signed in, so a sign-in from any terminal on this computer counts. > [accordion] The codex CLI isn't installed yet. Install it first, then sign in. > > You started **Sign in with ChatGPT** (for example on **Settings** > **Providers**) before Codex was installed. Install it from **Settings** > **Runtimes** > **Codex** > **Install**, then sign in again. > [accordion] Sign-in timed out. Try again. > > The browser sign-in was not finished within about 15 minutes. Click **Retry**, or run `codex login` in a terminal and click **Re-check**. > [accordion] Sign-in did not complete (exit 1). Run codex login in your terminal, then re-check. > > Codex's sign-in stopped with an error (the exit number can differ). Run `codex login` in a terminal to see Codex's own message, finish the sign-in there, then click **Re-check**. > [accordion] A run fails with no Codex credentials provisioned for managed home > > The agent's Codex folder holds no usable sign-in, and Clawboo found none to copy from `~/.codex`. Run `codex login` in a terminal, then send the message or start the task again. The next run copies the new sign-in into the agent's folder. > [accordion] Comments reach a Codex agent only at its next run > > The run fell back to `codex exec`, which cannot take input mid-run. That happens when the installed Codex has no `codex app-server`, or the app-server does not answer within 20 seconds. Update Codex with `npm install -g @openai/codex@0`, then try again. ## Check that it works 1. In **Settings** > **Runtimes**, the **Codex** row reads **Connected**. 2. Open the team chat of a team with a Codex member. Type `@`, pick that agent, and ask: "Say hello in one sentence." 3. You should see a reply under the agent's name. If the run fails, the chat shows "The run failed:" and the reason; look it up in [Troubleshooting](#troubleshooting). > [accordion] Under the hood > > * Each run starts `codex app-server` with `CODEX_HOME` set to the agent's Codex folder, sends `initialize` (20-second limit), then `thread/start` or `thread/resume` with `approvalPolicy: 'never'` and `sandbox: 'danger-full-access'`, then one `turn/start`. Any request the app-server sends back is answered with an error. > * If the app-server cannot start or the handshake fails, the run uses `codex exec --json --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check`, or `codex exec resume <id>` to continue a thread. > * A comment on a running task goes in with `turn/steer`. A comment that arrives after the turn ended waits in the agent's mailbox for its next run. > * Run a board task on Codex directly with `POST /api/runtimes/codex/run`; see the [Runtimes API](/reference/rest-api/runtimes). A run without an agent id uses the folder `~/.clawboo/runtimes/codex/codex`, and its `costUsd` is the token estimate. ## Related * [Choose a runtime](/runtimes/index): how Codex compares with the other runtimes. * [Connect AI providers and runtimes](/using/providers-and-runtimes): the ChatGPT sign-in, keys and disconnecting. * [What agents can do](/concepts/permissions): permissions and safety for every runtime. * [Budgets and spend](/using/budgets-and-spend): how estimated costs reach your budgets. * [Hermes](/runtimes/hermes) and [OpenClaw](/runtimes/openclaw): two more runtimes that can run on a ChatGPT plan, each with its own sign-in. # Hermes runtime Source: https://docs.claw.boo/runtimes/hermes Run agents on the open-source Hermes agent: install it with Python, connect a key or your ChatGPT plan, and see what it can do on your computer. Hermes is an open-source agent, published as the `hermes-agent` Python package. Clawboo starts one `hermes chat` per turn or task, inside a folder that belongs to that agent and stays between runs. What Hermes saves there (its `MEMORY.md`, the skills it writes, its session history) carries over to the agent's next run. Use this page to set it up, see which provider it uses, and understand what it can do on your computer. | Topic | Hermes | | - | - | | Best for | An agent that builds up its own memory and skills over time; many models through one OpenRouter key | | You need | Python 3.11, 3.12 or 3.13, and one credential: an OpenRouter key, an Anthropic or OpenAI key, or a ChatGPT sign-in | | Who bills you | The provider it runs on (OpenRouter, Anthropic, OpenAI), or your ChatGPT plan | | Cost in Clawboo | Hermes reports none; Clawboo records an estimate | | Steering | A comment stops the run and resumes it with your comment | To compare it with the other runtimes, see [Choose a runtime](/runtimes/index). ## Before you begin * Python 3.11, 3.12 or 3.13. `hermes-agent` does not install on older or newer versions. * [pipx](https://pipx.pypa.io) is recommended. Without it, Clawboo installs Hermes with `pip --user`. * One credential: * an [OpenRouter](https://openrouter.ai/keys) key, or * an Anthropic or OpenAI key you already connected (for Clawboo Native, say), or * a ChatGPT plan. The in-app sign-in appears once Codex is connected; see [Codex](/runtimes/codex). ## Set up Hermes **1. Install Hermes** Open **Settings** (at the bottom of the left sidebar, or press Cmd+, on macOS and Ctrl+, on Windows or Linux), then **Runtimes**. On the **Hermes** row, click **Install**, then **Install** in the panel that opens. With pipx, Clawboo runs `pipx install --python <python> 'hermes-agent[anthropic]<1'`, passing the newest Python 3.11 or later it finds. Without pipx, it runs `python -m pip install --user 'hermes-agent[anthropic]<1'`, and retries with `--break-system-packages` on a system that blocks user installs. The `[anthropic]` extra is what lets Hermes run on an Anthropic key. You should see the row change to **Needs key**, or to **Connected** if a key it can use is already connected. **2. Connect a credential** Pick one way. Each makes the row read **Connected**. > [tab] OpenRouter key > > Click **Connect** on the row, paste your key into the `OPENROUTER_API_KEY` field (**Get a key** opens OpenRouter), and click **Connect**. Clawboo checks the key with OpenRouter before it saves it; if OpenRouter is only unreachable from this computer, click **Connect anyway**. > [tab] Anthropic or OpenAI key > > There is nothing more to do. Hermes also reads as connected when an Anthropic or OpenAI key is available, for example the one you connected for Clawboo Native. Its runs then use that provider, as described in [Which provider Hermes uses](#which-provider-hermes-uses). > [tab] ChatGPT plan > > With Codex connected, the **Hermes** panel shows "Codex is connected. Hermes can run on your ChatGPT subscription instead of a key." Click **Use my ChatGPT subscription**. On a Hermes that is already connected, open **Manage** and use **Sign in with ChatGPT** on its **ChatGPT subscription** row. > > Hermes signs in with a one-time code: the panel shows the code, and you enter it on OpenAI's page. If that page says device authorization is disabled, turn on device code authorization under ChatGPT **Settings** > **Security**, then retry. You can also run `hermes auth add openai-codex` in a terminal, then click **Refresh** at the top of the Runtimes panel. > > Hermes keeps this sign-in separate from Codex's and OpenClaw's. **3. Put a Hermes agent on a team** Create a team, and in **Customize team** set a member's runtime to **Hermes**. You can also pick a model for that member: OpenRouter models when an OpenRouter key is connected, and ChatGPT models when Hermes is signed in with ChatGPT and Codex is connected. Leave it on **Default** to let Clawboo choose. Then deploy the team. See [Teams](/using/teams) for the full flow. ## What Clawboo controls on Hermes | What | On Hermes | | - | - | | Persona | The agent's `SOUL.md` (which the Personality sliders write into) is added to every prompt as a persona block, up to 4,000 characters. | | `AGENTS.md` | Draws the agent's links on the team graph. Hermes never reads it. | | Other agent files | Not used. Hermes keeps its own `MEMORY.md` in its folder (below), apart from the agent page's file tabs. See [Agents](/using/agents). | | Model | The model picked for the member in team setup, or an OpenRouter model chosen later with the model selector on the agent's page. Otherwise a default for its provider. See [Which provider Hermes uses](#which-provider-hermes-uses). | | Hermes folder | Each agent has its own Hermes home that persists between runs (below). | | Clawboo tools | Every run gets Clawboo's Tasks, Memory, Tools and TeamChat servers (MCP), listed in the folder's `mcp.json`. | | Leading a team | A Hermes leader hands work to teammates with the `team_delegate` tool. | | Worktree | Only a task-runner run that names a repository gets its own git worktree. Team chat and the tasks a team hands out run without one. See [Worktrees and handoff](/concepts/worktrees-and-handoff). | | Steering | Stop and resume. Hermes cannot read input mid-run, so a comment stops the run and starts it again in the same session with your comment. See [Board](/using/board). | | Memory | Hermes's own `MEMORY.md`, skills and session history in its folder, plus Clawboo's shared memory through the Memory tools. | | Cost | Hermes reports none. See [Costs](#costs). | Clawboo starts Hermes only as `hermes chat`, once per turn or task. It never starts Hermes's own gateway or its built-in scheduling; use [Routines](/using/routines) for recurring work. ### Which provider Hermes uses For each run, Clawboo works down this list and uses the first match: 1. **The model picked for the agent** (in team setup, or on the agent's page), if its key or sign-in is available. Clawboo pins Hermes to that model's provider (`--provider openrouter` for an OpenRouter model, `--provider openai-codex` for a ChatGPT model). A pick whose key or sign-in is missing is dropped, with its model, and the next rule applies. 2. **A provider named in the agent's Hermes config** (`model.provider` in its `config.yaml`). Clawboo passes no provider flag, and Hermes uses the provider and model from the config. 3. **An OpenRouter key**: `--provider openrouter`, model `openai/gpt-4o-mini`. 4. **An Anthropic key**: `--provider anthropic`, model `claude-haiku-4-5`. 5. **An OpenAI key**: `--provider openai-api`, model `gpt-4o-mini`. 6. **A ChatGPT sign-in**: `--provider openai-codex`, model `gpt-5.5`. 7. **Nothing**: Hermes uses its own default. The pick in rule 1 applies when the agent works in its team or in its own chat. A run started over the REST API has no pick; a model named in that request replaces the default models in rules 3 to 6. Clawboo hands Hermes only its OpenRouter, Anthropic and OpenAI keys. If your Hermes config names another provider (Gemini, say), set that provider's key in the environment you start Clawboo from. ### The agent's Hermes folder Each Hermes agent has its own Hermes home at `~/.clawboo/runtimes/hermes/<agent id>`, created so only your user can open it. Everything Hermes saves there is its own and stays between runs. Clawboo writes only three files there: * `mcp.json`, refreshed before every run, which connects Hermes to Clawboo's servers. * `config.yaml`, copied once from your `~/.hermes/config.yaml` when the folder has none. Clawboo never updates the copy, so later changes to your own config do not reach the agent. * `auth.json`, a copy of your Hermes ChatGPT sign-in, made when the copy there is missing, unusable or older than yours. Clawboo never writes to your own `~/.hermes`, and it does not copy your Hermes `.env`: provider keys reach each run through its environment. ## Permissions and safety > [!WARNING] > > Clawboo runs Hermes with `--yolo`, which approves every Hermes tool call without asking, and `--accept-hooks`, which runs any hooks your Hermes config declares. Nothing asks you before it runs a command, edits or deletes a file, or reaches the network. It runs as your user account and can read and change any file that account can. * **A worktree is not a sandbox.** Only a task-runner run of a task that changes files, with a repository named, works in its own git worktree, which keeps its edits off your main checkout. Everywhere else there is no worktree: in team chat, in the agent's own chat, and on research and review runs (which includes every agent Routine you create in the app), Hermes starts in its team's folder, `~/.clawboo/workspaces/<team id>`, which every member of the team shares (a task or an agent with no team uses `workspaces/_no-team`). That folder only sets where it starts: Hermes can still read and change any file your user account can, including Clawboo's database and key vault. * **Clawboo's approvals do not cover Hermes's own tools.** Calls the agent makes through Clawboo's Tools server (connectors, for example) go through Clawboo's checks and can ask you first. Hermes's built-in tools never do. * **Clawboo hides some secrets from it, by name only.** Before a run, Clawboo removes its own secrets and a fixed list of common credentials (GitHub, npm, cloud, Stripe, Slack and database variables) from the environment Hermes inherits. Other variables pass through, and credential files on disk stay readable. For work you do not trust, run Clawboo under a separate user account or in a virtual machine. [What agents can do](/concepts/permissions) compares every runtime. ## Costs Hermes reports neither tokens nor dollars to Clawboo. Clawboo estimates each run's cost from the length of the prompt and the reply, at about four characters per token, in team chat, in the agent's own chat, and on runs started over the REST API or by an agent Routine. It prices those tokens at the model's rate when Clawboo knows it, and otherwise at $3 per million input and $15 per million output tokens. That estimate counts toward your budgets. Your provider bills the real usage: OpenRouter, Anthropic or OpenAI per token, or your ChatGPT plan. See [Budgets and spend](/using/budgets-and-spend) for how each runtime's cost reaches your budgets. ## Disconnect Hermes In **Settings** > **Runtimes**, click **Manage** on the **Hermes** row, then **Disconnect**. The dialog mentions only the OpenRouter key, but Clawboo deletes every key Hermes reads from its vault: OpenRouter, Anthropic and OpenAI. The vault keeps one copy of each key for every runtime. A Clawboo Native team or Claude Code that runs on your Anthropic or OpenAI key therefore loses it too, unless it finds the key elsewhere. A Hermes ChatGPT sign-in is not removed, so Hermes can still read **Connected**. To remove a single key instead, open **Manage**, click that provider's row, and use **Disconnect OpenRouter** (or the button for that provider). That removes the one key for every runtime. See [Disconnect a key or a runtime](/using/providers-and-runtimes#disconnect-a-key-or-a-runtime). ## Limits * No live text: a Hermes reply appears when its run finishes. * Hermes reports no cost, so Clawboo's figures for it are estimates. * A comment stops the run; the tool call in progress at that moment is lost, and finished work is kept. * The agent's `config.yaml` is a one-time copy of yours. * Python 3.11 to 3.13 only. ## Troubleshooting > [accordion] Hermes needs Python 3.11 or newer, but the Python on this machine is 3.9 > > The newest Python Clawboo found is too old, and pipx is not installed. The message names your version, and adds "(Xcode Command Line Tools)" on a Mac that only has Apple's Python. Install a newer Python (for example `brew install python@3.12`) or pipx (`brew install pipx`), then click **Retry**. > [accordion] Python 3.11+ (with pip or pipx) not found. > > Clawboo found neither pipx nor Python 3. Install Python 3.12 or pipx, then click **Retry**. > [accordion] The install fails with (from versions: none) > > pip found no `hermes-agent` release for your Python. That happens when the Python it used is older than 3.11 or newer than 3.13. Clawboo picks the newest Python 3.11 or later it finds, so a Python 3.14 wins over a 3.12 you also have. Install Hermes yourself with a supported Python, then click **Refresh** at the top of the Runtimes panel: > > ```bash theme={"theme":"github-dark"} > pipx install --python python3.12 'hermes-agent[anthropic]<1' > ``` > [accordion] Hermes installed, but its row still reads Not installed > > Hermes lands in Python's user folder for programs, which is often missing from the server's search path. Clawboo also looks in the usual user folders, so this is rare. If the install log says "Installed, but 'hermes' isn't on PATH or the known user-install dirs yet. Restart the server if it stays missing.", run `clawboo restart`. > [accordion] the 'anthropic' package is required > > This Hermes was installed without the `[anthropic]` extra, so it cannot run on an Anthropic key. Reinstall it with the extra: `pipx install --force 'hermes-agent[anthropic]<1'` for a pipx install, or `python3 -m pip install --user 'hermes-agent[anthropic]<1'` for a pip install. > [accordion] The sign-in page says device authorization is disabled > > Hermes's ChatGPT sign-in uses a one-time code, which some ChatGPT accounts turn off. Turn on device code authorization under ChatGPT **Settings** > **Security**, then retry the sign-in. > [accordion] My change to \~/.hermes/config.yaml has no effect > > Each agent uses its own copy, made once. Edit `~/.clawboo/runtimes/hermes/<agent id>/config.yaml`, or delete it so the next run copies your current config again. ## Check that it works 1. In **Settings** > **Runtimes**, the **Hermes** row reads **Connected**. 2. Open the team chat of a team with a Hermes member. Type `@`, pick that agent, and ask: "Say hello in one sentence." 3. You should see a reply under the agent's name once the run finishes. If the run fails, the chat shows "The run failed:" and the reason; look it up in [Troubleshooting](#troubleshooting). > [accordion] Under the hood > > * Each run is `hermes chat -q <prompt> -Q --yolo --accept-hooks [-m <model>] [--resume <session id>] [--provider <id>] [--continue]`. Clawboo sets `HERMES_HOME` to the agent's folder and puts the connected OpenRouter, Anthropic and OpenAI keys in its environment. > * `-Q` prints the reply on stdout and the session id on stderr. When the task runner picks a task back up on Hermes, Clawboo resumes the saved session with `--resume` (never in a newly created folder). To take a comment, it stops the run and starts it again with `--resume`, or with `--continue` when it does not know the session id yet. > * Hermes has its own task board; Clawboo does not sync it. Hermes works on Clawboo's board through the Tasks tools. > * Run a board task on Hermes directly with `POST /api/runtimes/hermes/run`; see the [Runtimes API](/reference/rest-api/runtimes). ## Related * [Choose a runtime](/runtimes/index): how Hermes compares with the other runtimes. * [Connect AI providers and runtimes](/using/providers-and-runtimes): keys, sign-ins and what Disconnect removes. * [What agents can do](/concepts/permissions): permissions and safety for every runtime. * [Budgets and spend](/using/budgets-and-spend): how each runtime's cost reaches your budgets. * [Memory](/concepts/memory): how Clawboo's shared memory works next to Hermes's own. # Choose a runtime Source: https://docs.claw.boo/runtimes/index Compare the five runtimes Clawboo runs agents on: what each needs, what it costs, how it treats files, comments and commands, and how to pick. A runtime is the program that runs an agent's work. It sends the agent's prompts to an AI model, runs the agent's tools, and reports back to Clawboo. Clawboo supports five runtimes. Every agent runs on one of them, and one team can mix them. Use this page to pick a runtime for a new agent or team, and to see what each one can and can't do. To connect one, see [Connect providers and runtimes](/using/providers-and-runtimes). ## Providers, models, runtimes and the Gateway Four words come up whenever you connect AI to Clawboo. Each names a different layer. | Layer | What it is | Examples | Where you choose it | | - | - | - | - | | **Provider** | The company, or the program on your computer, that runs AI models. A cloud provider bills you for what you use. | Anthropic, OpenAI, OpenRouter, Google, a local Ollama | **Settings > Providers**, a ChatGPT sign-in, or Ollama | | **Model** | The AI model an agent thinks with. Each provider offers several. | `claude-sonnet-5`, `gpt-5.4`, `llama3.2` | Per agent. Codex and Claude Code use their own default model | | **Runtime** | The program that runs the agent: its turns, its tools, its files. | Clawboo Native, OpenClaw, Claude Code, Codex, Hermes | Per agent, when you create it | | **OpenClaw Gateway** | OpenClaw's own server, which hosts OpenClaw agents. Only the OpenClaw runtime uses it. | `ws://localhost:18789` | **Settings > Runtimes > OpenClaw** | So one agent might run on Clawboo Native with the model `llama3.2` from your local Ollama, while its teammate runs on Claude Code with your Anthropic key. One key can serve several runtimes: an Anthropic key works for Clawboo Native, Claude Code and Hermes. ## Compare the runtimes | | Clawboo Native | OpenClaw | Claude Code | Codex | Hermes | | - | - | - | - | - | - | | **Best for** | Most teams; nothing to install | OpenClaw users, messaging channels | Coding with Anthropic's agent | Teams on a ChatGPT plan | An open-source agent that keeps its own skills | | **You need** | A provider key, or a local Ollama | A key, a ChatGPT sign-in or Ollama, and a supported Node.js | An Anthropic API key and the Claude Agent SDK | A ChatGPT sign-in | An OpenRouter, Anthropic or OpenAI key, or a ChatGPT sign-in; Python 3.11 to 3.13 | | **Installed by** | Built in | Clawboo (**Set up OpenClaw**) | Clawboo (**Install**) | Clawboo (**Install**) | Clawboo (**Install**) | | **Cost you see** | Exact for six models, estimated for the rest | Estimated | Reported by Claude Code | Estimated from token counts | Estimated from text length | | **Shared memory** | Its team's | Global only | Its team's | Its team's | Its team's | | **Own git worktree for a task** | Task runs on a repository | Never | Task runs on a repository | Task runs on a repository | Task runs on a repository | | **A comment on a running task** | Read before its next step | Read before its next step | Read before its next step | Read before its next step | The run pauses, reads it, then resumes | | **Agent files it reads** | `SOUL.md` | `SOUL.md`, `IDENTITY.md`, `AGENTS.md` | `SOUL.md` | `SOUL.md` | `SOUL.md` | | **Asks before running a command** | Every time, if you turn commands on | Per its Gateway policy | No | No | No | What the rows mean: * **Cost.** Estimates count toward your budgets the same way real costs do. A model your own Ollama serves costs nothing, on Clawboo Native or OpenClaw, so its runs are recorded at \$0 and read **free**. [Budgets and spend](/using/budgets-and-spend) explains how each runtime's spend is worked out. * **Worktrees.** A worktree is a separate checkout of a git repository for one task. No runtime gets one in team chat. Only a task run that names a repository does: one started through the REST API, or an agent Routine whose template names a repository. See [Worktrees and handoff](/concepts/worktrees-and-handoff). * **Agent files.** Clawboo Native uses `SOUL.md` as the agent's instructions; Claude Code, Codex and Hermes get it as a persona at the top of each prompt. On every runtime, `AGENTS.md` also draws the agent's links on the team graph. OpenClaw shows a `TOOLS.md` tab, but OpenClaw 2026.9 and later doesn't load that file. See [Agents](/using/agents). * **Commands.** A new OpenClaw agent asks before running a command it hasn't been allowed (**Ask for Unknown**). See [OpenClaw](/runtimes/openclaw#what-a-new-boo-is-allowed-to-run). > [!WARNING] > > Claude Code, Codex and Hermes run with their own sandboxes and approval prompts turned off. Nothing asks before they run a command or change a file, and they can read and change any file your user account can. A per-task worktree keeps their edits apart from your checkout, but it is not a sandbox. Read [What agents can do](/concepts/permissions) before you point them at work you care about. ## How to pick * **Start with Clawboo Native.** It is built in, so there is nothing to install. Connect one provider key, or a local Ollama to run with no key at all. Onboarding sets it up first. See [Clawboo Native](/runtimes/native). * **No API key, but a ChatGPT plan?** Choose **Sign in with ChatGPT** in onboarding. Your team then runs on [Codex](/runtimes/codex) and uses your plan instead of a per-token bill. See [Run Clawboo without an API key](/guides/no-api-key). * **Coding work in a repository?** [Claude Code](/runtimes/claude-code) and [Codex](/runtimes/codex) bring their own coding tools. Read the warning above first. * **Already use OpenClaw, or want its channels?** [OpenClaw](/runtimes/openclaw) agents keep their messaging channels and their own cron jobs on the Gateway, and join your teams in Clawboo. * **Want an agent that builds up skills over time?** [Hermes](/runtimes/hermes) keeps a home for each agent, where its skills and memory carry over between runs. * **Mix them.** When you deploy a team, each member has its own runtime picker in **Customize team**, and Boo Zero leads them all. An agent keeps the runtime it was created on, so choose before you deploy. To move work to another runtime, create a new agent there. ## Steering a running task Steering means getting a message to an agent while it is still working on a task. Open the task in the board's task drawer, a team chat's **Tasks** card, or the agent's **Tasks & Routines** card. Type your comment and click **Send**. The line beside **Send** tells you what will happen for that task, and once you send, a note under your comment says where it went. Unless the task is finished, your comment goes into the agent's mailbox, which every run reads when it starts. If a run is working on the task right now, Clawboo also passes the comment into that run: | Runtime | What happens to a comment on a running task | The line beside **Send** in the **Tasks** card | | - | - | - | | Clawboo Native | Read at the start of its next turn. A run that was about to finish takes one more turn to read it. | "… gets this now, before its next step." | | OpenClaw | Joins the running turn at its next step, without stopping a tool that is running. | "… gets this now, before its next step." | | Claude Code | Joins the running turn at its next step. | "… gets this now, before its next step." | | Codex | Joins the running turn at its next step. | "… gets this now, before its next step." | | Hermes | Clawboo stops the run and resumes the same session with your comment. A tool call in progress at that moment is lost; the run carries on. | "… pauses to read this, then carries on." | If the comment can't get into the run (the run ended a moment earlier, or the runtime refused it), nothing is lost: the agent reads it from its mailbox at its next run, and the note under the comment says so. When it does land, the mailbox copy is marked as delivered, so the agent doesn't read it twice. A comment on a task that is not running behaves by the task's status: a task that waits on you, or waits in **To do**, starts again for it, and a finished task does not get it at all. Each case is in [Steer a running task](/using/board#steer-a-running-task). > [accordion] Under the hood: how each runtime takes a comment > > A person's comment is posted with `POST /api/board/:taskId/comments` and `authorType: 'user'`, and the answer's `delivery` field says where it went (see the [Board API](/reference/rest-api/board)). Each runtime declares how it takes input mid-run in its `steering` capability: `live` (Clawboo Native, OpenClaw, Claude Code and Codex) or `interrupt-resume` (Hermes). > > * **Clawboo Native** queues the comment into the conversation as a user message for its next turn. > * **OpenClaw** gets it through `chat.send` with `queueMode: 'steer'` over the server's own Gateway connection. If the run ends with the steer still unread, Clawboo cancels it (`chat.abort`) and the comment waits in the mailbox. > * **Claude Code** gets a user message pushed into the SDK's streaming input. > * **Codex** gets `turn/steer` on the app-server's in-progress turn. > * **Hermes** has no way in once a run starts, so Clawboo stops `hermes chat` and resumes the same session with the comment. The orchestrator sees one run that carries on, so the task stays in progress with the same agent. > > Small team notices, such as "a task you delegated just finished", use the same channel but never keep a finishing run going and never stop a run. ## Connect a runtime Open **Settings** (Cmd/Ctrl + ,) and choose **Runtimes** under **Workspace**. Each runtime has a row with its status (**Not installed**, **Needs key**, **Needs login** or **Connected**) and one button: **Install**, **Connect**, **Sign in** (Codex) or **Manage** once it is connected. OpenClaw's row offers **Set up**, or **Reconnect** when its Gateway is offline. ![The Runtimes panel in Settings, listing OpenClaw, Codex, Claude Code, Hermes and Clawboo Native with their status and buttons](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/runtimes-panel.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=60bd52b75ba67d2a4e3e3729d30b1e6d) The full walkthrough, including what **Disconnect** removes, is in [Connect providers and runtimes](/using/providers-and-runtimes). > [accordion] Under the hood: runtime ids and the REST API > > The runtime ids are `clawboo-native`, `openclaw`, `claude-code`, `codex` and `hermes`. The `/api/runtimes` routes cover the four runtimes other than OpenClaw, so `/api/runtimes/openclaw/...` answers `404`. OpenClaw is set up through `/api/system/*` and runs over the Gateway connection. See the [Runtimes API](/reference/rest-api/runtimes) and the [System API](/reference/rest-api/system). > > Each runtime's adapter declares its capabilities (streaming, MCP, worktrees, steering, its runtime class and its home directory). The full matrix is in [The runtime adapter](/internals/runtime-adapter). ## Related * [Clawboo Native](/runtimes/native): the built-in runtime, its providers and local Ollama * [OpenClaw](/runtimes/openclaw): set up the Gateway, and what Clawboo runs and writes there * [Connect providers and runtimes](/using/providers-and-runtimes): install, connect, sign in and disconnect * [What agents can do](/concepts/permissions): each runtime's access to your files, commands and network * [Budgets and spend](/using/budgets-and-spend): how each runtime's cost reaches your budgets # Clawboo Native Source: https://docs.claw.boo/runtimes/native The built-in runtime: agents run on a provider key or a local Ollama with nothing to install. Providers, default models, tools, costs and limits. Clawboo Native is the runtime built into Clawboo. It runs each agent's conversation inside the Clawboo server and calls your AI provider directly, so there is nothing to install and no Gateway to run. Onboarding puts your first team on it. * **Best for:** most teams. It works with ten cloud providers or a free local model, and it reports cost after every turn. * **You need:** an API key for one of the providers below, or a local [Ollama](https://ollama.com) with a chat model pulled. ## Set up Pick one way to give Clawboo Native a model. You can add more providers later. > [tab] Provider key > > **1. Get an API key** > > Create a key on your provider's site. Most provider rows on the **Providers** page have a **Get a key** link. > > **2. Save it in Clawboo** > > Open **Settings > Providers**, click **Connect** on the provider's row, paste the key and click **Save**. Clawboo checks the key with the provider before it saves it. If the check fails, the reason shows under the field, and **Save anyway** stores the key without the check (useful when the provider just can't be reached). > > In onboarding, the **Connect your AI provider** step does the same thing. > > **3. Check the runtime** > > Open **Settings > Runtimes**. **Clawboo Native** reads **Connected**. Click **Manage**, and your provider is listed under **LLM providers**. > [tab] Local Ollama (no key) > > **1. Pull a model** > > Install Ollama, start it (`ollama serve`) and pull a chat model that can use tools: > > ```bash theme={"theme":"github-dark"} > ollama pull llama3.2 > ``` > > **2. Turn Ollama on** > > Open **Settings > Runtimes** and click **Connect** (or **Manage**) on **Clawboo Native**. On the **Ollama** line ("Local · no key needed"), click **Use**. Clawboo checks that Ollama answers first; **Use anyway** skips the check. > > In onboarding, picking the **Ollama** card on the connect step does the same thing. > > **3. Check the model** > > Under **LLM providers**, open the **Ollama** row and click **Make default** if Ollama isn't your default yet. Its **Default model** list then shows the chat models your Ollama has pulled. Saving a key on the **Providers** page also copies it, in plain text, into OpenClaw's `~/.openclaw/.env`, so an OpenClaw Gateway can use it too. See [Connect providers and runtimes](/using/providers-and-runtimes) for where keys are kept. ## Providers and default models Clawboo Native works with eleven providers: ten with an API key, plus a local Ollama. The **Providers** page also lists MiniMax, NVIDIA, Hugging Face and Venice; their keys reach OpenClaw only. These are the built-in models for an agent whose model you leave on **Default** when you deploy a team. A team's leader, and a native Boo Zero, get the leader model; everyone else gets the specialist model. On your default provider, the leader runs the model you picked instead (see the next section). | Provider | Key name | Leader model | Specialist model | | - | - | - | - | | Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-5` | `claude-haiku-4-5` | | OpenAI | `OPENAI_API_KEY` | `gpt-5.4` | `gpt-4o-mini` | | OpenRouter | `OPENROUTER_API_KEY` | `anthropic/claude-haiku-4.5` | `openai/gpt-4o-mini` | | Ollama (local) | none | A model you have pulled (`llama3.2` first) | Same as the leader | | Google | `GEMINI_API_KEY` | `gemini-2.0-flash` | `gemini-2.0-flash` | | xAI | `XAI_API_KEY` | `grok-2-latest` | `grok-2-latest` | | Groq | `GROQ_API_KEY` | `llama-3.3-70b-versatile` | `llama-3.1-8b-instant` | | Mistral | `MISTRAL_API_KEY` | `mistral-large-latest` | `mistral-small-latest` | | Together | `TOGETHER_API_KEY` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | | Cerebras | `CEREBRAS_API_KEY` | `llama-3.3-70b` | `llama-3.3-70b` | | Moonshot | `MOONSHOT_API_KEY` | `moonshot-v1-32k` | `moonshot-v1-8k` | The key name is where Clawboo stores the key, and the variable it reads if you export the key in your shell instead. Keys look like `sk-ant-…` (Anthropic), `sk-…` (OpenAI), `sk-or-…` (OpenRouter), `AIza…` (Google), `xai-…` (xAI), `gsk_…` (Groq) and `csk-…` (Cerebras). ### Which provider a new agent gets When an agent's model is left on **Default**, Clawboo picks its provider in this order: 1. **Your default provider, if it is set up.** That is the provider and model you chose in onboarding, or later with **Make default** (open a provider row under **Settings > Runtimes > Clawboo Native > Manage**). The leader runs the model you picked, and specialists run that provider's specialist model. On Ollama, every agent runs the model you picked. 2. **Otherwise, the first provider that is set up**, in this order: Anthropic, OpenAI, OpenRouter, Ollama, Google, xAI, Groq, Mistral, Together, Cerebras, Moonshot. The agent gets that provider's models from the table above. 3. **With nothing set up**, the agent is created on Anthropic, and its first run reports the missing key. A key in your shell, or in OpenClaw's `~/.openclaw/.env`, never outranks the default you picked. The deploy dialog names the model each row will get (`Default · <model>`), so you can check before you deploy. Changing the default model later also moves a native Boo Zero onto it. Other agents keep their model; change one from its own page. ### Local Ollama * Clawboo reaches Ollama at `http://localhost:11434/v1`. To use another address, set `OLLAMA_BASE_URL` to its OpenAI-compatible base, `/v1` included. * The model lists show only the chat models your Ollama has pulled. Embedding models such as `nomic-embed-text` are left out, and a model that can't use tools is marked `(can't use tools)`. Agents call tools on almost every turn, so pick one that can. * An agent created without a model pick gets your default Ollama model if Ollama has it. If not, it gets the most recently pulled model that can use tools. The leader and the specialists get the same model, so Ollama doesn't swap models in and out of memory between turns. * A default model that Ollama doesn't have, for example one you removed, shows as `<model> (not pulled)`, with its `ollama pull` command and a **Check again** button. * When Ollama is your only provider, a native Boo Zero runs on it too. * **Disconnect Ollama** (on the Ollama row) stops Clawboo Native from using Ollama and leaves your keys alone. Agents on an Ollama model can't run until you click **Use** again. ## What Clawboo Native can do A native agent acts only through the tools Clawboo gives it. Which tools it has depends on where it runs. | Tool | What it does | When the agent has it | | - | - | - | | Peer room | Posts and reads notes in its team's [peer room](/concepts/peer-chat), as itself. The dashboard doesn't show it. | Team chat and team tasks | | Memory | Reads and saves shared [memory](/concepts/memory): its team's in team runs, the global memory in 1:1 chat | Every run | | Board | Reads tasks on the board. Clawboo itself makes the board changes. | Every run | | Connectors | Uses the [connectors](/using/connectors) you grant it | Every run | | `delegate` | Hands a task to a teammate by name. Clawboo turns each call into a board task. | Team chat | | `read_file`, `write_file`, `list_files` | Reads and writes files inside the run's folder, and nowhere else, not even through a symlink | Every run | | `run_command` | Runs one program after you approve it | Every run, once you allow it, not on Windows | The run's folder is the task's own git worktree when the task run has one; see [Worktrees and handoff](/concepts/worktrees-and-handoff). Everywhere else it is the team's folder, `~/.clawboo/workspaces/<team id>`, shared with every teammate: in team chat, on the tasks agents delegate there, on research and review task runs, and in the agent's 1:1 chat. In its 1:1 chat, an agent on no team, such as Boo Zero, uses `~/.clawboo/workspaces/_no-team`. For how this compares with the other runtimes, see [What agents can do](/concepts/permissions). ### Running commands `run_command` is off for every agent until you allow it. To allow it, open the agent's page, then **Details**, then the **Permissions** tab. Under **Running commands**, switch on **Let this Boo ask to run commands** and confirm with **Allow it to ask**. * **You approve every command, and nothing is remembered.** Each one raises a card that reads "*agent* wants to run a command on this computer." with the command and the folder. **Run it** approves it. There is no **Always** option. * **One program, no shell.** The agent can't use pipes, redirects or shell operators. Programs that run other programs (shells, interpreters, package runners and similar) are refused before you're asked. * **It runs as you.** The command runs in the run's folder (the team's folder or the task's worktree), under your user account, with network access. Your API keys are not passed to it. * **Limits per run:** the agent can ask about 10 commands. Each card waits 10 minutes for an answer, a command is stopped after 5 minutes, and output is cut at 64 KiB. * **A refusal ends it.** If you decline, or nobody answers in time, the agent can't ask again in that run. > [!WARNING] > > The list of refused programs is a speed bump, not a sandbox: some allowed programs can still reach a shell. You reading each card is the safeguard. Check the command and the folder before you click **Run it**. See [Approvals](/using/approvals) for answering a card, and [Command permissions](/using/command-permissions) for the OpenClaw equivalent. ## Costs Clawboo Native reports the cost of every turn: * **Six models have an exact price:** `claude-haiku-4-5`, `claude-sonnet-4-6`, `gpt-4o`, `gpt-4o-mini`, `openai/gpt-4o-mini` and `anthropic/claude-haiku-4.5`. Clawboo records their cost at list price. * **Every other model is estimated**, including the default leader models `claude-sonnet-5` and `gpt-5.4`. Clawboo prices the turn's token counts from its rate table and records the result as an estimate. A model missing from the table is priced at USD 3 per million input tokens and USD 15 per million output tokens. * **Local Ollama models are free.** A turn your own Ollama serves is recorded at \$0, charges no budget, and its tasks read **free** on the Board. Ollama's cloud models (a `-cloud` tag) run on ollama.com and are estimated like any other unpriced model. Estimates count toward your budgets. Budgets only warn you unless you set a hard cap. Turns with an exact price are charged as they happen, so a hard cap can stop a native run partway through. Estimated turns are charged when the run ends, in team chat and in 1:1 chat alike. See [Budgets and spend](/using/budgets-and-spend). ## Disconnect Open **Settings > Runtimes > Clawboo Native > Manage**. You can remove one provider or the whole runtime: * **Disconnect *Provider*** (for example **Disconnect OpenAI**) on an open provider row removes that provider's key from Clawboo's vault and from OpenClaw's `~/.openclaw/.env`. Your other keys stay. * **Disconnect Ollama** stops Clawboo Native from using Ollama. Your keys stay. * **Disconnect**, at the bottom of **Manage**, disconnects Clawboo Native itself. > [!WARNING] > > The bottom **Disconnect** deletes all ten provider keys from Clawboo's vault, not one, and turns Ollama off. Clawboo keeps a single copy of each key, so Claude Code and Hermes lose the keys they shared with Clawboo Native. Until you connect Clawboo Native again (for example with **Use** on a provider row), a key in your shell or in `~/.openclaw/.env` doesn't count for it. To remove one key, use its provider row instead. ## Limits * **Files only in the run's folder.** File tools work only inside the team's folder, where a 1:1 chat also starts, or the task's worktree. * **No commands on Windows.** `run_command` is never offered there. * **16 turns per run.** A run ends after 16 turns (model calls). * **No ChatGPT plan.** A ChatGPT subscription runs agents on [Codex](/runtimes/codex), not on Clawboo Native. * **Ten keyed providers.** Keys for MiniMax, NVIDIA, Hugging Face and Venice don't work with Clawboo Native. ## Troubleshooting > [accordion] Clawboo Native has no provider key connected > > The full message is `Clawboo Native has no provider key connected. Open Settings → Runtimes → Clawboo Native to connect a provider.` > > **What it means:** the provider this agent runs on has no key Clawboo can use. Clawboo Native can still read **Connected** when another provider has a key. The agent's page shows **No provider key**, or **Disconnected** when no provider is set up at all, next to **Set up in Runtimes →**. > > **Fix:** connect a key for that provider on the **Providers** page, or open the agent's page and pick a model from a provider you have connected. An agent on Ollama shows this message after **Disconnect Ollama**; click **Use** on the Ollama line to turn it back on. > [accordion] Ollama doesn't have the model yet > > The chat shows `Ollama doesn't have <model> yet, so this run couldn't start. Pull it with "ollama pull <model>", or pick a model you have pulled in this agent's settings.` > > **Fix:** run the `ollama pull` command it gives, or pick a pulled model on the agent's page. After a pull, **Check again** refreshes the model lists. > [accordion] The model can't use tools > > The chat shows `<model> can't use tools, and this agent needs them. Pick a model that can in this agent's settings, or pull one with "ollama pull llama3.2".` > > **Fix:** pick a model that isn't marked `(can't use tools)`, or pull one that can. > [accordion] Invalid API key, or Could not reach the provider > > When you save a key, Clawboo checks it with the provider. `Invalid API key.` means the provider refused it: copy the key again from the provider's site. `Provider returned <status>.` is another refusal. `Could not reach <provider>.` (or `Could not reach <provider> (timed out).`) means the check got no answer. If you are sure the key is right, **Save anyway** stores it without the check. > [accordion] Ollama isn't answering on this machine > > `Ollama isn't answering on this machine. Start it (ollama serve) and try again.` appears when you click **Use** on the Ollama line and nothing answers. Start Ollama, or check `OLLAMA_BASE_URL`, then click **Use** again. **Use anyway** turns Ollama on without the check. > [accordion] The agent never offers to run a command > > `run_command` exists only when both of these hold: **Let this Boo ask to run commands** is on for that agent, and the server is not on Windows. A run that started before you switched it on doesn't have it. If the agent says a command was refused, the program was on the refused list, or a command was already declined in that run. > [accordion] Clawboo Native reads Connected though you never connected it > > A key exported in the shell that started Clawboo, or present in OpenClaw's `~/.openclaw/.env`, counts as connected. So does an `OLLAMA_BASE_URL` you set, unless you turned Ollama off. After you disconnect Clawboo Native (see the warning above), only keys you connect again count. ## Check it works **1. Check the runtime** Open **Settings > Runtimes**. **Clawboo Native** reads **Connected**. **2. Send a message** Open an agent that runs on Clawboo Native from the agent column, type a short message in its chat and send it. **3. Read the reply** You should see a reply stream in. If an error appears instead, find it under [Troubleshooting](#troubleshooting). > [accordion] Under the hood > > * **Where it runs.** Inside the Clawboo server process. Anthropic models go through the Anthropic SDK. Every other provider goes through the OpenAI SDK with that provider's base URL; Ollama's is `OLLAMA_BASE_URL`, default `http://localhost:11434/v1`. > * **A run.** Up to 16 turns; each turn is one model call plus the tool calls it asks for, and each turn reports its own cost. > * **Fallback providers.** An agent's config can list fallback providers (through the API). If a provider fails with an auth, rate-limit, overload or network error before it has produced any output, the next one is tried. A provider that answers is kept for the rest of the conversation. > * **Saved conversations.** Each run's transcript is saved under `~/.clawboo/runtimes/clawboo-native/<agent>/sessions/` (the folder follows `CLAWBOO_HOME`). > * **Context window.** Clawboo Native declares 200,000 tokens for every provider, set high on purpose. A long task run moves to a fresh session late rather than early, and a provider's own context-limit error ends the run cleanly. > * **Agent config.** Each agent's provider, model and tool switches are stored as JSON in the settings table (`native-agent-config:<agentId>`) and checked on every load. A corrupt one falls back to the defaults: Anthropic, `claude-haiku-4-5`, read-only board access. > * **REST.** Connecting, key checks and disconnecting are in the [Runtimes API](/reference/rest-api/runtimes). The command switch is `GET` and `POST /api/agents/:agentId/shell`. ## Related * [Choose a runtime](/runtimes/index): how Clawboo Native compares with the other runtimes * [Connect providers and runtimes](/using/providers-and-runtimes): keys, where they are stored, and what Disconnect removes * [What agents can do](/concepts/permissions): what each runtime can touch on your computer * [Budgets and spend](/using/budgets-and-spend): warn-only and hard-cap budgets, and estimated costs * [Run Clawboo without an API key](/guides/no-api-key): a full team on a local Ollama # OpenClaw Source: https://docs.claw.boo/runtimes/openclaw Run OpenClaw agents in Clawboo: set up the OpenClaw Gateway from the dashboard, approve this device, and see what Clawboo writes, runs and costs. [OpenClaw](https://docs.openclaw.ai) is a separate agent platform. Its agents live in a long-running server called the **OpenClaw Gateway**, where they keep their own workspaces, messaging channels and cron jobs. Clawboo connects to a Gateway on your computer, shows its agents next to your others, and runs them in team chat, on the board and in Routines. * **Best for:** people who already use OpenClaw, or who want agents that also answer on OpenClaw's messaging channels (set up in OpenClaw itself). * **You need:** a Node.js version that OpenClaw supports, and a credential it can use: a provider key, a ChatGPT sign-in or a local Ollama. Clawboo installs and starts the Gateway for you. ## Before you begin * **Node.js in OpenClaw's range:** the setup installs the newest 2026.9 release of OpenClaw. Since 2026.9.34 that release needs 24.22.1 or later in the 24 line, or 26.12.1 and later (`>=24.22.1 <25 || >=26.12.1`). Node 22 and Node 25 can no longer run it, even though Clawboo itself runs on Node 22.12 and later. The setup doesn't check, so run `node --version` first. See OpenClaw's [Node.js guide](https://docs.openclaw.ai/install/node). * **`npm` on your `PATH`.** The setup installs OpenClaw with `npm install -g openclaw@~2026.9`. * **A credential OpenClaw can reuse:** a provider key you connected in Clawboo, a ChatGPT sign-in, or the local Ollama you set up for [Clawboo Native](/runtimes/native). With none of these, the setup asks you for a key. You don't need OpenClaw installed already. If it is, the setup keeps your install, fills in only the settings that are missing, and uses a Gateway that is already running. ## Set up OpenClaw **Set up OpenClaw** is one button. It reuses a credential you already connected, installs OpenClaw if needed, starts the Gateway and connects to it. The same setup runs from **Settings > Runtimes** and from onboarding's **Add more runtimes** step. **1. Start the setup** Open **Settings > Runtimes**. On the **OpenClaw** row ("Run OpenClaw agents on a local Gateway"), click **Set up**, then **Set up OpenClaw**. In onboarding, the **Add more runtimes** step has the same row. The row's footer reads **In setup**, and the panel shows **Preparing OpenClaw…**. **2. Let Clawboo reuse a credential** Clawboo looks for a credential in this order and uses the first one it finds: 1. **A provider key saved in Clawboo** (on the **Providers** page, in onboarding or on a runtime's card) or already in OpenClaw's `~/.openclaw/.env`. A key that is only exported in your shell doesn't count. With several keys, it takes the first in this order: Anthropic, OpenAI, Google, OpenRouter, xAI, Groq, Mistral, Moonshot, MiniMax, Together, NVIDIA, Hugging Face, Cerebras, Venice. 2. **A ChatGPT sign-in that OpenClaw already holds.** 3. **A ChatGPT sign-in that only Codex holds.** The panel asks you to sign OpenClaw in too (next step). 4. **The local Ollama you set up for Clawboo Native.** 5. **Nothing.** The panel asks you for a key (next step). It then writes OpenClaw's configuration. It sets the Gateway to local mode and otherwise fills in only what is missing, so your default model and other settings stay. See [What Clawboo writes to OpenClaw](#what-clawboo-writes-to-openclaw). **3. Add a key or sign in, if asked** **Nothing connected.** The panel reads "Connect a provider key for OpenClaw to use. It powers the local Gateway." Pick **Anthropic**, **OpenAI** or **OpenRouter**, paste the key and click **Connect & set up** (or press Enter). Clawboo saves the key the way the **Providers** page does, then runs the setup again. **Codex is signed in, OpenClaw isn't.** Each runtime keeps its own ChatGPT sign-in, so OpenClaw needs one too. The panel installs OpenClaw first if it is missing, then shows **Sign in with ChatGPT**. Click it: your browser opens on OpenAI's sign-in page, with no code to type, and the setup carries on once the sign-in is saved. **Re-check** runs the setup again, and **Use an API key instead** switches to the key prompt. On Windows, the panel gives you the command to run in your own terminal instead. **Ollama doesn't have the model.** If your Ollama is missing OpenClaw's default model, or isn't running, the setup stops before it starts the Gateway. The panel names the model it couldn't find and shows the `ollama pull` command for it. Run that command, then click **Check again**. **Continue anyway** starts the Gateway regardless, but OpenClaw agents can't answer until the model is there. **4. Install OpenClaw and start the Gateway** If OpenClaw isn't installed, the panel shows **Installing OpenClaw…** and runs `npm install -g openclaw@~2026.9`, with npm's output in a log. Then it shows **Starting the Gateway…**. A Gateway that is already running on OpenClaw's port is used as it is. Otherwise Clawboo starts one and waits up to 60 seconds for it to answer. If OpenClaw asks for `openclaw doctor --fix` before it will start (this can happen after an OpenClaw upgrade), Clawboo runs it once and starts the Gateway again. The log fills with install and Gateway lines, then the panel moves on to connecting. **5. Approve this device** OpenClaw asks you to approve each new device once before it can connect, so the panel shows an **Approve this device** card. Click **Approve this device**. The card reads "Approved. Reconnecting…" and connects again. You should see **OpenClaw connected**. The panel closes about a second later, and the OpenClaw row reads **Connected**. ![Set up OpenClaw on onboarding's Add more runtimes step: the install log streams while the Gateway starts](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/openclaw-inline-setup.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=6df616565286efe25b68f8a3d3fc7426) ### If a step fails The panel replaces its progress with the error and a **Retry** button. **Retry** runs the whole setup again: it skips the install once OpenClaw is installed, and joins a Gateway that is still starting instead of launching a second one. If the reconnect after approval fails, or OpenClaw still says the device isn't approved, the error reads "OpenClaw still needs this device approved. Retry to approve it again." **Retry** then brings back a fresh approval card. **Cancel** closes the panel at any point before it connects. An install that has already started still finishes in the background. The exact errors and their fixes are under [Troubleshooting](#troubleshooting). ### Approve from a terminal instead The approval card runs two OpenClaw commands for you. You can run them yourself: ```bash theme={"theme":"github-dark"} openclaw devices approve --latest openclaw devices approve <requestId> ``` The first prints the id of the waiting request; the second approves it. Then run **Set up OpenClaw** or **Reconnect OpenClaw** again so Clawboo connects. See OpenClaw's [devices command](https://docs.openclaw.ai/cli/devices). ### Use your ChatGPT plan OpenClaw can run on a ChatGPT subscription instead of an API key, through its own `openai-codex` provider. It needs its own sign-in, separate from Codex's: * During setup, or later under **Manage > ChatGPT subscription**, click **Sign in with ChatGPT**. This needs Codex connected first; until then the row reads "Connect Codex first". * Or run OpenClaw's own login in a terminal: `openclaw models auth login --provider openai-codex`. Use it rather than `openclaw onboard`, which can reset your channels, memory and cron jobs. With the sign-in in place, the setup writes no key, and sets OpenClaw's default model to `openai-codex/gpt-5.5` if none is set. OpenClaw agents, Codex agents, Hermes on a ChatGPT sign-in and your own Codex use all share your plan's limits. ## Default models When OpenClaw has no default model yet, the setup picks one for the provider it reused. It never replaces a default you already have. | Provider | Default model | | - | - | | Anthropic | `anthropic/claude-sonnet-4-5` | | OpenAI | `openai/gpt-5.4` | | Google | `google/gemini-2.0-flash` | | OpenRouter | `openrouter/anthropic/claude-sonnet-4-5` | | xAI | `xai/grok-3` | | Groq | `groq/llama-3.3-70b-versatile` | | Mistral | `mistral/mistral-large-latest` | | Moonshot | `moonshot/kimi-k2.5` | | MiniMax | `minimax/MiniMax-M2.5` | | Together | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` | | NVIDIA | `nvidia/llama-3.1-nemotron-70b-instruct` | | Hugging Face | `huggingface/deepseek-ai/DeepSeek-R1` | | Cerebras | `cerebras/zai-glm-4.7` | | Venice | `venice/llama-3.3-70b` | | Local Ollama | `ollama/` plus a model your Ollama has pulled (`llama3.2` first) | | ChatGPT sign-in | `openai-codex/gpt-5.5` | Change it under **Manage > Default model**, which every OpenClaw agent uses unless it has its own model. An agent's own model is set on its page. For OpenClaw's model settings, see [Model providers](https://docs.openclaw.ai/concepts/model-providers) and, for Ollama, [Ollama](https://docs.openclaw.ai/providers/ollama). ## What a new Boo is allowed to run OpenClaw's Gateway decides whether an OpenClaw agent may run a command, from each agent's command policy. Clawboo sets that policy when it creates an agent: * **A new OpenClaw agent gets Ask for Unknown.** It asks you before running a command it hasn't been allowed. (A caller of the REST API can pass another posture, and Clawboo writes that one instead.) * **A refused policy stops the creation.** If the Gateway won't take the policy, Clawboo removes the half-made agent and **Create Boo** shows "Could not set this agent's command permissions on the Gateway, so it was not created", followed by the reason. * **An agent made outside Clawboo** has no policy from Clawboo. It follows the Gateway's own defaults, which let it run commands without asking unless you changed them. * **Nothing re-applies a posture later.** Clawboo writes it when it creates the agent, and when you change **Command Execution** on the agent's **Permissions** tab. A change made outside Clawboo stays. Answering **Always** to a command card adds a standing permission. The agent's **Permissions** tab lists those under **Commands this Boo can run without asking**, where you can revoke one. See [Command permissions](/using/command-permissions) and OpenClaw's [exec approvals](https://docs.openclaw.ai/tools/exec-approvals). ## Manage OpenClaw Once OpenClaw is connected, its row reads **Connected** and offers **Manage**: | In **Manage** | What it does | | - | - | | **Gateway** | Shows **Running** or **Stopped**, the port and the uptime, with **Restart** (or **Start** if it stopped) | | **LLM providers** | Lists the keys saved on the **Providers** page, each with its own **Disconnect** | | **ChatGPT subscription** | OpenClaw's own ChatGPT sign-in: **Sign in with ChatGPT**, "Connect Codex first", or **Connected** | | **Default model** | The model every OpenClaw agent uses unless it has its own | | **MCP attach config** | The snippet for attaching Clawboo's MCP servers by hand; see [MCP servers](/operating/mcp-servers) | | **Re-check** | Checks the connection again | | **Disconnect** | Stops the local Gateway ("Stop the OpenClaw gateway?"). OpenClaw agents go offline until it starts again | | **Details** | A read-only diagnostics view | When OpenClaw is set up but its Gateway is offline, the row shows that and offers **Reconnect**. **Reconnect OpenClaw** runs the same setup as above: it reuses your configuration and starts the Gateway. For OpenClaw's own service commands, see [the gateway command](https://docs.openclaw.ai/cli/gateway). ## What Clawboo writes to OpenClaw Clawboo changes OpenClaw's files in these places. `~/.openclaw` is OpenClaw's state folder (`OPENCLAW_STATE_DIR` moves it), and `~/.clawboo` is Clawboo's (`CLAWBOO_HOME` moves it). | Where | What Clawboo writes | When | | - | - | - | | `~/.openclaw/.env` | A Gateway token (`GATEWAY_AUTH_TOKEN`) when there is none yet; the provider key it reused, under its usual name such as `ANTHROPIC_API_KEY`; `OLLAMA_API_KEY=ollama-local` for Ollama, a marker rather than a secret | **Set up OpenClaw**, and every key you save on the **Providers** page | | `~/.openclaw/agents/<agent>/agent/auth-profiles.json` | The same provider key, for each OpenClaw agent that exists | Same as above | | `~/.openclaw/openclaw.json` | Local Gateway mode (`gateway.mode: local`). Only where missing: port 18789, token authentication, a default model, and agent-to-agent messaging. With `OLLAMA_BASE_URL` set, an Ollama provider entry that points at it, because OpenClaw doesn't read that variable | **Set up OpenClaw** | | `openclaw.json`, through the Gateway | Clawboo's MCP servers, and `sessions_spawn` and `sessions_yield` on the denied tools list (see [Clawboo tools inside OpenClaw](#clawboo-tools-inside-openclaw)) | Each time the server connects, skipped when nothing changed | | `openclaw.json` | The default model (with its context window, when Clawboo knows it), an agent's own model, and the default command approval for all agents | When you change **Default model** in **Manage**, an OpenClaw agent's model, or **Command Approval** in **Settings > System** | | The Gateway's command policies | Each agent's **Command Execution** posture | When Clawboo creates an agent, and when you change its posture | | The agent's workspace, `~/.openclaw/workspace-<name>` | `SOUL.md`, `IDENTITY.md`, `TOOLS.md` and `AGENTS.md` (see [Agent files](#agent-files)) | When Clawboo creates the agent, and when you edit a file | | `~/.clawboo/settings.json` | The Gateway address, with the port from `openclaw.json`, and the Gateway token | **Set up OpenClaw**, and when Clawboo reads a new token from `.env` | > [!WARNING] > > Provider keys in `~/.openclaw/.env` and in each agent's `auth-profiles.json` are stored in plain text, because that is where OpenClaw reads them. Clawboo writes both files readable only by your user account (mode `0600`), but anything that runs as you can read the keys. Clawboo's own copy stays in its encrypted vault. To back up or reset these folders, see [Where Clawboo keeps data](/operating/data-and-state). For OpenClaw's own settings, see its [configuration guide](https://docs.openclaw.ai/gateway/configuration). ## How Clawboo talks to the Gateway Clawboo keeps two connections to the Gateway. Both use a device identity that you approved once during setup, stored in `~/.clawboo/proxy-device-identity.json`. That identity only proves Clawboo to the Gateway; it does not protect the dashboard itself (see [Self-host securely](/guides/self-host-securely)). **The server's own connection** stays open whenever the Gateway runs, with or without a browser tab. Over it, Clawboo: * runs every OpenClaw agent turn in team chat, including the tasks those agents are given; * runs OpenClaw agent Routines, and lists the Gateway's own cron jobs on the Routines page, where you can turn them on or off, run them or delete them; * keeps its copy of your OpenClaw agents up to date, so they still show when the Gateway is down; * receives command approval requests, so they wait in Clawboo's approvals even with no tab open; * records activity and token use from OpenClaw sessions, including ones Clawboo didn't start, such as a channel message or one of an agent's own cron jobs; * registers its MCP servers in the Gateway's configuration, and passes your comments into running tasks. **The browser's connection** goes through the Clawboo server, which adds the saved Gateway token, so the server never sends that token to the browser. It carries your 1:1 chats with an OpenClaw agent and streams replies as they arrive. **When the Gateway is down**, your OpenClaw agents still show (from Clawboo's last copy) but can't run. You can't create an OpenClaw agent or edit its files: **Create Boo** makes a Clawboo Native agent instead, and says so. Deleting an OpenClaw agent is remembered and sent to the Gateway when it reconnects. If you also have agents on other runtimes, the dashboard loads as usual with a banner, "OpenClaw Gateway is offline", and a **Reconnect** button. For the protocol detail, see [Gateway and events](/internals/gateway-and-events). ## Clawboo tools inside OpenClaw Each time the server connects, Clawboo registers three of its MCP servers in the Gateway's configuration, so every OpenClaw agent can use them. One Gateway configuration serves every OpenClaw agent, so Clawboo can't tell which agent is calling, and each server is limited accordingly: | Server | What OpenClaw agents can do | The limit on OpenClaw | | - | - | - | | Memory | Read and save shared [memory](/concepts/memory) | Global memory only: no team's memory, and nothing saved to one team | | Tasks | Create, update, block and link board tasks | Can't claim or assign a task | | Tools | Use apps reached through Composio | Connectors you give an agent on the graph don't reach it, and every Composio action asks you first ([why](/using/connectors#openclaw-agents-and-connectors)) | TeamChat isn't registered. OpenClaw agents take part in team chat through Clawboo, which posts each of their turns under the right name. Clawboo also adds `sessions_spawn` and `sessions_yield` to OpenClaw's denied tools, so agents hand work to their team instead of spawning sub-agents of their own. An OpenClaw leader hands work on by calling OpenClaw's `sessions_send` tool, or with a `<delegate to="@Name">…</delegate>` block in its reply; Clawboo turns either one into a board task. ## Agent files An OpenClaw agent's files live in its workspace on the Gateway. Clawboo writes them when it creates the agent, and when you edit them on the agent's page (**Details**, then the file tabs). Editing needs the Gateway running. | File | What it is for | | - | - | | `SOUL.md` | Persona, tone and boundaries. OpenClaw loads it every session. | | `AGENTS.md` | Operating instructions, loaded every session. For a team's agents, Clawboo writes the team roster and how to work with the team here. It also adds web-browsing instructions to every OpenClaw agent. Put tool notes in its `## Tools` section. | | `IDENTITY.md` | The agent's name, vibe and emoji. | | `TOOLS.md` | Clawboo still shows this tab, but OpenClaw 2026.9 and later doesn't load it. Move its content into `AGENTS.md`; OpenClaw's `openclaw doctor --fix` merges an existing `TOOLS.md` there for you. | Clawboo neither creates nor reads `HEARTBEAT.md`, `USER.md` or `MEMORY.md`; the file editor shows them only when they already have content. To schedule an OpenClaw agent's own work, use OpenClaw's [cron jobs](https://docs.openclaw.ai/automation/cron-jobs) or a Clawboo [Routine](/using/routines). OpenClaw's [agent workspace guide](https://docs.openclaw.ai/concepts/agent-workspace) covers every file. ## Comments on a running task A comment you leave on a task an OpenClaw agent is working on joins its running turn at the next step, without stopping a tool that is running. If the run can't take it, the comment waits for the agent's next run. See [Steering a running task](/runtimes/index#steering-a-running-task). ## Costs OpenClaw's Gateway reports no cost to Clawboo, so every OpenClaw figure you see is Clawboo's estimate: * **Budgets** are charged an estimate for each OpenClaw turn in team chat, and for each OpenClaw Routine run, whether it succeeds, fails, times out or is stopped. It is worked out from the length of the prompt and the reply, at about four characters per token, priced at the agent's model in `openclaw.json`. An agent whose model is an `ollama/` one runs on your own Ollama, so it is charged nothing and its tasks read **free**. * **Tokens Used** shows the token counts OpenClaw records for each turn, priced from Clawboo's rate table. A turn your own Ollama served is priced at \$0. * **A 1:1 chat** with an OpenClaw agent shows in **Tokens Used**, but is not charged to budgets. On a ChatGPT plan you pay a flat fee, so these figures are a budget guide, not your bill. Budgets only warn you unless you set a hard cap. See [Budgets and spend](/using/budgets-and-spend). ## Limits * **No worktree, no checks.** OpenClaw agents work in their own Gateway workspace. Clawboo never gives them a git worktree and never runs verification on their work; see [How work is checked](/concepts/verification). * **Global memory, no connectors from the graph.** See [Clawboo tools inside OpenClaw](#clawboo-tools-inside-openclaw). * **A Gateway on this computer.** **Set up OpenClaw** configures and starts a local Gateway. Clawboo registers its MCP servers at a `127.0.0.1` address, which a Gateway on another machine can't reach. * **Routine runs stop after 10 minutes.** An OpenClaw Routine run that hasn't finished by then is stopped. `CLAWBOO_ROUTINE_OPENCLAW_TIMEOUT_MS` changes the limit. * **The 2026.9 line.** Clawboo installs `openclaw@~2026.9`, because it writes OpenClaw's configuration in that line's format. * **Not on the runtimes API.** `/api/runtimes/openclaw/...` answers `404`. OpenClaw is managed through the [System API](/reference/rest-api/system). ## Troubleshooting > [accordion] NOT\_PAIRED: pairing required, device is not approved yet > > **What it means:** OpenClaw hasn't approved this device yet. Every connect is refused until you approve it once. > > **Fix:** click **Approve this device** on the card the setup shows. If you approve from a terminal instead (see [Approve from a terminal instead](#approve-from-a-terminal-instead)), run **Set up OpenClaw** or **Reconnect OpenClaw** again afterwards to connect. > [accordion] OpenClaw still needs this device approved. Retry to approve it again. > > **What it means:** the approval went through, but the reconnect after it failed, or OpenClaw still reported the device as unapproved. > > **Fix:** click **Retry**. The setup runs again and shows a fresh approval card if the device still needs one. > [accordion] The approval card shows an error > > The card shows what the approval step reported: > > * `No pending device pairing requests found`: OpenClaw had no request waiting, for example because the device is already approved. Close the panel with **Cancel** and run **Set up OpenClaw** again; it connects, or shows a fresh card. > * `OpenClaw not installed`: Clawboo can't find the `openclaw` command. Run the setup again, or check that npm's global folder is on your `PATH`. > * `Failed to approve device: …` or `Failed to run openclaw: …`: the OpenClaw command failed. Run the two terminal commands above to see OpenClaw's own message. > [accordion] Permission denied. Your Node.js installation may require sudo for global installs. > > **What it means:** npm can't write to its global folder. The message suggests `sudo npm install -g openclaw@~2026.9`. > > **Fix:** prefer a Node.js version manager (nvm or fnm) or Homebrew's Node, which install global packages without `sudo`. Then click **Retry**. > [accordion] Installation failed with exit code > > **What it means:** `npm install -g openclaw@~2026.9` failed. A Node.js version outside OpenClaw's range is a common cause. > > **Fix:** run `node --version` and compare it with [Before you begin](#before-you-begin). Install a supported Node.js, then click **Retry**. > [accordion] Gateway did not start, or did not become reachable within 60s > > **What it means:** the Gateway exited with a reason (`Gateway did not start: …`), or never answered on its port within 60 seconds. > > **Fix:** read the Gateway's log at `~/.clawboo/gateway.log`, fix what it reports, then click **Retry**. OpenClaw's [troubleshooting guide](https://docs.openclaw.ai/help/troubleshooting) and [doctor command](https://docs.openclaw.ai/gateway/doctor) help with OpenClaw's own errors. > [accordion] OpenClaw needs openclaw doctor --fix, and running it failed > > **What it means:** OpenClaw asked for a data migration before it would start, and the `openclaw doctor --fix` that Clawboo ran failed. > > **Fix:** run `openclaw doctor --fix` in a terminal to see the whole report, then start the Gateway again with **Retry** or **Reconnect OpenClaw**. Do the same if the message says OpenClaw still asks for `openclaw doctor --fix` after Clawboo ran it. > [accordion] Clawboo couldn't find the model in your Ollama > > **What it means:** OpenClaw's default model isn't pulled in your Ollama, or Ollama isn't running. > > **Fix:** start Ollama, run the `ollama pull` command the panel shows, then click **Check again**. > [accordion] OpenClaw Gateway is offline, or unreachable > > **What it means:** the Gateway stopped or can't be reached. Your OpenClaw agents are paused; agents on other runtimes keep working. > > **Fix:** click **Reconnect** on the banner, or **Reconnect OpenClaw** in **Settings > Runtimes**. If the Gateway rejected Clawboo's token, the banner offers **Restart Gateway** instead, because the Gateway reads its token only when it starts. > [accordion] Could not set this agent's command permissions on the Gateway, so it was not created > > **What it means:** the Gateway refused the command policy for a new agent, so Clawboo removed the agent rather than leave it unguarded. The reason follows the message. > > **Fix:** check that the Gateway is running and connected (**Manage > Re-check**), then create the agent again. > [accordion] The API answers 503 gateway\_disconnected > > **What it means:** the request needs the Gateway (creating an OpenClaw agent, reading or writing its files), and Clawboo's server isn't connected to it. > > **Fix:** start the Gateway from **Settings > Runtimes > OpenClaw**, then try again. ## Check it works **1. Check the runtime** Open **Settings > Runtimes**. **OpenClaw** reads **Connected**, and under **Manage** the **Gateway** reads **Running**. **2. Talk to an OpenClaw agent** Open an OpenClaw agent from the agent column and send a short message in its chat. With no OpenClaw agent yet, **Create Boo** makes one while the Gateway is connected. You should see a reply stream in. **3. Put OpenClaw agents on a team** Deploy a team from the **Marketplace**. While the Gateway is connected, a marketplace team's members default to OpenClaw; change any member in **Customize team**. In onboarding, members default to Clawboo Native instead, or to Codex if you signed in with ChatGPT. > [accordion] Under the hood > > * **The connections.** The browser connects to `/api/gateway/ws` on the Clawboo server, which adds the token and signs the device before passing it on. The server's own connection is an operator client (client id `cli`) that sends the Gateway host as its Origin and declares the `tool-events` and `exec-approvals` capabilities. Clawboo supports Gateway connect protocols 3 and 4. > * **Write budget.** The Gateway allows three configuration writes per 60 seconds, so the MCP registration skips its write when nothing has changed. > * **`configure-openclaw`.** The dashboard never calls `POST /api/system/configure-openclaw`. It writes a fresh `openclaw.json` and `.env`, replacing what is there, and makes a new Gateway token on every call, so use it only on a fresh OpenClaw. **Set up OpenClaw** uses `POST /api/system/auto-configure-openclaw`, which fills in only what is missing. See the [System API](/reference/rest-api/system). > * **Model refs.** Clawboo writes the ChatGPT model as `openai-codex/<model>`. `openclaw doctor --fix` later rewrites it to `openai/<model>` and turns on the plugins that form needs. > * **The pin.** `openclaw@~2026.9` holds OpenClaw's configuration format (the agent roster under `agents.entries`, and stricter `config.patch` rules), not the connect protocol. > * **More detail:** [Gateway and events](/internals/gateway-and-events) and [Agent sources](/internals/agent-source). ## Related * [Choose a runtime](/runtimes/index): how OpenClaw compares with the other runtimes * [Connect providers and runtimes](/using/providers-and-runtimes): keys, sign-ins and what Disconnect removes * [Command permissions](/using/command-permissions): review and revoke what an OpenClaw agent may run * [Where Clawboo keeps data](/operating/data-and-state): back up or reset `~/.clawboo` and `~/.openclaw` * [OpenClaw docs](https://docs.openclaw.ai): install, channels, cron jobs and troubleshooting for OpenClaw itself # Create and edit agents Source: https://docs.claw.boo/using/agents Create an agent, open its page, change its model and personality, edit the files it follows on each runtime, and delete it. Each agent in Clawboo appears as a Boo, a small mascot with its own name and color. Use this page to add an agent, open its page, change the model it runs on, tune how it sounds, edit the files it follows, and delete it. ## Create an agent **1. Pick the team** In the team rail, click the team the agent should join. With no team selected, the new agent belongs to no team. **2. Open Create Boo** In the agent column, below the team's chats, click **Create Boo**. **3. Describe the agent** Enter a **Name** and, if you like, a **Role**: a few sentences on what the agent does and how. The role becomes the start of the agent's SOUL.md. Leave it empty for a general helpful assistant. **4. Check the runtime and create** The line under the fields says where the agent will run: **Runs on OpenClaw, through your Gateway.** or **Runs on Clawboo Native: no OpenClaw Gateway is connected.** Click **Create Boo**. You should see the agent's Boo tile in the team's agent column. **Create Boo** needs no OpenClaw Gateway. It creates the agent on OpenClaw while Clawboo is connected to an OpenClaw Gateway, and on Clawboo Native otherwise. A Clawboo Native agent needs a connected provider (an API key or a local Ollama) to answer. To put an agent on Claude Code, Codex or Hermes, deploy it from the [Marketplace](/using/marketplace) or as part of a [team](/using/teams), and pick its runtime on the customize step. ### What a new agent starts with * The **House Style** personality (see [Tune the personality](#tune-the-personality)). * The browser connectors, Playwright and Chrome DevTools. Chrome DevTools asks you before each action; with the access a new agent gets, Playwright offers only its reading tools. See [Connectors](/using/connectors) for how to let an agent act through Playwright. * On OpenClaw: **Ask for Unknown** for commands, so it asks your approval before running a command that is not on its list. Clawboo sets this at the Gateway, and if the Gateway refuses the setting, the agent is not created. * On Clawboo Native: no commands at all until you turn on **Let this Boo ask to run commands** (see [Permissions](#permissions)). ## Open an agent's page * **From the sidebar:** click the agent's Boo tile in the agent column. To list every agent, right-click Boo Zero's mascot at the top of the team rail and choose **Show all agents**. * **From the Org chart or a team graph:** right-click the agent's Boo, then **Open agent**. [Boo Zero](/using/boo-zero) has its own way in: click its mascot at the top of the team rail. ![Ada's agent page: her chat on the left; on the right, the Graph card with her skills, connectors and the Claude Sonnet 5 model control, above the Details card open on Personality](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/agent-detail.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=38162b571ca208c4b6fe39de92c24f72) The chat sits on the left and cards stack on the right. The buttons in the page header show or hide each card. Each card has **Maximize** and close buttons, and you can drag the gaps between the chat and the cards to resize them. A fresh install opens **Graph** and **Details**, and this browser remembers your layout. | Card | What it shows | | - | - | | **Graph** | The agent with its skills and connectors. Drag from the agent's port onto empty space, or use **+**, to add a skill or connector. The model control sits in the top-right corner. | | **Tasks & Routines** | The agent's tasks in **Needs you**, **In progress**, **To do**, **Done** and **Cancelled**, then the routines that send it work, with **New routine**. | | **Browser** | The latest screenshot from the agent's web browser. | | **Activity** | The agent's tool calls, results and errors as they happen. | | **Details** | **Workspace**, **Personality**, **Permissions**, **Brief** (Boo Zero only) and **Agent Files**. | In **Tasks & Routines**, click a task to read its output and progress. The comment box at the bottom (**Comment or steer this task…**) sends your comment to the agent the task belongs to, and the line beside **Send** says what sending does. On a running task, the agent gets it during the run. A task that waits on you, or waits in **To do**, starts again for it. A finished task cannot run again, so the note under your comment offers **Send as a follow-up task** instead. Each comment you send shows where it went. See [Steer a running task](/using/board#steer-a-running-task) for every case. **Open on Board** opens the task on the board. **Workspace** under **Details** is a read-only view of the agent's files. It shows the worktree of each board task the agent took that changes files. For a Claude Code, Codex, Hermes or Clawboo Native agent it also shows the folder its runs start in when there is no worktree, as in team chat and in its own chat: **Team folder**, or **Shared folder (no team)** for an agent with no team. When there is more than one, pick it from the list at the top. The team folder is not a git checkout, so its files carry no change badges, and until the agent's first run there it reads "No files yet." OpenClaw agents keep their files in the OpenClaw Gateway's workspace instead. ## Chat with an agent The chat on an agent's page is a one-to-one conversation with that agent. For a Clawboo Native, Claude Code, Codex or Hermes agent, the Clawboo server runs the chat, so it needs no OpenClaw Gateway. The reply appears as the agent writes it, except on Hermes, whose reply appears in one piece when it finishes. An OpenClaw agent's chat goes through the OpenClaw Gateway, so it works while the Gateway is connected. * The chat shows the latest 150 messages. **Load earlier messages** at the top brings in older ones. * While the agent works, the send button turns into **Stop**, and it stays that way until the turn ends. * The agent remembers the conversation from one message to the next. Claude Code, Codex and Hermes agents also get their SOUL.md with every message, as in team chat. * On Clawboo Native, Claude Code, Codex and Hermes, the chat starts in the agent's team folder, `~/.clawboo/workspaces/<team id>`, or `~/.clawboo/workspaces/_no-team` for an agent on no team, such as Boo Zero. Claude Code, Codex and Hermes only start there: they can still read and change any file your user account can. A Clawboo Native agent can read and write files only inside that folder. With **Let this Boo ask to run commands** switched on, it can also ask to run a command there, and the request waits for your answer above the message box. See [What agents can do](/concepts/permissions). * A turn that fails before the agent writes anything leaves a notice in the chat with the reason, such as a runtime with no key, or "the run went quiet for too long and was ended" when nothing happened for 30 minutes. A turn that fails partway keeps what the agent had written. * Type `/reset` (or `/new`) and send it to start fresh. Every message stays on screen, a divider marks the point, and the agent stops carrying the conversation from before it. With your next message, a Clawboo Native, Claude Code, Codex or Hermes agent gets back up to eight recent notes from its own memory and the global memory (not a team's), as background. See [Team chat](/using/team-chat) for how a reset works in a team. Only deleting the agent removes its chat history. ## Change the model The model control sits in the top-right corner of the **Graph** card, beside the agent's runtime icon. | Runtime | What you can change | | - | - | | Clawboo Native | Pick a model. It saves at once and needs no Gateway. | | OpenClaw | Pick a model. Clawboo saves the choice in OpenClaw's own config for this agent. | | Hermes | Pick an OpenRouter model. The agent then runs it through OpenRouter, even if it ran on a ChatGPT subscription model before. A **ChatGPT subscription** model can be picked only when you deploy the team, once Hermes is signed in with ChatGPT. | | Claude Code | Nothing: the control reads **Claude Code default**, the model Claude Code picks. | | Codex | Nothing: the control reads **Codex default**, the model of your Codex account. Change it with the Codex CLI. | For a team's members, you can also pick models when you deploy the team. See [Choose a runtime and model for each member](/using/teams#choose-a-runtime-and-model-for-each-member). ## Tune the personality Open **Details > Personality**. * **Presets** are seven one-click styles: **House Style**, **Ghost**, **Tired Senior**, **Glass Skull**, **The Blocker**, **The Rival** and **Feral**. Hover one to read what it is like. A new agent starts on **House Style**. * **Sliders** are six dials from 0 to 100. A change saves when you let go of the slider: you see **Saving…**, then **Saved**. | Slider | What it sets | At 0 | At 100 | | - | - | - | - | | **Bite** | How hard you hit the work. Never the person who wrote it. | House Style | Feral | | **Spine** | Whether you actually do what you're told. | Just Do It | Blocks the Board | | **Glass Box** | How much of the thinking the room gets to watch. | Result Only | Full Stream | | **Elbows** | How you treat the other agents in the room. | Stays In Lane | Picks Fights | | **Receipts** | How much you admit about your own run. | Ships Clean | Full Confession | | **Hobbyhorse** | The one hill you die on, and how often you climb it. | No Crusade | It's The Whole Personality | Each slider adds one of five passages, depending on its value. Turn on **Show prompts** to read the exact text at the current values. Whatever you choose, the text ends with a **Limits** section that no slider removes. It keeps criticism on the work, never on a person, and the style never changes what the agent reports. Clawboo writes the personality into the agent's SOUL.md, below the role text. Every runtime follows SOUL.md (see [Edit the agent files](#edit-the-agent-files)), so the personality reaches the agent on any runtime. **Preview SOUL.md** shows the result. ### Use your own instructions instead Click **Use Custom Instructions** to write the personality yourself. Your text replaces the slider text, **Limits** included. It saves when you click away from the box or press `Cmd/Ctrl+S`. **Switch to Sliders** clears your text and goes back to the sliders. > [!WARNING] > > While custom instructions are on, the SOUL tab under **Agent Files** still shows the slider personality, not your text. If you edit and save SOUL.md there, the slider text replaces your custom instructions in the file the agent follows. While custom instructions are on, change the personality only here. If it happens, click in your custom instructions and press `Cmd/Ctrl+S` to write them back. ## Edit the agent files Open **Details > Agent Files**. Each file has a tab. Which tabs you see, and what each file does, depends on the agent's runtime. | Runtime | Tabs | What the agent follows | | - | - | - | | OpenClaw | SOUL, IDENTITY, TOOLS, AGENTS | The OpenClaw Gateway loads SOUL.md, IDENTITY.md and AGENTS.md. OpenClaw 2026.9 and later does not load TOOLS.md, so put tool guidance in AGENTS.md. | | Clawboo Native | SOUL, AGENTS | SOUL.md is the agent's whole set of instructions. Saving it changes what the agent runs on. | | Claude Code, Codex, Hermes | SOUL, AGENTS | Clawboo adds the first 4,000 characters of SOUL.md to each turn as the agent's persona. | * On every runtime, the @mentions in AGENTS.md draw the agent's links on the Org chart and team graphs. Only OpenClaw also reads AGENTS.md as instructions. * A file that already has text keeps its tab on any runtime. That is why agents made with **Create Boo** or from the Marketplace also show IDENTITY and TOOLS: those files are saved, but only OpenClaw reads them. * Clawboo never creates or reads USER.md, HEARTBEAT.md or MEMORY.md. Their tabs appear only when the file already has text, for example one your OpenClaw Gateway wrote. On Clawboo Native, an agent from a team template has Clawboo's team-lead or specialist instructions at the end of its SOUL.md. They tell the agent how to hand off or report work, so keep them unless you mean to change how it works with its team. An empty SOUL.md falls back to Clawboo's default instructions. **1. Pick a file** Open **Details > Agent Files** and click a file's tab. **2. Edit it** A changed file shows an amber dot, and the card's footer reads **Unsaved**. **3. Save it** Click **Save** or press `Cmd/Ctrl+S`. You should see **Saved** with the file's name, for example **Saved SOUL.md**. Changed files also save when you leave the page or switch to another agent. Saving AGENTS.md redraws the agent's links on the graphs. Clawboo keeps the files of Clawboo Native, Claude Code, Codex and Hermes agents itself, so editing them never needs the OpenClaw Gateway. An OpenClaw agent's files live in the OpenClaw Gateway. While Clawboo's server is not connected to the Gateway, that agent's tabs open empty and **Save** fails. Do not type into those empty tabs: if the connection comes back before you save, your text replaces the real file. ## Permissions **Details > Permissions** sets what an agent may run, for two runtimes: * **OpenClaw:** **Execution Permissions** sets the agent's **Command Execution** to **Run Freely**, **Ask for Unknown** or **Always Ask**, and the Gateway enforces it. Below it, **Commands this Boo can run without asking** lists the commands it may run without asking, so you can revoke them. * **Clawboo Native:** the **Running commands** switch, **Let this Boo ask to run commands**. With it on, the agent asks you before every command, and nothing is remembered. See [Command permissions](/using/command-permissions) for the details. Claude Code, Codex and Hermes agents have no **Permissions** section. Clawboo runs them with their own approval prompts and sandboxes turned off, so they can read and change any file your user account can. See [What agents can do](/concepts/permissions). ## Delete an agent * **On the Org chart or a team graph:** right-click the agent's Boo, then **Delete**. It deletes at once, with no confirmation. * **In the all-agents list:** right-click Boo Zero's mascot and choose **Show all agents**. Hover the agent's row, click the trash icon, then confirm. The prompt warns **This cannot be undone.** Deleting removes the agent, its files, its personality and its own chat history. It works without the OpenClaw Gateway. An OpenClaw agent leaves Clawboo at once, and leaves the Gateway at once too, or the next time Clawboo connects if the Gateway is offline. If the Gateway refuses the delete (it will not delete its only agent), the agent comes back. ## Troubleshooting > [accordion] Save fails with gateway\_disconnected > > The message reads `Failed to save SOUL.md: Write SOUL.md failed (503): gateway_disconnected` (with the file you saved). > > **What it means:** this is an OpenClaw agent, and Clawboo's server is not connected to the OpenClaw Gateway, where the agent's files live. > > **Fix:** reconnect OpenClaw in **Settings > Runtimes** (or check **System Health**), then reopen the agent's page so its files load before you edit. > [accordion] Save fails with a prompt-injection finding > > The message ends with `(422): agent content blocked: prompt-injection finding`. > > **What it means:** the text matches a known prompt-injection pattern, so Clawboo refused it and recorded the refusal in the Governance audit log. > > **Fix:** remove or reword the flagged text, then save again. > [accordion] The message box says Gateway not connected > > This is an OpenClaw agent, and its chat goes through the OpenClaw Gateway. Connect the Gateway in **Settings > Runtimes**. Clawboo Native, Claude Code, Codex and Hermes agents chat through the Clawboo server, so their chat never shows this. > [accordion] A Clawboo Native agent does not answer > > The page header reads **Disconnected** or **No provider key**: Clawboo Native has no provider it can use for this agent. Click **Set up in Runtimes** in the header and connect an API key or a local Ollama. > [accordion] The SOUL tab does not show my custom instructions > > This is expected while **Use Custom Instructions** is on. See the warning under [Use your own instructions instead](#use-your-own-instructions-instead). > [accordion] Under the hood > > * Clawboo stores the slider values and any custom instructions in its database, and writes the generated text into SOUL.md below a `---` line and a `clawboo:personality` marker comment. When **Agent Files** loads, it rebuilds that block from the saved slider values. > * File routes: `GET` and `PUT /api/agents/:agentId/files/:name`, where the name is one of the seven agent files (any other name returns 400 `invalid file name`). Personality: `POST /api/personality`. Model: `PATCH /api/agents/:agentId/model`. > * `DELETE /api/agents/:agentId` answers `upstreamDeletePending: true` when an OpenClaw agent's Gateway is offline. Until the Gateway confirms, `GET /api/agents` lists that agent in `pendingDeleteIds`. > * See the [Agents API reference](/reference/rest-api/agents) for every field. ## Next steps * [Teams](/using/teams): put agents together and set rules they all follow. * [Team chat](/using/team-chat): give your agents work as a team. * [Command permissions](/using/command-permissions): decide what each agent may run. * [What an agent sees](/concepts/agent-context): everything that goes into an agent's turn. * [Agents API reference](/reference/rest-api/agents): the routes behind this page. # Answer approval requests Source: https://docs.claw.boo/using/approvals What waits for your yes, where approval cards appear, what each button does, what Always remembers, and how long a request waits. Some actions wait for a person before they happen. Each one appears as an approval card, and nothing happens until you answer it or it runs out of time. Use this page when an agent is waiting on you, or when you want to decide what it may do without asking next time. ## What asks for your approval | Request | When it asks | How long it waits | | - | - | - | | A shell command from an OpenClaw agent | When the agent's **Command Execution** setting says to ask. Agents you create in Clawboo start on **Ask for Unknown** | Until the OpenClaw Gateway's deadline, or 30 minutes when it sets none | | A shell command from a Clawboo Native agent | Before every command, once you let the agent ask to run commands | 10 minutes | | A connector call | When Clawboo rates the call as risky, for example one that changes or deletes something in an app | 5 minutes | | A risky hand-off | When a leader in team chat hands off a task whose text contains a risky word | 5 minutes | Claude Code, Codex and Hermes never ask before using their own tools, such as their shell and file editing: Clawboo runs them with their own approval prompts turned off. Their connector calls and hand-offs still follow the rules above. [What agents can do](/concepts/permissions) explains what they can reach. [Command permissions](/using/command-permissions) sets when the two shell checks ask, and [Connectors](/using/connectors#when-a-connector-call-asks-you-first) lists which connector calls ask. The risky-word check matches whole words, in any letter case: * delete, destroy, deploy, publish, release, secret, credential, prod, production * the phrases drop table and rm -rf * api\_key, api-key or apikey, and force-push, force\_push or forcepush Other forms do not trigger it: "deleted", "deployment" and "secrets" pass, and so do "API key" and "force push" written with a space. [Budgets and guardrails](/concepts/governance#approval-for-risky-delegations) has the full rules. ## Where approvals appear * **Needs you** on the [Board](/using/board), the first column. It shows requests for the teams in the board's filter, plus Boo Zero's requests and any request Clawboo cannot tie to an agent. * **The chat**, in a tray above the message box. A team chat shows that team's requests, and a one-to-one chat shows that agent's. The tray holds three cards, then a **+N more, view on the board** link. * **The Tasks card** beside a team chat and the **Tasks & Routines** card on an agent's page, under **Needs you**. * **Settings > Insights > Governance**, in the **Approval queue**. It lists every pending request from every team, or "No pending approvals". * **Settings > Workspace > Capabilities**, at the top of the panel, while something is waiting. Answer a request in any of these places and it clears from the others within a few seconds. A request that Clawboo cannot tie to an agent shows in every chat and under every board filter. Connector calls from OpenClaw agents arrive this way, because OpenClaw agents reach connectors without an agent identity. ## Read a card Requests appear on two kinds of card. ### Command cards on the Board and in chat On the Board, in the Tasks cards and in the chat tray, an OpenClaw agent's command appears on a command card: * **Wants to run a command** with the agent's Boo and name, or in the chat tray an **Exec Approval** label (with the agent's name in a team chat). * **expires Ns**: roughly how long is left. * The command, cut off after about two lines. * An error line, when your last answer did not go through. * Three buttons: **Allow Once**, **Always** and **Deny**. ### Request cards Every other request uses a request card, and so does every request in the Governance and Capabilities queues, OpenClaw commands included. A request card shows: * For a call to an app reached through Composio, the app's logo and name. Otherwise the agent's name, or **Waiting on you** when Clawboo cannot tell which agent asked. * A short label for the kind of action, such as **Runs a command**, **Reads only** or **Acts on your Gmail**, and the seconds left. * One sentence naming the agent and what it wants, such as "Ada wants to run a command on this computer." * The details that decide the answer, such as the recipient, the file or the command: two on the Board and in chat, three in the queues. A **Show N more details** link opens the rest, including any note the agent gave, which Clawboo marks as not verified. * On some cards, a checkbox that remembers your answer (see [Answer a request](#answer-a-request)). * An allow button named for the action, such as **Run it**, **Allow read**, **Send it** or **Delete it** (otherwise **Allow**), and **Don't allow**. A request card shows a command in full unless it is very long, and adds the **Folder** it runs in when Clawboo knows it. A risky hand-off card names the leader, and the task text and the teammate it is for sit under **Show N more details**. ## Answer a request | Request | Allow it | Remember the answer | Refuse it | | - | - | - | - | | OpenClaw command, on the Board or in chat | **Allow Once** | **Always** | **Deny** | | OpenClaw command, in the Governance or Capabilities queue | **Run it** | Tick **Always allow this command in this folder**, then **Run it**. The box appears only when the Gateway accepts Always for that command | **Don't allow** | | Clawboo Native command | **Run it** | Not offered: every command asks | **Don't allow**. The agent cannot ask about another command for the rest of that run | | Connector call | The allow button | Tick **Do not ask again for this for 30 days**, then allow. Only some cards offer it | **Don't allow**. The call is refused | | Risky hand-off | **Allow** | Not offered: every risky hand-off asks | **Don't allow**. The hand-off is skipped and the leader is told | A few details decide what you see: * **Always on the Board and in chat.** The command card offers **Always** every time. When the Gateway does not accept it for that command, for example because the agent is set to **Always Ask**, the card shows an error. Choose **Allow Once** or **Deny** instead. * **The 30-day box on connector cards.** It appears only when the call was made under access you shared with the agent, Clawboo could read the request, and the call is not one that must ask every time. Access set through the API to ask on every call (`approvalPolicy: always`) never offers the box. Neither does a run that has read private data and also taken in outside content: it asks before any call that could send data out. * **Hand-offs in a row.** A leader's hand-offs in one turn start one after another, so the hand-offs after a risky one wait for your answer too. ## What Always remembers * **An OpenClaw command.** The Gateway lets that agent run the command in that folder without asking, with no end date. To take it back, open the agent's page, then **Details** > **Permissions**, and revoke it under **Commands this Boo can run without asking** (see [Command permissions](/using/command-permissions)). Revoking does not undo a command that already ran. * **A connector tool.** Clawboo stops asking about that tool, with any arguments, for 30 days, for the access you shared with that agent. Calls that change or delete something still ask every time, because a second check asks about them whatever you ticked, and so do calls that must always ask. Access set to ask on every call (`approvalPolicy: always`) asks even for a tool you answered **Always** before it was set that way. To end it early, stop sharing the connector with the agent on the graph ([Connectors](/using/connectors)). Sharing it again starts with nothing remembered. * **A risky hand-off.** Nothing is remembered. An answer of `allow_always` sent through the API allows that one hand-off, and the next risky hand-off asks again. > [!WARNING] > > Approve a command only when you understand it. An approved command runs on this computer as your user account, with network access. Clawboo cannot undo what it did. ## If nobody answers in time * **OpenClaw command.** The card goes away and Clawboo records the request as expired, not as a refusal. Clawboo's server keeps a copy of each request, so the card waits for you even while no browser tab is open. If an open tab received the request live, the agent's one-to-one chat in that tab also notes that the approval timed out. * **Clawboo Native command.** The command does not run, and the agent cannot ask about another one for the rest of that run. The run waits while the card is open. * **Connector call.** The call is refused. * **Risky hand-off.** The hand-off is skipped and the leader is told. When the hand-off came from a task, that task gets the comment `Delegation to <name> not approved (<reason>); skipped.` ## What a card does not show * **The folder, on the Board and in chat.** A command card there shows only the command, and cuts a long one off. Open the **Approval queue** in **Governance** to see the full command and its folder. * **Why the agent wants it.** No card shows an agent's reason for a command, even when it gave one. * **Every field at once.** A request card shows the deciding fields first. The rest, and any note from the agent, are behind **Show N more details**. * **Secrets.** Values that look like passwords or keys are masked. * **An exact countdown.** The seconds left are worked out when the card is drawn, so treat them as approximate. * **Past answers.** The dashboard keeps no list of requests you already answered. The audit log in **Governance** records risky hand-off decisions. ## Check that it worked * The card leaves **Needs you**, the chat tray and the queues. When nothing is left, **Needs you** reads "Nothing needs you right now". * An allowed command runs and the agent carries on. After **Always**, if the command's output does not reach the chat, Clawboo asks the agent to run it again so you see the result. The command is on the agent's list by then, so no new card appears. After **Allow Once** Clawboo does not do this; if no output appears, ask the agent to run the command again. * A refused or expired hand-off leaves the "not approved" comment on the task it came from. * On the [Org chart](/using/org-chart) and team graphs, an OpenClaw agent with a command waiting shows a pulsing amber ring. The ring appears only for requests this browser tab received while it was open. ## Troubleshooting > [accordion] A card disappeared before I answered > > It ran out of time. The waits are in [What asks for your approval](#what-asks-for-your-approval). For OpenClaw agents that ask too often, set **Command Execution** to **Ask for Unknown**, so only commands the agent is not already allowed to run ask. See [Command permissions](/using/command-permissions). > [accordion] I pressed Always and the card shows an error > > The Gateway did not accept **Always** for that command. An agent set to **Always Ask** never remembers a command, and the Gateway can refuse it for other commands too. Press **Allow Once** or **Deny**, or change the agent's **Command Execution** setting first. > [accordion] I allowed a command but its output never appeared > > After **Allow Once**, Clawboo does not ask the agent to repeat the command, and the Gateway may not send the output to the chat. Ask the agent to run the command again. > [accordion] clawboo could not reach the Gateway to send that decision. > > Your tab has no connection to the OpenClaw Gateway, and Clawboo's server could not pass your answer on either. Check that OpenClaw is connected under **Settings** > **Workspace** > **Runtimes**, then answer again before the card expires. > [accordion] An agent ran a command without asking me > > Claude Code, Codex and Hermes never ask before a command. An OpenClaw agent runs without asking when it is set to **Run Freely**, or when the command is already on its list under **Commands this Boo can run without asking**. A Clawboo Native agent asks before every command. > [accordion] clawboo could not tell which agent asked for this. > > The request arrived with no agent identity, which is how connector calls from OpenClaw agents arrive. Decide from the details on the card. The card appears in every chat and under every board filter. > [accordion] A leader is waiting, but I see no card > > Set the board's team filter to **All teams**, or open the **Approval queue** in **Governance**, which lists every pending request. A risky hand-off waits only 5 minutes, so it may have expired already. > [accordion] Under the hood > > * **OpenClaw requests.** Clawboo's server keeps its own connection to the Gateway and copies each command request into Clawboo's tool-call approvals table as a row of kind `exec`, keyed by the Gateway's id. Every 30 seconds it marks rows past their deadline `expired`, never `deny`, and writes no audit row for them. > * **Answers.** A tab with its own Gateway connection sends the answer straight to the Gateway (`exec.approval.resolve`). Every other answer goes through `POST /api/tools/approvals/:id/resolve`, which answers the Gateway first for an `exec` row. Answering twice is safe: the second answer changes nothing. > * **Clawboo's own requests.** Connector calls, Clawboo Native commands and risky hand-offs are rows in the same table, and the dashboard checks for new ones every 3 seconds. Clawboo records answers as `allow_once`, `allow_always` and `deny`; OpenClaw's words are `allow-once`, `allow-always` and `deny`. > * **Remembered answers.** A connector **Always** writes a standing rule bound to the access you shared, expiring after 30 days, and stopping the share deletes its rules. A risky hand-off is filed per leader under the scope `delegate:code` and marked never-remember, so every one raises a card. > * **Cleanup.** Once an hour, Clawboo expires any request still pending after `CLAWBOO_APPROVAL_TTL_MS` (24 hours by default). That cleanup does not lengthen the waits on this page. > * **History.** `GET /api/approvals?agentId=<id>` lists the OpenClaw command decisions made from tabs with a Gateway connection, newest first. > * Shapes and codes: [Governance API](/reference/rest-api/governance) and [Tools and MCP API](/reference/rest-api/tools-and-mcp). Every waiting time is in [Limits and defaults](/reference/limits-and-defaults). ## Next steps * [Command permissions](/using/command-permissions): choose when OpenClaw and Clawboo Native agents ask, and revoke what **Always** allowed. * [Connectors](/using/connectors): share connectors with agents and see which calls ask. * [What agents can do](/concepts/permissions): what each runtime can reach without asking. * [Budgets and guardrails](/concepts/governance): the risky hand-off check and the other limits on agents. * [Board](/using/board): everything else that waits in **Needs you**. # Track work on the board Source: https://docs.claw.boo/using/board See every task your agents work on, act on what needs you, give an agent a task, steer a running task, and review what it made. The board shows every task your teams work on. Each task is a card, and its column says where it stands. Agents add and move cards as they work. You use the board to see what is happening, act on work that waits on you, give an agent a task, and steer one that is running. The board opens on **Kanban**: every task by status, newest first. **Today**, **Week** and **Month** are calendar views that lay the same cards out by the day each was added or finished. Every task handed out in a [team chat](/using/team-chat) is a card here. How tasks, statuses and dependencies work underneath is explained in [The board](/concepts/the-board). ![The board on Kanban for all teams: five tasks that need a person lead the first column, each with its reason and one button, then Backlog, To do, In progress and Done, with Cancelled folded into a rail](https://mintcdn.com/privatedocs/tuYc7V3BIxpSQNHP/images/board-kanban.png?fit=max\&auto=format\&n=tuYc7V3BIxpSQNHP\&q=85\&s=17b420f2abf0c61098543eabcd17ffcf) ## Track work in five steps **1. Open the board** Click **Board** in the sidebar, or press `Cmd+2` (`Ctrl+2` on Windows and Linux). The board opens on **Kanban** the first time, and on the view you picked after that. The team filter beside the **Board** title shows **All teams** or one team. **2. Read a card** Each card shows the task's title and one row of details: the agent's face, a status mark such as **Verifying** or the cost, and when the task was added or finished. See [Read a card](#read-a-card). **3. Act on Needs you** The first column, **Needs you**, holds approvals and tasks that will not move until you act. Each card says why it waits and carries the one button that gets it moving. See [Things that need you](#things-that-need-you). **4. Open a task** Click a card to open its drawer: the agent's report, the brief, the task's properties, every run, comments with a box to reply to the agent, and live activity. See [The task drawer](#the-task-drawer). **5. Find a task** Click the search button in the toolbar, press `/` while the board has focus, or press `Cmd+K` (`Ctrl+K`). Type part of a title, an agent's name or words from the details. **Enter** shows the card on the board, and **Shift+Enter** opens its drawer. ## The header and toolbar The header holds the **Board** title, the team filter, and **New task** (press `C`). When something changed while you were away, a chip beside the team filter says what; see **Since you last looked** under [Views and the calendar](#views-and-the-calendar). The toolbar under it holds these controls, left to right: | Control | What it does | | - | - | | **Needs you** | Shows when the Needs you column is out of view, with its count. Click it to bring Needs you into view | | Agents working | Reads, for example, **2 agents working** while runs are going, with the faces of the agents at work. Click it to show only running work, and again to show everything | | Search button | Opens **Find** (`/` or `Cmd/Ctrl+K`) | | **Filter** | Shows only some agents' tasks, tasks with **No agent**, or what is **Running now**. The button counts the filters that are on, and **Clear filters** turns them off | | **Display** | **Routine runs** (calendar views only) shows or hides upcoming routine runs. **Cancelled column** opens or folds Cancelled. **On cards** turns each card's **Cost** and **Time** on or off | | View menu (names the view) | Switches between **Kanban**, **Today**, **Week** and **Month**, and lists each view's key | The filter applies to every column, Needs you included, and starts over when you change the team filter. **Find** still searches every task. When the filter hides every task, the board says "No tasks match these filters" with **Clear filters**. On a calendar view, the left of the toolbar holds **Today**, the previous and next arrows, and the range title. On Kanban it reads "Every task by status, newest first". There is no refresh button: the board reloads itself every five seconds while it is on screen, and a small spinner shows only when a reload is slow. ## Columns and statuses | Column | What it holds | | - | - | | **Needs you** | Approvals, and tasks that will not move until a person acts. In a calendar view its items sit in today's row | | **Backlog** | Tasks set aside for later. No agent picks them up until they move to **To do** | | **To do** | Tasks waiting for their agent and, in a calendar view, the upcoming runs of your routines. The **+** in its title opens **New task** | | **In progress** | Tasks an agent is working on. A task whose work is being checked shows **Verifying** | | **Done** | Finished tasks | | **Cancelled** | Tasks that will not run again. The column starts folded into a narrow rail; click the rail to open it | There is no Blocked column and no In review column. A blocked task always needs a person, so it sits in **Needs you**. A task under review stays in **In progress** with a **Verifying** mark. A task with a status the board does not recognize appears in an extra **Other** column. Each column title has a small status mark in the column's color, and counts the tasks the column holds in the range on screen (every task on Kanban). An empty column shows no count, and an empty Kanban lane says so, for example "Nothing running". ### Read a card Cards are neutral: color appears only in the status marks, and amber marks what needs you. A card shows the task's title, on up to two lines, and one row of details: * **Agent**: the face of the agent that has the task. In **To do**, a dashed circle means no agent is bound to it. * **Status mark**: **Verifying** while the work is checked. After a check, an amber cross means it failed, an amber dashed circle means it passed with debt (open issues noted), and a green check means it passed (until the card reaches Done). A card with no mark shows its cost here. * **Cost**: what all of the task's runs cost, once a run has recorded a cost. A figure that starts with `~` is an estimate: the runtime reported no charge, so Clawboo priced the run itself. A cost under a cent reads `<$0.01`, and a task whose runs all ran on a model your own Ollama serves reads `free`. When a status mark takes the cost's place, point at the card to see the cost. Which runtimes report real cost is in [Budgets and spend](/using/budgets-and-spend). * **Subtask**: a small arrow when another task handed this one out and the card has no mark or cost to show. Otherwise the mark's or the cost's tooltip says it is a subtask. * **Time**: on Kanban, how long ago the task was added or finished, such as "4m ago", "Yesterday" or "3d ago". A calendar view shows the clock time, and the Earlier row the date. A running task shows how long its current run has gone, after a small live dot. Point at the time for the full date. **Display** > **On cards** turns the **Cost** and **Time** off and on for every card. ## Things that need you **Needs you** collects everything that waits on a person, for the teams in the filter. It holds two kinds of item: * **Approvals**: an agent wants to run a command, use a risky tool, or make a risky handoff. Answer them on their card. See [Approvals](/using/approvals). * **Tasks that will not move by themselves**. Each card says why in amber, with the reason the run recorded when there is one, and shows the agent. | Badge | What happened | | - | - | | **Failed** | The last run ended in an error | | **Timed out** | The agent stopped responding and its run was ended | | **Stopped** | You pressed **Stop** on its run. It never restarts by itself | | **Needs review** | Verification could not pass it | | **Blocked** | An agent marked it blocked | | **Unassigned** | No agent is bound to it, so nobody will pick it up | | **Not started** | It is in In progress with no agent, so no run is going | When several runs in a row failed, the badge counts them, for example **Failed 2×**. Each card has one button for the next step, and a **More** menu (the three dots) with the rest. | Button | Shows for | What it does | | - | - | - | | **Retry** | **Failed**, **Timed out**, **Stopped** or **Not started**, with an agent bound to the task | Puts the task back in **To do** and sends it straight to its agent. It works even after Clawboo has stopped retrying the task by itself | | **Assign** | **Unassigned**, or one of those four with no agent bound to it | Lists the members of the task's team. The one you pick starts the task at once, and the task becomes yours (see [Give an agent a task](#give-an-agent-a-task)) | | **Review** | **Needs review** | Opens the task's drawer, where the check's result is | | **Open** | **Blocked**, or a task neither **Retry** nor **Assign** can move | Opens the task's drawer | The **More** menu holds what the card's button does not: **Retry**, the team's agents under **Assign to** (or **Give to someone else** when the task is bound to an agent), and always **Dismiss**. **Dismiss** asks first, then moves the task to **Cancelled**, along with any later plan steps that were waiting on it. **Retry** and **Assign** also leave a note in the task's comments. Tasks that failed the same way share one card that lists them, with **Retry all** and, in its **More** menu, **Dismiss all**, which asks first. When nothing waits, the column says "Nothing needs you right now". In Week and Month, Needs you shows three items, then **+N more**. While you look at another week or month, Needs you folds into a narrow rail with its count: click it to go back to today. ### When a task runs again by itself If a run crashes or stops responding and its task goes back to **To do**, Clawboo starts it again by itself. After three unsuccessful runs in a row it stops trying, and the task waits in **Needs you** as **Failed 3×** or **Timed out 3×**. A task you stopped never restarts by itself. **Retry** always runs a task again, whatever its count. When a task fails, the later plan steps that were waiting on it are cancelled. When an agent handed the task out, that agent is told, so nothing waits forever. ## Give an agent a task **1. Open New task** Click **New task** in the board's header, or press `C`. The **+** in the **To do** column's title opens it too. **2. Say what should get done** Write one sentence under **What should get done?**, for example "Draft the launch announcement". It becomes the task's title. **3. Check the agent** Beside **For**, Clawboo suggests an agent, with a note that says why: **Fits the task**, **Last used** or **Team lead**. To give the task to someone else, pick another agent from the list. Picking an agent also picks its team. **4. Add details if you need to** **More options** opens **Team**, **Agent** and **Details**. Use **Details** for context, constraints, or a description of what done looks like. **5. Send it** Press **Enter**, or click **Send to** followed by the agent's name. You should see the card appear in **To do** and move to **In progress** when the agent starts. If the agent is busy, the task waits in **To do** and starts when the agent is free. How Clawboo picks the agent it suggests: * It matches the words of your sentence against each agent's name and the role in its SOUL.md (see [Agents](/using/agents)). A word in the name counts more than one in the role, and forms of a word count as one, so "drafting" matches "drafts". OpenClaw agents, and agents whose SOUL.md states no role, match by name only. * When nothing matches, it suggests the agent you last gave a task to in this browser, then the team lead, then the team's first agent. * The list holds the agents of the team in the board's team filter. With **All teams**, it holds every team's agents, each with its team's name. A task you give an agent this way is yours: * Only that agent works on it. The agent is asked to do the work itself rather than hand it to a teammate. It is asked to make reasonable assumptions and note them instead of asking you questions, though you can still steer it with a reply. * Its result goes on the task's card, on the board and in the team's General chat. It is not passed to the team leader. * If the run fails, the card moves to **Needs you** with the reason. **Assign** on a Needs you card works the same way for a task that has no agent. ## Steer a running task Reply on a task to talk to the agent it belongs to. While the task runs, the agent reads your reply during the run where its runtime allows it. When the task waits on you, your reply starts the agent on it again, so answering a blocked task needs no click on **Retry**. **1. Open the task** Click its card to open the drawer. The reply box sits under **Comments** and reads **Reply to** followed by the agent's name. You can also open the task in a team chat's **Tasks** card (see [Team chat](/using/team-chat)), or in **Tasks & Routines** on the agent's page (see [Agents](/using/agents)), where the box reads **Comment or steer this task…**. **2. Check what sending does** The line beside **Send** says what sending does for this task as it stands, for example that the agent gets it in the current run, or that sending starts the agent on it again. **3. Write and send** Type your reply and press **Send** or **Enter** (`Shift+Enter` adds a new line). You should see your reply under **Comments** as **You**, with a note under it that says where it went. | The task is | What your reply does | The note under it, for an agent named Ada | | - | - | - | | In progress, with a run working on it | Goes into the run. Clawboo Native, Claude Code, Codex and OpenClaw take it before their next step without stopping. Hermes pauses its run, reads it and carries on | "Sent to Ada’s run", or "Ada will see this on the next run" when it could not get in | | In **Needs you** (for example **Failed**, **Stopped**, **Blocked** or **Not started**), or waiting in **To do** | Starts the agent on the task again, as **Retry** does | "Ada is starting on this" | | A plan step still waiting for an earlier task | Waits with the task | "Ada will see this when the task it waits on is done" | | Being checked (**Verifying**) | Waits for the agent to read after the check | "Ada will see this after the check" | | In **Backlog** | Waits for the agent to read when the task starts | "Ada will see this on the next run" | | **Done** or **Cancelled** | Is not sent, because the task cannot run again. The note offers **Send as a follow-up task** | "Not sent: the task is finished" | | With no agent | Is saved on the task only | "Not sent: no agent has this task" | When a reply starts a task again, Clawboo adds the note "Started again for a person’s reply." to its comments. Replies you send within 15 seconds of each other start one run, not one each. The note under a reply is not saved with it: it shows until you close the task. **Send as a follow-up task** gives the same agent a new task, named after the finished one (for example "Follow up: Draft the launch post"). Your reply becomes its brief, the finished task is its **Parent**, and it goes to the agent like a task you add with **New task**. The note then reads "Sent as a follow-up task for Ada". Every reply to a task that has an agent and is not finished also goes into the agent's mailbox. If a run cannot take it in time, the agent reads it from there when its next run starts. The agent receives the first 1,000 characters of a reply, and the task keeps all of it. How each runtime takes a reply mid-run, including older Codex versions, is in [Choose a runtime](/runtimes/index). ## The task drawer Click any card to open its drawer on the right. It reads like a document: the task's title, with its status, agent and team under it. Press **Escape** or click outside it to close it. To step through the card's column without closing the drawer, press `J` for the next task and `K` for the previous one, or use the up and down arrow buttons at the top of the drawer. The drawer shows where you are, for example "2 of 5". The keys do nothing while you type in a field or have a list open. Each section folds from its heading, and a section with nothing in it is not shown. Top to bottom: | Section | What it shows | | - | - | | Needs you | Only for a task that waits on you: why, with **Retry**, **Assign to…** (**Give to someone else…** when an agent is bound to the task) and **Dismiss** | | The agent's report | What the agent reported, with its formatting, titled with its name, for example **Rin's report** | | **Brief** | The task's details, when they say more than its title | | **Properties** | **Status** (which you can change), **Runtime**, **Cost**, when it was **Added** and **Finished**, its **Parent** for a subtask, and the work it is **Part of** | | **Verification** | After a check: **Passed**, **Failed** or **Passed with debt**, the check command and who chose it, who reviewed the work, and the latest attempt's findings | | **Workspace** | The task's own git checkout, when it has one. See [Review what an agent built](#review-what-an-agent-built) | | **Run history** | Every run: its runtime, outcome, time, cost and tokens, and the error when it failed | | **Comments** | Your replies and actions (**You**) and the notes Clawboo writes (**Clawboo**), then a box to reply to the agent. See [Steer a running task](#steer-a-running-task) | | **Activity** | The task's live tool calls, results and errors | Click the **Parent** to open that task in the drawer. A run that is still going can show OpenClaw as its runtime in **Run history** until it ends. The drawer's cost is the same total as the card's, to four decimal places, or `free` for a task that ran only on a local model. An image in the agent's report or its comments shows as a link named after its alt text, such as **Image: sales chart**. It opens in a new tab only when you click it. Clawboo never loads it by itself, so an agent cannot send data out in an image's address without your click. An address that is not a web or same-site one is only named, not linked. ![The task drawer open over the Kanban board: Rin's report, the brief, the task's properties, and a passed verification](https://mintcdn.com/privatedocs/tuYc7V3BIxpSQNHP/images/task-detail-drawer.png?fit=max\&auto=format\&n=tuYc7V3BIxpSQNHP\&q=85\&s=40672572815d1e80725d319523f5ff2e) ### Change a task's status Use **Status** in the drawer's **Properties**, or drag the card to another column. Both follow the same rules: * Only moves the board allows are offered. While you drag, columns that will not take the card are dimmed, and so is **Needs you**. Done and Cancelled cards cannot be moved. * **In progress** is set only by an agent's run, so you cannot move a card there by hand. * Moving a card to **Done** or **Cancelled** from a day other than today asks which day it finished: **Today**, the day it sits on, or **Another day**. * Cancelling always asks first, because it cannot be undone. Later plan steps that were waiting on the task are cancelled with it. * If an agent is still working on the task, the prompt says so. Moving the card to Done, Cancelled or To do stops that run. When an agent handed the task out, that agent is told a person decided it. * Your move stands. After you move a card, its agent cannot change the task's status, block it or unblock it until a new run starts on the task. The agent is told the status is yours to set and not to try again. A run that starts after your move works the task as usual. * A card moved back to **To do** goes back to its agent's queue, and a run that was going starts over from the beginning. A card with no agent waits in **Needs you** as **Unassigned**. * If the task's stored check result cannot pass, moving it to **Done** asks you to confirm with **Complete anyway**. The override is recorded in the audit log. Cards in **Needs you** move with their own buttons instead of by dragging. With a mouse, drag a card from anywhere on it. To drag with the keyboard, focus a card's handle and press **Space**, choose a column with the arrow keys, then press **Space** to drop it or **Escape** to cancel. On a touch screen, drag a card by its handle. ### Review what an agent built The **Workspace** section shows only for a task that ran in its own git worktree: a separate checkout of a repository, made for that task. That happens for runs started through the API with a repository path, such as an agent routine created through the API. Work handed out in team chat, tasks you give from the board, and routines you create in the dashboard have no worktree, so their drawer has no **Workspace** section. Those runs start in the team's folder instead, which the agent's own **Workspace** tab shows under **Details** on its page. See [Worktrees and handoff](/concepts/worktrees-and-handoff). For a task with a worktree, the section shows: * the **Branch** (`clawboo/task-<id>`), the **Worktree** folder, and **Changes**: how many files changed, and the lines added and removed; * under **System-of-record files**, the files Clawboo keeps in the worktree to track the task, such as `TASK.md` and `VERIFICATION.md`, each folded; * the full **Diff** against where the branch started; * a live view of the worktree's files, with change badges and what the agent touched last, when the task has an agent; * a **Preview** of the page the agent built. The **Preview** shows the worktree's `index.html` once the agent writes one, served as static files. Scripts do not run in the preview, so an app that needs JavaScript shows only its static shell. **Open** shows the page in a new tab. The preview checks for changes every 10 seconds while it is on screen. Clawboo does not merge the branch. Review it and merge it yourself. ### When work is checked Clawboo checks an agent's work in one case only: a run in its own git worktree that changed files. The check runs the task's verify command and, for a larger change or a handed-off task, a separate review by a model. Clawboo does not know your project's test command: the check fails until the `VERIFY_CMD` line in the worktree's `init.sh` names one. Work handed out in team chat, tasks you give from the board, and routines you create in the dashboard are not checked. A task with no check result reaches **Done** normally. While a check runs, the card shows **Verifying**. A pass lands in **Done**, and a failed check sends the work back to the agent for another attempt. When the attempts run out, or the work passes with debt, the task goes to **Needs you**. It shows **Needs review** if its result cannot pass, or **Blocked** if it passed with debt. **Retry** runs the task again and clears the old result. See [Verification](/concepts/verification). ## Routine runs In **Today**, **Week** and **Month**, the upcoming runs of your [routines](/using/routines) appear in **To do** on the days they will run. **Kanban** has no days, so it shows no runs. A run is not a task yet, so it is drawn as a dashed strip with the routine's name and time, not as a card. The next run on screen is drawn solid. A routine that runs several times a day shows once that day, with the number of runs. Click a run to see the routine's schedule in plain words, where it goes, its next runs, and what happens when it runs. **Run now** starts it at once, and **Open routine** opens it to edit, pause or delete it. When an agent routine runs, it becomes a task card on that day. A team routine posts to the team's General chat, and the tasks the team creates appear as they are made. Only routines that will run show runs. A paused routine, one halted after a failed run, and a one-time routine that already ran have none, and neither do the OpenClaw Gateway's own scheduled jobs. **Routine runs** under **Display** shows or hides the runs, and its note says how many are coming up on screen. Times are in your local time, and when the server runs in another time zone, the schedule names that zone. ## Views and the calendar Pick a view from the view menu at the right of the toolbar, which names the view you are on, or press its key: | View | Key | Rows | | - | - | - | | **Kanban** | `K` | One row with every task, newest first. It has no days and no routine runs. This is the default | | **Today** | `D` | Today only, with the Earlier row above it. Every card shows | | **Week** | `W` | The seven days of a week, starting on your locale's first weekday | | **Month** | `M` | Every day of a month | In a calendar view, a card sits on one day: * Open work sits on the day it was added, and stays on that row as it moves from Backlog to To do to In progress. * Finished work sits on the day it finished. * Open work from before the range on screen is gathered in an **Earlier** row at the top, so nothing in play is hidden. It opens by itself when it holds running or waiting work. The board remembers your view and your **Display** choices in this browser. The filter is not remembered. > [accordion] Reading the calendar > > * Lanes and cards are neutral. Each column's title carries its status mark in the column's color, and Needs you is amber. > * Today's date sits in a red circle. Weekends and the Earlier row are shaded. > * Pick a date in the day column to light its whole row. Pick it again, or press **Escape**, to put it out. > * A day with nothing on it folds into a thin line, and empty days in a row share one line. Click the line to open those days. Today never folds. > * A cell shows three cards (two in Month view), then **+N more**, which opens the rest in place. **Today** and **Kanban** show every card. > * A card an agent just added or moved fades in, so you can see what changed. > * A move you make shows one message saying what happened. When the card went to a day the board is not showing, the message has a **Show** button that takes you there. > [accordion] Moving through time > > * In **Week** and **Month**, the **Today** button goes to the current week or month and brings today's row into view. While today is out of sight, a small arrow on the button points the way. > * The arrows beside it step one week or month back or forward. > * Click the range title, for example "Oct 25 to 31, 2026", to pick a date. A dot marks each day that holds a task. In **Today** view, picking a date opens that date's week. > [accordion] Keyboard shortcuts > > With the board focused, these keys work. They do nothing while you type or while a dialog or the task drawer is open. > > | Key | What it does | > | - | - | > | **C** | Open **New task** | > | **K**, **D**, **W**, **M** | Switch to Kanban, Today, Week or Month (**A** also opens Kanban) | > | **T** | Go to today (calendar views) | > | **Left arrow**, **Right arrow** | Step one week or month back or forward (Week and Month) | > | **/** | Open Find | > | **Escape** | Put out a lit day | > > **Cmd+K** (**Ctrl+K** on Windows and Linux) opens Find from anywhere on the board, even while you type in a field, but not while a dialog is open. In the task drawer, **J** and **K** step to the next and previous task. Every shortcut is listed in [Keyboard shortcuts](/reference/keyboard-shortcuts). > [accordion] Find > > The search button in the toolbar, **/** or **Cmd+K** opens **Find**. Type part of a task's title, the name of its agent, words from its details, or the start of its id. Routines match by name, except on **Kanban**. In a calendar view, results are grouped by where they sit against the range on screen, for example **In this week**, **Before this week** and **After this week**, then **Routines**. > > * **Enter** shows the task: the board moves to its day and highlights the card. > * **Shift+Enter** opens the task's drawer. > * **Enter** on a routine opens the routine. > * **Escape** closes Find. > [accordion] Since you last looked > > Come back to the board after being away, and a chip beside the team filter says what changed meanwhile, for example "Since 2:10 PM: 3 done, 2 started, 1 new". It shows only when something changed. Cards that changed carry a small dot until you look at them. **Show** brings the oldest change you have not seen into view, and **Mark seen** clears the chip and every dot. Changes that sent work to **Needs you** are counted but not dotted, because Needs you lists them already. ## Tasks in team chat The same tasks appear in the team chat they came from: as cards in the conversation, and in the chat's **Tasks** card. General shows the tasks handed out in General, and each room shows only its own. The expand icon on a chat card, and **Open on Board** in the **Tasks** card, open the task here with its drawer open. See [Team chat](/using/team-chat). ## Troubleshooting > [accordion] A task has been In progress for a long time > > If an agent's run stops reporting, Clawboo ends it after a while and releases the task. The task then runs again by itself, or waits in **Needs you** as **Timed out**. The exact waits are in [Limits and defaults](/reference/limits-and-defaults). If the agent is working but slowly, reply on the task to steer it (see [Steer a running task](#steer-a-running-task)), or press **Stop** in its team chat. > [accordion] A task reached Done with no check mark > > That is expected for most tasks. Clawboo checks work only for a run in its own git worktree that changed files, and a task with no check result reaches Done normally. See [When work is checked](#when-work-is-checked). > [accordion] I cannot move a card to In progress > > Only an agent's run puts a task in progress, so the board does not offer that move. To start a task, move it to **To do** with an agent bound to it, or use **Retry** or **Assign** in **Needs you**. > [accordion] Cards are missing from the board > > Check the **Filter** button: a number on it means a filter is on, and **Clear filters** turns it off. The agents-working chip turns on **Running now** too. Also check the team filter beside the **Board** title, and turn on **Cancelled column** under **Display** to see cancelled work. > [accordion] A routine's runs are missing from the calendar > > Routine runs show only in **Today**, **Week** and **Month**, never on **Kanban**. Check that **Routine runs** under **Display** is on and that the routine is active in **Settings > Routines**. A paused routine, one halted after a failed run, and a one-time routine that already ran show no runs. > [accordion] The board says it couldn’t load > > The board shows "Couldn’t load the board." when its first read fails. Click **Retry**. If it keeps failing, check that the Clawboo server is running; see [Troubleshooting](/help/troubleshooting). > [accordion] Under the hood > > * The board reads `GET /api/board`, every five seconds while the tab is visible. The drawer reads `GET /api/board/:taskId`, `GET /api/board/:taskId/executions` and `GET /api/board/:taskId/workspace/detail`. The preview is served from `GET /api/board/:taskId/preview/`. > * **New task** is `POST /api/board` with an `assigneeAgentId`. It reads each agent's role from `GET /api/teams/member-roles`. **Retry**, **Assign** and replies are `POST /api/board/:taskId/retry`, `/assign` and `/comments`; the reply's answer carries a `delivery` field that says where it went. A status change is `PATCH /api/board/:taskId`, and a confirmed **Complete anyway** sends `humanOverride`, which is written to the audit log. > * A status change you make records its time on the task (`personMovedAt`). Until a run starts after that time, the agent's `update_task_status`, `block_task` and `unblock_task` tools are refused. See the [tasks tools](/reference/mcp-tools#tasks-server). > * The view and the **Display** choices are stored in this browser's local storage under `clawboo.board.calendar`, and the agent you last gave a task to, per team, under `clawboo.board.new-task-agents`. > * Full request and response shapes are in the [Board API](/reference/rest-api/board). How the board stores tasks and recovers stuck work is in [Board internals](/internals/board-internals). ## Next steps * [Team chat](/using/team-chat): give a team work and follow it beside the conversation. * [Verification](/concepts/verification): when work is checked, and what to do with a result that cannot pass. * [Worktrees and handoff](/concepts/worktrees-and-handoff): where an agent's file changes go. * [Routines](/using/routines): set up the recurring work that shows on the calendar. * [Approvals](/using/approvals): answer the requests that appear in Needs you. * [The board](/concepts/the-board): statuses, dependencies and recovery behind the cards. # Boo Zero, your team leader Source: https://docs.claw.boo/using/boo-zero How Boo Zero leads every team, how Clawboo picks it, what it reads, and how to use its own chat, its display name and the briefs. Boo Zero is your team leader, and it leads every team. When you send a message in a team's chat without @mentioning anyone, Boo Zero answers it or hands the work to the right teammate. Each hand-off becomes a task on the board. Boo Zero belongs to no team, so you never add it to a team or a room. It still leads **General** and every room of every team that has at least one member. Use this page to see how Clawboo picks Boo Zero, what Boo Zero reads, and how to use its own chat, its display name and the briefs. You can spot Boo Zero by its red Boo, which never wears an accessory. It sits at the top of the team rail, and every team on the Org chart branches off it. ![The Org chart: Boo Zero at the top, leading the Growth, Support and Platform teams](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/ghost-graph.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=34b71bf37bb52bcf2ad5ea30a3ffa335) ## How Clawboo picks Boo Zero Your install has one Boo Zero. Clawboo picks it in this order: 1. **An agent designated as Boo Zero.** The dashboard does this in one case: if you set up Clawboo with **Sign in with ChatGPT**, the lead agent of the team you deploy during setup becomes Boo Zero. It does not replace a Boo Zero you designated or a Clawboo Native one. You can also designate any agent through the API (see **Under the hood** at the end of this page). 2. **A Boo Zero on Clawboo Native that Clawboo creates for you.** It appears once you have a Clawboo Native agent on a team and a connected provider (an API key or a local Ollama). It is named **Boo Zero** and starts on your default Clawboo Native model. 3. **Your OpenClaw Gateway's default agent**, when there is no Clawboo Native Boo Zero. This is the case in an install that uses only OpenClaw. With none of these, there is no Boo Zero. Each team's chats are then led by the team's own [Leader](/using/teams), or by its first member. Boo Zero can change as your install grows. Say you use OpenClaw, then put a Clawboo Native agent on a team with a provider connected. Clawboo creates a Clawboo Native Boo Zero, which takes over from the Gateway's default agent. ## Boo Zero's own chat Click the mascot at the top of the team rail. Boo Zero's page opens and works like any [agent's page](/using/agents); the agent column hides while you are there. The message box reads **Ask me anything… use @ to tag a team**. Use this chat to ask Boo Zero about your teams. Every message you send here carries a fixed set of rules that names Boo Zero and tells it how to lead. You cannot edit them. To give a team work, send the request in that team's chat instead. Boo Zero leads there, and its hand-offs become tasks on the board. A Clawboo Native Boo Zero cannot hand out work from its own chat. ### Ask about one team with @TeamName Start your message with @ and the team's name, or click the team in the **Teams** row above the message box: ```text theme={"theme":{"light":"github-dark","dark":"github-dark"}} @Growth What is still open for Friday's review? ``` Clawboo then adds a snapshot of that team to this one message: its brief (the first 900 characters), a summary of its board, a few facts from its memory, and its recent chat. Only a mention at the very start of the message counts. ## What Boo Zero reads | Text | Where you edit it | When Boo Zero reads it | | - | - | - | | **Team rules** | The team's brief and rules, or `/rule` in a team chat | Every turn in that team's chats. Every teammate reads them too. | | **A team's brief** | The team's brief and rules | Only when you start a message in Boo Zero's own chat with @ and the team's name, and then only its first 900 characters. Team chats do not include it. | | **Its fixed rules** | Nowhere: they are built in | Every message in Boo Zero's own chat. | | **Global brief** | **Details > Brief** on Boo Zero's page | Never. Clawboo does not use the Global Brief yet. | In a team's chats, Boo Zero follows its own instructions. Each turn also gives it the team's rules, what you told the team about yourself, and who is on the team. See [What an agent sees](/concepts/agent-context) for the full list. Team rules and the brief are explained on [Teams](/using/teams#team-rules). On Clawboo Native, Boo Zero's own instructions are its SOUL.md, under **Details > Agent Files** on its page. You can read and change them there, but they tell Boo Zero how to hand out work, so change them with care. ## Set the display name and Global Brief **1. Open Boo Zero's page** Click Boo Zero's mascot at the top of the team rail. **2. Open the Brief section** In the **Details** card, click **Brief**. If **Details** is closed, open it with the folder icon in the page header. **3. Change the display name** Under **Display name**, type a name of up to 80 characters, then click **Save**. Reload the page to see the new name everywhere. **4. Edit the Global Brief (optional)** Edit the text, then click **Save**. **Regenerate from teams** rebuilds a draft that lists your teams; nothing changes until you click **Save**. The display name is the name the dashboard shows for Boo Zero, and the name its fixed rules use in its own chat. It starts as **Boo Zero**. While the OpenClaw Gateway is connected, saving also puts the new name at the top of Boo Zero's SOUL.md. > [!NOTE] > > Clawboo does not use the Global Brief yet. It saves what you write, but no chat or task sends it to Boo Zero, whatever the description above the box says. The default text includes a copy of Boo Zero's fixed rules; editing that copy changes nothing. For guidance Boo Zero must follow in a team, add a [team rule](/using/teams#team-rules). ## The team's own Leader Besides Boo Zero, a team can have its own Leader: a member whose name or role reads as a lead, marked with a **Leader** badge when you deploy the team. Boo Zero still leads. The Leader sits under Boo Zero on the team graph, and leads the team's chats only when your install has no Boo Zero. See [Teams](/using/teams). ## Troubleshooting > [accordion] A team is led by one of its members, not Boo Zero > > There is no Boo Zero yet. On Clawboo Native, Clawboo creates one once a Clawboo Native agent is on a team and a provider is connected. Connect an API key or a local Ollama in **Settings > Runtimes**. > [accordion] The Brief section is missing > > **Brief** appears only on Boo Zero's own page. Open the page from the mascot at the top of the team rail. If the dashboard has not identified Boo Zero yet, for example right after it connects, reload the page. > [accordion] Boo Zero still shows its old name > > The dashboard applies the display name when it loads. Reload the page. > [accordion] Boo Zero ignores what I wrote in the Global Brief > > This is expected: Clawboo does not use the Global Brief yet. Put the guidance in a [team rule](/using/teams#team-rules) instead. > [accordion] Under the hood > > * `GET /api/boo-zero/override` returns `overrideAgentId`, the `effective` Boo Zero, and the `tier` it came from: `override`, `native`, `openclaw`, or `null` when there is none. > * `POST /api/boo-zero/override` with `{ "agentId": "<agent id>" }` makes any agent Boo Zero, on any runtime. `{ "agentId": null }` clears the choice. A missing or archived agent returns 404. > * Display name: `GET` and `PUT /api/boo-zero/display-name/:agentId`. Global Brief: `GET` and `PUT /api/boo-zero/global-brief`. Team briefs: `GET`, `PUT` and `DELETE /api/boo-zero/team-briefs/:teamId`. > * See the [Boo Zero API reference](/reference/rest-api/boo-zero) for every field. ## Next steps * [Teams](/using/teams): team rules, the team brief and the team's Leader. * [Team chat](/using/team-chat): how messages reach Boo Zero and the team. * [Agents](/using/agents): the agent page that Boo Zero's page is built on. * [What an agent sees](/concepts/agent-context): everything in a turn, for Boo Zero and every teammate. * [Agent model](/concepts/agent-model): agents, runtimes and Boo Zero in one picture. # Track spend and set budgets Source: https://docs.claw.boo/using/budgets-and-spend See what your agents spend and where each figure comes from, then set a warn-only or hard-cap budget in the Governance panel. Clawboo itself is free. The providers you connect bill you for model usage, and Clawboo shows what your agents spend so you can keep track of it. A **budget** puts a dollar limit on an agent, a team or a chain of hand-offs. It warns you by default; a **hard cap**, which pauses work at the limit, is something you turn on. Use this page to read the spend figures, set a budget, and resume one that paused. [Budgets and guardrails](/concepts/governance) explains the model behind it. ## Where spend shows | Where | What it shows | What it covers | | - | - | - | | **Settings** > **Insights** > **Tokens Used** | Token counts for today, the last 7 days and the last 30 days, by team and agent | Every runtime | | A task's card and drawer on the [Board](/using/board) | What the task's runs cost. `~$0.02 est.` is an estimate; `free` is a local model | Board tasks whose runs recorded a cost | | **Settings** > **Insights** > **Governance** | Each budget's spend against its limit | Work charged to a scope that has a budget | | **Settings** > **Insights** > **Fleet** | **Spend · 24h**: the total cost of board tasks that changed in the last 24 hours | Board tasks | None of these is your bill. Your provider's own billing page shows what you were charged. When an agent runs on a ChatGPT plan, you pay the plan's fee, and Clawboo's figures are only a guide for your budgets. ## Where each cost comes from Some runtimes report what a run cost. For the others, Clawboo estimates it. Estimates count toward budgets the same way as reported figures, and the Board marks them **est.** | Runtime | What it reports to Clawboo | What Clawboo records | Marked est. | | - | - | - | - | | Claude Code | The dollar cost of each run | That figure | No | | Clawboo Native, on a priced model | Token counts for each model turn | The cost at Clawboo's list price for that model | No | | Clawboo Native, on any other model | Token counts for each model turn | The tokens priced from Clawboo's rate table, or at the default rate when the model is not in it | Yes | | Codex | Token counts, no dollar figure | The tokens priced at the default rate, because Clawboo has no prices for Codex models | Yes | | Hermes | Nothing | An estimate from the length of the prompt and the reply, in team chat, one-to-one chat, routines and API runs alike | Yes | | OpenClaw | No cost with its runs | An estimate from the length of the prompt and the reply, for every team-chat run and every routine run | Yes | * **The priced models** are claude-haiku-4-5, claude-sonnet-4-6, gpt-4o and gpt-4o-mini, plus the OpenRouter ids anthropic/claude-haiku-4.5 and openai/gpt-4o-mini. The default leader models, claude-sonnet-5 and gpt-5.4, are not among them. * **The rate table** lists Anthropic's Claude models plus gpt-4o and gpt-4o-mini. **The default rate** is $3 per million input tokens and $15 per million output tokens. * **Length estimates** count about four characters as one token, then apply the model's rate. * **Runs that end without a report.** A run that reports neither a cost nor token counts gets a length estimate, marked **est.**, in team chat, one-to-one chat, routines and API runs alike. That includes a Claude Code run that fails or is stopped before it reports its cost, and an OpenClaw routine run that fails, times out or is stopped. * **Local Ollama models cost nothing.** A run on a model your own Ollama serves is recorded at \$0, charges no budget, and reads **free** on the Board. That covers a Clawboo Native agent on the Ollama provider and an OpenClaw agent whose model in `openclaw.json` is an `ollama/` one. Ollama's cloud models (a `-cloud` tag) run on ollama.com, so they keep the default rate. Clawboo tells them apart by that tag alone, so a cloud model you copied under a name without it counts as free. * **Tokens Used** counts OpenClaw differently: it shows the token counts the OpenClaw Gateway records for each turn. Those counts and the budget's estimate for the same turn can differ. The other runtimes are listed with the figures their runs record. The runtime pages repeat these rules with their setup: [Clawboo Native](/runtimes/native), [OpenClaw](/runtimes/openclaw), [Claude Code](/runtimes/claude-code), [Codex](/runtimes/codex) and [Hermes](/runtimes/hermes). ## Read Tokens Used Open **Settings** (the **Settings** row in the sidebar, or `Cmd/Ctrl + ,`), then **Insights** > **Tokens Used**. * **Today**, **This Week** and **This Month** show token counts, not dollars. **This Week** covers the last 7 days and **This Month** the last 30, each counted from midnight on the computer running Clawboo. A count turns amber at 10,000 tokens and red at 100,000. * **Tokens by Team** lists every agent, grouped by team, with agents outside a team under **Unassigned**. Each row shows a bar for the agent's share and its input and output tokens; hover them for exact counts. * The chart below shows daily totals for the last 30 days, with empty days at zero. Tokens Used counts every runtime. An OpenClaw agent is counted turn by turn from the Gateway's own token counts: its one-to-one chat, its team-chat turns and its routines. Clawboo Native, Claude Code, Codex and Hermes agents are counted run by run: Board and API runs, routines and team-chat runs, but not their one-to-one chat. A run that reported no token counts is counted from the length of its prompt and reply. Work from before this was added is not backfilled, and an OpenClaw agent that runs on your Gateway but is not in Clawboo is not counted. The panel loads its figures once, when you open it. Close it and open it again to see new usage. ## What counts against a budget A budget counts spend only from the moment you create it, and only for its own scope: * **Team chat**, meaning General, rooms, team routines, tasks you give an agent on the Board, and the tasks a leader hands off from there: charged to the agent and the team, and to the mission when the run works on a task. * **Agent routines and runs started through the API** on Clawboo Native, Claude Code, Codex or Hermes: charged to the agent, the mission and the team. A run that reports no cost is charged an estimate when it ends. * **OpenClaw agent routines**: charged to the agent, the mission and the team, from an estimate when the run ends, whether it succeeded, failed, timed out or was stopped. * **One-to-one chat with a Clawboo Native, Claude Code, Codex or Hermes agent**: charged to that agent only. * **One-to-one chat with an OpenClaw agent**: shows on **Tokens Used**, but no budget is charged. A **mission** is one chain of hand-offs, counted on its top task: the first task in the chain. ## Set a budget ### Create the budget **1. Open Governance** Open **Settings** > **Insights** > **Governance**. The **Budgets** section lists your budgets, or reads **No budgets yet**. **2. Pick the scope** In the form below the list, pick **agent**, **team** or **mission**. **3. Pick who it covers and the limit** In the next list, choose the agent, team or board task by name; type to search. Agents show their team, so two agents with the same name stay apart, archived teams are left out, and for a mission the list holds the top-level board tasks, newest first. Enter the limit in dollars in the next field, which reads **warn at $** (or **cap $** once you pick a hard cap). The smallest limit is \$0.01. If what you picked already has a budget, a line under the form says so, for example `This already has a hard cap budget of $5.00.`, and the form switches to that budget's mode. **Set budget** then changes that budget instead of adding a second one. **4. Pick the mode** Pick **warn only** (selected at first, unless what you picked already has a budget) or **hard cap**. See the next section for the difference. **5. Save it** Press **Set budget**. You should see a row with the scope, the agent, team or task by name (or its id, once it is deleted), a **warn** or **cap** pill, the status **active**, the spend against the limit (for example `$0.00 / $5.00`) and a bar. The list refreshes every 5 seconds while the panel is open. If no row appears, Clawboo refused the budget, for example because the limit was \$0. The form clears without a message, so check the list. A budget cannot be deleted. To stop one from pausing work, raise its limit, or save it again as **warn only**. ## Warn only or hard cap | Status reached | **warn only** (the default) | **hard cap** (you opt in) | | - | - | - | | Under 80% of the limit | **active** | **active** | | 80% or more | **soft\_capped** (amber) | **soft\_capped** (amber) | | 100% or more | Stays **soft\_capped**. Nothing pauses | **paused** (red), and Clawboo stops work as described below | Hover a row's mode pill to read it in a few words: **warn** says "Track-and-warn: never auto-pauses", and **cap** says "Hard cap: auto-pauses at 100%". When an agent routine, a run started through the API, or an OpenClaw routine takes a budget past 80% or 100%, Clawboo also writes a `budget` entry to the audit log. Spend from team chat and one-to-one chat moves the bar and the status, but writes no audit entry. ## What a hard cap stops A paused hard cap refuses new runs on every path below. Whether it also stops a run that is already going depends on where the work runs: | Where the work runs | Before a run starts | While a run is going | | - | - | - | | Team chat: General, rooms, team routines, Board tasks and hand-offs | The run is refused | A Clawboo Native run on a priced model stops after its next model turn. Every other run is charged when it ends, so it finishes | | Agent routines and API runs on Clawboo Native, Claude Code, Codex or Hermes | The run is refused | A run that keeps working past the cap is stopped. Claude Code and Codex report their cost only as the run ends, so their work is already done: the result is kept and posted, and the next run is refused. A run with no cost report is charged an estimate when it ends | | OpenClaw agent routines | The run is refused | The run finishes. Its estimate is charged when it ends, whether it succeeded or not, and the next run is refused | | One-to-one chat with a Clawboo Native, Claude Code, Codex or Hermes agent | The turn is refused when the agent's own budget is paused | A Clawboo Native run on a priced model stops after its next model turn. Every other run is charged when it ends, so it finishes | What you see when a hard cap stops work: * **A run started through the API** that a cap stops is cancelled, and its task goes back to **To do** with the comment `Auto-paused: <scope> budget reached. Raise the cap (or resume) to continue.` A run refused before it starts answers `422` with the reason `budget_paused` and leaves the task as it was. * **A run whose last cost report crossed the cap** (the usual case for Claude Code and Codex) had already ended, so it is not stopped: its result is posted, its task moves on as usual, and the comment reads `Budget cap reached (<scope>) by this run's final cost report. The run had already ended, so what it produced is kept; the next run is refused until you raise the cap or resume.` * **An agent routine** counts the refused or stopped run as a failure. The routine stops until you resume it, and its task waits in **Needs you**; see [When a routine stops](/using/routines#when-a-routine-stops). * **An OpenClaw routine** whose estimate crosses the cap leaves the comment `Budget cap reached (<scope>). This run is recorded; the next scheduled fire is blocked until you raise the cap or resume.` * **Team chat and one-to-one chat**, before a run starts: Clawboo checks the agent's budget, the team's and, for a run that works on a task, the mission's (one-to-one chat checks only the agent's, the one budget it is charged to). When a hard cap among them is paused, nothing runs, and the chat shows `The <scope> budget is paused at its hard cap, so this run did not start. Raise the cap or resume it in Governance to continue.` A handed-off task, or a task you gave an agent on the Board, is not started either: it waits in **Needs you** with that sentence in a comment, and for a hand-off, the agent that handed it off is told. * **A team routine** whose message is refused that way counts as a failed run with that sentence as its error, and stops until you resume it. If the lead was busy when the routine fired, the fire already counted as posted, so the refusal shows only in the chat. * **Team chat and one-to-one chat**, during a run: a stopped run that had not replied yet leaves the message `The run failed: the run was stopped by a budget cap`. In team chat, a handed-off task it was working on waits in **Needs you**. A run that is already going when its budget pauses usually finishes. To end it at once, press **Stop** in the team's chat. [What a hard cap stops](/concepts/governance#what-a-hard-cap-stops) has the full rules. ## Resume or raise a budget A paused budget shows a **Resume** button on its row. * **Resume** puts the budget back to **active** at once, without changing the limit. If spend is still at or over the limit, a message warns you and the row shows **will re-pause**: the next recorded spend pauses it again. * **Set cap** changes the limit. Type the new amount in the row's **new cap \$** field and press **Set cap**. It keeps the row's mode. A limit above the current spend un-pauses the budget: it reads **active** again, or **soft\_capped** while spend is still 80% of the new limit or more. To give a paused budget room, use **Set cap** with a higher limit. Then restart what it stopped, for example with **Retry** on its task in **Needs you**, **Resume** on the routine under **Settings** > **Workspace** > **Routines**, or by sending your message again. You can also change a budget from the form below the list: pick the same agent, team or task, and the form switches to that budget's mode. Enter the new limit, pick the other mode if you want to change it, and press **Set budget**. A save that names no mode, such as a `POST /api/governance/budgets` without `mode`, keeps the budget's mode. ## Try a budget out first To see how a budget behaves without pausing anything, start with **warn only**: **1. Set a small warn-only budget** Create a **warn only** budget with a small limit, such as \$0.25, on one team. **2. Use the team** Give the team some work in its chat, then watch the budget's row in **Governance**. The bar fills as runs are charged. **3. Watch the status** At 80% the status turns **soft\_capped** (amber). At 100% it stays amber, and nothing pauses. **4. Switch to a hard cap when you are ready** In the form, pick the same scope and the same agent, team or task, enter the limit you want, pick **hard cap**, and press **Set budget**. The spend so far is kept, so if it is already at or over the new limit, the budget pauses at once. You should see the **cap** pill on the row. ## Read the audit log The **Audit log** at the bottom of **Governance** keeps a record of what Clawboo's guardrails did, newest first, up to 200 entries at a time. Each entry shows the time of day, the event type, the start of the agent's id and a one-line summary. Narrow it with the **agent id** box (the full id), the event type list and **All**, **1h**, **24h** or **7d**. It reloads when you change a filter or press **Refresh** at the top of the panel. The event types that matter for spend: | Event type | What it records | | - | - | | `budget` | A budget passing 80% or 100% on an agent routine, an API run or an OpenClaw routine, and each run a hard cap auto-paused or finished past | | `cap_hit` | A team-chat turn that tried to start more hand-offs than the fan-out cap allows | | `circuit_break` | A circuit breaker stopping an agent routine or API run that was going nowhere | | `approval` | Decisions on risky hand-offs, and unanswered approvals that Clawboo's cleanup expired | Resuming a budget writes no entry, and neither does a run a paused budget refused, or spend in team chat or one-to-one chat. ## The rest of the Governance panel * **Caps (enforced in code)** shows three chips you cannot change. **max spawn depth** 2 means a task can have at most two tasks above it, so a chain of hand-offs is three tasks deep. **max fan-out** 8 is the most hand-offs one team-chat turn can start. **per-node cost** names a per-run cost limit that only a routine set up through the API can carry; the chip is not a limit in force. [Budgets and guardrails](/concepts/governance#caps-on-delegation-and-task-creation) explains each one. * **Approval queue** lists every pending approval from every team. See [Approvals](/using/approvals). ## Troubleshooting > [accordion] My budget passed 100% and nothing stopped > > A **warn only** budget never pauses anything; switch it to **hard cap** (see [Resume or raise a budget](#resume-or-raise-a-budget)). A hard cap refuses new runs, but a run already going stops partway only on Clawboo Native with a priced model. Other runs finish, and their cost is charged when they end, so the budget can end up past 100%. See [What a hard cap stops](#what-a-hard-cap-stops). > [accordion] The budget is paused, but the team still runs > > New runs are refused once a hard cap pauses, but a run that was already going finishes, unless it is Clawboo Native on a priced model, which stops after its next model turn. Press **Stop** in the team's chat to end what is running. If new messages still get answers, check the budget's mode pill: a **warn** budget never pauses. > [accordion] I resumed a budget and it paused again at once > > Spend is still at or over the limit, which the **will re-pause** pill on the row tells you. Raise the limit with **Set cap**, then resume what it stopped. > [accordion] My hard cap turned into warn only > > Someone saved it again with **warn only** picked, or sent `"mode": "warn"` through the API. Pick the same scope and the same agent, team or task in the form, pick **hard cap**, and press **Set budget**. Use **Set cap** on the row whenever you only want a new limit. > [accordion] Set budget did nothing > > With no agent, team or task chosen, or a limit that is not a number, the button does nothing. With a limit of $0, Clawboo refuses the budget and the form clears without a message. Enter a limit of at least $0.01. > [accordion] The budget's bar never moves > > A budget made through the API for an id that matches nothing is never charged, because the API accepts any id. A **tenant** budget is never charged either. A budget also counts only spend after it was created, and some work charges no budget at all: one-to-one chat with an OpenClaw agent, and anything on a local Ollama model, which costs nothing. > [accordion] Couldn't load budgets. > > The panel could not read the budget list from Clawboo's server. Press **Retry** in the alert. If the alert stays, check that Clawboo is still running. > [accordion] Tokens Used shows zero for an agent > > Tokens Used lists Clawboo Native, Claude Code, Codex and Hermes runs only from the release that added them, so earlier work reads zero. An OpenClaw agent's turns count only when Clawboo's server sees them and can match them to an agent in Clawboo. > [accordion] An Ollama run shows a cost > > A model your own Ollama serves is free, so its runs read **free** and charge no budget. A cost means the run was not recognized as local: one of Ollama's cloud models (a `-cloud` tag), which runs on ollama.com, or an OpenClaw agent whose model in `openclaw.json` is not an `ollama/` one. > [accordion] The bar is amber, but the audit log shows no warning > > Spend from team chat and one-to-one chat updates the budget's bar and status without writing an audit entry. Warnings appear in the log only for agent routines, API runs and OpenClaw routines. > [accordion] Under the hood > > * **Statuses and modes.** Budgets are stored with the modes `warn` and `cap` and the statuses `active`, `soft_capped` and `paused`. A `warn` budget never reads `paused`. Spend is kept in fractions of a cent, so many tiny costs add up, and each scope (agent, mission, team) is updated in its own write. > * **Budgets over REST.** `GET /api/governance/budgets` lists them. `POST /api/governance/budgets` takes `{ "scope", "scopeId", "limitUsdCents", "mode" }`, where `limitUsdCents` is a positive whole number. A new budget without `mode` is `warn`; saving an existing scope updates its limit, and its mode only when you send one. `POST /api/governance/budgets/:scope/:scopeId/resume` resumes a budget and answers `willRepause`; when spend is at or over the limit, an optional `graceUsdCents` raises the limit to the current spend plus the grace and keeps the mode. There is no delete route. > * **The audit log over REST.** `GET /api/governance/audit` takes `agentId`, `eventType`, `since` and `limit` (at most 1,000). > * **Another way to get a hard cap.** Creating a Clawboo Native agent through the API with `budgetUsd` also creates a hard-cap budget for that agent. > * **Run costs.** Each run records its cost with a source of `reported`, `local`, `token-estimate` or `char-estimate`. A task's total reads as an estimate when any of its runs is neither `reported` nor `local`, and as free when all of them are `local`. > * **Tokens Used** reads `GET /api/cost-records/summary`. Clawboo's server writes one cost record for each OpenClaw turn it can match to an agent in Clawboo, and one for each run of the other runtimes. > * Shapes and codes: [Governance API](/reference/rest-api/governance) and [Costs and fleet API](/reference/rest-api/costs). ## Next steps * [Budgets and guardrails](/concepts/governance): the model behind budgets, caps and circuit breakers, path by path. * [Approvals](/using/approvals): answer the requests in the **Approval queue**. * [Routines](/using/routines): resume a routine that a hard cap stopped. * [Board](/using/board): read what each task cost. * [Limits and defaults](/reference/limits-and-defaults): every threshold and waiting time in one place. # See what your agents can use Source: https://docs.claw.boo/using/capabilities The Capabilities panel lists every skill, tool and connector across your runtimes, and switches the ones Clawboo is allowed to change. The **Capabilities** panel is one list of everything your agents can use: skills, tools and connectors, grouped by the runtime that owns them. Use it to check what each runtime gives its agents, to find out why a row is greyed out, and to switch off the rows Clawboo is allowed to change. ## Open the panel Open **Settings** (the gear in the sidebar, or **Cmd/Ctrl + ,**) and choose **Capabilities** under **Workspace**. The header counts what it found (`N capabilities · M runtimes`) and has a **Refresh** button. Approval cards waiting on you also appear at the top of the panel; see [Approvals](/using/approvals). You can also start from a runtime: on the **Runtimes** page, open a runtime's diagnostics (the **i** button on its card) and press **View capabilities**. The panel opens showing only that runtime. ![The Capabilities panel in Settings, scrolled to the Claude Code group: its skills and tools with Ready pills, Disable on the two skills, and the built-in rows marked as managed by Claude Code](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/capabilities-panel.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=380d0877815d38e023f336b1d1e30ad7) ## Read the list Rows are grouped by runtime, in this order: Clawboo Native, OpenClaw, Claude Code, Codex, Hermes. When more than one runtime has rows, pills above the list (**All runtimes**, then one per runtime) narrow it to one group. Each row's icon shows its kind: a puzzle piece for a skill, a wrench for a tool, a plug for a connector. | Pill | Meaning | | - | - | | **Ready** | Available and on | | **Disabled** | Available but switched off | | **Pending auth** | Listed but waiting on a sign-in. Only Codex rows show it, and they cannot be switched here | | **Unavailable** | Something it needs is missing, such as a key. The row is greyed out | A row with no button reads **built-in, managed by** and the runtime's name: Clawboo can read it but not change it. ## What each runtime lists | Group | What it lists | Can you switch it here? | | - | - | - | | Clawboo Native | Clawboo's own built-in tools; for each Clawboo Native agent, its **memory MCP**, **tasks MCP** and **tools MCP** servers; every connector you connected, and each app reached through Composio | The built-in tools and the per-agent servers, yes. Connectors, no: use [Connectors](/using/connectors) or the graph | | OpenClaw | The tools on the OpenClaw Gateway's allow and deny lists, the Gateway's MCP servers and plugins, the Clawboo servers registered there, and one **Built-in tools** row | Only the Gateway's tools, and only while the Gateway is connected | | Claude Code | The four Clawboo servers attached to every Claude Code run, and one **Built-in tools** row | No | | Codex | The same four servers, marked **Pending auth**, and one **Built-in tools** row | No | | Hermes | The skills each Hermes agent wrote for itself, the servers Clawboo attaches to its runs, and one **Built-in tools** row | No | Skills you add to an agent are listed in the group of that agent's runtime. The panel does not say which agent a per-agent row belongs to; to see or change one agent's skills and connectors, open its ring on the [Org chart](/using/org-chart) or its page. > [accordion] About Clawboo's built-in tools > > The Clawboo Native group lists four built-in tools: `echo`, `note`, `web_search` and `delete_path`. They are simple tools that exercise Clawboo's approval checks. `web_search` reads **Unavailable** unless a Tavily key (`TAVILY_API_KEY`) is set, and neither it nor `delete_path` searches the web or deletes anything. For real web access, give an agent a browser or a search connector; see [Connectors](/using/connectors). ## Switch a capability on or off **1. Find the row** Use a runtime pill, or scroll to the group. **2. Press Disable or Enable** The row shows a spinner while Clawboo makes the change. **3. Check the pill** The panel reloads, and the pill flips between **Ready** and **Disabled**. If Clawboo cannot make the change, a message starting **Could not disable** (or **Could not enable**) says why, and nothing changes. What the switch does depends on the row: | Row | **Disable** does | **Enable** does | | - | - | - | | A built-in tool | Turns the tool off for every agent | Turns it back on | | An agent's **memory MCP**, **tasks MCP** or **tools MCP** | Takes that server's tools away from that one agent | Gives them back. For an agent on a team, **tasks MCP** comes back with read access to the board only, because the team's engine makes the board changes | | An OpenClaw Gateway tool | Puts the tool on the Gateway's deny list | Puts it on the allow list | | A skill | Removes the skill from that agent, so the row disappears | Not offered: add the skill again from the [Marketplace](/using/marketplace) or the graph | ## Skills A skill in Clawboo is a label for something an agent is good at, chosen from the Marketplace's built-in list. You add one from the **Skills** tab of the [Marketplace](/using/marketplace), or on the graph: pull a thread off a Boo, or press **+**, and pick from **Skills**. Adding a skill records it on the agent: it appears in this panel and as a tile on the agent's ring. It does not give the agent a new tool or change its instructions. Tools come from [connectors](/using/connectors) and from the runtime itself. Hermes is the exception in this list: the skills under the Hermes group are skill files a Hermes agent wrote for itself, which Hermes reads. Clawboo shows them and never changes them. > [accordion] Under the hood > > Every row has a manageability tier that decides whether Clawboo may change it: > > | Tier | Who owns it | Switch in the panel | > | - | - | - | > | Managed | Clawboo (its built-in tools, the per-agent servers, skills) | Yes | > | Written through the runtime | The runtime's own files, which Clawboo would edit on its behalf (Codex) | No: Clawboo makes no Codex changes, so these rows stay **Pending auth** | > | Runtime of record | The runtime, which Clawboo asks to make the change (OpenClaw) | Only where Clawboo can make that request: the Gateway's tools | > | Observe only | The runtime or a running connector | Never | > > The panel reads `GET /api/capabilities` and switches rows with `POST /api/capabilities/enable` or `/disable`, which refuse a row its tier forbids with `422`. When a runtime cannot be read, the server serves the rows it last read for it. The model behind the tiers is in [Capabilities](/concepts/capabilities), and the request shapes are in the [capabilities API reference](/reference/rest-api/capabilities). ## Troubleshooting > [accordion] A row is greyed out and reads Unavailable > > Something the capability needs is missing, such as an API key or a setting. Provide it, then press **Refresh**. The row offers no button until it is available. > [accordion] Codex rows read Pending auth and Enable is greyed out > > Clawboo lists Codex's servers so you can see them, but it makes no changes to Codex. The button's hint suggests running `codex login`; signing in does not change these rows. > [accordion] An OpenClaw server or plugin has no button > > Clawboo can switch only the Gateway's tools. Turn the Gateway's MCP servers and plugins on or off in OpenClaw itself; see the [OpenClaw runtime page](/runtimes/openclaw). > [accordion] Switching a Gateway tool fails with gateway\_disconnected > > Gateway tools are changed through the OpenClaw Gateway, so it must be connected. The panel keeps showing the rows it last read while the Gateway is down. Reconnect it, then try again. > [accordion] A warning says a runtime is unavailable and shows its last-known capabilities > > Clawboo could not read that runtime just now, for example because the OpenClaw Gateway is disconnected, so it shows the rows it read last time. Fix the connection, then press **Refresh**. > [accordion] The panel says it could not load the capability inventory > > The panel could not reach the Clawboo server. Press **Retry**. This is different from **No capabilities found**, which means the server answered with an empty list. ## Next steps * [Connectors](/using/connectors): connect tools and give them to agents. * [Marketplace](/using/marketplace): add skills, agents and teams. * [Approvals](/using/approvals): answer the cards at the top of this panel. * [Capabilities](/concepts/capabilities): the model behind the inventory and its tiers. * [Capabilities API](/reference/rest-api/capabilities): read and switch rows from scripts. # Control which commands agents can run Source: https://docs.claw.boo/using/command-permissions Let a Clawboo Native agent ask to run commands, choose when an OpenClaw agent asks, and review or revoke what it may run without asking. Two runtimes let you decide, from Clawboo, whether an agent may run shell commands on your computer: Clawboo Native and OpenClaw. Use this page to turn commands on or off for an agent, choose when it asks you first, and take back a permission you gave with **Always**. | Runtime | Out of the box | What you control here | | - | - | - | | Clawboo Native | Runs no commands | A switch that lets the agent ask before each command | | OpenClaw | **Ask for Unknown**, for agents you create in Clawboo | When the agent asks, and the list of commands it may run without asking | | Claude Code, Codex, Hermes | Runs any command without asking | Nothing: Clawboo runs them with their own approval prompts turned off | [What agents can do](/concepts/permissions) explains what each runtime can reach. When an agent asks, the request appears as an approval card; [Approvals](/using/approvals) covers the cards. ## Before you begin * Open the agent's page: click its Boo in the sidebar, or right-click it on the Org chart and choose **Open agent** (see [Agents](/using/agents)). * In the **Details** card, choose **Permissions**. It appears only for Clawboo Native and OpenClaw agents. ## Let a Clawboo Native agent ask to run commands **1. Open the setting** On the agent's page, choose **Details** > **Permissions**. You see **Running commands**. **2. Turn it on** Switch on **Let this Boo ask to run commands**. **3. Confirm** In the dialog **Let this Boo ask to run commands?**, press **Allow it to ask**. You should see **Saved**, and a note under the switch saying that commands run in the agent's working folder, as you, with access to the network. To stop it, switch it off. Runs that start after the change follow it. With the switch on: * **Every command asks first.** Nothing is remembered and there is no **Always**, so a command you allowed asks again the next time. * **Only runs with a working folder can ask.** Team chat, tasks you start from the Board and routines made in the dashboard work in the team's folder. A run inside a git repository, which you set up through the API, works in its worktree. One-to-one chat on the agent's page works in the team's folder too, and its requests wait above the message box. * **Never on Windows.** The switch is there, but Clawboo does not offer the agent a way to run commands. * **One program at a time.** A command is a program and its arguments, run without a shell, so pipes and redirects do not work. Programs that run other commands, such as shells, `python`, `node` and `npx`, are refused before you are asked; [What agents can do](/concepts/permissions#clawboo-native) lists them. * **At most 10 commands per run.** Each card waits 10 minutes, and the run waits with it. * **A refusal ends the asking.** After you choose **Don't allow**, or a card runs out of time, the agent cannot ask about another command for the rest of that run. * **The approved program runs, or nothing does.** If the program changes on disk while the card waits, Clawboo does not run it. > [!WARNING] > > An approved command runs on this computer as your user account, with network access, in the agent's working folder. The list of refused programs is a speed bump, not a boundary: ordinary programs such as `git` or `find` can still start others. Approve only commands you understand. ## Choose when an OpenClaw agent asks On the agent's page, **Details** > **Permissions** shows **Execution Permissions** with a **Command Execution** dropdown. The line under the dropdown describes the option you picked. | Option | The line under it | What happens | | - | - | - | | **Run Freely** | Executes commands without asking | No card. The agent runs what it decides to run | | **Ask for Unknown** | Asks approval for unlisted commands | A command that is not on the agent's list raises an approval card | | **Always Ask** | Asks approval for every command | Every command raises a card, and the Gateway never remembers one with **Always** | Pick an option and you should see **Execution permissions updated**. The agent follows it from its next message. * **The starting point.** An agent you create in Clawboo starts on **Ask for Unknown**. For an agent created in OpenClaw itself, the dropdown shows **Run Freely** until you pick an option, because Clawboo has no setting stored for it. * **Clawboo applies the change at the OpenClaw Gateway.** If the Gateway refuses it, for example while Clawboo is not connected to it, a message starting **Not applied.** appears and the dropdown goes back. Clawboo still keeps your choice in its own record, so after a reload the dropdown can show an option the Gateway is not using. Pick it again once the Gateway is connected. * **Changing the option keeps the list.** Commands the agent may run without asking stay on file whichever option you pick. To remove one, revoke it (below). * **A separate default.** The **Command Approval** card under **Settings** > **System** holds a default in OpenClaw's own config; see [System](/using/system). ## See what an OpenClaw agent may run without asking Below the dropdown, **Commands this Boo can run without asking** lists the agent's standing permissions. Each one comes from an **Always** you gave on a command card, or from OpenClaw's own settings. A row names the program and says how wide the permission is: | What the row says | What the agent may run without asking | | - | - | | One exact command, in one exact folder | That command, typed the same way, in that folder. **Always** creates this kind | | One exact command | That command, in any folder | | This program, matching arguments | That program, when its arguments match a pattern | | This program, any arguments, any folder | Anything that program can do. The row has a **Broad** pill | | Any command at all | Anything. The row has a **Broad** pill | | Part of another grant on this computer | Nothing by itself: it is one half of another permission | | Kept on file, but OpenClaw skips it | Nothing. The row has a **Not in effect** pill | A few rows carry more: * **and its companion rule**: one **Always** wrote two rules, and Clawboo shows them as one permission. It groups them only when the agent has exactly one of each; otherwise the companion shows as its own row with **Not removable here**. * On the narrowest kind: "clawboo cannot show which command: OpenClaw keeps only a fingerprint of it and the folder it ran in. The same command typed differently, or run elsewhere, asks again." * "Also granted to every Boo. Removing it here would not stop it." Its revoke button is turned off. When some permissions apply to every agent, a footer says how many and that they "are set outside clawboo and cannot be changed here." The list does not update by itself. It is read when you open **Permissions**, after a revoke, and when you press the **Refresh** icon beside the heading, so press it after answering **Always**. ## Revoke a permission **1. Pick the row** Click the trash icon on the permission's row. **2. Confirm** The dialog **Revoke this standing permission?** says the command will need your approval again the next time this agent runs it. For a grouped row, both rules go together. A command already approved and waiting to run may fail, because OpenClaw checks the whole rule set again before it starts. Press **Revoke**. **3. Check the result** You should see **Standing permission revoked**, and the row is gone. Clawboo reads the list again before it reports, so a row that is still there is still in force. Reading the list needs only Clawboo: it opens OpenClaw's own state database on this computer, read-only. Revoking goes through the Gateway, so Clawboo's server must be connected to it. To give a permission back, answer **Always** on the next card for that command; Clawboo cannot add or edit a permission directly. ## When the list says No standing permissions The empty list comes in two versions, and they mean different things: | The line under **No standing permissions** | What it means | | - | - | | When you answer Always to a command prompt, what you allowed appears here. | Clawboo read OpenClaw's permissions, and this agent has none | | Nothing on this computer has been granted a standing exec permission yet. | Clawboo found no OpenClaw permissions where it looks | Treat the second version with care. It is also what you see when the OpenClaw Gateway runs on another computer, or keeps its data in a folder Clawboo is not reading. In both cases the agent can have permissions this list cannot show. A red alert is different again. It starts **clawboo could not read this computer's permission list** and ends "Nothing here is safe to trust until it can be read again." Clawboo found the permissions but could not read them, or its server did not answer. The permissions on file still apply. Press **Refresh**; if the alert stays, check the permissions in OpenClaw itself. > [accordion] Where Clawboo reads the list > > Clawboo opens `state/openclaw.sqlite` inside OpenClaw's state folder, read-only. The folder is the first of: > > 1. `OPENCLAW_STATE_DIR`, `MOLTBOT_STATE_DIR` or `CLAWDBOT_STATE_DIR`, when set in the environment Clawboo starts in > 2. `~/.openclaw`, when it exists > 3. `~/.clawdbot` or `~/.moltbot`, when one of them exists > > If your OpenClaw keeps its state somewhere else, set `OPENCLAW_STATE_DIR` for Clawboo too. See [Data and state](/operating/data-and-state) for the other files Clawboo reads in OpenClaw's folder. ## Limits * **Same computer only.** The list reads OpenClaw's files directly, so it works only when the Gateway runs on the same computer as Clawboo. * **This agent only.** Permissions that apply to every agent show only as the footer count, and you change them in OpenClaw. * **Revoke only.** You cannot create, edit or narrow a permission here. **Always** on a command card is how Clawboo adds one. * **No live updates.** A permission you add by answering **Always** appears after you press **Refresh**. ## Troubleshooting > [accordion] The switch is on, but my Clawboo Native agent never asks > > The agent can ask only on a run with a working folder, and never on Windows. Team chat, one-to-one chat, Board tasks and routines made in the dashboard work in the team's folder, so the agent can ask there. A run that started before you switched it on cannot ask; send the next message, or start the task again. > [accordion] Not applied. saved in clawboo, but the Gateway did not accept it > > The Gateway refused the new **Command Execution** option, or Clawboo is not connected to it. The agent keeps its old setting. Check OpenClaw under **Settings** > **Workspace** > **Runtimes**, then pick the option again. > [accordion] The revoke button is greyed out > > Either the permission is also granted to every agent (the row says "Also granted to every Boo"), which you change in OpenClaw, or a revoke is still in progress. > [accordion] the Gateway accepted the change and still lists those grants > > Clawboo sent the revoke, and the Gateway's reply still contains the permission. Press **Refresh**. If the row is still there, the permission is still in force; try the revoke again. > [accordion] that grant is set for every Boo, not just this one, so it cannot be removed here > > The permission is in the set that applies to every agent, so removing it from this agent would not stop it. Change it in OpenClaw. > [accordion] those grants were already gone > > Someone or something removed the permission before your revoke landed. Press **Refresh** to see the current list. > [accordion] Under the hood > > * **Clawboo Native.** `GET` and `POST /api/agents/:agentId/shell` (body `{ "enabled": true }`) read and set the switch, stored as `tools.shell` in the agent's config. A missing value means off, and the route answers 400 for other runtimes. The tool exists only on runs with a working folder, never on Windows, and a run can ask at most 10 times. > * **OpenClaw setting.** `POST /api/exec-settings` saves Clawboo's record, then writes the agent's entry in the Gateway's approvals policy under OpenClaw's own agent id, with the values `off`, `on-miss` and `always`. A refusal answers 502 with `savedLocally: true`. **Run Freely** removes the agent's ask setting but keeps its list. > * **The list.** `GET /api/exec-allowlist?agentId=<id>` answers `ok` or `absent` (200), `not-applicable` for other runtimes, or `unreadable` (502, with the last known rule count). > * **Revoke.** `POST /api/exec-allowlist/revoke` with `{ "agentId": "<id>", "keys": [...] }`. Clawboo rewrites OpenClaw's whole permissions document and checks the Gateway's reply before it answers: 200 `revoked`; 409 `already-absent`, `blocked-wildcard` or `no-such-agent`; 502 `not-verified`. ## Next steps * [Approvals](/using/approvals): answer the cards these settings raise. * [What agents can do](/concepts/permissions): what each runtime can reach, with and without asking. * [Agents](/using/agents): the agent page and its **Details** card. * [OpenClaw](/runtimes/openclaw): set up and manage the OpenClaw Gateway. * [System](/using/system): the default **Command Approval** for OpenClaw agents. # Connect tools to your agents Source: https://docs.claw.boo/using/connectors Connect MCP servers and apps in Connectors, then give each one to the agents that should use it. Connecting alone gives no agent access. A connector gives your agents tools beyond chat: a browser, a database, Linear, Notion, Gmail. Each connector is an MCP server (Model Context Protocol, the common way to hand tools to an AI agent). Use this page to connect one, give it to the agents that need it, and take it back. Connecting and giving access are two separate steps. **Connect** starts the connector, but no agent can see or call its tools until you give it to that agent on the graph. ## Before you begin * Node.js on the machine that runs Clawboo. Most connectors start with `npx`, and the first start downloads the connector's pinned package. * For a connector you sign in to (Linear, Sentry, Stripe, Composio), a browser on that same machine. The sign-in page sends you back to a temporary address on that machine. * An account or key for the service, when the connector asks for one. ## Connect a connector **1. Open Connectors** Click **Connectors** in the sidebar, or press **Cmd/Ctrl + 3**. **2. Find the connector** Search by name, or pick a category. Connected ones come first, then the ones that need the least setup. **3. Press Connect** What happens next depends on what the connector needs: | The connector needs | What **Connect** does | | - | - | | Nothing (Playwright, Chrome DevTools, Context7, Knowledge Graph Memory, Sequential Thinking) | Starts it right away | | A sign-in (Linear, Sentry, Stripe, Composio) | Opens the provider's sign-in page in a new tab, then connects | | A key (GitHub, Notion, Figma, Supabase, Airtable and others) | Opens a field in the row. Paste the key and press **Save and connect** | | A folder or a database file (Filesystem, SQLite) | Opens a field with suggestions. Pick one or type a path, then press **Save and connect** | | Nothing Clawboo has reviewed (a community entry) | Shows the exact command it will run and asks you to confirm | **4. Check that it connected** The row shows a green tick, and a message confirms it with the connector's tool count and the reminder "Connect it to an agent to use it." Now give it to an agent: see [Give a connector to an agent](#give-a-connector-to-an-agent). A connector that cannot run on this install shows **Why not** instead of **Connect**. It says what is missing and offers a config block you can paste into another app that can run it. Keys and sign-in tokens go to Clawboo's encrypted vault on this machine, and no API sends them back to the browser. A key you exported in your shell (for example `NOTION_TOKEN`) is not picked up: enter it in Connectors. > [accordion] Suggestions for Filesystem and SQLite > > Filesystem suggests the folder you started Clawboo from, plus your Documents, Desktop and Downloads folders, each only if it exists. SQLite suggests up to five `.db`, `.sqlite` or `.sqlite3` files within two folder levels of where you started Clawboo. You can always type a path instead. > [accordion] GitHub needs a token, not a sign-in > > GitHub offers no sign-in that Clawboo can use, so its connector takes a personal access token. Create a fine-grained token, choose only the repositories your agents should work in, and give it Read and write on Contents, Issues and Pull requests. Paste it into the GitHub row. ## What the list holds The list reads top to bottom in four bands: | Band | What it is | | - | - | | **Popular** | Well-known connectors Clawboo has run and reviewed | | **More connectors** | The rest of the reviewed set, plus any server you added yourself | | **Reached through Composio** | Apps such as Gmail, Slack, Google Calendar and Jira, which you sign in to through Composio | | **From the community** | Servers from the public MCP registry that Clawboo has not reviewed | The reviewed set holds 20 connectors. The community band holds a snapshot of 400 registry servers, shipped with Clawboo, so the list works offline. It stays hidden until you press **Show 400 more** at the foot of the list or search for something; then it shows 60 at a time (**Show 60 more**, then **Show the last N**). ### Apps reached through Composio Clawboo cannot sign in to Google, Slack or Atlassian directly, so those apps go through [Composio](https://composio.dev), a separate service that signs you in to each app. **1. Add your Composio key** In the **Reached through Composio** band, press **Get a key** to open Composio's API keys page. Copy the project key (it starts with `ak_`), paste it into the **Composio key** field and press **Save**. You do this once. **2. Connect an app** Press **Connect** on the app and approve it in the tab that opens. Back in Clawboo, the app's row reads **Connected**. **3. Connect the Composio connector** Your agents reach every Composio app through the **Composio** connector in the **Popular** band. Press **Connect** on it once and sign in to Composio. **4. Give the app to an agent** Give the app to an agent the same way as any connector (next section): the graph's picker lists connected apps with the connectors. Each app an agent holds shows as its own tile, so you can see which agents can reach your email. Composio keeps each app's sign-in tokens on its own servers, and Clawboo holds only its own key and sign-in for Composio. Anything you connect there is reachable by Composio, not only by your agents. ## Give a connector to an agent You give access on a graph: the **Org chart**, a team's **Team graph** card, or the **Graph** card on an agent's page. Click a Boo to open its ring of tiles; each connector tile on the ring is one the agent can use. > [tab] Pull a thread off the Boo > > **1. Start from the Boo** > > Hover the Boo: a small **+** port appears with **Drag to connect** beside it. Drag from the port and let go on an empty spot. > > **2. Pick the connector** > > In the menu that opens, choose **Connectors** and pick one. A connected connector reads **Give access**. One that is not connected yet shows what it needs (for example **Turn on** or **Connect**), and Clawboo connects it first. Connectors that need a key or a folder say to set them up in Connectors first. > > **3. Check the ring** > > The Boo's ring opens and the new tile joins it. The agent has write access, and risky calls ask you first. > [tab] Place a tile, then drop it on a Boo > > **1. Add the tile** > > Press **+** on the graph, choose **Connectors**, and pick one. A loose tile marked **Not attached** appears. > > **2. Drop it on the Boo** > > Point at the tile: a port appears with **Drag onto an agent** beside it. Drag from the port and let go on the Boo that should have it. The tile joins the Boo's ring, with write access and risky calls asking first. > [tab] Share it from another agent > > **1. Drag an existing tile** > > On the ring of an agent that has the connector, drag from the small handle on the right edge of its tile and let go on another Boo. > > **2. Choose the access** > > The **Share** dialog asks for **Read**, **Write** or **Admin** access (see the table below) and starts on **Read**. Press **Grant read access** (the label follows your choice). The first agent keeps its own access. | Access | What the agent may do | | - | - | | **Read** | Look things up. Only tools that a reviewed connector marks as read-only; the agent does not see the rest | | **Write** | Create and update. Risky calls ask you first | | **Admin** | Also the tools a reviewed connector marks as destructive, such as deleting. Use sparingly | Pick **Write** for most connectors. Many tools do not say whether they only read, so a **Read** grant hides them. ### Who can use a connector * **Right after you connect it:** no agent. Its tools are hidden from every agent, and a call to them is refused. * **After you give it to an agent on the graph:** that agent only, with the access you chose. Every other agent still has no access until you give it to them too. * **The exception:** the two browser connectors, which every agent gets automatically (next section). ### Every agent gets a browser The two browser connectors, **Playwright** and **Chrome DevTools**, are connected for you the first time Clawboo starts, and every agent gets both with write access. OpenClaw agents get the tiles too, but cannot use them; see [OpenClaw agents and connectors](#openclaw-agents-and-connectors). In Chrome DevTools, actions that change something, such as opening a page or clicking, ask you first. Playwright marks each of those actions as destructive, so with write access an agent sees only Playwright's reading tools, such as a snapshot of the page. To let an agent act through Playwright, **Stop sharing** Playwright with it, then share it again from another agent's tile with **Admin** access. Each action still asks first. Each agent has its own browser profile, so two agents can be signed in to different accounts on the same site. The browser tiles sit on the agent's ring like any other connector, so you can take them away (next section), and a removed browser stays removed after a restart. ## See and take back access The graph is the list of who can use what: every tile on a Boo's ring is something that agent can reach. A tile that several agents share shows a small count, such as **x3**; hover it to highlight the others. Click a connector tile to see its toolbar: | Button | What it does | | - | - | | **Stop sharing** | Takes the connector away from this agent only. The message that confirms it offers **Undo** for a few seconds | | **Turn off** | Stops the connector for every agent. It stays off after a restart until you connect it again | | **Sign in** | Shown when the connector's sign-in has expired | | **Retry** | Shown when the connector reports an error. Reads its state again | | **Enable** / **Disable** | Shown on capabilities that a runtime lets Clawboo switch | In Connectors, **Turn off** appears when you hover a connected row. A connector you signed in to also has **Sign out** in its detail pane (click the row): it deletes the stored tokens and stops the connection. A server you added yourself has **Remove**. ## When a connector call asks you first Every call an agent makes through a connector passes Clawboo's checks first. When a call needs your answer, an approval card appears in **Needs you** on the board, in the chat where the agent is working, and in the **Governance** and **Capabilities** panels. If nobody answers within five minutes, the call is refused. See [Approvals](/using/approvals) for the cards themselves. Which calls ask depends on the connector: * **GitHub, Linear, Sentry, Airtable and Notion, and any server you added yourself, ask before every call.** They can read your private data, take in content from outside and send data out. That combination is how data gets leaked, so you cannot turn these questions off. * **Actions in apps reached through Composio ask every time too.** * **On the other connectors**, a call that only reads runs without asking when Clawboo reviewed the connector and the tool says it only reads. A call that changes something asks first when the connector can reach the internet. A tool the connector marks as destructive needs **Admin** access and asks first. Some cards offer **Do not ask again for this for 30 days**. Ticking it saves your answer for that tool on that agent, but a call that changes or deletes something still asks every time: a second check asks about those calls whatever you ticked. ## When an agent asks for a connector Agents are told which connectors are connected and which ones you could add. An agent that needs one it does not have says so and stops, and a card in the chat offers a button for each connector it named, such as **Connect Linear** or **Add key Notion**. The button opens that connector in Connectors. The card only takes you to the connector: connecting it there does not give the agent access. Give it to the agent on the graph, then press **Continue** on the card (in a team chat) so the agent picks the work back up. ## Add your own MCP server At the foot of the list, **Add your own MCP server** adds a server that speaks MCP over stdio, which means a program Clawboo starts on this machine. **1. Fill in the form** Enter a **Name**, the **Command** (an absolute path, or a program name found on your `PATH`) and its **Arguments**, separated by spaces. Arguments go straight to the program, never through a shell. **2. Add it** Press **Add**. The server joins **More connectors** as one of your own entries. **3. Connect it and give it to an agent** Press **Connect** on its row, then give it to an agent on the graph. The form has no URL field, so a remote MCP server cannot be added here. A community entry works the same way once added: **Connect** on it shows the exact command, package and version, and **I trust it, add it** makes it one of your own entries. Clawboo has reviewed neither kind, so it treats them as able to read private data, take in outside content and send data out, and every call they make asks you first. ## OpenClaw agents and connectors OpenClaw agents reach Clawboo's tools through one setting in the OpenClaw Gateway that all of them share, so Clawboo cannot tell which OpenClaw agent is calling. As a result, connectors you give an OpenClaw agent on the graph do not reach it, the browser connectors included. It can use apps reached through Composio, and each action in those apps asks you first. The Gateway's own MCP servers and plugins are separate: they are listed in the [Capabilities](/using/capabilities) panel. ## Keep connectors safe > [!WARNING] > > A local connector is a program running under your user account. Clawboo starts it with a short list of ordinary environment variables plus the keys you entered for it, never your AI provider keys, and passes its arguments without a shell. That is not a sandbox: the connector can still read any file your account can, including your SSH keys and Clawboo's database. Connect only servers you would be willing to run yourself. ## Troubleshooting > [accordion] An agent says a connector is connected but cannot use it > > Connecting makes a connector available; it gives no agent access. Agents are told which connectors are connected whether or not they hold one. Give the connector to that agent on the graph, then ask again. > [accordion] Connect fails with: needs npx, which comes with Node.js > > The machine running Clawboo has no `npx` on its `PATH`. Install Node.js from [nodejs.org](https://nodejs.org), restart Clawboo, and press **Connect** again. > [accordion] The first connect fails with: did not respond in time > > The first start of a connector downloads its package, which can take a minute on a slow network. The button reads **Working…** meanwhile. Press **Connect** again; the second start is faster. > [accordion] Connect fails with: did not accept that key > > The service refused the key or token. Copy it again in full, and check that it has not expired. Then open the connector's detail pane (click its row) and paste it under **What you gave it**. > [accordion] Connect fails with: accepted the key but refused the request > > The key works but lacks a permission. Check the scopes or permissions on the token you created at the service, for example Read and write on Contents, Issues and Pull requests for GitHub. > [accordion] Sign-in says: Your browser blocked the sign-in window > > Allow pop-ups for the Clawboo address in your browser, then press **Connect** (or **Sign in**) again. > [accordion] Saving the Composio key says: That is a Composio login key > > You pasted the key the Composio command line uses. The key this field needs starts with `ak_` and is on your Composio project's API keys page (**Get a key** opens it). > [accordion] A tile shows: Changed since you approved it > > Clawboo refuses that connector's calls because its command or its list of tools changed after you gave access. If you changed its key, folder or command yourself, turn it off and connect it again in Connectors: that approves the new setup. A changed tool list is not approved again automatically, and there is no dashboard control that approves one, so read what changed in the connector's detail pane before you rely on it. More fixes for error messages are in [Troubleshooting](/help/troubleshooting). ## Next steps * [Approvals](/using/approvals): answer the cards that connector calls raise. * [Capabilities](/using/capabilities): every skill, tool and connector your agents have, in one list. * [Org chart](/using/org-chart): the graph where you give and take back access. * [What agents can do](/concepts/permissions): what an agent can reach on each runtime, beyond connectors. * [Connectors API](/reference/rest-api/connectors): connect connectors and manage access from scripts. # Use Clawboo Source: https://docs.claw.boo/using/index Find the how-to for what you want to do, and where each part of the dashboard lives: the sidebar, team chats, the board and Settings. Use this page to find the how-to for what you want to do, and to learn where things are in the dashboard. Each how-to is a short set of steps that uses the labels you see on screen. ## Find what you want to do ### Build your team | I want to... | Go to | | - | - | | Start a team from a ready-made template, or from scratch | [Teams](/using/teams) | | Look through ready-made teams, agents and skills before I deploy one | [Marketplace](/using/marketplace) | | Add one agent, or change an agent's model, personality or files | [Agents](/using/agents) | | Set rules that every member of a team follows | [Team rules](/using/teams#team-rules) | | Understand how Boo Zero, my team leader, coordinates every team | [Boo Zero](/using/boo-zero) | ### Work with your team | I want to... | Go to | | - | - | | Ask a team for something and follow the conversation | [Team chat](/using/team-chat) | | Work with part of a team in its own room | [Team chat](/using/team-chat) | | See every task, what needs me, and what got done | [Board](/using/board) | | Comment on a running task to steer it | [Board](/using/board) | | See how my agents are organized, and attach tools by dragging | [Org chart](/using/org-chart) | ### Connect AI and tools | I want to... | Go to | | - | - | | Add an API key, sign in with ChatGPT, or use a local model on Ollama | [Providers and runtimes](/using/providers-and-runtimes) | | Add a runtime such as Claude Code, Codex, Hermes or OpenClaw | [Providers and runtimes](/using/providers-and-runtimes) | | Pick the right runtime for a job | [Choose a runtime](/runtimes/index) | | Give agents apps and tools, such as GitHub or a web browser | [Connectors](/using/connectors) | | See every tool and skill each agent can use | [Capabilities](/using/capabilities) | | See or add what my team remembers | [Memory](/using/memory) | ### Automate | I want to... | Go to | | - | - | | Have a team or an agent do something on a schedule | [Routines](/using/routines) | ### Stay in control | I want to... | Go to | | - | - | | Answer an agent that asks to run a command or use a tool | [Approvals](/using/approvals) | | Decide which commands an agent may run without asking | [Command permissions](/using/command-permissions) | | See what I am spending, and set a budget | [Budgets and spend](/using/budgets-and-spend) | | Understand what agents can do on my computer | [What agents can do](/concepts/permissions) | ### Monitor and maintain | I want to... | Go to | | - | - | | See how my agents and runs are doing, and find errors | [Observability](/using/observability) | | Find out why part of Clawboo is not working | [System Health](/using/system-health) | | Update OpenClaw, or set its default command approval | [System](/using/system) | | Switch between light and dark mode | [Theming](/using/theming) | | Fix something that went wrong | [Troubleshooting](/help/troubleshooting) | ### Go further | I want to... | Go to | | - | - | | Follow a worked example, such as a content team or running without an API key | [Recipes](/guides/index) | | Run Clawboo for my team, or on another machine | [Self-hosting](/operating/index) | ## Where things live The dashboard has a sidebar on the left and a main area beside it. Everything opens from the sidebar. ![A team's General chat: the sidebar on the left, the chat in the middle, and the Team graph and Tasks cards on the right](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-space.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c6bed9e50135bba92df482a1ddf6cd8d) ### The sidebar The sidebar has two columns. The **team rail** is the narrow column on the far left: * Boo Zero's red mascot sits at the top. Click it to open [Boo Zero](/using/boo-zero)'s page. Right-click it and choose **Show all agents** to list every agent. * Each team has an icon. Click one to open that team. Right-click it to archive the team, refresh its protocol, or delete it (see [Teams](/using/teams)). * The dashed **+** at the bottom (**Create a team**) opens the [Marketplace](/using/marketplace) on its **Teams** tab. The **agent column** sits next to it. From top to bottom: * The team's name, with a search icon that filters its agents. * The team's agents, one Boo tile each, with Boo Zero last. Click a tile to open that [agent's page](/using/agents). * **New room**, then **General (Main Room)** and your rooms: the team's chats. * **Create Boo**, which adds an agent to this team. * The nav rows (below), then the theme switch. **Home** and the team's chats appear once the team has at least one agent. Press `Cmd/Ctrl+\` to hide or show the sidebar, and drag its edge to resize it. ### The nav | Row | What it opens | Shortcut | | - | - | - | | **Home** | The team's home page, which holds only a placeholder | | | **Org chart** | Every team and agent in one chart, led by Boo Zero | `Cmd/Ctrl+1` | | **Board** | Every task, by status and by day, with a **Needs you** column | `Cmd/Ctrl+2` | | **Connectors** | Apps and tools you connect for your agents | `Cmd/Ctrl+3` | | **Memory** | What your agents have saved, as a graph or a list | `Cmd/Ctrl+4` | | **Marketplace** | Ready-made teams, agents and skills | | | **Settings** | Setup, insights and system panels, in three groups (below) | `Cmd/Ctrl+,` | ### A team's chats Click **General** or a room in the agent column. The chat fills the main area. Its header has a gear for the team's brief and rules. It also has buttons that show or hide four cards beside the chat: **Browser**, **Team graph** (**Room graph** in a room), **Tasks** and **Activity**. See [Team chat](/using/team-chat). ### Settings Open **Settings** from the sidebar, or press `Cmd/Ctrl+,`. It opens on **Runtimes**. Type in **Search settings** to find a panel by what it does, for example "api key" or "cron". | Group | Panel | What it is for | How-to | | - | - | - | - | | **Workspace** | **Providers** | API keys and the ChatGPT sign-in | [Providers and runtimes](/using/providers-and-runtimes) | | | **Runtimes** | Connect, install and manage the five runtimes | [Providers and runtimes](/using/providers-and-runtimes) | | | **Memory** | The memory list (the sidebar's **Memory** adds the graph) | [Memory](/using/memory) | | | **Capabilities** | Every tool, skill and connector, grouped by runtime | [Capabilities](/using/capabilities) | | | **Routines** | Scheduled work for teams and agents | [Routines](/using/routines) | | **Insights** | **Fleet** | An overview of your agents, recent runs and spend | [Observability](/using/observability) | | | **Tokens Used** | Token use by team and agent | [Budgets and spend](/using/budgets-and-spend) | | | **Observability** | Traces, errors and fleet health | [Observability](/using/observability) | | | **Governance** | Budgets, the audit log and the approval queue | [Budgets and spend](/using/budgets-and-spend) | | **System** | **System** | OpenClaw's default command approval, versions and **Check for Updates** | [System](/using/system) | | | **System Health** | Start-up checks for Clawboo's own parts | [System Health](/using/system-health) | ### Approvals Approvals have no panel of their own. They appear where you are working: in the board's **Needs you** column, above the message box in a chat, and in the **Tasks** cards. The **Governance** and **Capabilities** panels list them too. See [Approvals](/using/approvals). ## Next steps * [Deploy your first team](/getting-started/first-team): go from a template to a finished task. * [Dashboard tour](/getting-started/dashboard-tour): a guided look at each part of the screen. * [Key concepts](/concepts/index): how teams, the board and runtimes fit together. * [Keyboard shortcuts](/reference/keyboard-shortcuts): every shortcut in one place. * [Glossary](/appendices/glossary): the names you see in the app, and what they mean. # Browse and deploy from the Marketplace Source: https://docs.claw.boo/using/marketplace Browse ready-made teams, agents and skills, see what each agent runs on, and deploy it onto the runtime you choose. The Marketplace is a catalog of ready-made teams, single agents and skills. Use it to start a team without writing any instructions, or to add one specialist agent. Open it from **Marketplace** in the sidebar. The dashed **+** in the team rail opens it on the **Teams** tab. > [!NOTE] > > Most of the catalog is community work, adapted from open-source projects by their original authors. Clawboo checks each pack's license, pins every import to a commit, verifies a content digest, and scans for known prompt-injection patterns. It does not audit what an agent will do once you deploy it, so review anything you deploy. ![The Marketplace on its Teams tab: category and pack filters above the Curated teams banner, the Start from scratch card, and team cards with Details and Deploy buttons](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-gallery.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=8a66172b8d46c009793cf4c2513f41a5) ## What is in the catalog The Marketplace has three tabs, each with a count. | Tab | What it lists | | - | - | | **Teams** | Ready-made teams: a roster of agents and who hands work to whom. This tab opens first. | | **Agents** | Single agents, one specialist each. | | **Skills** | Skills you can note on an agent you already have. | The **Teams** and **Agents** counts come from the catalog that Clawboo downloads. The full catalog holds 85 teams and 436 agents in 19 packs, and it grows as packs are added. Offline, or until the download lands, you see only the built-in pack: 5 teams and 15 agents. The 44 skills are built in. Clawboo's server downloads the catalog from the Clawboo repository on GitHub when it first needs it, then refreshes it at most every 6 hours. It checks every pack against its SHA-256 digest before reading it, skips any pack that does not match, and keeps the packs it downloaded in Clawboo's data folder. If the download fails, the Marketplace shows the built-in pack. Looking for connectors? They have their own row in the sidebar. See [Connectors](/using/connectors). ## Find a team, agent or skill * **Search.** Each tab has its own search box. It matches names, descriptions and tags, and roles on the **Agents** tab. * **Category.** Pills for the busiest categories, with the rest under **+N more**. * **Pack.** On **Teams** and **Agents**, one pill per pack, such as **Clawboo**, **Agency Agents** or **Engineering Agents**. * **Skills.** Category pills (Code, File, Web, Comm, Data, Other) and a menu that sorts by name or by category. Each tab keeps its own search and filters when you switch tabs. When nothing matches, **Clear filters** resets that tab. ## Read the details Click **Details** on any card. * **A team's details** show where it came from (**Where this came from**: author, license and pinned commit), its description, an optional **Workflow**, and its **Agents**: each agent's role, its skills, and who it hands work to (**Routes to:**). * **An agent's details** show where it came from, its description, its **Skills**, the teams it appears in, and its **Identity**: the agent's full instructions. Click a skill or a team to jump to it on its own tab. Both have a **Deploy** button. ## What each runtime runs A catalog agent comes with three texts: its full instructions (the **Identity** you read in its details), a shorter persona, and tool notes. Clawboo saves all three on the new agent, as IDENTITY.md, SOUL.md and TOOLS.md, and adds the default personality to SOUL.md. Which text the agent follows depends on its runtime. | Runtime | What the agent follows | | - | - | | OpenClaw | IDENTITY.md (the full instructions), SOUL.md, and AGENTS.md (the team and how to hand off work). OpenClaw 2026.9 and later does not load TOOLS.md. | | Clawboo Native | SOUL.md (the persona and personality) plus Clawboo's team-lead or specialist instructions. IDENTITY.md is kept for reference only. | | Claude Code, Codex, Hermes | SOUL.md, which Clawboo adds to each turn as the agent's persona. IDENTITY.md is kept for reference only. | So on any runtime other than OpenClaw, the long text you read in **Details** is not what the agent follows. To give the agent more of it, copy what you need into its SOUL.md (see [Agents](/using/agents#edit-the-agent-files)). On Claude Code, Codex and Hermes, Clawboo passes on only the first 4,000 characters of SOUL.md. If Clawboo numbered the agent to avoid a name clash, the agent's name inside these texts is updated to match. ## Deploy a team You need at least one connected runtime to deploy agents: Clawboo Native with an API key or a local Ollama, the OpenClaw Gateway, Claude Code, Codex or Hermes. See [Providers and runtimes](/using/providers-and-runtimes). **1. Click Deploy** Click **Deploy** on a team card, or in the team's details. The team dialog opens on its customize step, filled in with the template's name, icon and color. **2. Customize the team** Rename the team, change its badge and colors, and choose each member's runtime and model. See [Customize the team](/using/teams#customize-the-team). **3. Deploy the team** Click **Deploy team**. You land in the new team's **General** chat, and the team gets an icon in the team rail. ## Deploy one agent Click **Deploy** on an agent card, or in the agent's details. Clawboo puts the agent in a new team of its own, named after the agent's role (for example **Research Analyst**). The dialog reads **Deploy agent**: choose the agent's runtime and model, rename the team if you like, then click **Create agent**. You land in the new team's **General** chat. To add a blank agent to a team you already have, use **Create Boo** instead (see [Agents](/using/agents)). ## Add a skill to an agent **1. Find the skill** Open the **Skills** tab and find the skill. **Used by N agents** under a skill opens the **Agents** tab, searched for that skill. **2. Add it** Click **Add**, then pick one of your agents. You should see a message such as **Added "Web Search" to Ada's tool profile**. Adding a skill notes it on the agent: the skill shows in [Capabilities](/using/capabilities) and on the agent's graph. It does not install a tool or change the agent's instructions. To give an agent new tools, connect a [connector](/using/connectors) and attach it to the agent. ## Check that it worked * After a deploy, you are in the new team's **General** chat and the team has an icon in the team rail. * On an OpenClaw agent, the IDENTITY tab under **Details > Agent Files** shows the text you read in the Marketplace. On other runtimes, open the SOUL tab: that is what the agent follows. * After you add a skill, it shows on the agent in **Capabilities** and on the agent's graph. ## Troubleshooting > [accordion] The Marketplace shows only 5 teams and 15 agents > > **What it means:** Clawboo is showing its built-in pack because the catalog download did not land. When the server cannot reach GitHub, it keeps showing the built-in pack for up to 6 hours. When your browser cannot reach the server, you also see **Showing the built-in teams only.** with a **Try again** button. > > **Fix:** check your network connection, then click **Try again** if it is shown. To make the server download the catalog again sooner, restart Clawboo (see the [CLI reference](/reference/cli)). > [accordion] Some agents are missing after a deploy > > One agent that fails to create does not stop the others, and a message lists the ones that failed. See [What happens when you deploy](/using/teams#what-happens-when-you-deploy). > [accordion] Failed to install a skill: skill blocked > > The full message reads `Failed to install "<skill>": skill blocked: injection / supply-chain finding`. The skill's text matched a known prompt-injection or supply-chain pattern, so Clawboo refused it and recorded the refusal in the Governance audit log (see [Budgets and spend](/using/budgets-and-spend)). > [accordion] Under the hood > > * The server downloads the index from `https://raw.githubusercontent.com/clawboo/clawboo/refs/heads/main/catalog/dist/v1/index.json`, waits up to 5 seconds, and keeps the result for 6 hours. Set `CLAWBOO_CATALOG_INDEX_URL` to use a mirror instead. > * A source checkout that contains a built `catalog/dist` folder reads the catalog from disk and downloads nothing. > * Verified pack files are cached, keyed by their digest, in the `catalog` folder of Clawboo's data directory (`~/.clawboo/catalog/` by default). > * The pack format, all 19 packs, their licenses and pinned commits are in the [Marketplace catalog reference](/reference/marketplace-catalog). ## Next steps * [Teams](/using/teams): customize and manage the team you deployed. * [Agents](/using/agents): edit a deployed agent's SOUL.md and its other files. * [Capabilities](/using/capabilities): where the skills you add show up. * [Marketplace catalog reference](/reference/marketplace-catalog): every pack, with its license and pinned commit. # Browse and manage team memory Source: https://docs.claw.boo/using/memory See, search and add the facts your agents share, tell Clawboo which ones helped, and set up local embeddings for similarity search. **Memory** is the store of facts your agents share. Agents on every runtime save to it and search it through Clawboo's Memory tool. Use this page to browse it as a graph or a list, search it, add a fact yourself, and tell Clawboo which facts helped. This shared memory is separate from each runtime's own private memory, such as a Hermes agent's `MEMORY.md` or Claude Code's own memory files, which Clawboo neither shows nor edits. [How memory works](/concepts/memory) explains both kinds and how they fit together. ![Memory in its List view: the search box with its fts, vector and hybrid modes, and the facts the teams have saved, each with its tags and scope](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/shared-memory.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=8e8643ecf40c7f957651a9fdcbbd38da) ## Open Memory * **Sidebar:** click **Memory**, or press **Cmd/Ctrl + 4**. Memory opens full screen with a **Graph** | **List** switch, and your browser remembers the choice. * **Settings:** **Settings**, then **Memory** under **Workspace**, shows the list only. **Open full view** switches to the full-screen Memory. The store holds two kinds of entry. A **fact** is a short note with a title, such as a decision or a customer's preference. A **procedure** is a named, versioned set of steps that agents build up; you can read procedures here but not change them. ## Search memory **1. Type a query** In the list, type into **Search the memory store…**. **2. Pick a mode** The chips under the box choose how to match: `fts`, `vector` or `hybrid` (the default). Without an embedding provider the chips are replaced by a line that says why search matches keywords only, and offers the fix when there is one. **3. Run it** Press **Enter** or **Search**. You get up to 25 results, each with the mode that matched it, a score and the first 240 characters. A query that matches nothing reads **No matches**. | Mode | How it matches | Needs | | - | - | - | | `fts` | Keywords in the title and text | Nothing | | `vector` | Meaning, so a search for "pricing" can find "how much we charge" | An embedding provider | | `hybrid` | Both, ranked together | An embedding provider; without one it runs as keyword search | The list itself shows the 50 most recently updated facts and procedures; search to reach older ones. ## Add a fact Agents write most of the store, but you can add what the whole team should know. **1. Open the form** Click **Add a fact** beside the **Facts** heading. **2. Fill it in** Enter a **Title** and the fact itself under **What should the team remember?**. Both are required. Optionally add **Tags**, separated by commas. **3. Save it** Click **Save fact**. The form closes and the fact appears at the top of **Facts**, marked **Global**. A fact you add is global: every agent, on every team, can find it. Before Clawboo stores any fact, from you or an agent, it removes anything that looks like a secret, such as an API key or a token. ## Rate a fact Each fact has **Helpful** (thumbs up) and **Outdated** (thumbs down). Agents report the same way when a fact they used turned out right or wrong. Clawboo combines the reports into a status, shown as a pill on the fact: | Pill | Meaning | | - | - | | **preferred** | Two or more different agents or tasks found it useful | | **tentative** | One report found it useful. Your own **Helpful** counts once, however often you press it | | **contested · verify** | The reports disagree | | **dead end** | Only negative reports | | **used N×** | Agents have been handed it N times, with no rating yet | Newer reports count for more: a report loses half its weight every 30 days. Click a fact to see its recent reports; **Full history** shows the rest once agents have used it. The status changes what agents get. Facts marked **dead end** are left out of the facts Clawboo hands to agents automatically (see [What agents remember on their own](#what-agents-remember-on-their-own)), though an agent can still find them by searching. **preferred** facts rank higher, and **contested** ones arrive with a note to check them. ## Fix or remove a fact You cannot edit or delete a fact, in the dashboard or through the API. To retire a wrong one, mark it **Outdated** so it stops reaching agents, then add the correct version with **Add a fact**. Facts live in Clawboo's database on this machine; see [Data and state](/operating/data-and-state) for where. ## Explore the memory graph The **Graph** view draws each fact as a node, and each procedure as one node however many versions it has. Links show how entries relate; the legend at the bottom left names them: | Link | Drawn as | Means | | - | - | - | | **similar** | Solid line | The two facts are close in meaning. Needs an embedding provider | | **shared tag** | Dashed line | The facts carry the same tag. A tag on a large share of the store is skipped, so it does not link everything | | **version** | Dotted line | Procedures with the same name in different places, such as a team's copy and the global one | Connected nodes form clusters, named after their most common tag. The legend lists each cluster with its size; untick one to hide it. * **Search** (top left) dims everything that does not match as you type. **Enter** runs a full search, highlights the hits and their neighbors, and opens the top hit. * **The inspector** (right) shows the selected entry's text, kind, scope and neighbors. For a fact it also has **Helpful**, **Outdated** and **Full history**. * **Filters** (top right): scope (**All**, **Global**, **Team**, **Agent**), age (**All**, **24h**, **7d**, **30d**; older nodes fade rather than vanish), and **Community hulls**, which draws or hides the cluster outlines. The graph shows the newest 500 facts and 200 procedure rows. When the store holds more, a pill says so (**Showing newest N of M facts**, or the same for procedures). If new similarity links arrive while you are looking at something, the graph offers **Show new similarity links** instead of rearranging itself. ## Who can see a fact | Scope label | Who can read it | How a fact gets it | | - | - | - | | **Team-shared** | Agents on that team | An agent saved it while working for the team | | **Global** | Every agent | You added it here, or an agent saved it outside a team: in a 1:1 chat with a Clawboo Native, Claude Code, Codex or Hermes agent, as an agent on no team, or as an OpenClaw agent | | **Agent-scoped** | That one agent | Saved with an agent scope through the API | An agent on a team reads its team's facts, its own and the global ones, never another team's. OpenClaw agents read and save global facts only, because Clawboo cannot tell which OpenClaw agent is asking. ## What agents remember on their own Nothing is saved automatically: an agent saves a fact when it decides to, with the Memory tool. What Clawboo hands an agent without being asked depends on how the agent is working: | How the agent is working | What it gets from Memory automatically | | - | - | | A routine runs it (Clawboo Native, Claude Code, Codex or Hermes agent), or a script starts it through the run API | At the start of the run, up to five facts most relevant to the task plus up to two closely related ones, 1,500 characters at most, with a note on saving and rating facts | | A 1:1 chat with a Clawboo Native, Claude Code, Codex or Hermes agent | Up to eight of its most recent notes (its own and global ones) on the first turn of a conversation, and again after `/reset` | | Team chat, delegated work, and board tasks you start | Nothing: agents search memory with the tool when they choose to | | An OpenClaw agent | Nothing: it can search the global facts with the tool | Routine runs and team chat run work in two different ways; [How a team works](/intro/how-it-works) explains both. ## Turn on similarity search An embedding is a numeric fingerprint of a fact's text. Embeddings power **similar** links, `vector` and `hybrid` search, and better matching for the facts handed to a run. Everything else in Memory works without them, using keyword search. Clawboo picks an embedding provider in this order: 1. **Ollama on this machine**, at `http://localhost:11434`, with the `nomic-embed-text` model installed. Fact text stays on this machine. 2. **An OpenAI key**, exported as `OPENAI_API_KEY` or added under **Providers**. Fact text and every memory search, including the recall at the start of a run, are then sent to OpenAI. 3. **None**: keyword search only. To set up the local option: **1. Install and start Ollama** Install [Ollama](https://ollama.com) on the machine that runs Clawboo and start it. **2. Install the model** In Memory, press **Install model** in the note that says the model is missing. It downloads a few hundred megabytes through Ollama and shows progress; **Cancel** stops it. Running `ollama pull nomic-embed-text` in a terminal does the same. **3. Wait for indexing** Within about a minute, Clawboo notices the model and indexes your facts in the background (**Indexing N facts**). When it finishes, **similar** links appear in the graph. Memory always looks for Ollama at `http://localhost:11434`; the `OLLAMA_BASE_URL` setting that Clawboo Native chat uses does not apply here. To keep memory on keyword search, set `CLAWBOO_DISABLE_EMBEDDINGS=1` before you start Clawboo. > [!NOTE] > > Two rules keep your facts from leaving the machine by surprise. Once any fact has been indexed by Ollama, Clawboo never switches to OpenAI on its own, even while Ollama is down: if an OpenAI key is connected, **Use OpenAI instead** switches for that outage only, and says how many facts it sends. And with OpenAI as the provider, only facts that have no fingerprint yet are sent automatically. When Ollama serves again, facts move back to local indexing. Clawboo re-checks the provider on its own: every 10 minutes while Ollama serves, every 30 seconds otherwise, and at once when a call fails or you connect or remove a key. A very long fact is fingerprinted from its first 8,000 characters (2,000 if the provider turns that down); keyword search still covers all of it. ### What the embedding note says The legend (in the graph) and the line under the search box (in the list) say which state memory is in: | You see | Why | What to do | | - | - | - | | **Similarity links unavailable: embedding model not installed** | Ollama runs, but `nomic-embed-text` is missing | Press **Install model**, or run `ollama pull nomic-embed-text` | | **Similarity links unavailable: Ollama is not reachable** | Facts were indexed locally, and Ollama is not answering | Start Ollama; indexing resumes on its own | | **Similarity links unavailable: no embedding provider** | No Ollama, and no OpenAI key Clawboo can use | Set up Ollama, or add an OpenAI key under **Providers** | | The same, with "Embeddings are turned off on this server." | `CLAWBOO_DISABLE_EMBEDDINGS=1` is set | Unset it and restart Clawboo | | **Indexing N facts** | Indexing is under way | Nothing | | **Indexing stopped** or **Embedding calls are failing** | The provider failed, for example a revoked key or a used-up quota | Fix the reason shown, then press **Retry** | | **Using OpenAI until Ollama is back** | You chose OpenAI during an Ollama outage | Start Ollama; memory moves back on its own | | **Similarity uses OpenAI** | OpenAI is the provider while Ollama lacks the model | Press **Install model** to move indexing to this machine | | **N facts could not be indexed** | The provider turned those facts down | Nothing: keyword search still finds them | | **New facts are not being indexed** | Older facts still have links, but nothing can index new ones | Apply the fix from the first three rows | ## Troubleshooting > [accordion] The list says it could not load the memory store > > The Clawboo server did not answer. Press **Retry**. An empty store reads **No facts yet** and **No procedures yet** instead. > [accordion] Saving a fact says: Could not save the fact. Please try again. > > The server did not accept the save, or could not be reached. Check that Clawboo is still running, then save again. > [accordion] Search only matches keywords > > No embedding provider is working. Read the line under the search box, which names the reason, and use the table above. > [accordion] The model installed but no similar links appeared > > Clawboo notices a model installed outside the app within about a minute. Similar links need at least two indexed facts. To check straight away, ask the server to re-check and index what is missing: > > ```bash theme={"theme":"github-dark"} > curl -X POST http://127.0.0.1:18790/api/memory/embedding/reindex > ``` > > It answers at once, and the legend shows the progress. `18790` is the default port; see the [CLI reference](/reference/cli) to find yours. ## Next steps * [How memory works](/concepts/memory): shared and private memory, and how agents use them. * [Connectors](/using/connectors): the Knowledge Graph Memory connector is a separate tool, not this store. * [Routines](/using/routines): runs that start with the most relevant facts. * [Memory API](/reference/rest-api/memory): search, save and rate facts from scripts. * [MCP servers](/operating/mcp-servers): attach the Memory tool to an agent outside Clawboo. # Check on your agents Source: https://docs.claw.boo/using/observability Use the Fleet and Observability panels and the Activity feeds to see how your agents are doing, what they did and what went wrong. Use this page to check how your agents are doing, find out what one of them did, and see what went wrong. These views read what Clawboo records in its own database on your computer: tasks moving on the board, runs starting and ending, tool calls, costs and errors. [Observability](/concepts/observability) explains what is recorded and where the gaps are. ## Where to look | You want to | Open | | - | - | | See every runtime and agent at a glance | **Settings** > **Fleet** | | Find out what an agent did on a task | The task's card on the **Board**, or the task in a team chat's **Tasks** card | | Watch one agent work | The **Activity** card on the agent's page | | Watch a whole team | The **Activity** card beside the team's chat | | Watch every team at once | **Activity**, the terminal icon in the **Org chart**'s header | | Find errors, stuck agents and run traces | **Settings** > **Observability** | | See budget warnings, pauses and approvals | **Settings** > **Insights** > **Governance**, under **Audit log** (see [Budgets and spend](/using/budgets-and-spend)) | | Check that Clawboo's own parts are working | **Settings** > **System Health** (see [System Health](/using/system-health)) | To open **Settings**, click **Settings** at the bottom of the left sidebar, or press Cmd+, (macOS) or Ctrl+, (Windows, Linux) while no text field has focus. **Fleet** and **Observability** are in the **Insights** group. ## Check the fleet **Fleet** fits your whole setup on one screen: how many agents you have, how the last day of board work went, what it cost, and whether each runtime is ready. ![The Fleet view in Settings: the Agents, Task pass-rate, Verify pass-rate and Spend cards above a tile for each runtime, with a health dot, a badge and healthy, degraded and down counts](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/fleet-health.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c706a2cc4c551d550dcb6999665c966e) | Part | What it shows | | - | - | | **Agents** | Every agent that is not archived, on any runtime | | **Task pass-rate · 24h** | Of the board tasks updated in the last 24 hours that ended **Done** or **Cancelled**, the share that ended **Done**. Below it: how many are done and how many are still active | | **Verify pass-rate · 24h** | Of the verification results on board tasks updated in the last 24 hours, the share that passed, and how many results there were. Most work is never checked, so this often shows a dash (see [How work is checked](/concepts/verification)) | | **Spend · 24h** | The cost recorded on board tasks updated in the last 24 hours, and your number of budgets (or how many are paused). Some runtimes' costs are estimates (see [Budgets and spend](/using/budgets-and-spend)) | | **Runtimes** | A tile for each runtime (see below) | | **Recent issues** | The six newest errors, each with its class, runtime and time. **All clear** means there are none | Every runtime gets a tile, even one with no agents. The dot beside its name is green when the runtime is ready and red when it is not: * **Claude Code**, **Codex** and **Hermes**: green when the runtime's program is installed. It does not tell you whether the runtime is signed in or has a key. * **Clawboo Native**: green when it has a provider it can use, such as an API key or a local Ollama. * **OpenClaw**: green while Clawboo is connected to the OpenClaw Gateway. The badge under the name says how Clawboo drives the runtime: **Native peer** (Clawboo Native runs inside Clawboo), **Wrapped one-shot** (Clawboo starts the runtime's program to do each piece of work) or **Connected substrate** (OpenClaw, a separate Gateway that Clawboo connects to). The chips count the runtime's agents by state: **healthy** (idle or working), **degraded** (in an error state) and **down** (any other state, such as asleep). Fleet refreshes every 8 seconds while its tab is visible. **Refresh** reloads it at once. ## Find out what an agent did **1. Open the task** On the **Board**, click the task's card to open its drawer. In a team chat, you can also open the **Tasks** card and click the task there. **2. Read the report** The drawer shows what the agent reported, titled with its name (for example **Rin's report**). In a chat's **Tasks** card, the same text is under **Output** or **Latest report**. **3. Check each run** The drawer's **Run history** lists every run of the task: the runtime, how it ended, when it started, what it cost and how many tokens it used, and the error if it failed. **4. Follow the activity** **Activity** in the drawer (**Progress** in a chat's **Tasks** card) lists the task's events in order: each tool the agent called, what came back, and every error. You should now know what the agent did, what it cost and where it went wrong. [Track work on the board](/using/board) covers the rest of the drawer, retries and steering. ### Read an Activity feed The same live feed appears in several places. On a task it covers that task. On an agent's page it covers that agent. Beside a team chat it covers the whole team, even in a room. The Org chart's **Activity** dock covers every team. | Badge | What happened | | - | - | | **tool** | The agent called a tool. The row shows the tool and what the agent sent it | | **result** | What the tool returned. A red badge means the call failed | | **error** | A run or a tool reported an error | | **run** | A run started, or ended with its outcome | | **status** | The task moved to another status | | **task**, **claim** | A task was created; an agent took it | | **note** | A comment was added to the task | | **cost** | A run reported its cost and tokens. Runs in team chat report their cost on the task instead | | **approval** | Someone was asked to approve something, and the answer | | **grant**, **connector** | A connector call was allowed, denied or held for approval; a connector's health changed | A feed that spans several agents names the agent on each row. Long tool input folds behind a toggle. The pill in the corner reads **Live** while new events stream in, and **Reconnecting** if the connection drops. The feed follows new rows while you stay at the bottom. Activity from every runtime lands in the same log. That includes OpenClaw agents' own work that Clawboo did not start, such as a cron job or a message from another channel, while Clawboo is connected to the Gateway. ![The Org chart with its Activity dock open on the right: run, cost, note, status, tool and result rows from every team, with a Live pill](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/activity-feed.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=358c037653a1e151028f3d2c6e5c89d6) ## Read the Observability panel Open **Settings** > **Observability**. The left column holds **Fleet health**, **Error taxonomy** and **Traces**. The right column holds the **Eval scorecard**, the trace you select, and the **Delegation graph**. The header counts the tasks and agents in recent events. The panel refreshes every 5 seconds while its tab is visible, and **Refresh** reloads it at once. ### Fleet health Each agent that appears in recent events gets a row with a status, how many runs it has open, and the cost its runs reported to the log. Rows show the agent's id (its first 12 characters), not its name. | Status | Meaning | | - | - | | **working** | A run is open and something happened in the last 5 minutes | | **stalled** | A run is open, but nothing has happened for 5 minutes | | **zombie** | A run is open, but nothing has happened for 30 minutes | | **idle** | No run is open | A long model call can be quiet for minutes, so **stalled** on its own is not a failure. Team-chat runs report their cost on the task, so their agents show \$0.000 here; see the task on the board for what they cost. ### Error taxonomy Clawboo sorts each error into a class, such as **RateLimited** or **ProviderError**. This section groups the newest 500 errors by class, most frequent first. Its title counts the classes and the harness bugs among them. An error that fits no class is **Unknown** and carries a **harness bug** label: most likely a gap in Clawboo's own error handling rather than a problem with your prompt or provider. In team chat, a tool call that a guardrail refused can also land here, so read the message first. If it is not a refusal, [open an issue](https://github.com/clawboo/clawboo/issues) with the error text. [What to do about an error](#what-to-do-about-an-error) covers the other classes. ### Traces A trace is one run's events laid out as a timeline: the run itself, with each tool call inside it. Click a trace in the list to open it. A bar above the timeline totals the run: | Figure | Meaning | | - | - | | **cost** | What the run cost, in US dollars | | **tokens** | Input tokens, then output tokens | | **tok/min** | Output tokens per minute | | **tools** | How many tool calls the run made | | **tool err** | The share of tool calls that failed, amber when above 0% | Only runs started by Clawboo's task runner have traces: a routine aimed at one agent on Clawboo Native, Claude Code, Codex or Hermes, and runs started through the API. Team chat, the tasks your team takes on from chat, team routines and OpenClaw work have no traces. Most everyday work happens in team chat, so an empty **Traces** list is normal. The list's empty message suggests running a board task, but a task your team runs from chat does not add one. [How a team works](/intro/how-it-works) explains the two paths. ### Delegation graph When recent tasks were handed out or wait on each other, this section lists the links, one per row: the first task's id, an arrow, the second task's id, and the kind. **delegation** means the second task was handed out from the first. **dependency** links a task to another it waits on. The section is hidden when there are no links. ### Eval scorecard **Run smoke evals** runs Clawboo's built-in test tasks on throwaway boards, with no AI model and no API keys, and never touches your own board. It shows how many test tasks passed (**pass\@1** and **pass^k**) and a row for each one. If the run fails, the panel shows a short error; try again, and if it keeps failing, [open an issue](https://github.com/clawboo/clawboo/issues). The **Ablation** table below it, marked **CI only**, is filled in only by the project's own test runs. In the app it shows the table's layout with no numbers. > [accordion] Under the hood > > * Fleet health and the delegation graph read the newest 5,000 events, so agents and tasks with older events drop out of them. A trace shows at most 5,000 events. > * The **Traces** list comes from the newest 300 events and shows up to 40 traces. > * The harness-bug count covers only the newest 500 errors. > * **Fleet** reads `GET /api/fleet/summary` and, for **Recent issues**, `GET /api/obs/errors`. The Observability panel reads `GET /api/obs/health`, `/api/obs/errors`, `/api/obs/events`, `/api/obs/graph` and `/api/obs/traces/:traceId`. See the [Observability API](/reference/rest-api/observability). > * **Run smoke evals** calls `POST /api/eval/smoke`. When it fails, the server log records only the failed request; the reason is in that call's JSON response. ## What to do about an error Each error's class tells you where to look. [Events and errors](/reference/events-and-errors) lists the words that put an error in each class. | Class | What it usually means | What to do | | - | - | - | | **RateLimited** | The provider is throttling you or you hit a quota | Wait, then retry. If it keeps happening, check your plan's limits with the provider | | **UserAborted** | The run was stopped or cancelled | Nothing, unless nobody meant to stop it | | **Timeout** | A model call, tool or program took too long | Retry. If the same step keeps timing out, check the service it calls | | **UnexpectedEnv** | A program or file is missing, or permission was refused | Check the runtime is installed in **Settings** > **Runtimes**, and that the files or folders the task needs exist | | **ContextOverflow** | The conversation or input was too long for the model | Start the chat fresh with `/reset`, or split the task into smaller ones | | **InvalidArgs** | A request or tool call was malformed | Often the agent recovers on its own. If a task keeps failing this way, read the error text in its **Activity** | | **ProviderError** | The provider failed or was overloaded | Retry later, and check the provider's status page | | **PolicyDenied** | A guardrail refused a tool call (task-runner runs only) | Nothing: the guardrail did its job. Change the agent's permissions if the call should be allowed | | **Unknown** | Nothing matched: a **harness bug** | [Open an issue](https://github.com/clawboo/clawboo/issues) with the error text | ## Export traces with OpenTelemetry Clawboo can also send its traces to an OpenTelemetry collector, such as Jaeger. Export is off until you set `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) to the collector's OTLP over HTTP address, for example `http://localhost:4318`, in the environment that starts Clawboo's server. `OTEL_SERVICE_NAME` names the service, `clawboo` by default. Only runs with traces are exported, so team-chat work never reaches the collector. > [!NOTE] > > **The npm package does not include the OpenTelemetry SDK.** On an install from npm (`npx clawboo@latest` or a global install), setting the endpoint exports nothing: the exporter cannot load, and traces stay in Clawboo's own log. Export works where the two packages it needs, `@opentelemetry/sdk-node` and `@opentelemetry/exporter-trace-otlp-http`, are installed, as in a source checkout of the repository. To check whether export started, open **Settings** > **System Health** and read the **OTel exporter** check. "OTel exporter initialized" means it loaded. A **Degraded** check means it did not. Trust the check, not the **observability** row further down: that row reads **OTel exporter active** whenever an endpoint is set, even when the exporter failed to load. ## Troubleshooting > [accordion] The Observability panel is empty > > The Observability panel shows only what Clawboo has recorded, and a fresh install has recorded nothing yet. Give your team some work in [team chat](/using/team-chat) or on the [board](/using/board), then come back. **Fleet health**, **Error taxonomy** and the **Delegation graph** fill in from any work. **Traces** fill in only from task-runner runs (see [Traces](#traces)). > [accordion] The Traces list is empty, but my team is busy > > This is expected. Work done in team chat, including the tasks your team takes on from chat, has no traces. Use the task's **Activity** on the board, or the **Activity** card beside the chat, to see what happened. > [accordion] An agent reads stalled or zombie > > Open the agent's task on the board. If it is still **In progress** with nothing happening, the run may have hung: Clawboo puts a task whose run stops checking in back in **To do** after a few minutes, so it can run again. If the task has already moved on, the run's end never reached the log, and the agent can keep this status here until newer events push the old run out of view. The board is the one to trust. > [accordion] The OTel exporter check is Degraded > > An OTLP endpoint is set, but the OpenTelemetry SDK could not load. On an npm install this is expected, because the package does not include the SDK. To clear the warning, unset `OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` and restart Clawboo. To export traces, run Clawboo from a source checkout. ## Next steps * [Observability](/concepts/observability): what the event log records, and where its gaps are. * [Events and errors](/reference/events-and-errors): every event kind and error class in detail. * [Track work on the board](/using/board): the task drawer, retries and steering. * [Budgets and spend](/using/budgets-and-spend): where cost figures come from, and which are estimates. * [System Health](/using/system-health): the checks Clawboo runs on its own parts. * [Observability API](/reference/rest-api/observability): the same data over REST. # Org chart and team graphs Source: https://docs.claw.boo/using/org-chart See how your agents are organized, on the Org chart or a team graph beside a chat, and give them routes, skills and connectors by dragging. The Org chart draws every agent you have, on every team, around Boo Zero. Each agent appears as a Boo, a small mascot, with lines for how work flows between agents and, when you open one, the skills and connectors it has. Use it to see how your teams are organized and what each agent is doing, and to give agents routes, skills and connectors by dragging. The same canvas appears in smaller places: as a **Team graph** card beside a team's chat (**Room graph** in a room), and as the **Graph** card on an agent's page. ![The Org chart in its Tree layout with team halos on: Boo Zero at the top, each team's badge with a count of running and idle agents, and each team's Boos in a row inside its halo](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/ghost-graph.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=34b71bf37bb52bcf2ad5ea30a3ffa335) ## Where the graph appears | Graph | What it draws | Where to find it | | - | - | - | | **Org chart** | Every agent on every team, around Boo Zero | **Org chart** in the sidebar, or `Cmd+1` (`Ctrl+1` on Windows and Linux) | | **Team graph** card | One team's agents and Boo Zero | Beside the team's General chat. See [Team chat](/using/team-chat) | | **Room graph** card | One room's members and Boo Zero | Beside the room's chat | | **Graph** card | One agent with its skills, connectors and model | On the agent's page. See [Agents](/using/agents) | Right-clicking the Boo Zero mascot at the top of the team rail and choosing **Show all agents** also opens the Org chart. The Org chart ignores the team selected in the sidebar and keeps that selection for when you come back. Only the Org chart has the layout switch, team halos, the Activity dock and the agent browser. Beside a chat, Activity and the browser are cards of their own. Each graph keeps its own saved positions, so arranging a room's graph never moves the team's graph or the Org chart. ![A team's Team graph card, maximized: Boo Zero above the team's badge, Ada below the badge, and four teammates under Ada](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-graph.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c0b2e3f7364ad35416269aa5f4d133f1) ## Read the graph * **Boos** are your agents. Boo Zero sits at the root, a little larger than the others and in its own red. Under each name, a Boo shows its status and a count such as `12 capabilities · 1 route`. Point at the count to see what kinds they are. A small badge on each Boo shows its runtime. * **Teams** hang from Boo Zero through a junction that wears the team's badge, the same icon as in the team rail. Point at a badge to see the team's name: its Boos light up and the rest dim. On the Org chart, a pill beside each badge counts the team's agents by status, for example **2 running** and **3 idle**. * **Routes** are the red lines with arrowheads between Boos. Each means "this agent hands work to that one", and each is a line in the first agent's AGENTS.md file. See [What a route does](#what-a-route-does). * **Skills and connectors** circle a Boo when you click it, along with the model it runs on. See [Open a Boo](#open-a-boo). * **Thought bubbles** appear while an agent works. The bubble shows what it is doing: its latest words, its reasoning, or the tool it is using, with three pulsing dots. Before anything arrives it says "thinking". After an error the bubble stays, so you can read the reason. * **Status glows**: a running Boo glows mint, a Boo with an error glows red, and a sleeping Boo looks faded. Hovering a Boo dims everything not connected to it, which is the quick way to read one agent's links in a busy graph. The count beside a graph's title, such as `3 Boos · 1 skill`, counts skills only, not tools or connectors. ### What a route does A route is a line such as `- Route to @Sage for delegated tasks.` in the first agent's AGENTS.md file. OpenClaw agents read that file, so a route tells an OpenClaw agent whom to hand work to. Agents on Clawboo Native, Claude Code, Codex and Hermes do not read AGENTS.md: for them a route only draws the line. Those agents can hand work to any member of the chat they work in (see [How a team works](/intro/how-it-works)). ## Open a Boo Click a Boo to fan out its skills (mint), connectors (violet) and model around it. Click it again to fold them. Several Boos can be open at once, and the view zooms to fit what is open. * On a team graph, a room graph and an agent's **Graph** card, a Boo shows every capability it has. * Five or more read-only items of one kind fold into one group tile, such as `41 plugins`. Click the tile to list them. * The Org chart draws at most 24 tiles per Boo and counts the rest on one overflow tile. Open the agent's own **Graph** card to see all of them. With the keyboard, move between Boos with **Tab** and press **Enter** or **Space** to open or fold one. ## Add routes, skills, connectors and agents Every graph that draws a Boo works the same way: pull a thread out of a Boo and let go where you want it to end. **1. Find the port** Point at a Boo. A small disc with a plus, its port, appears on its right edge with the hint **Drag to connect**. On a touch screen the port is always shown. **2. Pull a thread** Drag from the port. Every Boo the thread can reach shows a dashed docking ring. **3. Let go** Drop on another Boo to add a route from the first agent to the second. Drop on empty canvas to open a picker of what the thread can end in: **Connectors**, **Skills** or **New agent**. You should see the new route drawn at once, or the picker open. You can also click a port and then click another Boo. If the route is already there, a message says so and nothing changes. In the picker, type to search every kind at once; the last row offers a new agent with the name you typed. What you pick goes to the agent you pulled from: * A skill is installed on that agent. * A connector is turned on if it is not running yet (with a sign-in, if it has one), and the agent gets Write access to it. Risky calls still ask you first. * **New agent** creates an agent on the same team, already routed from the one you pulled from. On a room's graph it joins the room too. A thread from Boo Zero gives the new agent the graph's team, or on the Org chart the team selected in the sidebar (or your first team). The **New agent** row names the team. A new agent runs on OpenClaw while Clawboo is connected to an OpenClaw Gateway, and on Clawboo Native otherwise, the same as **Create Boo** (see [Agents](/using/agents)). A connector that needs a key or a folder is listed but greyed, with the reason: set it up in [Connectors](/using/connectors) first. ### The + button Every graph's toolbar starts with **+**, labelled **Add a connector, skill or agent** (on an agent's **Graph** card, **Add a connector or skill**). It opens the same picker with no agent behind it, so what you pick lands on the canvas as a loose tile marked **Not attached**. A new agent picked here joins the graph's team, and the room on a room's graph. On the Org chart it joins the team selected in the sidebar, or your first team. To attach a loose tile, point at it: a port appears with **Drag onto an agent** beside it. Drag from the port and let go on a Boo: that agent gets the skill, or the connector at Write access with risky calls still asking first. Point at a loose tile for an **x** that takes it off the canvas. Loose tiles stay after a reload. Each graph keeps its own, and a room's graph shares its team's. ### Give a second agent a skill or connector To install a skill on another agent too, drag from the small handle on the right edge of its tile and let go on that agent's Boo. You can also point at a skill and click **Install →**, then pick an agent. To share a connector, drag from the small handle on the right edge of its tile and let go on another Boo. A dialog opens to share it. The second agent gets its own access, and the first keeps its own. Pick a level: | Level | What the agent can do | | - | - | | **Read** | The default. Only the connector's tools marked as read-only: look things up | | **Write** | Create and update. Risky calls ask you first | | **Admin** | Settings and deletion too | Connecting a connector in **Connectors** gives no agent access by itself: you attach it to agents here. See [Connectors](/using/connectors). ## Take things back Click a line to open its panel at the bottom of the canvas, then click **Remove Connection**. Or select the line and press **Backspace** or **Delete**, as long as you are not typing in a field. * Removing a skill's line takes the skill off that agent. * Removing a share's line ends that agent's access, with an **Undo** for 8 seconds. **Stop sharing** on the connector tile does the same. **Turn off** on the tile stops the connector itself. * Removing a route deletes every line in the first agent's AGENTS.md that mentions the second agent. Removing a route needs the dashboard connected to an OpenClaw Gateway. Without that connection, **Remove Connection** on a route does nothing. Edit the agent's AGENTS.md instead: on its page, open **Details**, then **Agent Files**, then **AGENTS**, and delete the route line. Some lines cannot be removed, and the panel says why: a capability that came with the agent's runtime, and the model tile, which is part of the Boo. **Backspace** never deletes an agent; deleting is on the right-click menu. ## Right-click a Boo Right-click a Boo, or focus it and press the Menu key, **Shift+F10** or **Alt+Enter**, to open its menu: * **Open agent** opens the agent's page, with its chat, files and personality. * **Delete** deletes the agent at once. It works without an OpenClaw Gateway. > [!WARNING] > > **Delete** on the graph does not ask first, and it cannot be undone. If the Boo is still there afterwards, the delete failed without a message. Delete it from the all-agents list instead to see why: right-click the Boo Zero mascot, choose **Show all agents**, and use the agent's trash icon. ## Layouts and tools on the Org chart ### Tree and Radial The **Tree** and **Radial** switch in the Org chart's toolbar picks the layout: * **Radial**, the default, puts Boo Zero in the center with the teams around it. Each team's Boos fan out from its badge, spaced so that no Boo, name or bubble lands on a neighbor. * **Tree** puts Boo Zero at the top and the teams in a row beneath it. Your browser remembers the choice, and each layout keeps its own saved positions. ### Arrange and re-layout Drag Boos to arrange any graph, and Clawboo saves where you put them. **Re-layout** in the toolbar, shown once the first layout has run, drops that graph's saved positions and lays it out again. Use it when Boos overlap or the lines look tangled. ### Team halos **Team halos**, on the Org chart only, draws a colored outline around each team's Boos. A team with one agent gets none. Halos are off each time the app loads. ### Activity and the agent browser * **Activity**, the terminal icon in the Org chart's header, opens a live feed of every team's tool calls, results and errors at the right edge. Close it with the same icon, its close button, or **Escape**. * **Agent browser**, the globe at the end of the toolbar, shows the page an agent is looking at. Tabs pick a team (or **No team** for agents such as Boo Zero), and faces pick an agent. It opens on the agent that captured a page most recently. Only one of the two is open at a time. In the Radial layout, opening the agent browser or picking a team in it turns the chart around Boo Zero to bring that team beside it. Picking an agent steps it forward while its teammates make room. Picking another team, or closing the browser, settles the team back. The turn is saved with the layout; the focus is not. ### Move around the canvas The controls at the bottom right zoom out, zoom in, **Fit to view**, lock the canvas, and show the minimap, which is hidden at first. **Lock canvas** freezes positions, hides the ports and stops drops until you unlock it. | Key | What it does | | - | - | | **Tab** | Move between Boos | | **Enter** or **Space** | Open or fold the focused Boo | | Arrow keys | Move the selected Boo | | Menu key, **Shift+F10** or **Alt+Enter** | Open the right-click menu for the focused Boo | | **Backspace** or **Delete** | Remove the selected line | Drawing a route and dragging a tile need a pointer. ## Reduced motion When your system's reduce motion setting is on, the graph skips its animations. Tiles appear at once when you open a Boo, and Boos land straight on their final positions. On the Org chart, turns and focus happen without moving through the motion, and the docks open without sliding. The dots in a thought bubble hold still. ## What is not on the canvas Creating a team, adding your own MCP server, entering a key, choosing a folder, and editing a personality each need a form. They live in the [Marketplace](/using/marketplace), [Connectors](/using/connectors), and the agent's page. An agent's runtime cannot be changed after the agent is created. ## Troubleshooting > [accordion] Boos pile up or the layout looks broken > > Saved positions from an earlier arrangement can cover only part of the current graph. Click **Re-layout** to drop them and lay the graph out again. > [accordion] A Boo shows no port, or a thread will not land > > The canvas is probably locked: unlock it with the padlock at the bottom right. If the thread lands but a **Failed to save routing** message appears, the agent's file could not be written. For an OpenClaw agent, check that Clawboo is connected to its OpenClaw Gateway. > [accordion] Remove Connection on a route does nothing > > Removing a route needs the dashboard connected to an OpenClaw Gateway. Without one, delete the route line from the agent's AGENTS.md on its page instead. See [Take things back](#take-things-back). > [accordion] A route I drew does not change who gets the work > > Only OpenClaw agents read routes. Agents on other runtimes can hand work to any member of their chat, whatever the graph shows. See [What a route does](#what-a-route-does). > [accordion] The Org chart says No agents yet > > There are no agents to draw. Deploy a team from the [Marketplace](/using/marketplace), or click **Create Boo** in the sidebar. > [accordion] Under the hood > > * Drawing a route appends `- Route to @<Name> for delegated tasks.` to the first agent's AGENTS.md and, best-effort, turns on agent-to-agent messaging in the OpenClaw Gateway's config. > * Positions are saved on the server through `/api/graph-layout`, per graph: one set per Org chart layout, one per team graph, and one per room graph. The layout choice is kept in the browser under `clawboo.atlas.layout`. > * Installing a skill goes through `POST /api/capabilities/install`. Sharing a connector creates a grant for that agent; see [Capabilities](/using/capabilities). ## Next steps * [Team chat](/using/team-chat): give the team work and watch its graph card light up. * [Agents](/using/agents): open an agent's page to edit its files and personality. * [Connectors](/using/connectors): connect the apps and tools you then attach on the graph. * [Capabilities](/using/capabilities): see every skill, tool and connector across your agents. * [Observability](/using/observability): the live activity behind the Activity dock and the thought bubbles. # Connect providers and runtimes Source: https://docs.claw.boo/using/providers-and-runtimes Connect API keys, your ChatGPT plan or a local Ollama, add runtimes, and see what Disconnect removes and where Clawboo keeps your keys. Every agent needs an AI model to answer with. Clawboo itself is free; the model comes from a provider you connect, and that provider bills your usage. Use this page to connect providers and runtimes in **Settings**, choose the default model for new agents, and remove a key without surprises. Two words matter here: * A **provider** is the service whose model answers: Anthropic, OpenAI, OpenRouter, Google and others, or Ollama running models on your own computer. You connect one with an API key, a ChatGPT sign-in, or (for Ollama) nothing at all. * A **runtime** is the engine an agent runs on. **Clawboo Native** is built in; **OpenClaw**, **Claude Code**, **Codex** and **Hermes** are separate programs that Clawboo installs and connects. Each agent runs on one runtime, and the runtime uses the providers you connect. [Choose a runtime](/runtimes/index) compares them. Prompts go only to the providers and apps you connect. No Clawboo account, and Clawboo itself sends no telemetry. ## Where to connect what Open **Settings** from the bottom of the left sidebar, or press Cmd+, (macOS) or Ctrl+, (Windows, Linux) while no text field has focus. **Providers** and **Runtimes** are the first two items under **Workspace**. | You want to | Go to | | - | - | | Add, replace or remove an API key | **Settings** > **Providers** | | Run agents on your ChatGPT plan | **Settings** > **Providers** > **ChatGPT subscription** | | Run agents on a local Ollama | **Settings** > **Runtimes** > **Clawboo Native** | | Choose the default model for new Clawboo Native agents | **Settings** > **Runtimes** > **Clawboo Native** > **Manage** | | Add Claude Code, Codex or Hermes | **Settings** > **Runtimes** | | Set up, reconnect or stop OpenClaw | **Settings** > **Runtimes** > **OpenClaw** | | Remove a key or sign out | [Disconnect a key or a runtime](#disconnect-a-key-or-a-runtime) | ![The Runtimes panel in Settings: Codex, Claude Code, Hermes and Clawboo Native read Connected, and OpenClaw shows Set up](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/runtimes-panel.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=60bd52b75ba67d2a4e3e3729d30b1e6d) ## Connect an API key **1. Open the provider's row** Open **Settings** > **Providers**. OpenAI, OpenRouter, Anthropic, Google, xAI, Groq and Mistral are listed first; click **More providers (7)** for Moonshot, MiniMax, Together, NVIDIA, Hugging Face, Cerebras and Venice. **2. Paste your key** Click **Connect** on the row. If you don't have a key yet, most rows show a **Get a key** link to the provider's console. Paste the key into the field. **3. Save it** Click **Save**. Clawboo checks the key with the provider before it saves it. If the check fails, the row shows the reason: fix the key, or click **Save anyway** when the provider is only unreachable from this computer. MiniMax, NVIDIA, Hugging Face and Venice keys save without a check. You should see the row read **Connected**. To replace the key later, click **Update**. One key serves every runtime that uses that provider: | Key | Runtimes that can use it | | - | - | | Anthropic | Clawboo Native, Claude Code, Hermes, OpenClaw | | OpenAI | Clawboo Native, Hermes, OpenClaw | | OpenRouter | Clawboo Native, Hermes, OpenClaw | | Google, xAI, Groq, Mistral, Moonshot, Together, Cerebras | Clawboo Native, OpenClaw | | MiniMax, NVIDIA, Hugging Face, Venice | OpenClaw | | ChatGPT subscription (a sign-in, not a key) | Codex; Hermes and OpenClaw with their own sign-in | | Local Ollama (no key) | Clawboo Native; OpenClaw when you set it up on Ollama | A key you save on the **Providers** page is also copied, in plain text, into OpenClaw's files so an OpenClaw Gateway can use it. See [Where keys are stored](#where-keys-are-stored). ## Run agents on your ChatGPT plan Your ChatGPT plan works through Codex, OpenAI's coding agent, which signs in with your ChatGPT account instead of a key. **1. Install Codex** Open **Settings** > **Runtimes**. On the **Codex** row, click **Install**, then **Install** in the panel that opens. **2. Sign in with ChatGPT** Open **Settings** > **Providers**. On the **ChatGPT subscription** row, click **Connect**, then **Sign in with ChatGPT**. Codex's own sign-in opens OpenAI's page in your browser; approve there. Clawboo never sees your password. You should see the row read **Connected**, with "Managed by the Codex CLI". The **Codex** row under **Runtimes** reads **Connected** too. **3. Put agents on Codex** When you create a team, choose **Codex** as a member's runtime in **Customize team**. Onboarding's **Sign in with ChatGPT** path does this for your first team. Clawboo Native cannot use the ChatGPT plan. Hermes and OpenClaw can, but each signs in separately; see [Hermes](/runtimes/hermes) and [OpenClaw](/runtimes/openclaw). Codex runs are not billed per token, but Clawboo still records an estimated cost for your budgets; see [Codex](/runtimes/codex). ## Run agents on a local Ollama [Ollama](https://ollama.com) runs open models on your own computer, with no key and no per-token bill. Clawboo Native can use it. **1. Pull a chat model** Install Ollama, start it (`ollama serve`), and pull a chat model that can use tools: ```bash theme={"theme":"github-dark"} ollama pull llama3.2 ``` **2. Turn Ollama on** Open **Settings** > **Runtimes** and click **Manage** on **Clawboo Native** (or **Connect**, if it has no provider yet). On the line **Ollama** ("Local · no key needed"), click **Use**. Clawboo checks that Ollama answers first; if it is not running yet, start it or click **Use anyway**. **3. Make it the default** Click the **Ollama** row to open it. Click **Make default** if Ollama is not your default yet, then pick the **Default model** from the models your Ollama has pulled. You should see the **Ollama** row read **Connected**, with a **Default** badge. Picking **Ollama** on onboarding's connect step does the same. Clawboo reaches Ollama at `http://localhost:11434/v1`, or at `OLLAMA_BASE_URL` when you set one. Models that can't use tools are marked `(can't use tools)`, and embedding models are left out. Ollama runs cost you nothing, so Clawboo records them at \$0 and their tasks read **free** on the Board. Ollama's cloud models (a `-cloud` tag) run on ollama.com and are estimated like other models without a price. [Clawboo Native](/runtimes/native) has the details. ## Choose the default provider and model The default decides what new Clawboo Native agents run on when you leave their model on **Default**. **1. Open the provider list** Open **Settings** > **Runtimes** and click **Manage** on **Clawboo Native**. Under **LLM providers** you see each connected provider; the current default has a **Default** badge. **2. Make a provider the default** Click a provider's row to open it, then click **Make default**. **3. Pick its model** Choose the **Default model**. The choice saves at once, and "Saved" appears next to the label. What changes: * A Clawboo Native Boo Zero switches to the new default right away. * Agents you deploy later with their model on **Default** use it. A team's leader runs the default model; other members run that provider's faster, cheaper model. On Ollama, every agent runs the default model. * Agents you already deployed keep their models. * If the default provider stops being set up, new agents fall back to the first provider that is: Anthropic, OpenAI, OpenRouter, Ollama, then the others. The built-in model for each provider is listed in [Clawboo Native](/runtimes/native). ## Add Claude Code, Codex or Hermes Each of these is a separate program. Clawboo installs it for you from **Settings** > **Runtimes**; click **Install** on its row, then **Install** in the panel that opens. Then connect it: > [tab] Claude Code > > Needs an Anthropic API key. On an npm install of Clawboo, also install the Claude Agent SDK first: `npm install -g clawboo @anthropic-ai/claude-agent-sdk`. Click **Connect** on the row and paste the key, or save it under **Providers**. See [Claude Code](/runtimes/claude-code). > [tab] Codex > > Needs a ChatGPT plan. Click **Sign in** on the row, then **Sign in with ChatGPT**. See [Codex](/runtimes/codex). > [tab] Hermes > > Needs Python 3.11 to 3.13 and one credential: an OpenRouter key (click **Connect** on the row), an Anthropic or OpenAI key you already connected, or a ChatGPT sign-in. See [Hermes](/runtimes/hermes). Once a row reads **Connected**, you can pick that runtime for a member in **Customize team** when you create a team. Until then the option is greyed out with "Connect in Runtimes". ## Set up OpenClaw OpenClaw runs its own Gateway on your computer. On the **OpenClaw** row, click **Set up**, then **Set up OpenClaw**. Clawboo installs OpenClaw if it is missing and configures it with a credential you already connected (a key, a ChatGPT sign-in or your local Ollama), or asks you for a key. Then it starts the Gateway and connects. When the Gateway is offline, the row offers **Reconnect** instead. **Manage** on a connected OpenClaw holds its Gateway controls and default model, and its **Disconnect** stops the local Gateway. See [OpenClaw](/runtimes/openclaw) for the full setup, including device approval. ## Disconnect a key or a runtime Removing a key and disconnecting a runtime are different actions: | You click | Where | What happens | | - | - | - | | **Disconnect** on a provider's row | **Settings** > **Providers** | Removes that one key from Clawboo's vault and from OpenClaw's files. No runtime can use it afterwards, unless it is also exported in your shell. | | **Disconnect** plus the provider's name, such as **Disconnect OpenRouter** | A provider row in a runtime's **Manage** list | The same as above, for that one provider. | | **Disconnect Ollama** | The **Ollama** row in **Clawboo Native** > **Manage** | Clawboo Native stops using Ollama. Your keys stay. | | **Disconnect** at the bottom of **Manage** | A runtime's row in **Settings** > **Runtimes** | Deletes every vault key that runtime reads (see the next table). | | **Sign out** | **Codex** > **Manage** | Runs Codex's own `codex logout`. Codex then reads **Needs login**. | | **Disconnect** | **OpenClaw** > **Manage** | Stops the local OpenClaw Gateway. Nothing is deleted; **Reconnect** starts it again. | > [!WARNING] > > A runtime's **Disconnect** deletes keys that other runtimes share. Clawboo keeps one copy of each key in its vault for every runtime. Disconnecting Hermes, for example, also deletes the Anthropic and OpenAI keys that Clawboo Native or Claude Code may run on. The confirmation dialog mentions only one key. To remove a single key, use **Disconnect** on its row under **Settings** > **Providers** instead. | Runtime | Its **Disconnect** deletes from the vault | Also | | - | - | - | | Clawboo Native | All ten provider keys: Anthropic, OpenAI, OpenRouter, Google, xAI, Groq, Mistral, Together, Cerebras, Moonshot | Turns off local Ollama. Connecting a key later does not turn it back on; only **Use** on the Ollama line does. | | Claude Code | Anthropic | | | Hermes | OpenRouter, Anthropic, OpenAI | A Hermes ChatGPT sign-in stays, so Hermes can still read **Connected**. | | Codex | Nothing; it has **Sign out** instead | | | OpenClaw | Nothing | Stops the Gateway. | After a runtime's **Disconnect**: * That runtime ignores keys in your shell environment and in `~/.openclaw/.env` until you connect it again, so it reads **Needs key** even if a copy is still there. * Other runtimes still find a key in your shell environment or in `~/.openclaw/.env`, where keys saved on the **Providers** page also live. A runtime's **Disconnect** never deletes those copies. * To reconnect Clawboo Native without pasting a key again, click **Connect** on its row, then **Use** on a provider whose key is still saved. ## Where keys are stored * **Clawboo's encrypted vault.** Every key you connect is saved in `~/.clawboo/secrets/runtime-keys.json` (in the folder `CLAWBOO_HOME` names, if you set it), encrypted with AES-256-GCM. The master key sits next to it in `secrets/master.key`, readable only by your user. The encryption protects the key file when it is copied on its own, in a backup or a sync folder. It does not protect against a program running as your user, which can read the master key too. * **Plain-text copies for OpenClaw.** A key you save on the **Providers** page is also written, unencrypted, to `~/.openclaw/.env` and to every existing OpenClaw agent's `auth-profiles.json`, because the OpenClaw Gateway reads keys from there. Clawboo writes those files readable only by your user account, and creates `~/.openclaw` for this even if you never installed OpenClaw. **Set up OpenClaw** copies the key it reuses there too. **Disconnect** on the **Providers** page removes these copies. * **Vault only.** A key you paste on a runtime's row under **Runtimes**, or during onboarding, stays only in the vault until **Set up OpenClaw** reuses it. * **ChatGPT sign-ins stay with each tool.** Codex keeps its sign-in in `~/.codex/auth.json`, Hermes in `~/.hermes/auth.json`, and OpenClaw in its own files. Clawboo copies the Codex and Hermes sign-ins into each agent's own folder for its runs. * **Keys from your environment.** Clawboo also uses a key exported in the shell you start Clawboo from, such as `ANTHROPIC_API_KEY`. Such a key makes runtimes read **Connected**, but the **Providers** page does not list it. Deleting `~/.clawboo` removes the vault but not the plain-text copies, and Clawboo still reads keys from `~/.openclaw/.env` afterwards. To remove keys for good, disconnect them on the **Providers** page first. See [Data and state](/operating/data-and-state) for every file Clawboo writes. > [accordion] Under the hood > > * For each runtime, Clawboo looks for a key in this order: the server's environment, the vault, then `~/.openclaw/.env`. A runtime you disconnected reads the vault only until you connect it again; Clawboo records that in `secrets/disconnected-runtimes.json`. The Ollama choice is recorded in `secrets/keyless-providers.json`. Neither file holds a secret. > * `CLAWBOO_SECRETS_MASTER_KEY` supplies the master key from outside the folder. Move your existing key into it rather than generating a new one, or saved keys stop decrypting. See [Environment variables](/reference/environment-variables). > * Clawboo never logs a key, returns one from its API, or writes one to its database. > * The routes behind these screens are `GET /api/providers`, `POST /api/providers/:id/connect` and `/disconnect`, and `POST /api/runtimes/:id/connect`, `/disconnect` and `/logout`. See the [Runtimes API](/reference/rest-api/runtimes). ## Check that it worked * **Settings** > **Providers**: the provider's row reads **Connected**. * **Settings** > **Runtimes**: the runtime's row reads **Connected**. **Manage** > **Re-check** asks again, and **Details** opens its health checks. * When you create a team, the runtime can be picked for a member in **Customize team**. * In team chat, type `@`, pick an agent on that runtime, and ask it something short. You should see a reply; if the run fails, the chat says "The run failed:" and why. ## Troubleshooting > [accordion] Invalid API key. > > The provider rejected the key. Copy it again from the provider's console, or create a new one, and save it again. > [accordion] Could not reach openai (timed out). > > The key check could not reach the provider from this computer within 8 seconds (the message names the provider). Check your network, proxy or firewall. If you are sure the key is right, click **Save anyway** (or **Connect anyway** on a runtime's row) to store it without the check. > [accordion] Provider returned 429. > > The provider answered the key check with an error other than a rejected key (the number varies). A 429 means the provider is limiting requests; a number from 500 up means a problem on the provider's side. Wait a moment, check your account with the provider, then try again. > [accordion] Clawboo Native has no provider key connected. > > The agent's provider has no key that Clawboo Native can read. Save that provider's key on **Settings** > **Providers**. If you disconnected Clawboo Native earlier, click **Connect** on its row and **Use** a saved key. > [accordion] My team stopped working after I disconnected another runtime > > A runtime's **Disconnect** deleted a key that your team's runtime also used. Save the key again on **Settings** > **Providers**. See [Disconnect a key or a runtime](#disconnect-a-key-or-a-runtime). > [accordion] A runtime still reads Connected after I removed its key > > It found another credential: a key exported in your shell, another key it can use (Hermes also runs on Anthropic and OpenAI keys), or, for Hermes, a ChatGPT sign-in. To stop a runtime from using shell or OpenClaw copies, use its own **Disconnect** under **Runtimes**. > [accordion] Ollama isn't answering on this machine. Start it (ollama serve) and try again. > > Clawboo could not reach Ollama at `http://localhost:11434` (or your `OLLAMA_BASE_URL`). Start Ollama, then click **Use** again. **Use anyway** turns it on without the check. > [accordion] Ollama doesn't have llama3.2 yet, so this run couldn't start. > > The agent's model is not pulled (the message names the model). Run the `ollama pull` command the message gives, or pick a model you have pulled in the agent's settings. **Check again** on the Ollama row refreshes the list. > [accordion] The codex CLI isn't installed yet. Install it first, then sign in. > > **Sign in with ChatGPT** needs Codex installed. Install it from **Settings** > **Runtimes** > **Codex**, then sign in again. More fixes for each runtime are on its page: [Clawboo Native](/runtimes/native), [OpenClaw](/runtimes/openclaw), [Claude Code](/runtimes/claude-code), [Codex](/runtimes/codex) and [Hermes](/runtimes/hermes). ## Next steps * [Choose a runtime](/runtimes/index): what each runtime is good at, and what it can do. * [Clawboo Native](/runtimes/native): the built-in runtime, its default models and local Ollama. * [Teams](/using/teams): create a team and pick each member's runtime. * [Budgets and spend](/using/budgets-and-spend): how each runtime's cost reaches your budgets. * [Data and state](/operating/data-and-state): every file Clawboo keeps on your computer. # Run work on a schedule with routines Source: https://docs.claw.boo/using/routines Create routines that send a team or one agent its instructions on a schedule, follow their runs, and resume one that stopped after a failure. A **routine** sends instructions to a team or to one agent on a schedule, with nobody at the dashboard: a morning briefing, an hourly inbox check, a weekly report. Use this page to create one, pick its schedule, follow its runs, and get one going again after it stops. What happens inside the scheduler is in [How routines run](/concepts/scheduling). ## Team routine or agent routine Each routine is one of two kinds. Choose by who should do the work: | | **A team task** | **An agent task** | | - | - | - | | What each run does | Posts your instructions in the team's **General** chat. The team's lead answers or brings in teammates, as if you had typed the message | Puts a new task on the board for one agent, which starts on it right away | | Where the result shows | The team chat, and the board for any work the lead hands out | The task's card on the board, which the team chat shows too | | Good for | Work that may need several agents | A clear chore one agent owns | | Example | "Every weekday at 9am, summarize what the team finished yesterday and flag anything blocked." | "Every hour, check the support inbox and draft replies to anything urgent." | The lead is Boo Zero when you have one, otherwise the team's Leader, otherwise its first active member. Instructions that start with `@Name` go to that teammate instead. ## Before you begin * A team task needs a team with at least one member. An agent task needs an agent; agents on no team can have routines too. * Agents on OpenClaw need the OpenClaw Gateway connected when the routine runs. * Clawboo must be running. Routines fire from the Clawboo server, so nothing runs while it is stopped or the computer sleeps. ## Create a routine **1. Open Routines** Open **Settings** (the gear in the sidebar, or **Cmd/Ctrl + ,**) and choose **Routines** under **Workspace**. You can also start from an agent's page: **New routine** in its **Tasks & Routines** card opens the same dialog, set up as an agent task for that agent. **2. Start a new routine** Click **New routine**. Under **Who it is for**, choose **A team task** or **An agent task**. **3. Pick who gets it** Pick the **Team**, and for an agent task the **Agent** on it. Agents on no team are listed under **No team (standalone agents)**. **4. Write the instructions** Under **What should happen**, write what each run should do. For a team task this is the message the lead receives; for an agent task it is the task's description. Write it so the work can be done without asking you anything: a team lead is told you may not be watching, and to make reasonable assumptions and state them. **5. Name it** Optionally give it a **Name**. Without one, it is named after the first line of the instructions. **6. Choose when it runs** Under **Runs**, pick a preset or **Custom** (see the next section). The dialog shows when the next run would be. **7. Create it** Click **Create routine**. The routine appears under **Team routines** or **Agent routines** with an **on** pill and a countdown such as `in 5m`. While **Create routine** is disabled, the line beside it says what is missing, such as "Choose an agent." A team with no members shows a warning, because nobody would pick the routine up. ## Choose a schedule | Preset | Runs | Cron | | - | - | - | | **Every 5 minutes** | Every 5 minutes | `*/5 * * * *` | | **Every 15 minutes** | Every 15 minutes | `*/15 * * * *` | | **Every 30 minutes** | Every 30 minutes | `*/30 * * * *` | | **Every hour** (the default) | On the hour | `0 * * * *` | | **Every 6 hours** | At 0:00, 6:00, 12:00 and 18:00 | `0 */6 * * *` | | **Every 12 hours** | At 0:00 and 12:00 | `0 */12 * * *` | | **Daily · 9am** | 9:00 every day | `0 9 * * *` | | **Weekdays · 9am** | 9:00 Monday to Friday | `0 9 * * 1-5` | | **Weekly · Mon 9am** | 9:00 every Monday | `0 9 * * 1` | **Custom** takes a cron expression: minute, hour, day of month, month and day of week. For example, `30 8 * * 1-5` runs at 8:30 on weekdays. For a single run at a set time, type `once@` followed by a date and time, such as `once@2026-12-01T09:00:00Z`. The `Z` means UTC; leave it off to use the local time of the machine that runs Clawboo. After that run it reads **finished** and never runs again; a time in the past is refused with "This schedule never runs again." Times follow the clock and time zone of the machine that runs Clawboo. If Clawboo was stopped or the computer was asleep when a routine was due, it runs once when Clawboo is back, then carries on with its schedule; missed times are not made up. A routine never runs twice at once: a time that passes while a run is still going is skipped. Routines are saved in Clawboo's database, so they carry on after a restart; a run cut off by the restart shows as **interrupted**. ## What a run does | | Team task | Agent task on Clawboo Native, Claude Code, Codex or Hermes | Agent task on OpenClaw | | - | - | - | - | | Each run | Posts the instructions in General, labelled **Routine** and the routine's name | Files a new task for the agent and runs it | Files a new task and sends it to the agent through the OpenClaw Gateway | | Needs | A team member to answer | The agent's runtime connected | The Gateway connected | | Counts as done | When the message reaches the lead (**posted**) | When the task finishes (**done**) | When the task finishes (**done**) | | Gets from [Memory](/using/memory) | Nothing automatically; agents search it as needed | The most relevant facts, at the start of the run | Nothing automatically | | Time limit | Ends after 30 minutes with no activity | Ends after 30 minutes with no activity | Ends after 10 minutes in total | Budgets apply to routine runs like any other work, and an agent task is refused before it starts when a hard-cap budget it falls under is already paused; see [Budgets and spend](/using/budgets-and-spend). [How routines run](/concepts/scheduling) lists the other guardrails each kind gets. A routine made in the dashboard has no repository, so its agent tasks run without a git worktree, in the team's folder. Claude Code, Codex and Hermes run with their own permission prompts turned off and can read and change any file your user account can; see [What agents can do](/concepts/permissions). Agents may also need your approval mid-run, for example for a connector call. A routine runs while you may be away, and a call nobody approves in time is refused, so prefer tools that do not ask for routine work. ## Follow a routine's runs The Routines view groups rows under **Team routines**, **Agent routines** and, if you use OpenClaw, **OpenClaw's own cron jobs**. It refreshes itself every few seconds while it is open; **Refresh** reads it at once. Each row shows the routine's name, its status, where it goes (for example `Research team chat · Boo Zero leads`, or `Ada · Research` for an agent on the Research team), how often it runs, its next run and its last run. | Pill | Meaning | | - | - | | **on** | Waiting for its next run | | **queued**, **starting**, **running** | A run is on its way or under way | | **paused** | You paused it. It does not run until you resume it | | **failed** | Its last run failed, and it stopped. See [When a routine stops](#when-a-routine-stops) | | **finished** | A one-time routine that already ran | | **disabled** | An OpenClaw cron job that is turned off on the Gateway | Click a row to open the routine: * **Sends to** names the team chat and its lead (with **Open team chat**), or the agent, its team and its runtime. * **What happens** shows the instructions, **Schedule** shows how often it runs and when next, and **Last run** shows when it last ran. * **Recent runs** lists the last ten runs, newest first, with how long each took. A team run reads **posted**; an agent run reads **done** once its task is finished, and **View task** opens that task with the agent's report. A run cut off because Clawboo stopped reads **interrupted**. Elsewhere, the [board](/using/board)'s calendar shows upcoming runs in **To do** as strips with the routine's name and time; click one to see its schedule or run it now. Each run's events also appear in [Observability](/using/observability). ## Change, pause, run or delete The buttons at the bottom of an open routine, and the shortcuts at the right of its row, do the following: | Button | What it does | | - | - | | **Edit** | Change who it is for, the instructions, the name or the schedule, then **Save changes**. Changing the schedule of a paused or stopped routine does not turn it back on | | **Pause** / **Resume** | Stop it from running until you resume it. Resuming sets its next run from the schedule. Neither works while a run is under way | | **Run now** | Start a run straight away without changing the schedule. Only while the routine is **on**: resume a paused or stopped routine first | | **Delete** | Asks first, then removes the routine. Tasks and messages it already produced stay | A greyed-out button says why when you hover it, for example "resume it first" or "it is running now". ## When a routine stops A routine whose run fails stops itself, on purpose: a routine that kept retrying a broken run would spend money and fill the board without getting anywhere. Its pill reads **failed**, the row shows the error, and the open routine says **Stopped after a failed run.** It does not run again until you resume it. **1. Read the error** Open the routine. The note under **Stopped after a failed run.** quotes the error; the table below lists the common ones. **2. Fix the cause** Do what the table says, for example raise a budget or reconnect the Gateway. **3. Resume it** Press **Resume**. The pill returns to **on**, and the routine runs at its next scheduled time. To check at once, press **Run now**. When an agent task's run fails, its task is set aside in **Needs you** on the board with a note naming the agent and the error, so it does not sit in **To do** looking like waiting work. The routine files a new task on its next run, so you can dismiss the old one or hand it to an agent yourself. Two outcomes are not failures and leave the routine running: another agent had already claimed the task, or you moved the task's card on the board while the agent worked on it. | Error | Cause | Fix | | - | - | - | | `dispatch refused: budget_paused` (on OpenClaw, `budget_paused:` and the budget's scope) | A hard-cap budget the agent falls under is paused | Raise or resume the budget in **Governance**, then resume the routine. See [Budgets and spend](/using/budgets-and-spend) | | The team budget is paused at its hard cap, so this run did not start. (or agent) | A team routine's message was refused by a paused hard-cap budget | Raise or resume the budget in **Governance**, then resume the routine | | `gateway_disconnected` | An OpenClaw agent's routine ran while the Gateway was not connected | Connect the Gateway (see [OpenClaw](/runtimes/openclaw)), then resume the routine | | The team has no active members. | Nobody was on the team to take the message | Add an agent to the team, then resume the routine | | The team this routine posts to no longer exists. | The team was deleted | Edit the routine to send it somewhere else, or delete it | | The team "..." is archived. | The team was archived | Edit the routine to send it to another team, or delete it | | `agent ... not found`, or The agent "..." was removed. | The agent was deleted, or removed from the OpenClaw Gateway | Edit the routine to send it to another agent, or delete it | | Starts with `run` | The agent's run itself failed or timed out | Open the task (**View task** in **Recent runs**) to see what went wrong | An OpenClaw cron job is different: the Gateway owns it, so a failed run shows **failed** but the job stays enabled. ## OpenClaw's own cron jobs The OpenClaw Gateway has a scheduler of its own that OpenClaw agents can use. The Routines view lists those jobs under **OpenClaw's own cron jobs**, so everything scheduled is in one place. You can **Disable** or **Enable**, **Run now** or **Delete** a job there, but not edit it: Gateway jobs are created in OpenClaw. While the Gateway is disconnected, a notice says the jobs are hidden until it reconnects, or that the list shown is the last one it sent. Your own routines are not affected. ## Example: a weekday briefing for a team This walks through a team routine from creation to its first result. **1. Create the routine** Click **New routine**, choose **A team task** and pick the team. Under **What should happen**, write "Summarize what the team finished yesterday and flag anything that is blocked." Name it "Morning briefing", choose **Weekdays · 9am**, and click **Create routine**. **2. Try it now** Open the routine and press **Run now**. Within a few seconds the run starts, and **Recent runs** gains an entry. **3. Read the result** Press **Open team chat**. In General, a message labelled **Routine · Morning briefing** appears, followed by the lead's reply and any work it hands out. The routine's **Recent runs** shows the run as **posted**. **4. Check the schedule** Open the [board](/using/board): the next runs appear in **To do** on the coming weekdays. An agent version works the same way: choose **An agent task** and pick the agent. Each run then files a task on the board, and **View task** in **Recent runs** opens it with the agent's report. > [accordion] Create and manage routines from the API > > `POST /api/schedules` creates a routine. This one posts to a team every weekday at 9:00: > > ```bash theme={"theme":"github-dark"} > curl -X POST http://127.0.0.1:18790/api/schedules \ > -H 'Content-Type: application/json' \ > -d '{ > "source": "clawboo-routine", > "domain": "team-task", > "target": "team", > "teamId": "<team-id>", > "cronSpec": "0 9 * * 1-5", > "label": "Morning briefing", > "taskTemplate": { "description": "Summarize what the team finished yesterday and flag anything that is blocked." } > }' > ``` > > For an agent routine, send `"target": "agent"` (the default) and an `agentId` instead of the `teamId`. The routine is filed on the agent's own team. A team or agent that does not exist or is archived, or a `teamId` that is not the agent's team, is refused with `400` and `code: "invalid_routine_target"`. A schedule Clawboo cannot read is refused with `400` and `code: "invalid_body"`, with the reason in `error`. Unlike the dialog, the API accepts a `once@` time in the past; that routine reads **finished** straight away. > > The task template takes fields the dialog does not offer: > > | Field | What it does | > | - | - | > | `repoPath` | Runs each agent task in its own git worktree of that repository, and checks any changes it makes before the task lands done (see [When work is checked](/concepts/verification)). The check needs a `VERIFY_CMD` in the worktree's `init.sh`. A repository Clawboo cannot check out fails the run with `dispatch refused: workspace_unavailable` | > | `kind`, `model`, `priority` | The task kind (default `code`), the model, and the task's priority | > | `maxNodeCents` | A spending cap for each run, in cents | > | `teamTaskId` | Runs one existing board task instead of filing a new one each time | > > A routine bound to an existing task with `teamTaskId` must be a one-time `once@` routine (`400`, `code: "bound_recurring_schedule"` otherwise), because a task can be claimed only once. Binding is refused with `409` and `code: "duplicate_firing_owner"` when another scheduler already owns that task. A team routine cannot be bound. > > Routine ids look like `clawboo-routine:<id>`. `PATCH /api/schedules/:id` pauses or resumes (`{ "action": "pause" }`) or edits (`{ "patch": { "cronSpec": "0 8 * * 1-5" } }`), `POST /api/schedules/:id/run` runs it now and answers `202` at once, and `GET /api/schedules/:id/runs` returns the recent runs (ten by default). Full shapes and codes are in the [Schedules API reference](/reference/rest-api/schedules). ## Troubleshooting > [accordion] A routine did not run at its time > > Check its pill. A **paused** or **failed** routine waits for you; resume it. If Clawboo was stopped or the computer was asleep at that time, the routine runs once when Clawboo is back. A time that passed while an earlier run was still going is skipped. > [accordion] Run now or Pause is greyed out > > Hover the button to see why. A paused or stopped routine must be resumed before **Run now**, and nothing can be paused or resumed while a run is under way. > [accordion] A row reads Team not found or Agent not found > > The team or agent the routine points at was deleted after the routine was made, so its next run will fail. Edit the routine to send it somewhere else, or delete it. > [accordion] The agent asks questions in its reply and waits > > Nobody may be there to answer. Rewrite the instructions so the work can be done without asking: name the inputs, the output you want and where it should go. ## Next steps * [How routines run](/concepts/scheduling): missed runs, restarts, failures and the guardrails each kind of run gets. * [Board](/using/board): upcoming routine runs on the calendar, and the tasks agent routines file. * [Team chat](/using/team-chat): where a team routine's message and the lead's work appear. * [Budgets and spend](/using/budgets-and-spend): cap what routine runs can spend. * [Schedules API](/reference/rest-api/schedules): every field and status code. # Update and maintain Clawboo Source: https://docs.claw.boo/using/system Update Clawboo from the Update available chip, update OpenClaw and restart its Gateway, set the default command approval, and find backups and logs. Use this page to update Clawboo, update OpenClaw and restart its Gateway, and find your backups and logs. It also covers the **System** panel in Settings, which shows your OpenClaw and Node.js versions, updates OpenClaw, and sets the default command approval for OpenClaw agents. ## Where things are | You want to | Go to | | - | - | | Update Clawboo | The **Update available** chip at the bottom left of the dashboard | | Update OpenClaw | **Settings** > **System** > **Check for Updates** | | Restart the OpenClaw Gateway | **Settings** > **Runtimes** > **OpenClaw** > **Manage** | | Set the default command approval for OpenClaw agents | **Settings** > **System** > **Command Approval** | | Change one agent's command approval | The agent's page, **Details** > **Permissions** | | Change the default model for OpenClaw agents | **Settings** > **Runtimes** > **OpenClaw** > **Manage** > **Default model** | | Add or remove a provider key | **Settings** > **Providers** (see [Connect providers and runtimes](/using/providers-and-runtimes)) | | Check that Clawboo's own parts work | **Settings** > **System Health** (see [System Health](/using/system-health)) | | Back up your data, or read a log | The terminal (see [Backups and logs](#backups-and-logs)) | **Check for Updates** in the **System** panel updates OpenClaw, not Clawboo. Clawboo updates from the chip. ## Update Clawboo When a newer Clawboo is on npm, an **Update available** chip appears at the bottom left of the dashboard, with the new version number. Clawboo asks npm's registry for the newest version number, then reuses the answer for six hours. What the chip does depends on how you run Clawboo: > [tab] Global install > > **1. Click the chip** > > The chip reads **Click to update**. Click it. It shows **Installing update…** while npm installs `clawboo@latest`, then **Restarting…**. > > **2. Wait for the reload** > > The server restarts on the same port, and the page reloads into the new version. You should see the dashboard as you left it. This browser remembers the last update check for up to an hour, so the chip can show again in that time even though you are up to date: dismiss it with its **×**. `clawboo --version` in a terminal prints the version you now have. > > If the page has not come back after about 90 seconds, the chip reads "Update installed. Restart Clawboo to finish." Run `clawboo restart`. > [tab] npx > > The chip reads **Copy command**. Click it to copy `npx clawboo@latest`, then run that in a terminal. It finds the older server still running and offers to restart it. The **×** on the chip hides it until a newer version comes out. To update a global install from a terminal instead: ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} npm install -g clawboo@latest clawboo restart ``` Your data stays where it is: when a newer version opens your database, it adds what it needs. [Install, update and remove Clawboo](/getting-started/installation) covers installs in full. If the update fails, see [Troubleshooting](#troubleshooting). ## The System panel Open **Settings** > **System**, in the **System** group. The panel has two cards: * **Command Approval**: the default command approval for OpenClaw agents. It appears only while the dashboard is connected to an OpenClaw Gateway. See [Set the default command approval](#set-the-default-command-approval). * **System Info**: the installed **OpenClaw** version (or **Not installed**), the **Node.js** version Clawboo runs on, OpenClaw's **State Dir** (its folder, usually `~/.openclaw`), and **Config** (`openclaw.json`, or **Not found**). **Check for Updates** sits below them. **System Info** is read once when you open the panel, and again after an update. ## Update OpenClaw **1. Update the OpenClaw program** Open **Settings** > **System** and click **Check for Updates**. It starts at once, with no confirmation: it runs `npm install -g openclaw@~2026.9` and shows npm's output below the button. You should see "Updated to" and the version, and the **OpenClaw** row shows the new version. **2. Restart the Gateway** The Gateway that is already running keeps the old version until it restarts. Open **Settings** > **Runtimes**, click **Manage** on the **OpenClaw** row, and click **Restart** in its **Gateway** block. You should see "Gateway restarted". **Check for Updates** installs the newest release in the OpenClaw line this Clawboo is tested with (2026.9), which may not be OpenClaw's newest. If OpenClaw is not installed, the same button installs it; to set OpenClaw up and start its Gateway, use **Settings** > **Runtimes** > **OpenClaw** instead (see [OpenClaw](/runtimes/openclaw)). npm can refuse the install when your Node.js is outside the range OpenClaw supports, which is narrower than Clawboo's; the OpenClaw page lists it. ### If OpenClaw needs its data updated A newer OpenClaw can refuse to start until `openclaw doctor --fix` updates data that an older version wrote. When Clawboo starts the Gateway and OpenClaw asks for this, Clawboo runs `openclaw doctor --fix --non-interactive` once, then starts the Gateway again. It does this whenever Clawboo starts the Gateway: **Restart**, **Reconnect OpenClaw**, or the OpenClaw setup. It leaves a Gateway you run as a launchd or systemd service alone. The repair usually takes seconds. If the repair fails, the error says so and includes the reason ("OpenClaw needs `openclaw doctor --fix`, and running it failed"). Run `openclaw doctor --fix` in a terminal to see what it needs, then start the Gateway again. Do the same if OpenClaw still asks for it after Clawboo ran it. When the Gateway fails to start for another reason, the error reads "Gateway did not start:" followed by OpenClaw's own reason. The Gateway's log is `~/.clawboo/gateway.log`. ## Set the default command approval The **Command Approval** card sets what OpenClaw agents do before they run a shell command, when they have no setting of their own. It writes `tools.exec.ask` in OpenClaw's `openclaw.json`. | Option | What it means | | - | - | | **Run Freely** | Agents run commands without asking | | **Ask for Unknown** | Agents ask you about commands not on their allowlist | | **Always Ask** | Agents ask you about every command | Pick an option from the **Default:** list. You should see "Default command approval set to" and the option's name. If the write fails, the list goes back to its old value and you see "Failed to update Gateway config". This is a fallback, not an override. Every OpenClaw agent Clawboo creates gets its own setting, **Ask for Unknown**, and follows that instead. So the default mostly affects OpenClaw agents created outside Clawboo. When `openclaw.json` sets no default, the list shows **Run Freely**, which is also what OpenClaw does for an agent with no setting. To change one agent, open its page and go to **Details** > **Permissions**; the hint under the list mentions a Personality tab, but the setting is on **Permissions**. [Command permissions](/using/command-permissions) covers per-agent settings and the commands an agent may run without asking. The default does not affect Clawboo Native, Claude Code, Codex or Hermes agents; [What agents can do](/concepts/permissions) explains what they may run. ## Backups and logs * **Back up the database**: run `clawboo backup`. It writes a one-file copy of `~/.clawboo/clawboo.db` to the current folder, even while Clawboo runs. Your saved keys, and the key that decrypts them, are in `~/.clawboo/secrets/` and not in the copy. A good moment is right before you update. * **Restore or reset**: [Data and state](/operating/data-and-state) has the steps, and lists what a full backup needs. * **Server log**: `~/.clawboo/server.log`, for a server started with `clawboo`. It rolls over to `server.log.1` past 5 MB. After an update from the chip, the restarted server writes nothing to it until you run `clawboo restart`. * **Gateway log**: `~/.clawboo/gateway.log`, for an OpenClaw Gateway that Clawboo started. The [CLI reference](/reference/cli) covers `clawboo stop`, `clawboo restart` and `clawboo backup` in full. > [accordion] Under the hood > > * The chip reads `GET /api/system/self-version`, and **Click to update** calls `POST /api/system/self-update`, which runs `npm install -g clawboo@latest` and restarts the server on the same port. It works only for a global install; an npx run gets the command to copy instead. > * **Check for Updates** calls `POST /api/system/install-openclaw`. **Restart** calls `POST /api/system/gateway` with `{"action": "restart"}`, which waits up to 60 seconds for the Gateway's port. > * **Command Approval** reads `GET /api/system/openclaw-config` and writes `PATCH /api/system/openclaw-config` with `{"exec": {"ask": "on-miss"}}` (or `off`, `always`). > * See the [System API](/reference/rest-api/system) for every route. ## Troubleshooting > [accordion] The chip says the update failed > > The chip shows npm's own reason, such as no matching version, a network failure, a permission problem or a package install script that failed, with a button that copies the command. > > "Permission denied. A global install needs elevated permissions." means npm cannot write to its global folder, which is common with a system-wide Node.js. Run the copied command in a terminal, or install Node.js with a version manager such as nvm or fnm, which keeps global packages in your home folder and needs no `sudo`. > [accordion] Check for Updates shows Permission denied > > The log reads "Permission denied. Your Node.js installation may require sudo for global installs." and suggests a `sudo npm install -g` command. npm cannot write to its global folder. Installing Node.js with a version manager such as nvm or fnm, or with Homebrew, keeps global packages where you can write them, so no `sudo` is needed. Then click **Check for Updates** again. > [accordion] OpenClaw still runs the old version after an update > > **Check for Updates** replaces the OpenClaw program but leaves the running Gateway alone. Restart the Gateway: **Settings** > **Runtimes** > **OpenClaw** > **Manage** > **Restart**. > [accordion] The Command Approval card is missing > > The card appears only while the dashboard is connected to an OpenClaw Gateway. Connect or reconnect OpenClaw in **Settings** > **Runtimes**. If you don't use OpenClaw, there is nothing to set here. > [accordion] I can't find how to start or stop the Gateway > > The OpenClaw row in **Settings** > **Runtimes** holds those controls, not the **System** panel. While the Gateway runs, **Manage** shows **Restart**, and **Disconnect** stops it. While it is stopped, the row offers **Reconnect**, which starts it again. See [OpenClaw](/runtimes/openclaw). ## Next steps * [Install, update and remove Clawboo](/getting-started/installation): install methods, updating from a terminal, and uninstalling. * [OpenClaw](/runtimes/openclaw): set up OpenClaw, manage its Gateway, and fix Gateway errors. * [Command permissions](/using/command-permissions): review and revoke what each agent may run. * [System Health](/using/system-health): check Clawboo's folder, database, vault and connections. * [Data and state](/operating/data-and-state): every file Clawboo keeps, and how to back up, restore and reset. # Check System Health Source: https://docs.claw.boo/using/system-health Read the checks Clawboo runs on its own folder, database, key vault and connections, and fix the one that fails. **System Health** shows whether Clawboo's own parts work: its folder, its database, the vault that holds your keys, the tool servers your agents use, and its connections to the OpenClaw Gateway and an OpenTelemetry collector. Use this page when something in Clawboo is not working, or to check an install after an upgrade or a move. For your agents' own work and errors, see [Check on your agents](/using/observability). ## Open System Health Open **Settings** (the **Settings** row at the bottom of the left sidebar, or Cmd+, on macOS and Ctrl+, on Windows and Linux), then **System Health** in the **System** group. It works on a fresh install before you connect anything, and while the OpenClaw Gateway is down. Clawboo runs its checks each time its server starts. The panel shows the results of the last run and reads them again every 30 seconds while its tab is visible, but reading does not run the checks again. Click **Re-run probe** to run them now, for example after you fix something. A failed check never stops Clawboo. The server keeps running, so you can read the details here. ## Read the panel * **The header pill** sums up the checks: **All systems go**, **N degraded** or **N fatal**. If you don't use OpenClaw, expect **1 degraded** from the **OpenClaw Gateway** check (see [Fix a failing check](#fix-a-failing-check)). * **A banner** appears when a check fails. "Running degraded." means some optional parts are unavailable and the rest of Clawboo works normally. "The install has a fatal problem." means Clawboo cannot work properly until you fix it. The banner points you to the failing check, and to resetting `~/.clawboo` only as the last resort. * **Checks** lists each check with an **OK**, **Degraded** or **Fatal** pill, how long it took, and a one-line result. A failed check also shows its details: what went wrong, often with the file or setting involved. * **Runtime state** and **Production defaults** show what this server is using (see [Runtime state and defaults](#runtime-state-and-defaults)). ## The checks The checks run in this order. Three of them are fatal when they fail, because nothing works without them; the rest are degraded. | Check | What it checks | When it fails | | - | - | - | | **Clawboo home writable** | Clawboo can create and write its folder, `~/.clawboo` (or the folder `CLAWBOO_HOME` names) | Fatal | | **Secrets vault permissions** | Only your user account can read the key vault's folder, its master key, and the key Clawboo uses to identify itself to the OpenClaw Gateway (macOS and Linux) | Degraded | | **Master key** | The vault's master key can still decrypt what it encrypted | Degraded | | **Database integrity** | The database opens and passes SQLite's integrity check | Fatal | | **Database schema** | Every table and column Clawboo needs is in the database | Fatal | | **API port file** | The port file names the port this server listens on | Degraded | | **MCP servers** | Clawboo's four tool servers start and answer: tasks (the board), memory, tools (connected apps) and teamchat (messages between agents) | Degraded | | **OpenClaw Gateway** | Clawboo's server is connected to the OpenClaw Gateway. Without a running Gateway it always reads **Degraded**, even if you don't use OpenClaw | Degraded | | **OTel exporter** | The OpenTelemetry exporter loaded. Passes as disabled when no endpoint is set | Degraded | ## Fix a failing check Fix the fatal checks first: a database that cannot open also stops the **MCP servers** check. After a fix, click **Re-run probe**. Where a fix says to restart Clawboo, run `clawboo restart` (on an install protected by an access token, see the [CLI reference](/reference/cli) instead). > [accordion] Clawboo home writable is Fatal > > **What it means:** Clawboo cannot create or write its folder. The details give the system's reason, such as a permission error. > > **Fix:** make sure your user account owns `~/.clawboo` and can write to it, or set `CLAWBOO_HOME` to a folder you can write. Then restart Clawboo. > [accordion] Secrets vault permissions is Degraded > > **What it means:** other users on this computer could read the files that protect your keys. The details name each file and its current mode, such as `secrets/ is 755`. > > **Fix:** make those files private to your account, then click **Re-run probe**. Run the line for each file the details name: > > ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} > chmod 700 ~/.clawboo/secrets > chmod 600 ~/.clawboo/secrets/master.key > chmod 600 ~/.clawboo/proxy-device-identity.json > ``` > > If you set `CLAWBOO_HOME`, use that folder instead of `~/.clawboo`. > [accordion] Master key is Degraded > > **What it means:** the key that encrypts the vault is not the one that encrypted it, so Clawboo cannot read your saved provider keys or connector sign-ins. The **master key** row under **Runtime state** reads **unreadable** and asks you to re-enter runtime keys. > > The key is `~/.clawboo/secrets/master.key`, or the value of `CLAWBOO_SECRETS_MASTER_KEY` when that is set. It usually changes when that variable is set, changed or removed, when `master.key` is deleted or replaced, or when `~/.clawboo` is restored without its `secrets` folder. > > **Fix:** > > 1. If you can, put the original key back: set `CLAWBOO_SECRETS_MASTER_KEY` to its old value, or restore the original `master.key`. Restart Clawboo, then click **Re-run probe**. Your saved keys work again. > 2. If the original key is gone, the saved keys cannot be recovered. Delete `~/.clawboo/secrets/runtime-keys.json`, the vault file that holds them, and click **Re-run probe**. Then re-enter your keys under **Settings** > **Providers** (or on a runtime's card under **Settings** > **Runtimes**, wherever you first entered them) and reconnect your connectors. > > If the result says the vault is unreadable instead, the details give the reason, such as a `CLAWBOO_SECRETS_MASTER_KEY` that is not a 32-byte key. > [accordion] Database integrity is Fatal > > **What it means:** the database could not be opened, or SQLite found damage in it. The details give SQLite's own words. > > **Fix:** stop Clawboo with `clawboo stop` and restore `~/.clawboo/clawboo.db` from a backup. With no backup, keep a copy of the damaged file, then reset the data: Clawboo starts with an empty database, and you lose everything that was in it, such as your teams, board, chats and memory. [Data and state](/operating/data-and-state) has the steps for both. > [accordion] Database schema is Fatal > > **What it means:** a table or column Clawboo needs is missing from the database. The details name it. > > **Fix:** restart Clawboo first. Each time it opens the database it adds any missing columns, so this usually clears. If the result says the schema "cannot be upgraded in place", this version of Clawboo cannot run on that database: the details name the file and the choices. Either run the Clawboo version that created the database, or move the file aside to start clean, which loses everything stored in it, such as your teams, board, chats and memory. > [accordion] API port file is Degraded > > **What it means:** `~/.clawboo/api-port.txt` names a different port from the one this server listens on, or is missing. The `clawboo` command reads this file to find the server, so `clawboo stop` and `clawboo restart` can miss this one. A common cause is a second Clawboo server that started later and wrote its own port. > > **Fix:** if you run more than one Clawboo server, stop the one you don't need with `CLAWBOO_API_PORT=<port> clawboo stop`. Then run `clawboo restart`, and the running server writes its port again. This server's port is the **API port** row under **Runtime state**. > [accordion] MCP servers is Degraded > > **What it means:** one of Clawboo's tool servers did not start or answer, so agents may lose its tools: board tasks, memory, connected apps or messages between agents. The details name the server and its error. "MCP servers not probed (database unavailable)" means the database failed first; fix that. > > **Fix:** restart Clawboo. If the check still fails, look for the error in the server log, `~/.clawboo/server.log`, and [open an issue](https://github.com/clawboo/clawboo/issues) with the details. > [accordion] OpenClaw Gateway is Degraded > > **What it means:** Clawboo's server was not connected to the OpenClaw Gateway when the checks ran. The result reads "OpenClaw Gateway not reachable", followed by the connection state, and the details give the Gateway's address. If you don't use OpenClaw, this is expected and harmless: Clawboo checks for a Gateway at its default address on every start, so the check reads **Degraded** and the panel says "Running degraded." even though nothing is wrong. If you do use OpenClaw, your OpenClaw agents still show as they were at the last sync but cannot take work. Your other runtimes are not affected. > > **Fix:** if you use OpenClaw, start or reconnect the Gateway in **Settings** > **Runtimes** > **OpenClaw** (**Reconnect**, or **Manage** > **Restart**), then click **Re-run probe**. The check reflects the moment the checks ran, so a Gateway that started afterwards reads **Degraded** until you run them again. [OpenClaw](/runtimes/openclaw) covers the Gateway's own errors. > [accordion] OTel exporter is Degraded > > **What it means:** an OpenTelemetry endpoint is set (`OTEL_EXPORTER_OTLP_ENDPOINT` or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`), but the exporter failed to load. Traces stay in Clawboo's own log. On an install from npm this is expected: the package does not include the OpenTelemetry SDK. > > **Fix:** to clear the warning, unset both variables and restart Clawboo. To export traces, run Clawboo where the SDK is installed, such as a source checkout. See [Export traces with OpenTelemetry](/using/observability#export-traces-with-opentelemetry). ## Runtime state and defaults **Runtime state** shows what this server is using. It helps when you have moved Clawboo's folder, or run more than one copy. | Row | What it shows | | - | - | | **clawboo home** | Clawboo's folder: `~/.clawboo`, or the folder `CLAWBOO_HOME` names | | **database** | The database file this server uses | | **API port** | The port this server listens on | | **OpenClaw state dir** | OpenClaw's folder, usually `~/.openclaw` | | **secrets vault** | Whether the encrypted key vault exists: **present** or **not yet created** | | **master key** | **verified**, or **unreadable** when the **Master key** check failed | **Production defaults** shows how this version is set up out of the box. Its **log level** and **budget posture** rows always show the shipped default, not your own settings: | Row | What it shows | | - | - | | **log level** | Always `info`, the shipped level. Setting `LOG_LEVEL` changes what the server logs, but not this row | | **budget posture** | Always "track-and-warn (no global hard cap; warns at 80%)". It does not reflect your budgets: a hard cap you set in **Governance** does not show here (see [Budgets and spend](/using/budgets-and-spend)) | | **observability** | "local event log (OTel opt-in)", or "OTel exporter active" whenever an OpenTelemetry endpoint is set, even if the exporter failed to load. The **OTel exporter** check is the one to trust | > [accordion] Under the hood > > * `GET /api/health` returns the report from the last run of the checks, and `POST /api/health/recheck` runs them again (the **Re-run probe** button). Both carry a top-level `ok`, which is `true` when no check is fatal, plus `fatal` and `degraded` lists of check ids. A monitor can read that one field. > * With an access token set (`STUDIO_ACCESS_TOKEN`), these routes need the access cookie like every other `/api` route. > * Check ids, in order: `clawbooHomeWritable`, `vaultPerms`, `masterKeyBootSentinel`, `databaseIntegrity`, `databaseSchema`, `apiPortFileMatches`, `mcpServersHealthy`, `openclawGatewayReachable`, `otelExporterReachable`. > * Credential-shaped text in a check's details is masked before it is sent. > * See the [Settings, health and layout API](/reference/rest-api/settings) for the full report shape. > > ```bash theme={"theme":{"light":"github-dark","dark":"github-dark"}} > curl -s http://localhost:18790/api/health > ``` > > Use the port your Clawboo prints when it starts, if it is not 18790. ## Next steps * [Data and state](/operating/data-and-state): every file in `~/.clawboo`, and how to back up, restore and reset. * [CLI reference](/reference/cli): restart Clawboo and find its log. * [OpenClaw](/runtimes/openclaw): start, restart and repair the OpenClaw Gateway. * [Connect providers and runtimes](/using/providers-and-runtimes): re-enter a provider key. * [Check on your agents](/using/observability): your agents' work, errors and traces. * [Troubleshooting](/help/troubleshooting): fixes for other problems. # Work with your team in chat Source: https://docs.claw.boo/using/team-chat Give a whole team work in General or a room: send a message, aim it at one agent with @, stop it, start fresh, and follow its tasks beside the chat. Team chat is where you give a whole team work. Each team has one main chat, **General**, and can have **rooms**: separate chats with some of its members. You write one message. Boo Zero, your team leader, decides who does what, and each piece of work shows up as a task you can follow beside the chat. Use this page to open a chat, send work, aim a message at one agent, stop work, start a conversation over, and organize rooms. What happens between your message and the answer is explained in [How a team works](/intro/how-it-works). ![A team's General chat: teammates' messages and two task cards on the left, and the Team graph and Tasks cards stacked on the right](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-space.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=c6bed9e50135bba92df482a1ddf6cd8d) ## Before you begin * A team with at least one agent. See [Teams](/using/teams) to create one. * Each agent's runtime set up. Clawboo Native needs a provider (an API key or a local Ollama), OpenClaw agents need the OpenClaw Gateway, and Claude Code, Codex and Hermes need their own setup. See [Providers and runtimes](/using/providers-and-runtimes). ## Open a team's chat **1. Pick the team** Click the team's icon in the narrow rail at the far left. The team's Home page opens, and the sidebar lists the team's agents and then its chats. **2. Open General or a room** Click **General**, listed as **General (Main Room)**, or a room's name. General is always first, right under **New room**. **3. Meet your team (first visit only)** The first time you open a chat of a new team, Clawboo introduces the team before the chat opens. See [Meet your team](#meet-your-team). You should see the chat on the left and its cards on the right. The team graph and **Tasks** cards start open. ## Meet your team The first visit to a team's chat shows a short introduction instead of the chat. It runs once per team. **1. Meet the team** A **Meet your team** card shows the team's agents and says the team is led by Boo Zero. Click **Get Started**. The button stays disabled while the team has no agents. **2. Introduce yourself** Write a few words about who you are and what you are working on (at least 5 characters). Click **Continue to Team Space**. You should see a **Welcome aboard! Your team is ready.** notice, a short welcome from Boo Zero, and the chat with its cards. The welcome is a fixed greeting shown once on this screen, not a reply from a model, so it is gone after a reload. Your introduction is saved with the team. The agent that answers you in any of the team's chats receives it with your message, so it knows who you are from the first message. Clawboo also adds it to each team member's SOUL.md file under an **About the User** heading, where you can edit it (see [Agents](/using/agents)). You do not see this introduction again for the same team, and its rooms open straight to their chat. A team whose General chat already has messages, for example from a team routine, also opens straight to the chat. ## Send a message Type in the box at the bottom of the chat and press **Enter**. **Shift+Enter** starts a new line. The box reads "Message General… (@name to target)" in General, and names the room in a room. Who answers depends on how the message starts: | Your message | Who gets it | | - | - | | Starts with `@` and an agent's name | That agent | | Anything else | Boo Zero, your team leader | | Anything else, and there is no Boo Zero | The team's Leader if it is in this chat, otherwise the chat's first member | The team's Leader is the optional member that carries the **Leader** badge (see [Teams](/using/teams)). A few rules decide whether an `@` mention counts: * Only a mention at the very start of the message picks the agent. A mention later in the message is plain text. * Names match whatever their capitals, and the longest matching name wins. The name must be followed by a space or end the message. * In a room, only the room's members and Boo Zero can be mentioned. A message to anyone else goes to the leader instead. * The agent receives your message exactly as you typed it, mention included. Typing `@` opens a list of names to pick from. The **Tag** row under the conversation inserts a mention when you click a name. Two commands are handled by Clawboo instead of being sent to an agent: * `/reset` (or `/new`) starts the conversation fresh. See [Start a conversation fresh](#start-a-conversation-fresh). * `/rule` followed by a space and some text saves a lasting rule for the whole team. Rules are covered in [Teams](/using/teams). ## What happens next Boo Zero reads your message and either answers it or hands parts of the work to teammates. Each handoff becomes a task on the team's [board](/using/board). A card for it appears in the conversation and in the **Tasks** card, and both update as the work moves. Teammates work at the same time. When their results are in, the leader writes one answer that pulls them together. ![Boo Zero's answer in a team's General chat, combining three teammates' work, with the Team graph and Tasks cards beside it](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/leader-synthesis.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=a780a23f9ba8a5c4c2d2de9ee0e37f8b) * The work runs on the Clawboo server, not in your browser. Closing the tab does not stop it, and the chat catches up when you come back. * A leader hands out at most 8 tasks in one turn. Numbers like this are listed in [Limits and defaults](/reference/limits-and-defaults). * A teammate can hand off part of its own task. If it has finished its task by the time those results come back, its follow-up goes on its task's card as a comment, not into the chat. * What each agent is told on every turn is described in [What an agent sees](/concepts/agent-context). * An image in an agent's reply shows as a link named after its alt text, such as **Image: sales chart**. It never loads on its own and opens in a new tab only when you click it. ### Handoffs that need your approval A handoff whose text mentions a risky action waits for you before it starts. The check matches whole words, in any letter case: delete, destroy, deploy, publish, release, secret, credential, prod or production, the phrases drop table and rm -rf, and api\_key, api-key, apikey, force-push, force\_push or forcepush. Other forms such as "deleted", "secrets", "API key" or "force push" written with a space do not trigger it. The request appears under **Needs you** in the **Tasks** card and on the board. If you deny it, or nobody answers within 5 minutes, the handoff is skipped and the leader is told. See [Approvals](/using/approvals). ### Task cards in the conversation Each task card shows the task, the agent on it, and a status: **Queued**, **Working**, **Verifying**, **Done** or **Cancelled**. A task that waits on you shows why instead, for example **Failed**, **Stopped** or **Unassigned**, with **Resolve on board**. A finished card shows the agent's report, and a failed one shows the reason. * **Comments & activity** at the foot of a card opens its comments and its live activity inside the card. * The expand icon opens the task on the [board](/using/board) with its details open. * After you [clear a chat](#start-a-conversation-fresh), cards from before the clear leave the conversation. The tasks stay on the board. ## Stop the work While agents in this chat are working, the send button turns into **Stop**. Pressing it: * ends every run in this chat, and drops replies and handoffs that were still waiting to start; * sends the chat's tasks back to **To do**, where they wait under **Needs you** as **Stopped**; * leaves the team's other chats running. A stopped task never restarts by itself. To run it again, press **Retry** on the board, or open it in the **Tasks** card and send a comment: a comment on a stopped task starts its agent on it again. ## Start a conversation fresh There are two ways to start over, and they keep different things: | | `/reset` (or `/new`) | **Clear chat** | | - | - | - | | How | Type it in the message box and send it | **...** menu on the chat's row in the sidebar, then confirm | | Messages | Stay on screen. A divider marks where the fresh start is | Leave the chat, which starts over from its welcome. Nothing is deleted | | Task cards in the conversation | Stay | Cards from before the clear leave the conversation | | Tasks | Stay on the board and in the **Tasks** card | Stay on the board and in the **Tasks** card | | What the agents carry | Each agent's next reply starts without the earlier conversation | The same | | Team rules, your introduction, memory | Kept | Kept | | The team's other chats | Not touched | Not touched | Use `/reset` when you want the agents to let go of a long thread but keep it readable. Use **Clear chat** when you want an empty chat. Neither one stops work that is running: use **Stop** for that. OpenClaw agents keep their conversation inside the OpenClaw Gateway, so both actions send them `/reset` through the dashboard's connection to the Gateway. If the dashboard is not connected to the Gateway at that moment, an OpenClaw agent keeps its conversation. Reconnect and send `/reset` in the chat again. `/reset` also works in an agent's own chat; see [Agents](/using/agents). > [accordion] Under the hood: what each action changes > > * **Clear chat** renames the chat's stored messages with a `#cleared:<time>` suffix, so nothing reads them any more. It deletes nothing. > * Both actions drop the conversation that Clawboo keeps for Clawboo Native and Codex agents in this chat, so their next turn starts a new session. Both send `/reset` to OpenClaw agents. > * `/reset` writes one divider into the conversation, which stays after a reload. ## Rooms A room is a separate chat inside a team, with some of its members. Use one to keep a side project's conversation and tasks apart from General. A room has its own conversation, its own task cards and its own graph card (**Room graph**), which draws only its members. Boo Zero leads every room the way it leads General, though it is never listed as a member. The team's rules, brief, memory and board are shared by all of its chats. ### Create a room **1. Start a room** Click **New room** at the top of the team's chats in the sidebar. **2. Name it and pick members** Enter a **Name** (up to 80 characters) and choose at least one member. Boo Zero is not offered, because it is in every room already. **3. Create it** Click **Create room**. The room's chat opens with a "Welcome to" greeting and the room's name. ### Talk in a room A room works like General, with three differences: * An `@` mention reaches only the room's members and Boo Zero. * With no mention, a message goes to Boo Zero, else to the team's Leader if it is in the room, else to the room's first member. * Agents in a room see only the room's members as teammates, so they hand work only to them. ### Manage rooms Point at a chat's row in the sidebar and open its **...** menu: | Action | What it does | | - | - | | **Pin** / **Unpin** | Pinned rooms sit right under General, most recently pinned first. The rest follow, newest first | | **Rename** | Edit the name in place. **Enter** or clicking away saves it, **Escape** cancels | | **Clear chat** | See [Start a conversation fresh](#start-a-conversation-fresh) | | **Delete** | Asks first, then archives the room | General's menu has only **Clear chat**: General cannot be renamed, unpinned or deleted. To change a room's members, click the members' faces in the room's header (**Edit room members**). The dialog renames the room, changes its members, or deletes it. A member you remove finishes the turn it is taking, and the room's later turns go only to the members who remain. Deleting a room keeps its conversation, but the room leaves the sidebar and its running work stops. Its tasks stay on the board, where **Retry** or **Assign** runs them from General. If you were in the room, you land in General. ## The cards beside the chat The header above the chat shows the team's icon, the chat's name and its members. The gear opens the team's brief and rules, which are covered in [Teams](/using/teams). Next come the toggles for the four cards, which stack beside the chat in this order: | Card | What it shows | | - | - | | **Browser** | The page each agent in this chat has open in its browser | | **Team graph** (**Room graph** in a room) | The chat's agents and how work flows between them. See [Org chart](/using/org-chart) | | **Tasks** | This chat's tasks, grouped by what they need | | **Activity** | Live tool calls, results and errors from the whole team's runs, in a room too | The **Team graph** and **Tasks** cards start open. Show or hide a card with its toggle in the header, or close it with its own close button. **Maximize** in a card's header fills the view with that card until you press **Restore**. Drag the gap between the chat and the cards, or between two cards, to resize them. Your browser remembers which cards are open and how big they are. With every card closed, the chat takes the full width. ### The Tasks card **Tasks** lists this chat's tasks in five sections, newest first: **Needs you**, **In progress**, **To do**, **Done** and **Cancelled**. The last two start folded. General lists the tasks handed out in General, and a room lists only its own. The [board](/using/board) shows them all. **Needs you** always shows. It holds approvals waiting on you, which you answer right there. It also holds tasks that will not move until you act: failed, timed out, stopped, needing review, blocked, unassigned or not started. The board's [Needs you](/using/board#things-that-need-you) column uses the same rule. Click a task to open it inside the card. It shows the task's description, status, agent, runtime and cost. Below come its **Output** once it is done (or its **Latest report**, when it has one), its **Progress** as live activity, and its **Comments**. Click **Tasks** in the card's title, or press **Escape**, to go back to the list. **Open on Board**, the kanban icon in the card's header, opens the task on the board. The box at the bottom of an open task, **Comment or steer this task…**, passes your comment to the agent the task belongs to. The line beside **Send** says what sending does for that task, for example that the agent gets it before its next step, or that sending starts the agent on it again. Once you send, a note under your comment says where it went. A task that waits on you, or waits in **To do**, starts again for your comment. A finished task cannot, so the note offers **Send as a follow-up task**, a new task for the same agent with your comment as its brief. How this works for each kind of task is in [Steer a running task](/using/board#steer-a-running-task). ### The Browser card The faces along the top of the **Browser** window are the agents in this chat. A small dot marks an agent with a page to show; click a face to see that agent's screen. Screens show for Clawboo Native agents, which browse through Clawboo. Agents on other runtimes browse with their own tools, so the card says their screen is not visible here. With nothing to show, the card says why: the agent has not opened a page yet, or has no browser granted. ## Arrange the sidebar The sidebar on the left holds the team rail, the team's agents and chats, and the main navigation. * Drag the sidebar's right edge to make it wider or narrower. Double-click the edge to reset its width. * Drag the edge far to the left, click **Collapse sidebar**, or press `Cmd+\` (`Ctrl+\` on Windows and Linux) to hide it. * While it is hidden, **Expand sidebar** sits at the start of the page's header. Point at it to show the sidebar over the page, or click it to bring the sidebar back. Other shortcuts are listed in [Keyboard shortcuts](/reference/keyboard-shortcuts). ## Troubleshooting > [accordion] An agent does not reply > > When a reply fails before any text arrives, the chat shows a notice with the reason. For example, it says when the runtime has no provider key connected and where to connect one. Check that the agent's runtime is set up in **Settings > Runtimes** (see [Providers and runtimes](/using/providers-and-runtimes)). An OpenClaw agent also needs Clawboo connected to its OpenClaw Gateway. More fixes are in [Troubleshooting](/help/troubleshooting). > [accordion] The message box is greyed out > > The box works when the chat has at least one agent and nothing is running. In a room, check that the room still has members on the team. While agents work, **Stop** replaces the send button: press it, or wait for the work to finish. Sending does not depend on the dashboard's connection to the OpenClaw Gateway, because the Clawboo server runs the work. > [accordion] My @ mention went to the leader instead > > A mention picks the agent only at the very start of the message, and the name must be followed by a space. In a room, agents outside the room cannot be mentioned: add them with **Edit room members**, or talk to them in General. > [accordion] The top of the conversation is missing > > The chat shows its most recent 150 items. **Load earlier messages** at the top adds 100 at a time without moving your place. If the chat was cleared, the messages from before the clear are set aside and do not come back into view. > [accordion] An OpenClaw agent still remembers the old conversation > > `/reset` and **Clear chat** reach OpenClaw agents through the dashboard's connection to the OpenClaw Gateway. Reconnect to the Gateway, then send `/reset` in the chat. > [accordion] Under the hood > > * `POST /api/teams/:id/chat` accepts a message and answers `202` at once, and the server runs the work. `GET /api/teams/:id/chat/stream` streams the chat as server-sent events. `POST /api/teams/:id/chat/stop` and `POST /api/teams/:id/chat/clear` stop and clear one chat. Each takes a `roomId` for a room. Full shapes are in the [Teams API](/reference/rest-api/teams). > * Each chat, General and every room, has its own engine on the server, which is why Stop and Clear chat touch only one chat. > * Agents also have a peer room that they post to with Clawboo's TeamChat tools. The dashboard does not show it. See [Peer chat](/concepts/peer-chat). ## Next steps * [Track work on the board](/using/board): act on Needs you, steer a running task, and review results. * [Org chart](/using/org-chart): see how a team is wired and give agents skills and connectors. * [Teams](/using/teams): set the team's brief and rules. * [How a team works](/intro/how-it-works): what happens between your message and the answer. * [Approvals](/using/approvals): answer the requests agents send you. # Create and manage teams Source: https://docs.claw.boo/using/teams Create a team from a template or from scratch, choose a runtime and model for each member, set team rules, and archive or delete a team. A team is a group of agents that work on your requests together. Each team has its own chats (**General** plus any rooms you add), its own tasks on the board, a brief, and rules that every member follows. [Boo Zero](/using/boo-zero), your team leader, coordinates every team. Use this page to create a team, choose each member's runtime and model, set team rules, and archive or delete a team. For a guided first run, follow [Deploy your first team](/getting-started/first-team). ## Before you begin A team template creates real agents, so you need at least one runtime that can run them: * Clawboo Native, the built-in runtime, with a provider: an API key or a local Ollama. * Or the OpenClaw Gateway, Claude Code, Codex or Hermes. See [Providers and runtimes](/using/providers-and-runtimes). An empty team needs nothing connected. ## Create a team **1. Open the team templates** In the team rail, click the dashed **+** at the bottom (**Create a team**). The [Marketplace](/using/marketplace) opens on its **Teams** tab. **2. Pick a starting point** Click **Deploy** on a team card to start from that template. To see who is on a team first, click **Details**. For an empty team, click the **Start from scratch** card. **3. Customize the team** Set the team's name, badge and colors, and each member's runtime and model. See [Customize the team](#customize-the-team). **4. Deploy** Click **Deploy team**, or **Create team** for an empty team. Clawboo creates each agent and shows its progress. You land in the new team's **General** chat. Before you can message the team, a **Meet your team** card asks you to introduce yourself. See [Team chat](/using/team-chat). An empty team opens on its chat too, but **Get Started** stays disabled until the team has an agent. Add one with **Create Boo** in the agent column (see [Agents](/using/agents)). ## Customize the team The customize step has three sections. | Section | What you set | | - | - | | **Name** | The team's name. | | **Team badge** | The team's icon (click the badge to change it) and its accent color. The color tints the team's icon in the team rail and its halo on the [Org chart](/using/org-chart). | | **Teammate colors** | A color collection for the members' Boos, with a live preview of the roster: Vivid Pop, Dusty Pastel Pro, Coastal Mist, Executive Jewel, Sharp SaaS, Soft Neutral Editorial, Monochrome Accent, or Classic (the default). | ### Choose a runtime and model for each member The roster under **Teammate colors** lists each member. Each row has: * A **runtime** picker: Clawboo Native, OpenClaw, Claude Code, Codex or Hermes. One team can mix runtimes. * A **model** picker, for members on Clawboo Native, OpenClaw or Hermes. Pick a provider first (only the providers you connected are listed), then a model. Until you pick one, it reads **Default**, followed by the model the member will run on Clawboo Native and OpenClaw. Members on Claude Code or Codex run on that tool's own default model, so they have no model picker. Changing a member's runtime clears its model pick. Which runtime a row starts on: * A Marketplace template suggests OpenClaw for every member. A team from scratch suggests Clawboo Native. During first-time setup, every member starts on the runtime you connected in setup. * When the suggested runtime is not connected, the row falls back to Clawboo Native and says so, for example **OpenClaw unavailable · using Clawboo Native**. * You can pick OpenClaw only while the OpenClaw Gateway is connected, and Claude Code, Codex or Hermes only once you have connected them. Clicking a greyed-out runtime closes this dialog and opens **Settings > Runtimes** so you can connect it. Clawboo Native needs a provider to answer. Without one, the team still deploys, but its Clawboo Native agents cannot reply until you connect a provider. The chat tells you why. ### What happens when you deploy * **Clashing names get a number.** If agents or a team with the same names already exist, Clawboo gives the new ones the next free number, for example **Code Reviewer Boo 2**. It also updates who hands work to whom to match. * **One failed agent does not stop the rest.** Clawboo keeps going, then lists any failures in a message such as **2 of 3 agents created (Writer Boo failed)**. If no agent can be created, you return to the customize step with **No agents could be created. Check that the runtime is connected, then retry.** The empty team stays, so you can retry or delete it. * **OpenClaw members need the Gateway.** If you put a member on OpenClaw while the Gateway is offline, the deploy stops with **Connect an OpenClaw Gateway to deploy the OpenClaw members, or switch them to another runtime.** * **The team gets a brief.** Clawboo writes a starting [team brief](#team-brief) that you can edit. ## The team's Leader When you deploy a template, a member whose name or role reads as a lead (CTO, Team Lead, Project Manager, Coordinator and similar) gets a **Leader** badge in the roster. That member becomes the team's Leader. * Boo Zero still leads the team. On the team graph, the Leader sits between Boo Zero and the other members. * On Clawboo Native, the Leader gets the team-lead instructions and the leader model. The other members get the specialist ones. * When your install has no Boo Zero, the Leader leads the team's chats. A team with no Leader is led by its first member. In a team chat, a message goes to the member you @mention. Otherwise it goes to Boo Zero, then to the Leader if it is in that chat, then to the chat's first member. See [Team chat](/using/team-chat). The dashboard has no control for changing the Leader after you deploy. The API can change it: see **Under the hood** at the end of this page. ## Brief and rules Open a team's settings from any of its chats: in **General** or a room, click the gear in the chat header. Its tooltip is the team's name followed by **brief & rules**. The settings window has five sections: **Icon**, **Accent color**, **Color collection**, **Brief** and **Rules**. Icon and color changes save as you make them. ![The Growth team's settings window: the Brief, with Save and Regenerate, above the Rules box with one rule per line and Save rules](https://mintcdn.com/privatedocs/6H-VHke03QIa-oHL/images/team-brief-rules.png?fit=max\&auto=format\&n=6H-VHke03QIa-oHL\&q=85\&s=d1288a89f78913bef451605b564344b9) ### Team rules Team rules are instructions that every member of the team follows. Clawboo adds them to every turn in the team's chats (General and every room), for Boo Zero and for each teammate, including the work Boo Zero hands out. A correction you save as a rule keeps applying, so you do not have to repeat it. Rules apply from the next turn. A team Routine posts into **General**, so its runs follow the rules too. A Routine aimed at one agent runs without them. You can add rules in two ways. > [tab] The Rules box > > In the team's settings, type in **Rules**, one rule per line, then click **Save rules**. You should see **Team rules saved.** Rules can be up to 4,000 characters; a longer save fails with **Could not save rules.** > [tab] /rule in a chat > > In any of the team's chats, type `/rule` and the rule, then send it: > > ```text theme={"theme":"github-dark"} > /rule Every price on the page comes from the live plan catalog. > ``` > > Clawboo saves it as a rule instead of sending it to an agent. The chat shows **Rule saved for team:** with your rule, and a **Team rule saved.** message appears. `/rule` keeps your rules as a bulleted list: it turns every line into a bullet, drops blank lines, and skips a rule that is already there (ignoring capital letters). It works only with a space and some text after it, so `/rules` or `/rule:` is sent as a normal message. If the new rule would take the rules past 4,000 characters, you see **Could not save team rule. Try again?** ### Team brief The brief describes the team: what it is for, who is on it, and how they work together. Clawboo writes one when you deploy a template, and you can edit it in the team's settings. **Regenerate** rebuilds it from the current members; nothing changes until you click **Save**. Boo Zero reads a team's brief only when you start a message in Boo Zero's own chat with @ and the team's name, and then only its first 900 characters. Team chats do not include the brief, so use rules for anything the team must follow. See [What Boo Zero reads](/using/boo-zero#what-boo-zero-reads). ## Rename a team The dashboard has no rename control for a team: you name it on the customize step. To rename a team later, use the API call under **Under the hood** at the end of this page. ## Refresh a team's protocol Each member's AGENTS.md lists the team and how to hand work to teammates. After you add or remove members, right-click the team's icon in the team rail and choose **Refresh Protocol**. Clawboo rebuilds that file for every member and keeps each member's routing rules. Refresh Protocol needs the OpenClaw Gateway connected, even for a team with no OpenClaw members. Without it you see **Not connected to Gateway**. Only OpenClaw agents read AGENTS.md as instructions; on every runtime it also draws the team graph's links (see [Agents](/using/agents#edit-the-agent-files)). ## Archive a team Right-click the team's icon in the team rail and choose **Archive**. The team leaves the team rail, which also puts its chats and its Home out of reach. Nothing is deleted: its agents, chats, tasks and the files in its team folder stay in Clawboo. **You cannot undo this from the dashboard.** **Unarchive** appears only on an archived team's icon, and the team rail does not show archived teams. To bring a team back, use the API call under **Under the hood** at the end of this page. ## Delete a team Right-click the team's icon in the team rail and choose how much to delete. | Choice | What it deletes | Confirm button | | - | - | - | | **Delete team only** | The team. Its agents stay in Clawboo, with no team. | **Delete team** | | **Delete with agents** | Every agent on the team, with their files and own chats, then the team. | **Delete everything** | Both choices also delete the team's rules, its brief, its rooms and your introduction to the team, and the team's folder (`~/.clawboo/workspaces/<team id>`) with every file its agents saved there. The confirmation says so: "The team's folder and the files its agents saved there are deleted too." If the folder cannot be removed, the team is still deleted, and a message says its team folder could not be removed. **Delete with agents** works without the OpenClaw Gateway. An OpenClaw member leaves the Gateway at once if it is connected, otherwise the next time Clawboo connects to it. If some agents cannot be deleted, the team is still deleted, and a message says how many agents were removed. > [!WARNING] > > Deleting a team cannot be undone, and the files in its folder go with it. **Delete with agents** removes the agents for good, along with their files and their own chats. ## Troubleshooting > [accordion] Connect an OpenClaw Gateway to deploy the OpenClaw members > > **What it means:** you put a member on OpenClaw while the OpenClaw Gateway is offline. > > **Fix:** connect the Gateway in **Settings > Runtimes**, or switch those members to another runtime on the customize step. > [accordion] No agents could be created > > **What it means:** no agent in the template could be created. The empty team stays in the team rail. > > **Fix:** open **Settings > Runtimes**, check that the runtimes you picked are connected, then deploy again. Delete the empty team if you do not need it. > [accordion] The new agents have a number after their names > > This is expected. A team or agents with the same names already exist, so Clawboo numbered the new ones to keep every name unique. > [accordion] Refresh Protocol says Not connected to Gateway > > Refresh Protocol needs the OpenClaw Gateway connected, even for a team with no OpenClaw members. Connect it in **Settings > Runtimes**, then try again. > [accordion] A team disappeared from the team rail > > The team was archived. Its agents, chats and tasks are still in Clawboo. The dashboard cannot unarchive it; use the API call under **Under the hood** below. > [accordion] The team does not follow a rule > > Rules apply from the next turn in the team's chats, not to replies already sent. Open the team's settings and check that the rule is in **Rules**. A Routine aimed at one agent runs without the team's rules; a team Routine follows them. > [accordion] Under the hood > > * **Files a deploy writes.** Each member gets a SOUL.md (its persona plus the default House Style personality), an IDENTITY.md, a TOOLS.md, and an AGENTS.md that lists the teammates and how to hand them work. Which runtime reads which file is on [Agents](/using/agents#edit-the-agent-files). > * **OpenClaw config.** If any member's routing names a teammate, Clawboo turns on agent-to-agent messaging in the OpenClaw Gateway's config. > * **API calls.** `PATCH /api/teams/:id` renames a team, changes its Leader, or brings back an archived team. It takes any of `name`, `icon`, `color`, `colorCollectionId`, `leaderAgentId` (an agent id, or `null` for no Leader) and `isArchived`. To find an archived team's id, call `GET /api/teams?includeArchived=true`. An unarchived team returns to the team rail after you reload the page. > > ```http theme={"theme":"github-dark"} > PATCH /api/teams/<team-id> > Content-Type: application/json > > { "isArchived": false } > ``` > > See the [Teams API reference](/reference/rest-api/teams) for every field, and the [REST API overview](/reference/rest-api/index) for how to call the API. ## Next steps * [Team chat](/using/team-chat): talk to your team, add rooms, and introduce yourself. * [Agents](/using/agents): change one member's model, personality or files. * [Boo Zero](/using/boo-zero): how your team leader is chosen and what it reads. * [Marketplace](/using/marketplace): find more teams and agents to deploy. * [Teams API reference](/reference/rest-api/teams): every team route and field. # Choose a light or dark theme Source: https://docs.claw.boo/using/theming Switch the dashboard between System, Light and Dark with the three-part switch at the bottom of the sidebar. The dashboard comes in a light and a dark look. Pick one, or let Clawboo follow your computer's light or dark setting. ## Change the theme **1. Find the switch** At the bottom of the left sidebar, below **Settings**, is a three-part switch with icons only: a monitor for **System**, a sun for **Light** and a moon for **Dark**. Hover a part to see its name. **2. Click the one you want** The whole dashboard changes at once, with no reload. The part you chose looks raised. | Choice | What you get | | - | - | | **System** | Follows your computer's light or dark setting, and changes as soon as that setting does. Hover it to see which look it is using now: "System (light)" or "System (dark)" | | **Light** | Always light | | **Dark** | Always dark | ## Good to know * Clawboo starts in **Light** until you choose. * Your choice is saved in this browser, for this address. Another browser, a private window, cleared site data, or the dashboard at another address or port starts in **Light** until you choose there. * The welcome screen and the onboarding steps keep their bright sky whatever you choose. ## If you can't see the switch * **The sidebar is hidden.** Press `Cmd+\` (macOS) or `Ctrl+\` (Windows, Linux), or click **Expand sidebar** at the start of the page's header. * **You are on Boo Zero's page.** It hides the part of the sidebar that holds the switch. Click a team in the team rail on the far left, and the switch is back. ## Next steps * [Tour the dashboard](/getting-started/dashboard-tour): where the sidebar, the views and Settings are. * [Keyboard shortcuts](/reference/keyboard-shortcuts): showing and hiding the sidebar, and every other shortcut. * [Design system](/internals/design-system): how the theme works, for contributors.