docs: add the user-facing MkDocs documentation site

Build a complete user-facing documentation site (MkDocs Material) under docs/, served at roboco.dev/docs via a new gh-pages deploy workflow.

- Sections: Get Started, The Company, the Tour, Operating the Panel, Choosing & Running Models, Cost & Observability, Optional Subsystems, Configure & Deploy, API Reference, Troubleshooting & Security (55 pages).
- mkdocs.yml (Material theme; excludes the agent-facing rag/ corpus, internal scratch, and orphaned stub trees) and .github/workflows/docs.yml (mkdocs gh-deploy to gh-pages).
- Retire the stale root usage.md and deployment.md to redirect stubs into the site.
- Fix the docs tooling: add the pymarkdownlnt dependency + .pymarkdown.json, run serve-docs/lint-docs/fix-docs under the docs extra, add a build-docs strict gate.
- Fix the roboco console-script entry point (cli, not the un-awaited async main).
- README: correct the project-structure tree (optimal.py, alembic) and link the docs site.
This commit is contained in:
Renn F
2026-06-22 15:53:00 +02:00
parent 3a3cd69e1e
commit 2fb63fed1f
64 changed files with 3553 additions and 500 deletions
+78
View File
@@ -0,0 +1,78 @@
# Common issues
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 — not hung | Wait; it self-resumes. See [Resilience](../models/resilience.md) |
| KB / `ask_mentor` returns nothing | Ollama unhealthy or models not pulled | Check `ollama-init` logs (below) |
| Agent containers exit immediately | `~/.claude` not mounted, or a Grok token expired | 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) |
## 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](../company/agent-gateway.md).
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.
!!! tip "Verify the mount inside the container"
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.
## Respawn 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.
## A "quiet" provider is 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, or a persistent overload (HTTP 529/500/503), 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.
!!! note "Don't restart to 'unstick' it"
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](../models/resilience.md).
## KB search / ask-mentor returns 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](../operations/health-and-metrics.md) for where these surface in the panel.
## 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`.
## Clone fails / 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 GitHub token**. Git auth is per-project, not global.
- 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 contents access (clone/push) and pull-request access. See [Register a project](../get-started/first-project.md#the-github-token) for the exact scopes.
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.
!!! warning "If you rotated ROBOCO_ENCRYPTION_KEY"
Every project PAT 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](./security.md).
## First-run and migration checks
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 ~3060s 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.
## Next
→ [Security](./security.md) — the trust model and how to harden a deployment, or back to the [troubleshooting index](./index.md).
+19
View File
@@ -0,0 +1,19 @@
# Troubleshooting & Security
When something looks stuck, or before you put RoboCo anywhere near a network you don't fully trust, start here.
<div class="grid cards" markdown>
- **[Common issues](common-issues.md)**
---
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.
- **[Security](security.md)**
---
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.
</div>
+54
View File
@@ -0,0 +1,54 @@
# Security
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.
!!! danger "Do not expose RoboCo to the public internet"
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.
## The default is 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.
## Hardening: secure mode + 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.
!!! tip "Set the secret before you flip the flag"
`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](../api/auth.md) for the exact headers and how the panel and external clients pass them.
## Per-agent token binding
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.
## Secrets handling
Two secrets are load-bearing, and both stay out of the repo:
| Secret | What it protects | Rule |
|--------|------------------|------|
| `ROBOCO_ENCRYPTION_KEY` | Fernet key that encrypts every per-project GitHub PAT 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. |
Project GitHub tokens are **encrypted the moment you save them** (with `ROBOCO_ENCRYPTION_KEY`) and stored in the `projects.git_token_encrypted` column. 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. Neither secret belongs in the repository; put them in your `.env` (which is gitignored) or your secret manager.
## The PAT never enters an agent container
This is the guarantee that makes it safe to hand RoboCo a private repo: **your GitHub PAT is never present inside an agent container.** 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](../company/agent-gateway.md).
## 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](../api/auth.md) for the header/token contract, [the gateway](../company/agent-gateway.md) for how agent capability is constrained, or back to [common issues](./common-issues.md).