# RoboCo — full docs text
> RoboCo is an open-source, self-hosted virtual software company: 25 role-gated AI agents plus one human CEO that work in real git repositories. Pull the stack from Docker Hub or GHCR, run it on your own hardware (homelab/Linux/Docker), and stay in control of every merge to master. Source on GitHub (AGPL-3.0).
Full text of every docs page, concatenated. Each page is headed by its URL and title.
================================
URL: https://docs.roboco.tech/docs
Title: Documentation
================================
import { DocImage } from "@/components/docs/doc-image";
import { Admonition } from "@/components/docs/admonition";
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { Rocket, Building2, Clapperboard, Github } from "lucide-react";
# RoboCo
**RoboCo is a self-hosted AI software company.** Twenty-five AI agents and one human — you — organized as a real engineering org: roles, a chain of command, formal reviews, and sign-offs. You don't wire up a swarm of bots or babysit a prompt loop. You run a company. You drop work in at the top, and the organization carries it all the way through planning, building, QA, review, and documentation, then brings it back to your desk for the final word.
You act at the two ends. The company fills in everything between.
RoboCo is a working prototype under active development. It runs in a homelab, has rough edges, and its API and database schema are not stable yet. It is **not production-ready** — don't expose it to the public internet as-is. Issues and pull requests are very welcome.
Go from a cloned repo to a running company looking back at you from the Command Center.
The org, the roles, the strict task lifecycle, and how every agent is structurally sandboxed.
Follow one feature — RoboCo building its own Prompter — from a rough idea to a merged pull request.
RoboCo is open source under AGPL-3.0. The whole stack, agents included, is on GitHub.
## What you're running
A single `docker compose up` brings up the whole stack behind one address — `http://localhost:3000` — and that one window is the company. Underneath it:
- **A virtual organization.** A Board sets direction, a Main PM coordinates three delivery cells (Backend, Frontend, UX/UI), and an Auditor watches everything. You sit above all of it as CEO. [See the org →](/docs/company/org-and-roles)
- **A strict lifecycle.** Every piece of work is a task, no task moves without acceptance criteria, and each task walks the same gated path — built, QA'd, documented, reviewed, approved. The structure is what turns a roster of models into a company that ships. [See the lifecycle →](/docs/company/task-lifecycle)
- **Real git, real pull requests.** Each agent works in its own clone; work flows up a cell → root → master merge chain as actual pull requests on your repository. **Only you ever merge to `master`.** [See the merge model →](/docs/company/merge-model)
- **A hard trust boundary.** Agents never touch your API or your shell directly. They act only through a narrow set of role-scoped verbs, so a developer can't merge, QA can't commit, and the Auditor can silently observe but never initiate a conversation. [See how agents are sandboxed →](/docs/company/agent-gateway)
## Watch it first
If you'd rather see it than read about it:
- [**The 26-minute intro**](https://www.youtube.com/watch?v=t1QNqJgBmkM) — what RoboCo is, a walkthrough, and how to use it.
- [**The 2.5-hour build session**](https://www.youtube.com/watch?v=xige_EUIjIA) — a single conversation taken all the way to a shipped feature.
- [**The 2:33 panel walkthrough**](/docs/videos/panel-full-walkthrough.mp4) — every page of the control panel, end to end.
## How models are powered
Agents run on **Anthropic Claude** by default, authenticated from the Claude Code session you already have on the host — no metered API key to wire up. You can also run the whole workforce (or just the delivery roles) on **xAI Grok**, **OpenAI Codex**, **Google Gemini**, or **Moonshot Kimi** — each its own subscription-authenticated official CLI, no metered key — pin roles to a cheaper model at a given task complexity, or point roles at **local / self-hosted** models. [Choosing & running models →](/docs/get-started)
---
RoboCo is licensed under the [GNU Affero General Public License v3.0](https://github.com/rennf93/roboco/blob/master/LICENSE). Contributions are welcome and require a signed [Contributor License Agreement](https://github.com/rennf93/roboco/blob/master/CLA.md), automated on your first pull request.
================================
URL: https://docs.roboco.tech/docs/get-started
Title: Get Started
================================
import { Admonition } from "@/components/docs/admonition";
Three steps take you from a cloned repository to a company working on your code:
1. **[Install & first run](/docs/get-started/installation)** — bring up the whole stack with Docker, set two secrets, and open the Command Center.
2. **[Register your first project](/docs/get-started/first-project)** — point RoboCo at a git repository it's allowed to work on.
3. **[Your first task](/docs/get-started/first-task)** — hand the company something to build and watch it go end to end.
You don't need Python, Node, or a database on your machine — everything runs in Docker. The one thing you provide is a way for the agents to reach a model: by default that's the **Claude Code** session already on your host, so there's no API key to wire up.
The [26-minute intro](https://www.youtube.com/watch?v=t1QNqJgBmkM) and the [full panel walkthrough](/docs/tour) are the fastest way to get the shape of the thing before you install it.
## What you'll need
| You need | Why |
|----------|-----|
| **Docker** + **Docker Compose** | The entire stack (PostgreSQL, Redis, Ollama, the orchestrator, the panel, nginx) runs as containers. |
| **A Claude Code auth directory** (`~/.claude`) | Mounted into the orchestrator so agents can reach the model. Run `claude` once on the host to create it. *(Or run the workforce — or just the delivery roles — on [Grok](installation.md#optional-run-on-grok-instead), [Codex](installation.md#optional-run-on-codex-instead), [Gemini](installation.md#optional-run-on-gemini-instead), or [Kimi](installation.md#optional-run-on-kimi-instead) instead.)* |
| **A GitHub Personal Access Token** | One per project you register, so agents can clone it and open pull requests. You add this in the panel later, not now. |
| **~10 GB of disk** and a few GB of RAM | The image set and the per-agent git clones. RoboCo is light at runtime — see the [resource notes](installation.md#resources). |
## The shape of the system
Everything is served behind a single address — **`http://localhost:3000`** — by an nginx reverse proxy. The browser only ever sees one origin; nginx routes `/api` and `/ws` to the orchestrator (FastAPI) and everything else to the Next.js control panel. That panel is your one window into the company.
When you're set up and looking at the **Command Center**, head into [The Company](/docs/company) to understand the org and the lifecycle, or take [the Tour](/docs/tour) to watch a real feature get built.
================================
URL: https://docs.roboco.tech/docs/get-started/installation
Title: Installation
================================
import { Admonition } from "@/components/docs/admonition";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
This page takes you from nothing to the **Command Center** open in your browser. The whole stack runs in Docker, so the only things you install on the host are Docker itself and (for the default Claude backend) the Claude Code CLI.
If you just want the pre-built registry images up with sane defaults, skip straight to `make quickstart` after step 1 and step 2 below (authenticate Claude Code, clone the repo) — it does steps 3 and 4 for you: scaffolds a fresh `.env` from `.env.example` with the required secrets freshly generated (or validates an existing one, untouched), then pulls and brings up `docker-compose.registry.yml` and runs a doctor-style readiness sweep, failing loud with the exact next command if any stage doesn't check out.
```bash
make quickstart
```
It's fully idempotent — re-running it never touches an existing `.env`. The step-by-step walkthrough below is what it automates, useful when you want to see or control each piece yourself (a pinned version, Grok/Codex/Gemini/Kimi instead of Claude, custom host paths).
## 1. Authenticate the model backend
By default every agent runs on Anthropic Claude, using the **Claude Code** session on your host rather than a metered API key. Authenticate once:
```bash
npm install -g @anthropic-ai/claude-code
claude # opens a browser to log in; creates ~/.claude
```
That `~/.claude` directory is mounted read-only into the orchestrator, which hands it to each agent container. If you'd rather run the workforce on Grok, skip this and see [Optional: run on Grok](#optional-run-on-grok-instead) below.
## 2. Get the code
```bash
git clone https://github.com/rennf93/roboco.git
cd roboco
cp .env.example .env
```
## 3. Set the two required secrets
Open `.env` and set these two — the orchestrator refuses to start without the first, and Docker deployments need the second:
```bash
# Encrypts every per-project GitHub token at rest (Fernet). REQUIRED.
ROBOCO_ENCRYPTION_KEY=
# Signs the per-agent auth tokens (HMAC). REQUIRED for docker compose.
ROBOCO_AGENT_AUTH_SECRET=
```
Generate each one:
```bash
# ROBOCO_ENCRYPTION_KEY
python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'
# ROBOCO_AGENT_AUTH_SECRET
python -c 'import secrets; print(secrets.token_hex(32))'
```
This key encrypts the GitHub tokens you'll store per project. If you lose it or change it later, **every stored token becomes undecryptable** and must be re-entered. Back it up with your other secrets, and never commit `.env`.
Everything else in `.env.example` has a working default for a local Docker run. On a NAS or remote server you'll also set the host-path variables (`ROBOCO_HOST_PROJECT_DIR`, `ROBOCO_HOST_CLAUDE_DIR`, `ROBOCO_DATA_DIR`) — those are covered in the deployment reference.
## 4. Bring up the stack
You have two ways to get the images. Both start from the clone above.
Every release publishes all RoboCo images to both the GitHub Container Registry and Docker Hub, so you can run the full stack without building anything:
```bash
docker compose -f docker-compose.registry.yml pull
docker compose -f docker-compose.registry.yml up -d
```
Pick the registry and version with two variables (defaults shown):
```bash
ROBOCO_REGISTRY=ghcr.io/rennf93 # or docker.io/renzof93
ROBOCO_VERSION=latest # or a pinned release, e.g. 0.20.0
```
The orchestrator pulls and spawns the matching pre-built agent images on demand — no build toolchain on your host.
The same stack, built locally from the Dockerfiles instead of pulled:
```bash
docker compose up -d # builds images on first run, then starts everything
```
The first start does real work: it pulls the local models into Ollama (the embedding model is quick; the local LLM is a couple of minutes), then the orchestrator waits for them, **runs the database migrations itself**, and indexes its knowledge base. You don't run migrations by hand — the stack brings its own schema up to date on every boot.
## 5. Watch it come up
```bash
docker compose logs -f orchestrator
```
Wait until the orchestrator reports it's serving, then open:
```text
http://localhost:3000
```
That's the **Command Center** — per-cell health, the CEO approval queue, live metrics, and recent activity. The company is up; it's just idle because you haven't given it a repository or a task yet.
nginx is the only externally-exposed service. It serves the panel and proxies `/api` and `/ws` to the orchestrator, so the browser sees a single origin at `:3000`. You generally won't hit the orchestrator's own port directly.
## Next
→ **[Register your first project](/docs/get-started/first-project)** — point RoboCo at a repository it's allowed to work on.
---
## Optional: run on Grok instead
RoboCo can run the entire workforce on **xAI Grok** using xAI's official `grok` CLI on a **SuperGrok subscription** — no metered API key, so a run can't stall mid-task on exhausted credits.
```bash
grok login # once, on the host — creates ~/.grok/auth.json
```
Then in `.env`:
```bash
ROBOCO_HOST_GROK_DIR=/home/youruser/.grok # the real host ~/.grok to mount in
# ROBOCO_GROK_AGENT_IMAGE=roboco-agent-grok:latest
# ROBOCO_GROK_CLI_MODEL=grok-build
# ROBOCO_GROK_REASONING_EFFORT= # low|medium|high|xhigh|max (empty = model default)
```
The orchestrator keeps the short-lived Grok token refreshed for you, so agents don't hang on an expired login. Which agents run on which backend is set on the **Settings → AI Providers** page — that, and the per-role model assignments, are covered in the models section.
## Optional: run on Codex instead
RoboCo can run its delivery agents (not the interactive Intake/Secretary chats — see [Run on Codex](/docs/models/codex)) on **OpenAI's Codex CLI** using a **ChatGPT subscription** — no metered API key.
```bash
codex login # once, on the host — creates ~/.codex/auth.json
```
Then in `.env`:
```bash
ROBOCO_HOST_CODEX_DIR=/home/youruser/.codex # the real host ~/.codex to mount in
# ROBOCO_CODEX_CLI_MODEL=gpt-5.3-codex
```
Set the **Codex** routing mode (or pin individual agents) on **Settings → AI Providers**, same as Grok above.
## Optional: run on Gemini instead
RoboCo can also run its delivery agents on **Google's Gemini CLI** using an **OAuth login** — no metered API key. See [Run on Gemini](/docs/models/gemini) for the same V1 delivery-roles-only scope.
```bash
gemini # once, on the host — interactive OAuth login, creates ~/.gemini
```
Then in `.env`:
```bash
ROBOCO_HOST_GEMINI_DIR=/home/youruser/.gemini # the real host ~/.gemini to mount in
# ROBOCO_GEMINI_CLI_MODEL=gemini-2.5-pro
```
Set the **Gemini** routing mode (or pin individual agents) on **Settings → AI Providers**.
## Optional: run on Kimi instead
RoboCo can also run its delivery agents (not the interactive Intake/Secretary chats — see [Run on Kimi](/docs/models/kimi)) on **Moonshot AI's Kimi K3** using a **Kimi subscription** — no metered API key, authenticated by a device-code OAuth login.
```bash
kimi login # once, on the host — device-code OAuth, creates ~/.kimi-code
```
Then in `.env`:
```bash
ROBOCO_HOST_KIMI_DIR=/home/youruser/.kimi-code # the real host ~/.kimi-code to mount in
# ROBOCO_KIMI_CLI_MODEL=kimi-code/k3
```
Set the **Kimi** routing mode (or pin individual agents) on **Settings → AI Providers**.
## Optional: secure mode
On a trusted LAN, RoboCo runs in **header-trust mode** by default: requests identify the caller by role headers, with no token required. That's fine on a private network and is the intended setup.
If you need to harden it so an agent can't spoof another's role, set `ROBOCO_AGENT_AUTH_REQUIRED=true`, keep your `ROBOCO_AGENT_AUTH_SECRET`, and generate the panel's CEO token with `make panel-token` into `ROBOCO_PANEL_AGENT_TOKEN`. nginx injects that token so the panel keeps working without the browser holding the signing secret. The full security model is in the Configure & Deploy reference.
Don't expose RoboCo to the public internet as-is. It's built to run on a trusted private network (homelab / LAN).
## Optional: cloud auth
Secure mode above hardens the *agent fleet's* headers. If you also want the CEO's own browser session to require a login — the case for exposing the panel beyond your LAN — set `ROBOCO_CLOUD_AUTH_ENABLED=true` plus `ROBOCO_CLOUD_AUTH_EMAIL` / `ROBOCO_CLOUD_AUTH_PASSWORD` / `ROBOCO_CLOUD_AUTH_SECRET` (the orchestrator refuses to start if the secret is missing while the flag is on). This is a separate concern from the agent HMAC token above; see [Cloud auth](/docs/optional/cloud-auth) for the full setup.
The session cookie is secure-only, so this only works behind TLS — terminate HTTPS in front of RoboCo before you enable it.
## Resources
RoboCo is light at runtime. Agent containers are spawned on demand and torn down when their work is done, so you rarely have more than a handful live at once.
| At idle (full stack, no task running) | RAM |
|---------------------------------------|-----|
| Ollama (models loaded) | ~2.2 GB |
| Orchestrator | ~150 MB |
| Postgres / Panel / Redis / nginx | ~120 MB combined |
The whole standing stack idles around **~2.5 GB**, almost all of it Ollama. Under load — five agents working concurrently — the stack peaked around **~6.6 GB**; even at full-fleet peak you stay well under ~10 GB. **Storage is the larger footprint:** the agent images all build from a shared base layer, so on disk they cost far less than their nominal sizes summed. `docker system prune` reclaims old image versions, stopped containers, and build cache.
```bash
docker stats # live RAM / CPU per running container
docker system df # image / container / build-cache disk usage
```
## Running on the host (for hacking on RoboCo itself)
If you want to develop RoboCo's own code rather than just run it, you can run only the backing services in Docker and the app on your host. RoboCo's code needs **Python 3.13+** (`uv` fetches it if needed):
```bash
uv sync
docker compose up -d postgres redis ollama # backing services only
uv run alembic upgrade head # migrate the database
uv run python -m roboco.cli # API + orchestrator
```
================================
URL: https://docs.roboco.tech/docs/get-started/first-project
Title: Your First Project
================================
import { Admonition } from "@/components/docs/admonition";
A **project** is a git repository RoboCo is allowed to work on, plus the configuration that tells the company how to build and check it. Until you register one, the agents have nowhere to put their work. You register projects in the panel under **Workstation → Projects → New**.
## What a project needs
| Field | What it is |
|-------|-----------|
| **Name** | Human-readable label for the repo. |
| **Slug** | URL-safe short name. It becomes the top of the workspace path (`{slug}/{team}/{agent}/`) and shows up in branch names. |
| **Git URL** | The clone URL. HTTPS is the common case and **requires a token** (below), unless the project is bound to a GitHub App installation instead — see [GitHub App authentication](#github-app-authentication). |
| **Forge** | Which forge API serves PR/CI/review operations: GitHub, Gitea, or GitLab. See [Choosing a forge](#choosing-a-forge). |
| **Git token** | A personal access token for that forge, stored encrypted. Required for private or HTTPS repos — see [The git token](#the-git-token). |
| **Assigned cell** | Which delivery cell owns this repo: Backend, Frontend, or UX/UI. |
| **Default branch** | The branch PRs ultimately target (or the first rung of an [environment ladder](/docs/optional/env-branches), if you configure one). **Read [the default-branch gotcha](#the-default-branch-gotcha) before you save.** |
| **Protected branches** *(optional)* | Extra branch names, beyond the always-protected `master`/`main`, the fleet refuses to rebase onto or force-push over as a task's own branch. See [Protecting extra branches](#protecting-extra-branches). |
| **Monthly Budget (USD)** *(optional)* | A calendar-month spend cap across the project's tasks — see [Task & project cost budgets](/docs/optional/task-budgets). |
| **Gate commands** *(optional)* | Per-project test / lint / format / typecheck / build commands, and a fast pre-submit `quality_command`. See [Gate commands](#gate-commands). |
## Choosing a forge
RoboCo talks to three forges through the same set of PR/CI/review operations — GitHub, Gitea, and GitLab are all first-class, not GitHub-plus-afterthoughts. The **Forge** field in the project dialog picks which one:
- **Auto-detect** covers `github.com` and `gitlab.com` — leave it on Auto-detect for either and RoboCo figures it out from the Git URL's host.
- **GitHub / GitHub Enterprise** — the default. For a GHE instance, also set `ROBOCO_GITHUB_API_BASE_URL` to your instance's API base (see the [environment reference](/docs/deploy/env-reference)).
- **Gitea (self-hosted)** — set this explicitly for any Gitea instance, since there's no public `gitea.com` to auto-detect against. Works out of the box with a **plain-http LAN instance** too — RoboCo matches whatever scheme your Git URL uses, no extra configuration needed.
- **GitLab (gitlab.com / self-hosted)** — auto-detects for `gitlab.com`; set it explicitly for a self-hosted GitLab instance, LAN or otherwise.
Whichever forge you pick, everything else on this page — the token, the default branch, gate commands, the workspace clone — works the same way. Cloning, branching, committing, and pushing are plain git and never cared which forge a repo lives on; only the PR-open/CI-read/review calls route through the forge-specific API.
## The git token
Agents clone your repository and open pull requests (or merge requests, on GitLab) on it, so they need a **personal access token** with permission to do that, from whichever forge you picked above.
- **Scopes:** the token needs repository read/write access (to clone and push branches) and pull/merge-request read/write access (to open and merge PRs). On GitHub, a classic `repo`-scoped token works, or a fine-grained token with *Contents: Read and write* and *Pull requests: Read and write*. On Gitea, a classic personal access token with repository scope. On GitLab, a personal access token with the `api` scope.
- **It's encrypted and write-only.** The token is encrypted at rest the moment you save it (with your `ROBOCO_ENCRYPTION_KEY`) and the API **never returns it** — the panel only shows whether a token is set, not its value.
- **Rotating or clearing it:** in **Edit Project**, entering a new token replaces it, an empty field clears it, and leaving it untouched keeps the current one.
If you give an HTTPS Git URL and no token, the clone fails — agents can't reach the repo. Set the token when you create the project.
Every stored project token is encrypted with `ROBOCO_ENCRYPTION_KEY`. If you ever change that key, all stored tokens become undecryptable and you'll have to re-enter every one. Pick it once at install and keep it backed up.
## GitHub App authentication
A GitHub project has a second, token-free auth path: bind it to an installation of a **GitHub App** you've configured on **Settings → GitHub App** (App id + private key, entered once). Once that's set up:
- The **New Project** dialog's **Select repo** button lists the App's installations and repositories — pick one and both the Git URL and the installation binding are filled in for you, no manual token entry.
- An **existing** project can bind or unbind the same way from **Edit Project**.
- Bound git operations mint a **short-lived installation token** instead of using the project's stored PAT, and commits/PR reviews attribute to the App's own bot identity. Any minting failure (App not configured, installation revoked, a network hiccup) **falls back to the project's own PAT automatically** — set both and a GitHub App hiccup never bricks git operations for that project.
- Unbinding (clearing the installation in Edit Project) reverts to the PAT below, with no other change.
This is GitHub-only — Gitea and GitLab projects always use the token path above.
## Protecting extra branches
Beyond the hardcoded floor (`master`/`main`, always protected), you can name extra **Protected Branches** in the project dialog — a chips editor: type a name and press Enter or comma to add it, click the × to remove. A branch on this list is refused as a rebase target and refused a force-push as a task's own synced branch, matched exactly and case-sensitively. Every remote branch delete (task-branch cleanup, the stale-branch sweep, a merged PR's source-branch cleanup) additionally refuses any [environment-ladder](/docs/optional/env-branches) rung, even one not explicitly listed here.
## The default-branch gotcha
The **Create Project** dialog defaults the branch to `main`, but several places in the backend assume `master`. **Set this field explicitly to match your repository's real default branch** (`main` or `master`) rather than trusting the pre-filled value. Getting it wrong is the most common first-run snag — branches get cut from, and PRs target, the wrong base.
## Gate commands
A developer agent runs a quality gate against its own work *before* it submits for QA. By default that's a sensible lint + typecheck pair, but you'll get far better results by pointing RoboCo at your repository's *real* checks:
- **`quality_command`** — the fast pre-submit gate, run at the moment an agent says it's done (for example `make gate`). If you set this, it replaces the default lint/typecheck pair. Keep it fast; it runs on every submission.
- **`test_command`, `lint_command`, `format_command`, `typecheck_command`, `build_command`** — the individual commands for the cell's QA and CI steps.
Setting these so they mirror what *you* would run locally is the single biggest lever on output quality: the company gates itself exactly the way you would.
## Sandboxing agents away from a repo
If you want to make sure agents can never point a project at a particular repository — RoboCo's own source, say, during a test run — set `ROBOCO_PROTECTED_GIT_URLS` to a comma-separated list of URL substrings. Creating or updating a project whose Git URL matches one is rejected.
## What happens under the hood
You don't have to manage any of this, but it's worth knowing what registering a project sets in motion:
- The first time an agent is assigned work on the project, RoboCo clones the repo into that agent's own workspace under `ROBOCO_WORKSPACES_ROOT` (default `/data/workspaces`). **Every agent gets its own clone**, so they work in parallel without stepping on each other. That directory is the disk to provision and back up.
- Right after cloning, RoboCo **scrubs the token out of the clone's git config** and verifies no token byte survived anywhere under `.git/` — destroying the workspace if one did. Your PAT never lives inside an agent container.
## Next
→ **[Your first task](/docs/get-started/first-task)** — hand the company something to build.
================================
URL: https://docs.roboco.tech/docs/get-started/first-task
Title: Your First Task
================================
import { Admonition } from "@/components/docs/admonition";
With a project registered, you can hand the company work. The way in is the **Task Assistant** — the Intake agent — on the **Prompter** page. You give it a rough idea; it reads your *actual* codebase, asks a few sharp questions, and hands back a properly-formed task with an objective, a per-cell breakdown, and the acceptance criteria that define "done."
## Draft a task with the assistant
1. Open the **Prompter** page (the **Task Assistant**).
2. Point it at the **project** you just registered (or a **product**, if you've mapped several repos together), and describe what you want — a feature, a fix, anything.
3. It spins up an agent that **clones the scope and reads the real code** before it says a word, then comes back with a grounded proposal: what to build, where it should live, and the acceptance criteria — citing your actual files and pages.
4. Refine it over a turn or two until the spec is right.
When the proposal is ready, you choose on a single card:
- **Keep chatting** — keep refining the draft.
- **Board review & Start** — send it to the Product Owner and Head of Marketing to sharpen the requirements before any code is written.
- **Approve & Start** — hand it straight to the Main PM.
The assistant is the easiest path, but a task is just a record. You can also create one directly through the API (`POST /api/tasks`) if you're scripting RoboCo — every task needs a title, a description, at least one acceptance criterion, a team, and a target project. The API reference covers the full schema.
## What happens after you approve
The moment you approve, the company takes over:
1. The **Main PM** breaks the task into per-cell subtasks and sets the cells running.
2. Each **cell PM** delegates to its developers, clears blockers, and triages.
3. **Developers** build it in their own clones and open pull requests; **QA** reads the real diff and passes or fails it; **Documenters** write down what was built.
4. A **PR reviewer** checks each assembled pull request before a PM merges it up the chain.
5. The **Main PM** opens the final pull request into `master` and notifies you it's done.
You can watch all of this live — on the **Kanban** board, the **Tasks** table, the Agents page's **Conversations** tab, and the **Command Center**. Nothing happens in the dark.
## The two moments you're needed
A whole feature only needs you at **two points**:
- **The green light at the start** — *Approve & Start* (or send it back).
- **The last call at the end** — the finished pull request lands in your **CEO Approval Queue**, where you **Approve & Merge**, **Request Changes**, or **Cancel**. Only you ever merge to `master`.
Everything between those two moments is the company doing its job.
## See it for real
The best way to understand the whole journey is to watch one happen. **[The Tour](/docs/tour)** follows RoboCo building one of its *own* features — the Prompter you just used — from this same starting point all the way to a merged pull request, with screenshots at every step.
To understand the machinery behind it — the roles, the gated lifecycle, the merge chain — read **[The Company](/docs/company)**.
================================
URL: https://docs.roboco.tech/docs/company
Title: The Company
================================
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { FileText } from "lucide-react";
What keeps twenty-five agents from dissolving into noise is that RoboCo is relentlessly opinionated about *how* work happens. Everything is a task, no task moves without acceptance criteria, and every task walks the same strict, role-gated lifecycle — built, QA'd, documented, reviewed, approved. The structure is the point. It's what turns a roster of models into a company that actually ships.
This section explains the machinery:
The twenty-five agents, the three cells, the Board, and the chain of command you sit on top of.
The fifteen states every task moves through, who owns each step, and how rejected work loops back.
How work flows up a cell → root → master pull-request chain — and why only you ever touch master.
Agents never touch your API or your shell. They act only through a narrow set of role-scoped verbs.
The environment ladder — head, middle, and prod rungs — that decides where PRs land and how releases promote toward master.
Structured findings, the collision map, and PR org-structure labels — how every rejection stays traceable and resolvable.
When work bounces back — QA failing a review, the PR-review gate blocking a merge, a PM sending it back at merge time, or you rejecting an `awaiting_ceo_approval` task — it's never just a paragraph of prose to re-read blind. Every rejection is recorded as one or more structured **findings**: a file and line, a severity from blocker down to nit, what was expected against what actually shipped, and a suggested fix. Whoever picks the task back up has to name each open finding by id before resubmitting — vague "fixed it" doesn't clear the gate — and the next reviewer sees every prior round instead of re-deriving the history from scratch. The Auditor can waive a stray minor or nit finding straight from its own review queue with a required note; blocker and major findings can only be closed by an actual fix. It's why rework is a number the company can report, not a guess.
That same insistence on discipline extends to the tools agents use to do the work: on a project that ships a Makefile, an agent's raw `uv` / `pip` / `conda` / `poetry` package-management calls are denied outright and it's redirected to that project's own `make quality` / `make test` targets, so the gate an agent runs against is the same gate a human developer would.
If you'd rather see these ideas in motion than read about them, [the Tour](/docs/tour) follows one real feature through the whole company.
================================
URL: https://docs.roboco.tech/docs/company/org-and-roles
Title: Org & Roles
================================
import { Mermaid } from "@/components/docs/mermaid";
RoboCo is **25 AI agents and one human — you, the CEO**. They're organized as a real company: a Board sets direction, a Main PM coordinates three delivery cells, and an Auditor watches everything. A few agents sit at your side on demand. You're on top of all of it.
drafts tasks with you"]
SEC["Secretary
gated chief-of-staff"]
PRR["PR Reviewer
inbound + root→master gate"]
end
subgraph Board["Board"]
PO["Product Owner"]
HOM["Head of Marketing"]
AUD["Auditor (silent)"]
end
MPM["Main PM
coordinates the cells"]
subgraph Cells["Delivery cells (6 agents each)"]
BE["Backend cell"]
FE["Frontend cell"]
UX["UX/UI cell"]
end
CEO --- Support
CEO --> Board
Board --> MPM
MPM --> BE & FE & UX`} />
## The cells
The three delivery cells — **Backend, Frontend, UX/UI** — are where code gets written. Each cell is a small, complete team of **six agents**:
| Role | Count | What they do |
|------|-------|--------------|
| **Cell PM** | 1 | Runs the cell like an engineering manager: delegates, clears blockers, triages, and folds the cell's work up to the Main PM. |
| **Developers** | 2 | Build the code in their own clones and open pull requests. |
| **QA** | 1 | Reads the real diff and decides whether work ships or comes back. Doesn't rubber-stamp. |
| **Documenter** | 1 | Writes down what was built, so the next agent — and you — don't start cold. |
| **PR Reviewer** | 1 | Reviews the cell's assembled pull request at the in-path gate before the PM merges it up. |
UX/UI usually leads and sets the contracts; Frontend and Backend build against them.
Frontend and UX/UI QA (`fe-qa`, `ux-qa`) carry one extra tool the other roles don't: a headless Chromium browser, driven through structured Playwright commands, for the moments reading the diff alone can't settle an acceptance criterion — rendered output, computed styles, an accessibility tree, or an actual visual check.
## The Board and the Main PM
| Role | Reports to | What they do |
|------|-----------|--------------|
| **Product Owner** | CEO | Turns your ask into requirements and acceptance criteria. Also runs five standing [Board Programs](/docs/optional/board-programs), when armed — Roadmap, Pest Control, Spackle, Scales, and Dogfood — each a periodic exploration that proposes work for your decision. |
| **Head of Marketing** | CEO | Reviews work from the positioning / naming / user angle. Also runs six standing [Board Programs](/docs/optional/board-programs), when armed — the X feature spotlight, Periscope, Megaphone, Mirror, Barfly, and War Room — drafting into your X Post Queue or a market-research report. |
| **Auditor** | CEO | Silent observer with read access to *everything*; reports quality concerns to you and never interferes. Never initiates contact with another agent, but replies in-thread if you open a direct message with it. Also runs three standing [Board Programs](/docs/optional/board-programs), when armed — Coroner, Librarian, and Sentinel — none of which change its silent-to-agents posture. |
| **Main PM** | Board | Coordinates all three cells: fans a task out into per-cell subtasks, integrates the results, and opens the final pull request. |
## At your side, on demand
Three agents work directly with you rather than in the delivery flow. They run only while you're interacting with them or have given an explicit instruction:
| Role | What they do |
|------|--------------|
| **Intake** | The conversational **Task Assistant** on the Prompter page. Reads your codebase and drafts a well-formed task with you. Chats only with you. |
| **Secretary** | Your conversational chief-of-staff. Reads the whole company's state to advise you and executes your directives — but every high-impact action is **gated** for your explicit confirmation. It spends nothing and approves nothing on its own. |
| **PR Reviewer** | The read-only main reviewer. Handles inbound external/fork pull requests and acts as the in-path gate on the final root → master pull request. It posts a review on the PR, and its only agent-to-agent initiation is delivering a change-request verdict to the owning PM — it never merges or decides, though it will reply if you open a direct message with it. |
## The full roster
25 agents, by their panel IDs. This count and roster are verified against the shipped agent registry as of v0.31.0 — no new seats shipped:
- **Backend:** `be-pm`, `be-dev-1`, `be-dev-2`, `be-qa`, `be-doc`, `be-pr-reviewer`
- **Frontend:** `fe-pm`, `fe-dev-1`, `fe-dev-2`, `fe-qa`, `fe-doc`, `fe-pr-reviewer`
- **UX/UI:** `ux-pm`, `ux-dev-1`, `ux-dev-2`, `ux-qa`, `ux-doc`, `ux-pr-reviewer`
- **Coordination:** `main-pm`
- **Board:** `product-owner`, `head-marketing`, `auditor`
- **At your side:** `intake-1`, `secretary-1`, `pr-reviewer-1`
The registry also carries one shared extra hand, `cell-pr-reviewer-2`: an overflow reviewer that backs all three cell PR reviewers when a same-cell gate piles up. It shares their seat rather than adding one — it's why the registry has 26 non-CEO rows while the org is 25 agents.
## How agents talk
Communication is constant and logged. Agents narrate their reasoning as they work, and formal **notifications** (the ones that need your acknowledgment) come only from PMs and the Board.
Agents coordinate primarily through task state, and message each other **directly** — a task-scoped, agent-to-agent (A2A) back-channel used for hand-offs and quick questions between a specific pair. The **Auditor has silent read access across the company** — tasks, evidence, journals, and notifications — and never initiates a conversation with another agent; its one exception is replying in-thread when *you* open a direct message with it, the same reachability the PR Reviewer has. You watch every A2A message live, and chime in yourself (with any agent, the Auditor and PR Reviewer included), on the [Agents page's Conversations tab](/docs/panel/agents-and-work-sessions#conversations-tab).
## Next
→ **[The task lifecycle](/docs/company/task-lifecycle)** — the path every piece of work walks.
================================
URL: https://docs.roboco.tech/docs/company/task-lifecycle
Title: Task Lifecycle
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
Everything in RoboCo is a task, and every task walks the same path. Each step is gated by role — only QA can pass QA, only the CEO can merge to `master` — so work can't skip a stage or land unreviewed. This is the backbone that makes the company trustworthy.
backlog
backlog --> pending: PM activates
pending --> claimed: agent claims
claimed --> in_progress: start work
in_progress --> verifying: self-verify
verifying --> awaiting_qa: submit (PR is open)
awaiting_qa --> awaiting_documentation: QA passes
awaiting_qa --> needs_revision: QA fails
needs_revision --> in_progress: rework
awaiting_documentation --> awaiting_pm_review: docs done
awaiting_pm_review --> completed: PM merges
awaiting_pm_review --> awaiting_ceo_approval: escalate
awaiting_ceo_approval --> completed: CEO approves & merges
awaiting_ceo_approval --> needs_revision: CEO requests changes
completed --> [*]
in_progress --> blocked: external dependency
in_progress --> paused: temporarily stopped
blocked --> in_progress: unblocked
paused --> in_progress: resumed`} />
## The states
| State | What it means | Who owns the next move |
|-------|---------------|------------------------|
| `backlog` | PM setup phase — dependencies or session setup still needed. | PM |
| `pending` | Ready for work; the orchestrator can spawn an agent for it. | the matching role |
| `claimed` | An agent has locked the task. | the assignee |
| `in_progress` | Active development. | the assignee |
| `blocked` | An external dependency is blocking progress. | whoever clears it |
| `paused` | Temporarily stopped; can resume. | the assignee |
| `verifying` | The developer is self-verifying before handing off. | the developer |
| `awaiting_qa` | Submitted for QA — **a pull request is already open** so QA reviews the real diff. | QA |
| `needs_revision` | QA, a PR reviewer, or the CEO asked for changes. | the developer |
| `awaiting_documentation` | The Documenter writes up what was built (the PR is already open). | Documenter / Developer |
| `awaiting_pr_review` | The in-path PR-review gate: a reviewer checks an assembled pull request before the PM merges it. | PR reviewer |
| `awaiting_pm_review` | Docs are done; the PM reviews and merges. | PM |
| `awaiting_ceo_approval` | A major task escalated to you for the final call. | **you** |
| `completed` | Terminal — work done and merged. | — |
| `cancelled` | Terminal — work cancelled. | — |
A pull request is opened *before* QA review, not after. That lets QA read the actual PR diff on GitHub, and means the whole downstream approval chain — PM, then you — is signing off on a pull request that already exists.
## When work is rejected
Rejection isn't a dead end — it's a loop. When **QA fails** a task, or a **PR reviewer rejects** an assembled pull request, the task drops back to `needs_revision`, the developer reworks it, and it re-enters the flow. The same is true when *you* request changes from the CEO Approval Queue. Nothing is lost; the task carries its history, branch, and pull request with it the whole way around.
## Sibling order: sequence is the bar
Subtasks under the same parent can carry a `sequence` number (lower runs first; ties run in parallel). This isn't a display hint — it's enforced at the same claim chokepoint every claim path crosses: **a task cannot be claimed while any lower-sequence sibling is still non-terminal**, independent of whatever dependency edges do or don't exist between them. A PM can delegate a batch of siblings with sequences 0..N and wire no explicit dependencies at all; claim order still holds. See [Tasks & Kanban](/docs/panel/tasks-and-kanban#sequence-ordering) for what this looks like from the board.
## The in-path PR-review gate
Most leaf developer tasks are reviewed by QA and never need a separate PR review. But when work is **assembled and pushed up the chain as a pull request**, it stops for a dedicated review before any PM merges it:
awaiting_pr_review: submit_up / submit_root
awaiting_pr_review --> awaiting_pm_review: pr_pass
awaiting_pr_review --> needs_revision: pr_fail`} />
- A **cell PM** runs `submit_up` to open the cell → root pull request.
- The **Main PM** runs `submit_root` to open the root → master pull request.
- Both land in `awaiting_pr_review`, where a PR reviewer either **`pr_pass`es** it on to the PM merge or **`pr_fail`s** it back to `needs_revision`.
This gives the merge step a real reviewer with the power to reject — the one thing a PM otherwise lacks. **Leaf dev tasks and branchless coordination roots skip the gate.**
The reviewer can no longer wave through an assembled pull request whose own CI is failing, still pending, or unresolvable — `pr_pass` refuses until the head commit's checks come back green. A repository with no CI configured at all still passes through (there's nothing to wait on), but that's recorded as an explicit evidence note on the review, not silently treated the same as a real green build. `pr_fail` stays available regardless, so a broken PR is never stuck — it just can't be waved through as passing.
## Role-gated transitions
Transitions aren't suggestions; they're enforced. A handful of the rules:
- **Activating** a task (`backlog → pending`) is PM-only.
- **Passing or failing QA** is QA-only, and a pass requires real review notes.
- **`pr_pass` / `pr_fail`** are PR-reviewer-only.
- **Merging** (`awaiting_pm_review → completed`) is PM-only; **escalating to the CEO** and the final **approve / request-changes / cancel** are CEO-only.
- **Cancelling** is PM-only.
How those role boundaries are enforced — and why a developer literally cannot call the merge verb — is the subject of [How agents are sandboxed](/docs/company/agent-gateway).
## Next
→ **[The merge model](/docs/company/merge-model)** — how a task's branch travels up to `master`.
================================
URL: https://docs.roboco.tech/docs/company/merge-model
Title: Merge Model
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
A feature in RoboCo isn't one commit on one branch — it's a small tree of work that converges, as real pull requests, up a fixed chain to your repository's default branch. The rule at the top is simple and absolute: **only you ever merge to `master`.**
## Branches, commits, and PRs are traceable
Every branch, commit, and pull request carries the task ID it belongs to, so your git history reads back to the work that produced it.
- **Branches** follow `{type}/{team}/{task-hierarchy}`, where the hierarchy uses `--` between levels (a `/` would collide with git's ref storage). Types are `feature`, `bug`, `chore`, `docs`, `hotfix`.
```text
feature/backend/ABC12345 # a root task
feature/backend/ABC12345--DEF67890 # a subtask
feature/backend/ABC12345--DEF67890--GHI11111 # a sub-subtask (max depth)
```
- **Commits** are auto-prefixed with the short task ID: `[ABC12345] Add the auth endpoint`.
- **Pull requests** are titled the same way: `[ABC12345] `.
A branch is created automatically the moment an agent claims a task, and a **work session** tracks its branch, base, commits, files changed, and pull request from claim to merge.
A developer's or PM's clone is shared across tasks, so a task's branch can sit unattended for a while — long enough for its real parent branch to move on without it. When that task is re-claimed (a pool release, an unclaim, an escalation redirect), RoboCo merges the advanced upstream base into the pre-existing task branch before work resumes, so the dev isn't quietly working off a stale snapshot. A clean merge is silent (or a no-op if there was nothing new to inherit); a genuine conflict surfaces as a note on the task pointing the dev at `sync_branch` to resolve it by hand, rather than failing the re-claim outright.
The shared rebase primitive behind every one of these syncs — `sync_branch`, a re-claim's base inheritance, and the PM/CEO rebase action — used to reset a task's branch hard to its last-known origin state before rebasing, which silently discarded any commit an agent had made but not yet pushed. It now checks first: strictly-ahead local work is never reset, and a genuinely diverged branch reports both sides instead of picking one to throw away. A reviewer re-examining a task now also always sees the latest pushed commits, not a stale pre-sync snapshot from an earlier round — the read path that resolves a branch's tip for diffing and file reads prefers origin over a diverged local ref, the same way a fresh commit already showing up in a later QA round should behave.
## Work converges up a chain
Each developer works in their **own clone** and opens a pull request from their branch. Those flow upward:
C["cell branch"]
D2["dev branch"] --> C
C --> R["root / integration branch"]
C2["other cell branch"] --> R
R --> M["master"]
M:::ceo
classDef ceo fill:#1f6feb,color:#fff,stroke:#1f6feb;`} />
1. **Developers → cell.** A cell's developers merge their work into the cell's branch.
2. **Cell → root.** The **cell PM** runs `submit_up` to open the cell → root pull request. The Main PM keeps one integration (root) branch per repository.
3. **Root → master.** The **Main PM** runs `submit_root` to open the final root → master pull request.
Each of those assembled pull requests passes through the [in-path PR-review gate](task-lifecycle.md#the-in-path-pr-review-gate) before its PM merges it.
`submit_up` and `submit_root` aren't always a hand-run PM action. When every child of an assembled parent has reached a terminal state, the system runs the submit to the PR gate **on the PM's behalf** — the submit's real substance (freshness rebase, integrity check, opening the PR) is deterministic, so spending a whole PM turn just to press the button is wasteful. If the gate rejects the assembly (it's out of date, or fails an integrity check), it falls back to spawning the PM to handle it. The PM keeps every *judgment* turn — the merge itself, and any request-changes. This turn-cut is on by default (`ROBOCO_PR_GATE_AUTO_SUBMIT_ENABLED`) and leaves a `task.auto_submitted` entry in the audit trail.
## Only the CEO merges to master
The final pull request — root → master — is the one place the company stops and hands the decision back to you. It lands in your **CEO Approval Queue** and waits.
- The agent-facing merge path **hard-refuses to target the default branch.** A PM can merge a cell PR up to the root, but the merge to `master` is reserved for the CEO action, taken from `awaiting_ceo_approval`.
- **Force-push is CEO-only** too.
From the queue you **Approve & Merge** (it ships to `master`), **Request Changes** (it loops back for another pass), or **Cancel**. This is the second of the only two moments the company needs you — the first being the green light that started the work.
Cell pull requests are squash-merged, so each cell's work lands as a single verified commit on the integration branch, co-authored by the agent that wrote it. The final pull request then carries one clean commit per cell — three streams of work folded into one reviewable history.
## Pull requests you didn't open
Not every pull request comes from inside the company. When an external contributor or a fork opens one against your repository, the read-only **PR Reviewer** reads the diff against your standards and posts a single change-request on the PR — it never chats, merges, or decides. The PR then surfaces in the **PR Review Queue** on the Command Center, where you **Supersede** it (the company cuts its own branch from the contributor's commits, hardens it, opens its own PR, and links back to the original once that merges) or **Dismiss** it. Either way the call is yours, and the org never pushes to anyone else's fork. *(This inbound-review flow is feature-flagged; see the optional-subsystems reference.)*
## Next
→ **[How agents are sandboxed](/docs/company/agent-gateway)** — why a developer agent literally cannot perform the merge.
================================
URL: https://docs.roboco.tech/docs/company/megatask
Title: MegaTask
================================
import { Mermaid } from "@/components/docs/mermaid";
import { Admonition } from "@/components/docs/admonition";
Most of the time you describe one piece of work and RoboCo builds it. Sometimes you have **several** things you want done at once — and they aren't always in the same repository. A **MegaTask** lets you describe the whole set in a single intake chat and hand it off as one batch that the company sequences and builds for you.
The motivating example: you want to ship a change to a SaaS app, the open-source core engine it depends on, and a framework adapter — three repositories that don't share a codebase. That's one MegaTask.
## Starting a MegaTask
The intake modal has three scopes:
- **Single cell** — one task in one project.
- **Board-led** — a feature spanning the cells of one product.
- **MegaTask** — several tasks across the projects you pick.
Choose **MegaTask** and check every repository the work spans (pick at least two). The intake agent clones and reads all of them, interviews you exactly as usual, and then — instead of proposing one draft — proposes the **whole batch at once**: one task per piece of work, each already assigned to the project it belongs to.
## How the batch is sequenced
For each task it proposes, the agent declares a small **collision surface**: which files or directories it will touch, whether it adds a database migration, and whether it edits a widely-shared component. RoboCo turns those surfaces into conflict-free **waves** with a deterministic analyzer — no guesswork:
- Tasks that touch the same files are **serialized** (the more important one first).
- Tasks that add a migration run in a **serial chain**, never two at once.
- A task that edits a shared surface runs **after** the tasks it overlaps.
- Everything else runs **in parallel**.
Each root-subtask is stamped with a `sequence` number derived from its wave — this is the same general sibling-ordering mechanism every parent's subtasks use (see [the task lifecycle](/docs/company/task-lifecycle#sibling-order-sequence-is-the-bar)), not something MegaTask-specific. It's enforced at the claim chokepoint itself, not just by wiring dependency edges: a root-subtask in wave N strictly cannot be claimed while any wave-(N-1) root-subtask is still non-terminal. Two root-subtasks in the same wave — no file overlap, nothing shared — carry the same sequence and run fully in parallel. A cancelled task still releases the wave behind it.
## What gets created
When you confirm, RoboCo creates one **umbrella** task that groups the batch, and one **root-subtask** per piece of work:
(no repo, no PR)"] --> A["Task A — project 1
own branch + PR"]
U --> B["Task B — project 2
own branch + PR"]
U --> C["Task C — project 3
own branch + PR"]`} />
The **umbrella** is the batch's single review-and-approve unit. It does no git of its own — it spans repositories that have no common `master`, so there is no mega-PR. Each **root-subtask** is a normal piece of work in its own repository, with its own branch and its own pull request, coordinated by the Main PM down to the cells exactly like any other task. The umbrella finishes only when every task in it is done.
## The two start buttons
Like a single task, a MegaTask offers two start paths:
- **Board review & Start** — the Product Owner and Head of Marketing review the **whole batch** first (they see every task and can adjust scope). The work is held until you approve the umbrella, then released wave by wave.
- **Approve & Start** — the batch goes straight to the Main PM and the first wave dispatches immediately.
Either way you review and approve the batch **once**, not task by task. After it launches, the umbrella and its tasks appear in your task views like any other work — you watch the waves progress, and each task lands as its own pull request for you to merge.
## Board review and redraft
Sending a batch for **Board review & Start** doesn't end your intake chat the way it might feel like it should — the chat stays open, parked against the umbrella, while the Product Owner and Head of Marketing review it. When they finish, their feedback comes back into that same still-live chat as one brief: every root-subtask's current title, description, and acceptance criteria, alongside the board's decision notes and an instruction to re-propose the whole batch in a single call. You can go back and forth with the intake agent from there, and confirming the revised batch **updates the existing umbrella and root-subtasks in place** rather than starting over — items whose project scope didn't move are patched positionally; only an item whose scope actually changed gets cancelled and recreated. The board can review again for another round if you want one.
The same redraft path works after the fact, too. Task Detail's **Re-draft with board feedback** button — normally a per-task control — also handles a MegaTask's branchless umbrella: it recovers the batch's multi-repo scope from the live root-subtasks instead of needing a project to redraft against, so redrafting a MegaTask from its umbrella works exactly like redrafting a single task.
If an earlier redraft round cancelled a root-subtask (because its project scope moved), a later round doesn't resurrect it — the redraft view only ever looks at the umbrella's current, non-cancelled children.
================================
URL: https://docs.roboco.tech/docs/company/agent-gateway
Title: Agent Gateway
================================
The most important thing to understand about trusting RoboCo with your repository: **agents never touch your API, your database, or a shell directly.** Every action an agent can take goes through a narrow, server-side gateway that only exposes the handful of verbs that agent's *role* is allowed to use. Capability is decided by role at spawn time — not by the model's good behavior.
## Agents speak in verbs, not API calls
Each agent container talks to RoboCo through two thin MCP servers, both backed by a single server-side component (the **Choreographer**) that composes the real services behind the scenes:
| Server | What it exposes |
|--------|-----------------|
| `roboco-flow` | **Intent verbs** — the lifecycle actions: `give_me_work`, `i_will_work_on`, `open_pr`, `i_am_done`, `claim_review`, `pass_review`, `complete`, `submit_up`, `submit_root`, `pr_pass`, … |
| `roboco-do` | **Content tools** — `commit`, `note`, `dm`, `read_a2a`, `evidence`. |
Two more read-only servers give every agent a read-only view of git (`status`, `log`, `diff`) and access to the knowledge base. Two further servers are mounted only for the roles that need them: `roboco-docs` (documentation file management) for documenters, cell PMs, the Main PM, and the Product Owner/Head of Marketing; and `roboco-search` (web `search`/`fetch`, feature-flagged) for cell PMs, the Main PM, and the Product Owner/Head of Marketing — never for developers, QA, the Auditor, or the PR Reviewer. That's the entire surface. There is no "run arbitrary SQL," no "call any endpoint," no general shell.
## A role can only call its own verbs
At spawn, every agent is handed a **manifest** listing exactly the verbs its role may call — and nothing else. The manifest is built from a server-side role configuration and mounted read-only into the container. The result is that the lifecycle's role rules aren't just policy, they're *unreachable code* for the wrong role:
| Role | Representative verbs | Verbs it does *not* get |
|------|----------------------|--------------------------|
| **developer** | `give_me_work`, `i_will_work_on`, `open_pr`, `i_am_done`, `commit`, `note`, `evidence`, `dm`, `read_a2a` | review or merge verbs |
| **qa** | `claim_review`, `pass_review`, `fail_review`, `note`, `evidence` | `commit`, `open_pr`, merge verbs |
| **documenter** | `i_documented`, `commit`, `note`, `evidence` | review or merge verbs |
| **cell_pm** | `delegate`, lifecycle progression such as `complete`/`submit_up`, `note`, `dm`, `evidence` | `commit`, review verbs |
| **main_pm** | merge verbs, `submit_root`, `delegate`, `note`, `dm`, `evidence` | `commit` |
| **auditor** | private `note`, `evidence`, `dm`/`read_a2a` (reply-only — see below), `triage`, `waive_finding`, plus its playbook-curation (`approve_playbook`, `reject_playbook`, `archive_playbook`) and [Board Program](/docs/optional/board-programs) (`propose_postmortem`, `propose_quality_report`, `propose_playbook_drafts`) verbs | any task-lifecycle status-changing verb (pass/fail/merge/complete on a task), initiating an A2A conversation |
| **pr_reviewer** | `pr_pass`, `pr_fail`, `post_pr_review`, `claim_pr_review`, `claim_gate_review`, `evidence`, `dm`, `read_a2a` (reply-only — see below) | `commit`, `open_pr` |
So when [the lifecycle](task-lifecycle.md#role-gated-transitions) says "only QA can pass QA" or "only the Main PM can assemble the root→master pull request, and only the CEO can merge it," that boundary is enforced at the gateway: the verb simply isn't available to anyone else.
The Auditor and PR Reviewer's `dm`/`read_a2a` are deliberately narrower than every other role's: both can read and reply inside a conversation *you* (the CEO) open with them, but neither can *initiate* one — the Auditor stays a silent observer to the rest of the fleet, and the PR Reviewer's only agent-initiated message is delivering its `pr_fail` verdict to the owning PM. The manifest enforces that asymmetry the same way it enforces everything else on this page.
## Every action returns a structured envelope
Agents don't guess at state. Every verb returns a standardized **envelope**:
- On success: `{ status, task_id, next, evidence?, context_briefing }` — where `next` tells the agent what to call next.
- On error: `{ error, message, remediate, missing }` — where `remediate` tells the agent exactly how to fix it and retry.
That `next` / `remediate` contract is why agents move through the lifecycle reliably instead of flailing: the gateway leads them, step by step, and rejects anything out of order with an explanation rather than a crash.
## The other guardrails
A few more protections run by construction, the same way on every backend (Claude, Grok, Codex, Gemini, Kimi, Ollama Cloud, or self-hosted):
- **Claim-locking** serializes work, so two agents can't grab the same task or race a merge.
- **The token never enters the container.** Your project's git token — GitHub, Gitea, or GitLab — is injected only for the moment of a git operation, orchestrator-side, and scrubbed from every clone — see [Register a project](../get-started/first-project.md#what-happens-under-the-hood).
- **A prompt-injection guard** screens task prompts, and a bash guard blocks credential-exfiltration and identity-forgery patterns.
- **Rate limits and overloads park, they don't crash-loop.** If a provider returns a 429 or a persistent overload, RoboCo *queues* that agent's work and probes for recovery instead of burning tokens retrying. You'll see an amber banner; the work resumes automatically when the provider does.
The practical upshot for you as operator: the workforce is structurally constrained to do its job and only its job. You're not relying on twenty-five AI agents all choosing to behave — you're relying on the fact that the misbehaving action isn't on the menu.
## Next
→ Watch it all in motion in [the Tour](/docs/tour), or head back to [the lifecycle](/docs/company/task-lifecycle).
================================
URL: https://docs.roboco.tech/docs/company/branching-strategy
Title: Branching Strategy
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
RoboCo treats a project's branches as a **release railroad**, not a free-for-all. By default every project has one branch that plays two roles: it's where task PRs land, and it's where the release manager cuts the next version. When you outgrow that — when work needs to bake on a `dev` or `qa` rung before it reaches `prod` — you can declare an ordered **environment ladder** instead. The ladder is company-level structure: it decides where every task's PR targets, where releases are tagged, and how the release executor promotes work up the chain.
## The single-branch default
A project that has never declared a ladder behaves exactly as before. Its `default_branch` is treated as both the **head rung** (where PRs land) and the **prod rung** (where releases are cut). Nothing moves, nothing breaks, and existing projects keep behaving byte-for-byte until you deliberately add rungs.
## The ladder
A ladder is an ordered list of `{name, branch}` rungs:
| Position | Name | What it means |
|----------|------|---------------|
| Index `0` | head / dev rung | Where every task's PR merges. The per-agent clone also points here. |
| Middle rungs | `qa`, `staging`, etc. | Optional stops between dev and prod. |
| Index `-1` | prod / release rung | Where the gated release executor commits the version bump and tags the release. |
### A typical two-rung ladder
| Rung | Name | Branch |
|------|------|--------|
| 1 (head) | `dev` | `slave` |
| 2 (prod) | `prod` | `master` |
With this ladder, day-to-day PRs still target `slave`, but releases are cut and tagged on `master`. `master` is no longer the branch developers work against by default.
## How releases climb the ladder
When the gated release manager cuts a release, it does not just tag the prod rung as-is. It first **promotes the full chain**: it merges every rung above prod into the prod branch, in order, before writing the version bump and CHANGELOG entry. If any of those merges conflicts, the release aborts before anything is committed — you get a clean failure, not a bad tag.
task PRs land here] -->|promote| QA{middle rung?}
QA -->|yes| MID[merge into next rung]
MID -->|continue| PROD[prod rung
release tagged here]
QA -->|no| PROD
style HEAD fill:#e0f2fe
style PROD fill:#dcfce7`} />
## Keeping the ladder in sync (EnvSync)
The ladder itself is just project configuration. The **EnvSync** engine that keeps the rungs in sync is a separate, off-by-default flag (`ROBOCO_ENV_SYNC_ENABLED`). When armed, EnvSync periodically cascades the ladder from the last rung down toward the first, merging each rung into the one below it:
- A **clean merge** auto-pushes to the lower rung and keeps walking down.
- A **conflict** stops the cascade for that project and opens exactly **one** sync PR plus **one** coordination task — never per-rung spam.
- EnvSync never targets the prod rung; it only merges into lower rungs. The prod rung still advances only through your explicit release approval.
See [Environment Branches & EnvSync](/docs/optional/env-branches) for the full configuration guide.
## Setting it
Open a project's **settings page** in the panel and use the **Environments card**'s ladder editor to add, name, reorder, and remove rungs. Saving rejects empty or duplicate branches. The ladder itself applies on the very next PR or release; EnvSync applies after the next backend restart once the flag is flipped.
A null ladder is not an error. RoboCo synthesizes a single-branch ladder from `default_branch` at read time, so every existing project keeps behaving exactly as it does today until you deliberately split it.
## Next
→ [Environment Branches & EnvSync](/docs/optional/env-branches) for the toggle and the sync cascade · [Autonomous Maintenance](/docs/optional/autonomous-maintenance) for the gated release manager that promotes the ladder at cut time · [The merge model](/docs/company/merge-model) for how task PRs flow up to master.
================================
URL: https://docs.roboco.tech/docs/company/review-and-quality
Title: Review & Quality
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
RoboCo ships with structural quality machinery: every rejection is recorded as a **finding**, every assembled PR is reviewed by a gate reviewer armed with a **collision map**, and every PR the company opens is **auto-labeled** with its org-structure role. The point is to turn "trust the model behaved" into "the decision surface is visible, named, and resolvable."
## Structured findings
When QA fails a review, the PR-review gate blocks a merge, a PM sends work back, or you reject an `awaiting_ceo_approval` task, the feedback is not a paragraph in a comment. It is one or more structured **findings**:
| Field | What it carries |
|-------|-----------------|
| `file` + `line` | The exact location in the diff. |
| `severity` | blocker / major / minor / nit |
| `expected` | What the reviewer expected to see. |
| `actual` | What actually shipped. |
| `fix` | A suggested fix, when the reviewer can give one. |
| origin + round | Who raised it and which review round it belongs to. |
A task that bounces back must name each open finding by id before it can be resubmitted. The next reviewer sees every prior round instead of re-deriving the history. The Auditor can waive a stray minor or nit with a required note; blocker and major findings close only with an actual fix.
Acceptance criteria get the same rigor at both ends of a delegation. **Down:** a PM can't hand off a subtask without mapping it onto the parent's actual acceptance criteria — an unresolvable reference is refused, naming the real criteria, rather than silently dropping coverage until it's discovered much later. **Up:** QA can't pass a review with a gestalt "looks good" — `pass_review` requires one verified entry per acceptance criterion, and each renders as its own `[AC] — verified: ` line in the QA notes on the task detail page, so a reviewer three steps removed can see exactly what was checked and how, not just that someone signed off.
B[Finding recorded: file:line, severity, expected, actual]
B --> C[Task moves to needs_revision]
C --> D[Developer reclaims, fixes each open finding by id]
D --> E[Next reviewer sees full finding history]
E --> F{All findings addressed?}
F -->|yes| G[Work advances]
F -->|no| H[Back to revision]`} />
## The collision map
Sibling subtasks — tasks that share the same parent — can declare overlapping file globs or migration chains at delegation time. The **collision map** surfaces that overlap to both the task detail page and the in-path PR reviewer:
- Each task's **Collision** tab shows its own declared surface and every sibling whose declared surface overlaps it.
- Status, branch, PR number, and **sequence badges** appear per sibling.
- Overlapping globs are highlighted.
- A sibling that actually touched files it never declared gets a **drift** callout.
- Root and standalone tasks render an explicit "no collision map" state, since they have no siblings to collide with.
The same collision surface powers the [sequence ordering](/docs/panel/tasks-and-kanban#sequence-ordering) that lets a PM enforce lower-sequence siblings finishing before higher-sequence ones are claimed.
## PR org-structure labels
Every PR the company opens — assembled cell→root and root→master PRs, and Supersede PRs from the external-PR loop — is labeled with its **org-structure role** the moment it is created: a literal `to master` / `to slave` target label, `root` on an assembled root→master PR, `MegaTask` on any PR whose task carries a batch id, and an owning-layer label (`main-pm`, `cell/{team}`, or `subtask/{team}`). Architectural-conventions scaffold PRs carry no task and get a single fixed `chore` label instead of this set. Labeling is best-effort: a GitHub hiccup never blocks PR creation. The labels are a filtering aid on GitHub's own PR list, not a gate — review and merge behavior do not depend on them.
## Makefile enforcement for agents
On a project that ships a `Makefile`, an agent's raw `uv` / `pip` / `conda` / `poetry` calls are denied and redirected to that project's own `make quality` / `make test` targets — `npm`/`pnpm` are approved tools and untouched by this guard. This keeps the gate an agent runs against identical to the gate a human developer runs. It also preserves the Makefile's `UV_NO_SYNC=1` setting and its private `UV_CACHE_DIR`, preventing the concurrent-venv-corruption race that bare package-manager invocations can trigger. See [Deployment](/docs/deploy/deployment#makefile-enforcement-for-agents) for the operator-side Makefile targets.
RoboCo does not rely on every model choosing to behave. It relies on the fact that the wrong action is off the menu: a developer's manifest has no merge verb, QA's has no commit verb, and the agent bash guard blocks raw Python package-manager calls when a Makefile is present.
## Next
→ [How agents are sandboxed](/docs/company/agent-gateway) for the role-scoped verb manifest that enforces these boundaries · [PR Review](/docs/optional/pr-review) for the inbound and in-path review loops · [Collision Map](/docs/optional/collision-map) for the sibling-surface reference · [Tasks & Kanban](/docs/panel/tasks-and-kanban) for the panel view of findings and collisions.
================================
URL: https://docs.roboco.tech/docs/panel
Title: Operating the Panel
================================
import { Admonition } from "@/components/docs/admonition";
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { FileText } from "lucide-react";
The control panel is your one window into the company — every task, agent, message, journal, and pull request is live in front of you, served at `http://localhost:3000`. As of v0.25.0, the panel went through a dense **hover-help sweep**: nearly every control, badge, and metric now carries both a hover tooltip and an accessible label explaining exactly what it means or does — when a label is ambiguous, hover it before you guess. The same polish round tightened the overview layout and spacing across the dashboard surfaces. This section is a reference: one short page per area, describing what you do there and what it shows.
Two small, site-wide scroll conveniences apply everywhere, not just on one page: expanding a row or toggling a list no longer jumps you back to the top of the page, and a floating back-to-top / jump-to-bottom control appears in the corner of any page long enough to scroll.
the home dashboard: cell health, your approval queues, live metrics, and the notifications inbox.
the X post queue, the video post queue, on-demand video requests, and a unified posted/rejected history.
the task table, the task detail page (with CEO god-mode), and the dev board.
the live workforce, spawn/stop control, live reasoning streams, and — on the Conversations and Journals tabs — the agent-to-agent message switchboard and each agent's reflection log.
the repositories the company works on, and multi-repo products — Products and Projects, tabbed on one page.
browse live git state, run operations per project, and the work-sessions ledger.
the in-house RAG: search, ask, and reindex.
your acknowledgment-required inbox — the header bell and the full Unread/Pending/All tabs.
read-only quality oversight across the whole company.
the charter, the Scorecard, the Cockpit, and pitches.
delivery performance and the token-usage dashboard.
feature flags, transcript retention, and configuration.
Picking which models power the agents is on the **Settings → AI Providers** page — see [Choosing & Running Models](/docs/models).
## Command palette (Cmd+K)
Press **Cmd+K** (Mac) or **Ctrl+K** (Windows/Linux) from anywhere in the panel — or click the header's search box, which is now a live trigger rather than the disabled "Coming Soon" placeholder it used to be — to open a global search overlay. Type to fuzzy-match across four categories at once, shown in this order: **Tasks** (searched server-side by title, description, and short ID), **Agents**, **Projects**, and **Pages** (the sidebar's own navigation). Arrow keys move the selection, Enter jumps straight to the matched task/agent/project detail or page, and Escape closes it. With the input empty, the palette instead shows your **Recents** — up to 10 items you've navigated to before, most-recent-first, remembered locally in your browser rather than synced server-side.
## Sidebar order
The left sidebar lists Overview, Task Assistant, Tasks, Git, Workstation, Social, Knowledge Base, Agents, Auditor, and Metrics, in that flat order — with **Business**, **AI Providers**, and **Settings** pinned in a separate footer group below it, since those three are configuration rather than day-to-day delivery surfaces. There's no separate **Kanban** entry: the board now lives as a tab on the **Tasks** page itself (see [Tasks & Kanban](/docs/panel/tasks-and-kanban#the-kanban-tab)), and the old `/kanban` URL redirects there so nothing bookmarked breaks. **Workstation** is a single sidebar entry that opens Products and Projects as tabs on one page, in that order — see [Workstation](/docs/panel/projects-and-products). **Agents** is likewise a single sidebar entry that opens Fleet, Conversations, and Journals as tabs on one page — the former A2A and Journals pages are now its Conversations and Journals tabs, and the sidebar's separate A2A and Journals entries are both gone — see [Agents & work sessions](/docs/panel/agents-and-work-sessions). The [Task Detail page](/docs/panel/tasks-and-kanban) has had a significant redesign recently — see its own page for what's new.
================================
URL: https://docs.roboco.tech/docs/panel/command-center
Title: Command Center
================================
import { Admonition } from "@/components/docs/admonition";
The Command Center at **`/overview`** is the panel's home page — the one screen you open to see whether the company is healthy and to act on the two things only you can decide. It pulls everything onto a single board: per-cell health, your decision queues, live metrics and cost, the auditor's open flags, current blockers, and a recent-activity feed. The v0.25.0 overview polish turned the top row into a real metrics strip: four dense cards, each backed by a live data source. Two of them — Auditor Alerts and Scorecard summary — are one-click doorways into their full view; the notifications bell in the top header rides along on every page.
## Quick Actions and key metrics
The page leads with a **Quick Actions** row — icon-and-label buttons for the common jumps, reachable without scrolling — followed immediately by a four-up metrics strip.
### Customizing Quick Actions
The row is yours to shape. A **pencil** button opens a dialog listing every one of the panel's real destinations — around 23 in all, including tab-parameterized deep links a plain sidebar entry can't express, like Git → Work Sessions, Metrics → Token Usage, or Business → Secretary — each with a checkbox to show or hide it, an up/down arrow pair to reorder the ones you've enabled, and a **Reset to defaults** button. Changes apply instantly and are **saved server-side against your account**, so your chosen set follows you to any browser or machine you open the panel from, rather than resetting per browser. An id from a since-removed destination is dropped silently rather than crashing the row, and hiding everything shows an honest "no quick actions selected" message instead of an empty strip.
The defaults cover the CEO's day-to-day path plus every destination the old fixed action bar offered, so upgrading never silently drops one: task intake, Tasks, Kanban, Git, Agents, A2A (which now opens straight into the Agents page's Conversations tab — see [Agents & work sessions](/docs/panel/agents-and-work-sessions)), Secretary, Journals (which now likewise opens straight into the Agents page's Journals tab), Auditor, Metrics, and Settings.
### Key Metrics
The first card shows the headline numbers from the dashboard's key-metrics endpoint: weekly task velocity, completion rate, documentation coverage, and the current count of active blockers. It is the fastest answer to "is work moving?" It is a static summary — the full velocity, task-status grid, agent-status, and per-cell health breakdown live on the [Metrics](/docs/panel/metrics) **Performance** tab.
### Auditor Alerts
The second card surfaces currently *unresolved* flags raised by the [Auditor](/docs/panel/auditor) — the count and the most severe recent items. It is read-only here; clicking the card takes you to the full Auditor page where you can inspect every flag, generate a report, and see which tasks produced them.
### Usage overview
The third card is a live token-and-cost summary driven by the `USAGE_SNAPSHOT` stream on `/ws/system`, with a fallback to HTTP polling when the socket is down. It shows tokens in, tokens out, total tokens, total cost, and the trend versus the prior period for the current window. It is a static summary — the full time series, model split, per-agent and per-team bars, monthly projection, and cache-efficiency details live on the [Metrics](/docs/panel/metrics) **Token Usage** tab.
### Scorecard summary
The fourth card is the org-wide **30-day rollup** from the Scorecards tab: tasks completed, first-pass yield, throughput per active-runtime hour, total active effort hours, and cost. It deep-links straight into the [Metrics](/docs/panel/metrics) **Scorecards** tab, where you can also see the CEO decision metrics and a per-member breakdown.
The Usage overview updates in place as agents spend. The other cards refresh together with the rest of the board when you click the page-scoped **refresh** icon in the header.
Right below the metrics strip sits a **Spend Trend (7d)** chart — a daily breakdown of the same provider-priced cost the Usage overview card summarizes, so you can see whether spend is climbing, flat, or spiky over the last week before diving into the full [Token Usage](/docs/panel/metrics#token-usage) tab. With no usage recorded yet, it shows an explicit empty state rather than a blank chart.
This is the first thing you see — the point is to answer "is anything on fire" before you scroll into anything detailed.
## Team Health
Below that sits a **health card per cell** — Backend, Frontend, UX/UI, and the management line — plus the on-demand Task Intake and Secretary agents. It comes from the CEO-overview endpoint and refreshes with the rest of the board.
## The CEO decision surfaces
The Command Center is where the company hands work, and decisions, *up to you*. Several panels exist specifically for that — each one hidden entirely when there's nothing waiting on you.
### CEO Approval Queue
Every task that a Main PM escalates lands in **`awaiting_ceo_approval`** and appears here. For each one you can:
- **Approve & merge** — the PR is merged and the task moves to `completed`.
- **Request changes** — the task drops to `needs_revision` and goes back to the cell.
- **Cancel** — the task is cancelled.
These are the same actions you'd take from the [Task Detail page](./tasks-and-kanban.md#ceo-god-mode), surfaced here so the day's approvals are in one list. See [the task lifecycle](/docs/company/task-lifecycle) for how a task reaches this state.
### PR Review Queue
This panel lists **inbound external / fork pull requests** RoboCo discovered on your repositories. For each one you decide:
- **Supersede** — let an internal task take the change forward.
- **Dismiss** — close it out.
The PR Review Queue only renders when there's something to decide. An empty board means no external PRs are waiting — not that the feature is off. (Inbound external/internal PR review is itself flag-gated; see [PR review](/docs/optional/pr-review).)
### Social summary
A compact card reports how many X and video drafts are currently awaiting your approval — held announcements, mention replies, and rendered clips — with an **Open Social** button. The full queues (edit, approve & post, reject) and the unified posted/rejected history across both live on the dedicated [Social](/docs/panel/social) page, not here; this card is a summary stand-in so the Command Center doesn't duplicate that surface.
Unlike the other CEO decision panels below, the Social summary card doesn't hide itself — it renders with a zero count when nothing's waiting. The [X engine](/docs/optional/x-engine) and [video engine](/docs/optional/video-engine) are both off by default, and even armed produce nothing without stored credentials — a zero count doesn't tell you which of those is true.
### Roadmap Review Queue
When the [Board roadmap engine](/docs/optional/roadmap-engine) is on, the Product Owner's weekly themed cycle shows up here as a card per open cycle, with every proposed item inside it. For each **proposed** item you can:
- **Approve** — materializes the item as a `BACKLOG` task.
- **Reject** — records your reason (it feeds the next cycle's prompt) without adding anything to the backlog.
Already-decided items in the same cycle show their resolved status (approved / rejected) rather than the action buttons.
This queue only renders once the Product Owner has authored a cycle — an engine that's off, or hasn't ticked yet, simply shows nothing here.
### Pest Control, Spackle, Mirror, and Dogfood Review Queues
Four more [Board Program](/docs/optional/board-programs) queues sit alongside Roadmap, one per program, each rendering the same way — hidden until that program has authored a cycle, a card per open cycle, approve/reject per proposed item:
- **Pest Control** — approving materializes a bug task backed by findings-ledger and rework evidence.
- **Spackle** — approving materializes a gap-fill task (an undocumented flag, an orphaned route, a dead-end tab).
- **Mirror** — approving materializes a docs task correcting a messaging drift against the charter.
- **Dogfood** — approving materializes a friction-fix task from a live walk of the product.
### Scales Review Queue
The one program queue here that doesn't create anything new: approving a **Scales** item either **reprioritizes** or **cancels** an already-existing task, depending on what the Product Owner proposed — the card names which and shows the target task. Rejecting leaves the task exactly as it was. See [Board Programs](/docs/optional/board-programs) for how Scales' monthly portfolio review picks its items.
### Strategy Signals
Next to the CEO Approval Queue sits a **Strategy Signals** panel. It surfaces charter/strategy-drift notices from the strategy engine, which is **off by default** and armed by `ROBOCO_STRATEGY_ENGINE_ENABLED` (toggled from [Settings → Feature Flags](/docs/panel/settings)). With the engine off, the panel simply has nothing to show. See [the strategy engine](/docs/optional/strategy-engine).
## Blockers and activity
The bottom row pairs:
- **Active Blockers** — tasks currently sitting in `blocked`, so a stuck dependency doesn't go unnoticed.
- **Recent Activity** — a rolling feed of what the company did in the last 24 hours.
## Page-scoped refresh
The **refresh icon** in the top **Header** (present on every dashboard page, not just here) re-runs only the data queries the *current page* registered — not a full reload. Each page registers its own refetch callbacks on mount and unregisters them when you navigate away, so clicking it here refreshes the CEO overview, auditor flags, tasks, and recent-activity feed that make up the Command Center; the same button on a different page refreshes whatever that page shows instead. It **disables itself** when the active page has registered nothing to refresh, and spins while a refresh is in flight.
## Notifications bell
The bell in the top **Header** (present on every dashboard page, not just here) is your inbox for formal notifications — the acknowledgement-required signals PMs and the Board send. Its badge shows your unread count (capped at "9+"), and the popover itself now does real work rather than just linking through: a connection icon (Wifi / WifiOff) shows whether the live update stream is connected, a pending-acknowledgement count appears when you have one, and each listed notification carries its own **Mark Read** and, where required, **Acknowledge** button — plus a header-level **Mark all read**. A **View All Notifications** button at the bottom still takes you to the full [Notifications inbox](/docs/panel/journals-and-notifications#notifications) at `/notifications` for the complete list and its Unread/Pending tabs.
A companion, invisible piece — mounted once alongside the bell — toasts (and, if you've enabled sound in [Settings](/docs/panel/settings), chimes for) newly-arrived notifications off the same live stream, so you don't have to have the popover open to notice one land.
If the orchestrator isn't running, the Command Center (like every page) renders a clear offline state with a **Retry** — it detects a refused connection rather than spinning. Bring the stack up and retry; see [common issues](/docs/troubleshooting/common-issues).
## Next
→ [Tasks & Kanban](/docs/panel/tasks-and-kanban) to act on individual work, or [Agents & work sessions](/docs/panel/agents-and-work-sessions) to watch the workforce run. → [Social](/docs/panel/social) for the full X/video queues and history. → [Board Programs](/docs/optional/board-programs) for how every review queue on this page — Roadmap, Pest Control, Spackle, Scales, Mirror, and Dogfood — gets populated.
================================
URL: https://docs.roboco.tech/docs/panel/social
Title: Social
================================
import { Admonition } from "@/components/docs/admonition";
The **Social** page (`/social`) is where every held marketing draft lands for your decision — X posts and rendered videos alike — plus the on-demand video request and a unified history of everything already posted or rejected. It replaces what would otherwise be one queue per engine on the Command Center; only a compact summary card and count stay there, linking through to this page (see [Command Center](/docs/panel/command-center)).
## X Post Queue
Every held X (Twitter) draft — release announcements, mention replies, feature-spotlight posts, and three [Board Program](/docs/optional/board-programs) sources (Megaphone's editorial calendar, Barfly's adjacent-conversation replies, War Room's release campaigns) — lands here for your decision. For each one you can:
- **Edit** the body inline, up to the 280-character limit (a live counter warns you if you go over).
- **Approve & post** — posts the draft to X immediately, using your edited body if you changed it.
- **Reject** — cancels the draft with a recorded reason; nothing is posted.
Populated by the [X (Twitter) engine](/docs/optional/x-engine) and, once armed, Megaphone/Barfly/War Room — all off by default. A War Room draft carries a recommended `publish_after` time as guidance in the card; it's still a normal item in this queue, decided the same way as everything else here.
### Pipeline strip
Above the Video Post Queue, a strip lists every video **in flight** — authoring, rendering, or render-failed — each row a colored stage chip from authoring through render-failed (with the failure reason when there is one). A row for a draft that's actually reached your decision deep-links straight to it in the queue below. This is what makes an armed-but-quiet video engine legible: without it, a video mid-authoring or mid-render is invisible until it either lands in the queue or fails.
## Video Post Queue
Every held video draft lands here once the render loop finishes both cuts. Each card carries a **9:16 / 1:1 cut switch** so you can preview either before deciding, plus the per-platform captions:
- **Preview** — a live composition preview embeds the authoring task's actual HyperFrames HTML before you decide, not just the two rendered cuts.
- **Edit** the captions per platform.
- **Approve** — posts the selected cut to X (native video) and/or TikTok (inbox upload) under a heartbeat-renewed lock.
- **Reject** — cancels the draft with a recorded reason.
- **Re-render** — discards whatever's already rendered and queues a fresh render from the same composition, behind a confirm dialog. It's unconditional: available for any draft with a proposed composition regardless of whether the existing render succeeded, so you can redo a healthy render too, not just recover from a failed one. The same control also appears on an in-flight authoring task's row in the pipeline strip above, before rendering has even happened once.
Populated by the [video engine](/docs/optional/video-engine), off by default.
### Request a video
A **Request a video** button opens an on-demand dialog: a **project picker** (limited to projects with the video engine enabled for them), an occasion, a free-text brief, and which platforms to target. Submitting it opens a normal, assigned UX/UI authoring task directly for the chosen project, independent of the two automatic triggers (release publish, approved feature spotlight) — it works even with both of those switches off, as long as the video engine's master flag is armed. With no video-enabled project yet, the dialog says so instead of letting you submit, and points you at enabling the engine per-project on that project's settings page (Budget & Ops card). The rendered clip lands back in this same queue once the authoring task completes and rendering finishes.
## History
A collapsed-by-default **History** section, once expanded, lists every acted-on draft across both queues — posted or rejected — newest first, merged client-side from the X and video history endpoints. A posted X entry links out to the tweet; a posted video entry lists each platform's posted id (an X id links out, TikTok's inbox upload has no public URL so only the raw id shows); a rejected entry of either kind shows the recorded reason.
An empty Social page — zero drafts everywhere — doesn't tell you whether the X and video engines are off, or armed but simply idle. Check [Settings → Feature Flags](/docs/panel/settings) to see which is true.
## Next
→ [X (Twitter) engine](/docs/optional/x-engine) and [Video engine](/docs/optional/video-engine) for what populates these queues · [Board Programs](/docs/optional/board-programs) for Megaphone, Barfly, and War Room · [Command Center](/docs/panel/command-center) for the summary card that links here · [Settings](/docs/panel/settings) for credentials and feature flags.
================================
URL: https://docs.roboco.tech/docs/panel/tasks-and-kanban
Title: Tasks & Kanban
================================
import { Admonition } from "@/components/docs/admonition";
Tasks are the unit of work in RoboCo — nothing happens without one. The panel gives you three views of them, two of them living on one page: the **Tasks page** (`/tasks`) opens on a **List | Kanban** tab pair — List for searching and filtering everything in a table, Kanban for a pipeline-style board of where work is in flight — and the **Task Detail** page (`/tasks/[id]`) for the full state of one task plus your god-mode overrides.
## The Tasks table (`/tasks`)
The master list of every task. It gives you full-text search and multi-select filters across **status, team, task type, project, and product**, with sortable, paginated, expandable rows. All of that filter/sort/page state lives in the URL query string, so a filtered view is shareable and survives the back button. Expanding or collapsing a row is one of those URL-tracked bits of state, but it's excluded from scroll restoration specifically — toggling a row keeps you exactly where you were on the page instead of bouncing you back to the top.
The **Create Task** dialog lets you author a task by hand — title, acceptance criteria, project/product, type, and an optional **Budget (USD)** cap (see [Task & project cost budgets](/docs/optional/task-budgets); a `0` is rejected, it would silently block everything). If you'd rather describe a rough idea and have an agent read your code and draft a properly-formed task with acceptance criteria, use the conversational [Task Assistant](/docs/get-started/first-task) at `/prompter` instead.
## Task Detail (`/tasks/[id]`)
The heaviest operator surface in the panel. The header carries the task's metadata; the body is tabbed (notes, commits, acceptance criteria, and more).
### Getting around a task
A **parent-task breadcrumb** sits above the title whenever a task has one — click through to the parent, or follow the chain up one hop at a time. Next to it, **prev/next buttons** (with `Alt+ArrowLeft` / `Alt+ArrowRight` shortcuts) step through the current task's neighbors in whatever order you last saw them in the Tasks list — filtered and sorted the way you left it. Opening a task straight from a link, search result, or notification carries no such order, so both buttons render disabled with a tooltip explaining why rather than guessing.
The tab bar below the header highlights the **active tab** and persists your selected tab in the URL query string, so a shared task link or the back button returns you to the same tab.
### Collapsible sections
Description, per-role Notes, Plan, Progress, and Acceptance Criteria each live in their own independently collapsible card, so a task with a long history doesn't force continuous scrolling to reach the part you actually want. A section starts collapsed or open based on how much is in it — long progress/checkpoint history and long criteria lists default **closed**, short ones stay **open** — so nothing you'd normally see at a glance disappears, only the sections that would otherwise dominate the page. Progress updates and notes each show an inline **absolute timestamp** next to the relative one ("3 hours ago"), falling back to the task's creation time when an entry carries none of its own.
When [architectural conventions](/docs/optional/conventions) attach a `## Constraints` section to a task, its card gets a distinct amber accent, tint, and lock icon — a visual cue that this content is read-only and system-authored, not something the task's creator wrote.
### Clickable branch and PR
When a task has a branch or a pull request, the git badges are **live links** into your repository's web host — the branch jumps to `…/tree/` and the PR to `…/pull/`, derived from the project's git URL. If the URL can't be parsed into a web link, it falls back to a plain label rather than a broken link, so you always see *something* useful.
### Per-role note sections
The **Notes** tab carries a separate, editable section for each role that touches a task, so the handoff trail is structured rather than a single free-text blob:
| Section | Written by |
|---------|-----------|
| **Developer Notes** | the dev (`dev_notes`) |
| **Documenter Notes** | the documenter (`doc_notes`) |
| **QA Notes** | QA (`qa_notes`) — with a pass/fail verdict pill from `qa_verified` |
| **PR Reviewer Notes** | the PR reviewer (`pr_reviewer_notes`) |
| **Auditor Notes** | the Auditor (`auditor_notes`) |
Empty sections still render so you can see what's expected — and add a note yourself if you need to. Each written section also shows a **written-at timestamp** next to its heading, so you can read the hand-off trail in order.
### The bounced chip
A task the header shows with an amber **bounced x*N*** chip (hover for the count and what it means) has been sent back to `needs_revision` *N* times — by QA, the PR-review gate, a PM, or you. It's hidden entirely on a task that's never bounced, so its absence is itself a signal. Click through to the Findings tab below for what specifically drove each round.
### Findings and Collision tabs
Two tabs round out the task detail page, alongside Overview, Plan, Progress, Commits, Notes, and Deps:
- **Findings** — the revision-findings ledger: every structured finding a QA fail, a PR-gate fail, a PM `request_changes`, or a CEO rejection recorded against this task. Findings are grouped into one **collapsible section per review round** — the newest round starts expanded, older rounds start collapsed — each carrying a badge for who raised it (QA / PR Review / PM / CEO) and a count of how many findings in that round are still open. Each finding card shows its severity and status (open / addressed / verified / waived, each with an explanatory tooltip), the expected-vs-actual text, the suggested fix, and — collapsed by default — the reviewer's raw evidence. When `file` is a real repo-relative path, the card also shows an **inline code snippet** of the surrounding lines pulled live from that file at the task's branch; the handful of legacy findings whose `file` field carries prose instead (a PR reference, from before the shape was enforced server-side) render their metadata plainly rather than attempting a doomed fetch. A row of per-origin summary badges (e.g. "QA: 1 open · 3 closed") sits above the rounds. An empty task just says no findings have been recorded yet.
- **Collision** — the sibling collision surface: this task's own declared file globs / migration flag / shared-surface flag, plus every same-parent sibling whose declared surface overlaps it — status, branch, PR number, and sequence badges per sibling, with the overlapping globs highlighted. A sibling that actually touched files it never declared shows a **drift** callout. Root and standalone tasks (no parent) render an explicit "no collision map" state instead, since they have no siblings to collide with.
See [the company's review-and-quality machinery](/docs/company) for how findings get raised in the first place, and [Sequence ordering](#sequence-ordering) below for what the collision map's sequencing badges mean.
### CEO god-mode
The lifecycle is normally enforced role-by-role at the [gateway](/docs/company/agent-gateway), but *you* are the CEO and can override any of it from this page. The action set covers the whole lifecycle: claim, start, pause, resume, block, unblock, verify, submit-qa, pass-qa / fail-qa, docs-complete, submit-pm-review, approve-and-merge, escalate-to-ceo, ceo-approve / ceo-reject, and cancel. You can also run git directly — **create a branch, create a PR, merge a PR** — and those operations are performed as agent id **`ceo`**.
This page also exposes **Approve & Start** for a board-reviewed pending task and **Re-draft with board feedback**, which routes back to the Task Assistant (`/prompter?redraft=`) — including for a MegaTask's branchless umbrella, which recovers its multi-repo scope from its live root-subtasks. See [MegaTask](/docs/company/megatask#board-review-and-redraft) for the full redraft loop.
The state-changing actions — pass/fail QA, approvals, rejections, cancellation — open a dialog that requires you to type a note before they go through. That note is written permanently to the task's history. God-mode is powerful by design; it is never silent.
See [the task lifecycle](/docs/company/task-lifecycle) for what each transition means and [the merge model](/docs/company/merge-model) for how PRs flow up to master.
## The Kanban tab
Switch the Tasks page to its **Kanban** tab (`?tab=kanban`) for a swim-lane board of the delivery pipeline, switched with the `?view=` query param into four boards:
| View | `?view=` | Shows |
|------|----------|-------|
| **Developer** | `dev` | tasks in the development states |
| **QA** | `qa` | tasks awaiting / in QA review |
| **PR Review** | `pr-review` | assembled PRs at the in-path review gate |
| **PM** | `pm` | tasks awaiting PM review and merge |
Each board is a read-at-a-glance view of where work sits in the [lifecycle](/docs/company/task-lifecycle). Switching the top-level List/Kanban tab, or a kanban sub-tab, updates the URL, so a specific board is shareable — and every filter you set on the List tab (search, status, team, task type, project, product) carries straight over to Kanban and back, since both tabs read and write the same URL-driven filter state. Kanban's own team picker is single-select, unlike the List tab's multi-select: when the List tab has exactly one team selected, Kanban's dropdown reflects it; zero or several selected teams show as "All Teams" there instead. Drag-and-drop and the mobile single-column board layout are unchanged.
The standalone `/kanban` route (and `/kanban?view=qa` and its siblings) now redirects straight to `/tasks?tab=kanban` (and `&view=qa`) the moment it loads, so an old bookmark or link never breaks. The sidebar's own **Kanban** entry is gone — reach the board from the Tasks page's tab bar instead.
## Project and product progress signals
v0.25.0 adds read-only progress signals to the tables you scan while triaging work, so you can spot a blocked or overloaded repo without opening it.
### Project table task counts
The **Projects** table carries a **Tasks** column on each row: a status dot followed by **done / active / blocked** counts for that project's tasks. The dot mirrors the same three numbers — a row with active or blocked work shows an at-risk signal, while a row that's entirely done reads clean. This is a quick health read; click the project name to open its full task list.
### Product table cell mappings
The **Products** table maps each delivery cell to the project it builds for that product. Each row shows its cell-to-project assignments in the **Cells** column; a cell with no project assigned reads **Unmapped**. The **Progress** column rolls up the same done / active / blocked counts across every mapped project, using the same at-risk status dot as the Projects table, so a multi-repo product surfaces its health in one glance.
### CI-watch badge
When a project has opted into [multi-repo CI-watch](/docs/optional/autonomous-maintenance), its row in the Projects table shows a **CI-Watch** badge under the project name. The badge means the project's default-branch CI is being watched and a fix task will be opened automatically if it goes red. It is an opt-in indicator, not a real-time CI status light.
For registering projects, editing the CI-watch opt-in, and setting the monitored workflow file, see [Workstation](/docs/panel/projects-and-products).
## Sequence ordering
A PM can stamp sibling subtasks with a `sequence` number — lower runs first, ties run in parallel — and it's a hard rule, not a suggestion: **a lower-sequence sibling has to reach a terminal state before a higher-sequence one can be claimed**, whether or not any dependency edge connects them. What you'll actually see on a board: a task that's technically `pending` (or back in `needs_revision`) but not yet claimable sits waiting in its lane while an earlier-sequence sibling is still in flight, and a claim attempt on it fails naming the sibling that's holding it — `"" (sequence N)`. This is the same mechanism a MegaTask uses to pace its collision-derived waves (see [MegaTask](/docs/company/megatask#how-the-batch-is-sequenced)), applied generally to any parent's subtasks.
A sequence-held claim surfaces as its own reason instead of a bare "concurrent transition" error, so it's obvious at a glance that the task is waiting its turn rather than genuinely broken. The dispatcher also no longer offers a sequence-held task to an agent it would only have to refuse — the same check the claim gate applies now runs before a task is handed out in the first place.
## Next
→ [Agents & work sessions](/docs/panel/agents-and-work-sessions) to see who's working these tasks, or [Git](/docs/panel/git) to inspect the branches and PRs they produce.
================================
URL: https://docs.roboco.tech/docs/panel/agents-and-work-sessions
Title: Agents & Work Sessions
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
The **Agents** page (`/agents`) is your view of the whole workforce — who's running, who's idle, who's stuck, what each one is costing in tokens, what they're saying to each other, and what they've learned. It's tabbed: **Fleet** (the roster, default), **Conversations** (the agent-to-agent message stream), and **Journals** (each agent's private reflection log) — the active tab is in the URL as `?tab=`. The git side of an agent's work — its branch, commits, and PR — lives on the **Work Sessions** tab of the [Git page](/docs/panel/git), not here.
`/a2a` redirects straight into the Conversations tab and `/journals` redirects straight into the Journals tab, so a bookmark or an old link never breaks. Nothing about either surface itself changed — same switchboard/transcripts/New DM flow, same agent picker/filterable entry list — they just live here now, and the sidebar's separate A2A and Journals entries are gone.
## Fleet tab
### The roster (`/agents`)
Agents are grouped into a dense, section-by-section grid that mirrors the org chart, each section header carrying a count badge for how many agents sit in it:
- **Leadership** — the Board (Product Owner, Head of Marketing, Auditor) folded together with the Main PM, so a lone Main PM card never wastes a full row on its own.
- **Backend Cell**, **Frontend Cell**, **UX/UI Cell**
- **Support** — the CEO-direct helpers (Intake/Prompter, Secretary, the root PR Reviewer), shown only when present
Cards auto-fill the available width (a phone gets one column, a wide monitor gets many) at a consistent card size, rather than a fixed grid that wastes space or crowds cards on different screens.
The **Total Agents** stat above the grid is the full roster size — every agent defined for the company — not how many containers the orchestrator currently has running. It stays a truthful headcount even when most of the roster isn't spawned at the moment you look.
Each card shows the agent's **live state** merged with its **token usage**, so health and cost sit together. States you'll see:
| State | Meaning |
|-------|---------|
| `active` | the agent is alive and working |
| `idle` | spawned but with no work in hand |
| `waiting_long` | blocked, waiting on human input or an external resolution |
| `error` | the container hit errors (the card shows the error count) |
A **Waiting Agents** alert surfaces any agent stuck in `waiting_long` at the top of the page, so a blocked agent doesn't sit unnoticed.
A card for any agent that can hold a direct conversation also carries a **DM button** (a message-square icon next to the actions menu) — click it and you land on the Conversations tab with the New DM dialog already open and that agent pre-selected, so starting a conversation doesn't mean hunting for the right name in a picker afterward. The **Intake** and **Secretary** cards carry the same chat icon, but since neither is A2A-DMable it instead navigates straight to their own dedicated screen (Intake → `/prompter`, Secretary → `/business?tab=secretary`) rather than opening a New DM dialog that would go nowhere. The icon is simply absent from a card for a role that can hold no conversation at all (yourself) — see [Starting a new conversation](#starting-a-new-conversation) below for the full picture.
### Agent detail (`/agents/[id]`)
Open an agent to control it and watch it think. The page gives you:
- **Spawn** — bring the agent's container up.
- **Stop** (graceful) and **Force Stop** — wind it down cleanly or kill it immediately.
- **Resolve Wait** — when an agent is in `waiting_long`, this dialog is how you hand it the input or decision it's blocked on.
- **Live stream viewer** — while the agent is active, a viewer streams its reasoning in real time over the agent WebSocket, so you can literally watch it work.
Above all of that sits an **activity panel** — a token-usage sparkline for the last 7 days next to a merged timeline of the agent's most recent work sessions and journal entries. It's drawn from history (usage rollups, work sessions, journals), not live status, so it renders the same whether the agent is running or not. The page header is also DB-backed: open the detail page for a **stopped** or unreachable agent and the header, sparkline, and timeline still render, with a clear "Not running" banner in place of the live stream instead of the page failing to load.
If an agent is crash-looping or burning tokens, **Force Stop** from its detail page is the fastest way to halt it. For provider rate-limits and overloads you don't need to intervene — those *park and auto-resume*; you'll see an amber banner instead (see [resilience](/docs/models/resilience)).
Per-agent token spend rolls up into the [Metrics](/docs/panel/metrics) page for cost analysis, and what agents learn is on this page's own [Journals tab](#journals-tab).
## Conversations tab
Agents coordinate primarily through task state, but they also message each other **directly** — a task-scoped, agent-to-agent (A2A) back-channel used for hand-offs and quick questions between a specific pair. The Conversations tab is your window into all of it: you watch every direct message as it happens, and you can chime in yourself.
A conversation-first, three-region layout puts the whole surface in view at once: a roster on the left, the open conversation in the middle, and read-only context on the right.
### Roster: switchboard or list
The left pane switches between two views of the direct-message activity:
- **Switchboard** (the default) is an org-chart of **pair cards** — one card for every agent pair that's allowed to talk directly, each avatar colored by the agent's **team**. Cards are grouped into sections (each cell, the PM chain, the Board, and cross-team), and within a section the pairs with conversation history sort first. A card flashes to full intensity the moment a new message flows between that pair, then fades back to baseline — a live cue for who's talking to whom, not just a static list.
- **Conversations** is the classic flat list of conversations, most-recent-first — the compact fallback (and the mobile default).
A **filter bar** above the roster narrows either view: **Agent** (multi-select) always applies; **Task** (by id fragment, or "no linked task"), **Status** (Active / Archived), and a **date range** additionally narrow the Conversations list (the Switchboard has no per-conversation dimension to filter those three against, since a pair card can represent zero or many conversations). Active filters show as removable chips with a Clear-all.
Clicking a pair card or a list row opens that conversation's transcript in the middle pane. A pair that has never talked shows an explicit "no A2A yet" state instead of an empty transcript.
Every A2A conversation has exactly **two participants** — there are no group threads here; the switchboard is a grid of two-agent conversations. Agent-to-agent conversations are also task-scoped, attached to the task the exchange was about. Your own conversations with an agent (opened via New DM, below) are the one exception: they carry no linked task, since they aren't about any one piece of work.
### Stream: the open conversation
The middle pane is the transcript plus the reply composer, with a **connection badge** in the page header showing which of the socket's four states you're in — `connecting`, `connected`, `disconnected`, or `reconnecting` — each rendered distinctly, so a live-but-quiet conversation never reads the same as a stream that's actually the problem. If the socket drops mid-session, a dismissible banner appears above the transcript itself (not a full-page takeover) and clears automatically once the connection state moves on.
### Context: who's talking, on what task
An optional third pane (visible at wider viewports, collapsible) shows read-only detail on the open conversation or pair: participant identity cards and a summary of the linked task, so you don't have to leave the page to see what the exchange is about.
### Starting a new conversation
A **New DM** button in the page header opens a fresh 1:1 with any agent — pick who from a picker and write the first message; sending both opens (or reopens) your own direct conversation with them and delivers it in one step. The picker leaves out only agents that couldn't do anything with a DM anyway: yourself, and the **Intake** and **Secretary** seats (human-only roles with no agent-comms verbs — they get their own chat screens instead, see below). The **Auditor** and **PR Reviewer** *are* in the picker — both now carry the tool to read and reply in-thread, so you can reach either mid-flight the same way you'd reach any other agent. This is the one surface that *creates* a conversation — the switchboard and the classic list only ever show ones that already exist.
The Fleet tab's per-card **DM button** (above) is a shortcut into this same dialog: it jumps you here with the New DM dialog already open and that agent pre-selected. The deep link is re-validated against the live roster and the same exclusion list before anything is pre-selected — an unrecognized or excluded target just opens the dialog empty instead of silently pre-picking a role that couldn't answer anyway.
If the agent you DM is currently offline, sending wakes it: the message queues as a notification that spawns the agent, which reads it and replies in the same thread once it's up.
### Chiming in
Below an open transcript is a composer — which one depends on what kind of conversation is open:
- **Your own conversations** (opened via New DM, or any thread where you're one of the two participants) get a **dedicated composer** that sends straight into that thread as you, no addressing needed — there's only one other participant.
- **Watched conversations** — an agent-to-agent pair you're observing, not participating in — use the reply composer: you reply **as the CEO**, addressing one of the two participants, on that conversation's linked task. Your message lands in **your own** conversation with that agent, not spliced into the two agents' transcript you were reading. A conversation with no linked task is read-only here, since the reply route requires one.
Watching and replying are **CEO-only**. The page is backed by CEO-scoped admin endpoints that read across every pair regardless of who's a party to it (`GET /api/a2a/chat/admin/pairs`, `.../chat/admin/conversations`, `.../messages`) and let you post into one (`POST /api/a2a/chat/admin/conversations/{id}/reply`).
### Agent → CEO is reply-only and budgeted
Talking to the CEO is not a channel an agent can open at will. The rule is asymmetric:
- **CEO → agent is unrestricted.** You can message any agent, any time — from the reply composer, or by opening a fresh thread with New DM.
- **Agent → CEO is reply-only and budgeted.** An agent can only message you **inside a conversation you opened**, and only **up to your own message count** in it — so an agent gets at most **one reply per message you send**, and can never *initiate* contact with you, whether that conversation started from a New DM or from you replying into a watched thread. Once it has answered your last message, it has to wait for you to speak again before it can send more.
This keeps your inbox a place *you* drive: agents answer when spoken to, they don't cold-call.
### How it stays live
A2A message] --> B[A2AService persists it]
B --> C[Publishes A2A_MESSAGE_SENT]
C --> D[websocket_bridge]
D --> E[a2a.message frame
on /ws/system]
E --> F[Conversations tab:
card pulses + refetch]`} />
Every persisted A2A message is announced on the operator stream (`/ws/system`) as an **`a2a.message`** frame. The frame carries only a capped excerpt of the body, so the page treats it as a *signal*: it pulses the matching switchboard card and re-fetches the affected conversation over REST, which stays the source of truth for the full text. That means the live view never lags behind, and it also never depends on the socket being up — with the stream down it falls back to a manual **Refresh**.
See [WebSockets](/docs/api/websockets) for the `a2a.message` / `A2A_MESSAGE_SENT` frame in the full event table.
## Journals tab
`/agents?tab=journals` is the per-agent reflection log, lifted in from the old standalone `/journals` page. Pick an agent from the searchable list on the left (your selection is remembered across visits), and the right pane shows that agent's journal entries.
Journals are where an agent records **reflections, decisions, and learnings** as it works — distinct from its task-scoped [A2A messages](#conversations-tab) on the Conversations tab next door. Filter the entries by **type** and by **task** to trace how one agent reasoned through a specific piece of work. A single entry still opens at `/journals/{entry}` — that detail route is unchanged.
Each entry card that's linked to a task shows a **Task #\** badge — click it to jump straight to that [task's detail page](/docs/panel/tasks-and-kanban) — sitting next to a **copy button** that puts the full task UUID on your clipboard, so you can paste it elsewhere without opening the task first.
This is the closest you get to an agent's "why." When a piece of work went a surprising way, the journal is where the agent explains its thinking — useful both for trust and for [feeding the knowledge base](/docs/panel/knowledge-base), which indexes journals as a retrievable source.
The Auditor's read access spans every task, every piece of evidence, and these journals and your notifications — with no participation of its own. It has no `dm`, no `read_a2a`; it can only leave a private note and read evidence. See [the Auditor](/docs/panel/auditor) for what it does with that access.
## Next
→ [Git](/docs/panel/git) for the Work Sessions ledger and to operate on branches directly · [Tasks & Kanban](/docs/panel/tasks-and-kanban) to see the work the agents are running · [Notifications](/docs/panel/journals-and-notifications) for the rest of the company's audit trail · [Org & roles](/docs/company/org-and-roles) for who's allowed to talk to whom.
================================
URL: https://docs.roboco.tech/docs/panel/git
Title: Git
================================
import { Admonition } from "@/components/docs/admonition";
The **Git** page (`/git`) is a real working git client over any of your registered projects, plus the ledger of every agent work session running against them. It's tabbed: **Repository** and **Work Sessions** (the active tab is in the URL as `?tab=`).
## Repository
The Repository tab is where you pick a project (and optionally a task), browse the live state of its workspace — status, branches, commit log, and diffs — and run git operations directly from the panel. It's how you inspect what the agents are doing in git, and step in by hand when you need to.
### Picking a project
Choose a project from the dropdown to load its repository. The selection is held in the URL, so a given project's git view is shareable and back-button-safe. Until you pick one, the page prompts you to choose. You can also scope to a specific task.
Git operations run server-side against the agent workspaces, so this page needs the orchestrator running. With the backend down it shows an offline state with a retry rather than failing silently.
### Browsing the repository
Once a project is loaded you get live, refreshable views:
- **Status** — staged and unstaged changes in the workspace.
- **Branches** — the branch list (including remotes).
- **Log** — the recent commit history.
- **Diff** — the staged/unstaged diff viewer for the working tree.
A **Refresh** re-pulls status, log, and branches together.
### Operations you can run
The page wires up the full set of git operations against the selected project:
| Operation | What it does |
|-----------|--------------|
| **Commit** | commit staged changes (returns the new commit hash) |
| **Push** | push the current branch to the remote |
| **Create branch** | cut a new hierarchical branch for a task (by branch type) |
| **Checkout** | switch to an existing branch |
| **Create PR** | open a pull request (returns the PR number) |
| **Merge PR** | merge a PR into its target branch |
| **Pull** | pull the current branch from the remote |
| **Fetch** | fetch from the remote |
| **Rebase** | rebase the current branch onto a target branch |
| **Clean Up Stale Branches** | sweep the project's completed/cancelled tasks and delete their spent branches, remote and local |
Each action confirms with a toast on success (or a clear failure message) and refreshes the relevant view. **Rebase** refuses onto `master`/`main` or any of the project's own declared [protected branches](/docs/get-started/first-project#protecting-extra-branches) — the same floor a task's own force-push sync (`sync_branch`) respects.
These operations act on the real workspace clone and your real remote — a merge here merges for real. The same branch/PR/merge actions are also available, task-scoped, from the [Task Detail page](./tasks-and-kanban.md#ceo-god-mode), where they run as agent id `ceo`. Remember that **only the CEO merges to master**; see [the merge model](/docs/company/merge-model).
Git authentication uses the project's encrypted git token, which you set when you [register the project](../get-started/first-project.md#the-git-token) — the panel never holds or shows the token. The same operations table above works identically whether the project's [forge](../get-started/first-project.md#choosing-a-forge) is GitHub, Gitea, or GitLab.
### Cleaning up stale branches
Every task branch an agent cuts eventually outlives its purpose — the task completes or gets cancelled, but the branch itself sits on the remote (and in every agent's local clone) forever unless something removes it. **Clean Up Stale Branches**, in the actions panel, sweeps a project's completed/cancelled tasks and deletes their branches — remote and local — in one confirmed action.
- **PM and CEO only.** Cell PM, Main PM, and CEO can run it; other roles are refused.
- **Never touches the default branch, an operator-declared [protected branch](/docs/get-started/first-project#protecting-extra-branches), or an [environment-ladder](/docs/optional/env-branches) rung.** A branch still serving as a head/qa/stag/prod rung — or named in the project's Protected Branches list — is skipped even if some task happens to share its name.
- **Skips a branch with open dependent PRs** — one still serving as another PR's base is left alone rather than stranding that in-flight work.
- **Capped per run, with a resume cursor.** A sweep processes up to 200 candidate branches at a time; if a project has more, the confirmation toast says so and clicking **Clean Up Stale Branches** again continues exactly where the last run stopped, instead of re-scanning from the top.
The button opens a confirmation dialog before it deletes anything, and the toast that follows reports what happened: how many remote branches were deleted, how many local ones, how many were skipped (no resolvable local clone to clean up), and how many hit an error.
Finished tasks also clean up after themselves automatically, with no button required: on completion or cancellation, a task's local branch ref and its video-render preview files are removed from the assignee's clone as part of the normal lifecycle transition — so a long-running clone doesn't quietly accumulate cruft from every task that ever ran through it.
## Work Sessions
The Work Sessions tab is the **read-only ledger** of active agent work sessions — a **WorkSession** links an agent's work to a task and tracks its git footprint: branch name, base and target branches, the PR number/URL, and merge status.
- **Search and filter** — search by branch name, and narrow by status (state lives in the URL, same as the rest of the panel).
- **Active Session Starts** — a chart of when today's active sessions began, bucketed by hour if they all fall within a 36-hour span, otherwise by day, so you can see whether work is clustering in bursts or spread out.
This tab shows currently **active** work sessions — it isn't a historical archive of every session a project has ever had. A session that finished (merged or abandoned) drops off the list.
For the lifecycle of a branch from cut to merge, see [the merge model](/docs/company/merge-model).
## Next
→ [Agents & work sessions](/docs/panel/agents-and-work-sessions) to tie sessions back to the agents that ran them, or [Workstation](/docs/panel/projects-and-products) to manage the repositories themselves.
================================
URL: https://docs.roboco.tech/docs/panel/knowledge-base
Title: Knowledge Base
================================
import { Admonition } from "@/components/docs/admonition";
The **Knowledge Base** page (`/knowledge-base`) is your window into RoboCo's in-house RAG — the pgvector-backed retrieval engine the agents use to find prior context. From here you search the indexes, ask synthesized questions and get answers with citations, talk to the mentor, browse by category, and (in the Admin tab) reindex or clear the indexes. As of v0.25.0 the `docs/map/` directory is re-indexed alongside `docs/rag/` and is searchable through `roboco_kb_search`. The page is organized into five tabs, with the active tab held in the URL.
## Search
Free-text search across the indexes, with an optional filter to scope the query to specific index types. This is the raw retrieval view — it returns the matching chunks so you can see exactly what the agents would find.
## Ask
The RAG question-and-answer surface. You ask a question in natural language and get a **synthesized answer with citations** back to the source documents, rather than a raw chunk list. This is the layer that turns the indexes into an answer.
## Mentor
A mentor chat — the same `roboco_ask_mentor` capability the agents reach through their gateway, exposed to you conversationally for asking about the codebase and accumulated knowledge.
## Browse
Browse the indexed content **by category** rather than by query, when you want to see what's in an index instead of searching for something specific.
## Admin
The control surface for the indexes themselves. The KB is split into several index types — Documentation, Conversations, Agent Journals, Error Solutions, Standards, Decisions, Code Reviews, Learnings, and the code map under `docs/map/` — and the Admin tab shows **per-index stats** and lets you maintain them:
| Action | Effect |
|--------|--------|
| **Reindex all** | rebuild every index from source, including `docs/rag/` and `docs/map/` (force) |
| **Refresh** *(per index)* | re-pull and re-embed that one index |
| **Delete** *(per index)* | clear that index's contents |
Clearing an index removes its embeddings, and a full reindex re-embeds everything from scratch — both take real work and real Ollama time. Delete is guarded behind a confirmation dialog. Treat the Admin tab as a maintenance surface, not a daily one.
## It depends on Ollama
The whole page — search, ask, mentor, and especially reindexing — runs on the embedding model served by **Ollama** (`qwen3-embedding:0.6b`). If Ollama isn't healthy, queries return nothing useful and reindexing can't embed.
A common first-run symptom is an empty or failing KB while Ollama is still pulling models, or when the embedding endpoint is unreachable. Check that the `ollama` service is up and the model is pulled — see [common issues](/docs/troubleshooting/common-issues). The RAG engine and its configuration are covered under [deployment](/docs/deploy/deployment).
## Next
→ [Journals](/docs/panel/agents-and-work-sessions#journals-tab) — the raw journals that feed several of these indexes — or [the Auditor](/docs/panel/auditor) for quality oversight.
================================
URL: https://docs.roboco.tech/docs/panel/projects-and-products
Title: Workstation
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
The **Workstation** page (`/workstation`) is the single sidebar entry for what the company builds and who builds it. It's tabbed — **Products** first, **Projects** second (the active tab is in the URL as `?tab=`) — rather than two separate sidebar entries. **Projects** are the git repositories RoboCo is allowed to touch. **Products** group several repositories into one shipping unit and route each delivery cell to the repo it owns. You only need Products when a single thing you ship spans more than one cell.
Both URLs redirect straight into the matching Workstation tab, so a bookmark or an old link never breaks. Nothing else about either feature changed — same fields, same tables, same behavior, just presented as tabs on one page.
Both tabs default to a **card-grid view** — one box per row, with the same status/badge visual language as the [Agents page](/docs/panel/agents-and-work-sessions)'s roster — with a **Cards / Table** toggle in the header to switch to the classic table. The choice is remembered independently per tab, per browser. Card view adds a **sort** control (plus an ascending/descending toggle) that reorders the cards client-side; the table view is untouched and keeps its own row order regardless of the sort picked in card view.
## Products
A **product** maps each delivery cell to the project (repository) it builds for that product. Use it when one deliverable spans multiple cells — a Backend repo and a Frontend repo, say, that ship together.
The page is a straightforward table plus a **New** button. A product carries a name and a set of cell-to-project assignments: Backend → repo A, Frontend → repo B, UX/UI → repo C. A cell with no assignment simply doesn't participate in that product — the table's **Cells** column reads "Unmapped" when none are set. Alongside it, a **Progress** column rolls up done / active / blocked task counts across every one of the product's mapped projects, with the same at-risk status dot the Projects table uses.
In card view, each product's card carries the same Cells mapping and Progress rollup as its table row, plus a pencil shortcut straight into the edit dialog. Sort by **Name** or by **Cell count** (how many cells the product maps).
### Why the mapping matters
When the Main PM decomposes a product into work and fans it out to the cells, the branch and PR structure follows the mapping. **The Main PM cuts one integration branch per distinct repository in the product.** A cell's work lands on its repo's integration branch; the cell PM opens the cell→root PR within that repo, and the Main PM opens the root→master PR per repo. Two cells assigned the *same* repo share one integration branch; two cells on *different* repos get one each.
B[Backend cell → repo A]
P --> F[Frontend cell → repo B]
A1[repo A: integration branch] --> MA[root→master PR · repo A]
B1[repo B: integration branch] --> MB[root→master PR · repo B]
B --> A1
F --> B1`} />
If everything you're shipping lives in one repo, you don't need a product at all — register the project and hand the Task Assistant or the Create Task dialog that project directly. Products exist purely to coordinate a multi-repo, multi-cell deliverable.
For how branches assemble and merge once the cells are building, see the [merge model](/docs/company/merge-model). For the lifecycle a single task moves through, see the [task lifecycle](/docs/company/task-lifecycle).
## Projects
A project is one git repository plus the configuration that tells the company how to build and check it. The Projects page is where you register, search, and edit them.
- **New** opens the create dialog — name, slug, git URL, [forge](../get-started/first-project.md#choosing-a-forge) (GitHub, Gitea, or GitLab), git token, assigned cell, default branch, and optional per-project gate commands (including the codegen command, alongside test/lint/format/typecheck/build/quality).
- The list supports **search**, a **cell filter**, and a **show-inactive** toggle so retired repos stay out of the way without being deleted.
- **Edit** now opens a slim **quick-edit dialog** — just name, assigned cell, and active — with a **Full settings →** link out to the project's own settings page for everything else.
### The project settings page (`/projects/[id]/settings`)
Every other project field lives on its own full page now, one **card per concern** instead of one long scrolling dialog:
| Card | What it holds |
|------|---------------|
| **Identity** | name, git URL, [forge](../get-started/first-project.md#choosing-a-forge) selection, and the **GitHub App** binding — the same **Select repo** picker the New Project dialog offers, once a GitHub App is configured on the Settings page (see [Registering a project](/docs/get-started/first-project#github-app-authentication)). The **slug** shows here too but is immutable — set once at creation, since it composes every agent's workspace path and every branch name. |
| **Git Auth** | the encrypted git token — write-only, never re-displayed once saved; clear it or replace it. |
| **Placement** | assigned cell, default branch, the **Protected Branches** chips editor (extra branches, beyond the always-protected `master`/`main`, the fleet won't rebase onto or force-push over), and the active toggle. |
| **Environments** | the environment-ladder editor — add, reorder, or remove named rungs mapping to branches (see [Environment branches](/docs/optional/env-branches)). |
| **CI/CD Commands** | test / lint / format / typecheck / build / quality-gate / codegen commands. |
| **Budget & Ops** | the **Monthly Budget (USD)** field (see [Task & project cost budgets](/docs/optional/task-budgets)), CI-watch opt-in plus its workflow file, the video engine per-project opt-in, the dependency-update bot's command and lockfile paths (see [Autonomous maintenance](/docs/optional/autonomous-maintenance)), and two [Board Programs](/docs/optional/board-programs) checkbox lists — **participates in** (Pest Control, Spackle, Mirror, Dogfood: opts this project into that program's cycles) and **excluded from** (org-scoped programs; today only Roadmap's exclusion is actually enforced). |
| **Sandbox** | the sandbox services opt-in (Postgres / Redis / Mongo) and, per service, which extensions or modules it should carry. |
Each card saves independently — editing one doesn't require touching or re-submitting the others. **Conventions** (see [Architectural conventions](/docs/optional/conventions)) sits alongside these as its own page-level tab, at its natural full width rather than squeezed into a card.
The table itself carries a **Tasks** column (a status dot plus done / active / blocked counts across the project's tasks, so an at-risk repo is visible without opening it) and, inline under a project's name, a **CI-Watch** badge whenever that project has opted into [multi-repo CI-watch](/docs/optional/autonomous-maintenance).
A project's card mirrors its table row exactly — the same cell badge, active/inactive status badge, git-token badge, and CI-Watch badge, plus the Tasks done/active/blocked counts — so nothing is card-only or table-only. Sort by **Name** or by **Cell**.
The field-by-field detail — what each field means, the token scopes you need, the encryption guarantee, and the default-branch gotcha — lives in [Register your first project](/docs/get-started/first-project). Read that page before you create a repo; this page doesn't repeat it.
The single biggest lever on output quality is pointing a project's `quality_command` and per-step commands at the *real* checks you'd run locally. The company then gates itself the way you would. Details in [Gate commands](../get-started/first-project.md#gate-commands).
## Next
→ [Tasks & Kanban](/docs/panel/tasks-and-kanban) to author and track the work · [Git](/docs/panel/git) to watch the branches and PRs land · [Board Programs](/docs/optional/board-programs) for what the participation checkboxes on the Budget & Ops card actually opt a project into.
================================
URL: https://docs.roboco.tech/docs/panel/journals-and-notifications
Title: Notifications
================================
import { Admonition } from "@/components/docs/admonition";
Your acknowledgment-required inbox — the formal signals that need you to act, as distinct from the company's ambient record of *what was learned.* This is a read surface for you as operator; the work happens elsewhere, and this records it. For an agent's private reflections, see the [Agents page's Journals tab](/docs/panel/agents-and-work-sessions#journals-tab); for the live agent-to-agent traffic itself, see its [Conversations tab](/docs/panel/agents-and-work-sessions#conversations-tab).
## Header notifications bell
The bell icon in the top header is a compact inbox that follows you across every panel page. Its badge shows your unread count, capped at "9+" once you cross nine (the exact count is still available in the badge's tooltip/aria-label), and the popover exposes the same formal signals as the full [Notifications inbox](#notifications):
- **Live-stream indicator** — a connection icon shows whether the notification live stream is currently connected or offline.
- **Per-item actions** — each listed notification has its own **Mark Read** and, when required, **Acknowledge** button.
- **Mark all read** — a header-level button clears the whole unread pile in one click.
- **View All Notifications** — jumps to the full Notifications page at `/notifications` for the complete Unread / Pending / All tabs.
A companion listener, mounted once alongside the bell, toasts (and optionally chimes, if sound is enabled in [Settings](/docs/panel/settings)) newly-arrived notifications off the same live stream, so you don't need the popover open to notice one land.
## Notifications
`/notifications` is your acknowledgment-required inbox. Where journals are an ambient record you browse, notifications are formal signals — sent by PMs and the Board — that may demand a response.
- Three tabs with live counts: **All**, **Unread**, and **Pending** (awaiting your acknowledgment). The page opens on Unread, the most actionable view.
- Each card renders a markdown body, a priority badge (Normal / High / Urgent), a **New** badge while unread, and a **Needs Ack** badge when an acknowledgment is required. The **From:** line shows the sending agent's actual name (e.g. "Backend Dev 1"), not its raw id — and any task a notification's subject or body refers to reads as the task's own title (falling back to a short `#id8` only when no title is available), not a raw UUID either.
- Actions per card: **Mark Read** and, where required, **Acknowledge**. **Mark All Read** clears the unread pile in one click.
A **Needs Ack** notification stays in your Pending tab until you acknowledge it. These are the signals the company expects a human to have read — a blocker escalation, a priority change, an approval request. Acknowledging records that you saw it; it is not the same as approving a task (that happens on the [task detail page](/docs/panel/tasks-and-kanban) or the Command Center approval queue).
A **Needs Ack** notification carries a deadline (`ROBOCO_NOTIFICATION_ACK_TTL_HOURS`, default 48 hours). If it's still unacknowledged when that deadline passes, RoboCo re-sends the same escalation one level up — the original recipient's manager, or you — so an inattentive PM can't both miss a blocker and keep it from ever reaching you. Each re-escalation after the first **doubles the wait** from a base interval (default 1 hour: 1h, 2h, 4h, 8h, ... capped at 24h between attempts), and past a hard cap (default 5 attempts) it's logged once as permanently-unacked and left alone — instead of re-firing every ~60-second sweep tick forever. Acknowledging it, from here or from a [Telegram](/docs/optional/telegram-bridge) escalation button, stops the clock at any point. Purely informational notifications never carry a deadline.
## Next
→ [Agents & work sessions](/docs/panel/agents-and-work-sessions) for the live agent roster, the Conversations tab's agent-to-agent message stream, and the Journals tab's per-agent reflection log · [Tasks & Kanban](/docs/panel/tasks-and-kanban) for the work itself.
================================
URL: https://docs.roboco.tech/docs/panel/auditor
Title: Auditor
================================
import { Admonition } from "@/components/docs/admonition";
The Auditor is the company's silent quality conscience. It has read access to every task and every piece of evidence, and it never *initiates* contact with another agent or broadcasts anything — but it does carry `dm` and `read_a2a` for one specific reason: so you can reach it directly. Open a DM with the Auditor from the [Conversations tab](/docs/panel/agents-and-work-sessions#conversations-tab) and it reads and replies in-thread like any other agent you've opened a line to; it just never starts that conversation itself, and it still has no merge verb. The `/auditor` page is your window into what it sees.
## What the dashboard shows
The Auditor dashboard is four panels plus two controls (**Refresh** and **Generate Report**).
| Panel | What it surfaces |
|-------|------------------|
| **Open Findings** | Every currently-open structured finding from the [revision-findings ledger](/docs/company) across the whole company — QA / PR-gate / PM / CEO bounces alike — sorted most-severe-first, each linking through to its task. |
| **Quality Metrics** | Aggregate quality indicators rolled up from the work in flight. |
| **Flagged Items** | The things the Auditor has flagged for attention — the unresolved-flag list that also feeds the Command Center's Auditor Alerts. |
| **Reports** | Audit reports, newest first. |
**Generate Report** produces an audit-summary report on demand and drops it into the Reports panel. **Refresh** re-pulls the dashboard's findings and metrics.
Reading Open Findings is what backs the Auditor's own `waive_finding` action: it can close out a stray **minor** or **nit** finding with a required note explaining why, but it's refused outright on a **blocker** or **major** finding — those can only be closed by an actual fix, verified by the reviewer that raised them. There's no waive control in the panel itself; this happens as part of the Auditor's own read-only review, not something you click.
## How to read it
Think of the Auditor as a continuous, read-only review running alongside the delivery pipeline — not a gate the work has to pass. Nothing here blocks a task; the lifecycle's own gates ([QA, the PR-review gate, PM and CEO approval](/docs/company/task-lifecycle)) do that. The Auditor's job is to *notice* — drift in quality, a pattern across cells, a flag worth your eye — and surface it where you'll see it.
An unresolved Auditor flag appears both here, in **Flagged Items**, and on the [Command Center](/docs/panel/command-center) as an Auditor Alert. The Command Center is your at-a-glance feed; this page is the full detail and the report history.
The Auditor cannot initiate a message to another *agent*, or broadcast anything — that restriction is enforced at the [agent gateway](/docs/company/agent-gateway), not by convention. Its one comms exception is replying inside a DM *you* open with it. So beyond that one-on-one line, the only way its observations reach the rest of the company is through *you*: you read the flags and reports here and decide what to act on.
## Next
→ [Journals](/docs/panel/agents-and-work-sessions#journals-tab) and [Notifications](/docs/panel/journals-and-notifications) for the raw record the Auditor watches · [Metrics](/docs/panel/metrics) for velocity and cost analytics · [Org & roles](/docs/company/org-and-roles) for where the Auditor sits in the company.
================================
URL: https://docs.roboco.tech/docs/panel/business
Title: Business
================================
import { Admonition } from "@/components/docs/admonition";
The Business page (`/business`) is the strategic layer above day-to-day delivery — the company's charter, its live scorecard against that charter, the Board's pitches awaiting your decision, and the standing Board Programs. It's tabbed: **Goals**, **Scorecard**, **Secretary**, **Pitches**, **Programs**, **Market Briefs**, and **Quality Reports** (the active tab is in the URL as `?tab=`).
For the *story* of how this layer drives work — how a north star shapes what the Board proposes and what the engines watch — see [The business workflow](/docs/tour/05-the-business-workflow). This page documents the panel surface.
## Goals — the company charter
The Goals tab is the CEO-owned charter. It's injected into every agent's briefing, so the whole company stays goal-aware. You edit it directly:
| Field | What it is |
|-------|-----------|
| **North star** | The long-term vision in a sentence or two. |
| **Objectives** | A list of objective rows (metric / target / status, add and remove freely). |
| **Constraints** | Hard rules, one per line — e.g. "AGPL only", "No external data egress". |
| **Operating policy** | Free-form policy keys the company operates under. |
**Save charter** persists it; the card shows when it was last updated and by whom.
## Scorecard
The **Company Scorecard** is a live read of how the company is tracking against the charter, pulled from the cockpit summary. It groups into:
- **Delivery** — tasks in flight, blocked, awaiting CEO, and completed in the last 30 days.
- **Spend** — 30-day spend, projected monthly, and your **monthly budget cap** (`monthly_budget_cap_usd`). When a cap is set and spend exceeds it, the figure turns red with an "over budget" marker; with no cap it reads "No budget cap set." A **Daily Spend (30d)** chart sits underneath, the day-by-day breakdown behind that 30-day total — bars over the trailing month, with a "no spend data" empty state when there's nothing yet.
- **Speed** — median lead time against a target of under 24 hours.
- **Objectives** — a placeholder section; per-objective tracking is not wired up yet.
The scorecard reads the same company snapshot the **Cockpit** API exposes (`/api/cockpit/summary`): a read-only roll-up of delivery, spend against the budget cap, lead time, pending pitches, and strategy signals. There is no separate Cockpit page — its data shows up here on the scorecard and as the Strategy Signals panel on the [Command Center](/docs/panel/command-center).
## Secretary
The Secretary tab is your on-demand chief-of-staff chat. It reads company state and can run gated CEO directives. It is a human-only seat — the Secretary has no agent chat verbs and runs only when you talk to it. It's part of the company-in-a-box layer; see the [optional subsystems index](/docs/optional).
## Pitches
The Pitches tab is where Board-originated proposals land for your decision. Each pitch is a card you can **Approve** or **Reject** (both prompt for a required note). Approving a pitch can, with pitch provisioning enabled, auto-provision a project from it.
Pitch provisioning is a default-off subsystem. Flip `ROBOCO_PROVISIONING_ENABLED` on from [Settings → Feature Flags](/docs/panel/settings), and read [Pitch provisioning](/docs/optional/pitch-provisioning) for what approving actually sets in motion. With it off, the tab still lists pitches but approval won't provision anything.
## Programs
The Programs tab is where you arm and monitor every [Board Program](/docs/optional/board-programs) — the fourteen standing Product Owner / Head of Marketing / Auditor assignments, including the Roadmap cycle and the X feature spotlight, which now ride this same registry. Each program is a card: its role, trigger kind, and scope as badges, an **Enabled** switch that persists immediately (no restart needed), and a **Run now** button that opens a cycle off-schedule — disabled while a cycle is already open, and permanently inert for Coroner specifically, since it only ever opens off its own incident hooks. A **Postmortems** card sits below the program list: the Auditor's Coroner autopsies, read-only, each with an incident kind, a root cause, and one proposed process change.
## Market Briefs
Read-only. Once the Periscope program is armed, the Head of Marketing's weekly market-research briefs land here — competitor and adjacent-tool moves, positioning shifts, each with its sources. There's nothing to approve or reject; it's a report you read, and it also feeds straight into the next Roadmap cycle's exploration prompt.
## Quality Reports
Read-only. Once the Sentinel program is armed, the Auditor's weekly state-of-quality drift reports land here — waiver accumulation, conventions-violation hotspots, docs staleness, and budget anomalies, rolled into one assessment per cycle.
## Next
→ [Board Programs](/docs/optional/board-programs) covers every program's trigger, materializer, and CEO queue in one place · [The business workflow](/docs/tour/05-the-business-workflow) walks the whole strategic loop end to end · [Optional subsystems](/docs/optional) covers the engines (strategy, research, provisioning) that this layer drives.
================================
URL: https://docs.roboco.tech/docs/panel/metrics
Title: Metrics
================================
import { Admonition } from "@/components/docs/admonition";
The Metrics page (`/metrics`) is where you watch the company's throughput and its spend. Four tabs: **Performance**, **Token Usage**, **Delivery**, and **Scorecards** (the active tab is in the URL as `?tab=`).
## Performance
The Performance tab is a snapshot of velocity and pipeline health, computed from the live task list and the orchestrator's agent status.
- **Velocity** — completed today, completed this rolling 7 days, total completed all-time, and a completion rate across all tasks.
- **Task Status** — a compact 2×3 grid of status tiles (Pending, In Progress, Blocked, Awaiting QA, Completed — each an icon, a label, and a count) paired with a **donut chart** of the same distribution. The grid gives you exact counts; the donut gives you the proportional shape, so you can read the pipeline health at a glance and cross-reference the numbers when you need them.
- **Agent Status** — how many agents are running, idle, waiting (need input), or in error.
- **Team Health** — one card per cell with a health score and its active / blocked / done breakdown. The score is a simple read on blockers: more blocked tasks pulls a cell's health down. A healthy cell sits near 100%; blockers visibly degrade it, so a cell sliding toward red is the signal to look at its [blockers](/docs/panel/command-center).
## Token Usage
The Token Usage tab is the cost dashboard. A **time-window selector** — 24h / 7d / 30d / **90d** — sits above everything and drives every card, chart, and table on the tab at once; it defaults to 24h. The 90-day option is the widest historical view and is useful for spotting monthly spend patterns and model-mix shifts.
- **Summary cards** — tokens in, tokens out, total tokens, total cost for the selected window, the trend versus the prior period, and dollars saved by prompt caching.
- **Time series + model donut** — usage over time, and the split across models.
- **Per-agent and per-team bars** — who and which cell is spending. Every chart on this tab has a **Chart / Table** toggle so you can switch from the visual to a sortable table of the same numbers.
- **Monthly projection** — projected monthly cost from a rolling-average daily run rate.
- **Cache efficiency** — cache hit rate and the cost it saved.
- **Sessions table** — the recent agent spawn sessions behind the numbers.
A window with nothing in it shows an explicit "no usage recorded" message in place of a chart, rather than an empty grid that looks like something failed to load.
The token panels subscribe to a live usage stream over `/ws/system` and update in place as agents spend, falling back to periodic HTTP polling when the socket is down. You don't need to refresh to watch cost accrue.
### Where the dollar figures come from
Cost is derived from per-session token counts using provider-aware pricing — and local / Ollama usage is intentionally priced at **$0**, so a self-hosted or Ollama-routed workforce shows tokens but no dollars. The full cost model, the budget cap, and where each number originates are documented in [Cost & usage](/docs/operations/cost-and-usage); this page only shows the numbers.
Metrics shows raw usage and projection. Your **monthly budget cap** and whether you're over it appear on the Company Scorecard in [Business](/docs/panel/business), not here.
## Delivery
The Delivery tab is the flow dashboard — not *what* the company shipped or what it cost, but *how the work moved*. Every panel is reconstructed from the task lifecycle history RoboCo already records (each status transition is logged), so it needs no extra bookkeeping. Cycle-time, bottlenecks, and rework look back 30 days; the scorecards look back 7.
- **Cycle Time by Stage** — the average time a task sits in each lifecycle stage (claimed, in progress, awaiting QA, awaiting documentation, awaiting PR review, awaiting PM review, …). This is where you see *where the time actually goes* — a tall "awaiting QA" bar means work waits on review, not on coding.
- **Bottlenecks** — the same data ranked by total time absorbed, with the single **worst stage** called out and a live count of how many tasks are **parked** in each stage right now, plus the current active-blocker count. It answers "what is holding the company up today?"
- **Rework** — how often work bounces back to `needs_revision` (the headline rate = reworked ÷ completed), broken down by cell and by agent, plus the token cost of that rework. Crucially, a bounce is attributed to the **QA or PR-reviewer who sent it back**, not the developer who owns the task — so a high `QA fails` number against a reviewer is a signal about *that reviewer's* gate, and a high rate against a developer is a signal about *their* first-pass quality.
- **Cell scorecards** — one card per cell (Backend / Frontend / UX-UI) with its completed count, average cycle time, rework rate, and cost over the last 7 days — the quick read on which cell is moving cleanly.
A bounce charges the reviewer who rejected it via the `task.qa_fail` / `task.pr_fail` events, while the *rate* (`reworked / completed`) is computed against the task's owner. So one agent can show a low rate (good first-pass work) while another shows many `QA fails` (an active, rejecting gate) — both are healthy. Watch for a developer with a high rate **and** a reviewer with near-zero fails: that's a gate letting work through that later needs revision.
## Scorecards
The Scorecards tab is the per-agent and per-org performance rollup, looking back a rolling **30 days** — longer than the Delivery tab's 7-day cell scorecards, and a different cut from Delivery's stage-by-stage flow view: this one is about who (and the company as a whole) is producing.
- **Organization** — member count, tasks completed, **first-pass yield** (the share of completed tasks that shipped with no QA / PR-gate / PM / CEO bounce at all), throughput per hour of active agent runtime, total active effort hours, and cost.
- **CEO (you)** — your own numbers: approval count and its p50/p90 latency (how long a task sits in your queue before you act), unblock count and its p50 latency, and a count of your god-mode actions.
- **Members** — one row per agent (excluding you and the system account) with tasks done, first-pass yield, active effort hours, average turns per completed task, QA pass rate, escalations, how many times this agent's work blocked someone else, and utilization (share of spawned time actually spent working, not idle). A row still mid-task shows a **live** badge, since its numbers include in-flight work.
This is the same org-wide rollup summarized on the [Command Center's](/docs/panel/command-center) quick-metrics row, with a deep link into the full tab from there.
## Next
→ [Cost & usage](/docs/operations/cost-and-usage) for the pricing model and budget cap · [Health & metrics](/docs/operations/health-and-metrics) for operational monitoring · [Command Center](/docs/panel/command-center) for the at-a-glance view.
================================
URL: https://docs.roboco.tech/docs/panel/code-snippets
Title: Code Snippet Viewer
================================
import { Admonition } from "@/components/docs/admonition";
The **Code Snippet Viewer** renders live `file:line` context directly inside the panel, so you can inspect a finding or a diff without leaving RoboCo.
## Where you see it
The viewer is used on the **Findings** tab of a task detail page. Each structured finding that points at a file and line now shows an inline snippet of the surrounding lines pulled live from that file at the task's branch, inside a fixed-height scrollable window so long files do not lock up the UI.
## What it does
- Fetches the file window from the task's branch via the git API — the live tip of the branch, not a snapshot pinned to the commit the finding was raised against.
- Renders a plain monospace block with the flagged line highlighted; there is no syntax highlighting.
- Shows a few lines of context above and below the flagged line, scrollable within the fixed-height viewport rather than a collapse/expand control.
## Why it matters
Reviewing a finding used to mean opening the repository in another tab, finding the right branch, and scrolling to the right line. The inline snippet brings that context into the decision surface, so the next reviewer can verify whether a blocker was really fixed without leaving the task page.
The viewer shows the file as it exists on the task branch. It does not edit code; editing still happens in the agent's workspace or your own checkout.
## Next
→ [Tasks & Kanban](/docs/panel/tasks-and-kanban) for the task detail page where the Findings tab lives · [Review & Quality](/docs/company/review-and-quality) for how structured findings are raised and resolved.
================================
URL: https://docs.roboco.tech/docs/panel/settings
Title: Settings
================================
import { Admonition } from "@/components/docs/admonition";
The Settings page (`/settings`) holds two things that genuinely change how the company runs — the **Feature Flags** card and **Transcript Retention** — alongside a handful of browser-local preferences and read-only connection info.
## Feature Flags
The Feature Flags card is the operator's master switchboard for the optional, default-off subsystems. Instead of hand-editing environment variables, you flip a switch here. Each row shows the subsystem's label, a one-line description of what it gates, and a toggle.
| Flag | Gates |
|------|-------|
| External PR review | Discovering and reviewing inbound external/fork PRs. |
| Internal PR review | The read-only safety reviewer on internal branch PRs. |
| Web research | Letting the Board and PMs run web research. |
| Strategy engine | Generating and maintaining strategy artifacts (drives the Command Center's Strategy Signals). |
| Self-healing | Watching RoboCo's own CI and notifying you on a regression. |
| Self-heal originate | Also opening a *pending* fix task for a regression — needs self-healing on, and the task waits for your approval. |
| Pitch provisioning | Auto-provisioning projects from approved [pitches](/docs/panel/business). |
| Toolchain match | Provisioning each agent workspace with the target project's own Python and blocking gates when its tests can't run. |
| Conventions | Enforcing the per-project architectural standard (`.roboco/conventions.yml`). |
| Possibilities matrix | The work-already-done fast path: a dev whose task already has commits, an open PR, covered ACs, and no open findings completes in one call, with green PR CI standing in for the local gate. |
| Task & project cost budgets | A monthly per-project spend cap refuses new work-starting claims (in-flight review/doc/gate work always finishes); a per-task $ cap blocks an over-budget task mid-flight and notifies you — see [Task & project cost budgets](/docs/optional/task-budgets). |
| RAG auto-update | Keeping the [knowledge base](/docs/panel/knowledge-base) index refreshed automatically. |
| Transcript prune | Running the background sweep that prunes old transcripts. |
| Gateway-health recovery | Probing stale-heartbeat agents' gateway out-of-band and recycling ones whose toolchain is broken. |
| Multi-repo CI-watch | Watching each opted-in project's CI and opening one fix task when it goes red. |
| Dependency-update bot | Read-only checking whether an upgrade changes a project's lockfiles, and opening an update task when it would. |
| Environment-branch sync | Cascading each declared [environment ladder](/docs/optional/env-branches) downward: clean merges auto-push to non-prod rungs, conflicts open one sync PR. |
| Docs-divergence sync | Opening a docs-update task into the delivery flow when a release drifts from the docs site. |
| Gated release manager | Assessing readiness and proposing a version bump + changelog for your approval — the org [proposes releases](/docs/optional/autonomous-maintenance), you click publish. |
| Organizational memory | Distilling one lesson per completed task and injecting relevant lessons + playbooks into claim-time briefings. |
| Sandboxed per-agent test DB/Redis | Letting a dev/QA agent request a throwaway Postgres/Redis/Mongo sibling container on demand for an opted-in project, instead of injecting RoboCo's own production DB credentials. |
| Strict model routing | Failing closed (parking work) when an agent's assigned provider is disabled, instead of silently falling back. |
| X (Twitter) engine | Drafting release-announcement posts, held for your per-post approval — expand this row for the credentials form. |
| X mention replies | A sub-switch on top of the X engine above: also polls mentions and drafts held replies (needs a paid X API tier to read mentions). |
| X feature spotlight | Periodically investigating shipped features and drafting a held spotlight post for an under-publicized one. |
| Video engine | The motion-graphics pipeline: UX/UI devs author compositions, a sidecar renders MP4 cuts, drafts wait in your video queue — expand this row for the TikTok credentials. |
| Video on release | A sub-switch: also opens a video-authoring task when a release publishes. |
| Video on spotlight | A sub-switch: also opens a video-authoring task alongside a feature-spotlight draft. |
| Board roadmap engine | Weekly: the Product Owner proposes a themed cycle of 3-7 roadmap items for you to approve or reject individually. |
| Fable + Ponytail doctrine | Composing the behavioral doctrine layers (+ agent hooks) into every spawned agent's prompt. |
| Obsidian vault | Projecting tasks, journals, and A2A digests into a wikilinked [Obsidian vault](/docs/optional/obsidian-vault). |
| Vault intake watcher | Turning `#roboco`-tagged notes in the vault inbox into held board-review drafts. |
| Vault weekly report | Writing a weekly org-metrics report note into the vault. |
| Vault KB ingest | Embedding your own `RoboCo/Notes/` folder so the fleet can retrieve what you write. |
| Telegram notifications | DMing you escalations and completions over Telegram — expand this row for the bot credentials. |
| Telegram inbound commands + actionable buttons | A sub-switch on top of the row above: also polls Telegram for `/status`, `/queue`, `/task`, and one-tap Approve/Reject/Open buttons on escalation DMs and the queue. |
Each subsystem has a full page in the [optional subsystems section](/docs/optional) — what it does, the exact `ROBOCO_*` env var behind it, and what turning it on changes.
Three rows nest their credential form directly underneath as a collapsible, rather than living in a separate card: **X (Twitter) engine** expands to the four OAuth 1.0a secrets (API key, API key secret, access token, access token secret) the [X engine](/docs/optional/x-engine) needs to post; **Video engine** expands to the TikTok distribution leg's credentials; and **Telegram notifications** expands to a bot token and chat id. All three forms are write-only — once saved, the fields never show the stored values again, the card only ever tells you whether credentials are set (mirroring the per-project git-token card), and the secrets are Fernet-encrypted at rest and never appear in `.env`. Set every field in a form to save or rotate it; leave them all blank and save to clear.
Both are off by default and need a per-project opt-in to do anything. The global flag here arms the engine; you then opt each project in from its **settings page → Budget & Ops card**: turn on CI-watch and (optionally) name its workflow file (`ci_watch_workflow`, default `ci.yml`), and/or set the dependency-update command (`dep_update_command`, e.g. `uv lock --upgrade` / `pnpm update`) with optional comma-separated lockfile paths (`dep_update_paths`). See [Autonomous maintenance](/docs/optional/autonomous-maintenance).
Toggling a flag persists the choice server-side, but it does **not** hot-reload — the backend reads it at startup. The toast says as much: "takes effect on next restart." A flag you've never set falls back to its environment / config default. So: flip it here, then restart the orchestrator for it to take hold.
## Transcript Retention
The Transcript Retention card sets how many days agent transcripts are kept before the prune sweep removes them (default 14). This is a real, server-persisted setting. It only ever prunes *agent-owned* transcripts — never your own Claude sessions. The sweep itself is gated by the **Transcript prune** feature flag above.
## User Info
The **User Info** card is where your own display name lives — the one the header chip and the rest of the panel show instead of a hardcoded name. It's a real, server-persisted setting (`ceo_name` in the settings store): trimmed, non-empty, capped at 60 characters, editable inline. Agent prompts refer to "the CEO" generically regardless of what you set here, so changing it never touches agent behavior — it's purely how the panel addresses you.
## GitHub App
The **GitHub App** card is a one-time credentials entry — the App id plus its RSA private key (PEM), both from a GitHub App you create on github.com — that unlocks a faster, token-free path for GitHub projects:
- **Write-only, like every other credentials card here.** Once saved, the panel only ever shows whether credentials are set, never the key itself; clearing requires an explicit confirmation.
- **Unlocks the "Select repo" picker** in the New Project dialog — browse the App's installations and repositories and pick one instead of typing a Git URL and pasting a personal access token by hand.
- **Existing projects can bind to an installation from Edit Project too** — the same picker appears there, and picking a repo stores the installation id alongside the project.
- **Git operations mint a short-lived installation token** for a bound project instead of using its stored PAT, attributed to the App's own bot identity in commits and PR reviews. If minting ever fails (App not configured, installation revoked, a network hiccup), git operations **fall back to the project's own PAT** automatically — a GitHub App hiccup never bricks a project that also has a token on file.
- Unbinding a project (clearing its installation in Edit Project) reverts it to the personal access token below, with no other change.
Unlike the rows in Feature Flags above, the GitHub App integration has no on/off switch — it's inert until you save real App credentials, then available to any project you choose to bind. It sits alongside git-token entry as an alternative auth path, not a replacement for it.
## Connection Info (read-only)
This card displays the API and WebSocket base URLs the panel is using (`NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_WS_URL`) — relative by default so nginx dispatches them on a single origin. It's informational; you can't edit it here.
## The browser-local preference controls
**Appearance** (theme, collapsed sidebar), **Data & Refresh** (Auto Refresh, Refresh Interval), and **Notifications** (Enable Notifications, Sound Alerts) all take effect immediately and persist to this browser's local storage — there is no page-level Save button; each switch/select writes through the moment you touch it. None of it is sent to the backend or synced across devices/browsers, which is the distinction that matters: **Auto Refresh** actually re-runs the current page's data queries on the interval you pick (the same mechanism the header's manual refresh icon triggers), and **Enable Notifications** / **Sound Alerts** actually gate the toast + chime the [notifications bell](/docs/panel/command-center#notifications-bell) fires for newly-arrived notifications. Only **Feature Flags**, **Transcript Retention**, and **User Info** are genuinely server-persisted settings.
## AI provider configuration lives elsewhere
Choosing which model and provider backs each agent is **not** on this page — it's the **AI Providers** page (`/settings/ai-providers`), linked from the sidebar footer. There you set the global routing mode (Anthropic / Grok / Codex / Gemini / Kimi / Ollama / self-hosted / cost-tiered), or pin individual agents in Mix mode, and store the encrypted provider keys. See [Provider routing](/docs/models/provider-routing).
## Next
→ [Optional subsystems](/docs/optional) for every feature flag in detail · [Provider routing](/docs/models/provider-routing) for the AI Providers page · [Register your first project](/docs/get-started/first-project) for the GitHub App vs. token choice on a per-project basis · [Environment reference](/docs/deploy/env-reference) for the `ROBOCO_*` variables behind the flags.
================================
URL: https://docs.roboco.tech/docs/deploy
Title: Configure & Deploy
================================
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { FileText } from "lucide-react";
The reference layer beneath [Get Started](/docs/get-started): the full configuration surface, the production deployment story, and how RoboCo's data and schema are managed.
The compose files, the single-origin nginx, the host-path mounts, data persistence and backup, secure mode, and the startup sequence.
Every ROBOCO_ setting, by category, with its default and purpose — the canonical configuration reference.
The core data model, the pgvector requirement, and how the stack migrates itself on every boot.
The always-on pg_dump sidecar: daily dumps, 14-dump rotation, and how to restore one.
What make db-init seeds — the agents a fresh company starts with.
================================
URL: https://docs.roboco.tech/docs/deploy/deployment
Title: Deployment
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
This is the operator reference for running RoboCo on a NAS or server. If you just want it up on your laptop, the [install quickstart](/docs/get-started/installation) is faster — this page assumes you've done that once and now want the durable, server-side setup: the compose files, the host mounts agents need, where data lives, how to back it up, and how to harden it.
RoboCo is built for a private LAN or homelab. Do not expose it directly to the public internet. nginx is the single entry point, but the orchestrator's WebSocket streams and (in header-trust mode) its API assume a trusted network. Put it behind your own VPN if you need remote access.
## The three compose files
There are **three tracked compose files**, and they are not interchangeable:
| File | What it does | Needs a build toolchain? |
|------|--------------|--------------------------|
| `docker-compose.yml` | Builds every image from the Dockerfiles in `docker/`. | Yes |
| `docker-compose.yaml` | **Byte-identical** to `docker-compose.yml`. | Yes |
| `docker-compose.registry.yml` | Pulls and runs the **pre-built published images**. | No |
`docker-compose.yml` and `docker-compose.yaml` are the same file under two names — Docker Compose picks up either, and the NAS deployment runs the `.yaml`. If you fork RoboCo and change a service, keep all three in sync.
### Which one to run
For a server you don't intend to hack on, run the **registry** file — it pulls finished images and needs no source tree or compiler on the host:
```bash
docker compose -f docker-compose.registry.yml pull
docker compose -f docker-compose.registry.yml up -d
```
`make quickstart` runs exactly this path for you — an idempotent `scripts/bootstrap.sh` that scaffolds `.env` with freshly generated secrets (or validates an existing one, untouched), pulls, brings the registry compose up, and runs a doctor-style readiness sweep with pointed remedies at whichever stage doesn't check out. See the [install quickstart](/docs/get-started/installation) for the one-command version.
Two variables choose what you pull (defaults shown):
```bash
ROBOCO_REGISTRY=ghcr.io/rennf93 # or docker.io/renzof93
ROBOCO_VERSION=latest # or a pinned release, e.g. 0.20.0
```
The orchestrator then spawns the **matching** pre-built agent images on demand (it reads `ROBOCO_AGENT_IMAGE_REGISTRY` / `ROBOCO_AGENT_IMAGE_TAG`, which the registry compose wires to the same registry and version). Pin `ROBOCO_VERSION` to a release tag in production so an upstream `latest` push can't silently change your fleet.
Build from source only when you're modifying RoboCo:
```bash
docker compose up -d # builds on first run
```
The `agent-*-image` services in every compose file are one-shot stubs — they exist so `docker compose build`/`pull` materializes each per-role agent image up front. They never run as long-lived containers. The orchestrator spawns the actual agent containers itself, on demand, over the mounted Docker socket, and tears them down when their work is done.
## The single origin
nginx (`docker/nginx.conf`, rendered from an envsubst template) is the only externally-exposed service. It listens on `localhost:3000` and routes by path:
N[nginx]
N -->|/| P[panel:3000]
N -->|/api/, /ws/, /health, /ready| O[orchestrator:8000]`} />
| Path | Upstream |
|------|----------|
| `/api/`, `/ws/`, `/health`, `/ready` | `roboco-orchestrator:8000` |
| everything else | `roboco-panel:3000` |
The browser only ever sees one origin (`:3000`), so there's no CORS to configure — the panel uses relative `/api` and `/ws` URLs and lets nginx dispatch. The panel container is never published directly; you reach it only through nginx. `/ws/` also gets a long (86400s) read timeout so live sockets stay open.
The backing services *do* publish host ports for direct inspection — Postgres on **15432**, Redis on **16379**, Ollama on **11435**, and the orchestrator on **8000**. You don't route browser traffic at these; they're there for `psql`, `redis-cli`, and the like.
### Two internal Docker networks, not one
Separate from nginx's browser-facing single origin, all three tracked compose files also split the **internal** Docker network in two: `roboco_default` (the agent mesh — panel, nginx, ollama, every spawned agent container, and their sandbox DB/Redis sidecars) and `roboco_data` (postgres + redis **only**). The orchestrator is the only multi-homed service, so an agent container cannot resolve or reach `roboco-postgres:5432` / `roboco-redis:6379` at all — network membership is the entire containment, which matters because Redis has no auth of its own. `ROBOCO_DB_NETWORK_ISOLATED` (config default `false`) is set `true` by these compose files and suppresses the legacy prod-creds gate-env injection accordingly; see [DB network isolation](/docs/optional/db-network-isolation).
## Required host-path mounts
The orchestrator is Docker-in-Docker: it mounts `/var/run/docker.sock` and spawns agent containers itself. Because those agent bind-mounts resolve on the **host** daemon (not inside the orchestrator container), several paths must be given as **absolute host paths** — the orchestrator passes them straight through to `docker run -v` for each agent.
| Variable | What it points at | Compose default |
|----------|-------------------|-----------------|
| `ROBOCO_HOST_PROJECT_DIR` | The RoboCo project directory on the host. | `/volume1/roboco` |
| `ROBOCO_HOST_CLAUDE_DIR` / `CLAUDE_AUTH_DIR` | The host `~/.claude` Claude Code auth dir, mounted into the orchestrator and each agent. | `/home/renzof/.claude` / `${HOME}/.claude` |
| `ROBOCO_HOST_DATA_DIR` | The host data dir handed to agents for shared volumes (workspaces, logs, grok-usage). | `/volume1/roboco/data` |
| `ROBOCO_DATA_DIR` | Host root for all persistent volumes mounted into the *backing* services and orchestrator (see below). | `./data` |
| `ROBOCO_HOST_GROK_DIR` | Host `~/.grok` SuperGrok auth — only needed if you run any agent on Grok. | `/home/renzof/.grok` |
| `ROBOCO_HOST_CODEX_DIR` | Host `~/.codex` ChatGPT-subscription auth — only needed if you run any agent on Codex. | `~/.codex` |
| `ROBOCO_HOST_GEMINI_DIR` | Host `~/.gemini` OAuth login — only needed if you run any agent on Gemini. | `~/.gemini` (registry) / `/home/renzof/.gemini` |
| `ROBOCO_HOST_KIMI_DIR` | Host `~/.kimi-code` Kimi-subscription auth, shared read-write across every Kimi agent plus the orchestrator — only needed if you run any agent on Kimi. | `~/.kimi-code` |
A relative path or a path that only exists *inside* the orchestrator container will make agent spawns fail, because the host Docker daemon resolves the bind. On a NAS the project and data dirs usually live on the RAID volume (e.g. `/volume1/roboco` and `/volume1/roboco/data`).
The host `~/.grok` is mounted **read-write** into the orchestrator (it rewrites the short-lived token in place to keep agents from hanging on an expired login) and **read-only** into each Grok agent. Run `grok login` on the host once before enabling Grok. `~/.codex` and `~/.gemini` are each mounted **read-only** — Codex's orchestrator-side refresh loop and Gemini's per-container in-process refresh both write only to the image's own local copy, never back to your host credential. `~/.kimi-code` is the odd one out: it's mounted **read-write and shared** into every Kimi agent *and* the orchestrator, because Moonshot's refresh token rotates with only a short reuse grace — independent per-container copies would eventually cross-invalidate each other, so every container instead redeems the same rotating chain, serialized by the CLI's own cross-process lock. Run `codex login` / the interactive `gemini` login / `kimi login` on the host once before enabling any of the three. Provider routing and the Grok/Codex/Gemini/Kimi runtimes are covered in the [models section](/docs/models).
## Data persistence and backup
Everything durable lives under `ROBOCO_DATA_DIR` (default `./data`). On a server, point this at a RAID volume:
```bash
ROBOCO_DATA_DIR=/volume1/roboco/data
```
| Subdirectory | Holds |
|--------------|-------|
| `postgres/` | The entire database — tasks, projects, work sessions, journals, encrypted git tokens, the pgvector store. |
| `redis/` | Append-only cache, sessions, rate-limit + event-bus state. |
| `ollama/` | The local model cache (embedding model + local LLM) — large, but re-pullable. |
| `workspaces/` | Each agent's git clone of each project. |
| `logs/` | Per-agent run logs. |
| `mcp-configs/`, `prompts-generated/`, `agent-settings/`, `briefings/`, `manifests/` | Per-agent spawn artifacts the orchestrator writes. |
| `grok-usage/` | Per-agent Grok cost/usage capture. |
| `backups/` | Daily `pg_dump` snapshots of `postgres/`, written by the always-on `backup` sidecar (see below). |
| `vault/` | The [Obsidian vault](/docs/optional/obsidian-vault) projection, when `ROBOCO_OBSIDIAN_VAULT_ENABLED` is on — a rebuildable read surface on the database, not load-bearing itself. |
The load-bearing directory is `postgres/` (everything else here is either re-derivable or a derived projection of it). `ollama/` and `workspaces/` are reconstructible — Ollama re-pulls models, agents re-clone repos — so they're not worth backing up at all.
A `backup` sidecar (`pgvector/pgvector:pg16`, same image as `postgres`) ships in both tracked compose files and takes care of Postgres automatically: a `pg_dump -Fc` on container start and then every 24 hours, written to `backups/roboco-.dump`, rotated to the newest 14. No flag — it's always on. Setting `ROBOCO_BACKUP_MIRROR_DIR` to a host path on a **different disk** additionally mirrors every successful dump there — see [Backups](/docs/deploy/backups) for the rotation detail, the off-disk mirror, and the restore procedure (including a quarterly restore-drill script). For a one-off manual dump instead of waiting for the sidecar's cycle, the same command works against the published port:
```bash
pg_dump -h localhost -p 15432 -U roboco -Fc roboco > roboco-backup.dump
```
Every per-project GitHub token in the database is Fernet-encrypted with `ROBOCO_ENCRYPTION_KEY`. **A database backup is useless without the key.** If you lose or change the key, every stored token becomes undecryptable and must be re-entered project by project. Store the key with your secrets, keep it stable across restarts, and never commit `.env`.
## Object storage (MinIO) — default **off**
MinIO is the optional durable object store for rendered MP4s. **Default is off** — empty `ROBOCO_MINIO_ENDPOINT` leaves the existing `FileResponse` media-serve path byte-for-byte unchanged, so upgrading is a no-op until you opt in. The NAS compose arms it; the registry compose omits it (NAS default-on, registry default-off).
The `minio` service runs on the `data` network only (off the agent mesh) with a named `minio-data` volume; the orchestrator reaches it over its `data` NIC. Host ports `19000:9000` (API) and `19001:9001` (console) are published for debugging only.
### Arming it
Set the five `ROBOCO_MINIO_*` vars on the orchestrator (see [env reference](/docs/deploy/env-reference)). Once `ROBOCO_MINIO_ENDPOINT` is non-empty:
- **Write path** — after each render's local write, the orchestrator PUTs the MP4 to `ROBOCO_MINIO_BUCKET` keyed by its filename. The PUT is non-fatal: if MinIO is down, the render still succeeds and stays on local disk (the source of truth); the failure is only logged.
- **Serve path** — the panel preview's media route streams the object from MinIO via `StreamingResponse`, with `_require_ceo` auth kept end-to-end (no presigned URLs, no redirect — same URL/headers/body the panel's axios-blob flow already uses). If the object is missing (a render predating MinIO) or MinIO is down, the route falls back to `FileResponse` from the local render dir.
### Backfilling existing renders
Renders created before MinIO was armed live only on the bind mount. Copy them in with `mc`:
```bash
mc alias set local http://localhost:19000 $ROBOCO_MINIO_ACCESS_KEY $ROBOCO_MINIO_SECRET_KEY
mc cp --recursive ./data/video-renders local/$ROBOCO_MINIO_BUCKET/
```
The serve route falls back to the local file for any key not yet in the bucket, so backfill is safe to run while the panel is up.
### Pointing a dev env at MinIO
A dev (non-NAS) environment can target the NAS MinIO by setting the five `ROBOCO_MINIO_*` vars to the NAS endpoint (and ensuring the dev host can reach `roboco-minio` / the published port). For a fully local dev MinIO, run the `minio` image standalone and point `ROBOCO_MINIO_ENDPOINT` at it; `minio-init` creates the bucket idempotently.
## Secure mode
On a trusted LAN RoboCo runs in **header-trust mode** by default (`ROBOCO_AGENT_AUTH_REQUIRED=false`): callers are identified by role headers, no token required. That's the intended homelab setup.
To harden it so one agent can't spoof another's role, turn on fail-closed auth:
```bash
ROBOCO_AGENT_AUTH_REQUIRED=true
ROBOCO_AGENT_AUTH_SECRET= # already required for docker compose
ROBOCO_PANEL_AGENT_TOKEN=
```
With auth required, every API call must carry a valid `X-Agent-Token`. The panel runs in your browser and can't hold the signing secret, so nginx injects the CEO's token for it: generate the token with `make panel-token` (it signs one using your `ROBOCO_AGENT_AUTH_SECRET`), put it in `ROBOCO_PANEL_AGENT_TOKEN`, and nginx adds it as `X-Agent-Token` on `/api` and `/ws`. The panel keeps working; the secret never reaches the browser.
`ROBOCO_ENCRYPTION_KEY` and `ROBOCO_AGENT_AUTH_SECRET` are both **required** for any docker compose run — the orchestrator service block guards them with compose `:?` so the stack refuses to start if either is unset. See [Security](/docs/troubleshooting/security) for the full sandboxing model and [the env reference](/docs/deploy/env-reference) for every knob.
### Cloud auth — for exposing the panel beyond the LAN
Header-trust and `ROBOCO_AGENT_AUTH_REQUIRED` both assume the *agent fleet's* identity headers. If you want the *CEO's own browser session* to require a login instead of relying purely on network trust, arm `ROBOCO_CLOUD_AUTH_ENABLED` — a single seeded user, a sliding 30-day session cookie, and a startup guard that refuses to boot without `ROBOCO_CLOUD_AUTH_SECRET` set.
```bash
ROBOCO_CLOUD_AUTH_ENABLED=true
ROBOCO_CLOUD_AUTH_EMAIL=you@example.com
ROBOCO_CLOUD_AUTH_PASSWORD=
ROBOCO_CLOUD_AUTH_SECRET=
```
The session cookie is `secure`-only — browsers won't send it over plain HTTP. You must terminate TLS somewhere in front of nginx (your own reverse proxy or tunnel) before this does anything useful; without it, login will look like it silently fails. See [Cloud auth](/docs/optional/cloud-auth) for the full model.
## Startup sequence
`depends_on` conditions enforce a strict boot order; the effective sequence is:
OL[ollama]
RD[redis] --> OL
OL --> OI[ollama-init]
OI --> OR[orchestrator]
AB[agent-base-image] --> OR
OR --> PN[panel]
PN --> NG[nginx]
OR --> NG`} />
- **postgres / redis / ollama** must each pass their healthcheck (`pg_isready`, `redis-cli ping`, `ollama list`) before anything downstream starts.
- **ollama-init** is a one-shot that best-effort pulls the embedding model and the local LLM, then gates success on the models being **present** — a degraded model registry can't take down a fully-cached deployment.
- **orchestrator** waits for postgres + redis + ollama healthy, ollama-init completed, and agent-base-image built. On startup it **runs the database migrations itself** (idempotently, to head) and indexes its knowledge base — you do not run `alembic upgrade head` by hand for the compose path.
- **panel** waits for the orchestrator; **nginx** waits for both.
First boot is the slow one: the model pulls (the LLM is a couple of minutes) plus knowledge-base indexing. Watch it come up:
```bash
docker compose logs -f orchestrator
curl http://localhost:8000/health
docker ps --filter name=roboco
```
When the orchestrator reports serving, open `http://localhost:3000`. A boot that hangs is almost always waiting on `ollama-init` (model pull) or a healthcheck — check `docker compose ps` to see which service is still `starting`. Migration and data details are in [Data & migrations](/docs/deploy/data-and-migrations); recurring boot symptoms are in [Common issues](/docs/troubleshooting/common-issues).
## Operator-relevant Makefile targets
The `Makefile` drives the **host** developer workflow (uv-based, for hacking on RoboCo itself) — it is separate from the Docker stack and needs `uv` on the host. The handful that matter operationally:
| Target | Does |
|--------|------|
| `make panel-token` | Prints a signed CEO token for `ROBOCO_PANEL_AGENT_TOKEN` (secure mode). |
| `make infra` | Brings up only postgres + redis (`make infra-down` stops them) — for host-side dev against the backing services. |
| `make migrate` | Runs `alembic upgrade head` on the host (the compose stack self-migrates; this is the host-dev path). |
| `make run` | Runs the API + orchestrator on the host (no `--reload`); `make api` is the reload dev server, `make dev` runs both. |
| `make quality` | The full merge gate: ruff format-check + lint, mypy, pytest with 80% coverage floor, complexity, security, dependency, and migration checks. |
| `make serve-docs` | Serves this documentation locally with `mkdocs serve`. |
| `make status` / `make logs` | Orchestrator status / recent logs against a running instance. |
Run `make help` for the full list.
## Makefile enforcement for agents
The agent runtime uses the same `Makefile` discipline as the host developer workflow. When an agent is working in a repository that has a `Makefile`, the bash guard routes quality commands through `make` targets and blocks the raw package-manager invocations that would bypass the project's guards.
- Agents run `make quality`, `make gate`, `make lint`, or `make test` instead of calling `uv run ...`, `pip install ...`, `conda ...`, or `poetry ...` directly.
- The bash guard rejects raw `uv run`, `pip install`, `conda`, and `poetry` invocations whenever a `Makefile` is present in the working directory.
- This preserves the Makefile's `UV_NO_SYNC=1` setting and its private `UV_CACHE_DIR`, preventing the concurrent-venv-corruption race that bare `uv run` would trigger.
This applies to the agent's runtime path inside a task branch, not to the NAS operator commands you run by hand. For the host operator workflow, the targets in the table above still behave the same way.
## Next
- **[Environment reference](/docs/deploy/env-reference)** — every `ROBOCO_*` setting, with defaults and on/off state.
- **[Data & migrations](/docs/deploy/data-and-migrations)** — the self-migrating schema and what to back up.
- **[Security](/docs/troubleshooting/security)** — the full agent sandboxing and auth model.
- **[Cloud auth](/docs/optional/cloud-auth)** and **[DB network isolation](/docs/optional/db-network-isolation)** — the two settings on this page that change trust boundaries rather than add a background job.
================================
URL: https://docs.roboco.tech/docs/deploy/env-reference
Title: Env Reference
================================
import { Admonition } from "@/components/docs/admonition";
This is the canonical list of every `ROBOCO_*` setting. They are all read by a single Pydantic-Settings class (`roboco/config.py`), loaded from the process environment and `.env`, prefixed with `ROBOCO_`, and case-insensitive. Most have a working default; the few that don't, and the ones the orchestrator refuses to start without, are flagged below.
For a working deploy you set the two required secrets, the host paths, and maybe a feature flag or two. The long tables here exist so that when you *do* need to tune a timeout or a window, you can find it. The defaults shown are RoboCo's config defaults; a few compose-only defaults differ and are called out.
A feature flag set in `.env` takes effect on the next backend restart. The env-gated subsystems can also be toggled from the panel's **Settings → Feature Flags** card, which persists to the settings store and overrides the env default; an unset toggle falls back to the env/config default. See the [Optional capabilities](/docs/optional) section for what each subsystem does.
## Required secrets
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_ENCRYPTION_KEY` | *(empty — required)* | Fernet key encrypting every per-project git token at rest. The orchestrator **refuses to start** without it (compose `:?` guard). Generate with `python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'`. Keep it stable — losing it makes stored tokens undecryptable. |
| `ROBOCO_AGENT_AUTH_SECRET` | *(empty — required for compose)* | HMAC secret signing the per-agent `X-Agent-Token`. Generate with `python -c 'import secrets; print(secrets.token_hex(32))'`. |
| `ROBOCO_CLOUD_AUTH_SECRET` | *(empty — required when [cloud auth](/docs/optional/cloud-auth) is on)* | Signs the session cookie's JWT. The orchestrator **refuses to start** if `ROBOCO_CLOUD_AUTH_ENABLED=true` and this is unset. Generate with `python -c 'import secrets; print(secrets.token_hex(32))'`. Irrelevant while cloud auth is off. |
## Security & auth
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_AGENT_AUTH_REQUIRED` | `false` | Fail-closed secure mode. When `true`, every API call must carry a valid token. Requires `ROBOCO_PANEL_AGENT_TOKEN` to keep the panel working. On a trusted LAN, leave `false` (header-trust mode). |
| `ROBOCO_PANEL_AGENT_TOKEN` | *(empty)* | The CEO token nginx injects as `X-Agent-Token` on `/api` and `/ws` in secure mode, so the panel works without the browser holding the signing secret. Generate with `make panel-token`. |
## Application & API server
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_APP_VERSION` | `0.29.0` | Reported app version. |
| `ROBOCO_DEBUG` | `false` | Debug mode. |
| `ROBOCO_ENVIRONMENT` | `development` | One of `development` / `staging` / `production`. Selects the JSON log renderer (prod) vs console renderer. The compose stack sets `production`. |
| `ROBOCO_HOST` | `127.0.0.1` | Bind address. Use `0.0.0.0` in containers. |
| `ROBOCO_PORT` | `8000` | API port. |
| `ROBOCO_API_URL` | *(unset)* | Override base URL for containerized agents (e.g. `http://roboco-orchestrator:8000`); otherwise built from host/port. |
| `ROBOCO_CORS_ORIGINS` | `["http://localhost:3000","http://localhost:5173"]` | Allowed CORS origins. The single-origin nginx setup means you rarely change this. |
| `ROBOCO_CORS_ALLOW_CREDENTIALS` | `true` | Whether CORS allows credentials. |
| `ROBOCO_PUBLIC_BASE_URL` | `http://127.0.0.1:8000` | Reachable base URL embedded in commit-trailer links — set to your LAN IP or domain so the links resolve. |
## Database
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_DATABASE_HOST` | `localhost` | Postgres host (`roboco-postgres` in compose). |
| `ROBOCO_DATABASE_PORT` | `5432` | Postgres port. |
| `ROBOCO_DATABASE_USER` | `roboco` | Postgres user. |
| `ROBOCO_DATABASE_PASSWORD` | `roboco` | Postgres password — change it for any real deployment. |
| `ROBOCO_DATABASE_NAME` | `roboco` | Database name. |
| `ROBOCO_DATABASE_ECHO` | `false` | Log every SQL statement. |
| `ROBOCO_DATABASE_POOL_SIZE` | `10` | Connection pool size. |
| `ROBOCO_DATABASE_MAX_OVERFLOW` | `20` | Extra connections beyond the pool. |
| `ROBOCO_DATABASE_POOL_TIMEOUT` | `10` | Seconds to wait for a pooled connection. |
| `ROBOCO_DATABASE_POOL_RECYCLE` | `1800` | Recycle a connection after this many seconds. |
A daily `pg_dump` of this database runs automatically via an always-on sidecar — no flag, no env var here to set. See [Backups](/docs/deploy/backups).
## Redis
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_REDIS_HOST` | `localhost` | Redis host (`roboco-redis` in compose). |
| `ROBOCO_REDIS_PORT` | `6379` | Redis port. |
| `ROBOCO_REDIS_DB` | `0` | Redis logical DB. |
| `ROBOCO_REDIS_PASSWORD` | *(unset)* | Optional Redis password. |
## RAG, embeddings & Ollama
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_RAG_PERSIST_DIR` | `.roboco` | Local RAG persistence dir. |
| `ROBOCO_RAG_CHUNK_STRATEGY` | `fixed` | One of `fixed` / `semantic` / `hierarchical` / `contextual`. `fixed` recommended; `semantic` loads an extra model. |
| `ROBOCO_RAG_CHUNK_SIZE` | `512` | Base chunk size. |
| `ROBOCO_RAG_CHUNK_SIZE_DOCS` | `1536` | Chunk size for docs. |
| `ROBOCO_RAG_CHUNK_SIZE_JOURNALS` | `1024` | Chunk size for journals/reflections. |
| `ROBOCO_RAG_CHUNK_OVERLAP` | `128` | Chunk overlap. |
| `ROBOCO_RAG_AUTO_UPDATE_ENABLED` | `true` | Whether the RAG index auto-refreshes. |
| `ROBOCO_RAG_AUTO_UPDATE_INTERVAL` | `300` | Seconds between auto-updates. |
| `ROBOCO_ANTHROPIC_API_KEY` | *(unset)* | Optional Anthropic key. Agents use the mounted Claude Code auth, not a metered key. |
| `ROBOCO_DEFAULT_EMBEDDING_MODEL` | `qwen3-embedding:0.6b` | Embedding model (1024-dim). |
| `ROBOCO_EMBEDDING_DIMENSIONS` | `1024` | Embedding dimensions — must match the model. |
| `ROBOCO_LOCAL_LLM_MODEL` | `glm-5:cloud` | Local LLM for RAG answer synthesis. |
| `ROBOCO_LOCAL_LLM_BASE_URL` | `http://roboco-ollama:11434/v1` | Ollama OpenAI-compatible endpoint. |
| `ROBOCO_OLLAMA_BASE_URL` | `http://roboco-ollama:11434` | Ollama native endpoint (embeddings, model management). |
## Workspaces & git timeouts
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_WORKSPACES_ROOT` | `/data/workspaces` | Root for all agent git clones. |
| `ROBOCO_WORKSPACE_AUTO_CLONE` | `true` | Auto-clone a repo on first workspace access. |
| `ROBOCO_WORKSPACE_CLONE_TIMEOUT` | `300` | Seconds for a `git clone`. |
| `ROBOCO_WORKSPACE_REFRESH_FETCH_TIMEOUT_SECONDS` | `60` | Timeout for the best-effort `git fetch` on re-entry into a healthy clone. |
| `ROBOCO_WORKSPACE_INSTALL_DEV_DEPS` | `true` | After cloning, install the project's dev dependencies into the workspace so `make quality` runs without re-downloading tooling. |
| `ROBOCO_WORKSPACE_DEP_INSTALL_TIMEOUT_SECONDS` | `600` | Timeout for that post-clone dependency install. |
| `ROBOCO_GIT_COMMAND_TIMEOUT_SECONDS` | `30` | Timeout for a single local git subprocess (status, log, checkout). |
| `ROBOCO_GIT_COMMIT_TIMEOUT_SECONDS` | `180` | Timeout for staging + committing a changeset. |
| `ROBOCO_GIT_NETWORK_TIMEOUT_SECONDS` | `120` | Timeout for git ops that talk to origin (fetch / pull / push). |
| `ROBOCO_PROTECTED_GIT_URLS` | *(empty)* | Repo URL substrings a project may not point at — blocks agent commits/merges from reaching a protected repo. |
## Agent images (spawn source)
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_AGENT_IMAGE_REGISTRY` | *(empty)* | Registry namespace for pre-built agent images (e.g. `ghcr.io/rennf93`). Empty = build locally. The registry compose wires this to `ROBOCO_REGISTRY`. |
| `ROBOCO_AGENT_IMAGE_TAG` | *(empty)* | Tag for pre-built agent images (e.g. `0.9.0`). Empty = implicit `:latest`. The registry compose wires this to `ROBOCO_VERSION`. |
A few variables are consumed by the compose files and host-mount wiring rather than by `config.py`: `ROBOCO_REGISTRY`, `ROBOCO_VERSION`, `ROBOCO_DATA_DIR`, `ROBOCO_HOST_PROJECT_DIR`, `ROBOCO_HOST_CLAUDE_DIR` / `CLAUDE_AUTH_DIR`, `ROBOCO_HOST_DATA_DIR`, `ROBOCO_HOST_GROK_DIR`, `ROBOCO_HOST_CODEX_DIR`, `ROBOCO_HOST_GEMINI_DIR`, `ROBOCO_HOST_KIMI_DIR`, and `ROBOCO_BACKUP_MIRROR_DIR`. They are documented in the [production deploy reference](./deployment.md#required-host-path-mounts) and [Backups](/docs/deploy/backups).
## Transcript retention
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_TRANSCRIPT_RETENTION_DAYS` | `14` | Days to keep agent Claude Code transcripts. A stored panel setting overrides this default. |
| `ROBOCO_TRANSCRIPT_PRUNE_ENABLED` | `true` | Whether the background sweep prunes old transcripts. |
| `ROBOCO_TRANSCRIPT_PRUNE_INTERVAL_SECONDS` | `3600` | Minimum seconds between prune passes. |
## Notifications
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_NOTIFICATION_ACK_TTL_HOURS` | `48` | Hours until an ack-required notification's deadline is stamped at creation. Past that deadline, still unacknowledged, it re-escalates one level up on the backoff schedule below. `0` disables stamping — the legacy behavior, where a notification never expires. Informational (non-ack-required) notifications never get a deadline regardless of this setting. See [Notifications](/docs/panel/journals-and-notifications). |
| `ROBOCO_NOTIFICATION_REESCALATION_BASE_SECONDS` | `3600` | Base interval for the re-escalation backoff: the first re-escalation fires at expiry, each one after that doubles the wait from this base (1h, 2h, 4h, 8h, ...) capped at 24h between attempts — instead of re-firing every ~60s sweep tick forever. |
| `ROBOCO_NOTIFICATION_MAX_REESCALATIONS` | `5` | Hard cap on re-escalations per notification. Past this many attempts, a still-unacked notification is logged once as permanently-unacked and left alone for good. |
## Spawn pacing, SLAs & reaper windows
The orchestrator's dispatcher uses these to pace spawns, detect loops, and reclaim stuck work. Defaults are tuned for real LLM latency — raise the reaper windows (not lower) if long agent tasks are being reaped mid-work.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_AGENT_TOOL_CALL_WARN` | `100` | Soft warning threshold for per-session tool calls. |
| `ROBOCO_AGENT_TOOL_CALL_HALT` | `300` | Hard cap on per-session tool calls; the orchestrator stops the container. Raised from 150 — that ceiling repeatedly halted legitimate multi-file work seconds after a real commit, burning a spawn and the resumed agent's re-verification turns. |
| `ROBOCO_AGENT_LOOP_THRESHOLD` | `3` | Identical tool+args repeats in the window that flag a loop. |
| `ROBOCO_AGENT_LOOP_WINDOW` | `10` | How many recent tool calls to inspect for loop detection. |
| `ROBOCO_AGENT_STOP_ATTEMPT_ALLOWANCE` | `1` | Stop-without-terminal attempts before auto-substitute. |
| `ROBOCO_AGENT_SLA_DEVELOPER_IN_PROGRESS` | `7200` | SLA (s) for a developer in `in_progress`. |
| `ROBOCO_AGENT_SLA_DEVELOPER_VERIFYING` | `1800` | SLA (s) for a developer in `verifying`. |
| `ROBOCO_AGENT_SLA_QA_CLAIMED` | `1800` | SLA (s) for QA on a claimed review. |
| `ROBOCO_AGENT_SLA_DOCUMENTER_CLAIMED` | `3600` | SLA (s) for a documenter on a claimed task. |
| `ROBOCO_AGENT_SLA_CELL_PM_CLAIMED` | `14400` | SLA (s) for a cell PM on a claimed task. |
| `ROBOCO_CLAIM_STALE_SECONDS` | `180` | Claim-heartbeat staleness used by the spawn trigger filter. |
| `ROBOCO_STALE_CLAIM_REAP_SECONDS` | `600` | Reaper-only stale-claim threshold before releasing a claim back to pending. |
| `ROBOCO_PM_CLOSURE_RECENTLY_PAUSED_SECONDS` | `45` | Debounce before respawning a PM to close a recently paused parent. |
| `ROBOCO_GROK_IDLE_KILL_SECONDS` | `900` | Idle-container kill threshold for Grok agents (they emit no SDK heartbeat). |
| `ROBOCO_GROK_MAX_COST_USD` | `0.0` | Per-agent Grok cost ceiling (USD) before kill; `0` disables. |
| `ROBOCO_INTERACTIVE_IDLE_REAP_SECONDS` | `1800` | Idle-reap threshold for live intake/secretary chats; `0` disables. |
| `ROBOCO_CLAIMED_NO_AGENT_GRACE_SECONDS` | `120` | Grace window before respawning/releasing a claimed task with no running agent. |
| `ROBOCO_PM_DECISION_WINDOW_SECONDS` | `300` | Recency window for a PM `journal:decision` to satisfy gating verbs. |
| `ROBOCO_SPAWN_COOLDOWN_SECONDS` | `60` | Per-task spawn-rate cooldown. |
| `ROBOCO_ROLE_SPAWN_RATE_PER_MINUTE` | `6` | Per-role spawn-rate limit per minute. |
## Gateway: manifests & tracing-gate minimums
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_MANIFEST_HOST_DIR` | `/app/manifests` | Orchestrator dir where per-agent tool manifests are written; must be a host-bind-mounted path so the daemon can mount each manifest into its agent. |
| `ROBOCO_QA_NOTES_MIN_CHARS` | `80` | Minimum characters for QA notes. |
| `ROBOCO_DOCS_NOTES_MIN_CHARS` | `20` | Minimum characters for docs notes. |
| `ROBOCO_DEV_NOTES_MIN_CHARS` | `40` | Minimum characters for a developer's `dev_notes`. |
| `ROBOCO_PR_REVIEWER_NOTES_MIN_CHARS` | `40` | Minimum characters for a PR reviewer's notes. |
| `ROBOCO_QUICK_CONTEXT_MIN_CHARS` | `30` | Minimum characters for a PM's `quick_context` resumption section. |
| `ROBOCO_COMMIT_SUBJECT_MIN_CHARS` | `20` | Minimum characters for a commit subject. |
| `ROBOCO_COMMIT_BANNED_WORDS` | `wip,tmp,asdf,oops,fix,update,change,stuff,things` | Banned single-word commit subjects. |
## Grok runtime
Only relevant if you run any agent on Grok. See the [models section](/docs/models/grok) for the full runtime.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_HOST_GROK_DIR` | `/home/renzof/.grok` (compose) | Host `~/.grok` SuperGrok auth dir; mounted read-write into the orchestrator (token auto-refresh) and read-only into Grok agents. The same value is both the source and target path. |
| `ROBOCO_GROK_AGENT_IMAGE` | `roboco-agent-grok:latest` | Image the orchestrator spawns for Grok agents. |
| `ROBOCO_GROK_CLI_MODEL` | `grok-build` | Grok CLI model id. |
| `ROBOCO_GROK_REASONING_EFFORT` | *(empty)* | `low`/`medium`/`high`/`xhigh`/`max` for all Grok agents; empty keeps the model default. |
| `ROBOCO_GROK_MAX_TURNS` | `200` | Hard ceiling on agentic turns per Grok run (loop guard). |
| `ROBOCO_GROK_IDLE_KILL_SECONDS` | `900` | (see reaper table) Idle-kill window for a wedged Grok container. |
| `ROBOCO_GROK_MAX_COST_USD` | `0.0` | (see reaper table) Per-agent Grok cost ceiling. |
## Codex runtime
Only relevant if you run any agent on Codex. V1: delivery roles only, not Intake/Secretary. See the [models section](/docs/models/codex) for the full runtime.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_HOST_CODEX_DIR` | `~/.codex` | Host `~/.codex` ChatGPT-subscription auth dir (from `codex login`), mounted read-only as a directory into each Codex agent. |
| `ROBOCO_CODEX_CLI_MODEL` | `gpt-5.3-codex` | Codex CLI model id — Codex has no reliable default, so this is always set explicitly. |
| `ROBOCO_CODEX_OAUTH_CLIENT_ID` | *(unset — built-in default)* | Override the OIDC client id used for the token-refresh grant, if the default is wrong for your account. A bad refresh never mutates `auth.json` — worst case is a parked provider. |
## Gemini runtime
Only relevant if you run any agent on Gemini. V1: delivery roles only, not Intake/Secretary. See the [models section](/docs/models/gemini) for the full runtime.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_HOST_GEMINI_DIR` | `~/.gemini` | Host `~/.gemini` OAuth login dir (from the interactive `gemini` login), mounted read-only; each container copies it into a writable local dir and refreshes its own copy in-process — no orchestrator refresh daemon needed. |
| `ROBOCO_GEMINI_CLI_MODEL` | `gemini-2.5-pro` | Gemini CLI model id — also accepts `gemini-2.5-flash` / `gemini-2.5-flash-lite`. |
| `ROBOCO_GEMINI_MAX_TURNS` | `200` | Hard ceiling on agentic turns per Gemini run (loop guard, Grok parity). |
| `ROBOCO_GEMINI_RATE_LIMIT_RETRY_AFTER_SECONDS` | `60.0` | Base park-and-retry delay after a quota/rate-limit exit. |
| `ROBOCO_GEMINI_AUTH_RETRY_AFTER_SECONDS` | `60.0` | Park-and-retry delay after a missing/invalid OAuth credential (entrypoint preflight failure). |
## Kimi runtime
Only relevant if you run any agent on Kimi. V1: delivery roles only, not Intake/Secretary. See the [models section](/docs/models/kimi) for the full runtime.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_HOST_KIMI_DIR` | `~/.kimi-code` | Host `~/.kimi-code` Kimi-subscription auth dir (from `kimi login`), mounted **read-write and shared** into every Kimi agent plus the orchestrator — Moonshot's refresh token rotates with only a short reuse grace, so every container redeems the same chain instead of an independent copy. |
| `ROBOCO_KIMI_CLI_MODEL` | `kimi-code/k3` | Kimi CLI model alias — also accepts `kimi-code/kimi-for-coding` (K2.7) as a cheaper lever. |
## OpenRouter runtime
Only relevant if you run any agent on OpenRouter. The fleet default is whatever model you picked from the live catalog. See the [models section](/docs/models/openrouter) for the full runtime.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_OPENROUTER_BASE_URL` | `https://openrouter.ai/api/v1` | API base for the OpenRouter provider. |
## Nebius runtime
Only relevant if you run any agent on Nebius Token Factory. See the [models section](/docs/models/nebius) for the full runtime.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_NEBIUS_BASE_URL` | `https://api.tokenfactory.nebius.com/v1` | Token Factory OpenAI-compatible API base. |
| `ROBOCO_NEBIUS_CLI_MODEL` | `nvidia/nemotron-3-super-120b-a12b` | Default Nebius model id (Nemotron 3 Super) used when the mode or role has no other assignment. |
| `ROBOCO_NEBIUS_RATE_LIMIT_RETRY_AFTER_SECONDS` | `60.0` | Park-and-retry delay after a 429. |
| `ROBOCO_NEBIUS_AUTH_RETRY_AFTER_SECONDS` | `60.0` | Park-and-retry delay after an auth preflight failure. |
## Hummin runtime (GLM)
Only relevant if you run any agent on the hummin GLM CLI. See the [models section](/docs/models/hummin) for the full runtime.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_HUMMIN_CLI_MODEL` | `glm-5.3-flash:high` | Default GLM model id. Applying hummin mode seeds role tiers from it: board/reviewer/PM roles on `:high`, delivery roles on `:low`. |
| `ROBOCO_HUMMIN_RATE_LIMIT_RETRY_AFTER_SECONDS` | `60.0` | Park-and-retry delay after a GLM rate limit. |
| `ROBOCO_HUMMIN_AUTH_RETRY_AFTER_SECONDS` | `60.0` | Park-and-retry delay after the hummin auth preflight fails. |
| `ROBOCO_HUMMIN_MAX_CONCURRENT` | *(uncapped)* | Cap on concurrent hummin sessions, if you want one. |
## Token Factory Sandboxes
Flags for the `run_sandbox_tests` gateway verb (QA test runs inside a Nebius Token Factory Sandbox microVM). Everything is inert while the flag is off; see [Run on Nebius](/docs/models/nebius).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_TOKEN_FACTORY_SANDBOXES_ENABLED` | `false` | Master switch (also on the Feature Flags card). Requires a saved Nebius key; spawn permission must be granted console-side. |
| `ROBOCO_TOKEN_FACTORY_SANDBOXES_BASE_URL` | `https://api.tokenfactory.nebius.com/sandboxes` | Sandboxes API base (the client appends `/v1`). |
| `ROBOCO_TOKEN_FACTORY_SANDBOXES_IMAGE` | `tag:python:3.12` | Default sandbox image; a verb call may override it. |
| `ROBOCO_TOKEN_FACTORY_SANDBOXES_TIMEOUT_SECONDS` | `900` | Default run deadline; a verb call may pass its own (30 to 3600s), and the instance is cancelled best-effort at the deadline. |
| `ROBOCO_TOKEN_FACTORY_SANDBOXES_MAX_ARCHIVE_BYTES` | 64 MiB | Upload ceiling for the archived workspace. |
## Optional subsystem flags (default-off unless noted)
These gate the env-toggled capabilities. Each is inert when off. See [Optional capabilities](/docs/optional).
### Web research — default **on**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_RESEARCH_ENABLED` | `true` | Master switch for web research. When `false`, the search MCP is not mounted into any agent. |
| `ROBOCO_RESEARCH_PROVIDER` | `tavily` | `tavily` / `brave` / `exa` / `null`. |
| `ROBOCO_RESEARCH_API_KEY` | *(unset)* | Provider key — **server-side only**, never reaches an agent. Unset = empty-result null provider. |
| `ROBOCO_RESEARCH_MAX_RESULTS` | `5` | Cap on results per search (1–20). |
| `ROBOCO_RESEARCH_FETCH_MAX_CHARS` | `20000` | Cap on extracted characters per fetch. |
| `ROBOCO_RESEARCH_TIMEOUT_SECONDS` | `15.0` | Per-request outbound timeout. |
| `ROBOCO_RESEARCH_DAILY_QUOTA_PER_AGENT` | `50` | Search+fetch calls per agent per UTC day. |
### GitHub repo provisioning — default **on** (inert without token/org)
Creating brand-new repos from an approved pitch is GitHub-only — see [Pitch provisioning](/docs/optional/pitch-provisioning). This is distinct from a project's per-project **Forge** selection (GitHub, Gitea, or GitLab), which routes PR/CI/review operations against a repo that already exists — that part works on all three forges. See [Choosing a forge](/docs/get-started/first-project#choosing-a-forge).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_PROVISIONING_ENABLED` | `true` | Master switch for pitch auto-provisioning. Inert with no token/org regardless. |
| `ROBOCO_PROVISIONING_TOKEN` | *(empty)* | GitHub PAT (repo + org admin) used to create repos — server-side only. |
| `ROBOCO_PROVISIONING_ORG` | *(empty)* | GitHub org where new repos are created. |
| `ROBOCO_GITHUB_API_BASE_URL` | `https://api.github.com` | Override for GitHub Enterprise — used both for provisioning and, per-project, whenever a project's Forge is set to GitHub / GitHub Enterprise. Gitea and GitLab need no equivalent variable: their API base is derived from the project's own Git URL host. See [Choosing a forge](/docs/get-started/first-project#choosing-a-forge). |
| `ROBOCO_PROVISIONING_TIMEOUT_SECONDS` | `30.0` | Per-request provisioning timeout. |
| `ROBOCO_PROVISIONING_REPO_PRIVATE` | `true` | Whether provisioned repos are private. |
### Architectural conventions — **off** (config) / **on** (compose)
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_CONVENTIONS_ENABLED` | `false` (config) / `true` (compose) | Master switch for the per-project conventions standard (scaffold, ambient injection, baseline constraints, gate enforcement). The compose orchestrator block defaults this **on** (left off in `docker-compose.registry.yml`); fully inert when off. |
### Toolchain matching — default **off**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_TOOLCHAIN_MATCH_ENABLED` | `false` (config) / `true` (compose) | Provision the agent workspace with the target project's Python and block delivery gates when the suite can't run. The compose orchestrator block defaults this **on**. |
### Provider overload break — default **on**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_OVERLOAD_BREAK_ENABLED` | `true` | Park a provider on a persistent overload (HTTP 529/500/503) the way a 429 is parked, instead of crash-retrying. |
| `ROBOCO_GATEWAY_HEALTH_ENABLED` | `true` | Probe a stale-heartbeat-but-live agent's gateway and kill + respawn it when the gateway is broken (a corrupted `/app` venv firing no verb), instead of the reaper protecting it forever. Off => spare live containers on verb-heartbeat liveness alone. |
| `ROBOCO_GATEWAY_HEALTH_GRACE_SECONDS` | `180` | How long an agent gateway may probe as broken before recovery — tolerates a transient probe miss. |
| `ROBOCO_IMAGE_PRUNE_ENABLED` | `true` | Background sweep prunes dangling (``) Docker images left by agent-image rebuilds, throttled ~6h. Only dangling images are removed — a tagged image or one backing a running container is never touched. Not a feature flag; disable to manage image cleanup yourself. |
### PR-gate turn cut — default **on**
Not a subsystem toggle — a delivery-flow behavior. See [the merge model](/docs/company/merge-model).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_PR_GATE_AUTO_SUBMIT_ENABLED` | `true` | When every child of an assembled parent is terminal, run the PM's `submit_up` / `submit_root` to the in-path PR gate system-side (as the owning PM) instead of spawning the PM just to press submit — the submit's substance (freshness rebase, integrity check, PR open) is deterministic gate code. A gate rejection falls back to the classic PM closure spawn, and the PM keeps its judgment turns (merge, revision). Off => every closure spawns the PM to submit. |
### Strategy engine — default **off**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_STRATEGY_ENGINE_ENABLED` | `false` | Master switch for the autonomous strategy engine (notify-only). When off the loop never runs. |
| `ROBOCO_STRATEGY_ENGINE_INTERVAL_SECONDS` | `1800` | Seconds between assessment passes. |
| `ROBOCO_STRATEGY_STRANDED_BLOCKED_MINUTES` | `120` | A task blocked longer than this is surfaced as stranded. |
### Possibilities matrix — default **off**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_POSSIBILITIES_MATRIX_ENABLED` | `false` | Master switch for the `i_am_done` fast path. Off: every task follows the standard multi-turn plan/verify/submit flow regardless of how complete it looks. On: a task with commits, an open PR, full acceptance-criteria coverage, and no open findings can submit to QA in one call, with PR CI-green standing in for the local quality gate (falling back to the local gate + toolchain check when no CI signal exists). Conventions and findings enforcement are never skipped. |
### Task & project cost budgets — default **off** (config) / **on** in the NAS compose
Project `monthly_budget_usd` and task `budget_usd` are project/task fields set in the panel, independent of this flag; the flag decides whether either is ever consulted. See [Task & project cost budgets](/docs/optional/task-budgets).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_TASK_BUDGETS_ENABLED` | `false` (config) / `true` (`docker-compose.yml`) / `false` (`docker-compose.registry.yml`) | Master switch. Off: neither cap is ever consulted regardless of field values. On: a work-starting claim is refused once a project's monthly spend cap is reached (review/doc/gate/inbound-PR claims are exempt), and a background sweep blocks an over-budget task (falling back to a per-`TaskType` default when `budget_usd` is null), notifying the CEO. |
### External / internal PR review — default **off**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_EXTERNAL_PR_ENABLED` | `false` (config) / `true` (compose) | Master switch for inbound external/fork PR review. The compose orchestrator block defaults this on. |
| `ROBOCO_EXTERNAL_PR_POLL_INTERVAL_SECONDS` | `300` | Seconds between inbound external-PR discovery passes. |
| `ROBOCO_EXTERNAL_PR_AUTHOR_ALLOWLIST` | *(empty)* | GitHub usernames auto-trusted. Empty = every external PR needs human confirmation. |
| `ROBOCO_EXTERNAL_PR_REQUIRE_HUMAN_CONFIRM` | `true` | Require explicit human confirmation before any agent fetches/checks-out/executes external code. |
| `ROBOCO_INTERNAL_PR_ENABLED` | `false` | Also review org-repo (non-fork) PRs not tied to an active task. |
### Self-healing CI loop — default **off**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_SELF_HEAL_ENABLED` | `false` | Master switch for the self-heal loop (detect + notify the CEO). When off the loop never runs. |
| `ROBOCO_SELF_HEAL_PROJECT_SLUG` | *(empty)* / `roboco-api` (compose) | The registered project that *is* RoboCo itself — the only repo the loop watches/originates into. |
| `ROBOCO_SELF_HEAL_CI_WORKFLOW` | `ci.yml` | GitHub Actions workflow file to scope the CI signal to. |
| `ROBOCO_SELF_HEAL_ORIGINATE_ENABLED` | `false` | Second opt-in: on a regression, also open a fix task and dispatch it to the Main PM automatically (no manual start). The loop never merges or deploys — the fix ships through the normal gates (QA, PR review, your merge). |
| `ROBOCO_SELF_HEAL_INTERVAL_SECONDS` | `1800` | Seconds between telemetry passes. |
| `ROBOCO_SELF_HEAL_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open self-heal tasks. |
| `ROBOCO_SELF_HEAL_MAX_PER_CYCLE` | `1` | Max self-heal tasks originated in one cycle. |
### Multi-repo CI-watch — default **off**
The global switch arms the engine; each project opts in via `ci_watch_enabled` (+ optional `ci_watch_workflow`) on its settings page.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_CI_WATCH_ENABLED` | `false` | Master switch for watching opted-in projects' CI. When off the engine never runs and no CI telemetry is fetched. |
| `ROBOCO_CI_WATCH_DEFAULT_WORKFLOW` | `ci.yml` | Workflow file to scope the CI signal to when a project sets no `ci_watch_workflow` of its own. |
| `ROBOCO_CI_WATCH_INTERVAL_SECONDS` | `1800` | Seconds between CI-watch passes. |
| `ROBOCO_CI_WATCH_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open CI-watch fix tasks per repo. |
| `ROBOCO_CI_WATCH_MAX_PER_CYCLE` | `1` | Max CI-watch fix tasks opened in one cycle. |
### Dependency-update bot — default **off**
The global switch arms the engine; each project opts in via `dep_update_command` (+ optional `dep_update_paths`) on its settings page. Detection is read-only — the command runs in a throwaway clone and only the lockfiles are diffed; the real repo is never mutated.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_DEP_UPDATE_ENABLED` | `false` | Master switch for the dependency-update bot. When off nothing runs and no throwaway clone is made. |
| `ROBOCO_DEP_UPDATE_INTERVAL_SECONDS` | `604800` | Seconds between dependency-update passes (default weekly). |
| `ROBOCO_DEP_UPDATE_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open update-dependencies tasks per repo. |
| `ROBOCO_DEP_UPDATE_MAX_PER_CYCLE` | `1` | Max update-dependencies tasks opened in one cycle. |
### Environment branches & EnvSync — default **off**
A project's environment ladder (an ordered list of `{name, branch}` rungs) is project configuration, set on its settings page — always available regardless of this flag. `ROBOCO_ENV_SYNC_ENABLED` only arms the automatic cascade between rungs. See [Environment Branches & EnvSync](/docs/optional/env-branches).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_ENV_SYNC_ENABLED` | `false` | Master switch for the ladder auto-cascade. Off: a project's ladder still resolves the PR-target branch and the release branch, but nothing syncs the middle rungs automatically. On: a periodic pass merges each rung down into the next; a clean merge auto-pushes, a conflict opens one sync PR + one coordination task and stops that project's cascade for the cycle. Never pushes to the last (release) rung. |
| `ROBOCO_ENV_SYNC_INTERVAL_SECONDS` | `1800` | Seconds between cascade passes. |
| `ROBOCO_ENV_SYNC_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open env-sync conflict tasks across all repos. |
| `ROBOCO_ENV_SYNC_MAX_PER_CYCLE` | `1` | Max projects cascaded in one cycle. |
### Docs-Sync — default **off**
No polling interval — triggered directly off the gated release manager's publish step, not a background loop. Requires the docs site itself to be registered as a RoboCo project with a git token. See [Docs-Sync](/docs/optional/docs-sync).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_DOCS_SYNC_ENABLED` | `false` | Master switch. Off: publishing a release never opens a docs-update task. On: a publish checks the registered docs-site project for drift and, if found, opens one task that rides the normal delivery flow (dev → QA → PR-review gate → your merge); never auto-merges. With no docs-site project registered, it skips silently regardless of this flag. |
| `ROBOCO_DOCS_SYNC_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open docs-sync tasks. |
| `ROBOCO_DOCS_SYNC_MAX_PER_CYCLE` | `1` | Max docs-sync tasks opened per release publish. |
### HTTP security guard (fastapi-guard) — off (config) / **active** on the NAS compose
Not a panel flag — see [HTTP security](/docs/optional/http-security). The registry compose omits this whole trio and stays off.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_GUARD_ENABLED` | `false` | Master switch. Off: `create_app` never mounts the middleware; the request path is byte-for-byte unchanged. |
| `ROBOCO_GUARD_PASSIVE_MODE` | `true` (config) / `false` (NAS compose) | `true` detects and logs only, never blocking. `false` actually blocks a matching request. The NAS compose flips this to enforce once passive-mode calibration reviewed clean. |
| `ROBOCO_GUARD_FAIL_SECURE` | `true` (config) / `true` (NAS compose, as of v0.29.0) | What happens when a security check itself errors: `true` fails closed (blocks). As of v0.29.0 the NAS compose also enforces fail_secure — the earlier `false` relaxation was a holdover from the passive-mode calibration phase. |
| `ROBOCO_GUARD_EMERGENCY` | `false` | Kill-switch: blocks every non-whitelisted IP, flippable without a redeploy during an active attack. |
| `ROBOCO_GUARD_EMERGENCY_WHITELIST` | *(empty)* | Comma-separated IPs exempted from the emergency lockdown above. |
| `ROBOCO_GUARD_TELEMETRY_ENABLED` | `false` | Reports security events/metrics to a guard-core platform via guard-agent. No data leaves the box while off. As of v0.29.0, telemetry payloads exclude HMAC/session headers via `agent_sensitive_headers`. |
| `ROBOCO_GUARD_AGENT_API_KEY` | *(empty)* | guard-agent API key — required when telemetry is enabled. |
| `ROBOCO_GUARD_PROJECT_ID` | *(empty)* | guard-core project id — required when telemetry is enabled. |
| `ROBOCO_GUARD_TRUSTED_CHAIN_PEERS` | *(empty)* | Comma-separated exact IP address(es) — never a CIDR range — of a host-proxy hop (e.g. Tailscale Serve's docker-bridge gateway) in front of nginx, trusted to appear as a recorded proxy hop in `X-Forwarded-For` when resolving the real client behind it. Empty: only a loopback rightmost hop peels. |
| `ROBOCO_GUARD_SCAN_RESPONSE_BODY` | `false` | Lets `return_pattern` rules read response bodies, not just status codes. Default off — Roboco's own rules never need it. |
| `ROBOCO_GUARD_LOG_SUSPICIOUS_LEVEL` | `WARNING` | Log level for suspicious-request entries. |
### Gated release manager (default-off)
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_RELEASE_MANAGER_ENABLED` | `false` | Master switch for the gated release manager. When off the loop never runs and no release is proposed. Even on it only PROPOSES — the CEO approves before any publish. |
| `ROBOCO_RELEASE_MIN_COMMITS` | `8` | Minimum unreleased commits since the last tag before a release is proposed (a feat/security change also qualifies). |
| `ROBOCO_RELEASE_MANAGER_INTERVAL_SECONDS` | `3600` | Seconds between release-readiness assessment passes. |
| `ROBOCO_RELEASE_GIT_NAME` | `RoboCo Release Manager` | Committer name on the release commit created by `ReleaseExecutor`. |
| `ROBOCO_RELEASE_GIT_EMAIL` | `release-manager@roboco.local` | Committer email on the release commit. |
| `ROBOCO_RELEASE_SIGN_COMMITS` | `false` | Set to `true` to GPG-sign the release commit. The executor only signs when this is explicitly enabled; the default is unsigned. |
### Organizational memory loop (default-off)
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_ORG_MEMORY_ENABLED` | `false` | Master switch for the org-memory loop. When off: legacy completion capture, no auto-inject, no playbook curation verbs. |
| `ROBOCO_ORG_MEMORY_TOP_K` | `3` | Max institutional-memory items injected into a briefing on claim. |
| `ROBOCO_ORG_MEMORY_MIN_SCORE` | `0.6` | Cosine-similarity floor for injected memory; below it, nothing is injected. |
### Sandboxed dev DB/Redis/Mongo — default **off**
Per-project opt-in (`sandbox_services` column, postgres/redis/mongo); see [Sandboxed dev DB/Redis/Mongo](/docs/optional/sandbox-db).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_SANDBOX_DB_ENABLED` | `false` | Master switch. When off, `request_sandbox` refuses every call and spawning is unaffected (the legacy prod-creds gate-env injection, itself gated by `ROBOCO_TOOLCHAIN_MATCH_ENABLED`, is what an opted-in project falls back to). Only projects with `sandbox_services` set participate even when on — and provisioning happens on-demand via the `request_sandbox` do-verb, not at spawn. |
### DB network isolation — default **off** (config) / **on** in all 3 tracked composes
Not a panel flag — it must travel with the compose `networks:` topology. See [DB network isolation](/docs/optional/db-network-isolation).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_DB_NETWORK_ISOLATED` | `false` (config) / `true` (all 3 tracked composes) | True when postgres/redis sit on a data-only `roboco_data` network agent containers never join. Suppresses the legacy prod-creds gate-env injection (unreachable creds are worse than none). |
### Cloud auth — default **off**
Not a panel flag — changes authentication behavior, so it's environment-only. See [Cloud auth](/docs/optional/cloud-auth).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_CLOUD_AUTH_ENABLED` | `false` | Master switch. Off: `get_agent_context` behaves byte-for-byte as today (header-trust). On: a valid session cookie or agent HMAC token is required for the CEO role; a spoofed `ceo` header is rejected. Requires TLS — the cookie is secure-only. |
| `ROBOCO_CLOUD_AUTH_EMAIL` | *(unset)* | Email for the single seeded CEO login user. Required when the flag is on. |
| `ROBOCO_CLOUD_AUTH_PASSWORD` | *(unset)* | Password for the single seeded CEO login user. Hashed at startup; required when the flag is on. |
| `ROBOCO_CLOUD_AUTH_SECRET` | *(unset — required when enabled)* | Signs the session JWT. **Startup fails loud** if the flag is on and this is unset. |
| `ROBOCO_CLOUD_AUTH_COOKIE_MAX_AGE` | `2592000` (30 days) | Sliding session cookie lifetime in seconds — every authenticated request re-mints the cookie, so only genuine inactivity past this window logs out. |
### Fable + Ponytail doctrine — default **off**
| Variable | Default | Notes |
|----------|---------|-------|
| `ROBOCO_FABLE_MODE_ENABLED` | `false` | Composes the Fable (outcome-first communication) and Ponytail (build-lazy) doctrine layers into every spawned agent's prompt, plus guard hooks on the Claude runtime. Off: the spawn path is byte-for-byte unchanged. |
| `ROBOCO_PONYTAIL_INTENSITY` | `full` | Developer-ladder aggressiveness: `lite` / `full` / `ultra`. A string value, not a flag; only read when fable-mode is on. Non-developer roles ignore it. |
### Telegram notifications — default **off**
The bot token and chat id are entered in the collapsible under the *Telegram notifications* flag row in **Settings → Feature Flags**, Fernet-encrypted at rest — never set via environment. The Mini App's own switch is environment-only, like cloud auth itself, and requires `ROBOCO_CLOUD_AUTH_ENABLED=true`. See [Telegram bridge](/docs/optional/telegram-bridge).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_TELEGRAM_ENABLED` | `false` | Master switch. Off: no Telegram API call is ever made. On (with credentials stored): sends one best-effort DM — subject plus a panel deep-link, never the body — on a CEO escalation, on task completion, and whenever any of the four held-draft queues — a release proposal, an X post, a video, or a roadmap review cycle — originates a new item. |
| `ROBOCO_TELEGRAM_INBOUND_ENABLED` | `false` | Sub-switch on top of the master switch. Off: the bot only sends the three DMs above, never polls, and the escalation DM and every held-draft-origination DM carry no buttons. On (with `ROBOCO_TELEGRAM_ENABLED` and credentials both set): polls for `/status`, `/queue`, `/task`, `/agents`, `/blocked`, `/usage`, `/secretary`, `/newtask`, and button taps, and the escalation DM and every held-draft-origination DM gain an Approve/Reject/Open row. |
| `ROBOCO_TELEGRAM_TIMEOUT_SECONDS` | `10.0` | Per-request timeout for the outbound Bot API `sendMessage` call. |
| `ROBOCO_TELEGRAM_POLL_INTERVAL_SECONDS` | `5.0` | Floor between `getUpdates` long-poll re-issues (each call itself blocks server-side up to the poll timeout below, so this isn't the effective latency). |
| `ROBOCO_TELEGRAM_POLL_TIMEOUT_SECONDS` | `25` | The `getUpdates` long-poll `timeout` param, in seconds. |
| `ROBOCO_TELEGRAM_MAX_UPDATES_PER_CYCLE` | `50` | Max inbound updates (messages + button taps) processed in one poll cycle. |
| `ROBOCO_TELEGRAM_PENDING_REPLY_TTL_SECONDS` | `300.0` | How long a "reply with your reason" prompt (e.g. after tapping Reject) stays live before it expires and you have to tap the button again. |
| `ROBOCO_TELEGRAM_MINIAPP_ENABLED` | `false` | Master switch for the Mini App (`POST /api/telegram/webapp-auth`), the `/tg` phone cockpit. Environment-only — not on the panel's Feature Flags card. Requires `ROBOCO_CLOUD_AUTH_ENABLED=true`; the orchestrator **refuses to start** if this is on without that. |
| `ROBOCO_TELEGRAM_INITDATA_MAX_AGE_SECONDS` | `600` | Max age, in seconds, of a Telegram-signed initData payload before Mini App sign-in refuses it as stale. |
### X (Twitter) engine — default **off**
Credentials (the 4 OAuth 1.0a secrets) are entered in **Settings → X (Twitter) Credentials** in the panel, Fernet-encrypted at rest — never set via environment. See [X (Twitter) engine](/docs/optional/x-engine).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_X_ENGINE_ENABLED` | `false` | Master switch. Off: no draft is ever originated and no X API call is made. |
| `ROBOCO_X_REPLIES_ENABLED` | `false` | Sub-switch for the mention-reply half specifically (needs a paid X API tier to read mentions). Off: only release-announcement posts draft, even with the master switch on. |
| `ROBOCO_X_MENTIONS_INTERVAL_SECONDS` | `1800` | Seconds between mentions-poll passes. |
| `ROBOCO_X_MENTIONS_MAX_PER_CYCLE` | `5` | Max held reply drafts the mentions poll may originate in one cycle. |
| `ROBOCO_X_MENTIONS_MIN_ENGAGEMENT` | `0` | Minimum combined like+reply+retweet count for a mention to count as worth replying to. |
| `ROBOCO_X_MAX_OPEN_POSTS` | `10` | Rolling cap on concurrently-open held posts/replies (both sources combined). |
| `ROBOCO_X_ACCOUNT_USER_ID` | *(unset)* | Numeric X user id of the account's own account. Empty resolves it once per mentions cycle via `GET /2/users/me`. |
| `ROBOCO_X_REQUEST_TIMEOUT_SECONDS` | `15.0` | Per-request timeout for outbound X API calls. |
| `ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED` | `false` | A third, independent sub-switch: periodically spawns the Head of Marketing to investigate RoboCo's own shipped, under-publicized capabilities and draft a spotlight post about one. Off: the master switch above only ever drafts release posts and mention replies — this half never spawns an agent. Unlike those two (local-model-only), this is a real cloud-LLM spawn per cycle, so it's a deliberate, costlier opt-in. |
| `ROBOCO_X_FEATURE_SPOTLIGHT_INTERVAL_SECONDS` | `86400` | Base seconds between feature-spotlight exploration cycles. A quiet-week guard stretches the effective cadence to 3× this when nothing has shipped since the last spotlight, so it doesn't fire daily against a stale codebase. |
### Board Programs — default **off**, no master flag
See [Board Programs](/docs/optional/board-programs). Unlike every other subsystem on this page, arming is entirely per-program on the **Business → Programs** panel tab (`board_program..enabled` in the settings store) — there is no `ROBOCO_BOARD_PROGRAMS_ENABLED`. The variable below is the only environment-level knob a *new* program reads; Roadmap and the X feature spotlight (`ROBOCO_X_ENGINE_ENABLED` + `ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED`, in the [X (Twitter) engine](/docs/optional/x-engine) section above) additionally keep their own pre-registry flags as the *default* their Programs-tab switch falls back to.
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_PEST_REWORK_THRESHOLD` | `0.3` | 7-day rework rate (0-1) above which the Pest Control program opens a cycle off-schedule, on top of its weekly cron. |
### Board roadmap engine — default **off**
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_ROADMAP_ENGINE_ENABLED` | `false` | Master switch. Off: no exploration cycle is originated and the Product Owner is never spawned for it. Even on, nothing auto-starts — approved items land in BACKLOG for normal PM activation. Doubles as the default for the `roadmap` program's switch on **Business → Programs**. |
| `ROBOCO_ROADMAP_INTERVAL_SECONDS` | `604800` | Seconds between roadmap-exploration cycles (default weekly). |
| `ROBOCO_ROADMAP_MIN_ITEMS_PER_CYCLE` | `3` | Minimum roadmap item drafts a themed cycle must propose. |
| `ROBOCO_ROADMAP_MAX_ITEMS_PER_CYCLE` | `7` | Maximum roadmap item drafts a themed cycle may propose. |
### Obsidian vault — default **off**
Not a panel flag — see [Obsidian vault](/docs/optional/obsidian-vault).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_OBSIDIAN_VAULT_ENABLED` | `false` | Master switch. Off: no note is ever written, `python -m roboco.vault` refuses to run, and every event seam is a no-op. |
| `ROBOCO_VAULT_PATH` | `/data/vault` | Root directory the vault materializes into. Only consulted when the master flag is on. |
| `ROBOCO_VAULT_INTAKE_ENABLED` | `false` | Second switch for the `#roboco` inbox watcher; both this and the master flag must be on. |
| `ROBOCO_VAULT_INTAKE_INTERVAL_SECONDS` | `300` | Seconds between inbox scan cycles. |
| `ROBOCO_VAULT_INTAKE_DIR` | `RoboCo/Inbox` | Vault-relative folder scanned for `#roboco`-tagged notes. |
| `ROBOCO_VAULT_INTAKE_MAX_PER_CYCLE` | `3` | Max held drafts one scan cycle may originate. |
| `ROBOCO_VAULT_INTAKE_MAX_OPEN_DRAFTS` | `10` | Rolling cap on concurrently-open held vault-note drafts. |
| `ROBOCO_VAULT_ARCHIVE_DAYS` | `30` | Age (past terminal timestamp) at which a completed/cancelled task's note moves to `RoboCo/Archive//`. `0` disables archival. |
| `ROBOCO_VAULT_REPORT_ENABLED` | `true` | Materializes a weekly `RoboCo/Reports/.md` org-report note (deterministic, no LLM) and notifies you. Needs the master vault flag on. |
| `ROBOCO_VAULT_KB_ENABLED` | `false` | Second switch: embeds your own note folders (default `RoboCo/Notes`) into a fleet-retrievable RAG corpus. Off: no note is ever embedded. |
| `ROBOCO_VAULT_KB_DIRS` | `RoboCo/Notes` | CSV of vault-relative folders scanned recursively for KB ingest. Rejected at startup if it overlaps the intake inbox or a reserved projection dir. |
| `ROBOCO_VAULT_KB_INTERVAL_SECONDS` | `900` | Seconds between vault-KB ingest scan cycles. |
### Video engine (HyperFrames) — default **off**
TikTok's OAuth2 secrets are entered in **Settings → TikTok Credentials** in the panel, Fernet-encrypted at rest — never set via environment. See [Video engine](/docs/optional/video-engine).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_VIDEO_ENGINE_ENABLED` | `false` | Master switch. Off: no video-authoring task is ever opened. Even on, distribution requires an explicit per-clip CEO approval. |
| `ROBOCO_VIDEO_ON_RELEASE` | `false` | Sub-switch: open an authoring task when a release publishes. Off even with the master switch on. |
| `ROBOCO_VIDEO_ON_SPOTLIGHT` | `false` | Sub-switch: open an authoring task when the CEO approves a feature-spotlight draft that requests one. Off even with the master switch on. |
| `ROBOCO_VIDEO_MAX_OPEN_POSTS` | `5` | Rolling cap on concurrently-open video tasks (authoring plus held post drafts combined). |
| `ROBOCO_VIDEO_RENDERER_BASE_URL` | `http://roboco-video-renderer:3001` | Base URL of the `video-renderer` sidecar. |
| `ROBOCO_VIDEO_OUTPUT_DIR` | `/data/video-renders` | Where rendered MP4s are written. Bind-mounted in all three compose files so renders survive container recreation. |
| `ROBOCO_VIDEO_RENDER_INTERVAL_SECONDS` | `120` | Seconds between render-loop passes. |
| `ROBOCO_VIDEO_RENDER_TIMEOUT_SECONDS` | `600` | Deadline for one render pass on the sidecar. |
| `ROBOCO_VIDEO_REQUEST_TIMEOUT_SECONDS` | `30` | Per-request timeout for outbound video-engine HTTP calls. |
### Object storage (MinIO) — default **off**
Landed in 0.19.0: config fields, the `minio` dependency, `minio` / `minio-init` compose services, and the write + serve paths. The orchestrator PUTs each render to MinIO after the local write (non-fatal on failure); the media route streams from MinIO when configured, falling back to `FileResponse` on a missing object or MinIO down. Empty endpoint = disabled and the existing `FileResponse` media-serve path is unchanged. See [Deployment](/docs/deploy/deployment).
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_MINIO_ENDPOINT` | `` (empty) | MinIO endpoint, e.g. `http://roboco-minio:9000`. Empty = disabled (`FileResponse` fallback). |
| `ROBOCO_MINIO_ACCESS_KEY` | `` | Access key. Required when endpoint is set. |
| `ROBOCO_MINIO_SECRET_KEY` | `` | Secret key. Required when endpoint is set. |
| `ROBOCO_MINIO_BUCKET` | `roboco-video-renders` | Bucket for rendered videos. Created idempotently by `minio-init`. |
| `ROBOCO_MINIO_REGION` | `us-east-1` | MinIO region. |
## Next
- **[Production deploy](/docs/deploy/deployment)** — compose files, host mounts, secure mode, startup.
- **[Optional capabilities](/docs/optional)** — what each flag above turns on.
- **[Settings panel](/docs/panel/settings)** — toggling flags from the UI instead of `.env`.
================================
URL: https://docs.roboco.tech/docs/deploy/data-and-migrations
Title: Data & Migrations
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
RoboCo keeps all of its state in PostgreSQL. There is no separate document store, no schema you have to hand-build, and — in normal operation — no migration command you have to remember. This page gives you the operator's-eye view: the handful of entities worth understanding, the one hard database requirement (pgvector), and how the schema keeps itself up to date.
## The entities you'll actually see
The full data model spans about thirty Pydantic models in `roboco/models/` with matching SQLAlchemy tables in `roboco/db/tables.py`. You don't need most of them. These are the ones that show up across the panel and explain how work flows:
| Entity | What it is |
|--------|------------|
| **Task** | The atomic unit of work. Carries its acceptance criteria, status (the 15-state [lifecycle](/docs/company/task-lifecycle)), priority, branch name, PR number/URL, ownership (`created_by` / `assigned_to` / `team`), and its place in the tree (`parent_task_id`, `dependency_ids`, `blocker_ids`). A task points at **exactly one** of a `project_id` or a `product_id` — a single-repo task uses the project, a Board/fan-out coordination task uses the product. A validator enforces "one or the other, never both." |
| **Project** | A git repository configuration: `git_url`, default branch, protected branches, the Fernet-encrypted GitHub PAT (`git_token_encrypted` — the API only ever returns `has_git_token`, never the token), the per-project CI/gate commands, and the assigned cell. See [Register your first project](/docs/get-started/first-project). |
| **WorkSession** | The link between an agent, a task, and a branch. One is created each time an agent claims a task. It tracks the base/target branches, the commits made, the files modified, the PR and merge outcome, and (when toolchain matching is on) the resolved Python version and toolchain status. |
| **Agent** | One row per member of the workforce — role, team, status — plus the human CEO. See [Org & roles](/docs/company/org-and-roles). |
| **Notification** | A formal signal that requires acknowledgment (sent by PMs and the Board), distinct from ad hoc agent communication. |
| **Journal** | Each agent's personal log of reflections and learnings, with `JournalEntry` rows underneath. |
Two newer entities round out the company-in-a-box features: **Product** (with `ProductProject`, the per-cell repo-routing map for fan-out work) and **Pitch** (a Board proposal that the CEO approves and that can auto-provision a repo). You'll meet these on the [Business](/docs/panel/business) page.
A handful of more recent additions, all inert while their feature flag is off: **`users`** (the single seeded CEO login row backing [cloud auth](/docs/optional/cloud-auth)), **`x_credentials`** + **`x_seen_mentions`** (the Fernet-encrypted OAuth secrets and the mentions dedup ledger behind the [X engine](/docs/optional/x-engine)), a `sandbox_services` column on **Project** (the per-project opt-in for the [sandboxed dev DB/Redis](/docs/optional/sandbox-db)), and **`vault_seen_notes`** (the dedup ledger behind the [Obsidian vault](/docs/optional/obsidian-vault)'s `#roboco` inbox loop — the vault's actual content lives on disk as markdown, not in Postgres).
## pgvector is required
RoboCo's in-house RAG engine stores embeddings in PostgreSQL using the **pgvector** extension. The orchestrator enables it for you at startup (`CREATE EXTENSION IF NOT EXISTS vector`), so the bundled `postgres` image — which ships pgvector — works out of the box.
On a bring-your-own PostgreSQL where the `vector` extension isn't available, the orchestrator logs a warning and **RAG features silently disable** — agents lose the knowledge base and mentor lookups, but the rest of the system runs. If you point RoboCo at an external Postgres, make sure pgvector is installed there.
Operationally, **PostgreSQL is the one service that must be backed up** — everything the company knows lives there. Redis holds only the event bus and short-lived coordination state, and is fine to lose. An always-on sidecar takes a daily `pg_dump` of Postgres automatically; see [Backups](/docs/deploy/backups) for the rotation and restore procedure.
Each project's GitHub PAT is stored encrypted with `ROBOCO_ENCRYPTION_KEY`. A database backup **without that key is useless for the tokens** — you'll be able to restore every project except its credentials. Keep the key safe and separate. See the [environment reference](/docs/deploy/env-reference).
## The stack migrates itself on startup
You almost never run a migration command by hand. When the orchestrator boots, `init_db()` enables pgvector and then `run_migrations()` runs `alembic upgrade head` in a worker thread:
B[init_db: enable pgvector]
B --> C[run_migrations: alembic upgrade head]
C --> D[create_all gap-fill on existing DB]
D --> E[dispose pool to refresh enum cache]
E --> F[API ready]`} />
The practical consequence: **a normal `docker compose up` / restart already applies any new migrations.** A fresh database is built entirely by the migration chain from base; an existing one runs only the pending steps (a pre-Alembic database is auto-stamped at the initial revision first, so it isn't re-built).
The documented manual step — after pulling a change that adds a migration —
```bash
docker compose exec orchestrator alembic upgrade head
```
is a safety net, not a routine requirement, since the orchestrator runs the same command on boot. In host-dev mode (no container) the equivalent is `uv run alembic upgrade head`, or `make migrate`.
## A fresh DB is built by migrations, not `create_all`
This is the one gotcha that bites operators who try to reset state by hand. Several migrations embed **seed data** — most importantly the LLM provider rows (Anthropic, Grok, Codex, Gemini, Kimi, Ollama Cloud, self-hosted). The schema must be built by **running the migration chain**, never by a bare SQLAlchemy `create_all`.
A `create_all`-only database has empty `provider_configs`, so the Settings → Providers endpoints return **404**. The fix is always the same: let the migrations run (restart the orchestrator, or run `alembic upgrade head`). The orchestrator's own startup uses `create_all` only as a *gap-fill* on an already-migrated database, never as the builder.
## Contributor notes
If you write or review custom migrations:
- **Revision-id length is capped at 32 characters.** PostgreSQL stores `alembic_version.version_num` as `VARCHAR(32)`. A longer id breaks a live `upgrade` even though the test suite (which renders offline / uses `create_all`) never catches it. The chain currently has **70 revisions** (71 files — revision `026` is split into two consecutive steps); the head is `070_vault_seen_notes`, and the longest ids in the tree are `015_drop_task_execution_outputs` and `063_video_engine_project_toggle`, tied at 31 characters, deliberately just under the limit.
- **The chain is linear.** Every revision has a single `down_revision`; there are no branches or merges to reconcile.
- **Models, tables, and migrations must stay in sync.** A Pydantic model in `roboco/models/`, its ORM table in `roboco/db/tables.py`, and the migration that creates the column are three layers that move together.
## Next
→ [Bootstrap & seeds](/docs/deploy/bootstrap-and-seeds) — what `make db-init` puts into a fresh database. → [Environment reference](/docs/deploy/env-reference) — every `ROBOCO_DATABASE_*` knob and the encryption key.
================================
URL: https://docs.roboco.tech/docs/deploy/backups
Title: Backups
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo's every stateful fact — tasks, projects, agents, journals, encrypted git tokens — lives in one PostgreSQL database. A `backup` sidecar ships in both tracked compose files (`docker-compose.yml` and `docker-compose.registry.yml`) and takes a daily dump automatically. There's no feature flag for it: a default-off backup would defeat the point, so it's always on.
## What it does
`backup` is a plain `pgvector/pgvector:pg16` container — the same image `postgres` itself uses — sitting on the data-only network next to `postgres`, running a small dump loop. On container start, and then every 24 hours, it runs `pg_dump -Fc` against the `roboco` database over the network and writes a timestamped custom-format dump to:
```text
${ROBOCO_DATA_DIR:-./data}/backups/roboco-.dump
```
The dump is written to a `.tmp` file first and renamed into place only on success, so a crash mid-dump never leaves a half-written file that looks complete.
There's no WAL/point-in-time archiving here — just one `pg_dump` a day. Up to 24 hours of writes can be lost if you restore from the latest dump. By default it's also stored on the same host as the database it's backing up, so it doesn't protect against whole-host loss (disk or NAS failure) — the off-disk mirror below closes that gap.
## Off-disk mirror
By default every dump lives on the same disk as the database it protects, so one disk failure loses both. Setting `ROBOCO_BACKUP_MIRROR_DIR` in `.env` to a host path on a **different** disk — an external USB drive, or a remote share the NAS OS mounts (SMB/NFS/a cloud-sync target) — arms a mirror step: after every successful dump, the sidecar copies it into that path (the same tmp-then-rename crash safety as the primary write) and prunes the mirror to the same retention count.
- **Unset is a structural no-op.** The mirror step never even attempts a copy, and the compose mount just re-points at the primary `backups/` dir so no stray directory gets created.
- **A missing or read-only mirror path logs a warning and skips** — the primary dump is never blocked by mirror trouble.
- **Each cycle mirrors only its own fresh dump.** There's no backfill: a dump whose mirror copy failed simply stays absent from the mirror rather than being retried later. Crash-orphaned `.tmp` files in either directory are swept at the next cycle.
An external disk plugged into the same NAS still protects you from a single-disk failure, but not from whole-host loss (theft, fire, a dead NAS). If that's in your threat model, point `ROBOCO_BACKUP_MIRROR_DIR` at a mounted cloud-synced share instead of a local external disk.
## Retention
After every attempt (successful or not) the sidecar prunes `backups/` down to the newest **14** dumps by modification time — roughly two weeks of history at the default daily cadence — deleting the rest.
## Failure behavior
A failed `pg_dump` (a network hiccup, Postgres briefly unhealthy mid-restart, a full disk) logs to `docker logs roboco-backup` and is simply retried on the next cycle. The dump loop itself never exits, so a transient failure doesn't crash-loop the container; `restart: unless-stopped` covers the rest.
## Restoring a dump
Stop anything writing to the database, then restore into a running (empty or throwaway) `roboco` database:
```bash
docker exec -i roboco-postgres pg_restore \
-U roboco -d roboco --clean --if-exists \
< ./data/backups/roboco-20260711T030000Z.dump
```
Ignore any `.tmp` files you see in the directory — those are in-progress dumps, not completed backups. To inspect a dump's contents or restore only part of it, list its objects first with `pg_restore -l `. To restore into a brand-new database instead of overwriting one in place, create it first (`createdb -U roboco roboco_restore`) and drop the `--clean --if-exists` flags.
## Restore drill
A backup that's never been restored is a hope, not a backup. Run this **quarterly** — it takes about two minutes and touches nothing in production, since it restores into a disposable throwaway container:
```bash
# 1. Newest dump (skip any .tmp files — those are in-progress, not backups)
DUMP=$(ls -1t ./data/backups/roboco-*.dump | head -1) && echo "$DUMP"
# 2. Throwaway postgres with the same image the stack pins
docker run -d --name roboco-restore-drill -e POSTGRES_PASSWORD=drill \
-e POSTGRES_USER=roboco -e POSTGRES_DB=roboco pgvector/pgvector:pg16
until docker exec roboco-restore-drill pg_isready -U roboco -q; do sleep 1; done
# 3. Restore the dump into it
docker exec -i roboco-restore-drill pg_restore -U roboco -d roboco \
--no-owner < "$DUMP"
# 4. Sanity-check: key tables non-empty and recent
docker exec roboco-restore-drill psql -U roboco -d roboco -c \
"SELECT (SELECT count(*) FROM tasks) AS tasks,
(SELECT count(*) FROM agents) AS agents,
(SELECT count(*) FROM projects) AS projects,
(SELECT max(created_at) FROM tasks) AS newest_task;"
# 5. Tear down
docker rm -f roboco-restore-drill
```
The drill passes when step 4 shows non-zero counts and a `newest_task` within the last backup interval. A restore error in step 3, or empty counts in step 4, means the backups aren't trustworthy — investigate before you actually need one.
Every project's GitHub token is Fernet-encrypted with `ROBOCO_ENCRYPTION_KEY` before it's stored. A restored database is useless for those tokens without that same key — you'll get every project back except its credentials, which you'd have to re-enter. Keep the key with your secrets, separate from the database backups, and never let it drift between the backup and the environment you restore into. See the [environment reference](/docs/deploy/env-reference).
## Tuning it
The dump interval, retention count, and mirror path aren't environment variables read by `roboco/config.py` — they're read directly by the sidecar's own entrypoint script. The interval and retention default to 24 hours / 14 dumps; to change them, set `BACKUP_INTERVAL_SECONDS` and/or `BACKUP_KEEP` on the `backup` service's `environment:` block in your compose file and restart that one container — no orchestrator restart is needed. `ROBOCO_BACKUP_MIRROR_DIR` (set in `.env`, not the compose file directly) is the one exception that's read at the compose level rather than passed straight through, since it doubles as both the mirror's env var name and its host bind-mount source.
If you run the [Obsidian vault](/docs/optional/obsidian-vault), almost everything under it — `Tasks/`, `Journals/`, `A2A/`, `Agents/`, `Reports/` — is a projection this `pg_dump` already covers: delete the whole vault and `python -m roboco.vault rebuild` reconstructs it from the database. `RoboCo/Notes/` is the one exception. It's your own writing, never round-tripped into Postgres, so a database restore doesn't bring it back. If you rely on that folder (directly, or via [KB ingest](/docs/optional/obsidian-vault#making-your-own-notes-fleet-searchable-kb-ingest)), include it in whatever offsite copy you keep alongside `backups/`.
## Next
→ [Data & migrations](/docs/deploy/data-and-migrations) — the entities the database holds and how the schema migrates itself. → [Environment reference](/docs/deploy/env-reference) — `ROBOCO_ENCRYPTION_KEY` and the rest of the required secrets. → [Deployment](/docs/deploy/deployment) — the full `ROBOCO_DATA_DIR` layout this sidecar's `backups/` subdirectory sits in.
================================
URL: https://docs.roboco.tech/docs/deploy/bootstrap-and-seeds
Title: Bootstrap & Seeds
================================
import { Admonition } from "@/components/docs/admonition";
A fresh RoboCo database isn't an empty shell — it comes pre-populated with the whole org chart: every agent, seeded once by slug. This page covers what `make db-init` seeds, and the console entry points that run the bootstrap.
## `make db-init` — seed a fresh instance
The one command you need to bring a clean database to life:
```bash
make db-init
```
This runs `python -m roboco.cli --db-only`, which enables the schema (the [migration chain](/docs/deploy/data-and-migrations)) and then seeds the company in a single transaction.
Every seed operation skips what already exists (agents by slug). Running `make db-init` against an already-seeded database is a harmless no-op, so you can re-run it any time without fear of duplicates.
## What gets seeded
| Seeded data | Detail |
|-------------|--------|
| **26 agent rows** | The 25 AI agents **plus the human CEO** ("Renzo"), each with a stable static UUID. A `system` sentinel row is also appended (it's the FK target for system-authored records and lives outside the team enum). |
The roster and the seeded UUIDs are derived from the foundation catalog (`roboco/foundation/identity.py`), so the seed data and the runtime role/permission rules can never drift apart. For the full org chart, see [Org & roles](/docs/company/org-and-roles).
## The console entry points
`make db-init` is a thin wrapper over the CLI. The supported way to invoke RoboCo directly is the module form:
```bash
python -m roboco.cli --db-only # seed the DB and exit (what make db-init runs)
python -m roboco.cli # seed + start the full orchestrator stack
python -m roboco.cli --skip-db # start the stack against an already-seeded DB
python -m roboco.cli --spawn be-dev-1 fe-dev-1 # also spawn the named agents on boot
```
`python -m roboco.cli` is what the orchestrator container runs and what `make db-init` / `make` targets call. Full system start does more than seed: it brings up the Redis event bus, constructs the orchestrator, starts the API under uvicorn, polls `/health` until the FastAPI lifespan finishes indexing (up to ~2 minutes), then begins dispatching.
`pyproject.toml` declares two console scripts, `roboco` and `roboco-bootstrap`. Use `python -m roboco.cli` (or `make db-init`) — that's the invocation the Makefile and the container use and the one that's verified to work. `roboco-bootstrap` maps to the same bootstrap routine; the bare `roboco` console script is not the supported path.
## Required configuration before first boot
Bootstrap itself needs almost nothing, but the orchestrator will refuse to start without two secrets:
- **`ROBOCO_ENCRYPTION_KEY`** — the Fernet key that encrypts project GitHub tokens. Its config default is an empty string, but startup is mandatory: no key, no boot.
- **`ROBOCO_AGENT_AUTH_SECRET`** — the HMAC secret that signs agent tokens. Unset, every token is treated as unsigned.
Everything else has a sensible default. See the [environment reference](/docs/deploy/env-reference) for the full list, and [Deployment](/docs/deploy/deployment) for the compose host-path mounts.
## Next
→ [Data & migrations](/docs/deploy/data-and-migrations) — the entities you just seeded, and how the schema stays current. → [Org & roles](/docs/company/org-and-roles) — the workforce these seeds create.
================================
URL: https://docs.roboco.tech/docs/models
Title: Choosing & Running Models
================================
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { FileText } from "lucide-react";
RoboCo's agents are backend-agnostic. By default the whole workforce runs on **Anthropic Claude**, authenticated from the Claude Code session on your host — no API key to manage. You can route the company onto **xAI Grok**, **OpenAI Codex**, **Google Gemini**, or **Moonshot Kimi** instead (each its own subscription-authenticated CLI, no metered key), point individual agents and roles at **local / self-hosted** models, or pin a role to a cheaper model at a given task complexity — all from one page in the panel.
The Settings → AI Providers page: the routing modes, agent-over-role-over-global precedence, cost-tiered complexity overrides, saved routing presets, and the fail-soft fallback.
The whole workforce on xAI Grok via the official CLI and a SuperGrok subscription — no metered key.
Delivery agents on OpenAI's Codex CLI via a ChatGPT subscription — no metered key, V1 delivery-roles-only.
Delivery agents on Google's Gemini CLI via an OAuth login — no metered key, V1 delivery-roles-only.
Delivery agents on Moonshot's Kimi K3 via the official CLI and a Kimi subscription — no metered key, V1 delivery-roles-only.
What keeps a run alive: crash auto-retry, and how rate limits and provider overloads park work instead of dropping it.
================================
URL: https://docs.roboco.tech/docs/models/provider-routing
Title: Provider Routing
================================
import { Admonition } from "@/components/docs/admonition";
By default every agent runs on Anthropic Claude — no key, no config. The orchestrator mounts your host `~/.claude` session into each container and that's the whole setup. When you want something else — the whole fleet on Grok, Codex, Gemini, or Kimi, a few agents on a self-hosted model, a cheaper model for mechanical low-complexity work, or a deliberate mix — you don't edit env files. You set it on **Settings → AI Providers** (`/settings/ai-providers`, linked from the sidebar footer), and the choice is stored server-side and resolved per agent at spawn.
## The routing modes
The page has one global **routing mode** that decides which backend every agent uses unless you override it per agent:
| Mode | What it does | Needs |
|------|--------------|-------|
| **Anthropic** | Every agent runs on Claude via the mounted `~/.claude` auth. The default. | Nothing — the orchestrator's `~/.claude` mount |
| **Grok** | Every agent runs on xAI Grok (`grok-build`). | A saved Grok key *or* a SuperGrok login — see [Run on Grok](/docs/models/grok) |
| **Codex** | Every delivery agent runs on OpenAI's Codex CLI (`gpt-5.3-codex`). | A mounted ChatGPT-subscription `~/.codex` login — see [Run on Codex](/docs/models/codex) |
| **Gemini** | Every delivery agent runs on Google's Gemini CLI (`gemini-2.5-pro`). | A mounted OAuth `~/.gemini` login — see [Run on Gemini](/docs/models/gemini) |
| **Kimi** | Every delivery agent runs on Moonshot's Kimi K3 (`kimi-code/k3`). | A shared device-code OAuth `~/.kimi-code` login — see [Run on Kimi](/docs/models/kimi) |
| **OpenRouter** | Every agent runs on an OpenRouter model you pick from the live catalog. | A saved OpenRouter key - see [Run on OpenRouter](/docs/models/openrouter) |
| **Nebius** | Every agent runs on Nebius Token Factory, NVIDIA's open-model cloud (Nemotron 3 Super by default). | A saved Nebius key - see [Run on Nebius](/docs/models/nebius) |
| **Hummin** | Every agent runs on the GLM-native hummin CLI over the Z.ai GLM Coding Plan (`glm-5.3-flash:high` by default). | A saved Z.ai key - see [Run GLM on hummin](/docs/models/hummin) |
| **Z.ai** | GLM through the Claude Code runtime via z.ai's Anthropic-compatible endpoint. The manual fallback behind hummin. | A saved Z.ai key |
| **Ollama** | Every agent runs on Ollama Cloud. | A saved Ollama Cloud key |
| **Self-Hosted** | Every agent runs against your own OpenAI-compatible endpoint. | A base URL with a successful **Test Connection** |
| **Mix** | Pin individual agents to specific models; everything else inherits the global default. | Whatever the pinned models need |
| **Cost-Tiered** | Additive, not a full-fleet switch: touches nothing already routed. The mode itself ships with no day-1 seed — see the capability floor below for why. | Nothing — re-runnable any time |
Anthropic, Z.ai, Ollama Cloud, and self-hosted models all speak the Anthropic Messages wire protocol, so they run *through* the same Claude Code runtime — the orchestrator just injects the endpoint and key. Grok, Codex, Gemini, Kimi, OpenRouter, Nebius, and hummin each speak their own protocol and run in their own agent image, authenticated by a mounted CLI subscription/login or a saved key. You don't have to think about that distinction; it's just why each has its own setup page.
As of v0.31.0 there are no delivery-only providers anymore. The interactive Intake and Secretary chats can ride any backend: providers with their own CLI session run through a generic live driver, so applying any mode fleet-wide (or pinning any agent in Mix mode) covers them too. The old Codex/Gemini/Kimi delivery-only exemption was retired when the generic driver landed.
Mix mode shows a per-agent table mirroring the org chart, but you (the CEO) are intentionally excluded — there's no model to assign to the human seat. The Intake (Prompter) and Secretary chats *are* in the table, routable to every provider like any other agent.
## How a route is resolved: agent > role(+complexity) > role > global
When the orchestrator spawns an agent, it picks the model by a precedence ladder, most specific first:
1. **Agent** — a model pinned to that exact agent (set in Mix mode).
2. **Role + complexity** — a model assigned to the agent's role *at a specific task complexity* (a **complexity override** — see below).
3. **Role** — a model assigned to the agent's role, complexity-blind.
4. **Global** — the global mode's default.
The first rung that has an assignment wins. So in Mix mode you can pin `be-dev-1` to one model, leave the rest of the backend cell on the global default, and the dispatcher resolves each one independently at spawn time. Switching the global mode (Anthropic / Grok / Codex / Gemini / Kimi / OpenRouter / Nebius / Hummin / Z.ai / Ollama / Self-Hosted) **replaces the role and global rows, but keeps your per-agent pins and complexity overrides** — the panel's confirmation says so explicitly. So flipping from, say, Grok back to Anthropic moves every un-pinned agent to Anthropic while a `be-dev-1` pin set in Mix mode stays exactly where you put it; mixed-provider routing (some agents pinned, everything else on the mode's default) is a normal, supported state rather than something a mode switch undoes.
## Cost-tiered routing: cheaper models for mechanical work
A role's plain default is one model, complexity-blind. **Complexity overrides** let you pin `developer`, `qa`, or `documenter` to a *cheaper* model specifically for tasks a PM estimated as **LOW** or **HIGH** complexity — reusing the same precedence rung a task's `estimated_complexity` resolves against at spawn.
- **Downgrade-only, enforced server-side.** An override can never point to a costlier tier than that role's own baseline model — this lever only saves cost, it never spends more. Attempting an upgrade is rejected.
- **Coordinator and Board/CEO-facing roles aren't offered a row at all.** `cell_pm`, `main_pm`, `pr_reviewer`, and every Board/CEO-facing role are deliberately excluded — a coordinator is the last place to gamble a downgrade.
- **The one-click "Cost-Tiered" button is a day-1 seed, not a mode switch — currently an empty one.** Unlike every other mode button, clicking it **never wipes existing routing**. It used to additively seed `developer:low` → Haiku, but that seed is retired: Haiku can't reliably emit the structured envelopes the lifecycle now runs on (per-criterion QA verification, delegation's parent-criteria mapping, the findings ledger), so the seed would have been immediately upgraded back to Sonnet by the capability floor below anyway. The mode stays wired for a genuinely-above-floor tier you add yourself in the **Complexity overrides** table.
- **Edit any role/complexity cell directly** in the Complexity overrides section below the mode buttons — a blank cell means no override at that complexity for that role.
QA, PM, and reviewer roles run on **structured verbs** — `pass_review`'s per-criterion `criteria_verified`, `delegate`'s `covers_parent_criteria` mapping, the revision-findings ledger — that Haiku-class models can't reliably emit. A Haiku QA or PM claims a task, fails validation, idles, respawns, and loops without ever advancing it. QA and Documenter's role defaults now run on Sonnet, not Haiku, and a **capability floor** at resolution time upgrades *any* Anthropic assignment that resolves to a Haiku-class model — a stray per-agent pin, a role row, or a future map edit — back up to Sonnet, whatever its source. This is an Anthropic-tier floor, not a provider policy: Grok, Codex, Gemini, Kimi, and self-hosted assignments are untouched. `pr_reviewer` and the Auditor stay on Opus, unaffected either way.
## Routing presets: save and swap whole setups
A **routing preset** snapshots the **entire current routing state** — the applied mode, every per-agent pin, every role row, and every complexity override — under a name you pick, so you can swap between whole configurations (e.g. "everything on Grok" vs. "cost-optimized mix") in one click instead of re-picking every field.
- **Save as preset…** captures exactly what the card currently shows.
- **Apply** performs a **full swap**: it replaces every current assignment with the preset's snapshot, not a merge — the confirmation dialog says so. Unlike a mode-button switch (which preserves pins/overrides), applying a preset can move pins and overrides too, because the preset *is* a full state capture including those.
- Applying a preset **validates every entry before touching anything** — an invalid saved entry (e.g. a role no longer eligible for a complexity override) is skipped and reported, but never triggers a partial wipe.
- **Delete** removes the saved snapshot only; it has no effect on whatever routing is currently applied.
Presets are stored server-side, so they persist across restarts and are available to whoever operates the panel.
## Saving keys and endpoints
Three secrets live on this page, all stored **Fernet-encrypted** server-side and **never returned by the API** — the panel only ever sees a `has_key` / `has_token` boolean, so you re-enter (you can't read back) a key:
- **Grok (xAI) key** — only needed if you authenticate Grok with a key rather than a SuperGrok login.
- **Ollama Cloud key** — required before any agent can route to Ollama Cloud.
- **OpenRouter key** — required before the live catalog picker (and any OpenRouter routing) works.
- **Nebius key** — required for Nebius Token Factory routing *and* for the `run_sandbox_tests` sandbox verb, which shares the same metered key.
- **Z.ai key** — powers both the hummin (GLM CLI) mode and the manual Z.ai mode.
- **Self-hosted endpoint** — a base URL plus an optional auth token. Hit **Test Connection** to confirm reachability and auto-discover the model list; **Refresh** re-discovers it. A self-hosted assignment won't save until the connection tests clean.
The panel guards you client-side: it blocks saving a Grok / Ollama / self-hosted assignment when the matching key or connection is missing, and warns when an Ollama-routed agent has no key (see below). Codex, Gemini, and Kimi need **no key on this page at all** — they authenticate from a mounted CLI login on the host, set up once as described on their own pages, so the Codex/Gemini/Kimi mode buttons are always clickable regardless of anything saved here.
## Fail-soft: misconfig falls back to Anthropic
Routing is deliberately fail-soft. A *stalled* spawn is worse than a *wrong* model, so on any of these the orchestrator silently degrades that agent to Anthropic Claude rather than refusing to start it:
- no assignment found for the agent, its role, or the global default;
- a self-hosted or Ollama endpoint that's unreachable (the orchestrator probes `/api/tags` before committing to it);
- a stored key or token that fails to decrypt.
Because the fallback is silent, an agent you *think* is on your self-hosted model may actually be running on Claude — for example if the endpoint went unreachable after you tested it. The fallback is logged in the orchestrator (`docker compose logs orchestrator` shows the warning). If you're routing away from Anthropic, watch the per-model breakdown on the [usage dashboard](/docs/operations/cost-and-usage) to confirm the traffic is landing where you intended.
The Anthropic, Grok, Codex, Gemini, Kimi, OpenRouter, Nebius, hummin (Z.ai), Ollama Cloud, and self-hosted provider rows are all created by `alembic upgrade head`, which the stack runs for you on boot. The AI Providers page only sets keys, mode, and assignments — it never creates providers. If you skipped migrations you'll see "provider not seeded" errors here.
## Next
- [Run on Grok](/docs/models/grok), [Run on Codex](/docs/models/codex), [Run on Gemini](/docs/models/gemini), and [Run on Kimi](/docs/models/kimi) — the four subscription-authenticated CLI providers, end to end.
- [Run on OpenRouter](/docs/models/openrouter), [Run on Nebius](/docs/models/nebius), and [Run GLM on hummin](/docs/models/hummin) — the key-metered providers with their own agent images.
- [What keeps a run alive](/docs/models/resilience) — crash retry and provider park-and-resume.
- The same guardrails apply on every backend — see [how agents are sandboxed](/docs/company/agent-gateway).
================================
URL: https://docs.roboco.tech/docs/models/grok
Title: Grok
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo can run the entire workforce — or just some agents — on **xAI Grok**, using xAI's official `grok` CLI on a **SuperGrok subscription** rather than a metered API key. That matters operationally: a subscription can't run out of credits mid-task, so a Grok fleet won't stall halfway through a delivery the way a metered key can. Grok reaches full parity with the Claude path by construction: the same MCP gateway, the same per-role tool manifest, the same prompt-injection and bash guards, the same per-agent cost capture.
## Set it up once
Authenticate Grok on the host with the official CLI, then point RoboCo at the resulting directory:
```bash
grok login # once, on the host — creates ~/.grok/auth.json
```
Then in `.env`:
```bash
ROBOCO_HOST_GROK_DIR=/home/youruser/.grok # the REAL host ~/.grok to mount in
```
The orchestrator mounts that directory's `auth.json` **read-only** into each Grok agent container. On a NAS or any docker-in-docker deploy the orchestrator's home is not the host's home, so `ROBOCO_HOST_GROK_DIR` must point at the actual host path where `grok login` wrote `~/.grok` — otherwise agents start with no credential.
Finally, on **Settings → AI Providers** set the routing mode to **Grok** (whole fleet) or pin individual agents to a Grok model in **Mix** mode. See [Choosing a provider](/docs/models/provider-routing) for the modes and precedence. The [installation guide](../get-started/installation.md#optional-run-on-grok-instead) covers the same first-run steps inline.
## The token refreshes itself
The Grok access token has a fixed ~6-hour, server-set lifetime, and the CLI can't refresh it headlessly — on an expired token it would hang forever at an interactive login prompt. RoboCo handles this for you:
- Once per dispatch tick the orchestrator mints a fresh token from the offline-access refresh token (xAI's OIDC `refresh_token` grant) before expiry and atomically rewrites the shared `auth.json` in place. The refresh fires `ROBOCO_GROK_AUTH_REFRESH_SKEW` seconds (default `1800`) ahead of expiry.
- As a backstop, each agent's entrypoint runs `python -m roboco.llm.providers.grok_auth --check` and **refuses to start** on a missing or expired token instead of hanging.
The orchestrator rewrites `auth.json` when it refreshes the token, so the orchestrator's own mount of `~/.grok` must be **read-write**. (The per-agent mount stays read-only — agents only read the credential.) If the orchestrator can't write it, the token will expire and Grok agents will fail their start-up `--check`.
## Per-fleet tuning
Three optional knobs let you trade cost against quality across the whole Grok fleet. All are env-set and apply uniformly (no per-role reduction — that's parity with the Claude path):
| Variable | Default | What it does |
|----------|---------|--------------|
| `ROBOCO_GROK_REASONING_EFFORT` | unset (model default) | The `grok --effort` level. Set `low` / `medium` / `high` / `xhigh` / `max` to dial cost vs quality; empty, `default`, or `full` keeps the model's own default. |
| `ROBOCO_GROK_MAX_TURNS` | `200` | The per-agent turn cap for a `grok -p` run. |
| `ROBOCO_GROK_MAX_COST_USD` | `0` (off) | A per-agent cost ceiling in USD. The grok CLI has no live usage hook, so the orchestrator reads each running container's captured cost and kills it once it crosses this ceiling — a backstop against a runaway loop. `0` disables the cap. |
Two more variables exist mostly for staged rollouts and tests:
| Variable | Default | What it does |
|----------|---------|--------------|
| `ROBOCO_GROK_AGENT_IMAGE` | `roboco-agent-grok:latest` | Override the Grok agent Docker image. |
| `ROBOCO_GROK_CLI_MODEL` | `grok-build` | The grok CLI model id passed to agents. |
Each Grok agent gets a per-agent usage directory mounted read-write so its token/cost is read back when the session finalizes. Grok traffic therefore lands on the same [usage dashboard](/docs/operations/cost-and-usage) as Claude, priced from the captured session totals.
## Next
- [Choosing a provider](/docs/models/provider-routing) — set the Grok mode or pin agents in Mix.
- [What keeps a run alive](/docs/models/resilience) — Grok agents park-and-resume on a rate limit like any other provider.
================================
URL: https://docs.roboco.tech/docs/models/codex
Title: Codex
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo can run delivery agents on **OpenAI's Codex CLI**, authenticated by a **ChatGPT subscription** rather than a metered API key — the same "subscription, not credits" posture as the Grok path, so a Codex-routed dev can't stall mid-task on exhausted API spend.
Codex runs the developer/QA/documenter/PM delivery roles. It does **not** back the interactive **Intake** or **Secretary** chats — those stay on Anthropic even under the one-click Codex mode, and an explicit per-agent pin to Codex on either seat is refused at spawn. This is a deliberate V1 scope, not a bug.
## Set it up once
Authenticate Codex on the host with the official CLI, then point RoboCo at the resulting directory:
```bash
codex login # once, on the host — creates ~/.codex/auth.json
```
Then in `.env`:
```bash
ROBOCO_HOST_CODEX_DIR=/home/youruser/.codex # the REAL host ~/.codex to mount in
# ROBOCO_CODEX_CLI_MODEL=gpt-5.3-codex # codex has no reliable default — always set explicitly
```
Finally, on **Settings → AI Providers**, either click the **Codex** mode button (whole fleet, delivery roles) or pin individual agents to a `gpt-*` model in **Mix** mode — see [Choosing a provider](/docs/models/provider-routing) for the full precedence ladder.
Unlike a single-file bind mount (which would pin the file's inode and block the orchestrator's own token refresh from ever reaching a running container), `ROBOCO_HOST_CODEX_DIR` is mounted **read-only as a directory**. Each agent's entrypoint symlinks `~/.codex/auth.json` to that mount, while Codex's own writable state (`config.toml`, `rules/`, `sessions/`) lives in the image's own `~/.codex` — so the CLI can write locally without ever touching your host credential.
## The token refreshes itself
Codex's access token is a JWT whose `exp` claim is the only expiry signal — there's no sibling `expires_at` field the way Grok's bundle carries one. The orchestrator runs the same refresh-loop shape as Grok's:
- It mints a fresh token from the refresh-token grant against `auth.openai.com/oauth/token` before expiry and atomically rewrites the shared `auth.json`.
- The refresh token is **single-use**: a process-wide lock (with a re-check inside the lock) guards against two refreshes racing and double-burning it.
- If your account needs a different OAuth client id than the built-in default, override it with `ROBOCO_CODEX_OAUTH_CLIENT_ID` — a bad refresh never mutates `auth.json`, so the worst case is a parked provider, not a corrupted credential.
## Guardrails without a `--disallowed-tools` flag
The Codex CLI has no CLI-flag equivalent to Grok's `--disallowed-tools`, so RoboCo enforces the same boundaries a different way:
- **Per-role sandbox level** — `workspace-write` for the `developer` role, `read-only` for every other Codex-routed role.
- **One shared execpolicy file** (`~/.codex/rules/default.rules`, Starlark `prefix_rule`s) denies git-mutation, destructive, and raw package-manager commands — the same guardrail the bash-guard hook enforces on the Claude path.
- **No verified system-prompt-file mechanism** — the composed role blueprint (team/role/identity layers) is prepended directly into the task prompt instead of mounted as a separate system file.
## No exit-code taxonomy — classified from the transcript's own error field
Every Codex CLI failure exits `1`, unlike Grok's or Gemini's distinguishable exit codes. RoboCo classifies a run's terminal state (rate-limit, auth failure, or neither) from the run's own structured JSONL `error.message` field plus stderr — **never** the model's own transcript prose, which could false-positive on ordinary on-topic text (this repo's own prompts, for instance, use the phrase "quota-limited" in plain engineering prose). A detected rate-limit or auth failure parks the Codex provider exactly like a Grok/Gemini exit does, and the background probe-and-resume loop revives it once the condition clears.
## Usage and cost still show up
Unlike Grok's output-only cost fallback, Codex usage capture sums each run's `turn.completed` events for a genuine four-bucket split — input, output, cache-read, cache-write — into the same usage-tracking shape every other provider feeds. Codex traffic lands on the same [usage dashboard](/docs/operations/cost-and-usage) as Claude and Grok, priced from the captured session totals.
## Next
- [Choosing a provider](/docs/models/provider-routing) — the Codex mode button, Mix-mode pins, and the full precedence ladder.
- [Run on Grok](/docs/models/grok) and [Run on Gemini](/docs/models/gemini) — the other two subscription-based CLI providers.
- [What keeps a run alive](/docs/models/resilience) — Codex agents park-and-resume on a rate limit like any other provider.
================================
URL: https://docs.roboco.tech/docs/models/gemini
Title: Gemini
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo can run delivery agents on **Google's Gemini CLI**, authenticated by an **OAuth login** to your Google account rather than a metered API key — the same "subscription, not credits" posture as the Grok and Codex paths.
Gemini runs the developer/QA/documenter/PM delivery roles. It does **not** back the interactive **Intake** or **Secretary** chats — those stay on Anthropic even under the one-click Gemini mode, and an explicit per-agent pin to Gemini on either seat is refused at spawn. This is a deliberate V1 scope, not a bug.
## Set it up once
Authenticate Gemini on the host with the official CLI's interactive login, then point RoboCo at the resulting directory:
```bash
gemini # once, on the host — interactive OAuth login, creates ~/.gemini
```
Then in `.env`:
```bash
ROBOCO_HOST_GEMINI_DIR=/home/youruser/.gemini # the REAL host ~/.gemini to mount in
# ROBOCO_GEMINI_CLI_MODEL=gemini-2.5-pro # or gemini-2.5-flash / gemini-2.5-flash-lite
```
Finally, on **Settings → AI Providers**, either click the **Gemini** mode button (whole fleet, delivery roles) or pin individual agents to a `gemini-*` model in **Mix** mode — see [Choosing a provider](/docs/models/provider-routing) for the full precedence ladder.
`ROBOCO_HOST_GEMINI_DIR` is mounted **read-only** at a staging path; the entrypoint **copies** it into a container-local, writable `~/.gemini` so the CLI's own in-process token refresh can write back locally without ever touching your host credential. Google's refresh token, unlike Grok's or Codex's single-use one, is **reusable** — so each container can safely refresh its own copy independently. There is no orchestrator-side refresh loop for Gemini the way there is for Grok and Codex.
## Guardrails via a policy file, not a CLI flag
The Gemini CLI has no CLI-flag equivalent to Grok's `--disallowed-tools` either. Tool scoping is expressed entirely through:
- A rendered TOML **Policy Engine** file (`~/.gemini/policies/roboco.toml`) — deny-only rules keyed by tool name and command prefix.
- `settings.json` entries: headless OAuth auth mode, a fleet-wide **subagent ban** (`experimental.enableAgents=false`), and disabled auto-memory configuration.
- `--approval-mode yolo` for headless auto-approval, the same posture every provider's automated run needs.
## Classified from the run's own structured result, not the transcript
Gemini has no dedicated CLI exit code for a quota/rate-limit hit, so RoboCo parses the run's own `--output-format stream-json` terminal `error.type` field and remaps it to the same park-and-resume signal Grok and Codex use — never the model's transcript prose. The CLI's own auth-failure exit code (`41`) passes straight through unmodified. Either condition parks the Gemini provider exactly like a Grok/Codex park, and the background probe-and-resume loop revives it once the condition clears.
## Usage and cost still show up
Usage capture reads the run's own terminal `result` event for real per-model token counts — no session-file scraping — and prices each of the three GA models (`gemini-2.5-pro` / `-flash` / `-flash-lite`) at its own rate before folding into the same usage-tracking shape every other provider feeds. Gemini traffic lands on the same [usage dashboard](/docs/operations/cost-and-usage) as Claude, Grok, and Codex.
## Next
- [Choosing a provider](/docs/models/provider-routing) — the Gemini mode button, Mix-mode pins, and the full precedence ladder.
- [Run on Grok](/docs/models/grok) and [Run on Codex](/docs/models/codex) — the other two subscription-based CLI providers.
- [What keeps a run alive](/docs/models/resilience) — Gemini agents park-and-resume on a rate limit like any other provider.
================================
URL: https://docs.roboco.tech/docs/models/kimi
Title: Kimi
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo can run delivery agents on **Moonshot AI's Kimi K3**, via the official `kimi` (kimi-code) CLI and a **Kimi subscription** rather than a metered API key — the same "subscription, not credits" posture as the Grok, Codex, and Gemini paths, authenticated by an OAuth device-code login instead of a saved key.
Kimi runs the developer/QA/documenter/PM delivery roles. It does **not** back the interactive **Intake** or **Secretary** chats — those stay on Anthropic even under the one-click Kimi mode, and an explicit per-agent pin to Kimi on either seat is refused at spawn. This is a deliberate V1 scope, not a bug.
## Set it up once
Authenticate Kimi on the host with the official CLI's device-code login, then point RoboCo at the resulting directory:
```bash
kimi login # once, on the host — device-code OAuth, creates ~/.kimi-code
```
Then in `.env`:
```bash
ROBOCO_HOST_KIMI_DIR=/home/youruser/.kimi-code # the REAL host ~/.kimi-code to mount in (defaults sensibly if unset)
# ROBOCO_KIMI_CLI_MODEL=kimi-code/k3 # or kimi-code/kimi-for-coding (K2.7) for a cheaper lever
```
Finally, on **Settings → AI Providers**, either click the **Kimi** mode button (whole fleet, delivery roles) or pin individual agents to a `kimi-code/*` model in **Mix** mode — see [Choosing a provider](/docs/models/provider-routing) for the full precedence ladder.
Unlike Codex's read-only directory mount or Gemini's copy-into-a-writable-local-dir, `ROBOCO_HOST_KIMI_DIR` is mounted **read-write** and shared across every Kimi agent plus the orchestrator itself. Moonshot rotates its refresh token with only a short reuse grace rather than making it truly reusable, so two independent per-container copies of one credential snapshot would eventually cross-invalidate each other's tokens. Every container instead redeems the SAME rotating chain, serialized by the CLI's own cross-process lock — so there's still no orchestrator refresh daemon, the CLI refreshes itself exactly like Gemini's does, just against shared rather than copied state.
## Model: K3 by default, K2.7 as the cheaper lever
`ROBOCO_KIMI_CLI_MODEL` defaults to `kimi-code/k3` — a 256k context window on mid subscription tiers, 1M on top tiers. Set it to `kimi-code/kimi-for-coding` (K2.7) to trade some capability for a cheaper run. Both aliases are namespaced under the login-managed `kimi-code` provider, so they resolve from whatever account `kimi login` authenticated — there's no separate per-model credential to manage.
## Guardrails without a `--disallowed-tools` flag
Like Codex and Gemini, the Kimi CLI has no CLI-flag equivalent to Grok's `--disallowed-tools`. Tool scoping is expressed as a rendered deny-first permission-rules array in the CLI's own config, and the same bash-discipline guard hook installed on the Claude and Grok paths rides along here too.
## No exit-code taxonomy — classified from the run's own structured error
Kimi's non-interactive mode has no dedicated exit-code taxonomy either, so RoboCo classifies a run's terminal state the same disciplined way it does for Codex: from a structured `error` field on the run's own event stream plus stderr — **never** the model's own echoed assistant/tool content, which could false-positive on ordinary on-topic prose. A detected rate-limit/quota condition exits **75** and a missing or expired credential exits **78**, parking the Kimi provider exactly like a Codex or Gemini park; the background probe-and-resume loop revives it once the condition clears.
## Usage and cost still show up
Usage capture sums each run's real input/output/cache-read/cache-write four-bucket split from the run's own session log into the same usage-tracking shape every other provider feeds. Kimi traffic lands on the same [usage dashboard](/docs/operations/cost-and-usage) as Claude, Grok, Codex, and Gemini, priced from the captured session totals.
## Next
- [Choosing a provider](/docs/models/provider-routing) — the Kimi mode button, Mix-mode pins, and the full precedence ladder.
- [Run on Grok](/docs/models/grok), [Run on Codex](/docs/models/codex), and [Run on Gemini](/docs/models/gemini) — the other three subscription-based CLI providers.
- [What keeps a run alive](/docs/models/resilience) — Kimi agents park-and-resume on a rate limit like any other provider.
================================
URL: https://docs.roboco.tech/docs/models/openrouter
Title: OpenRouter
================================
## Run on OpenRouter
OpenRouter is one metered key in front of hundreds of hosted models. It was RoboCo's first key-based external provider with a **live catalog**: what you pick from is what the API actually serves right now, with real per-token pricing, not a hardcoded list.
OpenRouter models run in their own agent image (the OpenRouter CLI runtime), so flipping the fleet to OpenRouter mode re-spawns agents into that image on their next dispatch.
## Set it up once
1. Get an API key at [openrouter.ai](https://openrouter.ai).
2. In the panel, open **Settings → AI Providers**, paste the key into the OpenRouter box, and save it. The key is encrypted server-side and never reaches an agent container as a file - it's injected as an environment variable at spawn.
3. Flip the routing mode to **OpenRouter**, or pin individual agents in **Mix** mode.
## Picking models
With the key saved, the picker on the same settings card queries the live catalog: model ids, context lengths, and prompt/completion prices per million tokens. Clicking a row sets the fleet default model. In Mix mode you can pin different OpenRouter models to different agents the same way.
## Guardrails
- Rate limits park the provider exactly like every other provider: the agent's session is paused with a retry-after and resumed automatically.
- Auth failures park instead of crash-looping, and the panel shows the park reason.
## Usage and cost
Token usage reported by the model's run log shows up on the agent's cost surfaces like any other provider, attributed at the catalog prices.
## Next
- [Provider Routing](/docs/models/provider-routing) - where OpenRouter sits in the ladder
- [Run on Nebius](/docs/models/nebius) - the open-model sibling with a live catalog
- [Resilience](/docs/models/resilience) - parks, retries, and the reaper
================================
URL: https://docs.roboco.tech/docs/models/nebius
Title: Nebius
================================
## Run on Nebius Token Factory
Nebius Token Factory is NVIDIA's inference cloud: one metered API key in front of 60+ open models (Nemotron, DeepSeek, Qwen, Llama). The provider's default model is **NVIDIA's Nemotron 3 Super** (`nvidia/nemotron-3-super-120b-a12b`), so a Nebius-mode fleet runs an NVIDIA open model by construction.
The same key also powers [Token Factory Sandboxes](#run-qa-tests-in-a-sandbox), the disposable microVMs the `run_sandbox_tests` verb uses.
## Set it up once
1. Get a Token Factory key at [tokenfactory.nebius.com](https://tokenfactory.nebius.com).
2. In the panel, open **Settings → AI Providers**, paste the key into the Nebius box, and save it. The key is encrypted server-side and injected as environment variables at spawn - no credential mount, no refresh loop.
3. Flip the routing mode to **Nebius**, or pin agents in **Mix** mode.
## Models: Nemotron at every level
Four live-verified Nemotron ids ship in the static catalog:
- `nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B`
- `nvidia/nemotron-3-super-120b-a12b` (the fleet default)
- `nvidia/Nemotron-3-Ultra-550b-a55b`
- `nvidia/Nemotron-3_5-Lightning`
Because they're catalog entries, Nemotron models are configurable at every routing level exactly like Claude: the Mix-mode per-agent picker renders a Nebius group, and complexity overrides accept them. Cost attribution currently ranks them at the cheapest tier (no published pricing rows yet) - the same documented ceiling as OpenRouter.
## Run QA tests in a sandbox
With the flag on (`ROBOCO_TOKEN_FACTORY_SANDBOXES_ENABLED`, also on the Feature Flags card), QA's `run_sandbox_tests` verb runs the test suite inside a disposable Token Factory microVM instead of the QA container: it archives the committed HEAD, uploads it, spawns an instance, runs the command, and returns exit code, output tails, duration, and the metered cost as evidence. Sandbox spawn permission must be granted console-side (the API returns a clear error string until then). With the flag off, or any failure along the way, QA keeps its container shell - the local path is never broken.
## Guardrails
Nebius agents run in their own image (the opencode CLI runtime) with an auth preflight before the first prompt: a missing or rejected key parks the session (exit 78) instead of crash-looping, and rate limits park with the standard retry-after taxonomy.
## Next
- [Provider Routing](/docs/models/provider-routing) - where Nebius sits in the ladder
- [Run on OpenRouter](/docs/models/openrouter) - the same shape against hundreds of models
- [Env Reference](/docs/deploy/env-reference) - the `ROBOCO_NEBIUS_*` and sandbox variables
================================
URL: https://docs.roboco.tech/docs/models/hummin
Title: GLM (hummin)
================================
## Run GLM on hummin
hummin is the GLM-native CLI: a pi-harness fork that runs headless in Docker against your **Z.ai GLM Coding Plan**. Since v0.31.0 it's the default way to run GLM in RoboCo - the Z.ai row in the provider picker fronts it, and the manual Anthropic-protocol path stays available as a fallback.
The automatic default model is `glm-5.3-flash:high`, and the hummin mode seeds per-role reasoning tiers out of the box: board roles, reviewers, and the Main PM run `:high`, delivery roles run `:low`. You can override any of it per role or per agent like any other provider.
## Set it up once
1. Get a Z.ai API key (the GLM Coding Plan).
2. In the panel, open **Settings → AI Providers**, paste the key into the Z.ai box, and save it. The key is encrypted server-side and injected as `ZAI_API_KEY` at spawn - no credential mount, no refresh loop, and the initial prompt travels as an environment variable, never as an argv token.
3. Flip the routing mode to **hummin**, or pin agents in **Mix** mode.
hummin agents run in their own image, which runs hummin's own auth check (`--provider zai`) before the first prompt: a bad key parks the session with the standard rate-limit/auth taxonomy instead of crash-looping.
## Guardrails without a hook surface
hummin has no PreToolUse hook surface, so the fleet's bash-guard cannot install into it. The boundary is hummin's own strict per-role `--tools` allowlist, rendered from the fleet's permission rules: an agent can only use the tools its role allows, and nothing else. Two deliberate V1 limits follow from the missing surfaces:
- hummin has no MCP client, so the RoboCo gateway is unreachable from inside a hummin agent. Completion still reaches the orchestrator (the owned-task path), and a bridge extension is the named follow-up.
- The bash-guard cannot install, so the tool allowlist is the only in-container boundary.
## Your vault stays yours
The container forces `HUMMIN_MEMORY=0`, so hummin's memory extension can never write to your personal vault.
## Usage and cost
Usage is scraped from hummin's own structured run log (the tee'd `--mode json` output): a real four-bucket token split across all assistant turn-end events, with turns counted as assistant messages. One pricing note: `glm-5.3-highspeed` has no published rate yet, so it is attributed at the `glm-5.3` rates - the safe direction (slight over-attribution) until a real price lands.
## Next
- [Provider Routing](/docs/models/provider-routing) - where hummin sits in the ladder
- [Resilience](/docs/models/resilience) - parks, retries, and the reaper
- [Cost & Usage](/docs/operations/cost-and-usage) - where the scraped tokens show up
================================
URL: https://docs.roboco.tech/docs/models/resilience
Title: Resilience
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
A long delivery run spans hundreds of spawned containers, and the things that can interrupt one — a model crash, a provider rate limit, a transient overload — are normal, not exceptional. RoboCo is built so none of those quietly kills the work or burns your tokens crash-looping. Two mechanisms do the heavy lifting: **crash auto-retry then escalate**, and **provider park-and-probe**. Both run server-side in the orchestrator, the same way on every backend.
## Crash: auto-retry, then escalate once
When an agent container stops, the orchestrator inspects how it exited:
- **A clean exit** (the agent called `i_am_idle` — it ran out of work) resets that agent's error count. Nothing to do.
- **A provider overload** (see below) parks the provider instead of treating it as a crash.
- **A genuine crash** goes to retry-or-escalate.
On a real crash the orchestrator bumps the agent's error count and **respawns it on the same task** — up to a cap of **3** retries. At exactly the cap it escalates **once** to a human notification (the agent is stranded), then stays quiet so a hard failure can't spam you. The respawn is cold (a fresh container), but it picks the task back up, so a one-off crash is invisible in practice.
B{How did it exit?}
B -->|i_am_idle, clean| C[Reset error count]
B -->|529 / 500 / 503 overload| D[Park the provider]
B -->|crash| E{error_count < 3?}
E -->|yes| F[Respawn on same task]
E -->|no, == 3| G[Escalate once to a human]`} />
## Rate limits & overloads: park, probe, resume
When a provider pushes back, retrying immediately just burns tokens against a wall. So instead of crash-looping, RoboCo **parks** that provider and queues its work:
- A **rate limit (HTTP 429)** parks the provider. The agent reports `i_am_blocked(reason="rate_limited")`, the spawn gate stops launching new work for that provider, and a background loop probes for recovery.
- A **persistent overload (HTTP 529 / 500 / 503)** parks the same way. The model SDK already retries genuinely transient blips; a *persistent* overload is detected from the dead container's log markers and parked rather than crash-retried straight back into the overload. This is gated by `ROBOCO_OVERLOAD_BREAK_ENABLED`, which is **on by default**.
- A **Claude session limit** — the org's rolling 5-hour usage window — parks the same way. Hitting it terminates the agent container with a 429 before the agent can report it, so RoboCo detects it from the dead container's exit (like an overload) and parks the provider instead of crash-respawning the whole fleet straight back into the limit; the queued work auto-revives when the window resets. Also covered by `ROBOCO_OVERLOAD_BREAK_ENABLED`.
- **Grok, Codex, Gemini, and Kimi each park on their own provider-specific rate-limit/auth signal**, feeding the exact same park-and-probe mechanism above: Grok and Gemini classify a run's exit code / structured result for a quota or auth failure; Codex and Kimi — neither has a distinguishable exit-code taxonomy for its non-interactive mode — classify from the run's own structured error field instead (`error.message` for Codex, `error` for Kimi), deliberately never the model's transcript prose (which could false-positive on ordinary on-topic text).
The crucial property: **work is queued, never dropped.** Parked tasks wait; the background probe-and-resume loop requires a real `2xx` from the provider before it lifts the park and revives the parked agents. When the provider recovers, the queued work flows again on its own — you don't restart anything.
While a provider is parked you'll see an amber banner across the panel: a per-provider countdown, how many agents are affected, and "operations paused — resuming automatically." It clears itself when the provider recovers. An empty banner means nothing is parked. The banner is driven live over the `/ws/system` WebSocket and re-syncs over HTTP if the socket drops — so a quiet provider reads as *paused, resuming*, not as a hang.
If a run goes quiet, check the banner before assuming something broke. A parked provider with a counting-down timer is RoboCo waiting out a rate limit on purpose. The work is held and will resume — there's nothing for you to do.
## Disk housekeeping: dangling-image prune
Every agent-image rebuild leaves the previous build behind as a dangling (``) Docker image. Left alone they pile up and eat disk. The orchestrator's background sweep prunes them on a throttle (~6h): it removes **only** dangling images — a tagged image, or one still backing a running container, is never touched. It is gated by `ROBOCO_IMAGE_PRUNE_ENABLED`, which is **on by default**. This isn't a feature flag you opt into; it's an always-on safety net you can disable if you'd rather manage image cleanup yourself.
## Next
- These guardrails are part of the broader [agent gateway](/docs/company/agent-gateway) — agents are structurally constrained, not trusted to behave.
- [Choosing a provider](/docs/models/provider-routing), and the per-provider setup pages: [Grok](/docs/models/grok), [Codex](/docs/models/codex), [Gemini](/docs/models/gemini), [Kimi](/docs/models/kimi).
================================
URL: https://docs.roboco.tech/docs/operations
Title: Cost & Observability
================================
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { FileText } from "lucide-react";
RoboCo measures what the company spends and how it's performing, and shows both in the panel. This section explains the cost model and the operational signals.
How token spend is measured, the Token Usage & Cost dashboard, provider-aware pricing (local is $0 by design), and the cost caps.
The health and readiness probes, and the delivery-performance metrics — velocity, blockers, and team health.
The gated release manager: readiness sweeps, a CEO-approved proposal, and a fail-closed executor that publishes.
================================
URL: https://docs.roboco.tech/docs/operations/cost-and-usage
Title: Cost & Usage
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo measures what your workforce spends in tokens and dollars, and shows it to you live in the panel. This page explains where those numbers come from, how to read the dashboard, how each provider is priced, and what cost controls exist (and where they don't).
## How spend is measured
Spend is captured **per agent session** — one container, doing one stretch of work. Each spawned agent runs a small in-container SDK server that watches the agent's own transcript and exposes its running token counts (input, output, cache-read, cache-write). The orchestrator runs a background sweep every **~60 seconds**: for every active agent it pulls the live counts, writes a usage snapshot, and updates the open session row so the database reflects progress mid-run. When the container stops, the session is **finalized** — final token counts are resolved, priced through the built-in cost table, and written to the closed session row along with the exit reason.
A separate daily rollup keeps a rolling per-day, per-agent, per-team, per-model tally that feeds the "today" figures.
Because cost is computed per session and firms up only when the session ends, the figures for a still-running agent update roughly every minute and aren't final. A session that crashes or is abandoned before its transcript is read can finalize at a low or zero value. The dashboard is an accurate ledger of *closed* sessions plus a live estimate for the ones still open — not a real-time invoice.
Grok, Codex, Gemini, and Kimi agents have no live SDK hook; each of those CLIs writes its own run's usage into the same grok-shaped `usage.json` the orchestrator reads at finalize (Grok's cost cap below reads this same file). Codex sums each run's `turn.completed` events for a genuine four-bucket split (input/output/cache-read/cache-write); Gemini prices its run's own terminal `result` event per the specific GA model used; Kimi sums the same real four-bucket split from its own run's session log.
## The Token Usage & Cost dashboard
Two panel surfaces show spend:
- The **command center** carries a **Token Usage & Cost** card that streams live while agents work. It listens on the `/ws/system` WebSocket and shows a **Live** badge when connected; if the socket drops it falls back to HTTP polling and shows **Polling** (or **Connecting**). See [the command center](/docs/panel/command-center).
- The **Metrics** page has a dedicated **Token Usage** tab with the full breakdown. See [Metrics](/docs/panel/metrics).
A **24h / 7d / 30d** selector drives every panel. What you get:
| Panel | What it shows |
|-------|----------------|
| Summary | Input, output, and total tokens for the period, plus total cost in USD |
| Trend | Percent change vs the immediately prior window of equal length |
| Time series | Hourly points for 24h; daily points for 7d/30d |
| Per-model | Donut of cost share by model |
| Per-agent / per-team | Bar charts of spend, each with its share of the total |
| Monthly projection | Forecast spend (see below) |
| Cache efficiency | Cache hit-rate and the dollars prompt-caching is saving |
| Recent sessions | Raw recent spawn-session rows (default 50) |
The total-token and cost figures sum **input + output + cache-read + cache-write**. Prompt-caching reads are cheap but not free, so they show up in both the totals and the cache-savings panel.
The monthly projection is a naive extrapolation: the average daily cost over the last 7 days × 30. It's a planning forecast, not a bill, and it overstates if you spun up the fleet for a one-off burst. The cache-savings figure uses a single aggregate baseline rate to estimate what the cached tokens *would* have cost at the full input price — treat it as indicative, not exact.
## How each provider is priced
Pricing is provider-aware, from a built-in USD-per-million-token table (`roboco/billing/pricing.py`). Model names are matched on substring, longest fragment wins.
| Provider / model | Input | Output | Priced? |
|------------------|-------|--------|---------|
| Claude Opus 4 | $5.00 | $25.00 | yes (cache read $0.50, write $6.25) |
| Claude Sonnet (4 / 3.7 / 3.5) | $3.00 | $15.00 | yes (cache read $0.30) |
| Claude Haiku 4 / 3.5 | $1.00 | $5.00 | yes |
| xAI `grok-build` | $1.00 | $2.00 | yes (cache read $0.20) |
| OpenAI `gpt-5.3-codex` | $1.75 | $14.00 | yes (cache read $0.175) — priced for cost attribution even though the subscription itself isn't metered |
| Google `gemini-2.5-pro` | $1.25 | $10.00 | yes (no published cache discount — cache priced at the input rate) |
| Google `gemini-2.5-flash` | $0.30 | $2.50 | yes (same cache convention as Pro) |
| Google `gemini-2.5-flash-lite` | $0.10 | $0.40 | yes (same cache convention as Pro) |
| Local Ollama (`ollama/…` or bare tag) | — | — | **$0 by design** |
| Ollama Cloud (`:cloud` tag) | — | — | **$0 by design** |
Self-hosted Ollama runs on hardware you own and Ollama Cloud is billed by flat subscription, so neither carries a per-token cost. RoboCo intentionally prices them at $0 — it is not undercounting, and you won't see a warning.
If a `claude`-named model isn't in the pricing table, RoboCo logs a warning and returns $0 for it rather than crashing. That **is** real spend going uncounted. If you point an agent at a Claude model newer than this build and see $0 cost on a busy fleet, the table needs the new rate added — check the orchestrator logs for the pricing warning.
## Cost controls (and the asymmetry)
There is exactly one always-on, per-agent dollar cap, and it only covers Grok:
| Setting | Default | What it does |
|---------|---------|--------------|
| `ROBOCO_GROK_MAX_COST_USD` | `0.0` | Per-agent Grok cost ceiling in USD, read from the container's `usage.json`. The orchestrator kills a Grok container once it crosses this, catching runaway-loop token burn. `0` disables it. **Grok agents only.** |
The Claude, Codex, Gemini, and Kimi paths are **observe-only** on cost at the container level. RoboCo will show you their spend live and historically, but it will not auto-kill one of these agents for crossing a dollar threshold the way it does Grok. Your protection against runaway spend on these paths is the structural one: rate-limit and overload **park-and-probe** (a provider 429 or persistent overload queues the agent and probes for recovery instead of retrying in a hot loop — see [provider resilience](/docs/models/resilience)), the per-task verb gateway, and watching the dashboard.
A separate, **default-off** subsystem caps spend at the task/project level instead of the container level — provider-agnostic, so it's the one dollar cap that also reaches Claude/Codex/Gemini/Kimi agents:
| Setting | Default | What it does |
|---------|---------|--------------|
| `ROBOCO_TASK_BUDGETS_ENABLED` | `false` (config) / `true` (NAS compose) | A project's monthly spend cap refuses a *new* work-starting claim once reached (in-flight review/doc/gate work always finishes); a per-task $ cap **blocks** the task (not the container) mid-flight and notifies you, rather than silently killing anything. See [Task & project cost budgets](/docs/optional/task-budgets). |
A related guard reaps abandoned interactive chats so they don't leak a container:
| Setting | Default | What it does |
|---------|---------|--------------|
| `ROBOCO_INTERACTIVE_IDLE_REAP_SECONDS` | `1800` | Reaps an idle live Intake/Secretary chat (by time since last turn) so it stops holding a container and burning tokens. `0` disables. |
## Quick checks from the shell
```bash
# Period summary (totals + cost + trend)
curl -s 'http://localhost:3000/api/usage/summary?period=7d'
# Monthly projection
curl -s http://localhost:3000/api/usage/projection
# Container-level resource use (not token cost)
docker stats
docker system df
```
## Next
- Read the live dashboard on [the command center](/docs/panel/command-center) and the full breakdown on [Metrics](/docs/panel/metrics).
- See where the live stream comes from in [WebSockets](/docs/api/websockets) and the `/api/usage/*` routes in the [REST API](/docs/api/rest-api).
- Understand the park-and-probe spend protection in [provider resilience](/docs/models/resilience).
- For operational (non-cost) health, see [health & metrics](/docs/operations/health-and-metrics).
================================
URL: https://docs.roboco.tech/docs/operations/health-and-metrics
Title: Health & Metrics
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
Two different questions an operator asks: *is the system up?* and *is the workforce productive?* RoboCo answers the first with two HTTP probes, and the second with an operational Metrics view. Neither has anything to do with token cost — for spend, see [cost & usage](/docs/operations/cost-and-usage).
## Liveness and readiness probes
The orchestrator exposes two probes behind nginx (so they're reachable at `localhost:3000/api/...`, or directly on the orchestrator):
| Endpoint | Checks | Returns |
|----------|--------|---------|
| `GET /api/health` | The process is up | Always `200` with `{ status: "ok", version, environment }` once the app is serving |
| `GET /api/ready` | Database (`SELECT 1`) **and** Redis (`PING`) | `200` with `{ status, database, redis }`; `status` is `ok` only when both pass, otherwise `degraded` |
```bash
curl -s http://localhost:3000/api/health
curl -s http://localhost:3000/api/ready
```
`/api/health` is a liveness probe — it stays `200` as long as the process answers, so it can't tell you a dependency is down. `/api/ready` is the real readiness signal: it actively pings Postgres and Redis and reports `degraded` (still HTTP `200`, but `status: "degraded"` with the failing dependency's error in the `database`/`redis` field) when either is unreachable. Point your uptime monitor at `/api/ready` and alert on `degraded`.
The app-level root `/health` used during container startup is additionally gated on the in-house RAG engine being operational, which is why the orchestrator can take a minute to report healthy after a cold start while it indexes documents. See [deployment](/docs/deploy/deployment) for the full startup sequence.
Those probes cover the *infrastructure*; individual agents have their own liveness story. An agent whose MCP gateway breaks (a corrupted `/app` venv firing no verb) stays "up" as a container but does no work, and its verb-heartbeat goes stale. With `ROBOCO_GATEWAY_HEALTH_ENABLED` (default on) the reaper probes such a stale-but-live agent's gateway out-of-band and, once it's been broken past `ROBOCO_GATEWAY_HEALTH_GRACE_SECONDS`, kills and respawns it instead of protecting it forever — so a wedged agent self-heals without operator action. You don't monitor this directly; it shows up as a "broken-gateway agent killed and evicted" line in the orchestrator log.
## The Metrics → Performance view
The **Metrics** page has a **Performance** tab driven by tasks, messages, and notifications — your read on whether work is actually flowing. See the panel walkthrough in [Metrics](/docs/panel/metrics).
What it surfaces:
| Group | Metrics |
|-------|---------|
| Velocity | Tasks completed vs created, average completion time (hours), completion rate |
| Blockers | Active blockers, average and longest blocked time, blockers by team |
| Team & agent | Per-team and per-agent performance |
| Communication | Message/notification volume |
### The ok / slow / critical health signal
Org and per-team health roll up to a single status driven mainly by the **blocked-task ratio**:
|ratio > 0.3| C[critical]
A -->|ratio > 0.15| S[slow]
A -->|otherwise| K[ok]`} />
A team with more than 30% of its tasks blocked reads **critical**; over 15% reads **slow**; below that, **ok**. A heuristic also flags a team sitting on stale active tasks with zero completions. When you see **slow** or **critical**, the Blockers panel tells you where the work is jammed and which team owns it — chase the longest-blocked tasks first.
This status is computed from task state, not exceptions or crashes. A green org-health with a `degraded` `/api/ready` means the infrastructure is wobbling even though the backlog looks healthy — watch both signals, they answer different questions.
## The Metrics → Delivery view
Where Performance answers *is work flowing*, the **Delivery** tab answers *where the time goes and how clean the work is* — reconstructed from the recorded task-transition history, so it costs no extra bookkeeping. Use it when velocity dips and you need the *why*:
| Panel | Operational read |
|-------|------------------|
| Cycle time by stage | Average dwell per lifecycle stage — a tall stage is where work waits (e.g. review vs coding) |
| Bottlenecks | The single worst stage by total time absorbed + how many tasks are parked in each stage right now |
| Rework | How often work bounces to `needs_revision`, by cell and by agent (the bounce charged to the QA/PR-reviewer who sent it back), plus its token cost |
| Cell scorecards | Per-cell completed / avg cycle / rework / cost over 7 days |
The full reading guide — especially how rework attribution works — is in the panel walkthrough at [Metrics → Delivery](../panel/metrics.md#delivery).
## Next
- Walk the panel surface in [Metrics](/docs/panel/metrics) and [the command center](/docs/panel/command-center).
- For token spend and the cost dashboard, see [cost & usage](/docs/operations/cost-and-usage).
- For the startup sequence and what each container needs, see [deployment](/docs/deploy/deployment).
================================
URL: https://docs.roboco.tech/docs/operations/release-management
Title: Release Management
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
RoboCo's **gated release manager** watches the company's own repository and proposes new versions for you to approve. It automates every step up to the decision, then stops and waits for your explicit approve or reject. Nothing publishes without you.
## How the proposal works
A background loop runs the same deterministic readiness sweep a human would:
- diff since the last tag
- conventional-commit classification and semver bump suggestion
- version-reference completeness
- CHANGELOG completeness and a docs-drift check
- migration single-head check
- the quality gate's state
Once the change set clears a threshold and the gate is green, the manager opens **one** release proposal and holds it. The proposal is an ordinary task owned by the Secretary; it is never dispatched or started by anyone. It appears in the panel for you to **approve** or **reject with changes**.
B{Readiness sweep:
commits · gate · docs · migrations}
B -->|below threshold or red gate| A
B -->|ready| C[Hold ONE release proposal for the CEO]
C -->|reject| D[CANCELLED — frees the slot]
C -->|approve| E[ReleaseExecutor: fail-closed]
E --> F{Environment ladder set?}
F -->|yes| G[Promote head→…→prod]
F -->|no| H[Bump on default branch]
G --> I[Version bumps + CHANGELOG]
H --> I
I --> J{Head rung CI verdict green?}
J -->|no| K[Abort — nothing committed]
J -->|yes| L[Commit + push chore release]
L --> M{Release-commit CI green?}
M -->|no| K
M -->|yes| N[Publish via GitHub REST]`} />
## Fail-closed execution
When you approve, `ReleaseExecutor`:
1. Promotes the environment ladder, merging every rung above prod into the prod checkout — head→…→just-below-prod — before touching any version file. A ladderless project has nothing to promote.
2. Writes version bumps across the canonical file set (derived from the previous `chore(release):` commit).
3. Adds the CHANGELOG entry.
4. Verifies the **head rung's CI verdict** — it does not re-run the full quality gate inside the production container.
5. Aborts before committing anything if that verdict is red.
6. Commits `chore(release): X.Y.Z` and pushes.
7. Waits for that commit's own CI to go green.
8. Publishes the release through the **GitHub REST API**, not the `gh` CLI.
An already-published version is a no-op: approving twice never double-releases.
## Release commit identity and signing
The release commit is authored as **RoboCo Release Manager** `` by default. You can override the committer identity:
```bash
ROBOCO_RELEASE_GIT_NAME=RoboCo Release Manager # default
ROBOCO_RELEASE_GIT_EMAIL=release-manager@roboco.local # default
ROBOCO_RELEASE_SIGN_COMMITS=false # set true to GPG-sign
```
## Curated CHANGELOG body
If the CHANGELOG contains a curated `[Unreleased]` section, the executor uses that body for the release entry and empties the section after stamping it into the new version. If no curated body is present, it falls back to the auto-generated conventional-commit summary.
## Environment-ladder promotion
If RoboCo's own project has declared an [environment ladder](/docs/optional/env-branches), the executor merges every rung above prod into the prod branch — head→…→just-below-prod — **before** bumping versions. Without a ladder, it behaves the same single-branch way as before. If any promotion merge conflicts, the release aborts before anything is committed.
## Bounds and tuning
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_RELEASE_MANAGER_ENABLED` | `false` | Master switch. Even on, it only proposes. |
| `ROBOCO_RELEASE_MIN_COMMITS` | `8` | Minimum unreleased commits since the last tag before a release is proposed. A `feat` or `security` commit also qualifies on its own. |
| `ROBOCO_RELEASE_MANAGER_INTERVAL_SECONDS` | `3600` | Seconds between readiness assessment passes. |
## Enable it
Flip **Settings → Feature Flags → "Gated release manager"** (or set `ROBOCO_RELEASE_MANAGER_ENABLED=true`) and restart the backend. There is no per-project opt-in — it always targets RoboCo's own repository.
The release manager only proposes. It never approves, merges, tags, or publishes on its own. Every release waits for your explicit decision.
## Next
→ [Autonomous Maintenance](/docs/optional/autonomous-maintenance) for the multi-repo CI-watch and dependency-update bot that share the same release-executor background · [Environment Branches & EnvSync](/docs/optional/env-branches) for the ladder the executor promotes · [Deploy](/docs/deploy/deployment) for the host release pipeline and Makefile targets.
================================
URL: https://docs.roboco.tech/docs/operations/whats-new-0-28
Title: v0.28.0 Release Notes
================================
import { Admonition } from "@/components/docs/admonition";
## [0.28.0] — 2026-07-29
Three things shipped in this release: the eval harness now drives real agent spawns, the Board's recurring review work runs through one shared program registry, and the X release-post drafter stopped parroting the changelog.
## The eval harness drives real agent spawns (#701)
`python -m roboco.eval run` now works end-to-end for a developer-role cohort. `OrchestratorStageSpawner.__init__` no longer raises `NotImplementedError` — it constructs a real `AgentOrchestrator()` the same way the production dispatcher builds one, so `run_stage` drives a real `spawn_agent` call per turn instead of a stub.
The no-production-reach guarantee lives in `_generate_mcp_config`, which now prefers `settings.api_url` when it's set, in both `PROJECT_HOST_PATH` branches. The harness's `_bench_environment` patches `settings.api_url` to the disposable stack's URL, so a spawned container's MCP servers resolve to the throwaway orchestrator instead of the real production hostname or `127.0.0.1:{port}`. `_seed_company` still seeds agents under their real production UUIDs — that's correct, since orchestrator-internal helpers keyed by the static registry need to resolve exactly as they would in a real deployment; the isolation is the URL, not the UUID.
The real spawn path needs a Docker daemon and built agent images. The injectable scripted `StageSpawner` used by the e2e smoke suite is unaffected — it injects its own `make_spawner` — and remains the unit-test fallback when Docker isn't available.
A new `tests/unit/runtime/test_eval_mcp_config_isolation.py` pins the no-production-reach guarantee without needing a Docker daemon.
## The Board Program registry (#689, #699)
Twelve new standing assignments for the Board now run through one generic `BoardProgram` / `BoardProgramEngine` registry instead of bespoke, one-off engines. Every program follows the same shape — a role explores, proposes once, and the proposal sits held until you decide:
| Program | Owner | What it does |
|---------|-------|---------------|
| **Pest Control** | Product Owner | Mines the findings ledger and QA-note flake patterns for recurring bugs; proposes bug tasks you approve/reject individually. |
| **Spackle** | Product Owner | Finds gaps between what's shipped and what's surfaced — routes with no panel page, flags with no docs — and proposes gap-fill tasks. |
| **Scales** | Product Owner | Checks the live backlog against your charter; approving a proposal reprioritizes or cancels a real existing task. |
| **Dogfood** | Product Owner | Walks a project's live surfaces with browser tools the way a user would, and proposes friction-fix tasks. |
| **Periscope** | Head of Marketing | Competitor and adjacent-tool research, delivered as a read-only market brief. |
| **Megaphone** | Head of Marketing | Mines shipped-task and changelog history for editorial angles beyond ordinary release posts, drafted to the X Post Queue. |
| **Mirror** | Head of Marketing | Checks a project's messaging surfaces (README, docs site) against the charter and proposes docs tasks. |
| **Barfly** | Head of Marketing | Searches X for conversations where RoboCo is relevant but unmentioned, and drafts a reply to the X Post Queue. |
| **War Room** | Head of Marketing | Plans a release campaign as an ordered set of draft posts, each carrying a recommended (never auto-scheduled) publish time. |
| **Coroner** | Auditor | Opens on a task's 3rd bounce, a cancel-after-work-started, or a budget-block; reports the root cause and one proposed process change. |
| **Librarian** | Auditor | Mines journals and learnings for a recurring pattern and drafts 1-3 playbooks for the normal review queue. |
| **Sentinel** | Auditor | Reports org-wide drift — waiver accumulation, conventions hotspots, docs staleness, budget anomalies. |
Nothing a Board program produces posts, publishes, or executes on its own. Every artifact — a report, a drafted task, an X post, a playbook — is held for your explicit approval, exactly like the rest of RoboCo's gated surfaces (the [release manager](/docs/operations/release-management), the [X engine](/docs/optional/x-engine)). The registry is a scheduling and consistency change, not a new autonomy grant.
See [Board Programs](/docs/optional/board-programs) for the full lifecycle (trigger → explore → propose → decide → materialize → learn) and per-project participation rules, and [Org & Roles](/docs/company/org-and-roles) for what the Product Owner, Head of Marketing, and Auditor do day to day.
## Release-post drafter: highlights in marketing order, not changelog order
The X announcement drafter that turns a release's CHANGELOG into a post had two bugs: it sometimes parroted changelog bullets back verbatim instead of writing marketing copy, and it could lead with the wrong section — a `Fixed` or `Security` bullet ahead of the release's actual headline features. Both are fixed: drafts no longer copy changelog prose directly, and highlights are now reordered into marketing order before the draft is written, so the lead bullet is the one worth announcing.
## Next
→ [Release Management](/docs/operations/release-management) for how this release itself was proposed and published · [Org & Roles](/docs/company/org-and-roles) for the Board roles that run the new program registry.
================================
URL: https://docs.roboco.tech/docs/operations/whats-new-0-29
Title: v0.29.0 Release Notes
================================
import { Admonition } from "@/components/docs/admonition";
## [0.29.0] — 2026-08-21
Three things shipped in this release: the guard stack moved to guard-core 3.12.0 and fastapi-guard 7.6.0 with new env vars and changed exclude_paths semantics, operators can now pause maintenance across three independent scopes, and the X post drafter gained an impact bar.
## guard-core 3.12.0 / fastapi-guard 7.6.0 adoption
Both packages are now floor-pinned: `fastapi-guard>=7.6.0` and `guard-core[redis]>=3.12.0`. The pairing matters because guard-core 3.12.0 with fastapi-guard below 7.6.0 silently drops every body-based `return_pattern` rule, so the floor pins are a safety contract, not just a version bump.
A new log-only behavior rule watches `status:401` responses for credential-probing visibility. It fires at a threshold of 20 in 300s and never bans, since a stale internal HMAC token must never earn a ban (bans override the whitelist).
The `exclude_paths` semantics changed upstream: excluded paths no longer skip every check. They skip only WAF and behavioral tracking, while IP-ban and rate-limit rules still enforce. An excluded path is now a narrow carve-out for internal endpoints, not a blanket bypass.
Three new environment variables control the guard's scanning and telemetry surface. `ROBOCO_GUARD_SCAN_RESPONSE_BODY` (default off) lets `return_pattern` rules read response bodies, not just status codes; RoboCo's own rules never need it. `ROBOCO_GUARD_LOG_SUSPICIOUS_LEVEL` controls the verbosity of suspicious-activity logging. `ROBOCO_GUARD_TELEMETRY_ENABLED` toggles telemetry payloads, which now exclude HMAC and session headers via `agent_sensitive_headers`.
`apply_guard` now exposes an internal readiness probe at `GET /_guard/status`, reachable only on the docker mesh or `localhost:8000` since nginx never routes it.
`route_resolution_strict` was evaluated and deliberately left off. Verified live, it turns every unresolved path into a `500` for every client regardless of whitelist, which would blind the new `status:404` rule (a scanner sweep would read as 500s, not 404s) and turn routine client and panel typos into apparent server errors.
See [HTTP Security](/docs/optional/http-security) for the full guard configuration reference, and [Env Reference](/docs/deploy/env-reference) for the new environment variables.
## Operator maintenance pause
Operators can now pause maintenance work without killing running agents. A pause drains: it lets in-flight work finish rather than killing active sessions.
Three independent scopes are each pausable on its own: `dispatch`, `board_programs`, and `engines`. You can pause one without touching the others, so a board-program pause does not halt the dispatch loop.
Pause state lives in the settings store with `paused_by`, `reason`, and `expires_at` fields. The default duration is 4 hours, with a maximum of 336 hours (two weeks). Pause and resume are CEO-only API routes.
The panel shows a banner with a live countdown and a Resume button when any scope is paused.
The stale-claim reaper and budget sweep continue running during a pause. A pause stops new dispatch and program work, not the housekeeping that keeps the backlog clean.
See [Autonomous Maintenance](/docs/optional/autonomous-maintenance) for the maintenance lifecycle and the new pause scopes.
## X impact bar for post drafting
The X post drafter now includes an impact bar that scores each draft's potential reach and engagement. This gives the drafter a visible signal during composition, so you can compare drafts before publishing rather than only measuring after the fact.
## Security fixes
Forwarded LAN IPs no longer ride the internal-mesh whitelist. Previously, a forwarded LAN address could be treated as internal, bypassing guard checks meant for external clients.
`cryptography` 50.0.0.0 is now adopted, bringing the latest security patches and deprecation cleanups.
## Next
→ [HTTP Security](/docs/optional/http-security) for the full guard configuration · [Autonomous Maintenance](/docs/optional/autonomous-maintenance) for the maintenance pause scopes · [Env Reference](/docs/deploy/env-reference) for the new guard environment variables.
================================
URL: https://docs.roboco.tech/docs/operations/whats-new-0-30
Title: v0.30.0 Release Notes
================================
import { Admonition } from "@/components/docs/admonition";
## [0.30.0] - 2026-09-13
The largest platform release so far: sixteen merged PRs that split the backend into three processes, decompose the orchestrator into mixins, make the gateway's WAF scanning real, move RAG writes onto a dedicated worker, and add the first live-catalog external provider. Nothing here changes how you drive the fleet day to day, but the deployment topology changes (three orchestrator-family processes instead of one), so read the first section before upgrading.
## The backend runs as three processes
`ROBOCO_ROLE` splits the old single backend process into `api`, `dispatcher`, and `indexer`. The API serves the panel, the dispatcher runs the fleet loops, and the indexer owns embedding and RAG writes. Measured on the production NAS, the split stops the three workloads from pinning one core against each other.
You don't configure any of this by hand: the shipped compose files carry the three-role topology, resolve the dispatcher per request (so nginx starts even before the dispatcher does), and wait for both split roles at bootstrap. Just pull and bring the stack up as usual.
## A stuck orchestrator can now be diagnosed live
0.29.0's night of an orchestrator sitting at 113% CPU for 80 minutes with no way to see why produced three permanent fixes:
- The image ships `py-spy`. Attach to the running process without a restart: `docker compose exec orchestrator py-spy dump --pid 1`. The compose files grant the `SYS_PTRACE` capability this needs.
- Postgres collects real query stats: the container now enables `pg_stat_statements` (with the preload flag set), so slow-query questions have data behind them.
- Vector search uses an HNSW index instead of `ivfflat`. The old index trained its clusters on an empty table and the planner never trusted it; the new one builds incrementally and repairs a previously-failed build on startup.
## The gateway's WAF scanning is real
Every gateway endpoint (all 102 of them) is now scanned by the WAF for real, without pulling the internal docker mesh off the whitelist or exposing it to rate limiting.
## Orchestrator internals: mixins, not a monolith
The orchestrator (previously a single ~19,700-line class) is mechanically decomposed into per-family mixins under `roboco/runtime/engines/`, with no behavior change. This is codebase health, not a feature - it's why the release was large and why future changes get smaller.
## OpenRouter: the first live-catalog provider
Save an OpenRouter key in **Settings → AI Providers**, pick a model from the live catalog (with real per-million pricing), and route the fleet or individual agents to it. OpenRouter models get their own agent image and the same rate-limit parking as every other provider. See [Run on OpenRouter](/docs/models/openrouter).
0.30.0 adopts fastapi-guard 8.0.0 / guard-core 4.0.1. guard-core 4.0.0 changed how rate-limit keys are hashed, so every in-flight rate-limit counter resets exactly once on deploy. No action needed - it just means a clean slate for limiters the first time this version comes up.
## Also in this release
- A `cancel_leaf` gateway verb gives Cell and Main PMs a mechanical way to close a zero-diff leaf task instead of bouncing it around.
- A CEO-gated portfolio dashboard route: the first per-project metrics view.
- Branch and parent-topology validation moved to `open_pr` (and the re-parent path), so a mismatched PR base is caught at PR time, not at closure.
- The eight MCP servers migrated to the mcp 2.x SDK.
## Next
- [Provider Routing](/docs/models/provider-routing) for how the new provider fits the ladder
- [HTTP Security](/docs/optional/http-security) for the guard configuration
- [v0.31.0 Release Notes](/docs/operations/whats-new-0-31) for what came next
================================
URL: https://docs.roboco.tech/docs/operations/whats-new-0-31
Title: v0.31.0 Release Notes
================================
import { Admonition } from "@/components/docs/admonition";
## [0.31.0] - 2026-09-17
This release is about model choice: three new providers land (Z.ai, Nebius Token Factory, and Hummin), hummin becomes the default way to run GLM, NVIDIA's Nemotron models become configurable at every routing level, and QA can run test suites inside a Token Factory Sandbox microVM. On the ops side, the NAS deploy path goes blue-green: a deploy becomes an nginx include-file flip with zero dropped requests.
## Three new providers
- **Hummin** (the GLM go-to): the GLM-native `hummin` CLI runs headless in Docker on your Z.ai GLM Coding Plan key, with `glm-5.3-flash:high` as the automatic default and per-role reasoning tiers seeded out of the box (board, reviewers, and the Main PM on `:high`, delivery roles on `:low`). See [Run GLM on hummin](/docs/models/hummin).
- **Nebius Token Factory**: NVIDIA's open-model cloud, with Nemotron 3 Super as the fleet default and four live-verified Nemotron ids configurable at every routing level, exactly like Claude. See [Run on Nebius](/docs/models/nebius).
- **Z.ai** (manual fallback): GLM through the Claude Code runtime via z.ai's Anthropic-compatible endpoint. Since hummin shipped, the GLM catalog entries point at hummin and Z.ai keeps its key endpoint as the manual path.
Every provider can now power every agent, **including the interactive Intake and Secretary chats** (previously those two were pinned to Anthropic; the exemption list is retired as of this release).
## Run QA tests inside a Token Factory Sandbox
A new `run_sandbox_tests` gateway verb executes the QA test run inside a Nebius Token Factory Sandbox microVM: it archives the committed HEAD, uploads it, spawns a disposable instance, runs the command, and returns exit code, output tails, and the metered cost as evidence. It's flag-gated (`ROBOCO_TOKEN_FACTORY_SANDBOXES_ENABLED`, also on the Feature Flags card) and inert on any failure - with the flag off, or without a Nebius key, QA keeps its container shell and nothing else changes.
## Blue-green NAS deploys
One compose file now carries both colors (blue always up, green under a profile), and a deploy is: ensure every image exists on the host, bring the new color up, poll its health, flip a traffic include file (guarded by `nginx -t`), reload nginx, stop the old color. Requests never drop, and rollback is re-running the stopped color. Each color exports its own API URL so agents always resolve their own generation's dispatcher.
## Panel fixes
- The OpenRouter and Nebius model pickers rendered blank nameless rows and could not actually select a model (the panel had been written against an invented response schema, and the tests mocked the same invented shape). Both now render the real catalog fields with per-million pricing, and a regression test pins that clicking a row selects it.
- Phantom contract fields on the panel's Task and Notification types were removed, and the notifications client stopped sending a query parameter the backend never accepted.
## Pricing, re-verified
Every published model price was re-verified against source (2026-09-16). Sonnet 5's 2/10 standard rate is permanent (the announced increase was cancelled), and `glm-5.3-flash` gained its pricing row.
This release ships six Alembic migrations (097 through 102: the Z.ai, Nebius, and hummin provider rows). Run `docker compose exec orchestrator alembic upgrade head` after pulling, before restarting the stack.
## Next
- [Provider Routing](/docs/models/provider-routing) for the full mode list
- [Env Reference](/docs/deploy/env-reference) for the new variables
- [v0.30.0 Release Notes](/docs/operations/whats-new-0-30) for the previous release
================================
URL: https://docs.roboco.tech/docs/optional
Title: Optional Subsystems
================================
import { Admonition } from "@/components/docs/admonition";
Beyond the core delivery loop, RoboCo ships a set of optional engines — most **off by default** — that you turn on when you want them. They're the "company-in-a-box" capabilities and the stricter quality gates. This page is the reference for all of them; each has its own page below.
## The Feature Flags card
You toggle these from **Settings → Feature Flags** in the panel rather than hand-editing environment variables. A toggle persists in the settings store and **takes effect on the next backend restart**. The matching `ROBOCO_*` environment variable is the same switch at the source — an unset flag falls back to its environment/config default, and some flags carry extra configuration (an API key, a project slug) that only lives in the environment.
Flipping a flag in the panel saves it immediately, but the subsystem it controls is wired up at startup — so the change lands on the **next backend restart**, not instantly.
## What you can turn on
| Subsystem | Flag | Default | What it does |
|-----------|------|---------|--------------|
| [Fable + Ponytail doctrine](/docs/optional/fable-mode) | `ROBOCO_FABLE_MODE_ENABLED` | off | Composes outcome-first communication + build-lazy doctrine into every spawned agent's prompt (+ guard hooks on the Claude runtime). |
| [Architectural Conventions](/docs/optional/conventions) | `ROBOCO_CONVENTIONS_ENABLED` | off | Per-project rules for *where code lives*; hard-gates agents from misplaced code and lint suppressions. |
| [Toolchain matching](/docs/optional/toolchain-matching) | `ROBOCO_TOOLCHAIN_MATCH_ENABLED` | off (on in the personal compose) | Builds each project under its own declared Python and blocks gates when the suite can't run. |
| [Web research](/docs/optional/web-research) | `ROBOCO_RESEARCH_ENABLED` | off | Gives Board/PM agents gated web search & fetch through a provider you supply. |
| [Strategy engine](/docs/optional/strategy-engine) | `ROBOCO_STRATEGY_ENGINE_ENABLED` | off | Notify-only nudges when the company drifts, goes idle, or stalls. |
| [Pitch provisioning](/docs/optional/pitch-provisioning) | `ROBOCO_PROVISIONING_TOKEN` (+ org) | inert until set | On pitch approval, auto-creates repos and seeds a build task. |
| [External / internal PR review](/docs/optional/pr-review) | `ROBOCO_EXTERNAL_PR_ENABLED` / `ROBOCO_INTERNAL_PR_ENABLED` | off | Reviews inbound external/fork PRs and untied org-repo PRs. |
| [Possibilities matrix](/docs/optional/possibilities-matrix) | `ROBOCO_POSSIBILITIES_MATRIX_ENABLED` | off | Lets a developer close an already-done task (commits, open PR, full AC coverage, no open findings) in one call, trusting PR CI-green in place of the local gate. |
| [Task & project cost budgets](/docs/optional/task-budgets) | `ROBOCO_TASK_BUDGETS_ENABLED` | off (config) / **on** in the NAS compose | A monthly per-project spend cap refuses new work-starting claims; a per-task $ cap blocks an over-budget task mid-flight and notifies you. |
| [Self-healing CI](/docs/optional/self-heal) | `ROBOCO_SELF_HEAL_ENABLED` (+ originate) | off | Watches RoboCo's own CI and, optionally, queues a CEO-gated fix task. |
| [Multi-repo CI-watch](/docs/optional/autonomous-maintenance) | `ROBOCO_CI_WATCH_ENABLED` (+ per-project) | off | Watches each opted-in project's CI and opens one fix task when it goes red; never auto-merges. |
| [Dependency-update bot](/docs/optional/autonomous-maintenance) | `ROBOCO_DEP_UPDATE_ENABLED` (+ per-project) | off | Read-only checks whether an upgrade changes a project's lockfiles and opens an update task; never auto-merges. |
| [Environment branches & EnvSync](/docs/optional/env-branches) | `ROBOCO_ENV_SYNC_ENABLED` | off | Cascades a project's declared environment ladder (dev → qa → prod, etc.) down automatically; a clean merge auto-pushes, a conflict opens one sync PR + task. |
| [Docs-Sync](/docs/optional/docs-sync) | `ROBOCO_DOCS_SYNC_ENABLED` | off | On every release publish, opens a docs-update task against the registered docs-site project when the docs look drifted; never auto-merges. |
| [HTTP security (fastapi-guard)](/docs/optional/http-security) | `ROBOCO_GUARD_ENABLED` | off (config) / **active enforcement** on the NAS compose | Fronts the API with a WAF, IP/rate controls, security headers, and prompt-injection / secret-exfil / SSRF validators for public/cloud exposure — calibrated so active mode doesn't false-positive on agent traffic. |
| [Sandboxed dev DB/Redis/Mongo](/docs/optional/sandbox-db) | `ROBOCO_SANDBOX_DB_ENABLED` (+ per-project) | off | A dev/QA agent requests a throwaway sandboxed sibling container on demand (`request_sandbox`) for an opted-in project, instead of production DB credentials in the agent's gate env. |
| [DB network isolation](/docs/optional/db-network-isolation) | `ROBOCO_DB_NETWORK_ISOLATED` | off (config) / **on** in all 3 tracked composes | Compose-topology setting (not a panel flag): a second `roboco_data` network carries only postgres/redis, so agent containers can't reach them at all. |
| [Cloud auth](/docs/optional/cloud-auth) | `ROBOCO_CLOUD_AUTH_ENABLED` | off | A single seeded CEO login + sliding session cookie, so the panel/API can be exposed beyond a trusted LAN. Requires TLS; not a panel flag. |
| [Telegram bridge](/docs/optional/telegram-bridge) | `ROBOCO_TELEGRAM_ENABLED` (+ `_INBOUND_ENABLED`, `_MINIAPP_ENABLED`) | off | Telegram DMs to you on CEO escalation and task completion — subject + panel link only, never the body. With the inbound sub-switch on, also `/status`/`/queue`/`/task`/`/agents`/`/blocked`/`/usage` commands, one-tap Approve/Reject buttons, and `/secretary`/`/newtask` chat bridging into the panel's own live conversations. With the Mini App sub-switch on (env-only, needs cloud auth), the bot's menu button opens a `/tg` phone cockpit — a Today brief, native approval cards, an operations ring (Ship/Ack all/Sweep/Fleet), a read-only board with tap-through task detail, inbox, A2A chat — that signs you in automatically off Telegram's own signed launch data; credentials entered in the panel. |
| [X (Twitter) engine](/docs/optional/x-engine) | `ROBOCO_X_ENGINE_ENABLED` (+ `_REPLIES_ENABLED`, `_FEATURE_SPOTLIGHT_ENABLED`) | off | Drafts release-announcement, mention-reply, and feature-spotlight posts (the last spawns the Head of Marketing to investigate what's shipped), held for your per-post approval; credentials are entered in the panel, never `.env`. |
| [Video engine (HyperFrames)](/docs/optional/video-engine) | `ROBOCO_VIDEO_ENGINE_ENABLED` (+ `_ON_RELEASE` / `_ON_SPOTLIGHT`) | off | Authors short marketing videos for X and TikTok via a credential-free video-renderer (HyperFrames) sidecar, held for your per-clip approval. |
| [Board Programs](/docs/optional/board-programs) | per-program switch on **Business → Programs** (no master flag) | off | The registry of 14 standing Board assignments (2 migrated + 12 new) — trigger, explore, propose once, hold for your decision, learn from the outcome. |
| [Board roadmap engine](/docs/optional/roadmap-engine) | `ROBOCO_ROADMAP_ENGINE_ENABLED` | off | Weekly, the Product Owner proposes a themed cycle of 3-7 roadmap items; you approve or reject each one into the backlog. Now rides the [Board Programs](/docs/optional/board-programs) registry, behavior-unchanged. |
| [Obsidian vault](/docs/optional/obsidian-vault) | `ROBOCO_OBSIDIAN_VAULT_ENABLED` (+ `_VAULT_INTAKE_ENABLED`) | off; not a panel flag | Projects tasks/journals/A2A into a browsable, rebuildable Obsidian vault; a `#roboco`-tagged inbox note becomes a held board-review draft. |
Provider overload parking (`ROBOCO_OVERLOAD_BREAK_ENABLED`) and the dangling-image prune (`ROBOCO_IMAGE_PRUNE_ENABLED`) are **on by default** — they're not things you enable, they're safety nets you can disable. See [Resilience](/docs/models/resilience).
================================
URL: https://docs.roboco.tech/docs/optional/conventions
Title: Conventions
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
The `make`-style gates you configure per project (ruff, mypy, eslint, your test suite) answer one question: *is the code valid?* They cannot answer a second one a senior reviewer would catch on sight: *does this code live where it belongs?* The Architectural Conventions Standard is RoboCo's answer to that second question. It gives each project a repo-canonical architecture map and a tree-sitter validator that **hard-gates** an agent from landing a model defined inside a router, a route handler that runs its own SQL, a React component that fetches its own data, or a `# noqa` / `# type: ignore` that quietly silences the linter.
It is **off by default** behind `ROBOCO_CONVENTIONS_ENABLED`. Every hook — the auto-scaffold, the prompt injection, the per-task constraints, and the gates — is fully inert until you turn it on.
## What it does
When enabled, the standard reaches the work in two directions. It *guides* agents up front — an "Architectural Standard" block is injected into every agent's prompt at spawn, and a `## Constraints` section listing the project's block-level rules and module boundaries is auto-attached to every task. And it *enforces* the same rules at the gate — a `block`-level finding refuses `i_am_done` (the developer's pre-submit) and `pr_pass` (the [in-path PR gate](/docs/optional/pr-review)) with the offending `file:line` and a fix hint, while QA sees the findings in its review evidence.
The rules live in a per-project `.roboco/conventions.yml` with four curated parts plus a set of structural checks:
| Part | What it is |
|------|-----------|
| **Module map** | Path prefixes mapped to a human purpose and the definition *kinds* forbidden there (`model`, `route`, `helper`, `business_logic`, `component`). "`routers/` is for HTTP routes — no models, no helpers." |
| **Rules** | A toggleable rule set. Each rule fires at `warn` (advisory, never blocks) or `block` (refuses the gate). |
| **Custom rules** | Project-specific regex rules — a pattern, a message, and a level, optionally scoped to languages. |
| **Waivers** | Accountable per-`(path, rule)` escape hatches with a written reason — the sanctioned way to relieve a false positive, reviewed in the PR. |
### Placement, hygiene, and modularity checks
The validator runs four check families over each changed file:
- **Placement** — a definition whose *kind* is forbidden in its module (a model in a router). A misplaced **model**, **route**, or **component** is `block` by default; a misplaced **helper** only `warn`s — `helper` matches *any* top-level function, too blunt a signal to hard-block a route file's small private glue (the body-level `thin_routes` check is the real fat-handler guard).
- **Hygiene** — the universal, stack-agnostic house-style rules seeded into every project: `no_lint_suppressions` (`block` by default — no `# noqa`, `# type: ignore`, `eslint-disable`) and `no_inline_comments` (`warn`). A small allowlist of *structurally unavoidable* framework codes is exempt from `no_lint_suppressions` — ruff's flake8-type-checking codes (`TC001`–`TC003`, for an import a framework needs at runtime) and pydantic's `prop-decorator` — so the rule keeps its teeth on genuine error-silencing without footgunning every pydantic/FastAPI project. A bare `# noqa` / `# type: ignore` or any other code is still flagged.
- **Custom** — your project-specific regex rules.
- **Modularity** — separation-of-concerns judgements the linters are blind to, inspecting a file's *composition* and a definition's *body*, not just its top-level kind:
| Rule | Fires when | Default level |
|------|-----------|---------------|
| `modular_cohesion` | One file mixes architectural concerns (e.g. a model *and* a route *and* a component) | `block` |
| `thin_routes` | A route handler does its own data access (SQLAlchemy `execute`/`scalars`/`add`/`select`…) instead of delegating to a service. Transaction-lifecycle calls (`commit`/`flush`/`refresh`) do **not** count — an explicit `await db.commit()` after delegating is a valid pattern | `block` |
| `thin_components` | A React component fetches data in its body instead of through a hook | `block` |
| `god_class` | A class grows past 15 methods (single-responsibility smell) | `warn` |
Every check fires only on a confident, structural signal, and abstains when it is uncertain — so a `block`-level gate is never tripped by a guess. If the validator genuinely *cannot* run on a diff (a parse or grammar error), it is **fail-loud**: it exits non-zero and the gate blocks rather than passing silently.
## The effective map: defaults, present, absent, or partial
Consumers never read the raw committed file — they read the **effective map**, so behaviour is identical whether `.roboco/conventions.yml` is present, absent, or partial. `ConventionsService` (`roboco/services/conventions.py`) builds it by auto-deriving a baseline from a repo scan (it infers modules from directory names like `routers/`, `models/`, `services/`, `components/`, `hooks/`; **excludes test and documentation trees** — `tests/`, `docs/` — since those legitimately define fixtures and aren't enforced code; detects languages by file extension; seeds the universal hygiene rules) and then overlaying the committed file on top. The result is cached per `(project, HEAD sha)`.
That is the load-bearing property: **the standard is enforced even before any file is committed.** A project with no `.roboco/conventions.yml` still gets sensible auto-derived rules and is gated by them. Resilience is built in — a missing file degrades to the auto-derived defaults, and an unparseable file falls back to the last-good cached map (status `degraded`) so the standard is never silently switched off by a typo.
The committed file and the repo scan are read from a project-level **read clone** that the service ensures on demand (pinned to the default branch's HEAD), not from any agent's working clone. This is the backfill: a project created long before the standard existed — with no manually-configured workspace path — still resolves its committed `.roboco/conventions.yml` the first time the panel, a spawn, or a task asks for it. There is nothing to wire up.
## The per-project Conventions editor
Each project carries a **Conventions** page-level tab on its settings page (`/projects/[id]/settings`, panel component `panel/src/components/conventions/conventions-tab.tsx`), alongside the settings cards rather than squeezed into a dialog. From there you manage the whole standard without hand-editing YAML:
- **Module boundaries** — add/remove modules, set each one's path and purpose, and click a kind badge (`no model`, `no route`, `no helper`, `no business_logic`, `no component`) to toggle whether it is forbidden there.
- **Rules** — flip each rule between `warn` and `block` with a switch.
- **Custom rules** — add a regex `id`/pattern/message and set its level.
- **Waivers** — exempt a file from a rule with a written reason.
- **Recent violations** — the latest findings recorded across this project's tasks, each tagged with its rule and level.
Two state banners keep you honest about what is in force:
When no file is committed yet, the tab shows a neutral **"Using auto-derived defaults"** banner — *not* an error. These rules are already enforced. The Save button reads **"Save defaults to repo"**: one click backfills the canonical file. (The earlier read-only, "missing"-as-broken behaviour is gone — this is the full editor with one-click backfill.)
If a committed file won't parse, an amber **"Conventions degraded"** banner appears. The effective map has fallen back to the last-good cache plus defaults; **Restore from last-good** re-commits the previous working file.
### Save and Restore open a PR
The standard is repo-canonical, so the editor never edits files behind your back. **Save to repo** and **Restore from last-good** each open a pull request against the project (via `GitService.open_conventions_pr`) carrying the new `.roboco/conventions.yml`. You review and merge it like any other change. If the project's workspace isn't cloned yet, the change is prepared on the branch and the toast tells you no remote PR was opened.
## How agents are hard-gated
B[i_am_done]
B --> C{conventions validator
on changed files}
C -->|block finding
or could-not-run| D[REJECTED
file:line + fix hint]
C -->|warn-only / clean| E[proceeds to QA]
D --> F[Dev fixes or commits a waiver]
F --> B
E --> G[QA sees findings in claim_review evidence]
G --> H[PR assembled]
H --> I[pr_pass at the in-path gate]
I --> J{same validator}
J -->|block / could-not-run| K[REJECTED back to needs_revision]
J -->|clean| L[PM merges]`} />
Enforcement is deterministic and lives at two gateway choke points (`roboco/services/gateway/choreographer/`):
- **`i_am_done`** — the developer's pre-submit. A `block`-level finding refuses the verb with the offending `file:line` and a fix hint; the findings are also persisted to the project's violations feed (even the ones that blocked).
- **`pr_pass`** — the [in-path PR-review gate](/docs/optional/pr-review). The same validator runs on the assembled PR's changed files; a `block` finding fails it back to `needs_revision`.
- **QA evidence** — when QA calls `claim_review`, the convention findings ride along in its review briefing so the human-style review sees them too.
A false positive is relieved one way only: the developer commits a **waiver** in their branch — an accountable `(path, rule, reason)` entry reviewed in the PR — never a silent in-code `# noqa`.
## Enable it
You can turn the standard on two ways; the effect is the same.
Open **Settings → Feature Flags** and toggle **"Enforce a per-project architectural standard"** on.
A feature-flag toggle persists in the settings store and is applied on the **next backend restart** — it is not a hot reload. Flip it, then restart the orchestrator.
Set the flag in your compose/env and restart:
```bash
ROBOCO_CONVENTIONS_ENABLED=true
```
An unset flag falls back to its config default (off). See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- Every agent's prompt carries the project's "Architectural Standard" block, and every project task gets an auto-attached `## Constraints` section.
- `i_am_done` and `pr_pass` block on `block`-level findings; QA sees findings in its review evidence.
- The project's **Conventions** tab is live — view the effective map and health, edit it, and Save/Restore via a PR.
- The validator CLI (`python -m roboco.conventions check --root --files …`) runs inside the agent image (the Python and TypeScript tree-sitter grammars are shipped there).
When the flag is off, none of the above happens — no prompt block, no constraints, no gating, no scaffold.
## Next
→ [Toolchain matching](/docs/optional/toolchain-matching) closes a different hollow-pass hole · [The in-path PR gate](/docs/optional/pr-review) is where `pr_pass` runs · back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/fable-mode
Title: Fable + Ponytail Doctrine
================================
import { Admonition } from "@/components/docs/admonition";
Fable-mode layers two pieces of behavioral doctrine into every agent the orchestrator spawns — making the fleet communicate outcome-first and build lazily-correct, on the model tiers you already run. It changes *how agents behave*, not which model backs them.
It's **off by default**. When off, the spawn path — composed prompt, hooks, everything — is byte-for-byte what it was before the feature existed.
## What it adds
**The Fable doctrine** is a universal cross-role layer composed into every agent's system prompt right after the base rules: lead every report with the outcome, finish work instead of narrating intentions, report failures verbatim, prefer readable prose over fragment-speak. That prompt-level doctrine reaches every runtime — Claude, [Grok](/docs/models/grok), [Codex](/docs/models/codex), and [Gemini](/docs/models/gemini) alike. The guard-hook half is not uniform: Claude-runtime agents get a full set (a stop-gate that catches promised-but-undone work, a bash-discipline nudge, an honesty nudge) alongside RoboCo's own hooks, never replacing them; Grok-runtime agents get the honesty nudge only, since a Grok-side hook denial cancels the whole run; Codex and Gemini get **no hooks at all** in this release — neither CLI exposes a verified hook mechanism, so their enforcement leans entirely on their own tool-scoping policy (sandbox levels / execpolicy for Codex, the TOML Policy Engine for Gemini) rather than a Fable-specific hook.
**The Ponytail doctrine** rides the same flag — no separate switch. It's the "lazy senior developer" ladder: question whether code needs to exist, reuse what the codebase already has, reach for the standard library before dependencies, one line before fifty. Developers get the full ladder; every other role gets an ethos-only cut so code-mechanics rules can't leak into task plans, review notes, or docs. The ladder explicitly yields to RoboCo's own bars — the coverage gate, QA review, the conventions standard, and reviewer feedback always win.
## Tuning the developer intensity
`ROBOCO_PONYTAIL_INTENSITY` (default `full`) sets how aggressive the developer ladder is:
| Level | Behavior |
|-------|----------|
| `lite` | Builds what's asked, names the lazier alternative. |
| `full` | Enforces the ladder — the default. |
| `ultra` | YAGNI-extremist: deletion before addition, challenges the requirement itself. |
Non-developer roles have no dial — they run a fixed, restrained stance.
## Enable it
Flip **"Fable + Ponytail doctrine"** in **Settings → Feature Flags**, or set the env var:
```bash
ROBOCO_FABLE_MODE_ENABLED=true
ROBOCO_PONYTAIL_INTENSITY=full # lite | full | ultra
```
Ponytail adds no hooks at all — bundling it under the Fable flag changes only the composed prompt. The measurable effect shows up in the delivery metrics you already have: rework rate, first-pass yield, and spawn waste on the [Metrics page](/docs/panel/metrics).
================================
URL: https://docs.roboco.tech/docs/optional/toolchain-matching
Title: Toolchain Matching
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
The agent image bakes one Python (RoboCo's own stack). But the repositories your agents build declare their *own* Python requirement — a target pinned to 3.14 cannot be honestly verified by an agent running 3.13. When the interpreters don't match, the suite fails to even *collect*, and a QA or PR-review pass degrades into "I read the source and it looks fine" — a **hollow pass** on a suite that never actually ran. Toolchain matching closes that hole.
It is **off by default** in config (`ROBOCO_TOOLCHAIN_MATCH_ENABLED`). When off, workspace provisioning behaves exactly as before, against the system interpreter.
## What it does
When enabled, two things happen.
**1. Provision against the target's declared Python.** When an agent's workspace is set up, RoboCo resolves the interpreter the *target* project needs and provisions the workspace's `.venv` with it (`uv sync --python `). The resolver (`roboco/services/toolchain.py`) reads both `.python-version` and `pyproject.toml`'s `requires-python`, and applies one load-bearing rule:
uv lets a `.python-version` file override `requires-python` during interpreter selection — so a repo pinned to 3.13 whose packages actually need 3.14 silently gets the *wrong* interpreter. RoboCo therefore honours the `.python-version` pin **only when it satisfies `requires-python`**; otherwise it resolves a concrete version from `requires-python` and passes that to uv explicitly with `--python`, which overrides the pin. A target that declares nothing actionable leaves provisioning unchanged.
**2. Write a runnability marker, then gate on it.** After provisioning, RoboCo runs a runnability smoke (`pytest --collect-only`) under the chosen interpreter and records the outcome to a per-workspace marker (`.git/.roboco-toolchain`):
| Status | Meaning |
|--------|---------|
| `ok` | The suite collected cleanly (or there are no tests) — the interpreter can run it. |
| `broken` | A collection / import error — the interpreter-mismatch signature. |
| `unknown` | Inconclusive — provisioning ran but the smoke couldn't confirm the suite is collectable. |
The delivery gates (`i_am_done`, `pass_review`, `pr_pass`) then read that status:
- **`broken` blocks.** The verb is refused: *"the project's test suite cannot be executed in this workspace (interpreter mismatch) — verifying by reading source is hollow."* The remediation tells the agent to call `i_am_blocked(reason='toolchain')` so the environment is rebuilt against the right interpreter, rather than passing on a source read.
- **`unknown` does not strand the task.** It fails *open* (precision over recall — a guess must never block a healthy task) but **never silently**: the orchestrator logs a `toolchain.unverified_gate_pass` warning so you can see the gate proceeded past an unconfirmed toolchain.
- **`ok` (or no marker)** passes normally.
B[resolve target Python
.python-version vs requires-python]
B --> C[uv sync --python <version>]
C --> D[pytest --collect-only smoke]
D --> E[write .git/.roboco-toolchain marker]
E --> F{gate reads status}
F -->|broken| G[BLOCK + i_am_blocked toolchain]
F -->|unknown| H[pass, log warning]
F -->|ok| I[pass]`} />
## Enable it
The config default is **off**, but this flag is turned **on** in the personal (non-registry) compose files. If you run from those, it is already active; the registry compose leaves it at the config default.
**Settings → Feature Flags** → toggle the toolchain-match flag on.
Feature-flag toggles persist in the settings store and apply on the **next backend restart**, not as a hot reload.
```bash
ROBOCO_TOOLCHAIN_MATCH_ENABLED=true
```
See the [environment reference](/docs/deploy/env-reference) for all flags.
## What changes when it's on
- New agent workspaces are provisioned against the *target* repo's declared Python instead of the image's system interpreter.
- Each workspace carries a `.git/.roboco-toolchain` marker recording its `(python, status)`.
- `i_am_done`, `pass_review`, and `pr_pass` block on a `broken` status, and log a visible warning on `unknown`.
When off, provisioning and the gates behave exactly as they did before — no resolution, no marker, no extra gating.
## Next
→ [Conventions](/docs/optional/conventions) closes the *placement* hollow-pass hole · [The PR review gate](/docs/optional/pr-review) is one of the gates this protects · back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/web-research
Title: Web Research
================================
import { Admonition } from "@/components/docs/admonition";
Web research lets your Board and PM agents ground their scoping in the live web instead of working from training data alone. When it is on, the Board (Product Owner, Head of Marketing) and the PM roles gain two tools — `web_search` and `web_fetch` — exposed through the `roboco-search` MCP server mounted into those agents. The provider's API key lives only on the server; it never enters an agent container, so the agent never sees or can exfiltrate it.
## Default state
Web research is **flag-gated by `ROBOCO_RESEARCH_ENABLED`**. The config default in `roboco/config.py` is `true`, but the Settings → Feature Flags card presents it as an operator toggle you turn on explicitly. The flag governs the mount: when it resolves to false, the `roboco-search` MCP server is not mounted into any agent and the `web_search` / `web_fetch` tools are simply absent.
The two surfaces can disagree, and the resolution rule is simple: a flag you set in **Settings → Feature Flags** wins, and an *unset* flag falls back to the environment / config default. Because the config default for `ROBOCO_RESEARCH_ENABLED` is `true`, a fresh deploy that has never touched the toggle has research **on**. If you want it off, set the toggle off (or set `ROBOCO_RESEARCH_ENABLED=false` in your environment) — don't assume the panel's switch position alone reflects the live state until you've set it once.
## Enable it
You need both the capability on *and* a provider configured, or agents get an empty stub.
1. **Set the flag.** Either set `ROBOCO_RESEARCH_ENABLED=true` in your deploy environment, or flip **Settings → Feature Flags → "Let the Board and PMs run web research"** on.
2. **Choose a provider and supply its key** in your environment (see below).
3. **Restart the backend.** Feature-flag changes take effect on the next backend restart — the panel toast says exactly this.
## Pick a provider and supply a key
| Env var | Default | Purpose |
|---------|---------|---------|
| `ROBOCO_RESEARCH_PROVIDER` | `tavily` | Search adapter: `tavily` \| `brave` \| `exa` \| `null`. Swapping providers is a config-only change. |
| `ROBOCO_RESEARCH_API_KEY` | (unset) | API key for the selected provider. **Server-side only — never reaches an agent container.** Unset → the `null` provider is used (empty results, no error). |
Only `tavily` and `exa` support fetching page contents. The `brave` adapter is search-only — a `web_fetch` call against it raises "unsupported". If your agents need to read the pages they find, choose `tavily` or `exa`. The `null` provider always returns empty results and is the graceful fallback when no key is set, so a missing key degrades quietly instead of erroring mid-task.
The provider's own API does the outbound web egress, not the agent — the agent calls the in-container MCP tool, the orchestrator-side `ResearchService` calls the provider with the server-held key, and only the cleaned results flow back.
## What changes when it's on
Board and PM agents can call `web_search` (a query → ranked results) and, on Tavily/Exa, `web_fetch` (a URL → extracted text). The service clamps every call so a provider can't run away with cost or context:
| Env var | Default | Purpose |
|---------|---------|---------|
| `ROBOCO_RESEARCH_MAX_RESULTS` | `5` | Hard cap on `web_search` results per call (1–20). |
| `ROBOCO_RESEARCH_FETCH_MAX_CHARS` | `20000` | Hard cap on characters returned by `web_fetch`. |
| `ROBOCO_RESEARCH_TIMEOUT_SECONDS` | `15.0` | Per-request timeout for outbound provider calls. |
| `ROBOCO_RESEARCH_DAILY_QUOTA_PER_AGENT` | `50` | Max `web_search` + `web_fetch` calls per agent per UTC day. |
The daily quota is your main cost lever. It is tracked per agent per UTC day in Redis and is **fail-open**: if Redis is unreachable the call is allowed through, because the quota is cost-control, not a security boundary.
## Required extra config
The only hard requirement beyond the flag is a real provider key. With `ROBOCO_RESEARCH_API_KEY` unset (or `ROBOCO_RESEARCH_PROVIDER=null`), the tools mount but every search returns empty — useful for a dry run, useless for actual grounding. Nothing else (no migration, no panel setup) is needed.
## Next
→ **[Strategy engine](/docs/optional/strategy-engine)** — the other notify-only steering loop. → **[The business workflow](/docs/tour/05-the-business-workflow)** — where the Board and PMs use research in practice.
================================
URL: https://docs.roboco.tech/docs/optional/strategy-engine
Title: Strategy Engine
================================
import { Admonition } from "@/components/docs/admonition";
The strategy engine is a background loop that watches the company against its charter and tells you when something has drifted — and that is all it does. It never spends, never builds, never approves, never merges. It only sends you a notification. Think of it as a quiet second pair of eyes on the whole org while you are not looking at the panel.
## Default state
The strategy engine is **off by default**, gated by `ROBOCO_STRATEGY_ENGINE_ENABLED` (config default `false`). When off, the background loop does not start at all — there is no cost, no assessment pass, and the delivery lifecycle is untouched. This is a genuinely dormant subsystem until you opt in.
## Enable it
1. Set `ROBOCO_STRATEGY_ENGINE_ENABLED=true` in your environment, or flip **Settings → Feature Flags → "Generate and maintain company strategy artifacts"** on.
2. **Restart the backend** — the change takes effect on the next restart.
That's the whole setup. There is no provider key, no migration, no extra service to wire up.
## What changes when it's on
A background loop wakes on an interval and runs an assessment pass against your charter (the north star, objectives, and operating policy you set in **Business → Goals**). It emits two kinds of observation and notifies the CEO when it sees one:
| Signal | What it means |
|--------|---------------|
| `idle` | There is no in-flight work, but a charter exists — the company is sitting still while it has stated objectives. |
| `stranded_blocked` | A task has been blocked longer than your threshold and needs a human decision to move. |
Notifications are bounded and deduped: you get one notification per observation kind, and it is suppressed until you acknowledge it, so the engine can't spam you about the same standing condition every cycle. The signals also surface on the dashboard's strategy-signals panel and feed the Company Scorecard.
Beyond notifying you, an `idle` observation makes a best-effort attempt to open a [Board Programs](/docs/optional/board-programs) Roadmap cycle right away, off its normal weekly cadence (still subject to Roadmap's own enabled/dedup checks — a no-op if it's off or a cycle is already open), and the notification names the outcome. `stranded_blocked` stays notify-only — a blocked task doesn't trigger Coroner; Coroner only ever opens off its own bounce/cancel/budget-block hooks. The strategy engine itself still never acts beyond that one nudge.
The engine has no authority. It cannot start a task, unblock work, approve a pitch, or spend a token. Every action it might prompt is still yours to take. That is the deliberate contract — it is a steering aid, not an autonomous operator.
## Tuning the cadence and threshold
| Env var | Default | Purpose |
|---------|---------|---------|
| `ROBOCO_STRATEGY_ENGINE_ENABLED` | `false` | Master switch. Off → the loop never runs. |
| `ROBOCO_STRATEGY_ENGINE_INTERVAL_SECONDS` | `1800` | Seconds between assessment passes (minimum 60). |
| `ROBOCO_STRATEGY_STRANDED_BLOCKED_MINUTES` | `120` | A task blocked longer than this is surfaced as `stranded_blocked` (minimum 5). |
Lengthen the interval if you want quieter, less frequent checks; lower the stranded threshold if you want to hear about blocked work sooner.
## Required extra config
None beyond the flag and a restart. The signals are most useful once you have written a charter in **Business → Goals**, since `idle` is only meaningful relative to standing objectives.
## Next
→ **[Web research](/docs/optional/web-research)** — the other opt-in capability for the Board and PMs. → **[Board Programs](/docs/optional/board-programs)** — the Roadmap cycle the idle signal can open early. → **[The business workflow](/docs/tour/05-the-business-workflow)** — the charter, Cockpit, and Scorecard the engine reads.
================================
URL: https://docs.roboco.tech/docs/optional/pitch-provisioning
Title: Pitch Provisioning
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
Your Board can propose new products, not just deliver the ones you hand it. A **Pitch** (title, problem, proposed solution, target cells) is drafted by the Product Owner or Head of Marketing and lands in **Business → Pitches** for your decision. When you approve a pitch and provisioning is configured, RoboCo turns that approval into real infrastructure: one private GitHub repo per target cell, registered as Projects, with a Main-PM build task seeded into the normal delivery lifecycle. This is the only place in the whole system that *creates* GitHub repos — everywhere else clones, branches, and opens PRs against repos that already exist.
## Default state
The master switch `ROBOCO_PROVISIONING_ENABLED` defaults to `true`, but the capability is **inert unless a token and an org are both set**. With no `ROBOCO_PROVISIONING_TOKEN` and no `ROBOCO_PROVISIONING_ORG`, approving a pitch raises `ProvisioningDisabledError` and creates nothing. So out of the box, pitch approval is a no-op until you point it at a GitHub org. The Settings → Feature Flags card exposes the toggle ("Auto-provision projects from approved pitches"), but the toggle alone does nothing without the credentials.
## Enable it
1. **Create a GitHub PAT** with `repo` + org-admin scope, in the org you want repos created in.
2. **Set the credentials** in your deploy environment:
| Env var | Default | Purpose |
|---------|---------|---------|
| `ROBOCO_PROVISIONING_ENABLED` | `true` | Master switch. Inert regardless unless token + org are also set. |
| `ROBOCO_PROVISIONING_TOKEN` | (empty) | GitHub PAT used to create repos. Needs `repo` + org-admin scope. **Server-side only.** |
| `ROBOCO_PROVISIONING_ORG` | (empty) | GitHub organization where new repos are created. |
| `ROBOCO_PROVISIONING_REPO_PRIVATE` | `true` | Whether provisioned repos are created private. |
| `ROBOCO_GITHUB_API_BASE_URL` | `https://api.github.com` | REST API base — override for GitHub Enterprise. |
| `ROBOCO_PROVISIONING_TIMEOUT_SECONDS` | `30.0` | Per-request timeout for GitHub provisioning calls. |
3. Confirm the **Settings → Feature Flags** toggle is on (it falls back to the config default if unset).
4. **Restart the backend.**
The token and org live only in server-side config — like the research key, they are never injected into an agent container.
## What changes when it's on
When you approve a pitch in **Business → Pitches**, `PitchService` runs the provisioning sequence:
B[CEO approves]
B --> C{token + org set?}
C -->|no| D[ProvisioningDisabledError
nothing created]
C -->|yes| E[Create 1 private repo
per target cell]
E --> F[Register each as a Project
+ a Product if multi-cell]
F --> G[Seed a Main-PM build task
source=pitch, confirmed_by_human=true]
G --> H[Normal delivery lifecycle]`} />
A single-cell pitch produces one repo named after the slug; a multi-cell pitch produces one repo per cell (`{slug}-{cell}`) and a **Product** mapping each cell to its repo, which is what drives the Main-PM integration-branch model. The seeded task is a real PENDING task assigned to the Main PM and flows through the same lifecycle as any other work.
## The partial-failure caveat
GitHub repo creation is an un-rollback-able side effect. If provisioning fails partway — say it creates the first cell's repo and then errors on the second — the database writes roll back (the approval route does not commit), but **the repos already created on GitHub remain**. A re-approval will then collide on the repo name and fail. You resolve this manually: delete or rename the orphaned repo(s) on GitHub, or finish wiring them up by hand, before re-approving. RoboCo deliberately does not auto-delete repos it created, so it can never destroy work.
## Required extra config
A GitHub PAT (`repo` + org-admin scope) and the target org are mandatory — there is no provisioning without them. For GitHub Enterprise, also set `ROBOCO_GITHUB_API_BASE_URL`. Provisioning writes Project / Product / task rows, so make sure your migrations are current (`alembic upgrade head`).
## Next
→ **[Workstation](/docs/panel/projects-and-products)** — what a Project and a multi-cell Product are. → **[The merge model](/docs/company/merge-model)** — how the Main PM cuts one integration branch per repo. → **[The business workflow](/docs/tour/05-the-business-workflow)** — drafting and reviewing pitches.
================================
URL: https://docs.roboco.tech/docs/optional/pr-review
Title: PR Review
================================
import { Admonition } from "@/components/docs/admonition";
Not every pull request against your repos comes from inside the company. A contributor or a fork can open one, or a branch can land that no agent task owns. These two opt-in loops let the read-only **PR Reviewer** look at those PRs, post a single change-request, and surface them to you — without ever chatting, merging, or pushing to anyone else's fork. This is the inbound counterpart to the in-path PR gate that already reviews assembled cell→root and root→master PRs.
## Default state
Both loops are **off by default**:
| Env var | Default | Purpose |
|---------|---------|---------|
| `ROBOCO_EXTERNAL_PR_ENABLED` | `false` | Inbound external/fork PR review. Off → the poll loop never runs and no inbound GitHub call is made. |
| `ROBOCO_INTERNAL_PR_ENABLED` | `false` | Read-only safety reviewer for org-repo PRs not tied to a task. Off → those PRs are never picked up. |
When both are off, no inbound discovery happens at all. The Settings → Feature Flags card exposes both ("Discover and review inbound external/fork pull requests" and "Run the read-only safety reviewer on internal branch PRs").
## Enable it
1. Set the flag(s) you want (`ROBOCO_EXTERNAL_PR_ENABLED` and/or `ROBOCO_INTERNAL_PR_ENABLED`), or flip the matching **Settings → Feature Flags** toggles.
2. **Restart the backend.**
## What changes when it's on
A background poll lists open PRs per active project and routes them to the read-only PR Reviewer agent, which reads the diff against your standards and posts **one** change-request comment on the PR. It never converses, never decides, and never merges.
**External PRs** (from outside contributors or forks) then land in the **PR Review Queue** on the Command Center, where the decision is yours:
- **Supersede** — the company cuts its own branch from the contributor's commits, hardens it, opens its own PR, and links back to the original once that merges. Like every PR the fleet opens, that superseding PR gets automatically labeled with its org-structure role (see below) the moment it's created.
- **Dismiss** — close the review and move on.
Either way the org never pushes to someone else's fork, and untrusted contributor code is never fetched, checked out, or executed until a human confirms it.
**Internal PRs** are org-repo (non-fork) PRs whose branch is **not** owned by an active task — branches pushed outside the agent task-flow. The reviewer runs the same read-only safety pass on them. The org's own in-flight integration PRs (whose branch a live task owns) are deliberately skipped, since those already pass QA and PM review in the normal lifecycle.
The PR Reviewer's only output is the change-request it posts on the PR. It holds no merge authority — see [How agents are sandboxed](/docs/company/agent-gateway). The accept/supersede/dismiss decision is always yours.
## Every fleet PR is labeled automatically
Every PR the company opens — the assembled cell→root and root→master PRs the in-path gate reviews, an architectural-conventions scaffold PR, and a Supersede PR from this page's own external-PR loop — is labeled with its org-structure role the moment it's created: whether it targets the dev branch or prod, whether it's a MegaTask root, and the owning layer (Main PM, a cell, or a subtask, tagged with its team). Labeling is best-effort and never blocks PR creation on a GitHub hiccup. It's a filtering aid on GitHub's own PR list, not a gate — nothing about review or merge behavior changes because of a label.
## What the in-path gate's reviewer sees
This page's two loops are the *inbound* half of PR review; the *in-path* gate — the reviewer who checks each assembled cell→root and root→master PR before a PM merges it — has picked up two upgrades worth knowing about even though its own claim/pass/fail flow lives outside this page's scope:
- **A collision map in their evidence.** When a reviewer claims a gate review, they now see which sibling tasks (same parent) declared overlapping file globs or a shared migration chain at delegation time, plus a drift check against the files the PR actually touched — the same collision surface `SequencingService` uses to order work, surfaced back to the human decision instead of disappearing after planning.
- **Inline code snippets on revision findings.** A task's Findings tab in the panel now renders the actual `file:line` context alongside each structured finding, so a reviewer checking whether a prior round's blocker was really fixed doesn't have to leave the panel to look.
## Tuning external review
| Env var | Default | Purpose |
|---------|---------|---------|
| `ROBOCO_EXTERNAL_PR_POLL_INTERVAL_SECONDS` | `300` | Seconds between inbound external-PR discovery passes (minimum 60). |
| `ROBOCO_EXTERNAL_PR_AUTHOR_ALLOWLIST` | (empty) | GitHub usernames trusted as known contributors. Empty → no author is auto-trusted; every external PR needs human confirmation. |
| `ROBOCO_EXTERNAL_PR_REQUIRE_HUMAN_CONFIRM` | `true` | Require explicit human confirmation before any agent fetches, checks out, or executes external contributor code. |
Leave `ROBOCO_EXTERNAL_PR_REQUIRE_HUMAN_CONFIRM` on unless you fully trust the inbound source — it is the gate that keeps untrusted code from running.
## Required extra config
The repos you want watched must be registered Projects with a valid git token (so the poll can list their PRs). No migration or provider key is needed beyond that.
## Next
→ **[The merge model](../company/merge-model.md#pull-requests-you-didnt-open)** — the operator-facing narrative of the Supersede / Dismiss flow. → **[How agents are sandboxed](/docs/company/agent-gateway)** — why the reviewer cannot merge.
================================
URL: https://docs.roboco.tech/docs/optional/pr-labels
Title: PR Org-Structure Labels
================================
import { Admonition } from "@/components/docs/admonition";
Every pull request the company opens is labeled with its **org-structure role** the moment it is created. The labeler runs as a side effect of PR creation, not a separate gate, so a GitHub hiccup never blocks the PR itself. The labels are a filtering aid in GitHub's own PR list — they do not change review or merge behavior.
## What gets labeled
The labeler applies to every fleet-opened PR, but not all of them get the same treatment:
- Assembled **cell→root** and **root→master** PRs reviewed by the in-path gate — the full org-structure set.
- **MegaTask** root PRs and their subtask PRs — the full org-structure set, plus the `MegaTask` label.
- **Supersede** PRs created from the external-PR review loop — the full org-structure set.
- **Architectural-conventions** scaffold/restore PRs — these carry no task and no org layer, so they get a single fixed `chore` label instead of the org-structure set below.
## What the labels say
A label encodes the PR's org-structure role rather than its file content. Each dimension below is applied as its own separate label:
| Label | Meaning |
|-------|---------|
| `to master` / `to slave` | Literal target-branch label. Today the only `to master` PRs are assembled root→master PRs; every other PR — cell→root, leaf dev, Supersede — gets `to slave`. Ladder-aware rung labels haven't shipped yet. |
| `root` | The PR is an assembled root→master PR. |
| `MegaTask` | The PR's task carries a `batch_id` — applied to any PR in the batch, not only its root. |
| `main-pm` / `cell/{team}` / `subtask/{team}` | The owning layer: `main-pm` for a Main-PM coordination root, `cell/{team}` for a cell-assembled PR (e.g. `cell/frontend`), `subtask/{team}` for a leaf dev PR (e.g. `subtask/backend`). Absent on a task-less PR. |
The exact set of labels is generated from the task and project metadata at PR-creation time. If GitHub returns an error, the PR still opens; the label is simply missing. Nothing downstream depends on it.
## Enabling and tuning
PR labeling is **always on** for PRs opened by the agent fleet. There is no dedicated feature flag and no per-project opt-in. It requires the same GitHub token the rest of the company uses to open the PR in the first place, with enough permission to manage labels on the repository.
If you want to control the labels that appear on your repositories, manage them through GitHub's own label settings. RoboCo creates missing labels as needed and re-uses existing ones that match by name.
A label is a convenience filter. The real enforcement is the agent gateway: a developer cannot merge, QA cannot commit, and the Auditor cannot speak, regardless of what any label says. Do not rely on labels for access control.
## Next
→ [PR Review](/docs/optional/pr-review) for the inbound and in-path review loops whose PRs get these labels · [The merge model](/docs/company/merge-model) for how cell→root→master PRs are assembled · [Review & Quality](/docs/company/review-and-quality) for the broader quality machinery.
================================
URL: https://docs.roboco.tech/docs/optional/possibilities-matrix
Title: Possibilities Matrix
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
Sometimes a task is already done by the time a developer agent picks it up — the commits exist, the PR is open, every acceptance criterion is covered, and there's nothing outstanding to fix. Without this capability, the agent still walks the full multi-turn plan-and-verify sequence to arrive at exactly the same conclusion. The **possibilities matrix** is a fast path: when the work genuinely is already done, `i_am_done` can submit straight to QA in one call instead of re-deriving what's already obvious.
It's **off by default**.
## What it does
B{ROBOCO_POSSIBILITIES_MATRIX_ENABLED
+ work already looks done?}
B -->|no| C[Standard multi-turn flow]
B -->|yes| D[Conventions gate +
findings-addressed check]
D -->|fails| C
D -->|passes| E{PR has CI?}
E -->|green| F[CI stands in for the
local quality gate]
E -->|red| G[Fast path refused outright]
E -->|no CI / unknown| H[Falls back to the local
quality gate + toolchain check]
F --> I[One-call submit to QA]
H --> I`} />
- **The preconditions, all four required.** The task already has commits, an open PR, every acceptance criterion marked addressed, and no open review findings against it. Miss any one of those and the fast path doesn't apply — the agent runs the normal flow.
- **One call instead of several.** When it applies, the developer's single `i_am_done` call carries the task from claimed/in-progress straight through self-verification and into `awaiting_qa`, instead of the usual plan → verify → submit sequence.
- **CI-green stands in for the local gate.** If the PR already has a green CI run, that check is trusted as the quality signal — there's no reason to re-run the same suite locally. A **red** CI check refuses the fast path outright rather than shipping known-failing work to QA.
- **No CI configured falls back to the real gate.** If the PR carries no CI signal at all, the fast path doesn't guess — it runs the standard local quality gate (and the [toolchain-matching](/docs/optional/toolchain-matching) interpreter check, if that's armed) before submitting.
- **The strict gates are never skipped.** Architectural-conventions block-level findings and the requirement that every open review finding be resolved both still run in full, every time — the fast path only ever shortens *redundant* work, never a real check.
This isn't about letting agents skip quality bars — every gate that would normally stop a bad submission still runs. It only removes the *re-derivation* of a conclusion the task's own state already proves: don't ask an agent to prove its work is done three different ways when the commits, the PR, and the acceptance criteria already say so.
## How it works in v0.25.0
The fast path is armed by the `ROBOCO_POSSIBILITIES_MATRIX_ENABLED` toggle (or the matching panel feature flag). When it is on, `i_am_done` checks whether the task's state already proves the work is done and, if so, moves it straight to the `WORK_ALREADY_DONE` orchestrator state and submits to QA in one call.
### Eligible statuses
Only two task statuses are eligible for the fast path:
- `claimed`
- `in_progress`
If the task is in any other status — `verifying` included, which routes through its own resume path — the fast path does not apply and the normal `i_am_done` flow runs.
### The four preconditions
All four must be true. Missing one falls back to the standard flow.
1. **Commits exist.** The task branch has at least one commit linked to it.
2. **A PR is open.** There is an open pull request for the task branch.
3. **Every acceptance criterion is addressed.** Each criterion has a referencing artifact (commit, file change, progress entry, or note) showing it is satisfied.
4. **No open findings.** There are no unresolved QA, PR-review, or PM-review findings against the task or its PR.
### Ownership assertion
The orchestrator asserts that the agent calling `i_am_done` owns the task and the work on the branch. If the task has been reassigned since the commits or PR were created, the ownership assertion fails and the task is forced through the normal flow. This prevents a new assignee from fast-pathing over work they did not produce.
### Architectural-conventions gate is kept
The fast path never skips the architectural-conventions block-level gate. If the diff contains a misplaced definition, a thin route/component, a god class, or any other block-level convention violation, `i_am_done` refuses the fast path and the agent must fix it through the normal flow.
### CI verdict behavior
The PR's CI status decides how the quality gate is satisfied:
- **CI green** — the CI run is trusted as the quality signal and the local gate is not re-run.
- **CI failing** — the fast path is refused outright; the agent must fix the failure through the normal flow.
- **No CI / unknown** — the orchestrator falls back to running the local quality gate (and the toolchain-matching interpreter check, if armed) before submitting.
### Orchestrator state
When the fast path succeeds, the orchestrator records the task in the `WORK_ALREADY_DONE` state before transitioning it to `awaiting_qa`. That state is the explicit signal that the task closed without redundant re-verification because its own commits, PR, and acceptance criteria already proved it was done.
## Enable it
**Settings → Feature Flags → "Possibilities matrix"** arms it.
The feature flag persists in the settings store and applies on the **next backend restart**.
```bash
ROBOCO_POSSIBILITIES_MATRIX_ENABLED=true
```
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- With the flag off, every task follows the standard `i_am_done` flow regardless of how complete it already looks.
- With the flag on, a task that already has commits, an open PR, full acceptance-criteria coverage, and no open findings can close in one call — with CI standing in for the local gate when the PR has a green run, and the local gate still running when it doesn't.
- Conventions and findings enforcement are identical either way; nothing about what counts as "done" gets looser.
## Next
→ [PR Review](/docs/optional/pr-review) — the in-path gate that still checks the assembled PR regardless of how a leaf task closed. → [Task Lifecycle](/docs/company/task-lifecycle) — the standard flow this fast path shortcuts. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/collision-map
Title: Collision Map
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
The **collision map** is the visibility layer on top of the sequencing engine. When a parent task fans out into sibling subtasks, each sibling declares the file globs, migration chains, and shared surfaces it intends to touch. The collision map compares those declarations and highlights where siblings might step on each other — both at delegation time and again when a PR is actually under review.
This page is about the concept. For the panel UI, see [Tasks & Kanban](/docs/panel/tasks-and-kanban); for how collision feeds release ordering, see [MegaTask](/docs/company/megatask).
## What the map shows
For any task that has siblings (i.e., it has a parent and is not standalone), the **Collision** tab on the task detail page shows:
| Element | Meaning |
|---------|---------|
| Own declared surface | This task's file globs, migration flag, and shared-surface flag. |
| Overlapping siblings | Every same-parent sibling whose declared surface intersects this task's. |
| Status / branch / PR | Where each sibling currently lives in the lifecycle. |
| Sequence badges | The PM-assigned `sequence` order, if any — lower runs first. |
| Highlighted globs | The exact globs that overlap between two siblings. |
| Drift callout | A sibling that touched files it never declared. |
Root tasks and standalone tasks render an explicit "no collision map" state, because they have no siblings to collide with.
## Where the data comes from
The declarations are captured when a task is delegated — the PM attaches them directly, or they arrive already declared on an intake/MegaTask draft. Once declared, they are stored on the task record and used by `SequencingService` to order work.
B[Each sibling declares surface]
B --> C{Surfaces overlap?}
C -->|no| D[Run in parallel]
C -->|yes| E[Collision map highlights overlap]
E --> F{PM assigned sequence?}
F -->|yes| G[Lower sequence must finish first]
F -->|no| H[Review collision before claiming]
E --> I[PR reviewer sees drift if actual files differ from declared]`} />
## Sequence badges
A PM can stamp sibling subtasks with a `sequence` number — lower runs first, ties run in parallel. The collision map shows those sequence badges so a reviewer can read the intended order at a glance. A lower-sequence sibling must reach a terminal state before a higher-sequence one can be claimed; an attempted claim fails with the name of the sibling that is holding it.
This is the same mechanism [MegaTask](/docs/company/megatask) uses to pace its collision-derived waves, applied generally to any parent's subtasks.
## Drift detection
Declarations are a plan, not a promise. When an assembled PR reaches the in-path gate, the reviewer's evidence includes a drift check: which files did the PR actually touch versus what was declared at delegation time? A sibling that touched files it never declared shows a **drift** callout on the collision map, so the reviewer can decide whether the overlap is harmless expansion or a real integration risk.
## Why it matters
Without the collision map, sequencing decisions disappear after planning. With it, the reasoning stays visible: the task detail page shows the declared surface, the board shows sequence badges, and the gate reviewer sees drift. It turns "I think these two tasks conflict" into a checkable fact.
Collision detection never auto-blocks a task or a merge. It surfaces information for the PM, the developer, and the gate reviewer to act on. The actual ordering is enforced by the `sequence` field and the normal lifecycle — the map just makes the reasoning readable.
## Next
→ [Tasks & Kanban](/docs/panel/tasks-and-kanban) for the panel view · [Review & Quality](/docs/company/review-and-quality) for how findings and the gate reviewer use the map · [MegaTask](/docs/company/megatask) for collision-driven batch sequencing.
================================
URL: https://docs.roboco.tech/docs/optional/task-budgets
Title: Task & Project Budgets
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo can cap real dollar spend two ways at once: a **monthly ceiling per project** (claim-time) and a **hard ceiling per task** (checked while work is running). Both are off by default — flip `ROBOCO_TASK_BUDGETS_ENABLED` and neither cap is ever consulted regardless of what's set in the project or task fields.
## Project monthly budget
Set a **Monthly Budget (USD)** on a project (the project's settings page, Budget & Ops card) — like the task field below, a `0` is rejected (leave it blank for no cap; a `0` would block every claim outright). Once this calendar month's summed agent-spawn spend across the project's tasks reaches that figure, a **new** work-starting claim (`give_me_work` → `i_will_work_on`, or a PM's `i_will_plan`) is refused.
The claim guard only applies to claims that **start** work. Review, documentation, gate, and inbound-PR-review claims are exempt — a task already moving through QA, docs, or the PR gate can always finish, even if the project crossed its cap mid-flight. The cap only stops *new* work from starting against an over-budget project.
The refusal names the exact cap and this month's spend, and tells you to either wait for next calendar month or raise the project's Monthly Budget field.
## Task budget
Set a **Budget (USD)** on an individual task (create/edit task dialog). A `0` is rejected outright — it would silently block everything, which is never what you meant. Leave it blank for **no cap** — this field is explicit-input only. There is no per-`TaskType` fallback figure; a blank budget means the sweep below never fires for that task, however long or expensive it runs.
A background sweep prices the active task's own spend (closed-session cost plus live-token pricing on any open session) against this cap, whenever one is set. On breach:
- The task is **blocked** — status `BLOCKED`, resolver type `HUMAN` — *before* the agent is stopped, so the graceful shutdown's own unclaim finds the task already blocked and never bounces it back through `pending` for an instant re-claim that would just re-burn the same budget.
- You get a **notification** naming both recovery steps: raise the task's budget, or let it stay blocked.
- **`unblock` re-checks live spend** before releasing a budget-blocked task — it refuses again while spend is still at or over the (possibly still-unraised) cap, so there's no silent re-breach loop where unblocking just walks straight back into the same wall.
A budget breach is a deliberate stop for your decision, not a race to prevent one more token from being spent — the sweep runs on its normal interval, not instantaneously. Set a figure that gives a well-scoped task real headroom, not a precise cutoff.
## Enable it
**Settings → Feature Flags** carries **"Task & project cost budgets"**. Flipping it takes effect on the next backend restart, same as every other flag on that card.
```bash
ROBOCO_TASK_BUDGETS_ENABLED=true
```
`docker-compose.yml` (the personal/NAS deploy) arms this **on** by default; `docker-compose.registry.yml` leaves it **off**, matching that file's safe-default posture for a fresh third-party deploy.
## Next
→ [Cost & usage](/docs/operations/cost-and-usage) for the spend the sweep prices against · [Workstation](/docs/panel/projects-and-products) for the project settings page · [Tasks & Kanban](/docs/panel/tasks-and-kanban) for the task dialog · back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/self-heal
Title: Self-Heal
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
RoboCo can watch its **own** repository's CI and heal itself: when its CI regresses, it notifies you, and — behind a second opt-in — queues a fix task into its own delivery lifecycle. It never starts that task, never merges it, and never deploys. Every downstream step stays a human decision.
The loop is **dormant by default**. Nothing runs and no GitHub call is made until you arm it (`roboco/services/self_heal_engine.py`).
## What it does
On an interval (`ROBOCO_SELF_HEAL_INTERVAL_SECONDS`, default 1800s) the engine assesses the latest completed CI run on the target repo's default branch. On a detected regression — a failing run — it sends you an **acknowledgement notification** as the CEO. That is the entire behaviour of the first opt-in: **detect and notify**.
With the second opt-in also on, a regression *additionally* opens a fix task into the regressed project's lifecycle — created at **`PENDING`** and held out of dispatch — and stops. It is layered so the loop can never run away with the backlog:
B{CI run on default branch}
B -->|green| A
B -->|regression| C[Notify the CEO]
C --> D{originate enabled?}
D -->|no| A
D -->|yes| E[Open fix task at PENDING]
E --> F[STOP — waits for your
Approve-&-Start]
F -.never auto.-> G[never merges · never deploys]`} />
The loop terminates at a human gate. An originated fix task sits at `PENDING`; once you Approve-&-Start it, it flows through the normal delivery lifecycle and ultimately escalates to `awaiting_ceo_approval` for your sign-off — exactly like any other task. The loop never approves, merges, or deploys on its own.
### Bounds on origination
Origination is capped so a flapping CI can't flood the backlog:
| Setting | Default | Meaning |
|---------|---------|---------|
| `ROBOCO_SELF_HEAL_MAX_PER_CYCLE` | `1` | Most fix tasks the loop may open in one cycle. |
| `ROBOCO_SELF_HEAL_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open self-heal tasks; the loop originates nothing more while this many are still open. |
## Required configuration
The loop watches exactly **one** project — RoboCo itself — and you must name it:
| Setting | Required? | Notes |
|---------|-----------|-------|
| `ROBOCO_SELF_HEAL_ENABLED` | yes (first opt-in) | Master switch: detect + notify. Off ⇒ the loop never runs, no CI telemetry is fetched. |
| `ROBOCO_SELF_HEAL_PROJECT_SLUG` | **yes** | The registered project slug that *is* RoboCo. Empty ⇒ the loop no-ops even when enabled. |
| `ROBOCO_SELF_HEAL_ORIGINATE_ENABLED` | yes (second opt-in) | Adds task origination on top of notify. Off ⇒ notify-only. |
| `ROBOCO_SELF_HEAL_CI_WORKFLOW` | no (default `ci.yml`) | Scopes the CI signal to one workflow file. |
`ROBOCO_SELF_HEAL_CI_WORKFLOW` defaults to `ci.yml`. Leave it empty *only* on a single-workflow repo — an empty value reads the latest completed run across **all** workflows on the default branch, which lets an unrelated green run mask a red CI run and makes the signal flicker.
## Enable it
**Settings → Feature Flags** exposes both switches: **"Watch RoboCo's own CI and notify you when it regresses"** and the originate opt-in. Toggle the first to go notify-only, both to enable origination.
Feature-flag toggles persist in the settings store and apply on the **next backend restart**. The project slug and CI workflow are environment settings, not flags — set them in env.
```bash
ROBOCO_SELF_HEAL_ENABLED=true
ROBOCO_SELF_HEAL_PROJECT_SLUG=roboco # the project that IS RoboCo
ROBOCO_SELF_HEAL_ORIGINATE_ENABLED=true # optional second opt-in
# ROBOCO_SELF_HEAL_CI_WORKFLOW=ci.yml # default; set empty only if single-workflow
```
See the [environment reference](/docs/deploy/env-reference) for the full list.
## What changes when it's on
- A background loop polls the target repo's CI on the configured interval; with both opt-ins off, nothing polls.
- On a regression you receive an acknowledgement notification as the CEO.
- With originate on, a `PENDING` fix task appears in the backlog (bounded by the caps above), held for your Approve-&-Start — it never auto-runs.
## Next
→ [Health & metrics](/docs/operations/health-and-metrics) for the CI/regression signal · [Task lifecycle](/docs/company/task-lifecycle) for what an originated task does once you start it · back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/autonomous-maintenance
Title: Autonomous Maintenance
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
RoboCo can keep itself and your projects healthy on their own schedule: it can watch each opted-in project's CI and open a fix task when it goes red, it can periodically check whether a dependency upgrade would change a project's lockfiles and open an "update dependencies" task when it would, and — for RoboCo's own repository specifically — it can assess its own release readiness and propose a release for you to approve. All three are **off by default**, and **none of them ever auto-merges or auto-publishes** — every task or proposal they open rides the normal `dev → QA → PR review → CEO merge` pipeline (or, for a release, your explicit approve/reject), exactly like work you'd start yourself.
The first two engines generalize the [self-healing CI loop](/docs/optional/self-heal), which watches only RoboCo's own repository. Multi-repo CI-watch and the dependency-update bot extend the same idea to *any* project you opt in; the release manager stays single-repo by nature — it's proposing RoboCo's own version, not a project's.
## Multi-repo CI-watch
CI-watch assesses each opted-in project's CI on its default branch. On a red conclusion it opens **one** fix task into that project and notifies that project's cell PM. It never starts that task, never merges it, and never deploys.
### What it does
On each pass (`ROBOCO_CI_WATCH_INTERVAL_SECONDS`, default 1800s) the engine checks each opted-in project's latest CI conclusion on its default branch. On a red conclusion it opens one fix task into that project and notifies the project's cell PM. The pass is resilient by construction:
- A **missing CI signal** is treated as "unknown", never a false green — an absent run never masks a real failure.
- One repo's **GitHub error never aborts the sweep** — the engine moves on to the next project.
- Origination is **bounded and deduped per repo**: a monorepo's cell-projects share one fix task, and the caps below stop a flapping CI from flooding the backlog.
B{For each opted-in project}
B --> C{CI conclusion on default branch}
C -->|green| B
C -->|unknown / missing| B
C -->|red| D[Open ONE fix task · notify the cell PM]
D -.never auto.-> E[never merges · never deploys]`} />
A CI-watch fix task is an ordinary task. It flows through the normal delivery lifecycle — QA, the in-path PR-review gate, and your merge — exactly like work you create yourself. The engine never approves, merges, or deploys on its own.
### Bounds on origination
| Setting | Default | Meaning |
|---------|---------|---------|
| `ROBOCO_CI_WATCH_MAX_PER_CYCLE` | `1` | Most fix tasks the sweep may open in one cycle. |
| `ROBOCO_CI_WATCH_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open CI-watch fix tasks per repo; the engine originates nothing more while this many are still open. |
### Enable it
**Settings → Feature Flags** carries the global **"Multi-repo CI-watch"** toggle. Then opt each project in from its **settings page → Budget & Ops card**: turn on `ci_watch_enabled` and optionally set `ci_watch_workflow` (the workflow file to scope the CI signal to, default `ci.yml`).
The feature flag persists in the settings store and applies on the **next backend restart**. The per-project fields apply on the next sweep.
```bash
ROBOCO_CI_WATCH_ENABLED=true # global switch
# ROBOCO_CI_WATCH_INTERVAL_SECONDS=1800 # default
# ROBOCO_CI_WATCH_MAX_OPEN_TASKS=3 # default
# ROBOCO_CI_WATCH_MAX_PER_CYCLE=1 # default
```
The per-project opt-in (`ci_watch_enabled`, `ci_watch_workflow`) lives on the project, not in env — set it on the project's settings page.
## Dependency-update bot
The dependency-update bot periodically checks whether a dependency upgrade would change a project's lockfiles and, if so, opens **one** "update dependencies" task into that project. Detection is read-only: nothing in the real repo is ever mutated.
### What it does
On each pass (`ROBOCO_DEP_UPDATE_INTERVAL_SECONDS`, default 604800s — weekly) the bot runs the project's `dep_update_command` (e.g. `uv lock --upgrade` / `pnpm update`) in a **throwaway clone** and diffs the lockfiles. If the lockfiles would change it opens one "update dependencies" task; otherwise it opens nothing.
- Detection is **read-only**: the command runs in a throwaway clone and only the lockfiles are diffed. The real repo is never mutated — nothing is committed or pushed.
- It is **fail-safe**: a null `dep_update_command` or a command that fails opens nothing.
- Origination is **bounded and deduped per repo**, with the caps below.
B{For each opted-in project}
B --> C[Run dep_update_command in a throwaway clone]
C --> D{lockfiles change?}
D -->|no / null / fails| B
D -->|yes| E[Open ONE update-dependencies task]
E -.never auto.-> F[never merges · never deploys]`} />
The update-dependencies task is an ordinary task — QA, the in-path PR-review gate, and your merge all apply. The bot only ever *detects* and *opens*; it never commits, pushes, merges, or deploys.
### Bounds on origination
| Setting | Default | Meaning |
|---------|---------|---------|
| `ROBOCO_DEP_UPDATE_MAX_PER_CYCLE` | `1` | Most update-dependencies tasks the bot may open in one cycle. |
| `ROBOCO_DEP_UPDATE_MAX_OPEN_TASKS` | `3` | Rolling cap on concurrently-open update-dependencies tasks per repo. |
### Enable it
**Settings → Feature Flags** carries the global **"Dependency-update bot"** toggle. Then opt each project in from its **settings page → Budget & Ops card**: set `dep_update_command` (e.g. `uv lock --upgrade` / `pnpm update`) and optionally `dep_update_paths` (comma-separated lockfile paths to watch; defaults to inferring `uv.lock` / `pnpm-lock.yaml`).
The feature flag persists in the settings store and applies on the **next backend restart**. The per-project fields apply on the next sweep.
```bash
ROBOCO_DEP_UPDATE_ENABLED=true # global switch
# ROBOCO_DEP_UPDATE_INTERVAL_SECONDS=604800 # default (weekly)
# ROBOCO_DEP_UPDATE_MAX_OPEN_TASKS=3 # default
# ROBOCO_DEP_UPDATE_MAX_PER_CYCLE=1 # default
```
The per-project opt-in (`dep_update_command`, `dep_update_paths`) lives on the project, not in env — set it on the project's settings page.
## Gated release manager
Unlike the two engines above, this one isn't per-project — it watches RoboCo's own repository and proposes RoboCo's own releases. It automates cutting a release right up to the decision: a background loop runs the same deterministic readiness sweep a human would (diff since the last tag, conventional-commit classification, semver bump, version-reference completeness, CHANGELOG completeness, a docs-drift check, migration single-head, and the quality gate's state) and, once the change set clears a threshold with a green gate, opens **one** release proposal and holds it for you.
### What it does
The proposal is an ordinary task — owned by the Secretary, held (never dispatched, never started by anyone) — that surfaces in the panel for you to **approve** or **reject with changes**. Nothing publishes without that explicit approval.
B{Readiness sweep:
commits · gate · docs · migrations}
B -->|below threshold or red gate| A
B -->|ready| C[Hold ONE release proposal for the CEO]
C -->|reject| D[CANCELLED — frees the slot
for a fresh proposal next cycle]
C -->|approve| E[ReleaseExecutor: fail-closed]
E --> F{Environment ladder set?}
F -->|yes| G[Promote head→…→prod
before bumping]
F -->|no| H[Bump on the default branch]
G --> I[Version bumps + CHANGELOG]
H --> I
I --> J{Head rung CI verdict green?}
J -->|no| K[Abort — nothing committed]
J -->|yes| L[Commit + push chore release]
L --> M{Release-commit CI green?}
M -->|no| K
M -->|yes| N[Publish via GitHub REST]`} />
- **Rejecting frees the slot.** A rejected proposal moves to CANCELLED, which the one-open-proposal dedup excludes — so the next readiness pass can originate a fresh proposal instead of the loop deadlocking behind a proposal you already turned down.
- **Approval runs the fail-closed executor.** `ReleaseExecutor` writes the version bumps across the canonical file set (derived from the previous `chore(release):` commit) plus the CHANGELOG entry, verifies the **head rung's CI verdict** (it does not re-run `make quality` inside the production container), and aborts before committing anything on red. It then commits `chore(release): X.Y.Z` and pushes, waits for that commit's own CI to go green, and publishes the release through the **GitHub REST API** rather than the `gh` CLI. An already-published version is a no-op — approving twice never double-releases.
- **The release commit uses a configurable bot identity.** By default the commit is authored as **RoboCo Release Manager** ``; set `ROBOCO_RELEASE_GIT_NAME` and `ROBOCO_RELEASE_GIT_EMAIL` to use your own identity. Commit signing is **opt-in** via `ROBOCO_RELEASE_SIGN_COMMITS` — when enabled the executor signs the release commit; when unset the commit is unsigned.
- **The curated `[Unreleased]` CHANGELOG body is preferred.** If the CHANGELOG contains a curated `[Unreleased]` section, the executor uses that body for the release entry and empties the section after stamping it into the new version. When no `[Unreleased]` body is present, it falls back to the existing auto-generated conventional-commit summary.
- **With an environment ladder set, it promotes the full chain first.** If RoboCo's own project record has declared an [environment ladder](/docs/optional/env-branches) (ordered rungs from a head/dev branch down to prod, rather than one `default_branch`), the executor merges every rung above prod into the prod checkout — head→…→just-below-prod — *before* bumping versions, so the release commits the fully-promoted state rather than whatever prod happened to already contain. Without a declared ladder, it's the same single-branch behavior as before; there's nothing to promote.
- **The panel shows real progress, not a silent wait.** The `execute` step runs in the background for as long as the CI wait takes (tens of minutes is normal). The proposal card shows a running badge while it's in flight, and on a failure — a red head-rung CI verdict, red release-commit CI, or an unexpected error — it shows the failure reason inline with a **Retry approve & publish** action, instead of leaving you watching a card that never updates.
### Bounds on origination
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_RELEASE_MANAGER_ENABLED` | `false` | Master switch. When off the loop never runs and no release is proposed. Even on, it only proposes — you approve before anything publishes. |
| `ROBOCO_RELEASE_MIN_COMMITS` | `8` | Minimum unreleased commits since the last tag before a release is proposed (any feat/security commit also qualifies on its own). |
| `ROBOCO_RELEASE_MANAGER_INTERVAL_SECONDS` | `3600` | Seconds between release-readiness assessment passes. |
| `ROBOCO_RELEASE_GIT_NAME` | `RoboCo Release Manager` | Committer name on the release commit. |
| `ROBOCO_RELEASE_GIT_EMAIL` | `release-manager@roboco.local` | Committer email on the release commit. |
| `ROBOCO_RELEASE_SIGN_COMMITS` | `false` | Set `true` to GPG-sign the release commit. |
### Enable it
**Settings → Feature Flags** carries the global **"Gated release manager"** toggle. There's no per-project opt-in — it always targets RoboCo's own repository.
The feature flag persists in the settings store and applies on the **next backend restart**.
```bash
ROBOCO_RELEASE_MANAGER_ENABLED=true # global switch
# ROBOCO_RELEASE_MIN_COMMITS=8 # default
# ROBOCO_RELEASE_MANAGER_INTERVAL_SECONDS=3600 # default
# ROBOCO_RELEASE_GIT_NAME=RoboCo Release Manager # default
# ROBOCO_RELEASE_GIT_EMAIL=release-manager@roboco.local # default
# ROBOCO_RELEASE_SIGN_COMMITS=false # set true to sign
```
## Operator maintenance pause
New in v0.29.0, the operator maintenance pause lets the CEO temporarily halt one or more of RoboCo's autonomous subsystems without stopping the backend. It is a safety valve: when something needs a pause — a debugging session, a capacity check, or just breathing room — you can pause the relevant scope, let it drain or kill, and resume when you are ready.
### What it does
A pause targets one of three independent scopes, and each scope can be paused on its own without touching the others:
- **`dispatch`** — pauses the task dispatcher. No new tasks are claimed or started while this scope is paused.
- **`board_programs`** — pauses the board-program runner that drives the CI-watch, dependency-update bot, and release manager engines above. No new cycles are originated.
- **`engines`** — pauses the background engine sweeps (the interval-driven loops that drive CI-watch, dependency checks, and release readiness). Existing in-flight work is not interrupted unless you choose kill mode.
Each pause has a **mode**:
- **Drain** — let currently in-flight work finish naturally, then hold. New work is not started. This is the graceful option.
- **Kill** — stop immediately. In-flight work is interrupted. Use this when you need an instant halt.
B{mode?}
B -->|drain| C[Let in-flight work finish · then hold]
B -->|kill| D[Stop immediately]
C --> E[Panel banner: countdown + Resume button]
D --> E
E --> F[CEO: DELETE /api/maintenance-pause/scope]
F --> G[Scope resumes]
E -.auto.-> H[expires_at reached → scope resumes]`} />
Pause state lives in the **settings store**, not in memory, so it survives a backend restart:
| Field | Default | Meaning |
|-------|---------|---------|
| `paused_by` | — | The agent (CEO) who set the pause. |
| `reason` | — | A free-text operator note for why the pause was set. |
| `expires_at` | 4h from set | When the pause auto-expires and the scope resumes on its own. Maximum 336h (14 days). |
### CEO-only API routes
The pause surface is CEO-only — no other role may set or clear a pause:
- **`GET /api/maintenance-pause`** — reads the current pause state for all three scopes.
- **`POST /api/maintenance-pause/{scope}`** — pauses a scope. The body carries the mode (`drain` or `kill`), an optional `reason`, and an optional `expires_at` (default 4h, max 336h).
- **`DELETE /api/maintenance-pause/{scope}`** — resumes a scope immediately, clearing the pause.
### Panel banner
When any scope is paused, the control panel shows a **banner with a live countdown timer** ticking down to `expires_at`.
The banner names which scope is paused and who set it.
A **Resume button** in the banner lets the CEO resume the scope without leaving the page — it calls `DELETE /api/maintenance-pause/{scope}` under the hood.
A maintenance pause holds the three scopes above — dispatch, board_programs, and engines. It does **not** pause the stale-claim reaper or the budget sweep. Those background processes continue to run during a pause so that stale claims are still reaped and budget limits are still enforced. Only the three scopes above are held.
## What changes when each is on
- With CI-watch on, a background sweep polls each opted-in project's CI on the configured interval; on a red conclusion a fix task appears in that project's backlog (bounded by the caps above) and its cell PM is notified. With the global flag off, nothing polls.
- With the dependency-update bot on, a background sweep checks each opted-in project's lockfiles on the configured interval; when an upgrade would change them, an update-dependencies task appears. With the global flag off, nothing runs and no throwaway clone is made.
- With the release manager on, a background sweep assesses RoboCo's own release readiness on the configured interval; past the commit threshold with a green gate, one held proposal appears for you to approve or reject. With the global flag off, nothing is ever proposed.
## Next
→ [Self-healing CI](/docs/optional/self-heal) for the single-repo loop CI-watch generalizes · [Environment ladders](/docs/optional/env-branches) for declaring the head→prod branch chain the release executor promotes · [Task lifecycle](/docs/company/task-lifecycle) for what an opened task does once you start it · [Environment reference](/docs/deploy/env-reference) for the full env list · back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/env-branches
Title: Environment Branches & EnvSync
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
By default a project has one branch that plays two roles: it's where every task's PR lands, and it's what the release manager tags. Most projects never need more than that. If yours has a real staging step — or several — between "a dev merged this" and "this is what customers run," you can declare an **environment ladder**: an ordered list of branches, from where work lands to where releases are cut. This page is the practical configuration guide; the ladder's place in the overall branching model is covered in [Branching Strategy](/docs/company/branching-strategy).
The ladder itself is just project configuration — always available. The **EnvSync** engine that keeps the rungs in sync automatically is a separate, **off-by-default** switch on top of it.
## The ladder
A ladder is an ordered list of rungs, each a `{name, branch}` pair:
- **The first rung** is where every task's PR targets — the branch cell/root PRs merge into. Think of it as the dev trunk.
- **The last rung** is the **release target** — the branch the [gated release manager](/docs/optional/autonomous-maintenance) commits the version bump + CHANGELOG to and tags.
- Anything in between (a `qa` or `staging` rung, say) is an intermediate stop.
If a project has never had a ladder configured, nothing changes: its single `default_branch` is treated as both the first and the last rung. Every existing project keeps behaving exactly as it does today until you deliberately add a ladder on its settings page.
### A typical two-rung example
The common case is one intermediate-free split: a `dev` rung for day-to-day work, a `prod` rung the release manager ships from.
| Rung | Name | Branch |
|------|------|--------|
| 1 (first) | `dev` | `slave` |
| 2 (last) | `prod` | `master` |
With that ladder set, every task's PR still targets `slave` as before — but releases are cut and tagged on `master`, and `master` no longer needs to be the branch developers work against day to day.
## Configuring it
Open a project's **settings page** (`/projects/[id]/settings`) and use the **Environments card**'s ladder editor: add rungs, name each one, point it at a real branch, and reorder with the up/down controls. Saving rejects an empty or duplicate branch. This part works whether or not the EnvSync engine below is armed — it's just where the release manager and every task's PR-target branch get resolved from.
## EnvSync: keeping the ladder in sync
B{ROBOCO_ENV_SYNC_ENABLED?}
B -->|off| Z[ladder still resolves PR
target + release branch,
nothing auto-syncs]
B -->|on| C[Cascade rungs top-down:
merge each rung into the one below]
C --> D{Clean merge?}
D -->|yes| E[Auto-push straight to
the lower rung, continue down]
D -->|conflict| F[Open ONE sync PR +
ONE coordination task,
stop this project's cascade]`} />
With `ROBOCO_ENV_SYNC_ENABLED` on, RoboCo periodically walks each opted-in project's ladder from the last rung down toward the first, merging each rung into the one below it:
- **Clean merge** — the lower rung is already an ancestor, or the merge applies with no conflict — **auto-pushes** directly to that lower rung and keeps walking down the ladder.
- **Conflict** — the cascade stops for that project this cycle. It opens exactly **one** sync PR (merging the upper rung into the lower) and exactly **one** coordination task tracking it, routed through the normal delivery flow like any other task — no per-rung spam, no silent drop.
- Only one open sync task per project at a time; a project with one already open is skipped until it resolves.
The cascade's target is always a *lower* rung — by construction it never pushes to the last (release) rung. That branch only ever advances through the release manager's own promotion below, which still ends at your explicit publish approval. "Only the CEO merges the release branch" holds either way.
## How releases use the ladder
When the [gated release manager](/docs/optional/autonomous-maintenance) cuts a release, it doesn't just tag the last rung as-is — it first **promotes the full chain**: it merges every rung from the first down to (but not including) the last into the release branch, in order, *before* writing the version bump and CHANGELOG entry. If any of those merges conflicts, the release aborts before anything is committed — you get a clean failure, not a bad tag. See [Release Management](/docs/operations/release-management) for the full proposal, approval, and publish flow.
This means the ladder and the release manager work together: EnvSync (when armed) keeps the middle rungs from drifting apart day to day, and the release manager's own promotion step is the final, authoritative sync that happens at cut time regardless of whether EnvSync is on.
## Enable it
Set up the ladder itself from a project's **settings page → Environments card** — this works with the EnvSync flag off or on. To arm the auto-sync cascade, go to **Settings → Feature Flags → "Environment sync"**.
The EnvSync feature flag persists in the settings store and applies on the **next backend restart**. A project's ladder itself applies to its very next PR/release.
```bash
ROBOCO_ENV_SYNC_ENABLED=true
# ROBOCO_ENV_SYNC_INTERVAL_SECONDS=1800 # default
# ROBOCO_ENV_SYNC_MAX_OPEN_TASKS=3 # default
# ROBOCO_ENV_SYNC_MAX_PER_CYCLE=1 # default
```
A project's ladder itself isn't an environment variable — set it on the project's settings page.
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- A project with no ladder configured is unaffected by any of this, regardless of the flag.
- A project with a ladder but the EnvSync flag off gets the ladder's *structural* benefits only — PRs target the first rung, releases promote + tag the last rung — with no automatic cascading in between.
- A project with a ladder **and** the flag on additionally gets the periodic cascade: clean merges flow down the ladder on their own, and a real conflict surfaces as one PR plus one task instead of silent drift.
## Docs-site PR labels follow the real target branch
RoboCo's own docs-site repo (the one serving these pages) is configured via **`ROBOCO_DOCS_SITE_*`** environment variables rather than a hardcoded repo/URL — the same env-driven pattern as every other project setting. One consequence you'll notice on any project's PRs, not just the docs site: a PR's **"to `{base}`"** label is now derived from that PR's **real target branch**, instead of assuming it's always the project's default. On a project with an [environment ladder](#the-ladder), that means the label correctly reads `to dev` for a normal task PR and `to master` for a release PR, rather than showing the same target for both.
## Next
→ [Branching Strategy](/docs/company/branching-strategy) — where the ladder fits in the company-wide branching model. → [Release Management](/docs/operations/release-management) — the gated release manager that proposes and publishes releases. → [Autonomous Maintenance](/docs/optional/autonomous-maintenance) — the multi-repo CI-watch and dependency-update bot that share the same release executor. → [Merge Model](/docs/company/merge-model) — how PRs and branches fit into the delivery lifecycle. → [Workstation](/docs/panel/projects-and-products) — where the ladder editor lives. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/docs-sync
Title: Docs-Sync
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
Every RoboCo release changes something the docs site ought to reflect — a new flag, a new page, a changed default. Nothing enforces that automatically unless you ask it to. **Docs-sync** watches for the moment a release publishes and, if the docs site looks like it's drifted, opens one docs-update task into the normal delivery flow — the same PM → dev → QA → PR-review → merge path any other task takes.
It's **off by default**.
## What it does
B{ROBOCO_DOCS_SYNC_ENABLED?}
B -->|off| Z[nothing happens]
B -->|on| C{Docs site registered
as a project?}
C -->|no| Y[skipped, logged —
nothing to open a task against]
C -->|yes| D[Open ONE docs-update task
into that project's delivery flow]
D --> E[Main PM decomposes → dev writes
the update → QA → PR-review gate]
E --> F[You merge the PR
— never auto-merged]`} />
- **Release-triggered, not a background loop.** Docs-sync doesn't poll on an interval — it hooks directly into the [gated release manager](/docs/optional/autonomous-maintenance)'s publish step. The moment a release actually publishes, it checks whether the docs site needs an update and, if so, opens exactly one task carrying the new version and the release's own changelog as context.
- **Rides the real delivery lifecycle.** The task it opens is a normal Main-PM coordination root against the docs site's own project — it gets decomposed, claimed, built, QA'd, and passes through the [in-path PR-review gate](/docs/optional/pr-review) like any other task. There's no special-cased shortcut for docs work.
- **Never auto-merges.** Docs-sync's job ends at opening the task. The resulting PR still needs your merge (or your PM's, per the normal lifecycle) — same posture as [self-heal](/docs/optional/self-heal) and the [CI-watch / dependency-update bots](/docs/optional/autonomous-maintenance): detect and queue, never publish on its own.
- **Bounded.** A rolling cap on open docs-sync tasks and a per-cycle cap on how many it opens keep a run of releases from flooding the docs project with duplicate work.
Docs-sync opens its task against a real RoboCo project record for the docs site — it needs that project to exist, with a git token configured, so a dev agent can actually clone it and open a PR. If the docs site isn't registered as a project yet, docs-sync silently skips (logged, not an error) rather than failing the release.
## Enable it
Register your docs site as a project first (**Workstation → Projects**, with a git token set), then **Settings → Feature Flags → "Docs sync"**.
The feature flag persists in the settings store and applies on the **next backend restart**. It only matters at your *next* release publish either way.
```bash
ROBOCO_DOCS_SYNC_ENABLED=true
# ROBOCO_DOCS_SYNC_MAX_OPEN_TASKS=3 # default
# ROBOCO_DOCS_SYNC_MAX_PER_CYCLE=1 # default
```
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- With the flag off, publishing a release never opens a docs-update task — you'd need to notice and update the docs site yourself.
- With the flag on and the docs site registered, a publish checks for drift and, when it finds some, queues one task for a PM to pick up — the update still ships through the exact same review and merge gates as any other change.
- Without a registered docs-site project, the flag being on changes nothing observable — it skips every time, quietly.
## Next
→ [Autonomous Maintenance](/docs/optional/autonomous-maintenance) — the gated release manager whose publish step docs-sync hooks into. → [PR Review](/docs/optional/pr-review) — the gate the resulting docs PR still passes through. → [Workstation](/docs/panel/projects-and-products) — where you register the docs site as a project. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/http-security
Title: HTTP Security
================================
import { Admonition } from "@/components/docs/admonition";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
RoboCo can front its API with a [fastapi-guard](https://pypi.org/project/fastapi-guard/) security layer — IP and rate controls, a signature WAF, security headers, cloud-provider and honeypot checks, an emergency kill switch, and three RoboCo-specific content validators — for when you expose the panel/API beyond a trusted LAN.
The layer is **off by default**. When `ROBOCO_GUARD_ENABLED` is unset, `create_app` never mounts the middleware and the request path is byte-for-byte unchanged; the per-route guard decorators are harmless no-ops (`roboco/security.py`). A personal NAS deploy needs none of this — it's built for cloud/public hosting.
## What it does
Armed, the middleware sits outermost and applies, per route:
- **Rate limiting & size caps** — a baseline throttle plus tighter per-endpoint limits on sensitive routes (provider-key writes, CEO release ops, agent verbs).
- **A signature WAF** — SQLi/XSS/path-traversal/etc. detection on request bodies, headers, params, and path.
- **Security headers** — HSTS, CSP, `X-Frame-Options`, `nosniff`, referrer and permissions policies on every response.
- **Cloud-provider & honeypot checks** — block datacenter egress on the highest-value routes; form-trap honeypots on human-facing POSTs.
- **Three RoboCo custom validators** the stock WAF cannot cover — **prompt-injection**, **secret-exfil**, and **internal-SSRF** scanning on the prompt-facing and agent-content surfaces.
- **An emergency lockdown** (`ROBOCO_GUARD_EMERGENCY`) — a flip-on-without-redeploy switch that blocks every non-whitelisted IP during an active attack.
Behind nginx, guard trusts the single proxy hop for the real client IP, excludes WebSocket upgrades and health/docs paths, and keeps RoboCo's own CORS.
- **The internal agent mesh is exempt from WAF/IP-ban entirely.** Agents reach the orchestrator directly over the docker bridge, HMAC-authenticated — guard's WAF/IP-ban/rate-limit exists for the *external* attack surface arriving through nginx, not for already-authenticated internal traffic. Without this exemption, guard IP-banned agent containers the moment a single journal or note body tripped a signature, wedging every subsequent verb that container tried. The exemption is scoped to loopback plus docker's own bridge address-pool range — never a broader LAN/CGNAT range that could also cover a real external client hitting nginx.
- **A host-proxied chain (e.g. Tailscale Serve terminating in front of nginx) resolves to the real client, not a whitelisted hop.** Guard's own logic peels a single fixed proxy hop, which is correct for a direct nginx client but wrong for one more hop in front of it — that shape used to resolve to a whitelisted gateway IP, silently going inert on the whole surface behind that hop. `ROBOCO_GUARD_TRUSTED_CHAIN_PEERS` names the extra hop(s) to peel (a single exact IP per entry, never a CIDR range — a range would readmit every sibling container's real address into the trusted-hop set). Left unset, only a loopback rightmost hop ever peels; a host-proxied chain behind an unconfigured docker-bridge gateway is logged once so the gap isn't silent.
The orchestrator API itself is not reachable from outside the host either way — see [how the orchestrator is kept off the open network](/docs/troubleshooting/security#how-is-the-orchestrator-api-kept-off-the-open-network) for that separate, always-on layer. Guard is what stands between nginx's forwarded traffic and the app once a request does arrive.
## Passive first, then enforce
Guard has two enforcement postures:
| `ROBOCO_GUARD_PASSIVE_MODE` | Behaviour |
|---|---|
| `true` (calibrate) | **Log-only.** Detections are logged but **never block** — legit *and* malicious requests pass through. Safe to arm on live traffic to surface false positives first. |
| `false` (enforce) | Detections **block** the request. |
RoboCo's request bodies *are* code, SQL, diffs, file paths, HTML, and URLs — task specs, agent notes and commits, RAG queries, git bodies, chat. A stock WAF reads that legitimate traffic as attacks. RoboCo ships a calibration (`excluded_detection_body_fields` in `build_security_config`, derived from the real request models) that excludes those free-text fields from WAF scanning so active mode does **not** false-positive — while the WAF stays active on every structured (id/enum/slug/branch) field and the prompt-injection / secret-exfil / SSRF validators keep firing regardless. Those two content validators are themselves calibrated against RoboCo's own vocabulary — documenting a placeholder secret in a doc-shaped string, or writing neutral engineering prose about a security control, no longer trips them. If you're building your own deployment from a config default rather than the tracked NAS compose below, arm **passive first**, watch the logs for any straggler false positive, then flip to active.
The **NAS composes** (`docker-compose.yml` / `docker-compose.yaml`) now default to **active enforcement** (`ROBOCO_GUARD_PASSIVE_MODE=false`) — passive-mode calibration on that deploy reviewed clean, so a matching request is genuinely blocked, not just logged. `ROBOCO_GUARD_FAIL_SECURE=true` on that same compose (as of v0.29.0) means a guard-internal error fails closed rather than letting a bad request through — the NAS no longer relaxes this knob now that the calibration is proven clean. The **registry compose** (`docker-compose.registry.yml`) still omits the guard trio entirely, so a fresh third-party deploy starts from the safe config default (guard off) and calibrates on its own schedule before enforcing. A bare `roboco/config.py` default is also off. See [The three compose files](/docs/deploy/deployment#the-three-compose-files) in Deployment for what each compose is for.
Active enforcement means a real false positive now genuinely blocks instead of just logging. The two triggers that used to catch legitimate RoboCo traffic — a documented placeholder secret in a doc-shaped string, and neutral engineering prose about a security control — are the ones the RoboCo-vocabulary calibration above fixed. If you still hit a straggler on unusual content, the instant rollback is env-only: set `ROBOCO_GUARD_PASSIVE_MODE=true` and restart.
## Scanner honeytrap & auto-ban
Automated scanners probe every host for well-known soft spots (`/.env`, `/wp-login.php`, `/phpmyadmin`, `/.git/config`, …). RoboCo turns those probes against the scanner, in two layers matched to where the traffic actually lands — behind nginx only `/api`, `/ws`, `/health`, and `/ready` reach the orchestrator, so guard can only see probes on those paths:
- **Guard adaptive ban (the `/api` surface).** The guard `threat_ban_config` carries `recon` / `sensitive_file` / `cms_probing` categories. A scanner probing those fingerprints on an `/api` path is detected on the URL-path scan, and repeated probes from one IP trip a per-IP auto-ban (redis-backed, 24h). This only bans in **active** mode (passive logs the recon hit) and requires redis (the 24h ban exceeds the in-memory cap).
- **nginx edge-drop (the classic root paths).** The classic scanner paths never reach the orchestrator, so nginx drops them at the edge with `444` (closes the connection, returns nothing) before they touch the panel. It's anchored to known scanner fingerprints — `/.well-known` and every real panel/API route are untouched — and is always on, independent of `ROBOCO_GUARD_ENABLED`.
- **status:401 behavior rule.** A second global log-only behavior rule watches `status:401` responses for credential-probing visibility — threshold 20 in 300s, and it **never bans**, only logs. A stale internal HMAC token must never earn one, since bans override the whitelist and a rotating agent credential would otherwise look like a brute-force sweep.
## Fail-secure
`ROBOCO_GUARD_FAIL_SECURE` decides what happens if a security check itself errors: `true` (default) fails **closed** — block the request — which is the right default for public hosting. As of v0.29.0 the NAS compose also sets this to `true` — the earlier `false` relaxation was a holdover from the passive-mode calibration phase, and the proven-clean calibration no longer justifies letting a guard-internal error pass a request through.
## Package floor-pinning
RoboCo floor-pins both guard packages: `fastapi-guard>=7.6.0` and `guard-core[redis]>=3.12.0`.
The two are mutually unpinned upstream and the pairing matters — guard-core 3.12.0 paired with fastapi-guard below 7.6.0 silently drops every body-based `return_pattern` rule, so a mismatched upgrade leaves your response-body rules inert with no error.
The floor pins in RoboCo's requirements prevent that combination from installing.
## Excluded paths
`exclude_paths` semantics changed upstream in v0.29.0 — excluded paths no longer skip every check.
An excluded path now skips only the WAF and behavioral tracking; IP-ban and rate-limit still enforce on excluded paths.
The practical effect: health checks and docs paths stay WAF-exempt (so a benign health probe doesn't trip a signature), but a banned IP or a rate-limit violator hitting an excluded path is still blocked.
## Readiness probe
`apply_guard` exposes an internal readiness probe at `GET /_guard/status`, reachable only on the docker mesh or `localhost:8000` — nginx never routes it, so it's invisible from outside the host.
Use it for container health checks and mesh-level readiness; it returns guard's current configuration posture without touching the request path.
## Enable it
```bash
ROBOCO_GUARD_ENABLED=true
ROBOCO_GUARD_PASSIVE_MODE=false # default on the NAS composes: enforce. true to log-only and calibrate first
ROBOCO_GUARD_FAIL_SECURE=true # now also the NAS compose default as of v0.29.0
# ROBOCO_GUARD_EMERGENCY=true # attack lockdown kill switch
# ROBOCO_GUARD_EMERGENCY_WHITELIST=1.2.3.4 # extra allowed IPs during lockdown
# ROBOCO_GUARD_TELEMETRY_ENABLED=true # + guard_agent_api_key + guard_project_id (payloads exclude HMAC/session headers)
# ROBOCO_GUARD_TRUSTED_CHAIN_PEERS=172.18.0.1 # exact IP(s) of a host proxy hop in front of nginx (e.g. Tailscale Serve's docker gateway); never a CIDR range
# ROBOCO_GUARD_SCAN_RESPONSE_BODY=true # let return_pattern rules read response bodies (default off; Roboco's own rules never need it)
# ROBOCO_GUARD_LOG_SUSPICIOUS_LEVEL=WARNING # log level for suspicious-request entries
```
Guard's `enforce_https` check is hardcoded off — TLS termination sits at nginx, in front of the orchestrator, so the app layer never needs to redirect or reject on scheme. There's no flag for this; it isn't derived from `ROBOCO_ENVIRONMENT` or any other setting.
**Settings → Feature Flags** exposes the master switch. The passive-mode and fail-secure knobs are environment settings, not flags — set them in env.
The flag persists in the settings store and applies on the **next backend restart**.
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## Response-body scanning & log levels
`ROBOCO_GUARD_SCAN_RESPONSE_BODY` (default off) lets `return_pattern` rules read response bodies, not just status codes — Roboco's own rules never need it, so it stays off on the tracked composes. `ROBOCO_GUARD_LOG_SUSPICIOUS_LEVEL` (default `WARNING`) controls the log level for suspicious-request entries when you need more or less detail in the guard logs.
## Telemetry (optional, off)
`ROBOCO_GUARD_TELEMETRY_ENABLED` reports security events/metrics to a guard-core platform via guard-agent. It is **off by default** and **no data leaves the box** while off; flip it on and set `ROBOCO_GUARD_AGENT_API_KEY` + `ROBOCO_GUARD_PROJECT_ID` to enable. As of v0.29.0, telemetry payloads exclude HMAC and session headers via `agent_sensitive_headers` — agent auth tokens never leave the box even with telemetry on.
## Route resolution
`route_resolution_strict` was evaluated for v0.29.0 and deliberately left off.
Enabled, it turns every unresolved path into a `500` for every client regardless of whitelist — which would blind the `status:404` behavior rule (a scanner sweep would read as 500s, not 404s) and turn routine client/panel typos into apparent server errors.
Leave it off unless you have a specific reason and understand this trade-off.
## Next
→ [Environment reference](/docs/deploy/env-reference) for every flag · [Deployment](/docs/deploy/deployment) for where to set them · back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/sandbox-db
Title: Sandboxed Dev DB/Redis/Mongo
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
By default, an agent gating its own work against a real database (when [toolchain matching](/docs/optional/toolchain-matching) is on) does so against **RoboCo's own production Postgres** — reachable creds injected straight into the agent's environment. The sandboxed dev DB/Redis/Mongo subsystem replaces that with something safer: a **throwaway sibling container the agent requests on demand**, only when its work actually needs one.
The capability is **off by default**, and even armed it only applies to projects that opt in.
## What it does
Nothing is provisioned at spawn. When `ROBOCO_SANDBOX_DB_ENABLED` is on and a project's `sandbox_services` column names the services it wants, a **developer or QA agent** calls the `request_sandbox` do-verb the moment its work needs a database — the orchestrator provisions inline and hands back creds in that same call:
request_sandbox] --> B{Guard chain:
flag on · active project
task · project opted in ·
service in opted set}
B -->|fails| C[invalid_state envelope
naming the fix]
B -->|passes| D[Orchestrator provisions the
project's WHOLE opted-in set]
D --> E[Cache hit + still live?
reuse · else provision fresh]
E --> F[Creds returned in this
call's evidence only]
F --> G[release_sandbox on
i_am_done / unclaim / i_am_idle /
pass_review / fail_review / i_documented]
G --> H[Backstop: container teardown
+ grace-windowed orphan janitor]`} />
- **On-demand, role-scoped.** Only `developer` and `qa` roles carry `request_sandbox` in their spawn manifest — the DB-needing gate roles. Spawn itself injects nothing but a marker env `ROBOCO_SANDBOX_SERVICES_AVAILABLE=` (never creds) for an opted-in project, plus one briefing line naming `request_sandbox()` explicitly, so an agent knows the tool exists before it needs it.
- **The guard chain, in order.** `ROBOCO_SANDBOX_DB_ENABLED` off → refused. No active, project-bound task (the agent hasn't `give_me_work`'d yet) → refused. The project has no `sandbox_services` opted in → refused. A requested service outside the project's opted set → refused, naming the allowed set. The orchestrator handle is unavailable (e.g. mid-restart) → refused, but this is the one **retryable** guard.
- **Always the whole opted-in set.** `ensure_sandbox` provisions the project's *entire* opted-in service set on first call, regardless of which subset this particular `request_sandbox` call named — so a later call for any subset or superset is a guaranteed cache hit (same creds, no `docker` calls) and can never trigger a mid-session teardown of a container another part of the session is still using. The response is filtered back down to just what this call asked for.
- **Cache hits are re-verified live.** A cached entry is checked against the real container (`is_live`) before being trusted; a container that died out-of-band (OOM-killed, manually removed) evicts the stale entry and triggers a fresh full-set provision with new creds. Concurrent calls for the same agent are serialized behind a per-agent-slug lock so they can't race each other's provision/teardown.
- **Creds only ever live in the verb's envelope.** A successful call returns one entry per service in the ok-envelope's `evidence`, each with a ready-to-`export` `env` sub-dict — `ROBOCO_TEST_DB_*`, `ROBOCO_TEST_REDIS_*`, or `ROBOCO_TEST_MONGO_*`. There is no spawn-time creds injection at all.
- **A provisioning failure is retryable, not a refusal.** If the sandbox itself can't be created (image pull, readiness timeout), the verb returns a retryable `invalid_state` envelope — there's no spawn left to refuse, since the request happens well after the agent is already running.
- **Three engines, pluggable.** The service set is a registry, not a hardcoded pair: `postgres:16-alpine`, `redis:8-alpine`, and `mongo:8` (MongoDB ships no Alpine variant). Adding a fourth engine is one class plus one registry line.
- **Extensions and modules, allowlisted.** A bare sandbox isn't always enough — a task that needs `pgvector` or RediSearch can ask for it. See "DB extensions and Redis modules" below.
- **Released at the end of the engagement, not just at container death.** `release_sandbox` fires best-effort on the successful exit of `i_am_done`, `unclaim`, `i_am_idle`, `pass_review`/`fail_review`, and `i_documented`, so an agent that finishes and moves on doesn't leave a sidecar running for the rest of its session. Container-removal teardown and a grace-windowed orphan janitor (at orchestrator startup and every reaper tick) remain the backstop for whatever that misses.
- **Throwaway, isolated.** Each sandbox is a fresh sibling container (`tmpfs` data directory, so nothing persists), labeled `roboco.sandbox=1`, with random per-sandbox credentials, capped at 512MB memory / 1 CPU each. Docker-in-agent stays structurally absent — the sandbox is created by the orchestrator, never by the agent itself.
Turning the master flag on doesn't change anything by itself. A project only participates when its `sandbox_services` column is set (e.g. `["postgres", "redis", "mongo"]`) from the project's settings page (Sandbox card); `sandbox_extensions` (e.g. `{"postgres": ["vector"]}`) is a separate, optional column on top of that — a project can opt into sandboxes with no extensions at all. Projects without it keep whatever behavior they had before (no sandbox, and — if toolchain matching is on — the legacy prod-creds injection, unless [DB network isolation](/docs/optional/db-network-isolation) is also in play).
The orchestrator's creds cache lives in process memory, keyed by agent slug. An orchestrator restart forgets every live sandbox. The next `request_sandbox` call for that agent re-provisions from scratch (pre-clearing any still-running stale container first) and returns fresh creds — a connection to the forgotten sandbox simply fails loudly rather than silently.
## DB extensions and Redis modules
A bare sandbox is Postgres or Redis with nothing extra loaded. Some work genuinely needs more — a task exercising a `pgvector` column, or a Redis cache that needs RediSearch — so a project can whitelist specific extensions/modules and an agent can request them the same call it requests the sandbox itself:
- **A fixed allowlist per engine**, not free-form `CREATE EXTENSION`: Postgres offers `vector`, `postgis`, `pg_trgm`, `citext`, `uuid-ossp`; Redis offers the `search` (RediSearch), `json` (RedisJSON), and `bloom` (RedisBloom) modules. Mongo has none to offer. A superuser-language extension like `plpython3u` can never appear here — the allowlist itself is the containment against a sandbox becoming an RCE vector.
- **Two layers, both validated.** A project can set a *standing* set (`sandbox_extensions`, e.g. `{"postgres": ["vector"]}`) from its settings page's Sandbox card, and `request_sandbox` accepts an additional *per-call* `extensions` argument for a one-off need. Both are checked against the same allowlist; a per-call ask for a service the project hasn't opted into, or a feature outside that engine's allowlist, is rejected with the allowed set named rather than silently ignored.
- **Activated after the container is ready, not baked in.** Extensions are enabled via a `docker exec` step once the base readiness probe passes — never through bind-mounts or `initdb` scripts — so a bare sandbox and an extended one boot the same way.
- **Surfaced in the evidence, not guessed.** A service with active features returns an `available_extensions` (Postgres) or `available_modules` (Redis) list alongside its creds in the `request_sandbox` evidence, so the agent knows what it can use without probing for it.
## Enable it
**Settings → Feature Flags → "Sandboxed per-agent test DB/Redis"** arms the subsystem. Then, per project, open its **settings page → Sandbox card** and check the services you want (Postgres / Redis / Mongo) — and, for Postgres/Redis, whichever extensions or modules that service should carry.
The feature flag persists in the settings store and applies on the **next backend restart**. The per-project `sandbox_services` selection applies to that project's next `request_sandbox` call.
```bash
ROBOCO_SANDBOX_DB_ENABLED=true
```
The per-project opt-in (`sandbox_services`) lives on the project record, not in env — set it on the project's settings page.
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- With the flag off, nothing changes: `request_sandbox` refuses every call, regardless of a project's `sandbox_services` setting.
- With the flag on, an opted-in project's developer/QA agents can request a sandbox mid-session instead of relying on RoboCo's production database credentials — and get one back within that same call.
- A project that hasn't set `sandbox_services` sees no change at all.
## Next
→ [DB network isolation](/docs/optional/db-network-isolation) — the compose-level topology change this pairs with. → [Toolchain matching](/docs/optional/toolchain-matching) — the subsystem that decides whether an agent's gate needs a real database at all. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/db-network-isolation
Title: DB Network Isolation
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
`ROBOCO_DB_NETWORK_ISOLATED` is different from every other flag on this page: it isn't something you flip at runtime, and it isn't in the panel's Feature Flags card. It's a **compose-topology setting** — it must travel with a specific `networks:` layout, because a runtime toggle can't change which Docker network a container joined at boot.
This flag only means something paired with the matching compose topology below. There's nothing to flip in Settings → Feature Flags for it — it's an environment/compose setting.
## What it does
All **three tracked compose files** (`docker-compose.yml`, `docker-compose.yaml`, `docker-compose.registry.yml`) ship two user-defined bridge networks instead of one:
+ sandbox sidecars]
end
ORCH[orchestrator] --- roboco_data
ORCH --- roboco_default`} />
- **`roboco_data`** carries **only** Postgres and Redis.
- **`roboco_default`** carries everything else: the panel, nginx, Ollama, every spawned agent container, and their sandbox DB/Redis sidecars.
- The **orchestrator is the only multi-homed service** — it joins both networks, since it's the one process that legitimately needs to reach the production database directly.
The practical effect: **agent containers cannot resolve or TCP-reach `roboco-postgres:5432` or `roboco-redis:6379` at all.** Since Redis has no auth of its own, network membership is its entire containment. Nothing else changes — agent↔agent A2A traffic, orchestrator→agent SDK polls, MCP→orchestrator calls, and the published host ports (`15432` / `16379` / `11435`) are all unaffected, as is anything that rides the Docker daemon socket (`docker exec`/`inspect`).
When this flag is true, the orchestrator's legacy `_append_gate_env` step (which — only when [toolchain matching](/docs/optional/toolchain-matching) is also on — hands an agent RoboCo's own database credentials so its test gate can run against a real Postgres) is **suppressed entirely**. Handing out credentials for a host the agent can't reach anyway is worse than handing out none — the connection would just dead-end in a timeout instead of failing cleanly. A project that needs a real database for its gate should opt into the [sandboxed dev DB/Redis/Mongo](/docs/optional/sandbox-db) instead.
## Default state
Unlike the other four features on this page, this one ships **armed in every tracked compose file**, including the registry compose — it's the only optional flag that is. You'd only see the old shared-network behavior by hand-editing the compose networks section yourself (not recommended).
| Variable | Default (config) | Default (all 3 tracked composes) |
|----------|-------------------|-----------------------------------|
| `ROBOCO_DB_NETWORK_ISOLATED` | `false` | `true` |
## Next
→ [Sandboxed dev DB/Redis/Mongo](/docs/optional/sandbox-db) — what an opted-in project uses instead of direct database access. → [Security](/docs/troubleshooting/security) — the full trust model this topology is part of. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/cloud-auth
Title: Cloud Auth
================================
import { Admonition } from "@/components/docs/admonition";
Cloud auth is what lets you safely put the panel somewhere beyond a trusted LAN — a single seeded login, in front of the CEO's view of the company. It is **off by default**; while off, RoboCo behaves byte-for-byte as it always has (header-trust — see [Security](/docs/troubleshooting/security)).
There is exactly **one** user: you, the CEO. There is no registration route, no invite flow, no roles beyond CEO. Cloud auth exists to put a lock on the door, not to onboard a team.
## What it does
With `ROBOCO_CLOUD_AUTH_ENABLED=true`:
- Exactly one user is seeded — from `ROBOCO_CLOUD_AUTH_EMAIL` / `ROBOCO_CLOUD_AUTH_PASSWORD` — the moment the flag is armed. No registration router is mounted; there is no way to create a second account.
- A login issues a **session cookie** (`roboco_session`) — an HTTP-only, `SameSite=Lax`, **secure-only** cookie carrying a signed JWT.
- The session is **sliding**: every authenticated request re-mints and re-sets the cookie, so an active session never lapses. Only genuine inactivity past `ROBOCO_CLOUD_AUTH_COOKIE_MAX_AGE` (default 2,592,000 seconds — 30 days) logs you out.
- Rotating your seeded password invalidates every previously-issued session cookie immediately — the token carries a fingerprint of the current password hash, so an old cookie stops working the moment the password changes, without needing a token-revocation list.
- With the flag off, `GET /auth/status` still always responds (the panel's middleware polls it before every navigation to know whether to show a login screen); the login/logout routes themselves don't exist until the flag is on.
## It closes the header-spoof hole, not just adds a login screen
Cloud auth isn't only "add a password before the dashboard." It changes what the API trusts. Off, every request is header-trust: whoever sets `X-Agent-Role: ceo` *is* the CEO for that request — the whole point of the default LAN-only posture. On, header-trust for the CEO role is killed:
- A caller with a **valid agent HMAC token** (`X-Agent-Token`) is verified exactly as before — the agent fleet is untouched by this flag.
- A caller claiming a role **without** a verifiable token is treated as a spoof and rejected with 401 — including a claimed `ceo` role.
- The **only** way to authenticate as the CEO without a token is a valid session cookie.
The login page isn't the only thing that can mint that cookie: with the [Telegram Mini App](/docs/optional/telegram-bridge) armed (its own env-only flag, on top of cloud auth), opening the bot's menu button validates Telegram's signed launch data server-side and issues the identical `roboco_session` cookie — a second front door onto the same session mechanism, not a second trust path.
See [API auth](/docs/api/auth) for how these two paths — the session cookie and the agent token — sit side by side.
## Required configuration
| Variable | Required? | Notes |
|----------|-----------|-------|
| `ROBOCO_CLOUD_AUTH_ENABLED` | Master switch | Off by default. Not a panel toggle — it changes authentication behavior, so it's environment-only. |
| `ROBOCO_CLOUD_AUTH_EMAIL` | **Yes, when enabled** | Login email for the single seeded user. |
| `ROBOCO_CLOUD_AUTH_PASSWORD` | **Yes, when enabled** | Login password for the single seeded user. Hashed at startup; never stored in plain text. |
| `ROBOCO_CLOUD_AUTH_SECRET` | **Yes, when enabled — fails loud otherwise** | Signs the session JWT. Generate with `python -c 'import secrets; print(secrets.token_hex(32))'`. |
| `ROBOCO_CLOUD_AUTH_COOKIE_MAX_AGE` | No (default `2592000`, 30 days) | Sliding session lifetime in seconds. |
If `ROBOCO_CLOUD_AUTH_ENABLED=true` and `ROBOCO_CLOUD_AUTH_SECRET` is unset, the orchestrator **refuses to start** — it will not silently mint unsigned sessions. Set the secret before you flip the flag, or the process won't come up at all.
`CookieTransport` is configured `cookie_secure=True`. Browsers will not send a secure cookie over plain HTTP, so if you arm this flag without terminating TLS in front of RoboCo, login will appear to silently fail — the cookie is set by the response but never sent back on the next request. **Terminate HTTPS before you enable cloud auth**, whether that's a reverse proxy you add in front of nginx or a tunnel that provides TLS for you.
## Enable it
1. Decide on the login email/password for the one CEO user.
2. Generate a session secret: `python -c 'import secrets; print(secrets.token_hex(32))'`.
3. Put all three in your environment:
```bash
ROBOCO_CLOUD_AUTH_ENABLED=true
ROBOCO_CLOUD_AUTH_EMAIL=you@example.com
ROBOCO_CLOUD_AUTH_PASSWORD=
ROBOCO_CLOUD_AUTH_SECRET=
# ROBOCO_CLOUD_AUTH_COOKIE_MAX_AGE=2592000 # default, 30 days
```
4. Make sure whatever sits in front of RoboCo terminates TLS.
5. Leave `ROBOCO_PANEL_AGENT_TOKEN` **unset** — a set panel token bypasses login entirely (the panel would authenticate as the CEO via the injected HMAC token instead of a session), which defeats the point of arming a login.
6. **Restart the backend.**
If you want to bring the stack up before you've settled on real credentials, set `ROBOCO_CLOUD_AUTH_ENABLED=false` for that run — the compose defaults on the NAS path arm this flag, so an operator who hasn't set the three values yet should explicitly disable it rather than let the process fail to start.
## What changes when it's on
- The panel shows a login screen instead of going straight to the Command Center; `GET /auth/status` is what it polls to decide.
- Every API request must carry either a valid `X-Agent-Token` or a valid session cookie — a bare role header is no longer trusted for `ceo`.
- Logging in mints a 30-day sliding session; changing the seeded password invalidates every session at once.
## Next
→ [API auth](/docs/api/auth) — the session cookie and the agent HMAC token, side by side. → [Telegram bridge](/docs/optional/telegram-bridge) — the Mini App, a second way to mint this session, from your phone. → [Security](/docs/troubleshooting/security) — the full trust model, on or off. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/telegram-bridge
Title: Telegram Bridge
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
RoboCo can DM you on Telegram the moment something needs you — a task escalating for your approval, or a task completing — and, once you turn on the inbound half, you can act right from the chat: check status, browse everything awaiting your decision, and approve or reject with a tap. A third layer, the **Mini App**, goes further: open the bot's menu button in Telegram and you land on a phone-sized cockpit — a daily brief, native approval cards, a read-only board, your inbox, and A2A chat, all without leaving the app. The panel's own notifications and journals stay the source of truth; Telegram is a faster way to notice, a faster way to act, and now a real second surface to run the company from your phone.
All three layers are **off by default**, and even armed none of them do anything until you've stored a bot token and chat id. The inbound half needs its own extra flag on top of the outbound one, and the Mini App needs cloud auth plus its own flag on top of that — see "Enable it" below.
## Outbound: DMs to you
Task completion /
held-draft origination] --> B{ROBOCO_TELEGRAM_ENABLED?}
B -->|off| Z[nothing sent]
B -->|on| C{Credentials stored?}
C -->|no| Z2[no-op, nothing sent]
C -->|yes| D[Subject + panel deep-link
sent via Bot API]
D --> E[Best-effort — never blocks
the escalation/completion/
origination itself]`} />
- **Three triggers.** A Telegram DM fires when a task escalates to `awaiting_ceo_approval`, when a task completes, and whenever any of the four held-draft queues — a release proposal, an X post, a video, or a roadmap review cycle — originates a new item.
- **Subject and a link, never the content.** The message is the notification's subject line plus (when `ROBOCO_PUBLIC_BASE_URL`/your panel URL is configured) a deep-link straight to that task in the panel. The task's body, comments, diffs, or notes are never sent to Telegram — if you want the detail, you follow the link into the panel.
- **Best-effort, deferred.** The send happens after the triggering database transaction commits and is wrapped so a Telegram outage, a bad token, or a rate limit never blocks the escalation, completion, or origination it's attached to. Worst case, you just don't get the DM — the org keeps moving.
- **Actionable when the inbound half is on.** With commands and buttons armed, the escalation DM and every held-draft-origination DM carry their own **Approve / Reject / Open** row, so you can act on it without leaving Telegram. The completion DM stays link-only — there's nothing to approve on a task that's already done.
## Inbound: commands and one-tap actions
With the inbound half armed, RoboCo also polls Telegram for what you send it — commands you type, and taps on the buttons that ride along on escalation DMs, held-draft-origination DMs, and the queue. Nothing fires without you typing a command or tapping a button; the bot never posts to you on its own beyond the three outbound triggers above.
- **`/status`** — a quick fleet snapshot: how many agents are active, and task counts by status.
- **`/queue`** — everything currently awaiting your decision across the panel's approval queues — CEO-escalated tasks, an open release proposal, held X/video drafts, and proposed roadmap items — each one sent as its own message with its own **Approve / Reject / Open** row (capped at 10 items per call; use the panel for the rest).
- **`/task `** — look up one task by its 8-character id or a title fragment. An ambiguous fragment lists the candidates instead of guessing.
- **`/agents`** — who's working on what, right now.
- **`/blocked`** — every blocked task plus everything currently awaiting you, capped to a phone-sized list.
- **`/usage`** — today's token spend.
- **`/help`** — the full command list, also shown as the fallback reply to anything unrecognized. The same list drives Telegram's own `/` command menu for the bot — one registry, so the two can never drift apart.
Every message and every button tap is checked against the stored chat id, and — when Telegram supplies a sender — the sender's id too. Anything from another chat or another sender is silently dropped, so this only ever works from the one private chat you set up.
### Chatting with Secretary or drafting a task, from the chat itself
Two more commands bridge the Telegram chat straight into the same live agents the panel's own chat surfaces drive — not a separate, lesser copy of them:
- **`/secretary [message]`** — opens (or continues) a conversation with the chief-of-staff. Send it with no message to just open the session; from then on, plain text you send is routed into that live conversation, the same as typing into the Secretary panel.
- **`/newtask [message]`** — opens a live Intake conversation the same way. Describe what you want built, back-and-forth, exactly as in the panel's Task Assistant. When the agent has enough to propose a task, it posts a **draft card** — title, team, description, acceptance criteria — with **Send to Board** and **Discard** buttons right under it. Tapping **Send to Board** confirms the draft into the normal board-review flow and **parks the chat**, so when the Product Owner and Head of Marketing finish reviewing it, their feedback streams right back into the same Telegram thread instead of stranding you back in the panel to see it.
- **`/end`** — closes whichever of the two sessions is open in this chat.
Because Intake and Secretary are single, process-wide conversations, starting one from Telegram takes over from a session left open in the panel, and vice versa — there's one live conversation per kind, not two independently drifting copies. A session left idle for a while is swept automatically; a parked session (waiting on board feedback) is exempt, so it can't be reaped out from under you mid-review. MegaTask batches (several tasks proposed in one draft) still confirm from the panel only — the chat bridge is single-task by design.
### Approving and rejecting from chat
Tapping **Approve** or **Reject** on a task, release, X/video draft, or roadmap item calls the exact same guarded service path the panel's own approve/reject buttons call — the same locks, the same idempotency, no shortcut. The action is audited alongside every other approval, tagged `via=telegram` so you can tell a chat-driven decision from a panel one.
- **Approving a task** needs a note (the same 20-character-minimum CEO note the panel requires), so RoboCo replies asking you to type it; your next message answers the prompt. Release, X/video, and roadmap approvals need no note and post-and-publish (or approve) immediately.
- **Rejecting anything** always prompts for a reason, with the same per-kind minimum length the panel's own reject forms enforce (a release rejection needs more detail than an X-post rejection, for instance) — reply to the prompt with your reason to complete it.
- **A reply prompt expires** if you don't answer it in time; RoboCo tells you to tap the button again rather than silently accepting a stale reply.
- **Stale buttons are refused, not silently accepted.** Tap Approve or Reject on an item someone (or you, from the panel) already decided, and RoboCo tells you plainly — "already rejected" or "already published" — instead of double-posting or re-publishing it.
## Setting up a bot with BotFather
Telegram bots are created through Telegram's own `@BotFather`, not through RoboCo:
1. Open a chat with **[@BotFather](https://t.me/BotFather)** in Telegram.
2. Send `/newbot`, then follow the prompts for a display name and a username (must end in `bot`).
3. BotFather replies with a **bot token** — copy it, you'll paste it into the panel.
4. Message your new bot directly (or add it to a group and message the group) so it has a chat to send into.
5. Get the **chat id**: the simplest way is to message a helper bot like `@userinfobot` (for a DM) or `@getidsbot` (for a group), which replies with the numeric id. Alternatively, hit `api.telegram.org/bot/getUpdates` (over https) after messaging your bot and read the `chat.id` field from the JSON response.
6. **Optional, for the Mini App:** send `/setmenubutton` to BotFather, pick your bot, then supply the button text and the URL `https:///tg`. Telegram only accepts an `https://` URL here — see "Mini App: a cockpit on your phone" below for the rest of what that needs.
## Mini App: a cockpit on your phone
Beyond the DM and command surfaces above, RoboCo ships a full **Mini App**: open the bot's menu button in Telegram and it loads `/tg`, a phone-sized cockpit with five tabs.
- **Today** — the tab you land on. One glance answers "does anything need me?": a spend hero (today's cost, its trend against yesterday, and a live 7-day sparkline), a **needs-you** banner when something's waiting plus a held-draft count per queue, the fleet as live avatars with each agent's current task title, the week's shipped-task count as a day-by-day bar chart, and the current version plus whether a release proposal is waiting (the ship state). A row of quick actions sits right under the spend hero — see "Operations ring" below.
- **Approvals** — the exact same release-proposal, X-post, video-post, and roadmap-review queues the panel's dashboard shows, as a native card stack you thumb through: full context per kind (a live 280-character counter while editing an X post, the actual video preview with a cut switch, the per-acceptance-criterion view on a release proposal, a roadmap pitch's full write-up), Telegram's own bottom **MainButton**/**BackButton** chrome for the primary action, and haptic feedback on every tap. If one of the four underlying queues fails to load, that failure is shown plainly — it never quietly renders as "you're all caught up."
- **Inbox** — every notification, newest first, with an Acknowledge button on the ones that require one.
- **Board** — every task, read-only, grouped into collapsible sections by status. Tapping a row opens a **task detail sheet**: status, the same "bounced" chip the desktop panel shows once a task has been sent back before, the description, every acceptance criterion, the open items from the [revision findings ledger](/docs/company/review-and-quality#structured-findings) (severity-coded, with the file/line and the suggested fix), and a link straight to the PR. It's read-only by design — decisions still happen in Approvals or the desktop panel — but you no longer have to leave the chat just to see what a task actually says.
- **Chat** — your A2A conversations: read a thread, reply, or start a new DM with any DM-capable agent.
Chat and Today both refresh live off the `/ws/system` operator stream, the same socket-with-fallback pattern the desktop panel uses everywhere (see [WebSockets](/docs/api/websockets)): the moment something changes, the tab updates without a manual pull-to-refresh, and it falls back to polling if the socket drops.
### Operations ring
Today's quick-action row is for doing things, not navigating — the tab bar below already handles navigation:
- **Ship** — jumps straight to the release proposal in Approvals when one is open; otherwise just shows the current version.
- **Ack all** — acknowledges every notification currently requiring one, in a single tap, with a haptic confirmation and a count of how many went through.
- **Sweep** — opens a confirmation sheet, then runs [Clean Up Stale Branches](/docs/panel/git#cleaning-up-stale-branches) across every project with git configured — the same completed/cancelled-task branch cleanup the Git page's button runs, reachable now without opening the desktop panel at all.
- **Fleet** — opens a full working-roster sheet: every mid-task agent, their live avatar, and what they're building, beyond the handful Today shows inline.
### The look
The cockpit carries its own brand identity rather than reading like a generic settings screen: a monospaced display face (Share Tech Mono) for labels, stats, and a blinking `ROBOCO_` wordmark; a hand-drawn duotone icon set in place of stock glyphs; and small, purposeful motion — the spend figure counts up, tabs rise in on switch, the sparkline draws itself in, sheets slide up from the bottom — all built from transform/opacity so it stays smooth on a phone and backs off automatically if you have reduced-motion set system-wide. Inside Telegram, the surfaces still repaint to *your* Telegram theme; the accent color and the brand type stay constant either way.
### Signing in is automatic and cryptographic
There's no password to type inside Telegram. When you open `/tg`, Telegram hands the page a signed **initData** payload identifying you; RoboCo validates its HMAC signature against your stored bot token (Telegram's own [WebApp validation algorithm](https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app)), checks the payload isn't stale (`ROBOCO_TELEGRAM_INITDATA_MAX_AGE_SECONDS`, default 600 seconds), and confirms the Telegram user id in the payload matches the chat id you configured for the bridge. Once all three check out, it mints the exact same cloud-auth session cookie `POST /api/auth/login` issues for a normal panel login (see [API auth](/docs/api/auth)) — so from that point on, the cockpit *is* an authenticated panel session, just running full-screen in Telegram. No password is ever typed inside Telegram, and the session respects the same sliding lifetime as a normal panel login. Sign-in attempts are rate-limited and recorded to the audit trail like any other auth event.
### Setup requirements
The Mini App layers on top of everything above, plus cloud auth:
1. **Public HTTPS.** Telegram only opens a Mini App over `https://` — a bare LAN address won't do; the deployment needs a real public host (reverse proxy, tunnel, whatever gets you TLS).
2. **Cloud auth armed.** `ROBOCO_CLOUD_AUTH_ENABLED=true` with the seeded CEO login configured (see [Cloud auth](/docs/optional/cloud-auth)) — the Mini App mints a cloud-auth session cookie, so there's nothing to mint without it; startup refuses to come up if the Mini App flag is on and this isn't. Leave `ROBOCO_PANEL_AGENT_TOKEN` unset, same reason as for cloud auth generally.
3. **`ROBOCO_TELEGRAM_MINIAPP_ENABLED=true`.** Deliberately environment-only — like cloud auth itself, it's a security/TLS-coupled switch, not something on the panel's Feature Flags card.
4. **Bridge credentials.** The same bot token and chat id as the DM/command bridge above, stored in the panel.
5. **Point the bot's menu button at your Mini App.** BotFather → `/setmenubutton` → your bot → button text, then `https:///tg`. No code involved — step 6 above.
`/tg` checks for Telegram's own launch context. Opened in a regular browser tab, it shows an "Open from Telegram" screen instead of the cockpit — there's no initData to validate, so there's nothing to sign in with. The one exception is a **demo mode** (`/tg?demo=1`) meant for evaluating the layout or taking screenshots: it renders the Today, Approvals, Board, and Inbox tabs against fixture data instead of your real fleet, with no backend or sign-in involved. Chat is the one tab demo mode can't fake, since it has no live conversations to show.
Mini App sign-in checks the initData's Telegram user id against the chat id you configured for the bridge. If that chat id belongs to a group, sign-in can never succeed — a group has no single Telegram user id to match. Use your own personal chat id here, even if you point the DM/command bridge at a group elsewhere.
Stale actions are refused the same way everywhere in RoboCo: approving or rejecting an item someone already decided — from the panel, from a DM button, or from a tap inside the Mini App's Approvals tab — is rejected rather than double-applied, because every surface calls the exact same guarded service path.
## Required configuration
| Setting | Where it lives | Notes |
|---------|----------------|-------|
| `ROBOCO_TELEGRAM_ENABLED` | Environment / panel flag | Master switch. Off ⇒ no Telegram API call is ever made. |
| `ROBOCO_TELEGRAM_INBOUND_ENABLED` | Environment / panel flag | Sub-switch on top of the master switch above. Off ⇒ the bot only sends the three outbound DMs; it never polls, never reacts to a command, and the escalation DM and every held-draft-origination DM carry no buttons. |
| Bot token, chat id | **Settings → Feature Flags**, in the collapsible under the *Telegram notifications* flag row | Fernet-encrypted at rest, the same pattern as a project's git token or the X engine's OAuth secrets. Write-only — once saved, the panel never displays them again. The same credentials back all three layers. |
| `ROBOCO_TELEGRAM_TIMEOUT_SECONDS` | Environment (default `10.0`) | Per-request timeout for the outbound Bot API call. |
| `ROBOCO_TELEGRAM_MINIAPP_ENABLED` | Environment only | Master switch for the Mini App (`POST /api/telegram/webapp-auth`). **Not** on the panel's Feature Flags card, on purpose — see the note below. Requires `ROBOCO_CLOUD_AUTH_ENABLED=true`; the orchestrator refuses to start if this is on without that. |
| `ROBOCO_TELEGRAM_INITDATA_MAX_AGE_SECONDS` | Environment (default `600`) | How old, in seconds, a Telegram-signed initData payload is allowed to be before Mini App sign-in refuses it as stale. |
The bot token and chat id are never read from environment variables — they live only in an encrypted database row, decrypted server-side only. Set them from the collapsible under the *Telegram notifications* flag row in **Settings → Feature Flags**.
`ROBOCO_TELEGRAM_MINIAPP_ENABLED` is security/TLS-coupled, the same reasoning as `ROBOCO_CLOUD_AUTH_ENABLED` itself — it changes what a request can authenticate as, so it stays environment-only rather than something a runtime toggle can flip mid-session. It isn't on the Feature Flags card.
## Enable it
**Settings → Feature Flags** carries **"Telegram notifications"** and, right below it, **"Telegram inbound commands + actionable buttons"**. Turn on the first for outbound DMs; also turn on the second for commands and one-tap buttons. Then expand the credentials collapsible under the *Telegram notifications* row and paste in the bot token and chat id from the BotFather setup above — all three layers share these. The Mini App's own switch isn't here — see the Environment tab.
Both feature flags persist in the settings store and apply on the **next backend restart**. Credentials take effect as soon as they're saved — no restart needed for those.
```bash
ROBOCO_TELEGRAM_ENABLED=true
# ROBOCO_TELEGRAM_INBOUND_ENABLED=true # also enables commands + one-tap buttons
# ROBOCO_TELEGRAM_TIMEOUT_SECONDS=10.0 # default
# Mini App — needs ROBOCO_CLOUD_AUTH_ENABLED=true and a public HTTPS host
# ROBOCO_TELEGRAM_MINIAPP_ENABLED=true
# ROBOCO_TELEGRAM_INITDATA_MAX_AGE_SECONDS=600 # default
```
The bot token and chat id are **not** environment variables — set them from the credentials collapsible under the flag row in the panel's Feature Flags card.
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- With all flags off, or with no credentials stored, nothing changes — every escalation, completion, and held-draft origination behaves exactly as it does today, just without the DM.
- With the outbound flag on and credentials saved, an escalation to `awaiting_ceo_approval`, a task completion, and a new item in any held-draft queue each send you one Telegram message: the subject, and a link back into the panel.
- With the inbound flag also on, the escalation DM and every held-draft-origination DM gain Approve/Reject/Open buttons, `/status`, `/queue`, `/task`, `/agents`, `/blocked`, and `/usage` start answering you in the chat, and `/secretary` / `/newtask` open live conversations bridged into the same runtimes the panel drives.
- With cloud auth and the Mini App flag both on, the bot's menu button opens `/tg` — the five-tab phone cockpit (Today, Approvals, Inbox, Board, Chat) with its own operations ring, live `/ws/system` refresh, and brand look — and signs you in automatically off Telegram's own signed launch data, minting the same session cookie a panel login would.
- A Telegram outage, an expired token, or a bad chat id degrades silently on the Telegram side only — the escalation/completion/origination itself still goes through, and a poll cycle that fails is just retried on the next one.
## Next
→ [Notifications](/docs/panel/journals-and-notifications) — where the same escalations and completions show up in the panel, including the ack-required expiry the Ack-all button clears. → [Review & Quality](/docs/company/review-and-quality) — the findings ledger the Board tab's task sheet reads from. → [Git](/docs/panel/git) — the stale-branch sweep the ops ring's Sweep button also runs. → [Cloud auth](/docs/optional/cloud-auth) — the session cookie the Mini App mints, and its own required setup. → [Settings](/docs/panel/settings) — where the flag and its credentials collapsible live. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/x-engine
Title: X (Twitter) Engine
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
RoboCo can draft posts for the company's X (Twitter) account — a release announcement whenever you publish a version, replies to mentions, and periodic spotlights on your own shipped-but-under-publicized capabilities — voiced as the Head of Marketing. It never posts anything on its own: every draft is **held** in the **X Post Queue** on the panel's [Social](/docs/panel/social) page for you to edit, approve, or reject.
The engine is **off by default**, and even armed it does nothing until you've stored real X API credentials.
## What it does
Three independent sources feed the same held queue:
B[draft_release_post hook]
C[Mentions poll interval] --> D{x_replies_enabled?}
D -->|no| Z[nothing polled]
D -->|yes| E[Fetch mentions]
B --> F[Draft via local LLM,
HoM voice]
E --> G{meaningful +
not already seen?}
G -->|no| C
G -->|yes| F
S[Spotlight cycle interval] --> T{x_feature_spotlight_enabled?}
T -->|no| Z2[cycle never fires]
T -->|yes| U[Spawn Head of Marketing
to investigate shipped features]
U --> V[propose_feature_spotlight]
F --> H[HELD draft in the
X Post Queue]
V --> H
H --> I{CEO decision}
I -->|approve| J[Posted to X]
I -->|reject| K[Cancelled, reason recorded]`} />
- **Release posts** are event-driven: `ReleaseProposalService.approve()`'s publish-success path calls the hook directly — there's no polling for this half. One draft per version (idempotent on retry); nothing drafts if the open-post cap (`ROBOCO_X_MAX_OPEN_POSTS`) is already reached.
- **Mention replies** are a periodic poll (`ROBOCO_X_MENTIONS_INTERVAL_SECONDS`, default 1800s) gated by a **second** switch, `ROBOCO_X_REPLIES_ENABLED` — reading mentions needs a paid X API tier, so replying is a deliberate opt-in on top of the base engine. A mention only gets drafted if it's "meaningful" (not a bare retweet, and at or above `ROBOCO_X_MENTIONS_MIN_ENGAGEMENT` combined likes+replies+retweets) and hasn't been seen before (deduped by mention id in `x_seen_mentions`).
- **Feature spotlights** are a third, independent switch (`ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED`) covered in its own section below — unlike the two sources above, this one spawns a real agent rather than drafting off a template.
- **Release and reply drafting run on the local LLM only** — never a cloud model in the hot path — using a fixed Head-of-Marketing voice prompt (confident, concise, no hashtag spam, speaks as "we"). A failed local-model call falls back to a plain template rather than drafting nothing.
- **Every draft is clamped to 280 characters** and, without stored credentials, the X client degrades to a no-op `NullXClient` — the same graceful-null pattern as an unconfigured research provider. No credentials, no draft, no API call.
- **The held task is owned by the Secretary** (`confirmed_by_human=False`), the same held-artifact pattern the [gated release manager](/docs/optional/autonomous-maintenance) uses — skipped by every dispatcher, acted on only through the CEO's approve/reject.
## Feature spotlight: an agent that finds its own good stories
The release and reply drafts write themselves off a template; the spotlight is different. On its own interval (`ROBOCO_X_FEATURE_SPOTLIGHT_INTERVAL_SECONDS`, base 1 day — a quiet-week guard stretches that to 3× when nothing new has shipped since the last spotlight, so it doesn't fire daily against a stale codebase), RoboCo spawns the **Head of Marketing** with real read access — the changelog, feature flags, docs, the codebase map, the charter, and the knowledge base — to go find a capability that's actually shipped but nobody's talked about yet, and write it up.
- **A real cloud-LLM spawn, not a template fill.** This is the one part of the X engine that costs an agent turn per cycle, which is why it's gated behind its own sub-switch (`ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED`) on top of the base engine rather than bundled into it.
- **One call, one draft.** The Head of Marketing investigates, then makes exactly one `propose_feature_spotlight` call — which marks the feature it picked as "seen" (so the same capability doesn't get spotlighted twice) and drops a new held draft into the same **X Post Queue** everything else here lands in.
- **Same review, same voice.** The draft is edited, approved, or rejected exactly like a release post or a reply — and it shares the same [brand-voice charter](/docs/panel/business) every other draft on this page does.
The feature spotlight was the second engine folded into the generic [Board Programs](/docs/optional/board-programs) registry — same flag, same cadence, same queue, nothing about it changed. What's new is company: the Head of Marketing now also runs **Megaphone** (a standing editorial calendar) and **Barfly** (adjacent-conversation replies), both of which drop their own held drafts into this same **X Post Queue** once armed — so a draft you didn't expect from a release or a mention might be one of those instead. **War Room** (release campaigns) lands here too, as a set of drafts with a suggested posting order.
Even fully armed with credentials, a draft sits in the queue until you explicitly approve it — optionally editing the body first, up to the 280-character limit.
### Rejecting with a reason redrafts (parity with the video engine)
Rejecting a draft always cancels it — it's never posted. But if you give a **non-blank reason**, that reason schedules a fresh redraft of the same source once the rejection commits: the engine re-drafts using your feedback instead of leaving the slot empty. A **blank** rejection is a plain cancel with no follow-up, exactly as before. This mirrors the [video engine](/docs/optional/video-engine)'s own reject-with-feedback re-authoring loop. The redraft is best-effort and deferred — a redraft failure never blocks or delays the reject itself, and a stale/replayed reject (e.g. a double-tapped Telegram button) on an already-cancelled draft is a no-op rather than scheduling a second redraft.
### Bounds on origination
| Setting | Default | Meaning |
|---------|---------|---------|
| `ROBOCO_X_MAX_OPEN_POSTS` | `10` | Rolling cap on concurrently-open held posts/replies (both sources combined); the engine originates nothing more past it. |
| `ROBOCO_X_MENTIONS_MAX_PER_CYCLE` | `5` | Most reply drafts the mentions poll may originate in one cycle. |
| `ROBOCO_X_MENTIONS_MIN_ENGAGEMENT` | `0` | Minimum combined like+reply+retweet count for a mention to count as worth replying to. |
## Required configuration
Credentials are the four OAuth 1.0a user-context secrets from your X developer app — **entered in the panel, never in `.env`**:
| Setting | Where it lives | Notes |
|---------|----------------|-------|
| `ROBOCO_X_ENGINE_ENABLED` | Environment / panel flag | Master switch. Off ⇒ no draft is ever originated and no X API call is made. |
| `ROBOCO_X_REPLIES_ENABLED` | Environment / panel flag | Sub-switch for mention replies specifically. Off ⇒ only release posts draft, even with the master switch on. |
| `ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED` | Environment / panel flag | Sub-switch for the feature-spotlight half. Off ⇒ the master switch above only ever drafts release posts and mention replies — no Head-of-Marketing spawn happens. |
| API key, API key secret, access token, access token secret | **Settings → X (Twitter) Credentials** card in the panel | Fernet-encrypted at rest, exactly like a project's git token. Write-only — once saved, the panel never displays them again. |
| `ROBOCO_X_ACCOUNT_USER_ID` | Environment (optional) | The account's own numeric X user id. Leave empty and the engine resolves it once per mentions cycle via `GET /2/users/me`. |
| `ROBOCO_X_REQUEST_TIMEOUT_SECONDS` | Environment (default `15.0`) | Per-request timeout for outbound X API calls. |
Unlike most settings on this page, the four OAuth secrets are never read from environment variables — they live only in the `x_credentials` database table, encrypted the same way a project's git token is. Set them from **Settings → X (Twitter) Credentials**.
## Enable it
**Settings → Feature Flags** carries **"X (Twitter) engine"**, **"X mention replies (needs a paid X API tier)"**, and **"X feature-spotlight marketing"** as three separate switches. Then scroll to the **X (Twitter) Credentials** card (same Settings page) and enter the four OAuth 1.0a secrets from your X developer app.
The feature flags persist in the settings store and apply on the **next backend restart**. Credentials take effect as soon as they're saved — no restart needed for those.
```bash
ROBOCO_X_ENGINE_ENABLED=true
# ROBOCO_X_REPLIES_ENABLED=true # optional: also poll + reply to mentions
# ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED=true # optional: spawn the HoM to draft spotlights
# ROBOCO_X_FEATURE_SPOTLIGHT_INTERVAL_SECONDS=86400 # default (1 day, stretches to 3x when idle)
# ROBOCO_X_MENTIONS_INTERVAL_SECONDS=1800 # default
# ROBOCO_X_MAX_OPEN_POSTS=10 # default
# ROBOCO_X_ACCOUNT_USER_ID= # optional; auto-resolves if empty
```
The four credential secrets are **not** environment variables — set them from **Settings → X (Twitter) Credentials** in the panel.
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- Publishing a release (via the [gated release manager](/docs/optional/autonomous-maintenance)) drafts one held announcement post, visible in the **X Post Queue** on the [Social](/docs/panel/social) page.
- With replies also on, a background poll checks mentions on its interval and drafts held replies to the meaningful ones, subject to the caps above.
- With feature-spotlight also on, the Head of Marketing periodically investigates the codebase and drafts one spotlight post per cycle for a capability that hasn't been spotlit yet.
- With no credentials stored, all three paths still run but produce nothing — the client is a no-op until you save real secrets.
## Next
→ [Social](/docs/panel/social) — where the X Post Queue lives. → [Board Programs](/docs/optional/board-programs) — the registry this program rides, including Megaphone, Barfly, and War Room, its new X-drafting siblings. → [Board roadmap engine](/docs/optional/roadmap-engine) — the other Board-facing held-queue capability. → [Video engine](/docs/optional/video-engine) — an approved feature-spotlight draft can also trigger a companion video. → [Settings](/docs/panel/settings) — where the credentials card lives. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/video-engine
Title: Video Engine (HyperFrames)
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
RoboCo can author and post short marketing videos for the company's X (Twitter) and TikTok accounts — a release announcement, a feature spotlight, or an on-demand CEO request — voiced as the Head of Marketing and rendered by a credential-free `video-renderer` sidecar (HyperFrames). It never posts anything on its own: every clip is **held** in the **Video Post Queue** on the panel's [Social](/docs/panel/social) page for you to preview, edit, approve, or reject.
The engine is **off by default**, and even armed it does nothing until the authoring task completes, the render sidecar returns MP4s, and you approve the held draft.
## What it does
Three independent triggers feed the same authoring → render → held-draft pipeline:
B{video_on_release?}
C[CEO approves a spotlight
that requests a video] --> D{video_on_spotlight?}
E[CEO POST /api/video/request] --> F[On-demand trigger]
B -->|no| Z[nothing]
D -->|no| Z
B -->|yes| F
D -->|yes| F
F --> G[Assigned UX/UI authoring task
builds a motion/ composition]
G --> H[Render loop tars motion/ source
to video-renderer sidecar]
H --> I[9:16 + 1:1 MP4 cuts rendered]
I --> J[HELD video_post draft in the
Video Post Queue]
J --> K{CEO decision}
K -->|approve| L[Posted to X and/or TikTok]
K -->|reject, reason given| M[Cancelled + a fresh
authoring task carrying the feedback]
K -->|reject, no reason| M2[Cancelled, no follow-up]`} />
- **Release videos** are event-driven: a release publish fires the video-on-release trigger — gated by `ROBOCO_VIDEO_ON_RELEASE` independently of the master switch.
- **Feature-spotlight videos** fire when the CEO approves a feature-spotlight draft that requests one — gated by `ROBOCO_VIDEO_ON_SPOTLIGHT`.
- **On-demand** is the CEO's escape hatch: `POST /api/video/request` opens an authoring task directly, independent of the two automatic triggers.
- **Authoring is a normal, assigned UX/UI task** (balanced across the two ux-devs), not a held draft — the dev builds a HyperFrames HTML composition under `motion/compositions//` (one `vertical.html` + one `square.html` carrying the render params), proposes its composition id + per-platform captions via the team-gated `propose_video` do-tool, and ships it through the standard commit/PR/QA/doc/review lifecycle.
- **The render loop** (`ROBOCO_VIDEO_RENDER_INTERVAL_SECONDS`) tars the merged `motion/` source and POSTs it to a credential-free `video-renderer` sidecar, which renders both the 9:16 and 1:1 MP4 cuts via HyperFrames (agent-authored HTML in headless Chrome + system `ffmpeg`) and streams the bytes back. MP4s persist under `ROBOCO_VIDEO_OUTPUT_DIR` (bind-mounted in all three compose files so renders survive container recreation) — the sidecar never writes to disk.
- **The held draft is owned by the Secretary** (`confirmed_by_human=False`), the same held-artifact pattern the [X engine](/docs/optional/x-engine) uses — skipped by every dispatcher, acted on only through the CEO's approve/reject.
- **Posting is idempotent** — approving an already-posted draft is a no-op, never a double-post.
Even fully armed, a clip sits in the queue until you explicitly approve it — optionally editing the per-platform captions first.
### Rejecting with a reason re-authors the composition
Rejecting a draft always cancels it — it's never posted. But a **non-blank** rejection reason opens a **fresh authoring task** (best-effort, never fails the reject itself) that carries your feedback verbatim plus a pointer to revise the existing composition in place, rather than starting from a blank brief. A **blank** rejection is a plain cancel with no follow-up. This is the same reject-with-feedback shape the [X engine](/docs/optional/x-engine) uses for its own held drafts.
### The panel-demo kit
Compositions aren't limited to text cards. The in-repo `motion/kit/` ships a reusable, `pk-`-namespaced set of building blocks that recreate the control panel itself inside a HyperFrames composition — frame chrome, a task card, status pills with an enter/exit swap, team/priority chips, a notification toast, a character-by-character typing reveal with a blinking caret, and a cursor sprite with a click pulse. Its color tokens are lifted straight from the panel's own dark theme, so kit output reads as the real product rather than an approximation of it.
The kit ships with a reference composition, `panel-demo` (12s, both cuts): a task title types into the intake box, the card materializes, the cursor glides in and clicks, the status pill flips to completed, and a toast slides in. It's the **demo register** — a UX/UI dev authoring a video can now simulate an actual product interaction instead of defaulting to a text card. Authoring guidance for the kit lives in `motion/kit/README.md`; the project-wide composition design bar is in `motion/README.md`.
Authoring quality is held to a catalog-grade design bar: `motion/README.md` carries spacing, pacing, and anti-generic-tells rules grounded in vendored HyperFrames craft references, backed by an in-repo catalog vocabulary index the dev checks before inventing a new visual pattern. You don't author videos yourself, so none of this changes what you see in the queue — it's quality bar for the devs building the compositions.
### Bounds on origination
| Setting | Default | Meaning |
|---------|---------|---------|
| `ROBOCO_VIDEO_MAX_OPEN_POSTS` | `5` | Rolling cap on concurrently-open video tasks (authoring plus held post drafts combined); the engine opens nothing more past it. |
| `ROBOCO_VIDEO_RENDER_INTERVAL_SECONDS` | `120` | Seconds between render-loop passes (scans completed authoring tasks with an unrendered composition). |
| `ROBOCO_VIDEO_RENDER_TIMEOUT_SECONDS` | `600` | Deadline for one render pass on the sidecar. |
| `ROBOCO_VIDEO_REQUEST_TIMEOUT_SECONDS` | `30` | Per-request timeout for outbound video-engine HTTP calls. |
## Required configuration
| Setting | Where it lives | Notes |
|---------|----------------|-------|
| `ROBOCO_VIDEO_ENGINE_ENABLED` | Environment / panel flag | Master switch. Off ⇒ no authoring task is ever opened. |
| `ROBOCO_VIDEO_ON_RELEASE` | Environment / panel flag | Sub-switch for the release-publish trigger. |
| `ROBOCO_VIDEO_ON_SPOTLIGHT` | Environment / panel flag | Sub-switch for the feature-spotlight trigger. |
| `ROBOCO_VIDEO_RENDERER_BASE_URL` | Environment (default `http://roboco-video-renderer:3001`) | Base URL of the `video-renderer` sidecar. |
| `ROBOCO_VIDEO_OUTPUT_DIR` | Environment (default `/data/video-renders`) | Where rendered MP4s are written. Bind-mounted in all three compose files. |
| TikTok OAuth2 secrets | **Settings → TikTok Credentials** card in the panel | Fernet-encrypted at rest, alongside the existing X credentials. Write-only. |
TikTok's OAuth2 secrets are never read from environment variables — they live only in the database, encrypted the same way a project's git token is. Set them from **Settings → TikTok Credentials**. X credentials (if you want videos posted to X) are the same four OAuth 1.0a secrets the [X engine](/docs/optional/x-engine) already uses.
## Enable it
**Settings → Feature Flags** carries **"Video engine"**, plus the two sub-switches **"Video on release"** and **"Video on spotlight"**. Then scroll to the **TikTok Credentials** card (same Settings page) and enter the OAuth2 secrets; the X engine's existing credentials card covers the X side.
The feature flags persist in the settings store and apply on the **next backend restart**. Credentials take effect as soon as they're saved — no restart needed for those.
```bash
ROBOCO_VIDEO_ENGINE_ENABLED=true
# ROBOCO_VIDEO_ON_RELEASE=true # optional: also draft on every release
# ROBOCO_VIDEO_ON_SPOTLIGHT=true # optional: also draft on spotlight approval
# ROBOCO_VIDEO_RENDER_INTERVAL_SECONDS=120 # default
# ROBOCO_VIDEO_RENDER_TIMEOUT_SECONDS=600 # default
# ROBOCO_VIDEO_RENDERER_BASE_URL=http://roboco-video-renderer:3001 # default
# ROBOCO_VIDEO_OUTPUT_DIR=/data/video-renders # default
```
The TikTok OAuth2 secrets are **not** environment variables — set them from **Settings → TikTok Credentials** in the panel.
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- A release publish, an approved spotlight that requests a video, or the CEO's on-demand `POST /api/video/request` opens an assigned UX/UI authoring task that builds a `motion/` composition.
- Once the authoring task completes, the render loop renders the 9:16 and 1:1 MP4 cuts and materializes a held `video_post` draft, visible in the **Video Post Queue** on the [Social](/docs/panel/social) page with a 9:16/1:1 cut switch.
- The CEO previews (an authenticated blob fetch that carries `X-Agent-ID`/`X-Agent-Role`), edits, approves, or rejects each draft. Approving posts the selected cut to X (native video) and/or TikTok (inbox upload) under a heartbeat-renewed lock.
- With no TikTok credentials stored, the TikTok leg is a no-op; with no X credentials, the X leg is a no-op; with no sidecar reachable, the render loop degrades gracefully. Every unconfigured leg is a no-op, never a crash.
## Next
→ [X (Twitter) engine](/docs/optional/x-engine) — the held-draft marketing engine this mirrors and extends. → [Social](/docs/panel/social) — where the Video Post Queue and on-demand request live. → [Settings](/docs/panel/settings) — where the TikTok credentials card lives. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/board-programs
Title: Board Programs
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
Before Board Programs, the Board's only standing work was the intake-draft pair review, plus two one-off engines — the [roadmap cycle](/docs/optional/roadmap-engine) and the [X feature spotlight](/docs/optional/x-engine#feature-spotlight-an-agent-that-finds-its-own-good-stories) — that shared no machinery and had no memory of their own past cycles. **Board Programs** is a single generic registry that both of those now ride, plus twelve new standing assignments for the Product Owner, Head of Marketing, and Auditor. Every program follows the same shape — a role explores, proposes once, and everything it proposes sits **held** until you decide — so a new program is a registry entry and a prompt, not a bespoke engine file.
Registered programs are individually **off by default**. There is no single master switch: each one has its own toggle on the **Business → Programs** tab.
## What it does
Every program — old or new — runs the same six-stage lifecycle:
cron tick, a metric crossing
a threshold, or an event] --> B[EXPLORE
solo one-shot spawn,
read-only]
B --> C["PROPOSE
one structured verb call,
e.g. propose_bug_hunt()"]
C --> D[Held in a
per-program CEO queue]
D --> E{DECIDE}
E -->|approve| F[MATERIALIZE
backlog task, X draft,
report, or playbook]
E -->|reject| G[Recorded with your reason]
F --> H[LEARN
outcome feeds the
next cycle's prompt]
G --> H`} />
- **TRIGGER.** Three kinds. **Cron** programs open a cycle once their interval has elapsed since the last one closed — the shortest registered cadence sets the orchestrator's wake-up floor, capped at once an hour, so a newly-armed program is picked up within the hour without a restart. **Metric** programs can also fire off-schedule when a predicate crosses a threshold (today, only Pest Control: a 7-day rework-rate spike). **Event** programs (Coroner, War Room, Dogfood) never fire off the cron loop at all — they open only when their specific hook runs (a bounce, a cancel, a budget-block, a release publish) or you hit **Run now**.
- **EXPLORE.** A real, one-shot cloud-LLM spawn of the program's role — Product Owner, Head of Marketing, or Auditor — with read tools scoped to what that program needs (project git/docs read, web search, KB/journal search, or Playwright for Dogfood). The engine itself never authors a word of content.
- **PROPOSE.** The agent calls the program's one structured verb **exactly once** — `propose_bug_hunt`, `propose_market_brief`, `propose_postmortem`, and so on. One call, one cycle; the agent can't trickle out items one at a time.
- **DECIDE.** What you see and where depends on the program's **materializer** — see the queue column in the catalog below. Some programs (Pest Control, Spackle, Scales, Mirror, Dogfood, Roadmap) hold several individually-decidable items in one cycle; others (Periscope, Sentinel, Coroner, Megaphone) complete the moment the agent proposes, because a report or a single postmortem has nothing left to decide item-by-item.
- **MATERIALIZE.** Approval is the only path to a real effect: a `BACKLOG` task, a live task getting reprioritized or cancelled, a drafted X post, a playbook draft, or nothing at all for a report (you just read it). Nothing here starts on its own — a materialized backlog task still needs ordinary PM activation.
- **LEARN — the genuinely new stage.** A `board_program_cycles` row per cycle (program key, items proposed/approved/rejected, your rejection reasons) feeds straight into the *next* cycle's exploration prompt: "last cycle you proposed X, it was rejected because Y." The old roadmap and spotlight engines had no memory of their own history; every program gets one now.
Each program dedupes against its own non-terminal exploration task — a new cycle never opens while the last one is still waiting on either the agent's proposal or your decisions. Coroner calls this an "autopsy" instead of a "cycle," but the same one-at-a-time rule applies.
## The program catalog
Fourteen programs are registered: the two migrated engines (Roadmap and the X feature spotlight, unchanged in behavior) plus twelve new ones, four per Board role.
### Product Owner
| Program | Trigger | What it explores | Where you decide |
|---------|---------|-------------------|-------------------|
| **Pest Control** | Weekly, or off-schedule when the trailing 7-day rework rate crosses `pest_rework_threshold` (default 0.3) | The findings ledger, `revision_count` hotspots, and QA-note flake patterns in one opted-in project at a time | Command Center → **Pest Control Review Queue** — approve/reject each bug task individually |
| **Spackle** | Biweekly | Gaps between what's shipped and what's surfaced — API routes with no panel page, armed flags with no docs, dead-end tabs — in one opted-in project at a time | Command Center → **Spackle Review Queue** — approve/reject each gap-fill task individually |
| **Scales** | Monthly | The live backlog against your charter, across every project | Command Center → **Scales Review Queue** — approving **reprioritizes** or **cancels** a real, already-existing task; rejecting leaves it untouched |
| **Dogfood** | A release publishing, or **Run now** — never the cron loop | Walks one opted-in project's live surfaces (the panel, the docs site) with browser tools, the way a user would | Command Center → **Dogfood Review Queue** — approve/reject each friction-fix task individually |
### Head of Marketing
| Program | Trigger | What it explores | Where you decide |
|---------|---------|-------------------|-------------------|
| **Periscope** | Weekly | Competitor and adjacent-tool web research plus the knowledge base | Business → **Market Briefs** tab — a report you read; there's no per-item decision |
| **Megaphone** | Every 3 days | The org's own shipped-task and changelog history, for editorial angles beyond release posts | Social → **X Post Queue** — edit / approve & post / reject, same as any other X draft |
| **Mirror** | Quarterly | One opted-in project's messaging surfaces (README, docs site) against the charter and shipped reality | Command Center → **Mirror Review Queue** — approve/reject each docs task individually |
| **Barfly** | Every 2 days | X search for conversations where RoboCo is relevant but unmentioned | Social → **X Post Queue** — same review as a mention reply |
| **War Room** | A release publishing, or **Run now** — never the cron loop | Plans a release campaign: an ordered set of draft posts | Social → **X Post Queue** — each draft carries a recommended `publish_after` time as **guidance only**; you approve every draft individually and nothing auto-schedules |
### Auditor
| Program | Trigger | What it explores | Where you decide |
|---------|---------|-------------------|-------------------|
| **Coroner** | Event only — a task's 3rd bounce into `needs_revision`, a cancel after work started, or a budget-block | One incident task: what failed, which stage, the root cause | Business → **Programs** tab → **Postmortems** card — a report with one proposed process change; read-only, no per-item decision |
| **Librarian** | Biweekly | Journals and learnings, mined for a recurring pattern worth a playbook | Knowledge Base's playbook review queue — it drafts 1-3 real playbook drafts directly, for the normal curate/approve flow |
| **Sentinel** | Weekly | Org-wide drift: waiver accumulation, conventions-violation hotspots, docs/map staleness, budget anomalies | Business → **Quality Reports** tab — a report you read; there's no per-item decision |
Coroner's registry entry carries a trigger kind for bookkeeping consistency, but a cycle only ever opens through its incident hooks — **Run now** on the Programs tab is a no-op for it specifically, unlike Dogfood and War Room (also event-triggered, but with a real off-schedule path).
## Per-project participation
A program's **scope** decides how a project opts in, and the polarity is deliberately different in each direction:
- **Project-scoped** programs (**Pest Control, Spackle, Mirror, Dogfood**) read one repository directly, so they need an **affirmative opt-in** before they'll even open a cycle. A project with nothing listed participates in none of them. Each cycle then rotates to whichever opted-in project has gone longest without a turn.
- **Org-scoped** programs (everything else, including Roadmap) run company-wide regardless of any project's settings — there's nothing to opt into. Roadmap's items, though, still each target a specific project, so a project can be **excluded as an output target** with a `!`-prefixed entry (e.g. `!roadmap`) without touching whether the program itself runs.
Both directions live in the same field, `projects.board_programs` — a plain key opts a project into a project-scoped program's cycles; a `!`-prefixed key opts a project out of an org-scoped program's output. It's set from the project's own **Settings → Budget & Ops** card, next to the CI-watch and video-engine opt-ins: a **"participates in"** checkbox list for the project-scoped programs, and an **"excluded from"** checkbox list for the org-scoped ones.
The `!key` exclusion form validates for every org-scoped program, but as of this release only Roadmap actually checks it when proposing or materializing an item. The other org-scoped programs either resolve against a fixed anchor project rather than an arbitrary target (Periscope, Sentinel, Scales, Librarian) or don't produce project-targeted artifacts at all (Megaphone, Barfly, War Room, Coroner post to the X queue or write a report) — so excluding a project from those has no effect yet.
## Enable it
**Business → Programs** lists every registered program as a card: its role, trigger kind, scope, an **Enabled** switch, a **Run now** button (disabled while a cycle is already open, and — for Coroner — always inert), and a one-line summary of its last cycle. Flipping a switch persists immediately; there's no separate save step and, unlike most feature flags on this site, **no backend restart is needed** — the change is visible to the loop on its next tick, at most an hour later.
Project participation is set per project, on that project's own **Settings → Budget & Ops** card — see [Per-project participation](#per-project-participation) above.
`ROBOCO_ROADMAP_ENGINE_ENABLED` and `ROBOCO_X_ENGINE_ENABLED` + `ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED` still work — they're what a program's switch on the Programs tab falls back to until you've touched it there. Once you flip the panel switch for `roadmap` or `x_feature`, that per-program value wins regardless of the environment setting.
```bash
# No master flag — every program is armed individually from
# Business -> Programs. These are the only environment-level knobs:
# The rework-rate accelerator on top of Pest Control's weekly cron
ROBOCO_PEST_REWORK_THRESHOLD=0.3 # default; 7-day rework rate, 0-1
# Roadmap and the X feature spotlight still read their pre-registry flags
# as the DEFAULT for their Programs-tab switch (see the panel tab for how
# the settings-store value takes over once you've touched the switch):
ROBOCO_ROADMAP_ENGINE_ENABLED=true
ROBOCO_X_ENGINE_ENABLED=true
ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED=true
```
See the [environment reference](/docs/deploy/env-reference) for the full flag list, including Roadmap's and the X engine's own cadence/cap variables.
## Guardrails
- **Every artifact is held.** You are the only path to materialization — nothing a program proposes starts work, posts to X, or changes a task on its own.
- **Nothing auto-starts, auto-posts, or auto-merges**, no exceptions, including War Room's `publish_after` timestamps (guidance only) and Librarian's drafted playbooks (still go through the normal curate/approve flow).
- **Dispatchers skip every program's exploration task** the same way they already skip the old Roadmap and X-spotlight tasks — a program's held task never gets picked up as ordinary delivery work.
- **Caps everywhere.** One open cycle per program, per-cycle item caps baked into the registry, and the exploration spawn itself only ever happens on a due cron tick, a qualifying event, or your own **Run now** — never more than one at a time.
- **External text is screened.** Barfly's X search results cross the same `injection_guard.screen_external_text` gate that X mentions and vault notes already go through, before anything reaches a prompt.
- **The Auditor stays silent to agents.** Coroner, Librarian, and Sentinel report to you; none of them message the fleet — that boundary is unchanged.
## What changes when it's on
- Arming a program on the Business → Programs tab starts its cron/metric cycle (or, for the three event-triggered ones, arms its hooks) within the hour, no restart required.
- Opting a project into a project-scoped program's `board_programs` list makes it eligible for that program's rotation; opting a project out of an org-scoped program's `!key` list (Roadmap only, today) keeps it out of that program's proposed items.
- New CEO decision surfaces appear only when there's something to decide: five new Command Center review queues (Pest Control, Spackle, Scales, Mirror, Dogfood), two new read-only Business tabs (Market Briefs, Quality Reports), a Postmortems card on Business → Programs, and three programs feeding drafts into the X Post Queue you already know from the [X engine](/docs/optional/x-engine).
## Next
→ [Board roadmap engine](/docs/optional/roadmap-engine) and [X (Twitter) engine](/docs/optional/x-engine) — the two programs that migrated onto this registry, still documented on their own pages for their program-specific settings. → [Business](/docs/panel/business) for the Programs tab and the two new report tabs. → [Command Center](/docs/panel/command-center) for the five new review queues. → [Workstation](/docs/panel/projects-and-products) for the per-project participation checkboxes. → [Org & roles](/docs/company/org-and-roles) for what the Product Owner, Head of Marketing, and Auditor do day to day. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/roadmap-engine
Title: Board Roadmap Engine
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
import { Tabs } from "@/components/docs/tabs";
import { TabsContent } from "@/components/docs/tabs";
The Board roadmap engine gives your Product Owner a standing weekly assignment: look at the company's projects, the charter, recent releases, and the metrics, and come back with a themed set of roadmap ideas. You then approve or reject each idea individually — approved ones land in the backlog exactly like any task you'd written yourself.
It's **off by default**. The engine never authors content itself — it only opens the standing assignment; the Product Owner does the actual proposing.
Roadmap was the first of what's now fourteen [Board Programs](/docs/optional/board-programs) — a generic trigger/propose/hold/learn engine the Product Owner, Head of Marketing, and Auditor all share. Nothing on this page changed: the flag, the cadence, the queue, and the item shape are all exactly what they were. Two things are new because of the move — the flag now doubles as this program's default on the **Business → Programs** tab (flip it there instead and the panel value takes over), and each cycle's outcome (what you approved, what you rejected and why) now feeds the *next* cycle's exploration prompt, closing a loop the engine never had before.
## What it does
B{A cycle already open?}
B -->|yes| A
B -->|no| C[Open ONE held exploration
task for the Product Owner]
C --> D[Board dispatcher spawns
the Product Owner]
D --> E[PO explores projects, charter,
releases, metrics]
E --> F["propose_roadmap() — one themed
cycle, 3-7 item drafts"]
F --> G[CEO reviews each item
in the Roadmap Review Queue]
G -->|approve| H[Materialized as a
BACKLOG task]
G -->|reject| I[Recorded with a reason]
H -.-> J[Normal PM activation
starts it — not automatic]`} />
- **One open cycle at a time.** The engine dedupes on `source=board_roadmap` non-terminal tasks — a new cycle is never opened while one is still awaiting either the Product Owner's authoring or your per-item decisions.
- **The engine itself never writes a single roadmap item.** On its weekly tick (`ROBOCO_ROADMAP_INTERVAL_SECONDS`, default 604800s) it opens exactly one `PENDING`, held exploration task, assigned to the Product Owner (`Team.BOARD`, `confirmed_by_human=False` — skipped by every dispatcher until the Board's own dispatch spawns the PO for it).
- **The Product Owner does the actual work**: once spawned, they explore the company's registered projects, the charter, recent releases, and current metrics, then call `propose_roadmap()` **exactly once** with a themed cycle of `ROBOCO_ROADMAP_MIN_ITEMS_PER_CYCLE`–`ROBOCO_ROADMAP_MAX_ITEMS_PER_CYCLE` item drafts (default 3–7) — a goal statement plus that many individual items, each with a title, description, rationale, team, and acceptance criteria.
- **You approve or reject item-by-item** in the **Roadmap Review Queue** on the Command Center. Approving one item materializes it as an ordinary `BACKLOG` task (idempotent — approving an already-approved item is a no-op); rejecting records your reason, which feeds into the *next* cycle's prompt, but does not add anything to the backlog.
- **Nothing auto-starts.** An approved item is just another `BACKLOG` task — it needs the same PM activation as anything else before an agent picks it up.
The Product Owner proposes a **coherent batch** — one goal, several related items — rather than trickling in one idea at a time. That's why only one cycle is ever open: the next one doesn't start exploring until you've cleared every item in the current one (approved or rejected).
### Bounds on the cycle
| Setting | Default | Meaning |
|---------|---------|---------|
| `ROBOCO_ROADMAP_MIN_ITEMS_PER_CYCLE` | `3` | Fewest item drafts a themed cycle must propose. |
| `ROBOCO_ROADMAP_MAX_ITEMS_PER_CYCLE` | `7` | Most item drafts a themed cycle may propose. |
| `ROBOCO_ROADMAP_INTERVAL_SECONDS` | `604800` (weekly) | Seconds between exploration cycles. |
## Enable it
**Settings → Feature Flags → "Board roadmap engine"** arms the weekly cycle. There is nothing else to configure per project — the engine targets RoboCo's own registered project record.
The flag persists in the settings store and applies on the **next backend restart**.
```bash
ROBOCO_ROADMAP_ENGINE_ENABLED=true
# ROBOCO_ROADMAP_INTERVAL_SECONDS=604800 # default, weekly
# ROBOCO_ROADMAP_MIN_ITEMS_PER_CYCLE=3 # default
# ROBOCO_ROADMAP_MAX_ITEMS_PER_CYCLE=7 # default
```
See the [environment reference](/docs/deploy/env-reference) for the full flag list.
## What changes when it's on
- A weekly background tick opens one held exploration task for the Product Owner whenever no cycle is currently open.
- The **Roadmap Review Queue** appears on the Command Center as soon as the Product Owner has authored a cycle — it's hidden entirely when there's nothing to review, the same as the other CEO decision queues.
- Approving items grows the backlog; nothing starts on its own.
## Next
→ [Board Programs](/docs/optional/board-programs) — the registry this program rides, and its thirteen siblings. → [X (Twitter) engine](/docs/optional/x-engine) — the other Board-facing held-queue capability. → [Org & roles](/docs/company/org-and-roles) — what the Product Owner does day to day. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/obsidian-vault
Title: Obsidian Vault
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
The Obsidian vault is the company's human-readable memory palace: a rebuildable projection of tasks, journals, and agent-to-agent conversations into a folder of wikilinked markdown notes you browse in [Obsidian](https://obsidian.md). It's a read surface on top of the database, not a second source of truth — the vault can be deleted and rebuilt from Postgres at any time.
It's **off by default** and isn't on the panel's Feature Flags card — like [DB network isolation](/docs/optional/db-network-isolation) and [Cloud auth](/docs/optional/cloud-auth), it's an environment/compose setting, not a runtime toggle.
Arming the vault means setting environment variables and restarting the backend. There's nothing to flip in Settings → Feature Flags for it.
## What it does
task status transition] --> B{ROBOCO_OBSIDIAN_VAULT_ENABLED?}
B -->|off| Z[no-op]
B -->|on| C[Best-effort markdown write
into ROBOCO_VAULT_PATH]
C --> D[Obsidian: Dataview boards,
Kanban, graph view]`} />
Every journal entry, every A2A message, and every task status change materializes as a markdown note with YAML frontmatter, written **best-effort** — a vault write failure never blocks the verb that triggered it. Private journal entries (`is_private`) are excluded from the projection.
A task's note isn't a projection you wait for: `TaskService.create` materializes it the moment the task is created, so a board or Kanban view built on the vault shows live work from creation, not just once it's curated or the vault is rebuilt.
## Vault layout
Notes land under a fixed `RoboCo/` tree:
```text
RoboCo/
Tasks// ().md
Journals// ().md
A2A/ ().md
Agents/.md
Archive//Tasks// ().md
Reports/.md
Notes/
_meta/
```
Every note carries a stable `aliases: []` in its frontmatter, and cross-links are written as `[[|]]` rather than by filename. That means renaming a task's title updates the note's own title line without breaking a single link that points at it elsewhere in the vault — archival and reorganization are free.
The vault ships a pre-configured `.obsidian/` folder (Dataview queries, Kanban board views, graph color groups by team/status, and a couple of native [Bases](https://help.obsidian.md/bases) views — a Task Board and a Reports index) and a `RoboCo/_meta/` folder with starter dashboards, materialized the first time the vault is enabled and never overwritten if you've already customized them.
### The Auditor's narrative
Deterministic sections (status, links, metadata) always exist on a task note. A free-text **`## Narrative`** section — what happened, the decisions made, the rework story — is written by the Auditor, who gets a dedicated `curate_vault` verb and is spawned once a root task completes specifically to fill it in. That narrative is preserved across a full `rebuild` (below); nothing else about the Auditor's role changes.
## The drift janitor: repair, archival, and a weekly report
A projection built from best-effort event seams can miss a write — a container restart mid-verb, a rare exception swallowed at the write site. Once the vault's been on for a while, an hourly-ticked janitor closes that gap in one sweep, gated on the master flag alone (the sweep and the archival pass), plus a second flag for the report:
B{Sweep due?
24h since last}
B -->|no| E
B -->|yes| C[Re-project changed tasks
+ random-sample drift check]
C --> D[Archive terminal tasks
older than the cutoff]
D --> E{Report due?
new ISO week}
E -->|yes| F[Write RoboCo/Reports/<week>.md
+ notify the CEO]
E -->|no| G[nothing]`} />
- **Re-projection is capped, not unbounded.** Each sweep re-projects up to 200 changed tasks and drift-checks a random sample of 20 more, so a long-downtime backlog drains across several hourly ticks instead of one large burst; a state file (`RoboCo/_meta/.janitor_state.json`) tracks how far the sweep got, so a restart resumes rather than restarts the count.
- **One bad row never wedges the sweep.** Each task is re-projected in isolation — a failure is logged and skipped, and the task re-qualifies next time it changes (or via the sample check).
- **Archival** moves a completed or cancelled task's note into `RoboCo/Archive//Tasks//` once it's been terminal for `ROBOCO_VAULT_ARCHIVE_DAYS` (default 30; `0` disables archival). Because links are alias-based, an archived task's note keeps every wikilink pointing at it working — nothing to fix up. The shipped Dataview queries and graph groups already account for the `Archive/` tree.
- **The weekly report** lands in `RoboCo/Reports/.md` once per elapsed ISO week — a deterministic summary (tasks completed/created, completion rate, average cycle time, rework rate and cost, token/dollar spend, plus the per-stage and bottleneck breakdown) assembled from `MetricsService`/`UsageService`, no LLM involved — and notifies you. It's on by default (`ROBOCO_VAULT_REPORT_ENABLED`) whenever the vault itself is.
## Making your own notes fleet-searchable (KB ingest)
Tasks, journals, and A2A threads are already retrievable by the fleet straight from Postgres — the vault projection is a read surface on data the RAG layer already indexes. Your own writing isn't: notes you drop into `RoboCo/Notes/` (or whatever folders `ROBOCO_VAULT_KB_DIRS` names) are pure filesystem content the database has never seen. KB ingest closes that gap, embedding them into a dedicated `vault_notes` RAG index so `roboco_kb_search`, the mentor's default search domain, and claim-time briefings can all surface something you personally wrote.
It's off by default (`ROBOCO_VAULT_KB_ENABLED`, needs the master vault flag too) and scoped tightly:
- **Human folders only, never the projection dirs.** `ROBOCO_VAULT_KB_DIRS` (default `RoboCo/Notes`) is validated at startup against every reserved projection folder (`Tasks/`, `Journals/`, `A2A/`, `Agents/`, `Archive/`, `Reports/`, `_meta/`, `.obsidian/`, and the intake inbox) plus path-traversal — it can never be pointed at data that's already a first-class DB-backed index, or escape the vault outright.
- **Injection-screened as a hard gate, not a soft flag.** Every note's body runs through the same untrusted-content screen the `#roboco` intake loop and the X-mentions engine use, but here a flagged note is quarantined outright — skipped, logged, and marked with a callout explaining why — rather than passed through neutralized. An indexed chunk that later gets retrieved into an agent's context is a stricter bar than a note that just becomes a task draft.
- **Bounded and incremental.** A scan cycle (`ROBOCO_VAULT_KB_INTERVAL_SECONDS`, default 900s) re-embeds only changed notes (content-hash deduped) and ingests at most 50 per cycle; a deleted note is de-indexed on the next scan.
To actually write into `RoboCo/Notes/` from your Mac, you need the vault synced locally first — see [Syncing the Vault to Your Mac](/docs/optional/obsidian-vault-sync).
## The rebuild / relocate CLI
```bash
python -m roboco.vault rebuild
python -m roboco.vault relocate /new/path
```
Both subcommands refuse to run unless `ROBOCO_OBSIDIAN_VAULT_ENABLED` is on.
- **`rebuild`** re-projects every live agent, task, journal entry, and A2A thread from the database into the vault from scratch, and (re-)materializes the shipped `.obsidian/` config and `_meta/` dashboards for anything not already present. A task's Auditor-authored `## Narrative` is read back from the existing note first, so a rebuild never erases it.
- **`relocate `** moves the vault tree. If `new-path` doesn't exist yet, the whole tree moves there. If it **does** exist — your own personal vault, say — only the `RoboCo/` subtree is grafted into it, plus whatever shipped assets are missing; your personal vault's own `.obsidian/` config is never touched. Because links are alias-based, nothing inside the moved notes needs rewriting.
## The `#roboco` inbox → board-review loop
A second, independently-gated capability turns vault notes into task drafts. Write a note anywhere in the watched inbox folder, tag it `#roboco` (in frontmatter or inline in the body), and it becomes one held task the Product Owner and Head of Marketing review — exactly the same **board-review** path a chat-confirmed "Board review & Start" draft takes.
in RoboCo/Inbox/"] --> B[Watcher scans every
ROBOCO_VAULT_INTAKE_INTERVAL_SECONDS]
B --> C[Local-model extraction
+ injection screening]
C --> D[ONE held PENDING draft
source=vault_note, team=Board]
D --> E[Board review:
Product Owner + Head of Marketing]
E --> F[CEO notification]
F -->|approve_and_start| G[Main PM — delivery]
F -->|no action| H[stays parked]`} />
- **Never starts work on its own.** The draft is PENDING, Product-Owner-assigned, `team=board` — routed to the same two-reviewer board review as any other board-originated task, ending in a notification to you. Nothing enters delivery until your explicit **Approve & Start**.
- **Local model only.** Extraction (title, description, acceptance-criterion candidates) runs on the local LLM, with a deterministic fallback (first heading, raw body, or checkbox lines) if that fails — never a cloud LLM in the hot path.
- **Screened before it reaches a prompt.** The note body passes through the same untrusted-content screen the X engine applies to tweet text before it's used for extraction or shown to the board/CEO.
- **Deduplicated, not reprocessed.** A `(path, content hash)` ledger means an unchanged note is scanned once; editing it afterward makes it eligible again.
- **Bounded.** `ROBOCO_VAULT_INTAKE_MAX_PER_CYCLE` caps how many drafts one scan can originate; `ROBOCO_VAULT_INTAKE_MAX_OPEN_DRAFTS` caps how many can sit open at once.
## Flags
| Variable | Default | Purpose |
|----------|---------|---------|
| `ROBOCO_OBSIDIAN_VAULT_ENABLED` | `false` | Master switch. Off: no note is ever written, `rebuild`/`relocate` refuse to run, and every event seam is a no-op. |
| `ROBOCO_VAULT_PATH` | `/data/vault` | Root directory the vault materializes into (bind-mounted from `${ROBOCO_DATA_DIR}/vault` on the host in both tracked compose files). Only consulted when the master flag is on. |
| `ROBOCO_VAULT_INTAKE_ENABLED` | `false` | Second switch for the `#roboco` inbox watcher. **Both** this and the master flag must be on — either off leaves the watcher inert. |
| `ROBOCO_VAULT_INTAKE_INTERVAL_SECONDS` | `300` | Seconds between inbox scan cycles. |
| `ROBOCO_VAULT_INTAKE_DIR` | `RoboCo/Inbox` | Vault-relative folder scanned for `#roboco`-tagged notes (non-recursive). |
| `ROBOCO_VAULT_INTAKE_MAX_PER_CYCLE` | `3` | Max held drafts one scan cycle may originate. |
| `ROBOCO_VAULT_INTAKE_MAX_OPEN_DRAFTS` | `10` | Rolling cap on concurrently-open held vault-note drafts. |
| `ROBOCO_VAULT_ARCHIVE_DAYS` | `30` | Age (since going terminal) past which a completed/cancelled task's note moves to `Archive//`. `0` disables archival. |
| `ROBOCO_VAULT_REPORT_ENABLED` | `true` | Weekly `Reports/.md` org-report note + CEO notification. Needs the master flag too. |
| `ROBOCO_VAULT_KB_ENABLED` | `false` | Master switch for KB ingest. Off: nothing is ever embedded and the `vault_notes` index stays empty. |
| `ROBOCO_VAULT_KB_DIRS` | `RoboCo/Notes` | CSV of vault-relative folders scanned for KB ingest. Validated at startup against the reserved projection dirs and path traversal. |
| `ROBOCO_VAULT_KB_INTERVAL_SECONDS` | `900` | Seconds between KB-ingest scan cycles. |
## Getting the vault onto your own machine
The vault materializes on the server (the NAS, in a production deploy) under `${ROBOCO_DATA_DIR}/vault` — not on your laptop. To actually browse it in the Obsidian app on your Mac, sync that folder down. See [Syncing the Vault to Your Mac](/docs/optional/obsidian-vault-sync) for the full step-by-step (Syncthing, SMB, or Obsidian Sync).
The projection is one-directional (database → vault) except for the `#roboco` inbox loop above. Editing a `Tasks/`, `Journals/`, or `A2A/` note directly in Obsidian doesn't write back to RoboCo — the next event or `rebuild` overwrites it. Only notes in the intake folder are read back in.
## Next
→ [Syncing the Vault to Your Mac](/docs/optional/obsidian-vault-sync) — the full runbook for reading it locally. → [MegaTask](/docs/company/megatask) and [Org & roles](/docs/company/org-and-roles) — the Auditor's other duties. → back to [Optional subsystems](/docs/optional).
================================
URL: https://docs.roboco.tech/docs/optional/obsidian-vault-sync
Title: Syncing the Vault to Your Mac
================================
import { Admonition } from "@/components/docs/admonition";
The [Obsidian vault](/docs/optional/obsidian-vault) materializes on the server — the NAS, in a production deploy — under `ROBOCO_VAULT_PATH`. Obsidian itself is a desktop/mobile app, so to actually browse the vault you need that folder on the machine running it. This page is the full runbook for getting it onto your Mac, three ways, best first.
## Option 1: Syncthing (recommended)
Real-time, two-way, no cloud hop, free. This is the right choice if you ever edit notes yourself — most importantly, dropping a note into `RoboCo/Inbox/` for the `#roboco` intake loop.
1. **Install Syncthing on the NAS.** Most NAS OSes either ship it in an app center or run it as a small Docker container — either works, it just needs to see the vault's root folder (e.g. `/volume1/roboco/data/vault`) on disk.
2. **Install Syncthing on the Mac.**
```bash
brew install syncthing
brew services start syncthing
```
3. **Pair the two devices.** Open the Syncthing web UI on each side (the NAS's container exposes one on its own port; the Mac's is at `http://localhost:8384`) and add the other as a remote device using its device ID — each UI shows its own ID and has an "Add Remote Device" flow that takes the other's.
4. **Share the vault folder from the NAS.** In the NAS's Syncthing UI, share the vault root (the folder containing `RoboCo/` and `.obsidian/`) with the Mac device.
5. **Accept it on the Mac.** Syncthing prompts you to accept the incoming share and pick a local destination folder. Point Obsidian's "Open folder as vault" at that folder.
6. **Set both sides to Send & Receive**, not Send Only / Receive Only. One-way sync means edits you make on the Mac (or the `#roboco` inbox loop reading them back) never reach the NAS.
7. **Ignore Obsidian's own transient window state.** Add this to `.stignore` on both devices:
```text
.obsidian/workspace.json
```
Without it, the NAS-side process and your Mac each rewrite `workspace.json` as they open/close panes, and Syncthing ping-pongs the two versions back and forth — harmless, but noisy. Everything else under `.obsidian/` (themes, community-plugin settings, the shipped Bases/Dataview/Kanban config) is fine to sync as-is.
Run `python -m roboco.vault relocate ` once (server-side) to graft the `RoboCo/` subtree straight into your existing personal vault instead of syncing a second, separate one — your vault's own `.obsidian/` config is never touched by the relocate. Then sync that one vault as above.
Conflicts are non-destructive: Syncthing writes a `.sync-conflict--.md` copy instead of overwriting, and because every note carries a stable `aliases: []`, a rename or an occasional conflict copy never breaks a wikilink pointing at it.
## Option 2: SMB mount (simpler, read-mostly)
Good enough if you mostly read the vault on your Mac and rarely write to it.
1. Share the vault's **parent** folder over SMB from the NAS (built into most NAS OSes' file-sharing settings).
2. On the Mac: Finder → Go → Connect to Server → `smb:///`.
3. Point Obsidian's "Open folder as vault" at the mounted share.
- **Never let iCloud Drive also sync the same folder.** iCloud and an SMB mount both trying to reconcile file versions on the same directory is a known way to corrupt an Obsidian vault's index. Keep a network-mounted vault out of `~/Library/Mobile Documents` entirely.
- **Search and the graph view are slower** over SMB than over local disk or a Syncthing-synced copy, since every read crosses the network.
- **No offline access.** NAS down or Mac off the LAN/VPN means the vault is simply unreachable — there's no local cache to fall back on.
- A network hiccup mid-write can leave `.obsidian/workspace.json` in a bad state (harmless — delete it, Obsidian regenerates it on next open) or, rarely, interrupt a note save.
## Option 3: Obsidian Sync (paid)
Obsidian's own end-to-end-encrypted sync service, sold as a subscription add-on to the app. No NAS-side install at all — you'd instead treat the NAS folder as one synced vault among Obsidian Sync's own device list. Worth it if you want the vault on more devices (phone, iPad) without standing up any extra infrastructure yourself. If it's just a Mac you care about, Syncthing gets you the same result for free with one extra install.
## Next
→ [Obsidian Vault](/docs/optional/obsidian-vault) — what the vault is and how it's populated. → [Deployment](/docs/deploy/deployment) — where `ROBOCO_VAULT_PATH` and `ROBOCO_DATA_DIR` live in the compose layout.
================================
URL: https://docs.roboco.tech/docs/api
Title: API Reference
================================
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { FileText } from "lucide-react";
RoboCo is API-first: the control panel is just a client of the same REST and WebSocket surface you can drive yourself. This section is for integrators and the curious; the live, always-current schema is at **`/docs`** (Swagger UI) and **`/redoc`** on the orchestrator.
The /api domain routes, the agent gateway verbs, the error envelope, and where to find the live OpenAPI.
The /ws live streams the panel consumes — per-resource feeds and the operator system stream.
Header-trust mode versus secure token mode, and how the panel stays authenticated.
================================
URL: https://docs.roboco.tech/docs/api/rest-api
Title: REST API
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo's backend is a single FastAPI application (`roboco/api/app.py`, one `create_app()` factory). It mounts every domain router under the `/api` prefix, the agent-gateway intent verbs under `/api/v1`, and the live WebSocket streams under `/ws`. Everything is fronted by nginx on `localhost:3000`, so the panel and any integrator use relative URLs (`/api/...`, `/ws/...`) against one origin with no CORS to configure.
The fastest way to see the full, current REST surface — every path, request body, and response schema — is the interactive docs the app serves itself:
- **Swagger UI** → [`http://localhost:3000/docs`](http://localhost:3000/docs)
- **ReDoc** → [`http://localhost:3000/redoc`](http://localhost:3000/redoc)
This page is a map of *where things live*; `/docs` is the authoritative reference for *exactly how to call them*.
## The two prefixes
There are two distinct API surfaces, and the prefix tells you which one you're on:
| Prefix | Audience | What it is |
|--------|----------|------------|
| `/api/*` | You / the panel / integrators | The domain REST surface — tasks, agents, projects, git, usage, settings, and the rest. This is what the control panel calls. |
| `/api/v1/flow/{role}/{verb}` and `/api/v1/do` | AI agents only | The [agent gateway](/docs/company/agent-gateway). Agents never call the domain routes above — they POST intent verbs here through their MCP servers, and the server-side Choreographer enforces state, locks, and evidence. |
Health and readiness probes sit at the root, not under `/api`: `GET /health` and `GET /ready`.
## Domain route groups (`/api/*`)
Every router is mounted under `/api`. The groups an operator or integrator hits:
| Route group | Prefix | Purpose |
|-------------|--------|---------|
| Tasks | `/api/tasks` | The largest router: full task CRUD and lifecycle transitions (claim, submit, pass/fail, complete, escalate). The CEO god-mode override (`PATCH /api/tasks/{id}` with `X-Agent-Role: ceo`) lives here, alongside two read-only panel feeds: `GET /api/tasks/{id}/findings` (the revision-findings ledger, newest round first, plus per-origin status counts) and `GET /api/tasks/{id}/collision-map` (the task's declared file/migration/shared surface plus overlapping same-parent siblings), and `POST /api/tasks/{id}/assign-review-pm` (assigns a review PM to a task). |
| Kanban | `/api/kanban` | Board view of tasks grouped by state. |
| Agents | `/api/agents` | Agent roster, roles, teams, current state. |
| Work sessions | `/api/work-sessions` | Git session records — branch, commits, files, PR. |
| Projects | `/api/projects` | Repository config, CI/quality commands, git-token management. |
| Products | `/api/products` | Product entities + CEO approve-and-start / cell-routing. |
| Board programs | `/api/board-programs` | Board program management, including `GET /api/board-programs/{key}/cycles` (lists the cycle history for a board program by its key). |
| Notifications | `/api/notifications` | Formal ack-required notifications. |
| Telegram | `/api/telegram` | CEO-only, write-only bridge credentials (bot token + chat id) for the Telegram notifications relay — `GET /api/telegram/credentials` reports whether they're set, `POST` sets or clears both together. The relay itself is a server-side fan-out from the same producers that raise in-app notifications; nothing else lives under this prefix. |
| Stream | `/api/stream` | Agent output stream access. |
| Journals | `/api/journals` | Agent journals and entries. |
| Optimal | `/api/optimal` | RAG queries (in-house pgvector engine). |
| Git | `/api/git` | Git operations surfaced for the panel, including `GET /api/git/file` — a branch-tip file read (via `git show`, no workspace mount needed) sliced to a window around a line or an explicit range, capped at 2000 lines with a `truncated` flag — and `POST /api/git/branches/cleanup` (PM/CEO only), a capped, cursor-resumable sweep that deletes completed/cancelled tasks' remote + local branches, never touching the default branch or an environment-ladder rung. `GET /api/git/file` backs the inline code snippets on the task detail page's Findings tab. |
| Providers | `/api/providers` | Model-provider routing config. |
| Orchestrator | `/api/orchestrator` | Agent-runtime control (spawn/stop, dispatcher state). |
| Dashboard | `/api/dashboard` | Aggregated dashboard data, including `GET /api/dashboard/stalled-tasks` (stuck-task feed), `GET /api/dashboard/metrics/spawn-waste` (spawn-waste metrics), and `GET /api/dashboard/metrics/provenance` (provenance metrics). |
| Usage | `/api/usage` | Token/cost analytics (`GET /api/usage/summary?period=24h\|7d\|30d`). |
| System | `/api/system` | Rate-limit introspection (`GET /api/system/rate-limits`). |
| Settings | `/api/settings` | App settings, including feature-flag persistence. |
| Maintenance pause | `/api/maintenance-pause` | CEO-only operator pause surface: `GET /api/maintenance-pause` reads all pause states, `POST /api/maintenance-pause/{scope}` pauses a scope (drain or kill mode), `DELETE /api/maintenance-pause/{scope}` resumes it. See [Autonomous maintenance](/docs/optional/autonomous-maintenance). |
| Company goals | `/api/company-goals` | The company charter. |
| Cockpit | `/api/cockpit` | CEO read-only business summary. |
| Release | `/api/release` | CEO-only gated release-manager surface: `GET /proposal` reads the single held proposal (404 when none), `POST /proposal/approve` dispatches the fail-closed executor and `POST /proposal/reject` records required changes. |
| Research | `/api/research` | Web-research subsystem (flag-gated). |
| Pitches | `/api/pitches` | Pitch provisioning (flag-gated). |
| Secretary | `/api/secretary` | Secretary chief-of-staff + its live-chat bridge. |
| Prompter | `/api/prompter` | Intake interviewer live chat (SSE relay). |
| Docs | `/api/docs` | Project documentation file management. |
| A2A | `/api/a2a` | Agent-to-agent messaging plumbing. |
`GET /health` is a liveness probe — it returns 200 once the app is up. `GET /ready` is a readiness probe — it checks PostgreSQL and Redis and returns a `degraded` payload if either is down. Wire your uptime monitor to `/ready` if you want it to react to a backing-store outage, `/health` if you only care that the process is alive. See [Health & metrics](/docs/operations/health-and-metrics).
`POST /api/release/proposal/approve` returns **202 Accepted** immediately rather than blocking — the clone → gate → CI → publish pipeline behind it can run for tens of minutes, well past nginx's read timeout. It dispatches the executor in the background and you poll `GET /api/release/proposal` for the outcome: `execute_status` / `execute_detail` report the last known result, and `execute_in_flight` tells you whether an approve is still running (a second approve while one is in flight is refused by a Redis mutex, not queued). The proposal task itself lands `completed` once a version actually publishes.
## The agent gateway (`/api/v1`)
Agents do not touch the domain routes. They go through the gateway, which exposes one POST endpoint per (role, verb) pair plus a shared content-tools endpoint:
| Endpoint | Role |
|----------|------|
| `POST /api/v1/flow/developer/{verb}` | Developer |
| `POST /api/v1/flow/qa/{verb}` | QA |
| `POST /api/v1/flow/documenter/{verb}` | Documenter |
| `POST /api/v1/flow/cell_pm/{verb}` | Cell PM |
| `POST /api/v1/flow/main_pm/{verb}` | Main PM |
| `POST /api/v1/flow/board/{verb}` | Board (Product Owner, Head of Marketing) |
| `POST /api/v1/flow/auditor/{verb}` | Auditor — its own dedicated endpoint, not the shared board one |
| `POST /api/v1/flow/pr_reviewer/{verb}` | PR reviewer |
| `POST /api/v1/do` | Content tools (`commit`, `note`, `dm`, `read_a2a`, `evidence`) for every role |
The `roboco-flow` MCP server in each agent container is a thin shim: it reads the agent's spawn manifest, registers only the verbs that role may call, and POSTs each one here. The verb set per role and the structural sandboxing are documented in [How agents are sandboxed](/docs/company/agent-gateway) — you generally won't call these endpoints yourself.
## The error envelope
Every gateway verb returns the same standardized **envelope** (`roboco/services/gateway/envelope.py`), so agents recover from a rejection instead of looping:
- **Success** carries `status`, `task_id`, `next` (the verb to call next), an optional `evidence` block, a `context_briefing`, and introspection fields (`current_state`, `valid_next_verbs`).
- **Error** carries an `error` flavor, a human `message`, a concrete `remediate` hint, and — for input gaps — a `missing` list and a `field_hints` answer-key.
The error flavors:
| `error` | Meaning |
|---------|---------|
| `tracing_gap` | A required tracing artifact (e.g. a commit, PR, or note) is missing. |
| `incomplete_input` | A required input field was not supplied; `missing` + `field_hints` tell the agent exactly which. |
| `invalid_state` | The verb isn't valid from the task's current state. |
| `not_authorized` | The role isn't allowed to perform this action. |
| `not_found` | The task or resource doesn't exist. |
| `circuit_open` | The agent has hammered one failing verb too many times; the breaker points it at a graceful exit. |
The domain routes (`/api/*`) use FastAPI's standard error model, with the exception-handler stack in `roboco/api/middleware.py` mapping domain and service errors to clean responses. Notably:
- A provider rate-limit error becomes **HTTP 429 with a `Retry-After` header**.
- A validation failure is a **422**, with a special remediation hint when an agent sends an 8-character short task id instead of a full UUID.
- Every response echoes back an `X-Correlation-ID` (and the request's `X-Response-Time-Ms`), so one id threads through the panel, the API, and the logs.
## Quick checks
```bash
curl -s localhost:3000/health
curl -s localhost:3000/ready
curl -s localhost:3000/api/system/rate-limits
curl -s 'localhost:3000/api/usage/summary?period=7d'
open http://localhost:3000/docs
```
## Next
- [WebSockets](/docs/api/websockets) — the live `/ws` streams the panel consumes.
- [Authentication](/docs/api/auth) — header-trust vs. secure mode, and who can claim which role.
- [How agents are sandboxed](/docs/company/agent-gateway) — the gateway, verbs, and envelope in depth.
================================
URL: https://docs.roboco.tech/docs/api/websockets
Title: WebSockets
================================
import { Admonition } from "@/components/docs/admonition";
import { Mermaid } from "@/components/docs/mermaid";
RoboCo pushes live updates over WebSocket endpoints under `/ws`, served by the orchestrator (`roboco/api/websocket.py`) and routed through nginx alongside the REST API. A single in-process `ConnectionManager` holds per-resource connection sets and broadcasts events to them. The panel consumes all of these through its `useWebSocket("/", …)` hook — you rarely connect to them directly, but they're the same streams an integrator can subscribe to.
## The endpoints
There are two per-resource streams plus one operator-wide stream:
| Endpoint | Stream | Auth |
|----------|--------|------|
| `/ws/agents/{agent_id}` | An agent's output and lifecycle events | `viewer_id`/`agent_id` query param, validated against the DB |
| `/ws/notifications/{agent_id}` | An agent's notifications | `agent_id` query param, validated |
| `/ws/system` | Operator/system-wide stream — no per-agent keying | **Unauthenticated, read-only** |
All sockets support a `ping`/`pong` keepalive: send `{"type": "ping"}` and you'll get a `pong` back.
The per-resource sockets validate their `agent_id`/`viewer_id` query param against the database, but they do **not** enforce the HMAC `X-Agent-Token` that secure-mode REST requires — token enforcement is REST-only. `/ws/system` is intentionally fully unauthenticated. None of the streams carry a control surface or secrets, so they're read-only by design, but the orchestrator port should be treated as trusted-network-only until WebSocket auth lands. See [Authentication](/docs/api/auth) and [Security](/docs/troubleshooting/security).
## How events reach the sockets
Server-side events are published to an in-process `StreamEventBus`. The bridge in `roboco/api/websocket_bridge.py` subscribes to it and registers a `_handle_*` forwarder per event type, mapping each `EventType` to the right socket broadcast.
|publish EventType| B[StreamEventBus]
B --> WB[websocket_bridge _handle_*]
WB --> R[/ws/agents, /ws/notifications/]
WB --> SYS[/ws/system/]
R --> P[Panel useWebSocket hook]
SYS --> P`} />
To add a new live event you define an `EventType`, publish it to the bus, add a `_handle_*` forwarder in `websocket_bridge`, and consume it on the panel via the same hook — you never stand up a parallel endpoint.
## Event types
| Event | Arrives on | What it carries |
|-------|-----------|-----------------|
| `RATE_LIMIT_HIT` | `/ws/system` | A provider just hit a rate limit / overload and was parked. Drives the panel's amber rate-limit banner. |
| `RATE_LIMIT_LIFTED` | `/ws/system` | A parked provider recovered; queued work resumes. Clears the banner. |
| `USAGE_SNAPSHOT` | `/ws/system` | A fresh token-usage/cost snapshot. Drives the live "Token Usage & Cost" dashboard. |
| `A2A_MESSAGE_SENT` | `/ws/system` | An agent-to-agent direct message was just persisted. Fanned out as an **`a2a.message`** frame (not the event-type name) carrying the conversation/task ids, the from/to slugs, and a capped `body_excerpt`. Drives the [Agents page's Conversations tab](/docs/panel/agents-and-work-sessions#conversations-tab) switchboard — the card pulses and the panel refetches the full text over REST. |
| `NOTIFICATION_SENT` / `NOTIFICATION_ACKED` | `/ws/notifications/{agent_id}` | A notification was sent to or acknowledged by an agent. |
| `AGENT_SPAWNED` / `AGENT_STOPPED` / `AGENT_WAITING` / `AGENT_RESUMED` / `AGENT_ERROR` | `/ws/agents/{agent_id}` | Agent runtime lifecycle transitions. |
Each forwarded message is a JSON object with a `type` field (the event-type name above) merged with the event's data.
## REST fallbacks
The two operator dashboards that ride `/ws/system` fall back to HTTP polling when the socket is down, so the panel keeps working without the stream:
| Live event | HTTP fallback |
|------------|---------------|
| `RATE_LIMIT_HIT` / `RATE_LIMIT_LIFTED` | `GET /api/system/rate-limits` |
| `USAGE_SNAPSHOT` | `GET /api/usage/summary?period=24h\|7d\|30d` |
See [Cost & usage](/docs/operations/cost-and-usage) and [Health & metrics](/docs/operations/health-and-metrics) for what the panel does with these.
## Next
- [REST API](/docs/api/rest-api) — the `/api/*` route map and the error envelope.
- [Authentication](/docs/api/auth) — the WebSocket-auth caveat in full.
================================
URL: https://docs.roboco.tech/docs/api/auth
Title: Auth
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo's API identifies a caller by a small set of headers — `X-Agent-ID`, `X-Agent-Role`, and optionally `X-Agent-Team`. How much it *trusts* those headers depends on one flag. Out of the box the API runs in header-trust mode, which is fine on a private LAN and dangerous anywhere else. This page covers both modes and the WebSocket caveat.
## The identity headers
Every REST request carries:
| Header | Required | Meaning |
|--------|----------|---------|
| `X-Agent-ID` | Yes | The agent's UUID or slug (e.g. `be-dev-1`). |
| `X-Agent-Role` | Yes | The role the caller is acting as (e.g. `developer`, `cell_pm`, `ceo`). |
| `X-Agent-Team` | No | The team (`backend`, `frontend`, `uxui`), when relevant. |
| `X-Agent-Token` | Only in secure mode | The HMAC token that proves the headers above weren't forged. |
These are resolved in `roboco/api/deps.py` (`get_agent_context`), and the role gates the action — so a request claiming `X-Agent-Role: ceo` can do CEO-only things like approving and merging.
## Header-trust mode (default)
By default `ROBOCO_AGENT_AUTH_REQUIRED` is unset/false. In this mode the API **accepts the role headers without verifying any token**. There is no proof of identity: whoever sets `X-Agent-Role: ceo` *is* the CEO for that request.
In header-trust mode there is no authentication. Any client that can open a connection to the orchestrator port can act as any agent, approve and merge work as the CEO, cancel tasks, or override task state. The app logs a loud startup warning to this effect. This is acceptable **only** on a trusted private network where nothing untrusted can reach the orchestrator — which is the default single-host LAN deployment behind nginx on `localhost:3000`. Do not expose the orchestrator to anything you don't control without first turning on secure mode.
## Secure mode (HMAC tokens)
Set `ROBOCO_AGENT_AUTH_REQUIRED=true` to require a signed token on every REST request. In this mode:
- `X-Agent-Token` becomes mandatory; a request without it is rejected with **401**.
- The token is an HMAC signed with `ROBOCO_AGENT_AUTH_SECRET` and **bound to the agent's id, role, and team**. The server recomputes the signature over the presented `X-Agent-ID` / `X-Agent-Role` / `X-Agent-Team` and compares it constant-time. If a caller swaps the role header to escalate to `ceo`, the signature no longer matches and the request is rejected with **401 — signature mismatch**.
- The orchestrator issues each agent its token at spawn time, so delivery agents are authenticated by construction.
- A presented token is **always** verified, even when auth isn't required — so you can roll out tokens before flipping the switch without breaking anything.
| Flag | Default | Purpose |
|------|---------|---------|
| `ROBOCO_AGENT_AUTH_REQUIRED` | `false` | When `true`, REST requires a valid HMAC `X-Agent-Token`. |
| `ROBOCO_AGENT_AUTH_SECRET` | (unset) | Shared secret the orchestrator uses to sign and the API uses to verify the per-agent token. |
The control panel acts as the CEO agent. In secure mode, nginx injects the panel's CEO `X-Agent-Token` so your browser session is authenticated without you handling the secret — you just use the panel as normal.
## Two trust boundaries, layered — not conflated
The two modes above (header-trust and the `X-Agent-Token` HMAC) are about the **agent fleet's** identity. A third, independent mechanism — [cloud auth](/docs/optional/cloud-auth) — is about **your own browser session**, and it's important not to conflate the two:
- **Agent HMAC token (`X-Agent-Token`)** — proves an agent container is who its headers claim, signed with `ROBOCO_AGENT_AUTH_SECRET`. This is what every delivery agent, and the panel-via-nginx in secure mode, presents.
- **Cloud-auth session cookie (`roboco_session`)** — proves *you*, the human CEO, logged in with the one seeded account. It's a signed, sliding-expiry JWT set as a secure, HTTP-only cookie, and it exists purely so the CEO's browser doesn't need to hold the HMAC signing secret. The panel's login form is the usual way to mint it; the [Telegram Mini App](/docs/optional/telegram-bridge) (its own opt-in flag, on top of cloud auth) mints the identical cookie off Telegram's signed launch data instead of a typed password.
`get_agent_context` (`roboco/api/deps.py`) layers them rather than picking one: while `ROBOCO_CLOUD_AUTH_ENABLED` is off, only the two modes above apply, byte-for-byte as always. Once it's on:
1. A request carrying a **valid `X-Agent-Token`** is authenticated exactly as in secure mode above — token verification always wins when a token is present, regardless of role.
2. A request with **no token** claiming any role *other than* `ceo` is rejected outright — cloud auth kills header-trust, so a bare role header is never enough on its own once it's armed.
3. A request with **no token** claiming `ceo` falls through to the **session cookie** — a valid, unexpired `roboco_session` authenticates as the CEO; anything else is a `401`.
The same layering applies to the WS panel-token gate (`_require_panel_token` in `roboco/api/websocket.py`): a presented `X-Agent-Token` is checked first, and only when cloud auth is on does a missing token fall back to checking the session cookie instead of rejecting outright.
If an agent container is failing to authenticate, the fix is the HMAC token path (`ROBOCO_AGENT_AUTH_REQUIRED` / `ROBOCO_AGENT_AUTH_SECRET`) above — cloud auth never touches agent traffic that already carries a valid token. Cloud auth exists solely to put a login in front of *your* session when the panel is reachable beyond a trusted LAN.
## The WebSocket caveat
Token enforcement is **REST-only**. The [WebSocket streams](/docs/api/websockets) do not check the HMAC token:
- The per-resource sockets (`/ws/agents|notifications/{id}`) validate their `agent_id`/`viewer_id` query param against the database, but not a token.
- `/ws/system` is fully unauthenticated.
The streams are read-only and carry no control surface or secrets, so this isn't a privilege-escalation path the way the REST headers are — but it does mean the orchestrator port should stay trusted-network-only until WebSocket auth lands, even when you've enabled secure-mode REST.
## What to do
- **Single-host LAN, nothing untrusted on the network** → header-trust is fine; that's the default.
- **Anything reachable beyond a trusted LAN** → set `ROBOCO_AGENT_AUTH_REQUIRED=true` and a strong `ROBOCO_AGENT_AUTH_SECRET`, and keep the orchestrator port off the public internet regardless.
For the full hardening checklist — network exposure, the GitHub PAT handling, and the prompt/bash guards — see [Security](/docs/troubleshooting/security).
## Next
- [REST API](/docs/api/rest-api) — the route surface these headers authorize.
- [WebSockets](/docs/api/websockets) — the live streams and their separate auth model.
- [Cloud auth](/docs/optional/cloud-auth) — the session-cookie setup, its required secret, and the TLS requirement.
================================
URL: https://docs.roboco.tech/docs/tour
Title: The Tour
================================
import { DocImage } from "@/components/docs/doc-image";
RoboCo is a virtual software company — 25 AI agents and one human: you. Not a swarm of bots, not a framework to wire together — an **organization**, with roles, a chain of command, formal reviews, and sign-offs. You don't micromanage it; you run it like a CEO. Drop work in at the top and the company carries it all the way through planning, building, review, and documentation, then brings it back to your desk for the final word. You act at the two ends; the organization fills in everything between.
And the proof is this guide. The screenshots throughout aren't a mock-up: they follow RoboCo building one of its *own* features — the **Prompter**, the task-authoring page now living in this very control panel. RoboCo's agents scoped it, built it across three cells, failed and re-ran its QA, documented it, and opened the real pull request you'll see at the end. RoboCo builds RoboCo — that is the whole proof of concept.
> The panel is your one window into the company. Every task, agent, message, journal, and pull request is live in front of you.
**Prefer video?** A [full screen-recording of the panel](/docs/videos/panel-full-walkthrough.mp4) walks through every page and detail end-to-end — useful as a first tour before diving into the screenshots.
*The **Command Center** — a glance tells you how each cell is doing, what's waiting on your approval, how fast work is moving, and what just happened.*
---
## Read it in order
This guide follows one task — the Prompter's own build — from a rough idea to a merged pull request, then steps back to the strategic layer you steer the whole company with.
1. **[The shape of the company](/docs/tour/01-the-company)** — the org, and how every piece of work is a task that nests into a tree.
2. **[It starts with you](/docs/tour/02-it-starts-with-you)** — drafting a task with the assistant, the Board review, and your first green light.
3. **[The cells build it](/docs/tour/03-the-cells-build-it)** — the three cells, the Dev Kanban, real QA, journals, and the integration branch.
4. **[The last call — and the loop](/docs/tour/04-the-last-call-and-the-loop)** — the final pull request, the CEO Approval Queue, the merge, and round it goes.
5. **[The business workflow](/docs/tour/05-the-business-workflow)** — the charter, the Cockpit, the Secretary, and the research and strategy engines that run above the day-to-day.
---
*RoboCo is early-stage, work-in-progress software (v0) — expect rough edges. The [Get Started guide](/docs/get-started) covers setup, configuration, and the security model.*
================================
URL: https://docs.roboco.tech/docs/tour/01-the-company
Title: 01 — The Company
================================
import { DocImage } from "@/components/docs/doc-image";
What keeps twenty-five agents from dissolving into noise is that RoboCo is relentlessly opinionated about *how* work happens: everything is a task, no task moves without acceptance criteria, and every task walks the same strict lifecycle — built, QA'd, documented, PM-reviewed, approved — each step gated by role. The structure is the point. It is what turns a roster of models into a company that actually ships.
Work in RoboCo is always a **task**, and tasks nest into a tree that mirrors the org itself:
```text
CEO (you, the human)
├── Intake (on-demand interviewer — drafts a task with you)
├── Secretary (your chief-of-staff — acts only on your command)
└── Board ── Product Owner · Head of Marketing · Auditor (silent) · PR Reviewer (inbound external PRs)
└── Main PM (coordinates the cells)
├── UX/UI cell ── PM · 2 Devs · QA · Documenter
├── Frontend cell ── PM · 2 Devs · QA · Documenter
└── Backend cell ── PM · 2 Devs · QA · Documenter
```
The walkthrough that follows traces the delivery path — Intake, the Board, the Main PM, the cells. The Secretary and the research and strategy engines sit one layer up, steering the company as a whole; they get their own chapter at the end.
In practice, one feature becomes a small tree of work — a parent task at the top, a branch for each cell underneath, every node carrying its own status, git branch, and pull request:
*A feature in motion. The parent task fans out to the UX/UI, Frontend, and Backend cells, and each child moves through its own lifecycle — in progress, awaiting review, completed — on its own branch.*
---
Next: **[It starts with you →](/docs/tour/02-it-starts-with-you)**
================================
URL: https://docs.roboco.tech/docs/tour/02-it-starts-with-you
Title: 02 — It Starts With You
================================
import { DocImage } from "@/components/docs/doc-image";
## 1 · It starts with you
You describe what you want — a feature, a fix, an entire product. The way in is the **Task Assistant** — which is the **Prompter** itself, the very feature whose build the rest of this guide follows. (You're about to use the tool RoboCo built for itself; further on, you'll watch the company build it.) Instead of filling a form from memory, you give it a rough idea and it reads your *actual* codebase, asks a few sharp questions, and hands back a properly-formed task — an objective, a per-cell breakdown, and the acceptance criteria that define what "finished" really means.
*Where it starts — point the assistant at a project (one repo) or a product (several), drop in a rough idea, and it spins up an agent that reads that code before it says a word.*
There is a third scope, **MegaTask**, for when you want several tasks at once across projects that don't share a codebase — the assistant proposes the whole batch and the company sequences it into conflict-free waves. See [MegaTask](/docs/company/megatask).
*No canned questions. The agent clones the scope and reads the real surface first, so everything it asks and proposes is grounded in what your code actually does.*
*It comes back having done the homework — naming the real pages, services, and files, laying out what to build and where, and refining with you over a couple of turns until the spec is right.*
*The proposal, ready to launch. Keep chatting to refine it, send it to the **Board** for review, or approve it straight to the Main PM — your call, on one card.*
*From a rough sentence to a real, scoped task in a single chat — acceptance criteria and all, already moving through the company.*
From here, every task follows the path you chose for it. To show that journey end to end, the rest of this guide follows the **Prompter's own** trip through the company — from this same starting point to a merged pull request. Send a task to the **Board** and their job is to pin it down: the Product Owner and Head of Marketing turn the draft into a settled spec, sharpening the requirements and the acceptance criteria before anyone writes a line of code. The Auditor watches the whole time but never interferes.
*The Product Owner working a task over — pinning down the requirements and the must-haves before anyone writes a line of code.*
*Two seats at the table. The Product Owner and the Head of Marketing review the same task from their own angles and put their reasoning on the record — this is the Board building the actual spec for the Prompter, the feature this whole walkthrough follows.*
## 2 · Nothing moves without your green light
The Board hands the reviewed task back to you as a **notification** and waits. You make one call: send it forward, or send it back. Approve it, and the **Main PM** picks it up, splits it across the cells, and sets them running.
*The Board's verdict lands in your notifications and pauses there. A single approval is what turns the whole company on.*
*The notification itself, spelled out: the Board has finished, the task is recorded, and nothing happens until you say so — Approve & Start hands it to the Main PM; reject it and it goes back. This is the first of the only two moments the company needs you.*
---
Previous: **[← The shape of the company](/docs/tour/01-the-company)** · Next: **[The cells build it →](/docs/tour/03-the-cells-build-it)**
================================
URL: https://docs.roboco.tech/docs/tour/03-the-cells-build-it
Title: 03 — The Cells Build It
================================
import { DocImage } from "@/components/docs/doc-image";
## 3 · The cells take over
Underneath the Main PM are three cells — **UX/UI, Frontend, Backend** — each a small team with its own PM. Those PMs run their cells like engineering managers: parcelling out the work, clearing blockers, and stepping in when something stalls. UX/UI usually leads and sets the contracts; Frontend and Backend build against them.
*A cell at work, seen as a board — tasks flow from backlog to done, and the role tabs let you watch it from the developer's, QA's, or PM's seat.*
## 4 · The work gets done — and checked
This is where it's actually built. Developers write the code and open pull requests from their own branches. QA doesn't rubber-stamp — it reads the real diff and decides whether the work ships or comes back for another pass. Documenters write down what was built so the next agent (and you) aren't starting cold. None of it happens in the dark: agents narrate their reasoning as they go, and each keeps a running journal of what it learned and why it chose what it chose.
*QA earning its seat. On this Prompter task it read the work, marked it **failed**, and sent it back — the developer's notes and the QA verdict sit side by side on the record, with the Auditor watching the whole exchange. Real review, not a rubber stamp; the gate only opens when the work is right.*
*Every agent keeps a journal — reflections, decisions, and lessons. Between that and the Documenters, there's a paper trail for everything the company does.*
## 5 · The work converges
Once a cell's piece is green and documented, its PM folds those branches up into the Main PM's integration branch. Three independent streams of work come back together into one. Each task brings its branch, pull request, commits, and docs along with it:
*One finished unit — branch, pull request, commits, and docs all attached. This is the thing that travels up the merge chain.*
*Three streams becoming one history. Each cell's work lands as its own **verified** commit, co-authored by the agent that wrote it — the UX/UI design, the backend endpoint, the frontend page — folded together into the single pull request that comes back to you.*
---
Previous: **[← It starts with you](/docs/tour/02-it-starts-with-you)** · Next: **[The last call — and the loop →](/docs/tour/04-the-last-call-and-the-loop)**
================================
URL: https://docs.roboco.tech/docs/tour/04-the-last-call-and-the-loop
Title: 04 — The Last Call & The Loop
================================
import { DocImage } from "@/components/docs/doc-image";
## 6 · The last call is yours
The cells' work is folded up, the Main PM opens the **final pull request** into `master`, and the company goes quiet. The decision comes back to exactly where it started — with you. You're the only one who ever touches `master`, and anything waiting on you sits in the **CEO Approval Queue** until you act.
*The hand-off back to you. The integrated PR is open, every cell has delivered, QA is green — and it waits. Nothing reaches `master` without your word.*
*And it is a real pull request, on the real repository — not a simulation. The company's work shows up exactly where any engineer would look for it.*
*Open it and the whole brief is there — the objective, what was built, the board-led split across the three cells, and the company's own notes — written by RoboCo, for you to read before you decide.*
*The real diff, laid out for you to inspect — the migrations, the endpoints, the panel components. This is the substance you are signing off on.*
*Your two words. **Approve & Merge** and it ships to `master`; **Request Changes** and it goes around for another lap. The last call has the same shape as the first — one decision, yours alone.*
## The other queue: PRs you didn't open
Not every pull request comes from inside the company. When someone opens a PR against your repo — an external contributor, a fork — the read-only **PR Reviewer** picks it up, reads the diff against your standards, and posts a single change-request directly on the PR (it never chats, never merges, never decides). The PR then surfaces in the **PR Review Queue** on the Command Center — your second decision surface. There you **Supersede** it: the company cuts its own branch from the contributor's commits, hardens the work to your standards, opens its own PR, and — once that replacement merges — closes and links the original. Or you **Dismiss** it. Either way the call is yours, and the org never pushes to anyone else's fork.
---
## And round it goes
You handed the company a task; it scoped it, built it, failed and re-ran its own QA, documented it, and brought it back as a single pull request for your sign-off. That's one complete pass.
*The whole tree in its final state — the parent waiting on your approval, every cell's task done beneath it. One feature, start to finish, with you at only the two ends.*
And the feature in these screenshots is the proof. The **Prompter** wasn't built for a demo — it's a real page RoboCo's agents shipped to RoboCo's own control panel. A company building its own product, in front of you, is the whole point of RoboCo. What makes that hold together isn't a clever model or a lucky run; it's the **organization** — the roles, the gated lifecycle, the reviews and the sign-offs that keep twenty-five agents moving as a company instead of a crowd. Run as many of these passes as you like, across as many projects as you like.
---
Previous: **[← The cells build it](/docs/tour/03-the-cells-build-it)** · Next: **[The business workflow →](/docs/tour/05-the-business-workflow)**
================================
URL: https://docs.roboco.tech/docs/tour/05-the-business-workflow
Title: 05 — The Business Workflow
================================
The first four chapters follow one task through the company. But a company isn't only its task queue — it has a direction, a budget, and a reason every task exists. That layer lives in the **Business** tab, and it is how you steer the whole organization rather than one job at a time.
## The charter
The charter is the company's standing context: its **north star**, its **objectives**, the **constraints** it must respect, and the **operating policy** it works under. You write it once in the Business tab and revise it whenever the direction shifts. It isn't decoration — the charter is threaded into every agent's briefing, into the pitches the Board produces, and into what the Secretary is allowed to act on. Set it well and every agent inherits the same sense of what the company is for; leave it empty and they only know the task in front of them.
## The Cockpit
Where the charter says what the company *should* be doing, the Cockpit shows what it *is* doing — the company reflected back against its own charter. It surfaces spend against budget and the signals worth your attention: drift from the stated objectives, agents sitting idle, work that has been blocked too long. It is the one screen that answers "is the company on course?" without your having to read every task.
## The Company Scorecard
On the Business tab's Goals view sits the Company Scorecard — live performance against the charter in one card. Where the Cockpit surfaces signals to act on, the Scorecard is the one-glance answer to "how is the company actually doing?": what's been delivered, spend against budget, the median lead time from task to merge, and progress on the objectives you set. It is the company's vital signs, read off the same work the rest of the panel tracks.
## The Secretary
The Secretary is your conversational chief-of-staff. You chat with it the way you'd brief a human one — ask it where things stand, or dictate a change to the charter. What it never does is act on its own: every directive it derives from your instruction is **gated**, landing in a queue for your explicit confirmation before anything happens. It reads the whole company's state to advise you, but it spends nothing, builds nothing, and approves nothing until you say the word. It is leverage with a safety catch — your intent, executed, but only after you confirm it.
## Web research and the strategy engine
Two capabilities run above the day-to-day. Both are **off by default** and master-switched from **Settings → Feature Flags** in the panel — the switch persists and takes effect on the next backend restart. The environment variables below are the same toggles at the source, and still carry the parts the panel deliberately doesn't surface (the research provider and its API key, which never leave the server). Their effect shows up inside agent runs once enabled.
### Web research
Flip **Web research** on in Settings → Feature Flags (or set `ROBOCO_RESEARCH_ENABLED=true`), choose a provider with `ROBOCO_RESEARCH_PROVIDER` (`tavily`, `brave`, or `exa`), and supply `ROBOCO_RESEARCH_API_KEY`. With that in place, the Board and PM agents gain `web_search` and `web_fetch` through the `roboco-search` MCP server — so a Product Owner scoping a feature can ground it in the live web, not just your codebase. The API key stays server-side; the agents never see it and never make the external call themselves. Leave it off and the tools simply aren't there — a no-op.
### The strategy engine
Flip **the strategy engine** on in Settings → Feature Flags (or set `ROBOCO_STRATEGY_ENGINE_ENABLED=true`) and a background loop begins watching the company against its charter. When it spots drift from the objectives, agents gone idle, or work blocked for too long, it tells you. It is **notify-only by design**: it never spends, never builds, never approves — it raises the flag and leaves the decision where every decision belongs, with you. Off, it is fully dormant.
### The self-healing CI loop
The same shape, pointed inward: flip **self-healing** on in Settings → Feature Flags (or set `ROBOCO_SELF_HEAL_ENABLED=true`) and RoboCo begins watching its **own** repository's CI. When a run regresses it tells you. Turn on the second switch (`ROBOCO_SELF_HEAL_ORIGINATE_ENABLED=true`) and it goes one step further — it opens a fix task for the regression and hands it straight to the Main PM, who coordinates the repair. It never merges or deploys that work itself: the fix still flows through the normal gates — dev, QA, PR review, and **your** merge — so the company can repair its own build autonomously while the decision to ship stays yours. Both switches are off by default, and it watches only the one repo you name as RoboCo itself.
## Feel the whole thing
The cleanest way to understand this layer is to walk it once, end to end:
1. Open the **Business** tab and set the charter — north star, a couple of objectives, your constraints, the operating policy.
2. Open the **Cockpit** and watch the company reflected against it — spend against budget, and the signals as they appear.
3. Chat the **Secretary** and dictate one change to the charter. Watch the gated directive land in the queue, and confirm it — that round trip, from your sentence to a confirmed action, is the whole shape of how you steer RoboCo from above.
---
Previous: **[← The last call — and the loop](/docs/tour/04-the-last-call-and-the-loop)** · Back to **[the index](/docs/tour)**
*RoboCo is early-stage, work-in-progress software (v0) — expect rough edges. The [Get Started guide](/docs/get-started) covers setup, configuration, and the security model.*
================================
URL: https://docs.roboco.tech/docs/troubleshooting
Title: Troubleshooting & Security
================================
import { CardGrid } from "@/components/docs/card-grid";
import { Card } from "@/components/ui/card";
import Link from "next/link";
import { FileText } from "lucide-react";
When something looks stuck, or before you put RoboCo anywhere near a network you don't fully trust, start here.
The usual snags — agents that spawn but do nothing, a "quiet" provider that's actually parked, an empty knowledge base — with their causes and fixes.
The trust model: header-trust versus secure mode, how secrets and tokens are handled, and the one rule — don't expose it to the public internet.
================================
URL: https://docs.roboco.tech/docs/troubleshooting/common-issues
Title: Common Issues
================================
import { Admonition } from "@/components/docs/admonition";
When something goes wrong, the failure is almost always one of a handful of things: a mount that didn't make it into the container, a model that isn't pulled, a token that expired, or a provider that's parked rather than hung. This page is the problem→cause→fix guide for the snags you'll actually hit, ordered by how often they bite on a fresh deployment.
## Quick reference
| Symptom | Most likely cause | Fix |
|---------|-------------------|-----|
| Agents spawn but do nothing useful (tool-discovery churn) | The role's tool-manifest didn't mount; the agent falls back to discovering verbs | Check the manifest mount (below) |
| Agent containers respawn in a loop, MCP servers stuck "pending" | MCP server launched without `--no-sync` against a workspace clone | Already fixed in the orchestrator; verify your image is current (below) |
| A provider's agents go quiet all at once | The provider is **parked-and-probing** after a 429 / overload / session-limit — not hung | Wait; it self-resumes. See [Resilience](/docs/models/resilience) |
| A task sits **blocked: "branch behind base / needs rebase"** | The agent's branch fell behind its base while it worked; there is no agent-layer rebase verb, so the agent escalates instead of improvising | Rebase it yourself from the panel **Git** tab (below) |
| KB / `ask_mentor` returns nothing | Ollama unhealthy or models not pulled | Check `ollama-init` logs (below) |
| Agent containers exit immediately | `~/.claude` not mounted, a Grok/Codex/Kimi token expired, or a Gemini OAuth credential missing | Check the mount / refresh the token (below) |
| Clone fails, agent can't reach the repo | Missing or invalid project PAT, or HTTPS URL with no token | Set the project token (below) |
| Orchestrator won't start | `ROBOCO_ENCRYPTION_KEY` unset, or a pending migration | First-run checklist (below) |
## Why do agents spawn but do nothing?
If agents come up but burn turns "looking for tools" instead of claiming work, the cause is almost always the **tool manifest**. At spawn the orchestrator writes a per-agent manifest and mounts it read-only at `/app/tool-manifest.json` (the mount is built in `roboco/runtime/orchestrator.py`, pointing `ROBOCO_TOOL_MANIFEST_PATH` at that path). The manifest lists exactly the verbs the agent's role may call — see [the gateway](/docs/company/agent-gateway).
If that file is missing inside the container, the agent has no allow-list to read and falls back to discovering the verb surface itself, which wastes turns and tokens. The mount source is `{DATA_HOST_PATH}/manifests/{agent_id}.json` on the host. Confirm the host directory exists and is writable, and that the data volume is mounted into the orchestrator so it can write manifests there in the first place.
Exec into a running agent container and check that `/app/tool-manifest.json` exists and is non-empty. If it's absent, the orchestrator couldn't write or mount the per-agent manifest — fix the host `manifests/` directory and the orchestrator's data-volume mount, then respawn.
## Why do agent containers respawn in a loop with MCP servers stuck "pending"?
A historical failure mode: each agent's MCP servers (`roboco-flow`, `roboco-do`, the read-only git and KB servers) launch via `uv run` from inside the agent's workspace clone. If `uv` re-syncs that clone on launch it can collide with the clone's lock, the gateway servers never come up, the agent has zero verbs, and the orchestrator respawns it in a loop.
The fix is already in the code: every MCP server is launched with `uv run --no-sync` (`roboco/runtime/orchestrator.py`), and the SDK startup hook pins `uv` to the pre-baked image venv. If you see this symptom, your running image predates the fix — rebuild and redeploy the agent images so the `--no-sync` launch is in effect.
## Why is a "quiet" provider parked, not hung?
If every agent on one provider goes silent at the same moment, it is almost never a crash. On a provider 429, a persistent overload (HTTP 529/500/503), or a Claude session-limit (the rolling 5-hour usage window), RoboCo **parks** that provider's work and runs a background probe that resumes it the moment the provider recovers — it does not crash-retry into the wall and burn tokens. You'll see an amber banner in the panel; the work revives on its own.
Restarting the orchestrator throws away the park-and-probe state and the parked agents' context. Leave it alone — it self-heals. The full mechanism, the banner, and the `ROBOCO_OVERLOAD_BREAK_ENABLED` flag are documented in [Resilience](/docs/models/resilience).
## Why does KB search / ask-mentor return nothing?
The knowledge base and `roboco_ask_mentor` run on the in-house pgvector RAG, which depends on Ollama for embeddings and the local LLM. If the KB is empty or `ask` comes back blank, Ollama is the usual culprit:
- **Models not pulled.** `ollama-init` pulls `qwen3-embedding:0.6b` (the embedder, ~30s) and the local LLM (~2min) on startup. Check `docker logs roboco-ollama-init` — if the pull failed or is still running, embeddings can't be produced and indexing/retrieval returns nothing.
- **Ollama unhealthy.** The healthcheck is `ollama list`, not a curl. If the `ollama` service is unhealthy the orchestrator's document indexing (run during FastAPI lifespan) never completes.
- **A `404 /api/embed`** in the logs means the embedding model isn't present — re-check `ollama-init`.
See [Health and metrics](/docs/operations/health-and-metrics) for where these surface in the panel.
## Why do agent containers exit immediately?
A container that starts and dies within seconds is missing something it needs at boot:
- **`~/.claude` not mounted (Claude agents).** Claude-backed agents mount the host's `~/.claude` (overridable via `ROBOCO_HOST_CLAUDE_DIR`) into the container so they can authenticate. If that directory is absent or unreadable on the host, the agent can't start. Confirm the host directory exists and the orchestrator user can read it.
- **Grok token expired (Grok agents).** Grok's access token has a fixed ~6h TTL and the CLI can't refresh it headlessly — on an expired token it would otherwise hang forever at an interactive login prompt. The entrypoint runs `python -m roboco.llm.providers.grok_auth --check` as a preflight and **exits 78** immediately rather than hang (`docker/scripts/grok-cli-agent-entrypoint.sh`). An exit code of 78 means the Grok token is missing or expired. The orchestrator mints a fresh token before expiry on each dispatch tick; if you keep hitting 78, re-run `grok login` on the host to repopulate the shared `auth.json`.
- **Codex token expired (Codex agents).** Same shape as Grok: the entrypoint's `python -m roboco.llm.providers.codex_auth --check` preflight also **exits 78** on a missing/expired token; a rate-limit/quota condition detected mid-run exits **75** instead, which parks the provider rather than crash-looping. The orchestrator refreshes the token before expiry the same way it does for Grok; re-run `codex login` on the host if you keep hitting 78.
- **Gemini OAuth credential missing (Gemini agents).** Gemini has no orchestrator-side refresh daemon — each container refreshes its own copy of the mounted OAuth credential in-process. A missing or unrefreshable credential surfaces as the CLI's own auth-failure **exit 41**, which parks the provider like a rate limit. Re-run the interactive `gemini` login on the host to repopulate `~/.gemini`.
- **Kimi credential missing or expired (Kimi agents).** Like Gemini, Kimi has no orchestrator-side refresh daemon — the CLI refreshes its own token, serialized by its own cross-process lock so every container and the orchestrator can safely share the same rotating credential chain. A missing/expired credential **exits 78**; a rate-limit/quota condition detected mid-run **exits 75** instead, parking the provider rather than crash-looping. Re-run `kimi login` on the host to repopulate the shared `~/.kimi-code` directory.
## Why does the clone fail, or the agent can't reach the repo?
Cloning is the first thing an agent does on a project, and it fails for one reason in practice: the **project's git token**. Git auth is per-project, not global — and it works the same way regardless of which [forge](../get-started/first-project.md#choosing-a-forge) the project is on.
- An **HTTPS Git URL with no token fails** — the agent can't authenticate. Set the token when you create the project.
- An **invalid or under-scoped token** fails the same way. The token needs repository read/write access (clone/push) and pull/merge-request access. See [Register a project](../get-started/first-project.md#the-git-token) for the exact scopes per forge.
- A **wrong Forge selection** fails differently — the clone itself may succeed, but PR/CI/review calls hit the wrong API. Double-check the Forge field matches where the repo actually lives, especially for a self-hosted Gitea or GitLab instance (auto-detect only covers `github.com` and `gitlab.com`).
Remember the API never returns a stored token, so the panel only shows whether one is set, not its value — if a clone fails, re-enter the token rather than assuming the stored one is good.
Every project token is Fernet-encrypted with `ROBOCO_ENCRYPTION_KEY`. Change that key and all stored tokens become undecryptable — clones will fail until you re-enter every token. See [Security](/docs/troubleshooting/security).
## What should I check on first run and after migrations?
When the orchestrator won't come up at all on a fresh deploy:
- **`ROBOCO_ENCRYPTION_KEY` must be set.** It defaults to empty in config, but the orchestrator refuses to start without it. Generate one with `python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'` and set it in your `.env`. It is load-bearing and must never change after first use.
- **Migrations.** Schema changes ship as Alembic migrations under `alembic/versions/`. The API applies them on startup (and falls back to `create_all` on a fresh DB), but after pulling a change that adds a migration you can apply it explicitly with `docker compose exec orchestrator alembic upgrade head`.
- **Startup is slow on purpose.** The FastAPI lifespan does ~30–60s of document indexing before the API answers, and the orchestrator polls `/health` for up to 120s before starting its dispatch loop. An "All connection attempts failed" early in the logs usually just means a dependent service hadn't finished its healthcheck yet — give the startup sequence time before treating it as an error.
## Why is a task stuck on a branch behind its base?
Agents have no rebase, pull, or merge verb — a task branch is brought current with its base only at claim. If the base (a cell branch, or master) moves forward while the agent works, the branch falls behind, and the agent escalates rather than improvising git surgery: the task surfaces **blocked** with a reason like *"branch behind base — needs rebase."* That escalation is by design — bringing the branch current is your call, not a unit of work the company decomposes. Rebase it from the panel **Git** tab — select the branch and **Rebase** it onto its base (or master) — and the task resumes on the next dispatch. (Automatic rebase-at-spawn, so a stale branch never reaches you at all, is on the roadmap.)
## Reliability fixes worth knowing about
A handful of dispatch-loop and git-sync bugs got fixed together this release. None of them change how you operate the panel — they just remove workarounds you may have needed before:
- **Sequence-held claims name their blocker.** A task waiting behind a lower-sequence sibling (see [Sequence ordering](/docs/panel/tasks-and-kanban#sequence-ordering)) used to surface as a generic "concurrent transition" error — indistinguishable from an actual bug. It now returns a dedicated reason naming the sibling that's holding it, and the dispatcher no longer hands a task to an agent only to have the claim gate refuse it.
- **An escalate/unblock ping-pong now trips a circuit breaker.** A cell PM escalating a task and the Main PM unblocking it, back and forth with no forward progress, used to spin indefinitely — one side of the round trip was never counted. The oscillation is now tracked at the task level: a new commit or an advancing revision count resets the count as real progress, but a genuine loop blocks the task for a human, with a notification naming both agents and the cycle count, instead of silently burning turns forever.
- **Committed work can no longer be lost to a branch sync.** See the note on [the merge model](/docs/company/merge-model#branches-commits-and-prs-are-traceable) — the rebase primitive behind `sync_branch` and every other branch sync used to discard commits an agent had made but not yet pushed; it no longer does. A reviewer re-examining a task also now always reads the latest pushed commits, not a stale snapshot from an earlier round.
- **CEO pause/resume from the panel now actually works.** The task detail page's god-mode pause and resume actions (see [CEO god-mode](/docs/panel/tasks-and-kanban#ceo-god-mode)) were refused by the backend for anyone but the task's own assignee — including you. Both routes now authorize the CEO tier the same way the rest of god-mode does.
- **Task budgets are explicit-input only.** A blank task budget used to fall back to a per-`TaskType` default figure; that fallback is gone. Leave the field blank and the task simply has no cap — see [Task & project cost budgets](/docs/optional/task-budgets).
- **Haiku is retired from delivery-lifecycle roles.** See [Provider Routing](/docs/models/provider-routing#cost-tiered-routing-cheaper-models-for-mechanical-work) — QA and PM need to reliably emit structured review output that a Haiku-class model kept failing to produce, so those roles (and any override that would land one on Haiku) now run on Sonnet instead.
## Next
→ [Security](/docs/troubleshooting/security) — the trust model and how to harden a deployment, or back to the [troubleshooting index](/docs/troubleshooting).
================================
URL: https://docs.roboco.tech/docs/troubleshooting/security
Title: Security
================================
import { Admonition } from "@/components/docs/admonition";
RoboCo runs a workforce of agents with access to your repositories, so its security model is worth understanding before you put it anywhere. The short version: it is built for a **trusted private network**, it ships in a permissive header-trust mode by default, and you harden it with one flag and one token. This page covers the trust model, how to lock it down, where secrets live, and the one rule that matters more than the rest.
RoboCo is designed to run on a trusted LAN behind nginx, which is the only externally-exposed service. The default authentication mode trusts request headers (below), and one WebSocket stream is unauthenticated by design. Treat the whole system as you would a database: reachable only from your own network, never from the open internet.
## How is the orchestrator API kept off the open network?
The orchestrator's `:8000` binds to `127.0.0.1` only — it is **not** published on a routable host interface. Both deploy composes used to publish `:8000` on `0.0.0.0`, so anyone who could reach the host hit the control plane directly: bypassing nginx and, in the default header-trust posture below, reading and writing runtime settings or spoofing `X-Agent-Role: ceo` to spawn/stop agents with no credential at all (GHSA-4f7g-w95g-5q2c).
nginx reaches the orchestrator over the internal Docker network, so normal operation and on-host debugging (`curl http://localhost:8000/health` run from the host itself) are unchanged. Off-host access has to go through nginx — and, if you've armed it, [cloud auth](/docs/optional/cloud-auth) — same as every other route. The header-trust design itself is unchanged; this closes the unintended off-host reachability that gave a header spoof any teeth.
## What is the default header-trust mode?
By default the API authenticates an agent purely from request headers: `X-Agent-ID`, `X-Agent-Role`, and optionally `X-Agent-Team`. There is no signed token required, which means **any client that can reach the API can claim any role — including `ceo`.** This is intentional for a single-operator deployment on a trusted network (it keeps the panel and `curl`-for-debugging simple), and the API logs a loud warning at startup whenever it's in this mode (`roboco/api/app.py`):
> Agent auth is in HEADER-TRUST mode … the API accepts X-Agent-Id / X-Agent-Role without verifying a signed token, so any client that can reach it may act as any role, including 'ceo'. Acceptable only on a trusted private network.
If your network boundary is solid, header-trust is fine. If it isn't — or you just want defense in depth — turn on secure mode.
## How do I harden RoboCo with secure mode and the panel token?
Set `ROBOCO_AGENT_AUTH_REQUIRED=true` to require a signed token on every request. In this mode:
- Every request must carry an `X-Agent-Token` that is an HMAC of `agent_id:role:team` signed with `ROBOCO_AGENT_AUTH_SECRET`. The orchestrator mints this token for each agent at spawn, so agent traffic keeps working transparently.
- A missing token is rejected with `401`. A **forged role won't help**: even in header-trust mode, any token that *is* presented is still verified, so you can't bypass auth by supplying an invalid token. The HMAC comparison is constant-time.
- The **panel** authenticates as the CEO, so it needs a CEO token to keep working in secure mode. Mint it with `make panel-token`, which prints the signed token (and refuses with an error if `ROBOCO_AGENT_AUTH_SECRET` is unset, since an unsigned token would be useless). Configure the panel with that token.
`ROBOCO_AGENT_AUTH_SECRET` is the signing key for every agent and panel token. Set it first, then `make panel-token`, then set `ROBOCO_AGENT_AUTH_REQUIRED=true` — otherwise agents and the panel can't produce valid tokens and every request 401s. If the secret is unset, token minting fails closed to a literal `UNSIGNED` value rather than producing a usable token.
See [API authentication](/docs/api/auth) for the exact headers and how the panel and external clients pass them.
## How does per-agent token binding work?
The token isn't a single shared bearer secret — it's **bound to the specific agent**. Because the HMAC covers `agent_id:role:team`, a token issued for `be-dev-1` as a `developer` is only valid for those exact header values. An agent on the Docker network can't take its own valid token and replay it claiming to be `main_pm` or `ceo`; the signature won't match the forged role, and the request is rejected. This is what stops one agent from escalating its own privileges by editing headers.
## How are secrets handled?
Three secrets are load-bearing, and all three stay out of the repo:
| Secret | What it protects | Rule |
|--------|------------------|------|
| `ROBOCO_ENCRYPTION_KEY` | Fernet key that encrypts every per-project git token (GitHub, Gitea, or GitLab) at rest | Set once at install; **never change it** or all stored tokens become undecryptable. Back it up. |
| `ROBOCO_AGENT_AUTH_SECRET` | HMAC signing key for agent and panel tokens | Required for secure mode; keep it off the repo and out of logs. |
| `ROBOCO_CLOUD_AUTH_SECRET` | Signing key for the [cloud auth](/docs/optional/cloud-auth) session cookie's JWT | Required when `ROBOCO_CLOUD_AUTH_ENABLED=true` — the orchestrator **refuses to start** without it. Irrelevant, and unused, while cloud auth is off. |
Project git tokens are **encrypted the moment you save them** (with `ROBOCO_ENCRYPTION_KEY`) and stored in the `projects.git_token_encrypted` column — the same treatment regardless of which [forge](/docs/get-started/first-project#choosing-a-forge) the project points at. The **API never returns a token** — responses only carry a `has_git_token` boolean, so the panel can show whether a token is set but never its value. The [X (Twitter) engine](/docs/optional/x-engine)'s four OAuth 1.0a credentials and the [Telegram bridge](/docs/optional/telegram-bridge)'s bot token get the identical treatment — Fernet-encrypted at rest, entered once in the panel, and never returned by the API. None of these secrets belong in the repository; put them in your `.env` (which is gitignored) or your secret manager.
## How does cloud auth lock the door for your own browser session?
Everything above is about the **agent fleet's** identity. [Cloud auth](/docs/optional/cloud-auth) (`ROBOCO_CLOUD_AUTH_ENABLED`, off by default) is a separate, orthogonal mechanism for **your own login** — a single seeded CEO user with a sliding, 30-day session cookie, for when you want to put the panel somewhere beyond a fully trusted LAN.
It's not a general multi-user system: there's no registration route and no way to create a second account. It changes what `get_agent_context` trusts, too — once armed, a caller claiming the `ceo` role without either a valid `X-Agent-Token` or a valid session cookie is rejected outright, closing the header-spoof hole for the one role that matters most. See [API auth](/docs/api/auth) for exactly how the session cookie and the agent token sit side by side, and [Cloud auth](/docs/optional/cloud-auth) for the full setup.
The session cookie is `secure`-only, so it only works behind HTTPS. Don't arm this flag until something in front of RoboCo terminates TLS — over plain HTTP the cookie is set but never sent back, and login will look like it silently fails.
## Why don't agents need your production database credentials?
Two more default-off subsystems remove a credential agents used to receive outright. When [toolchain matching](/docs/optional/toolchain-matching) is on, the orchestrator has historically injected RoboCo's own production Postgres credentials into an agent's environment so its self-gate could run against a real database (`_append_gate_env`). Either of the following now takes that credential away from agents entirely, for the projects/deployments where it applies:
- **[DB network isolation](/docs/optional/db-network-isolation)** (`ROBOCO_DB_NETWORK_ISOLATED`, on by default in all three tracked compose files) puts postgres/redis on a data-only network agent containers never join — the injection is suppressed because the credentials would dead-end in a connect timeout anyway.
- **[Sandboxed dev DB/Redis/Mongo](/docs/optional/sandbox-db)** (`ROBOCO_SANDBOX_DB_ENABLED`, off by default, per-project opt-in) replaces the production credentials with a throwaway sibling container a dev/QA agent requests on demand mid-session (`request_sandbox`), released when its engagement with the work ends.
Either one means an opted-in agent's gate still runs against a real database — just never RoboCo's own.
## Why does the token never enter an agent container?
This is the guarantee that makes it safe to hand RoboCo a private repo: **your git token is never present inside an agent container**, on any forge. The orchestrator decrypts the token only at the moment of a git operation, injects it for that operation, and immediately after cloning **scrubs the token out of the clone's git config** — then verifies no token byte survives anywhere under `.git/`, destroying the workspace if one did. A compromised or misbehaving agent has nothing to exfiltrate, because the credential was never on its disk. The clone scrub is described in [Register a project](../get-started/first-project.md#what-happens-under-the-hood), and the broader sandboxing model in [the gateway](/docs/company/agent-gateway).
## What's the WebSocket auth caveat?
The per-resource WebSocket streams are keyed to a resource, but the operator stream is not authenticated. **`/ws/system` carries no per-agent keying and no token** even when `ROBOCO_AGENT_AUTH_REQUIRED=true` — secure mode does not extend to it. It is **read-only** (it carries system events like rate-limit lifecycle and usage snapshots; it accepts nothing from the client), so the exposure is limited to a reader seeing system telemetry. It is, however, one more reason the system must sit on a trusted network: anyone who can open that socket can watch the operator stream.
## Next
→ [API authentication](/docs/api/auth) for the header/token contract, [Cloud auth](/docs/optional/cloud-auth) for the CEO login session, [the gateway](/docs/company/agent-gateway) for how agent capability is constrained, or back to [common issues](/docs/troubleshooting/common-issues).