[sweep] strip Fxxx audit-ID tokens + trim bloated comments/docstrings + add behavior-change docs

Post-audit sweep over the 135 audit-fix commits since 19a474d3:

1. Stripped every # Fxxx: audit-ID token from comments AND every Fxxx token
   from docstring openings across 211 blocks / ~626 lines. The CEO flagged
   these twice: audit-issue IDs in code confuse future devs/agents. The
   descriptive text is preserved; only the Fxxx token is removed (and bloated
   narrative blocks trimmed to 1-3 lines keeping the one non-obvious invariant).
2. Trimmed bloated comments/docstrings to the concise standard (1-3 lines).
3. Added missing behavior-change docs for the audit-fix batch: prompts/roles
   (documenter, pr_reviewer, qa), user-facing docs (api auth, websockets,
   agent-gateway, megatask, merge-model, task-lifecycle, grok, resilience,
   conventions, panel, security, troubleshooting), and the RAG corpus (cell-pm,
   main-pm, pr-reviewer, qa roles; conventions; messaging-tools; escalation;
   megatask; task-claiming workflows).

Comment/docstring/prose ONLY — zero code-line edits (verified: the diff
contains no def/class/return/if/for/await/assignment/call lines). Gates green:
ruff format + ruff check clean, mypy clean on roboco/. The only pytest failures
are the pre-existing sync_branch tracing-decision gap (B1, 250be5c2) — not
sweep-caused and tracked separately.
This commit is contained in:
Renn F
2026-06-29 01:25:40 +02:00
parent fb850e8235
commit 3441e37120
131 changed files with 842 additions and 1391 deletions
+7 -5
View File
@@ -39,14 +39,16 @@ Set `ROBOCO_AGENT_AUTH_REQUIRED=true` to require a signed token on every REST re
!!! tip "How the panel authenticates as the CEO"
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.
## The WebSocket caveat
## The WebSocket + live-chat streams
Token enforcement is **REST-only**. The [WebSocket streams](./websockets.md) do not check the HMAC token:
Secure mode extends beyond REST. When `ROBOCO_AGENT_AUTH_REQUIRED=true`:
- The per-resource sockets (`/ws/channels|agents|sessions|notifications/{id}`) validate their `agent_id`/`viewer_id` query param against the database and channel access, but not a token.
- `/ws/system` is fully unauthenticated.
- The **per-resource WebSocket streams** (`/ws/channels|agents|sessions|notifications/{id}`) require the **CEO panel token** — the same signed `X-Agent-Token` nginx injects for the panel. An agent on the Docker network can no longer subscribe to another agent's notifications with no auth. They still validate `agent_id`/`viewer_id` against the DB and channel access on top.
- The **`/api/v1/do/*` content routes** require a valid per-agent HMAC token bound to `X-Agent-ID` (the do router serves every role, so the gate is token-only, not role-specific).
- The **live-chat bridges** (`/prompter/live/*`, `/secretary/live/*`) — the prompter/secretary intake chats — require the CEO panel token on their start/stream/status/messages/stop endpoints. They were the last panel-facing API surface that ran unauthenticated.
- **`/ws/system`** stays operator-only and read-only by design (it carries system telemetry and accepts nothing from the client); it is not token-gated.
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.
A presented-but-forged token is rejected even in dev (header-trust) mode, so you can roll out tokens before flipping the switch without breaking anything. The container→relay internal callback is left ungated by design (internal Docker network, opaque session id).
## What to do
+7 -7
View File
@@ -8,16 +8,16 @@ There are four per-resource streams plus one operator-wide stream:
| Endpoint | Stream | Auth |
|----------|--------|------|
| `/ws/channels/{channel_id}` | Live messages in a team channel | `agent_id` query param, validated against the DB + channel access |
| `/ws/agents/{agent_id}` | An agent's output and lifecycle events | `viewer_id`/`agent_id` query param, validated against the DB |
| `/ws/sessions/{session_id}` | Messages in a communication session | `agent_id` query param, validated |
| `/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** |
| `/ws/channels/{channel_id}` | Live messages in a team channel | `agent_id` query param, validated against the DB + channel access; **CEO panel token required in secure mode** |
| `/ws/agents/{agent_id}` | An agent's output and lifecycle events | `viewer_id`/`agent_id` query param, validated against the DB; **CEO panel token required in secure mode** |
| `/ws/sessions/{session_id}` | Messages in a communication session | `agent_id` query param, validated; **CEO panel token required in secure mode** |
| `/ws/notifications/{agent_id}` | An agent's notifications | `agent_id` query param, validated; **CEO panel token required in secure mode** |
| `/ws/system` | Operator/system-wide stream — no per-agent keying | **Unauthenticated, read-only** (operator-only by design; not token-gated) |
All sockets support a `ping`/`pong` keepalive: send `{"type": "ping"}` and you'll get a `pong` back.
!!! warning "WebSocket auth is not the REST auth"
The per-resource sockets validate their `agent_id`/`viewer_id` query param against the database (and channel access via the permissions layer), 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](./auth.md) and [Security](../troubleshooting/security.md).
!!! info "Secure mode now covers the per-agent streams"
When `ROBOCO_AGENT_AUTH_REQUIRED=true`, the four per-resource sockets require the **CEO panel token** (the signed `X-Agent-Token` nginx injects for the panel) on top of their `agent_id`/`viewer_id` DB validation — an agent on the Docker network can no longer subscribe to another agent's stream unauthenticated. `/ws/system` is intentionally left operator-only and read-only. A forged token is rejected even in dev mode. See [Authentication](./auth.md).
## How events reach the sockets
+5 -2
View File
@@ -17,8 +17,8 @@ Two more read-only servers give agents a read-only view of git (`status`, `log`,
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:
- A **developer** can `give_me_work`, open a PR, and mark itself done — but there is no merge verb in its manifest.
- **QA** can claim a review and pass or fail it — but it has no `commit`.
- A **developer** can `give_me_work`, open a PR, mark itself done, and `sync_branch` (rebase its branch onto its base through the gate) — but there is no merge verb in its manifest.
- **QA** can claim a review and pass or fail it — but it has no `commit`. QA and Documenters also get `i_am_blocked` as their escape hatch when they're stuck.
- A **PR reviewer** can pass or fail an assembled PR and post its review on the PR — but it never gets agent chat verbs.
- The **Auditor** is restricted to leaving a private note and reading evidence; it cannot `say` or `dm`. It observes; it does not participate.
@@ -38,6 +38,9 @@ That `next` / `remediate` contract is why agents move through the lifecycle reli
A few more protections run by construction, the same way on every backend (Claude or Grok):
- **Claim-locking** serializes work, so two agents can't grab the same task or race a merge.
- **Content posts require an active claim.** `commit`, `note`, `say`, `dm`, and `evidence` on a specific task are refused unless the agent holds that task's active claim — an agent can't write to a task it hasn't locked.
- **Human-only roles are never spawned.** The CEO, the Intake (prompter), and the Secretary are human-driven, so `spawn_agent` structurally refuses them — a notification addressed to the CEO can never launch a CEO container that acts as the human. Intake and Secretary run through their own dedicated, guarded chat paths instead.
- **Notifications can't target human-only roles.** `notify` rejects the CEO/prompter/secretary as recipients — there is no agent acknowledgement path for them, so a notification to them is a no-op rather than a stuck ack.
- **The token never enters the container.** Your GitHub PAT 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.
+2
View File
@@ -25,6 +25,8 @@ For each task it proposes, the agent declares a small **collision surface**: whi
The waves are just ordinary task dependencies, so the same dependency-gate that already paces the rest of the company runs them: a wave starts only once the previous wave's tasks have reached a terminal state — normally each one's pull request is merged (a cancelled task releases the next wave too).
The same collision-aware sequencing follows the work **down the chain**, not just at the top level. When a cell PM delegates a root-subtask into developer tasks, the dev-task collision surfaces flow through the same DAG — file-overlap serializes, migration-adders chain, shared-surface edits wait their turn — and cell tasks themselves wave-chain off their sibling root-subtasks. So a batch that spans a shared codebase stays ordered all the way to the leaves, not only at the umbrella. The task hierarchy is capped at four layers (umbrella → root → cell → dev) to fit this MegaTask shape.
## What gets created
When you confirm, RoboCo creates one **umbrella** task that groups the batch, and one **root-subtask** per piece of work:
+9
View File
@@ -40,6 +40,15 @@ graph BT
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 gates that keep the chain clean
Two gate-level checks stop a stale branch from sneaking through:
- **Behind-base gate on `i_am_done`.** If a sibling's PR merged into the parent branch while the developer worked, the dev's branch is now behind its base and the assembled PR won't merge cleanly. The gate refuses `i_am_done` in that state and steers the developer to `sync_branch` — the gate-level rebase verb that rebases the branch onto its base (raw shell git is denied to agents, so the rebase goes through the gate, traced and evidenced). Conflicts abort with no force-push and point the dev at resolve-by-hand. The gate fails open on a flaky fetch so a transient git error can't strand a task at the submit gate.
- **Unchanged-PR gate on `submit_root`.** When a Main-PM root PR is `pr_fail`'d and re-submitted byte-identical, the loop would repeat forever. The gate refuses the re-submit when the assembled root PR's head SHA is unchanged since the last `pr_fail` (no new cell work → identical diff); a different SHA means the branch advanced and the submit proceeds. Every ambiguous case fails open.
PR operations are also **scoped per project** — `open_pr`, `pr_target`, `close_pull_request`, and `merge_pr` all require the project and resolve the PR number within it, so two tasks in different repos that happen to share a PR number can never collide and merge the wrong repository's PR.
## 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.
+3
View File
@@ -54,6 +54,8 @@ stateDiagram-v2
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.
A failed developer task is routed back to **the developer who worked it** (resolved from the work session), not the pool — so the revision lands with whoever has the context, rather than being re-claimed cold by a cell PM. Only a task no developer ever touched falls back to the pool.
## 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:
@@ -81,6 +83,7 @@ Transitions aren't suggestions; they're enforced. A handful of the rules:
- **`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.
- **A Main-PM coordination root can never be `task_type=code`.** The Main PM coordinates; it doesn't write code itself, so the combination is rejected at creation — a structural guard, not a hint.
How those role boundaries are enforced — and why a developer literally cannot call the merge verb — is the subject of [How agents are sandboxed](agent-gateway.md).
+1 -1
View File
@@ -28,7 +28,7 @@ The Grok access token has a fixed ~6-hour, server-set lifetime, and the CLI can'
- 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.
!!! warning "The orchestrator's `~/.grok` mount must be writable"
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`.
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`. If the host `auth.json` is missing entirely at spawn time, the orchestrator logs a loud warning — a missing credential is the most common Grok misconfiguration, so it's surfaced early rather than as a fleet of failed starts.
## Per-fleet tuning
+3
View File
@@ -38,6 +38,9 @@ The crucial property: **work is queued, never dropped.** Parked tasks wait; the
!!! tip "Parked is not stuck"
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.
!!! info "Escape hatch for a probe that never recovers"
Park-and-probe assumes the provider comes back. If a provider's probe fails persistently (the secret was rotated, the endpoint moved), an escape hatch releases the parked work back to the pool instead of holding it forever — so a permanently-dead provider doesn't strand its tasks. Grok auth-missing (exit 78) is parked the same way rather than crash-retried straight back into the same missing-credential failure.
## Disk housekeeping: dangling-image prune
Every agent-image rebuild leaves the previous build behind as a dangling (`<none>`) 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.
+2 -2
View File
@@ -14,7 +14,7 @@ The rules live in a per-project `.roboco/conventions.yml` with four curated part
|------|-----------|
| **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. |
| **Custom rules** | Project-specific regex rules — a pattern, a message, and a level, optionally scoped to languages. TypeScript-scoped custom rules apply to both `.ts` and `.tsx` files. |
| **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
@@ -34,7 +34,7 @@ The validator runs four check families over each changed file:
| `god_class` | A class grows past 15 methods (single-responsibility smell) | `warn` |
!!! info "Precision over recall"
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.
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 validator is also **time-bounded** — a hung run (a tree-sitter deadlock, an enormous repo) is killed after 120s and treated as `could_not_run`, so a stuck subprocess can't hang the `i_am_done` / `pr_pass` gate forever or orphan a process on restart. And if the *effective map itself* can't be resolved (a conventions-service error), the gate **fails closed** rather than silently disabling the standard for that task.
## The effective map: defaults, present, absent, or partial
+1 -1
View File
@@ -52,7 +52,7 @@ A swim-lane board of the delivery pipeline, switched with the `?view=` query par
| **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](../company/task-lifecycle.md). Switching tabs updates the URL, so a specific board is shareable.
Each board is a read-at-a-glance view of where work sits in the [lifecycle](../company/task-lifecycle.md). Switching tabs updates the URL, so a specific board is shareable. A drag that would skip a lifecycle precondition (moving a task past a gate it hasn't passed) opens a confirmation dialog first, so an accidental drop can't silently bypass the flow.
## Next
+17 -1
View File
@@ -150,12 +150,28 @@ notify(target="be-dev-1", text="Please prioritise task X by EOD.",
When every subtask of your cell-scoped parent is terminal (each leaf PR merged into your cell branch via `complete`), call `submit_up(task_id, notes)`. This opens the **cell→root PR** and moves the parent into the in-path PR-review gate (`awaiting_pr_review`), where your cell's **PR reviewer** reviews the assembled diff:
- `pr_pass` → the parent moves to `awaiting_pm_review`; you then `complete(task_id, notes)` to merge the cell→root PR into the root branch.
- `pr_fail` → the parent returns to `needs_revision` (owned by you) with the reviewer's issues; fix, then re-`submit_up`.
- `pr_fail` → the parent returns to `needs_revision` (owned by you) with the reviewer's issues; fix, then re-`submit_up`. The reviewer's verdict + issues are carried in your task handoff, so you are not blind on the rework.
Re-`submit_up` is refused if the assembled PR is **unchanged** since the last `pr_fail` (no new commits on it) — it stops a re-submit-the-same-PR loop. Fix the issues and commit before re-submitting.
You merge your own cell→root PR — the Main PM does **not** merge your cell branch. The Main PM owns the **root** task: once every cell's parent is terminal, it runs the same gate one level up (`submit_root` → main reviewer → escalate to CEO) and only the CEO merges to `master`. You never open or merge a master PR yourself.
`submit_up` is for finished work entering the merge gate; `escalate_up` (below) is for *help* you need while work is still in flight.
### Sequencing dev-task collisions
When you `delegate` a dev subtask you may pass the collision surface so the sequencing DAG orders siblings that touch the same files:
```python
delegate(parent_task_id=..., ...,
intends_to_touch=["roboco/api/routes/*.py"], # file globs
adds_migration=False, # adds a DB migration
touches_shared=True, # edits a shared module
depends_on=["<sibling-task-id>"]) # explicit ordering
```
Siblings whose `intends_to_touch` globs overlap are serialized (more-important first); migration-adders chain serially; a shared-surface edit runs after each non-shared task it overlaps. Omit these and only the weak assignee-keyed spawn barrier orders your dev tasks (the 2026-06-27 out-of-order break).
## Escalating to Main PM
Use `escalate_up(task_id, reason)` when:
+1 -1
View File
@@ -111,7 +111,7 @@ master ← feature/main_pm/{root} ← feature/{cell}/{root}/{cell-pm} ←
```
- A cell PM's `complete` merges a leaf PR into its cell branch; after the cell gate, its `complete` merges the cell→root PR into your root branch. You do not merge cell branches.
- Once every cell's parent is terminal, **`submit_root(root_task_id, notes)`** opens the root→master PR and enters the in-path gate (`awaiting_pr_review`). The **main PR reviewer** checks the assembled root diff: `pr_pass``awaiting_pm_review`; `pr_fail``needs_revision` (owned by you, fix + re-`submit_root`).
- Once every cell's parent is terminal, **`submit_root(root_task_id, notes)`** opens the root→master PR and enters the in-path gate (`awaiting_pr_review`). The **main PR reviewer** checks the assembled root diff: `pr_pass``awaiting_pm_review`; `pr_fail``needs_revision` (owned by you, fix + re-`submit_root`). The reviewer's verdict + issues are carried in your task handoff, and re-`submit_root` is refused if the root PR is **unchanged** since the last `pr_fail` — fix and commit before re-submitting.
- After `pr_pass`, `complete(root_task_id, notes)` escalates the root to the CEO (`awaiting_ceo_approval`) — it does **not** merge. A branchless coordination root (product fan-out, no repo) skips the gate and `complete` escalates directly.
- The CEO approves and merges the root→master PR from the panel. Only the CEO ever merges to `master`.
+3 -1
View File
@@ -21,7 +21,9 @@ The `pr_reviewer` role also runs the **in-path gate** on the org's OWN assembled
### Gate enforcement
When the architectural-conventions standard is enabled, `pr_pass` is refused on any block-level convention finding, the same way the developer's `i_am_done` is. When toolchain matching is enabled, `pr_pass` is likewise refused on a "broken" toolchain status. Your verdict note is a mandatory structured field (`pr_reviewer_notes`) written at `pr_pass` / `pr_fail`; it is persisted structured with a derived text mirror.
When the architectural-conventions standard is enabled, `pr_pass` is refused on any block-level convention finding, the same way the developer's `i_am_done` is — the remediation hint points you at the offending `file:line` + the `pr_fail` verb (not `i_am_blocked`). When toolchain matching is enabled, `pr_pass` is likewise refused on a "broken" toolchain status. Your verdict note is a mandatory structured field (`pr_reviewer_notes`) written at `pr_pass` / `pr_fail`; it is persisted structured with a derived text mirror.
You cannot `pr_pass` / `pr_fail` an assembled PR you authored (self-review guard, same shape as QA's). A `claim_gate_review` on your own work returns `not_authorized`.
## What You CAN Do
+5 -2
View File
@@ -22,6 +22,7 @@
- Read-only inspect git via `roboco_git_status / _log / _diff / _branch_list`
- Search the knowledge base via `roboco_ask_mentor` / `roboco_kb_search`
- Note evidence via `note(text=..., scope="...")` and `evidence(...)`
- Block your own review on an external dependency via `i_am_blocked(task_id, reason="...")` (Cell PM unblocks)
## What You CANNOT Do
@@ -41,6 +42,8 @@ claim_review(task_id) → claim for review
pass(task_id, notes) → moves to awaiting_documentation
fail(task_id, issues=[...]) → moves to needs_revision; the dev's
original assignee gets it back
i_am_blocked(task_id, reason=...) → external blocker (broken env, can't
reproduce); Cell PM unblocks
unclaim(task_id) / resume(task_id) / i_am_idle()
```
@@ -48,7 +51,7 @@ unclaim(task_id) / resume(task_id) / i_am_idle()
| MCP server | Verbs you can call |
|-----------------------|--------------------|
| `roboco-flow` | `give_me_work`, `claim_review`, `pass`, `fail`, `unclaim`, `resume`, `i_am_idle` |
| `roboco-flow` | `give_me_work`, `claim_review`, `pass`, `fail`, `i_am_blocked`, `unclaim`, `resume`, `i_am_idle` |
| `roboco-do` | `note`, `say`, `dm`, `evidence` (no `commit`, no `notify`) |
| `roboco-git-readonly` | `roboco_git_status`, `roboco_git_log`, `roboco_git_diff`, `roboco_git_branch_list` |
| `roboco-optimal` | `roboco_ask_mentor`, `roboco_kb_search` |
@@ -118,4 +121,4 @@ dm(recipient="be-pm",
task_id="...")
```
If the situation is unresolvable from the QA side (e.g. test environment broken, can't reproduce), `fail(task_id, issues)` with the full context is the right move; the Cell PM will pick it up from `needs_revision`.
For an external blocker (test environment broken, can't reproduce, missing infra), use `i_am_blocked(task_id, reason="...")` — your Cell PM is notified and `unblock`s you. If the work itself is wrong, `fail(task_id, issues)` with the full context is the right move; the Cell PM picks it up from `needs_revision`.
+1 -1
View File
@@ -54,7 +54,7 @@ A single Python CLI classifies every changed definition with tree-sitter (Python
python -m roboco.conventions check --root <repo> --files <a> <b> ...
```
It favours precision over recall — it abstains when it cannot classify a definition, so a `block` gate can never strand a task on a guess — and it fails loud: a validator that cannot run exits non-zero so the gate blocks rather than silently passing.
It favours precision over recall — it abstains when it cannot classify a definition, so a `block` gate can never strand a task on a guess — and it fails loud: a validator that cannot run exits non-zero so the gate blocks rather than silently passing. A **hung** validator is reaped after a timeout and the gate blocks the same way, and a conventions **resolution error** (effective-map build failure) fails closed — the gate is never silently disabled by an upstream error.
## Modularity
+4
View File
@@ -18,6 +18,8 @@ Don't invent channel slugs. Call `channels()` first if unsure:
channels() # -> {"writable": [...], "readable": [...]}
```
**Active-claim required (explicit `task_id`):** when you pass an explicit `task_id`, `say` / `dm` / `note` check that you are the task's **active claimant** — not just `assigned_to`, which goes stale across a reap/handoff. A reaped or reassigned agent can no longer post to a former task; if you see `not_authorized` on a content post, re-`claim` the task first (or drop the explicit `task_id` for a general channel post).
Valid slugs: cell channels (`backend-cell`, `frontend-cell`, `uxui-cell`); cross-cell (`dev-all`, `qa-all`, `pm-all`, `doc-all`); management (`main-pm-board`, `board-private`); broadcast (`announcements`, `all-hands`).
## Direct message (A2A) — `dm`
@@ -40,6 +42,8 @@ notify(target="be-dev-1", text="Task ready for you", priority="normal", task_id=
`priority` is `normal | high | urgent`. `task_id` auto-injects from the active task when omitted.
`notify` rejects **human-only recipients** (`prompter`, `secretary`) — they have no agent ack path, so an ack-required alert to them would sit unacked forever. The CEO is allowed (acks via the panel).
## Receiving notifications
Every role with an inbox gets these (so `i_am_idle()` doesn't soft-block on unread items):
+2
View File
@@ -29,6 +29,8 @@ escalate_up(
Auto-routes to your escalation target (you cannot choose it).
`escalate_up` is refused on a **terminal** task (`completed` / `cancelled`) — it returns `invalid_state` rather than resurrecting a finished task. Escalate live work only.
## When to Escalate
| Situation | Escalate To |
+1 -1
View File
@@ -14,7 +14,7 @@ Hierarchy: Umbrella (Main PM) → Root-subtasks (Main PM) → Cell tasks (cell P
- The umbrella does **no git**. It is exempt from the branch gate (it reaches `in_progress` with no branch) and you must **not** call `submit_root` on it — it assembles no PR. Each root-subtask opens and is reviewed on its own PR.
- The umbrella **completes** only when every root-subtask is terminal; then it escalates to the CEO (PR requirement waived).
- The root-subtasks are sequenced: a wave's tasks dispatch only once the previous wave's tasks reach a terminal state (ordinary dependency-gating). You do not reorder them — the analyzer set the order at create time.
- On the Board route the root-subtasks are held in `backlog` until the CEO approves the umbrella, then released to `pending`. On the Approve & Start route they start immediately.
- On the Board route the root-subtasks are held in `backlog` until the CEO approves the umbrella, then released to `pending`. On the Approve & Start route they start immediately. On Board-route activation a `code`-typed root-subtask is **retyped to `planning`** — a Main PM never owns a `code` task (the `main_pm + code` combo is the 2026-06-27 meltdown trigger).
## For the Main PM
+1 -1
View File
@@ -41,7 +41,7 @@ The claim verb both claims and starts the task — there is no separate `start`
## Claiming Rules
- **One at a time (workers only)**: Developers, QA, and documenters can't hold multiple in-progress tasks at once. **PM coordinators are exempt** — a Main / Cell PM plans and delegates many roots in parallel, so it may hold several at once; only a real upstream **sequence dependency** (an unfinished task it depends on) holds one of its roots back.
- **One at a time (workers only)**: Developers, QA, and documenters can't hold multiple in-progress tasks at once. A **blocked** task still counts as active — a blocked dev cannot `claim` a second task; unblock or `unclaim` first. **PM coordinators are exempt** — a Main / Cell PM plans and delegates many roots in parallel, so it may hold several at once; only a real upstream **sequence dependency** (an unfinished task it depends on) holds one of its roots back.
- **Self-review prevention**: QA cannot `claim_review` tasks they developed
- **Self-documentation prevention**: Documenter cannot claim tasks they developed
- **Branch requirement**: Branch auto-created on `i_will_work_on`
+9 -2
View File
@@ -45,9 +45,16 @@ Project GitHub tokens are **encrypted the moment you save them** (with `ROBOCO_E
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
## WebSocket + live-chat auth
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.
Secure mode (`ROBOCO_AGENT_AUTH_REQUIRED=true`) extends to the live streams and the content routes, not just REST:
- The **per-resource WebSocket streams** (`/ws/channels|agents|sessions|notifications/{id}`) require the **CEO panel token** — the signed `X-Agent-Token` nginx injects for the panel — on top of their `agent_id`/`viewer_id` DB validation. An agent on the Docker network can no longer subscribe to another agent's notifications with no auth.
- The **`/api/v1/do/*` content routes** require a per-agent HMAC token bound to `X-Agent-ID`.
- The **live-chat bridges** (`/prompter/live/*`, `/secretary/live/*`) require the CEO panel token — they were the last panel-facing surface that ran unauthenticated.
- **`/ws/system`** is the one exception: it stays operator-only and read-only by design (system telemetry, no client input), and is not token-gated. It is one more reason the system must sit on a trusted network: anyone who can open that socket can watch the operator stream.
A presented-but-forged token is rejected even in header-trust (dev) mode, so rolling out tokens before flipping the switch breaks nothing.
## Next