276 KiB
Changelog
All notable changes to RoboCo are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.28.0] - 2026-07-29
Added
-
The eval harness real-spawn path is wired (#701).
python -m roboco.eval runnow works end-to-end for a developer-role cohort:OrchestratorStageSpawner.__init__replaces itsNotImplementedErrorwith a realAgentOrchestrator()constructed the same way the production dispatcher builds it, sorun_stagedrives a realspawn_agentper turn. The no-production-reach guarantee lives in_generate_mcp_config, which now preferssettings.api_urlwhen set (bothPROJECT_HOST_PATHbranches) — the harness's_bench_environmentpatchessettings.api_urlto the disposable stack URL, so a spawned container's MCP servers resolve to the throwaway orchestrator instead of the real production hostname or127.0.0.1:{port}, even though_seed_companyseeds agents under their real production UUIDs (correct — orchestrator-internal helpers keyed by the static registry resolve as in a real deployment; the isolation is the URL, not the UUID). A newtests/unit/runtime/test_eval_mcp_config_isolation.pypins the guarantee without a Docker daemon. The injectable scriptedStageSpawner(tests/e2e_smoke/test_eval_bench.py) is unaffected — it injects its ownmake_spawner— and remains the unit-test fallback. The real spawn needs a Docker daemon + built agent images. -
The Board Program registry — the board finally works (#689, #699 + the Phase-2/3 program train). Board dormancy root-caused and fixed (#684: the research key never reached the container, the pitch verb was never prompted, auditor playbook curation had no dispatch path), then one generic registry/engine/loop (
BoardProgram,BoardProgramEngine, migrations 087-088) replaces the bespoke roadmap/spotlight loops behavior-preserved, adds a LEARN ledger (per-item CEO decisions feed the next cycle's exploration prompt), per-project scoping with dual polarity (projects.board_programs: plain keys opt into project-scoped programs,!keyopts out of an org-scoped program's outputs), and a single settings-store arming chokepoint (program_armed) so the panel toggle can never be silently vetoed by a legacy boot flag. Twelve programs ride it: Pest Control (PO bug hunts over the findings ledger and rework hotspots, rework-spike accelerated), Spackle (PO gap-fill audits of half-shipped surface area), Scales (PO monthly portfolio rebalance whose approvals reprioritize or cancel live backlog tasks), Dogfood (the PO walks the product with task-scoped browser tools), Periscope (HoM market briefs with mandatory source citations, feeding the roadmap prompt), Megaphone (HoM editorial calendar into the X queue), Mirror (HoM quarterly positioning audits), Barfly (HoM replies to adjacent X conversations, injection-screened, candidate-id-bound), War Room (HoM release campaigns as manual-cadence held drafts), Coroner (Auditor postmortems on bounce/cancel/budget events, drafting process-change playbooks), Librarian (Auditor playbook mining from recurring learnings), and Sentinel (Auditor weekly drift-watch quality reports). Every artifact is held; the CEO remains the only path to materialization; nothing auto-posts or auto-starts.
Fixed
- release-post drafts stop parroting changelog bullets. The X announcement drafter fed the changelog's bold leads to the local model in document order — Security first, so the WAF plumbing line became the tweeted headline — and its deterministic fallback template quoted the first raw bullet verbatim. Highlights now reorder to marketing order (Added/Changed features before Fixed/Security plumbing), the prompt bans verbatim highlight copying and internal jargon, and the fallback is a generic ships-announcement that can never quote a bullet.
[0.27.0] - 2026-07-25
Security
- The WAF now resolves the real client behind host-proxy hops instead of banning the hop (#646, #650). With Tailscale terminating on the host, every panel request reached the guard as the host-gateway IP, so one bad signature banned the operator's whole path. A dedicated
ClientIpResolutionMiddlewarestamps guard-core's pre-resolution seam with the true tailnet client — but only for the exact operator-declared chain shape (ROBOCO_GUARD_TRUSTED_CHAIN_PEERS, now actually wired through the compose files); every other chain abstains to the baseline depth-1 X-Forwarded-For posture, so nothing becomes spoofable that wasn't. The live-incident follow-up (#650) deliberately whitelists the tailnet CGNAT range after the honest resolution got the CEO's own device banned ("IP not allowed"), and the internal-mesh WAF exemption (#605) is scoped down to loopback + the docker bridge (scope the internal-mesh whitelist to loopback + docker bridge) rather than all of RFC1918. - Destructive git operations gained an operator-declared floor.
projects.protected_branches(#649, panel chips editor in the edit-project dialog) is unioned — only ever tightening — into the hardcoded{master, main}floor for rebase/force-push refusal and remote-branch deletion; the deletion chokepoint additionally protects every env-ladder rung branch (#651), fails closed on a lookup error, and covers all three remote-delete paths (task cleanup, stale-branch sweep, post-merge source cleanup).
Added
- Telegram Mini App V6 — the premium overhaul (#609). A native-type design system on the
#tg-shelltokens (borderless elevated cards, wallet-style tabular-numeral heroes, floating dock) with Telegram window-chrome painting riding the theme bridge; Inbox moves behind a header bell as a pushed sub-page with humanized notifications; a new Metrics tab (period-segmented spend hero, by-agent/team/model splits, delivery/efficiency, per-agent drilldowns over the previously-untapped time-series); Chat rebuilt with honest scopes — a participant-scoped Mine list with real unread counts and plain CEO send, a watch-or-interject Fleet list, and a pinned Secretary live chat on the same SSE runtime the panel drives; and the Board task sheet carries the CEO's own decide verbs (approve / request-changes / unblock). The/api/dashboard/*router is now token-gated at router level, closing an unauthenticated metrics exposure. - Codex and Gemini join the agent-provider roster (#659, #660).
ModelProvider.OPENAIruns OpenAI's officialcodexCLI on a ChatGPT subscription: read-only~/.codexdirectory mount with an orchestrator-side refresh loop for the single-use OAuth refresh token, per-role--sandboxlevels plus a shared execpolicy rules file standing in for the missing tool-scoping flags, the fleet-wide subagent ban expressed as[agents] enabled = false, structured error-field-only exit classification (no false parks on transcript prose), and real four-bucket token capture fromturn.completedevents; the agent image installs the CLI via npm (install codex CLI via npm, not the CDN-denied installer).ModelProvider.GEMINIruns Google's officialgeminiCLI on an OAuth login: the host~/.geminiis staged read-only and copied container-local so the CLI's own token refresh writes locally with no orchestrator daemon (Google's refresh tokens are reusable — the deliberate contrast to grok/codex), tool scoping rides a rendered TOML Policy Engine plussettings.json, quota/auth exits park the provider exactly like grok's, and per-model usage pricing reads the run's own stream-json terminal stats. Both are V1 delivery-roles-only: the Intake and Secretary chats stay on Anthropic even under a fleet-wide mode switch (#661), and an explicit per-agent pin to either is refused loudly at spawn instead of silently misbehaving. - Cost-tiered model routing + saved routing presets (#656). A compound
role:complexityassignment rung slots into the routing precedence (AGENT_SLUG > ROLE:complexity > ROLE > GLOBAL) so a role can ride a cheaper model at a given task complexity without touching the plain role row; the opt-incost_tieredmode seeds exactly one day-1 override (developer:low→ haiku) additively — every other mode's wipe-then-seed behavior is unchanged, and mode switches now spare complexity rows. On top, routing presets let an operator name-and-snapshot the full routing state (mode + every assignment, per-agent pins included) and restore it wholesale later — validate-every-entry-first, so an invalid payload never triggers the wipe. Panel: preset save/apply/delete plus one-click Codex/Gemini mode cards on the AI routing settings card (#661). - Per-task and per-project cost budgets, flag-gated (#654).
tasks.budget_usd+projects.monthly_budget_usdunderROBOCO_TASK_BUDGETS_ENABLED(default off): a claim-time project-month spend guard applies only to work-starting claims (reviews/docs/gates stay exempt so in-flight work can always finish), the orchestrator's budget sweep blocks an over-budget task before the graceful stop so the dispatcher never respawns onto it,unblockre-checks live spend, and the CEO notification names both recovery steps. Server-sidegt=0validation rejects the self-DoS zero cap (#661). - Golden-task eval harness (#655).
roboco/eval/replays fixture tasks through the real delivery lifecycle in a disposable environment (fake GitHub, real local git origin, throwaway DB) and scores each on deterministic metrics plus a clearly-marked non-deterministic local-model judge;agent_spawn_sessions.doctrine_versionstamps each spawn's composed-prompt doctrine so a cohort's model+doctrine combination is identifiable after the fact. Real-spawn is deliberately cut this release (scripted spawner only) — a real spawn would wire into the production orchestrator. - Work re-claims inherit an advanced upstream base (#644). When a developer or PM re-claims a pre-existing task branch whose base has moved on, the claim merges the upstream base in (pending/needs_revision claims only — QA, documentation, and gate claims never move the branch); a merge conflict surfaces as a
base_inheritance_conflicttransition note plus a dev note pointing atsync_branch(#661). - GitHub App authentication (#621, #633). Projects can authenticate through an installed GitHub App instead of a PAT: an App credentials card, installation-token minting with the PAT fallback covering every mint failure (PAT fallback covers every mint failure; video/motion/x cleanups), a Select-repo picker on project creation, and binding an existing project to the App from the Edit dialog.
- The X account grew a craft bar and a rework loop. Drafted posts follow a real voice guide with an anti-slop ban and caption craft (#615), release captions compose from curated CHANGELOG headlines instead of raw commit subjects (#607), and a CEO reject with a reason now re-enters the draft flow as a redraft carrying the feedback (#648) instead of dead-ending the draft.
- CEO ergonomics. The CEO display name is configurable and the last Renzo hardcode is gone (#612), with the settings write-through renaming the CEO agent row (ceo_name write-through renames the CEO agent row); the CEO can open DMs to the Auditor and PR reviewers (#623); the Secretary and Intake agent cards open their chat screens directly (#624); and the CEO can preview a video authoring task's rendered frames before approving (#608).
- Codegen drift heals itself before push (#632). The push path auto-regenerates generated artifacts, stages only the codegen-produced drift, and links the auto-commit to the task (stage only codegen-produced drift and link the auto-commit).
- One-command quickstart (#653).
make quickstartruns the idempotent bootstrap: a fresh.envfrom the example with the three required secrets injected using the documented formulas, registry pull + up, and a doctor-style readiness sweep grounded in real surfaces — each stage failing loud with the exact next command; the release workflow's new pull-smoke job literally pulls the registry compose against every just-published tag. - Off-disk backup mirror (#645). The backup sidecar can mirror each dump to a second disk (
ROBOCO_BACKUP_MIRROR_DIR, env-gated), with a quarterly restore-drill doc. - Panel consistency & UX wave (#694). A global Cmd+K command palette (
components/layout/command-palette.tsx) searches tasks/agents/projects/pages with recents + fuzzy match, wired into the header search box. The Tasks page gains top-level List|Kanban tabs sharing the page's URL filter state, and the standalone/kanbanroute now redirects there — its sidebar entry is gone and the bottom tab bar's kanban slot swaps to Agents. A forms-consistency audit synced the create/edit project and task dialogs against their backend schemas (docs/frontend/forms/forms-audit.md), and a touch-target/responsiveness sweep bumped every sub-36px icon control across the panel to the accessible floor, fixedAlertDialogclipping at short viewport heights, and wrapped the Scorecards Members table inResponsiveTable. - Project settings is a full page, not a dialog. The edit-project dialog carried ~30 fields across 7 concerns in one flat scroll with a per-tab width swap — outgrown. Project settings now live at
/projects/[id]/settingsas a card-per-concern grid (the settings page's own pattern) with per-card save and Conventions as a page-level tab at natural width; the list Edit action routes there, and a slim quick-edit dialog (name/cell/active) replaces the kitchen-sink (promote project settings to a full page).
Changed
- The
opusalias upgrades from Claude Opus 4.6 to Claude Opus 5.MODEL_MAP["opus"]now resolves toclaude-opus-5— the newest Opus tier, released 2026-07-25 at the same $5/$25 price — so every Opus-routed role (PR reviewer, Auditor, Board, CEO, Intake, Secretary) rides it at next spawn. The pricing table gains a dedicatedclaude-opus-5fragment (theclaude-opus-4substring doesn't cover it — without the row, Opus fleet usage would silently cost-track as $0), and a newtest_opus_is_pricedguard keeps the alias from ever pointing at an unpriced model. - Panel dialogs converge on one shape: one disclosure primitive, DialogFooter everywhere, three dialog widths.
collapsible-sectionbecomes the single sectioned-disclosure primitive — task dialogs' rawCollapsibleand create-project's ad-hocshowAdvancedboth move onto it; every hand-rolled dialog footer becomesDialogFooter; dialog widths collapse from ten ad-hoc classes to three named sizes, with deliberate outliers annotated. No behavioral change.
Fixed
- docs-redirects-only commits fire a CI run so the release gate gets a verdict. The CI paths filter covered docs/panel/motion-only slave commits but not
docs-redirects/**, so a redirect-stub commit landing as the slave tip produced no CI run at all — and the fail-closed release-readiness gate read the missing conclusion as "unknown", silently holding back every release proposal until an unrelated code commit happened to land. - Mix-mode routing gained its escape hatch (#663). A fully-pinned fleet had no way back to a global mode: mode switches deliberately spare per-agent pins, but the server refused an empty mix map and the panel refused an empty save — so with every agent pinned, the mode buttons were permanent no-ops behind a success toast and the mode label read "mix" forever. An empty per-agent map is now the explicit clear-all, Save mix with nothing picked confirms and clears instead of erroring, a Clear-all button resets every agent to inherit-global in one click, and the Routing-mode section warns when per-agent overrides outrank the mode buttons.
- The lazy DB engine rebinds per event loop (#663). The global engine cache was loop-agnostic while its pooled connections are loop-bound, so a second event loop touching it (a test's
asyncio.run, a second server thread, the eval bench's disposable stack) eventually drew a foreign-loop connection and died with "Future attached to a different loop" — the eval-bench smoke's flaky CI timeout. The engine/factory accessors now stamp the owning loop and discard-and-rebuild on cross-loop access; production runs one loop, so nothing changes there. - Expired-unacked notifications re-escalate with backoff instead of forever (#652). The re-escalation sweep fired on every ~60s tick for every expired unacked row — 3 escalations/minute to the CEO in the live incident. Each row now carries a backoff schedule (first fire at expiry, doubling from a 1h base capped at 24h, permanently parked past 5 attempts), and the attempt slot is claimed by compare-and-set before any delivery, so racing sweep ticks can never double-send.
- Post-finale completeness sweep (#661). The gap sweep over the provider/routing program: Codex/Gemini apply-modes and panel mode cards existed nowhere (the features were unreachable from the panel), the Gemini provider row was seeded disabled with no enable path (migration 086),
ROBOCO_GUARD_TRUSTED_CHAIN_PEERSandROBOCO_TASK_BUDGETS_ENABLEDreached no composeenvironment:stanza (dead on arrival), the Codex path was missing the fleet-wide subagent ban, budgets accepted a silently-blocking0cap, and the one-click Codex/Gemini modes would have killed the Intake/Secretary chats. - Notification plumbing hardening. Notifications render task titles and agent names instead of raw UUIDs (#616); every singleton-role lookup tolerates duplicate rows through one shared helper (#620, one duplicate-tolerant role lookup for every singleton-role site); ack writes can join the caller's transaction so a bell ping can't be lost to a foreign-key race (#603); and the orchestrator no longer respawns agents off their own notification churn (#643).
- Silent bug sweep across concurrency, state integrity, engine edge-cases, and panel data freshness (#638), plus the V6 review-gap fixes (V6 review gaps — honest errors, safe secretary start, real tests) and the release-approve write committing under the release lock (commit the approve() COMPLETED write under the release lock).
- Panel accuracy basket. Branch lists classify remote refs correctly and prune stale ones (#610); charts get real axes, humanized ticks, and dark-theme tooltips (#611) and days-view timeseries show dates instead of
02:00(#622); the AI Providers mix grid derives from the live roster (#613); forms catch up with the backend (#614); and A2A transcripts fall back to polling when the socket drops (#639). - Telegram Mini App responsiveness (#647). The missing
viewportexport meant safe-area insets were zero on notched phones; scoped to the/tggroup along with truncation fixes, contained video, and ≥36px touch targets. - Intake/Secretary chat replies survive a gated CLI (#665). The chat driver treated stream-event text deltas as the only text channel, so when the CLI's partial-message emission is server-side gated (observed live — identical binary/flags/model, zero
stream_eventlines), the reply was generated but never rendered and the CEO saw nothing.SdkIntakeSessionnow emits anAssistantMessage's complete text whenever no delta arrived during the turn; streaming mode is unaffected. - Telegram cockpit reported wrong numbers three ways (#666). GLM 5.2 now gets a grounded per-token rate instead of a silent $0.00 for a full fleet day of usage; the cockpit's spend/velocity series bucket by a configurable
ROBOCO_DISPLAY_TIMEZONEinstead of UTC; and agent activity ("active: 0, working: []") is finally real — every claim path now marks the claimantACTIVEwith acurrent_task_id, and every release path clears it. Task budgets also became explicit-input only: the per-TaskTypedefault cap table (which blocked an unbudgeted coordination root one opus planning turn in) is gone — a nullbudget_usdis genuinely uncapped. - Fleet PRs authored by a GitHub App bound as
[bot]were reviewed as external (#668). A bound GitHub App authors fleet PRs as<app-slug>[bot]withauthor_association=NONE, which the inbound classifier's author heuristics read as an outsider — the org's own dev-stream PR was ingested for adversarial external-PR review. Ownership now classifies BEFORE the author check: a same-repo PR whose head branch an active task owns is never treated as external, and the ownership scope widens to every project sharing the polled project'sgit_url(a monorepo's sibling cell-projects). A deleted-fork head now fails closed to review instead of risking a silent skip. - Tool-call budget caps reached no compose file (#672).
ROBOCO_AGENT_TOOL_CALL_HALT/_WARNwere read by the SDK server and defined in config but wired into no composeenvironment:stanza — the code default (halt at 300) repeatedly killed and respawned a legitimate multi-file task in 300-call slices. The build composes now default to halt=600/warn=200, matching the already-patched NAS; the registry compose passes them through unset. - Board wiring, revived: research key, pitch flow, auditor playbooks (#684).
ROBOCO_RESEARCH_API_KEY/_PROVIDERandROBOCO_PROVISIONING_TOKEN/_ORGreached no compose environment stanza, so research silently ran on the NullProvider and an approved pitch always died onProvisioningDisabledError; both now pass through. A proposed pitch nudges the CEO (notification + Telegram link to the Pitches tab);auditor_triagenow surfaces the oldest pending playbook draft once anomalies are clear, closing the loop on a curation queue nothing ever pointed the Auditor at. - A haiku-pinned QA/PM looped instead of reviewing (#680). Haiku can't reliably emit the structured envelopes the lifecycle now runs on (
pass_review's per-ACcriteria_verified,delegate'scovers_parent_criteria, the findings ledger) — a haiku QA claims, gets validation-rejected, idles, and respawns without ever passing a review.ROLE_MODEL_MAP's qa/documenter defaults move to sonnet, thecost_tieredmode'sdeveloper:low→haiku seed retires to empty, and a structured-verb capability floor now upgrades any below-floor Anthropic assignment to sonnet at resolution regardless of source. - The sequence claim bar could phantom-hold an unrelated sibling (#681).
stamp_wave_sequencestamps each sibling's wave from a partial, per-task view of the graph at delegate time, so two independent same-parent dev-task streams could coincidentally land on the same raw sequence number — and the old edge-agnostic bar held a wave-N sibling behind a totally unrelated wave-(N-1) sibling from a different stream. Outside MegaTask batch root-subtasks (which keep the original strict, staged-release rule), the bar now only lets a lower-sequence sibling block when it's a real dependency-graph predecessor. A held claim also returns a dedicatedsequence_helderror naming the blocker instead of a bare rejection the verb runner misdiagnosed as a concurrent-transition error. - An acceptance-criteria rewrite could silently waive the whole subtree's coverage gate (#682). Every post-create rewrite of
acceptance_criteria(the task PATCH route, the prompter's draft/batch update paths) overwrote the criteria text without touchingacceptance_criteria_ids, leaving them mismatched or empty — and an empty id list disabled the parent-coverage gate entirely.create/updatenow reconcile ids through one pure helper (text-unchanged criteria keep their id so live child/finding references never orphan); the coverage gate also self-heals an already-broken id list in place instead of silently waiving coverage. - A rebase could silently discard a developer's committed-but-unpushed work (#683). The shared rebase primitive (
sync_branch, the PM/CEO rebase route, submit-time freshening, the merge-conflict resolver) opened with an unconditionalreset --hard origin/<branch>— since thecommitdo-verb never pushes, a dev routinely has commits that reset would rewind past, and the follow-on force-push then republished the truncated branch as authoritative. The primitive now classifies local vs. origin post-fetch: behind/equal resets as before, strictly-ahead skips the reset and rebases from the local tip, and a genuine two-sided divergence (checked for patch-equivalence first, so a prior rebase's failed force-push self-heals on retry) refuses outright with neither side touched. - An escalate/unblock round trip could oscillate a task forever (#685). A cell PM's
escalate_up→ BLOCKED → a main PM'sunblock→ respawn →escalate_upagain loop had no fuel limit: the restored side dispatches through a path with no respawn counter at all.unblock()now counts strikes task-scoped (a cheap commit-count/revision_count/terminal-children progress fingerprint resets the count on real forward motion); past threshold the task is blocked for a human with both agents and the cycle count named in the CEO notification, and the dispatchers actually see the block now thatblocker_resolver_typeserializes over the wire. - A rejected
delegatecall named the missing field but never showed its shape (#686). A child draft rejected for acovers_parent_criteriaref that doesn't resolve now gets a copy-pasteable correcteddelegate(...)skeleton with the parent's real criteria inlined — a PM no longer loops on a rejection it can't act on. The coverage-reject path also self-heals a legacy parent whose criteria ids are empty or out of length before rendering the hint, and the plainpauseroute now authorizes the CEO tier like its sibling lifecycle routes. - A finding's
filecould be prose instead of a path (#687). A finding withfileset to something like a PR reference validated cleanly and then doomed the panel's code-snippet fetch.fileis now shape-gated to look like a repo-relative path (narrative belongs inevidence, and the remediate says so); the Findings tab groups findings into per-round collapsible sections and only attempts a snippet fetch for a path-shaped ref. - A reviewer's clone could stay frozen on pre-rebase history across every review round (#690).
diff/list_changed_files/read_file_at_branch(and the read-onlyroboco_git_logroute) kept local-ref priority on any divergence from origin, real or rewritten — a QA/PM/reviewer clone parked before a routine force-push sync stayed there forever, and QA repeatedly bounced work that had already landed. Every caller here is a reader, never the branch's own author mid-write, so origin now wins whenever it carries anything the local ref lacks. - A per-task worktree was created once and never refreshed again (#692). A worktree from a reviewer's first
claim_review/claim_gate_reviewstayed frozen at that commit across every later respawn even as new commits landed on origin — the mechanism behind a live multi-round QA/PR-gate bounce loop.ensure_worktree_self_healnow refreshes an already-present (or re-added) worktree against origin at every spawn, role-aware: an author's (developer/documenter) dirty uncommitted edits are never discarded to fast-forward, and only a pure reader's diverged copy gets hard-reset.
[0.26.0] - 2026-07-20
Security
-
Orchestrator API is no longer published on a routable host interface (GHSA-4f7g-w95g-5q2c). Both deploy composes published the orchestrator's
:8000on0.0.0.0, so anyone who could reach the host hit the control plane directly — bypassing nginx and, in the default header-trust posture (ROBOCO_AGENT_AUTH_REQUIREDunset, cloud auth off), reading/writing runtime settings and spoofingX-Agent-Role: ceoto spawn/stop agents with no credential. The publish is now bound to127.0.0.1; nginx reaches the API over the internal Docker network, so normal operation and on-host debugging are unchanged, while off-host access must go through nginx + cloud auth. The header-trust design itself is unchanged (it stays the deliberate local-no-login panel path); this closes the unintended off-host reachability that gave it teeth. -
fastapi-guard flipped to active enforcement, with its prose validators calibrated and HTTPS enforcement removed from the app layer.
ROBOCO_GUARD_PASSIVE_MODE's default flips fromtruetofalseindocker-compose.yml/.yaml(the registry compose already omits the guard trio and stays off) — the deliberately deferred step from the original guard build, now that cloud auth + Tailscale are armed. Activation exposed two calibration defects, both fixed before this ships:enforce_httpswas keyed offenvironment == "production", but TLS is nginx's layer (the single entry point — the app only ever sees proxy-HTTP), so the check blocked a plain-HTTP-behind-Tailscale production deploy's entire request stream and is now hardcoded off at the guard config; and the two custom content validators were never calibrated against RoboCo's own vocabulary — the secret-exfil env-key pattern now requires a real base64-shaped value (the documentedROBOCO_ENCRYPTION_KEY=<your-fernet-key>placeholder no longer blocks a commit/note) and the prompt-injection override pattern requires the second-person "your" (neutral engineering prose about the guard subsystem passes; real injections still block). -
can_a2a_directrefuses a CEO DM to a no-comms-role target. The CEO's asymmetric "may DM any agent" branch returnedTrue, Noneunconditionally, bypassing the non-DM-role exclusion (auditor, pr_reviewer, prompter, secretary — none carrydm/read_a2aon their manifest) that the panel's New-DM dialog only enforced client-side. A conversation to one of these roles is now refused atget_or_create_conversationwith a clear reason instead of silently creating an unreadable/unackable thread. The refusal set (NO_COMMS_ROLES) is now canonical inroboco/foundation/policy/communications.py, reused by both this check and thedm()sender-side guard incontent_actions.pyso the two can't drift apart. -
adm-zip overridden to >=0.6.0 in the motion toolchain (Dependabot alert 88). The transitive
adm-zip <0.6.0allowed a crafted ZIP to trigger a 4GB memory allocation; a pnpm override pins the patched version without unpinning the deliberately-held producer. -
Registry compose fails closed on missing auth and arms registry authentication by default. A registry deployment with unset credentials now refuses to come up in production instead of silently serving unauthenticated.
Added
- Telegram Mini App V4 — the phone cockpit becomes a real client (#576, #582). The
/tgsurface opens on a Today brief served by one DB-only round trip (GET /telegram/today): needs-you items, held-draft counts, fleet with per-agent task titles, day-rollup spend with a live sparkline, weekly shipped-task velocity, and ship state. The Approvals tab is a native card stack over all four held-draft queues riding Telegram's MainButton/BackButton/haptics (with visible fallbacks), including X-draft editing with a 280 counter, a blob-fetched video player, and a per-AC release view; Chat and Today refresh live off the shared/ws/systemsocket with a poll fallback; the shell adopts the user's TelegramthemeParamsscoped to#tg-shell. The bot tier grows/agents,/usage, and/blockedread commands off the singleBOT_COMMANDSregistry (synced to Bot APIsetMyCommands), and/secretary+/newtaskbridge the chat into the same in-process live intake/secretary runtimes the panel drives — a drafted task becomes a Send-to-Board/Discard keyboard, and board feedback streams back into the thread. - Telegram Mini App V5 — brand voice, custom iconography, and an operations ring (#583). Share Tech Mono (the vendored motion-brand face) becomes the cockpit's display voice via
next/font/localscoped to#tg-shell; a hand-drawn duotone icon set replaces stock linework on the tab bar and action surfaces; and the Today ring stops duplicating the tab bar to become real operations — Ship deep-focuses the open release proposal, Ack all bulk-acknowledges pending notifications, Sweep runs the stale-branch cleanup across every git-configured project behind a confirm sheet, and Fleet opens the full working roster. The Board gains a tap-through task sheet (status, bounce chip, acceptance criteria, open ledger findings, PR link) on a newTgSheetprimitive with native BackButton dismissal; motion lands dependency-free (spend count-up, tab rise-in, staggered sections, sparkline draw-in) and reduced-motion-safe; Board and Inbox join the/tg?demo=1fixtures. - Forge program: GitHub, Gitea, and GitLab are first-class forges (#569, #571, #575, #579, #581). The REST surface (PRs, CI status, reviews, labels, releases) is provider-routed: a ~20-method
GitProvidertransport contract,GitHubProvider/GiteaProvider/GitLabProviderimplementations, and a per-callForgeRouterkeyed onRepoRef.host, with both non-GitHub providers adapting their wire contracts back into the GitHub shapesGitServiceclassifies (Gitea status-to-check-run reshaping and slash-encoded refs; GitLab MR/pipeline reshapes, per-file diffs reassembled into unified text, approve-vs-note review routing). Neither has GitHub's server-side merges API, sosync_env_branchrides a shared local-clone merge fallback that aborts on conflict with the remote untouched. Projects opt in viaprojects.git_provider(migrations 075/076; gitlab.com auto-detects, self-hosted instances register explicitly, plain-http LAN forges supported); pitch provisioning reaches parity across all three; and env-gated live contract suites run against a dockerized Gitea and gitlab.com — both caught real gaps before ship. - Telegram bridge V2 + V3 (#551, #554, #568). V2 makes the bridge two-way: a
getUpdateslong-poll loop authorizes every update by both chat id and sender id and routes/status,/queue,/task, and Approve/Reject/Open inline-keyboard taps through the same CEO-gated service calls the HTTP routes use, stampingvia=telegramaudit rows; stale Approve/Reject buttons can no longer re-execute an already-decided draft. V3 adds the Mini App sign-in:POST /api/telegram/webapp-authvalidates Telegram's signedinitData(HMAC, constant-time, freshness-windowed) against the stored bot token and the CEO's chat id, then mints the same cloud-auth session cookie as/api/auth/login. All bot messages get real HTML styling with mandatory escaping at every interpolation, and every held-draft origination pushes a styled DM with its Approve/Reject keyboard the moment it materializes. - Agents hub + CEO-grade A2A (#547, #558, #559). The Agents page becomes a hub: a Fleet tab with the roomier card grid, a Conversations tab absorbing the
/a2aswitchboard (CEO pairs join the matrix, sections collapse), and a Journals tab; a DM quick-action opens a CEO conversation from any agent card, and a New-DM composer with a CEO-DM wake path replaces the old read-only surface. - Workstation + customizable Overview quick actions (#549, #556, #557). Products and Projects unify behind one Workstation sidebar entry with card grids, a view toggle, and sorting on the agent-card visual language; the Overview dashboard's quick-action row becomes user-customizable and server-persisted.
- Video craft program (#543, #544, #550, #552). A HyperFrames-grade design bar with four vendored reference bodies and a 133-entry catalog index sets the motion-graphics bar; choreography engines land alongside two renderer root-cause fixes (the broken producer release pinned back with its lockfile, clip windows rebuilt on structural layers so the clock can't lag); the three craft capabilities are wired into the video-authoring dev's prompt; and release-recap beats compose off clip windows onto base-hidden delayed animations.
- Git hygiene sweep (#548). Terminal task completion/cancellation force-deletes the task's local branch ref and
.previews/render dir in the assignee's clone; a PM/CEO can sweep older backlog branches project-wide viaPOST /git/branches/cleanupor the Git page's button; work sessions move under Git with panel charts. - Marketing drafts carry the deployer's product, not ours (#570). X posts, replies, feature spotlights, and video briefs thread
company_goals.company_name(falling back to RoboCo) through every generated-copy path, and the X/video queues show per-draft project badges; release and spotlight drafts record their source project. - The intake finally uses its task-history digest (#592). The prompter's injected history (built since the digest pipeline landed) now comes with explicit instructions — don't re-propose shipped work, cite prior related tasks by short id, let history inform batch sequencing — and cancelled tasks are excluded from the digest so dead work can't pose as precedent.
- Taste-skill part 2: niche-aesthetic vocabularies + image direction (#584). Industrial-brutalist / minimalist-editorial / premium-agency vocabularies keyed onto the existing design-bar dials reach both FE and UX/UI team prompts; a ux_ui-only image-direction section covers composition variety, palette discipline, anti-slop imagery, and mockup conventions.
- Delegation detail-fidelity + PM-loop hardening (#541). Details stop thinning at hand-off in either direction:
delegaterefuses children that don't map onto the parent's real acceptance criteria, and QA's pass requires a per-criterion verified stamp; the PM ping-pong respawn loop gets its breaker hardened. - Wave-1 quick wins (#546). Human agent names across the panel, scroll bounce-back fixed, chart empty states, model-pin preservation on mode switches, and UUID spawn normalization.
Changed
- Panel performance: virtualized kanban, memoized rows, batched scorecards (#594). The kanban card lists render through
@tanstack/react-virtualwindows (columns stay the dnd drop targets, so drag is unaffected) with memoized columns/cards and a ref-stabilized action handler; the task table's desktop row and mobile card are extracted and memoized; and the per-member scorecard N+1 (~20 requests per poll) collapses into oneGET /api/dashboard/metrics/membersbatch backed by grouped SQL shared with the single-agent path. - Project-agnosticism residue closed (#587). The docs-site repo/URL becomes config (
ROBOCO_DOCS_SITE_*, defaults unchanged); the Main PM prompt no longer asserts our repo layout as fact; PR labels deriveto {base}from the PR's real target branch instead of literal master/slave; the stale hardcoded headcount leavesbase.md; and the bash-guard's Makefile check requires an actualquality|gate|lint|testtarget before denying raw package managers, ending the false-remediation loop on Go/Rust Makefiles. - The test suite is hermetic and order-independent (#591, #595, #598, #594). Four stale Phase-0 tests asserting GitLab rejection (false since the provider landed) flipped to assert acceptance; the lazily-created global DB engine is disposed after every test, killing the wandering "Future attached to a different loop" class; an autouse fixture pins environment/encryption-key/cloud-auth/guard posture to schema defaults so a deploy-flavored
.envcan't fake failures (174 phantom local failures under an armed operator env → 0); and the new batch-scorecard tests use unique seeds with delta assertions for the shared one-process CI database. - Dependencies: selective updates with the Next family held at its 16.1.x pin (#560), plus routine Actions bumps.
- Docs caught up to the code. The agent-facing RAG corpus got a 17-page gap sweep (#596) — including real doc bugs: the QA pages named non-callable tool names, and a page described a field deleted from the codebase — covering required AC-coverage mapping, per-criterion QA stamps, the collision map, the possibilities-matrix fast path, auditor curation verbs, and forge/env-ladder semantics. The exhaustive codebase map got 15 slices refreshed plus
_complete_map.mdregenerated after drifting behind its own sources (#597). The compose twins are resynced under a quality-gate guard with cloud-auth env wired through (#555).
Fixed
- Notification
expires_atis now stamped at creation, so the ack-required re-escalation sweep can actually fire.NotificationService._create_notificationbuilt every row withoutexpires_at, soNotificationDeliveryService.sweep_expired_notifications'sexpires_at < now()query always matched zero rows — every notification was effectively immortal, and a stuck ack-required notification never got re-escalated to the recipient's up-role. Ack-required rows are now stampednow() + notification_ack_ttl_hours(new setting, default 48h,0disables stamping); informational (non-ack-required) rows are leftNULLsince the sweep never touches them. - Python quality gate: restored green on roboco-api@slave (CI run 29653535468). Two independent code-level bugs broke the 'Python quality gate' job, not a dependency pin (pydantic-settings was already correctly pinned at 2.14.2). ①
roboco/services/git.py:_delete_remote_branch_best_effortwas typed to returnboolbut its success path fell off the function end with no return statement, failing mypy'smissing-return-statementcheck and silently returningNoneinstead of the documentedTrue— added the missingreturn True. ②roboco/services/company_goals.py:CompanyGoalsService.upsert's six repetitiveif key in data: row.key = data[key]branches pushed the module's average cyclomatic complexity past xenon's--max-modules Agate — refactored into a data-driven loop over a_MUTABLE_FIELDStuple (behaviourally identical, now rank A). Verified by reproducing all three CI steps locally on the slave-branch worktree:uv sync --extra dev,alembic upgrade head(all 76 migrations apply cleanly), andmake quality(13,510 tests, 94.56% coverage, all gates green). - The Telegram Mini App no longer gets hijacked to the login page (#600). The globally-mounted agent-roster sync fires
/api/agentson every surface; on/tgit raced the initData sign-in, took the cloud-auth 401, and the client's blanket 401-interceptor bounced the webview to the password/loginpage it can't complete — landing it on the dashboard. The(tg)surface owns its auth UX and is now exempt from the global redirect. - A plain browser at
/tgin production sees the Open-from-Telegram wall instead of posting an empty initData payload that 422s into a "Couldn't sign in" error. /auth/loginno longer 422s (#580). FastAPI had demoted the db dependency to a query parameter on the login route.- Task cancellation closes the task's own PR, and the bulk branch sweep spares live dependents (#593). Every cancel path now best-effort-closes the recorded open PR for the task and its cascaded descendants; the stale-branch sweep excludes branches still recorded by a non-terminal task or serving as a live child's resolved parent branch.
- Release/readiness hardening basket. The root PR base resolves the project's env ladder instead of literal master; version detection accepts manifest variants and never crashes the sweep; unconfigured social platforms are skipped instead of becoming pending-forever failures (#545); the readiness sweep's CI wait polls the prod rung; and check-runs dedupe per name so a cancelled duplicate can't mask a green run.
- The active guard no longer IP-bans the internal agent mesh. With the guard active, an agent's journal/note body tripping a WAF signature banned the whole container's docker-bridge IP, and every subsequent gateway verb from that agent (dm, i_am_idle, claim_review) was blocked by ip_security — wedging the agent into a respawn loop (2026-07-20). The guard's WAF/threat-ban is for the external attack surface arriving through nginx; the internal RFC1918/loopback mesh (authenticated agents reaching the orchestrator directly on the docker bridge) is now guard-whitelisted. External traffic still carries the real client IP via X-Forwarded-For (trusted-proxy depth 1, un-spoofable into a private range) and is fully scrutinized.
- The release-proposal bell notification survives its own transaction.
send_ack_notificationinserted the notification row through a fresh session while the proposal task sat uncommitted in the origination engine's transaction, so therelated_task_idFK rejected it and the panel-bell ping was silently lost (the Telegram DM, which is DB-free, still went out). The service now accepts the caller's session so the insert joins the same transaction, and the release engine passes it. - Panel/ops hardening basket. Session links target the owning task (the
/work-sessions/<id>route never existed); PM review turns are restart-safe; dialog triggers behind tooltips fire again and dotted composition ids render;git pullon a target branch became a hard sync to origin; thegit_providermigration renumbered to 076 restoring a single Alembic head; CI-watch fixed its own regression on roboco-api (#563); agent commits no longer carry model self-attribution; and cockpit CI assertions became delta-based so the one-process run's row leaks can't flake them (#577, #578).
[0.25.0] - 2026-07-16
Added
- Env-branches ladder + EnvSyncEngine (default-off
ROBOCO_ENV_SYNC_ENABLED). Replaces a project's singledefault_branchwith an ordered environment ladder — nullableprojects.environmentsJSONB (migration 073): index 0 is the head rung (where dev/cell/leaf PRs land and the per-agent clone points), index -1 is the prod rung (where the gated release executor commits + tags). A null ladder degenerates to a single-branch ladder synthesized fromdefault_branchat read time (roboco/models/env_branches.py), so every existing project keeps behaving byte-for-byte until the CEO declares a real split. The release executor promotes the full chain head→…→prod before bumping (fail-closed on a merge conflict) and release-readiness diffsprod..headwith a tag-drift cross-check;EnvSyncEnginecascades the ladder prod→…→head via GitHub's merges API, auto-pushing a clean merge and opening one sync PR + Main-PM task on a conflict — never targeting the prod rung. The panel gains an environment-ladder editor on the project edit dialog. - Possibilities matrix — work-already-done fast path (default-off
ROBOCO_POSSIBILITIES_MATRIX_ENABLED). When a claimed/in_progress task already has commits, an open PR, every acceptance criterion addressed, and no open findings,i_am_donenow submits straight to QA in one call instead of the standard multi-turn plan/journal/local-gate derivation. The fast path still enforces ownership, branch-freshness, conventions, and findings-resolution, and trusts the PR's own CI-green signal as the quality-gate proxy — falling back to the localmake qualitygate (plus the toolchain-match guard when armed) when there's no CI signal, and refusing outright on a known-red CI. The dev spawn prompt steers a matching task straight to the one-call submit instead of walking it through re-verification. - Reviewer/PM collision map. The collision surface authored at delegate time (file globs, migration-adds, shared-surface flags) used to be consumed once by the sequencing analyzer and never shown to a reviewer again. QA's
claim_review, the PR-gate'sclaim_gate_review, and the PM's planning briefing now all surface the same-parent siblings a task would collide with — overlapping globs or a shared migration chain — plus a declared-vs-actual drift flag where real touched files are available. A new Collision tab on the task detail page (backed byGET /api/tasks/{id}/collision-map) gives a human the same view. - Telegram notifications bridge V1 (default-off
ROBOCO_TELEGRAM_ENABLED). Best-effort, outbound-only Telegram DMs to the CEO on escalation and completion, carrying only the notification's subject plus an optional panel deep-link — never the full body. Credentials (bot token + chat id) live in a singleton Fernet-encrypted row set from a CEO-only panel card, mirroring the X-credentials pattern; an unconfigured or disarmed bridge is a silent no-op that never blocks the notification producer. - PR org-structure labeler. Every fleet-opened PR now carries an org-structure label set —
to master/to slave,rootfor an assembled root PR,MegaTaskfor a batch member, and a layer label (main-pm/cell/{team}/subtask/{team}) — applied best-effort at every PR-opening site, so a human triaging the PR queue can tell at a glance which tree and layer a PR belongs to. - Agents are held to the Makefile. Role prompts and the bash-guard hook now point agents at
make quality/gate/lint/testinstead of rawuv run/pip install/conda/poetryinvocations, which bypassed the Makefile's venv-corruption guards (UV_NO_SYNC=1, a privateUV_CACHE_DIR). The bash-guard denies the raw commands whenever a Makefile is present in the workspace (a Makefile-less project is unaffected); the grok path mirrors it via a native deny rule that nudges the model back tomakewithout canceling the run. - docs/map is now KB-searchable. The agent-facing exhaustive codebase map under
docs/map/rides the same auto-index and periodic re-index loop asdocs/rag/, soroboco_kb_searchcan retrieve it — previously onlydocs/rag/was indexed. - Notification bell gains real read/ack actions. The bell used to show a transient WebSocket-buffer count with no way to act on it; it now derives its badge from the real unread count, lets an agent mark an item read or acknowledge it (or mark everything read) straight from the popover, and keeps the live stream only for the connection indicator.
- Metrics: 90-day window, a chart/table toggle, and a status donut. The time-window selector gains a 90-day option (daily buckets), every chart gains an inline table view via one shared
SegmentedControl, and the Performance tab gets a task-status donut fed by data already on the page. - Agent detail page gains a token sparkline and an activity timeline. A 7-day per-agent token sparkline plus a merged work-session/journal timeline now sit on the agent detail page (
GET /usage/time-seriesgained an optionalagent_slugfilter). - Product and project tables gain real progress signals. The product table now shows each product's cell mappings (team/project) and a done/active/blocked progress readout; the project table gains the same task-count readout plus a CI-watch badge when a project has opted into CI-watch — both computed in one grouped query per table, no N+1.
- Code-snippet viewer on revision findings. Each
file:linefinding on a task now shows the surrounding source, fetched from a newGET /git/fileendpoint that slices a file at a branch tip to a line window (explicit range, a centered context window, or the whole file capped at 2000 lines); a missing file renders a muted hint instead of breaking the card. - Dense hover-help across the entire panel (~700 new tips). Three annotate-by-default passes took every page tree — including the previously unswept sidebar/header chrome and the mobile tab bar — so every lifecycle action (a 25-action tip map on the task header), dialog field, sortable column, stat derivation, badge, feature flag, and disabled control explains itself on hover; roughly 25 controls gained their first accessible name. En route, two earlier tips that sat directly on disabled buttons (and so could never fire) moved to the span-wrap pattern, and dynamic tip labels are pinned non-empty since a falsy-to-truthy flip remounts the wrapped element.
Changed
- The release commit carries a bot identity, and signing is opt-in. The executor's fresh clone had no git identity (the container never configured one), so the release commit refused outright — and the unconditional
-Swould have failed next on the keyless container. The commit now runs as a configurableRoboCo Release Manageridentity, with-Sgated behindROBOCO_RELEASE_SIGN_COMMITSfor deployments that mount a signing key. - The release gate verifies CI instead of re-running the suite in production. The executor's pre-commit gate ran
make qualityinside the orchestrator container, where production env (armed flags, host mounts, live Redis) breaks clean-env test assumptions wholesale — 1000 spurious failures on a tree that was green in CI. The gate now re-verifies the head rung's CI verdict at execute time, fail-closed on an absent or red run with the branch, sha, and conclusion named in the failure detail; the pushed release commit still gets its own CI wait before publish. - Slave carries CI, and the release pipeline prefers curated notes. The dev branch joined the CI push triggers — the release gate is fail-closed on the head commit's verdict, and a branch with no runs read as "unknown", silently blocking every release proposal. The readiness drafter now uses the curated
[Unreleased]body as the release entry when one exists (falling back to per-commit transcription), and the executor empties[Unreleased]when stamping the entry so curated content never ships twice. CLAUDE.md gains the docs-sync engine paragraph it was missing.
Fixed
- E2E smoke test harness now isolates
ROBOCO_SDK_URLfor scripted-agent environments. The per-verb circuit breaker in flow_server and do_server POSTs every rejection to ROBOCO_SDK_URL (default http://localhost:9000) for tracking and tripping circuit_open on repeated failures. When the e2e-smoke suite runs inside a live agent container, that loopback port is the host agent's own SDK server — so the breaker records genuine attempts against ephemeral test-agent IDs and can trip mid-test even for deliberately-rejected test calls. The harness now setsROBOCO_SDK_URL = "http://127.0.0.1:1"alongside its existingROBOCO_AGENT_TOKENisolation, pointing it at a guaranteed-refused loopback so every environment sees the same fail-open bypass that CI already gets (no SDK server listening). This fixestest_request_sandbox_guard_chain_over_real_apiand ensures the e2e-smoke suite passes when run inside a spawned agent container. - Dispatcher no longer burns a spawn on a sequence-held task. The dev dispatch path booted a full container for a pending task the assignee-blind sequence guard would immediately refuse at claim — spawn, refuse, exit, respawn, forever — because the dispatcher's own prefilter only checked declared dependencies and the dev's own lane, not sequence siblings. It now reuses the exact claim-gate predicate before spawning, mirroring the PM path.
- Active tab and sidebar-footer highlighting. An invalid or typo'd
?tab=/?view=query param silently fell through as an out-of-set value and blanked the active tab highlight and its content pane; a newpickTabhelper validates against the known set and falls back to the default. The sidebar footer never had an active-state branch at all — it now highlights on an exact path match (not prefix, so/settingsdoesn't also light up on/settings/ai-providers). - Release-proposal reject no longer deadlocks the readiness loop, and a failed approve surfaces why. Rejecting a proposal left it PENDING rather than CANCELLED, so the one-open-proposal dedup blocked every future assessment cycle; reject now moves it to CANCELLED. A failed background execute (red gate, red CI, or an unhandled exception) used to leave the proposal silently pending with no signal; the approve route now stamps an execute-outcome marker on every terminal path so the panel can show a running/failed state and a retry instead of a silent wait.
- Tooltip and aria-label sweep. A shared
HelpTiphelper (a one-line wrapper over the verbose Radix Tooltip pattern) backs a broad accessibility pass: per-state tooltips on the task-status and agent-state badges (15 and 11 states respectively, previously undocumented anywhere), plus 35 moreHelpTip/aria-labeladditions across 22 panel components — icon-only buttons, status dots, truncated ids/branches. - Post-merge hardening basket.
GET /git/file's line-window cap now applies after resolving an explicitstart/endrange too, not only the whole-file default; collision-context evidence-building in bothclaim_gate_reviewand the collision-map route degrades to no siblings on a fetch/build failure instead of raising; Telegram sends are deferred until after commit so a slow Bot API call can no longer hold the caller's open transaction; the periodic re-index loop now watchesdocs/mapin addition todocs/rag; the possibilities-matrix fast-path prompt no longer offersWORK_ALREADY_DONEwhile a task isverifying(onlyclaimed/in_progress); and a shadowed loop variable in the lifecycle smoke-replay test is renamed for mypy 2.3 compatibility. - Task-detail active tab highlight (root-caused). The tooltip retrofit wrapped each tab trigger in
TooltipTrigger asChild, whose owndata-state="closed"survives the Radix Slot merge and clobbers Tabs' active/inactive attribute — so no tab ever matched the active styles. The trigger now re-asserts its owndata-state, recorded as the house pattern for any tooltip around a stateful Radix trigger. - Agent detail survives a stopped agent. The orchestrator's status endpoint 404s for any non-running container, and the page's error branch unmounted the DB-backed header, sparkline, and timeline it had already rendered. The fatal card now gates on the roster lookup alone; a live-status error degrades in place to a not-running banner with a spawn button, and the deterministic 404 no longer retry-loops.
- Panel counted phantom agent states. The Total/Active counters read
by_state.running/ready— states that don't exist in the orchestrator's enum — so they were structurally zero against a real backend. Total now counts the roster and Active readsby_state.active, fixed on the agents page, the metrics page, and the dashboard hook. - Overview and layout polish round. Quick actions and the key-metric cards now lead the overview with Team Health second and the decision queues last (PR Reviews and Social share a row); the agents page renders an intrinsic auto-fill grid of roomier cards with a merged, truthful stat row and a Board + Main-PM leadership band; metrics' five oversized status cards became a compact tile band beside the donut; journal ids render in full with copy buttons and a task deep-link; the AI-providers page groups per-agent overrides by org structure in two properly spaced columns with the key cards beside the self-hosted section; the environment-ladder editor speaks promotion flow ("PRs land" → "promotes to" → "release") with a worked-example tooltip; and an urgent flag's Report-CEO button renders only when a real handler is wired.
[0.24.0] - 2026-07-14
Added
- Scheduled auditor sweeps.
ROBOCO_AUDIT_INTERVAL_SECONDS(default 21600s / 6h,audit_interval_secondsinroboco/config.py) drives a periodic auditor spawn._dispatch_audit_worknow spawns the auditor on a scheduled sweep when the interval has elapsed, the auditor is not already active, and recent delivery activity exists (active delivery states or a task completed within the window). Reactive alert spawns also stamp_last_audit_spawn_atso the interval gate is shared. A one-tick notification sentinel and the existing active-agent breaker prevent auditor spawn storms;0disables scheduled sweeps. The auditor identity prompt and_build_audit_prompt(scheduled=True)support sweep-based reviews. - E2E smoke test for auditor triggers.
tests/e2e_smoke/test_auditor_triggers.pyexercises both auditor spawn paths end-to-end against the real orchestrator dispatcher: a scheduled sweep that sees recent delivery activity and a reactiveALERTcreated byPOST /api/tasks/{id}/fail-qa.spawn_agentis stubbed so the test asserts the dispatch decision without running an auditor container. The e2e harness now mounts/api/notificationsso_dispatch_audit_workcan poll alert rows. - Docs-divergence sync engine (default-off). With
ROBOCO_DOCS_SYNC_ENABLED, a successful release publish now hands the release toDocsSyncEngine, which originates exactly one PENDING Main-PM docs-update task against theroboco-websiteproject per release tag. The task carries the release's drafted CHANGELOG section and a pointer to the divergence checklist (declared-vs-actual agent count, stale verb-surface tables) so the public docs at docs.roboco.tech reflect what actually shipped. Bounded byROBOCO_DOCS_SYNC_MAX_OPEN_TASKS(default 3) andROBOCO_DOCS_SYNC_MAX_PER_CYCLE(default 1), deduped per release version via thedocs_sync_release_versionmarker, and never auto-merges — the docs update still ships through the normal dev → QA → PR-review → CEO-merge gates. Ifroboco-websiteis not registered as a project the engine logs a warning and no-ops. - Sandbox DB extensions/modules on the fly. A project may declare
sandbox_extensions(migration 072, jsonb null) — a per-service extension/module map, e.g.{"postgres": ["vector", "postgis"], "redis": ["search"]}— activated post-ready viadocker exec(CREATE EXTENSION IF NOT EXISTS/MODULE LOAD) and verified (pg_extension/MODULE LIST), never via bind-mounts or initdb.request_sandbox(extensions=...)unions a per-call override with the project's standing set, bounded to the opted-in service set plus the fixed allowlistsSANDBOX_PG_EXTENSIONS(vector/postgis/pg_trgm/citext/uuid-ossp) andSANDBOX_REDIS_MODULES(search/json/bloom);plpython3uand other superuser-RCE vectors are excluded by construction.image_for(features)selects the light upstream image for bare requests and the kitchen-sink image (pgvector/pgvector:pg16+ postgis apt for postgres,redis/redis-stack-serverfor redis) only when features are requested, so existing opters stay byte-for-byte bare. Cache-by-features: a cached entry satisfies a new call iff services are a subset AND every requested feature per service is already cached — a feature superset re-provisions (rotates creds);available_extensionsrides the evidence payload so an agent never guesses what was activated. The project edit dialog exposes a per-service Switch picker grouped under each enabled service. Seedocs/rag/architecture/sandbox-db.mdanddocs/internal/specs/2026-07-13-sandbox-extensions-on-the-fly.md. - Revision findings ledger.
fail_review,pr_fail,request_changes, andceo_rejectnow take structuredfindings: list[dict]validated intoFinding(roboco/foundation/policy/content/models.py) —file(repo-relative, ≤300 chars, no../absolute),line≥1,severity(blocker|major|minor|nit),criterion(≤500, must match an AC id or its exact text),expected/actual(≤300),fix(≤500),evidence(≤2000) — persisted as one append-only row per finding intotask_review_findings(migration 071;originqa|pr_gate|pm|ceo,round=revision_count+1read pre-transition,statusopen→addressed→verified|waived) viaReviewFindingsRepository, then mirrored intoqa_notes/pr_reviewer_notes/pm_notes(newtasks.pm_notescolumn +PmReviewContent) as a deterministic per-finding rendering[F-id8] file:line (severity) — expected → actual → fix. Count guardrails at the verb chokepoint (roboco/services/gateway/choreographer/findings.py): soft nudge aboveFINDINGS_NUDGE_COUNT(5), hard reject aboveFINDINGS_HARD_CAP(10) in one call;criterionmust resolve against the task's acceptance criteria or the envelope refuses. Legacyissues=[...]stays as a deprecated shim for one release (each string → a file-lessseverity=majorfinding, deprecation-logged) and merges withfindingsrather than one silently dropping the other;ceo_rejectnow validates its reason (no more empty-trivial 500) and stamps oneorigin=ceoblockerfinding. Resolution:i_am_done,submit_up, andsubmit_roottakeresolved_findings({finding_id, commit?, note?}) gated byRequirement.FINDINGS_ADDRESSED— every OPEN finding must be named by an unambiguous 8-char prefix match against the rendered[F-id8]or the envelope rejects, listing the still-open ids;pass_review/pr_pass/completebulk-verify their own origin'saddressedfindings same-transaction,ceo_approvestampsceo-origin best-effort, and the ledger mutation is owner-gated so a stale non-owner PM can never mutate it. Delivery:evidence()/build_task_handoffcarryrevision_findings(open only, capped) so a bounced dev gets what the role docs promise;claim_review/claim_gate_reviewcarry the fullprior_findingsledger so round-2+ reviewers check prior findings instead of re-deriving them; theREVISION_REQUIREDspawn prompt and PM triage "bounced" block render open findings inline; A2A fail bodies share it;GET /api/tasks/{id}/findings(capped 500, SQL-aggregated per-origin/statussummary+total/truncated) backs the panel's task-detail Findings tab and abounced xNheader chip (revision_count); metrics attributepm_rejects/ceo_rejects+ open/total findings counts per task; vault task notes render a capped## Findingssection (fail-open fetch, never blocks the note write). New audit eventstask.request_changes/task.ceo_rejectjointask.qa_fail/task.pr_failin_audit_events_forso rework metrics attribute every bounce kind. Seedocs/map/review-findings.mdanddocs/rag/architecture/review-findings.md. - Auditor
waive_findingverb + findings queue panel. The auditor-onlywaive_findingflow verb (IntentSpecinroboco/foundation/policy/lifecycle.py, auto-derived into the auditor manifest;flow_auditorroute +flow_serverMCP tool) wires the long-unwiredReviewFindingsRepository.mark_waivedto a severity-scoped closure: onlyminor/nitopen findings are waivable,blocker/majormust be fixed and are hard-refused, anoteexplaining the waive is required, and no task status change occurs — the ledger row movesopen → waivedand atask.finding_waivedaudit event (info, note capped 300) records the decision. Already-waived/addressed/verified rows are immutable (refused), and a missing finding 404s with a remediate pointing atGET /findingsortriage(). Registered inVERBS_WITHOUT_TRACING(the note + audit event is the durable rationale — nojournal:decisionneeded, mirroringdeclare_coverage). The auditor dashboard gains afindingsfield (ReviewFindingsRepository.list_open_findings, cross-task, blocking-severity-first then newest, capped 20) surfaced in a new read-onlyFindingsQueuePanel(panel/src/components/auditor/findings-queue-panel.tsx) restoring the 4-card auditor layout — each row links to its task, severity-colored, with an open-count destructive badge.GET /api/tasks/{id}/findingsremains the full per-task ledger view (the dashboard is a glance, not the ledger). Seeroboco/services/gateway/choreographer/board.py. - Obsidian vault V2 — drift janitor, archival, weekly report, KB ingest (default-off, gated on
ROBOCO_OBSIDIAN_VAULT_ENABLED). The V2 spec ships four pieces on top of the V1 materializer: a drift janitor (roboco/services/vault_janitor.py, hourly-ticked_vault_janitor_loop) running a daily changed-task re-projection + random-sample drift check + archival pass — each capped at 200/cycle and per-item isolated so one bad row never wedges the sweep — restart-proof via aRoboCo/_meta/.janitor_state.jsonstate file so real work and the weekly org-report (vault_report_enabled, default true —VaultWriter.write_org_reportfromMetricsService/UsageServiceintoReports/<ISO-week>.md, once per ISO week with a best-effort CEO notification) each fire exactly once per elapsed period regardless of loop/restart cadence; archival (vault_archive_days, default 30,0=off) moving old terminal tasks' notes intoRoboCo/Archive/<year>/Tasks/<project>/through onewrite_taskcode path shared with rebuild, alias links making the move free and the shipped Dataview/graph assetsArchive/-aware; KB ingest (vault_kb_enabled, default false — NAS compose arms ittrue, registryfalse) embedding the CEO'sRoboCo/Notes/(configvault_kb_dirs, csv, load-time-validated against traversal/overlap with reserved projection dirs) into a newIndexType.VAULT_NOTEScorpus via_vault_kb_loop(roboco/services/vault_kb_engine.py, default 900s), with every note re-checked for symlink/path-escape at read time and screened through the injection guard as a hard GATE — a flagged note is quarantined (skipped, logged, callout-marked, never embedded) rather than the intake watcher's screen-and-still-process posture — reachingroboco_kb_search,MentorService's default domain, andEvidenceRepo.similar_memory(claim-time briefings, relevance-floored, kindvault_note); plus Bases views (Task Board.base,Reports.base) and the Mac sync runbook shipped as vault assets. No migration (thechunkstable auto-creates; migration 030'sCHUNK_TABLEStuple appended per the chunks_playbooks precedent). Seedocs/internal/specs/2026-07-11-obsidian-vault-v2.md. - Intake technical depth now carries down the delegation chain. Intake's file:line targets, code examples, and rationale were getting lost as a task traveled umbrella → root-subtask → cell → dev — the detail survives in
Task.description, but PMs were re-authoring it away and the intake prompt never demanded depth.EvidenceRepo.ancestor_context_for_tasknow walks the parent chain (cycle-guarded, depth-capped 16, desc-clipped 1500) and surfaces it asparent_contextin the evidence payload;EvidencePayloadgainsdescription+parent_context(omit-when-empty, no null noise);_description_body(capped 4000) injects the description into the dev spawn prompt + SessionStart briefing. Themain_pm/cell_pm/developer/prompterrole prompts teach pass-the-torch-don't-dim-it, the prompter now demandsfile:line/code-examples inthe_work/notes(reconciled with the no-code-level-ACs-on-roots rule), andmain_pm's brief-not-a-spec is scoped so facts forward verbatim while the solution stays non-binding. Seedocs/internal/specs/2026-07-13-intake-technical-depth.md.
Fixed
- Restored five coordination-event notification producers with double-fire guards. Reassignment, collision-sequencing, unblock, dependency-revival, and stale-claim-reaped notifications are now wired at their lifecycle chokepoints in
TaskServiceand the orchestrator reaper, each with an idempotent upstream guard preventing duplicate ALERT rows. The duplicate route-levelnotify_assignee_of_unblockcall inPOST /api/tasks/{id}/unblockwas removed so unblock fires exactly one notification. Addeddocs/backend/services/coordination-events.mdandtests/e2e_smoke/test_notification_coordination_events.pycovering the restored producers. _fresh_orchestratortest helper initializes orchestrator state.tests/e2e_smoke/test_auditor_triggers.pyconstructs a bareAgentOrchestratorvia__new__so it can patchspawn_agent, but that bypasses__init__. The helper now explicitly sets_instances = {}and_last_audit_spawn_at = Noneso_is_agent_activeand_dispatch_audit_workno longer raiseAttributeErrorduring the auditor trigger e2e tests.- Hardened
AgentOrchestrator.__new__for__init__-bypass test instances.AgentOrchestrator.__new__now pre-initializes_last_audit_spawn_atand_notification_spawn_atalongside_instances, so bare-__new__orchestrator instances used by e2e/unit-test helpers no longer raiseAttributeErrorwhen the auditor-dispatch and notification-cooldown paths run. The existing_fresh_orchestratorhelper still sets these explicitly for clarity, but the safety net is now in the class itself. Normal construction via__init__is unchanged. - Python quality gate type hygiene on the auditor-revival branch.
roboco/services/task.py:get_all_descendantsnow usescast("UUID", child.id)instead of# type: ignore[arg-type]for the SQLAlchemyMapped[UUID]value, androboco/services/notification_delivery.pynarrows the return types ofget_ack_statusandget_delivery_summaryfrom baredicttodict[str, Any]. These are typing-only changes; runtime behavior is unchanged and the local ruff / mypy quality gate stays green. - Restored task.py auditor alerts and descendant-traversal cast after the docs-sync PR regression.
roboco/services/task.pyagain calls_alert_auditor_of_reworkimmediately afterawait self.session.flush()infail_qa,pr_fail, andrequest_changes, matching the pre-regression reactive auditor ALERT path; the_supersede_replacement_landeddescendant loop reverts to the original unquotedcast(UUID, child.id)form with a scoped# noqa: TC006.DOCS_SYNC_SOURCEandlist_open_docs_sync_taskswere not touched. - Docs-sync version-scoped query uses the generic JSON accessor.
list_open_docs_sync_tasks(version=...)inroboco/services/task.pynow comparesTaskTable.orchestration_markers[markers.DOCS_SYNC_RELEASE_VERSION].as_string()instead of the JSONB-specific.astext, becauseorchestration_markersis declared as genericJSON. This fixes theAttributeErrorraised by Postgres-backed integration tests and keeps the docs-sync dedupe/cap predicate in SQL. - PR-review and QA now judge coherence and intent, not just the AC checklist. The
qa.mdandpr_reviewer.mdrole prompts gain a Coherence & intent rule — intent read fromdescription+parent_context, coherence with project patterns and standards — andFinding.criterionis now optional so a criterion-less major finding can flag intent drift.parent_context+descriptionare wired into the gate/QA/inbound-PR evidence builders (fail-open, logged), so a round-2+ reviewer sees upstream intake analysis instead of re-deriving it blind. - Settings preferences persist as real client prefs instead of 422-ing as theater. The Settings page PUT four keys (
notifications_enabled,sound_enabled,auto_refresh,refresh_interval) the backend'sservices/settings.py_VALIDATORSallowlist never accepted — Save died on the first 422 and nothing consumed the prefs anywhere (no auto-refresh timer, no notification toast, no sound existed). The four prefs move into the persisted UI store (useUIStore, client-only, same idiom as theme/sidebar) and the cards apply instantly; the dead server plumbing and global Save button are gone, the backend allowlist stays strict and untouched. NewAutoRefreshDriverticks the page-refresh registry every N seconds when Auto Refresh is on (default-off, skips while nothing is registered or a refresh is in flight); newNotificationAlertstoasts each newly-arrived WS notification with an optional ~120ms Web-Audio chime (initial backlog never toasts, one chime per batch, autoplay blocks never throw). Lesson: a panel control persisting server-side must name a key in_VALIDATORS; client-only prefs belong inuseUIStore. - Pre-set
branch_nameis trust-but-verified against origin before push-by-name. Abranch_nameset on a task was treated as proof the ref existed on origin, so_finalize_claimskipped_ensure_branch_for_taskandcreate_branch/push never ran — a manual field write or a prior failedcreate_branchwhose rollback didn't restorebranch_nameleft the field set while the branch was never pushed; descendants thenls-remote'd the name, found it empty, and cut frommasterviacreate_branch's silent fallback, breaking the cell→root branch hierarchy (MegaTaskf7d0a61aroot-branch 404)._ensure_branch_for_tasknow probes origin and, when the ref is confirmed missing, runs the full create to push it (an inconclusive probe fails soft so a transient glitch can't fail a normal resume claim);_finalize_claimalways runs the chokepoint and snapshots+restoresbranch_nameon rollback so a failed first attempt can't leave the field half-set. Gated onproject_idso branchless coordination/umbrella tasks are untouched; newGitService.branch_exists_on_remotereturns True/False/None (fail-soft). - Task-detail tab state lives in the URL; nav, kanban overflow, and sidebar divider fixed. The active tab now persists in
?tab=(survives reload, back/forward, prev/next task jumps); the prev/next arrows moved into the header row next to Actions instead of their own row above the title; the Constraints section starts collapsed (project boilerplate). Kanban swaps Radix ScrollArea for native overflow scroll (thedisplay:tableviewport let cards grow past the column and clip), columns share width (flex-1, 18rem floor, 24rem cap), and dark column colors normalize to/40tints. The sidebar footer drops the doubledSeparator(the wrapper'sborder-talready carries it). A self-providingTooltiproot (300ms) plus hover hints lands across sidebar, header, task detail, kanban, and every icon-only button that had none. - Systematic tooltip and aria-label pass across the entire panel. Per a new
tooltip-aria-label-spec.md, accessible icon-only controls now carry anaria-labelplus a matchingRadix Tooltip— 8 controls retrofitted (bell, back-arrow, menu, toggle, drag-handle, move-forward, settings, review-link) plus theassignee-avatarfull-name tooltip. Regression tests confirm the bell'saria-label/title/Tooltip; a follow-up wrapped the refresh button in aTooltip(dropping the redundant nativetitle=), fixed missingTooltipProvider/Link/ArrowLeftimports that were blocking Panel lint + the QA image build, deduped acommand-centertooltip import, closed remaining a11y gaps (task-table row-expand + paginationaria-labels, work-session truncatedtask-id/branchtitles, secretary Start loading label), and reflowed the doc prose for the hard-wrap gate. - Compose vault mount divergence (
docker-compose.yaml).docker-compose.yamlhad drifted fromdocker-compose.ymland was missing the orchestrator's${ROBOCO_DATA_DIR:-./data}/vault:/app/vaultbind mount plus theROBOCO_VAULT_PATHenv (and every other vault env), sovault_pathfell back to its config default/data/vault(roboco/config.py, not bind-mounted) and notes were silently written into the container overlay instead of the host. Making.yamlbyte-for-byte identical to.yml(commitcf668bd9) restored the/app/vaultmount and the full vault env block, so the host volume receives projections again.
[0.23.0] - 2026-07-11
Added
-
Task-detail overhaul. Description, per-field Notes, Plan, Progress, and Acceptance Criteria now live in collapsible sections that auto-collapse past a content-length threshold (long progress/checkpoint history and long criteria lists default closed, short ones stay open) instead of forcing continuous scrolling. Progress updates and notes show an inline absolute timestamp next to the relative one, falling back to the task's creation time when a note has no timestamp of its own. A parent-task breadcrumb and prev/next buttons — with
Alt+ArrowLeft/Alt+ArrowRightshortcuts — replace the old always-goes-to-/tasksback button, moving between the current task's parent and its neighbors in the last-visited Tasks list order. The read-only Constraints card gets a distinct amber accent, tint, and lock icon so it reads apart from the task's own authored description. -
Playwright chromium headless verification for QA images.
agent-qa-feandagent-ux(the shared UX/QA image) now ship Playwright withchromium-headless-shellpre-installed for browser-based QA verification — fe-qa and ux-qa agents can now launch headless chromium to verify rendered output, computed styles, a11y trees, and visual design when reading the diff alone cannot settle an acceptance criterion. A new CI workflow (agent-image-smoke.yml) builds real before/after images, runs a headless-launch smoke check inside each, and reports the realdocker inspectsize delta as a PR comment — the only non-estimated verification source available since agent sandboxes have no Docker daemon. Seedocs/backend/qa/browser-verification.mdfor examples andfe-qa.md/ux-qa.mdidentity prompts for the built-in guidance. -
Playwright MCP server for QA's browser verification. Hand-scripting the Playwright Python sync API through
Bashis fragile for an agent, soagent-qa-fe/agent-uxnow also ship@playwright/mcp, registered by the orchestrator as aplaywrightMCP server (mcp__playwright__*) for thefe-qa/ux-qaroles only — role-gated, not image-gated, sobe-qaandux-dev(which sharesagent-ux's image) never see it. A wrapper entrypoint (docker/scripts/playwright-mcp-entrypoint.sh) points the server at the image's existing bakedchromium-headless-shellinstead of letting it download a second browser. QA now drives the browser through structuredbrowser_navigate/browser_snapshot/browser_evaluate/browser_take_screenshottools instead of multi-lineBash -cPython strings;agent-image-smoke.ymlverifies the binary + the resolved chromium path and takes a real headless screenshot of a live panel page from inside the ux-qa image. -
Obsidian vault V1. The org's human-readable memory palace as a rebuildable DB projection (default-off,
ROBOCO_OBSIDIAN_VAULT_ENABLED+ROBOCO_VAULT_PATH, armed in both compose files): tasks, journals, and A2A digests as wikilinked markdown with rename-safe alias links and private journals excluded; best-effort event seams that can never block a verb; shipped.obsidianconfig (Dataview, Kanban, graph groups);python -m roboco.vault rebuild/relocate(relocate graftsRoboCo/into an existing personal vault without touching its config). The Auditor gains acurate_vaultnarrative duty spawned on root completion, and a default-off inbox watcher turns#roboco-tagged notes (including meeting-bridge output) into board-review drafts that reach delivery only through the CEO's Approve & Start. -
A2A conversation-first redesign. Three-region layout (roster/stream/context), team-color agent identity, a four-state connection treatment, new-message arrival cues, and the four-dimension filter control (agent/task/status/date with chips).
-
Sidebar reorder + two-column objectives. The spec-exact flat navigation order with Business in the footer group, and the Company Charter objectives grid.
-
Video pipeline controls. Per-project video requests (picker limited to video-enabled projects), an unconditional re-render action on queue rows and the pipeline strip, and a pre-approval composition preview.
-
Sequence is the bar. Strict, assignee-blind sibling ordering enforced at the claim chokepoint — a task with sequence N cannot start while any lower-sequence sibling is non-terminal; delegation now stamps collision-derived wave sequences so independent siblings still run in parallel;
tasks.parent_task_idgains an index (migration 069). -
PR-review gate hardening.
pr_passrefuses while the assembled PR's own CI is failing, pending, or undeterminable (a repo with no CI passes through with an evidence note), and the reviewer prompt requires a per-AC file:line walk where silently dropped deliverables are an automatic fail. -
First database backups. A
pg_dumpsidecar on the data-only network: daily crash-safe dumps with newest-14 rotation and a restore walkthrough.
Fixed
- Gate reviews judge only what a task authored. The in-path review gate diffed cross-team tasks against the repo default branch (string-derived parent), attributing inherited base-branch content to the task under review — three live bounces on one fix task; the gate now resolves the real parent branch and threads it through the conventions guard too.
- Dependency content at branch cut. A dependent's fresh branch now merges each same-repo dependency's landed work when that merge sits outside the branch's ancestor chain; conflicts surface loudly without failing the claim.
- Revision commits always get CI. Squash-merging a subtask PR into a branch that is itself a PR head fires GitHub's
pull_requestwebhook unreliably — the verifying workflows now also trigger on fleet-branch pushes, deduped by a concurrency group, so absent checks can no longer read as green. - Prompt-injection screening for engine-ingested text. Tweets and vault notes ride an untrusted-content envelope with flagged-in-place detection through local-model prompts and CEO-facing drafts.
- Release images complete. The release workflow now builds all 17 images the registry compose pulls (the two Grok sub-images were never published), so a fresh registry pull succeeds.
- Settings accept the panel's JSON scalars. The feature-flags card's boolean PUTs no longer 422 on type alone.
- Agent tool-call budget raised to 300 (warn 100) — 150 repeatedly halted legitimate multi-file work mid-task.
- Dispatcher churn. Dependency- or sequence-held tasks are filtered before claim attempts instead of failing one per tick.
[0.22.0] - 2026-07-10
Added
- Board-review → redraft loop for MegaTask batches. Sending a MegaTask for "Board review & Start" now keeps the intake chat alive (parked against the umbrella) instead of ending it: when the Product Owner and Head of Marketing finish, their feedback is injected into the still-live chat as a batch-aware brief — every root-subtask's current title/description/AC plus the board's decision notes and an instruction to re-propose the whole batch in one
propose_batchcall. Confirming the revised batch updates the existing umbrella and root-subtasks in place (positional patching, cancel+recreate only when an item's project scope moved, dependency waves rewired, the same multi-project scope validation as creation), and the board route can loop for another review round. The cold path works too: the task-detail "Re-draft with board feedback" button now handles a branchless umbrella by recovering its multi-repo scope from the live root-subtasks — previously a hard 400. Multi-round redrafts survive earlier cancels via a cancelled-excluding child view. - Smart spotlight cadence. The X feature-spotlight loop drafts daily when there is fresh news to talk about and stays quiet when there isn't, instead of a fixed 3-day metronome.
Fixed
- MegaTask wave sequencing is enforced at the service chokepoint. The dependency gate lived only on the gateway claim verbs while the orchestrator dispatched root tasks through a raw claim call, so later-wave roots could start before their upstream waves finished — hit live on the first big batch. The guard now sits in
TaskServicewhere every claim path crosses it, and the cross-cell UX→frontend dependency wiring no longer skips MegaTask roots (it was keyed on a product id batches don't carry). - MegaTask intake papercuts from the same live batch. The review card gets its own scroll region so a tall batch can't clip the launch buttons off-screen; a failed confirm releases its Redis idempotency guard instead of wedging every retry behind "already in progress" for an hour; a vestigial top-level repo slug in cell-mapped drafts no longer 400s the whole batch; and the idle reaper no longer kills an intake chat the CEO is actively reading (the open stream now keeps the session alive).
- Subagent spawning is blocked at the Claude Code level. The
Tasktool is a default-permitted built-in, so omitting it from an allowlist never removed it — underbypassPermissionsagents could still spawn subagents. It is now explicitly disallowed on the intake/secretary SDK options and in the fleet's base deny list. - Security backlog cleared. All 104 open code-scanning and Dependabot alerts dispositioned: four real CodeQL findings fixed (path containment around the conventions-PR scaffold), 24 dependency bumps applied, and the remaining false positives dismissed with written justifications.
- Release-recap video hero logo renders correctly in dark mode.
[0.21.0] - 2026-07-09
Added
- Video pipeline visibility. A CEO-gated
GET /video/pipelinelists every in-flight video item with its stage, PR number, composition id, render status, attempt n/max, and the last render error — which the render loop now stamps onto thevideo_draftmarker instead of only logging. The Social page gains a pipeline strip above the Video Post Queue (stage chips from authoring through render-failed with the reason), the queue's empty copy is state-aware ("n videos in flight" vs. unconfigured), queue rows show the draft's title and script, missing cuts are disabled instead of a silently blank player, and notification cards deep-linkrelated_task_id.source_task_idis exposed on both video schemas, linking a draft back to its authoring task. - Rich video authoring briefs. Release-video briefs carry the full CHANGELOG section for the version (capped at 4,000 chars) plus a highlights list instead of one LLM-compressed sentence; the CEO's
brand_voicecharter text and a pointer tomotion/kit/'s design bar are appended centrally inopen_video_task, so release, spotlight, and on-demand paths all inherit them;suggested_input_props(version + highlights) is seeded on thevideo_draftmarker for the dev to pass throughpropose_video; and a third acceptance criterion pins the design bar.
Fixed
- Spotlight videos draft on CEO approval, not at authoring. The companion-video hook moved from
propose_feature_spotlight(Head-of-Marketing drafting time) to the CEO-approve success path of anx_featuredraft, mirroring the release-publish seam — a rejected spotlight no longer burns a ux-dev delivery cycle. The renderer also honors each composition's declareddata-fps(clamped 24-60) instead of hardcoding 30. sync_branchworks for standalone tasks. The protected-base guard refused every master/main base, structurally wedging any parentless task (video, CI-watch, dep-update) into block/PM/respawn churn when it needed to rebase — hit live on the v0.19.0 video task. The rebase only ever force-pushes the task branch, so the guard now refuses master/main only when the resolution is actually wrong (a branch-bearing parent exists, or the parent row is missing); the--prefix injection guard stays unconditional.- Panel pinned to Next.js 16.1.1 — the 16.2.x line breaks tab navigation in Chrome. In a production build, in real Chrome, a searchParams-only soft navigation fetched the target payload and then the router MPA-reloaded the current URL, locking every URL-driven tabbed page (kanban, business, metrics, notifications, knowledge base) on the first tab. Bisected with a scripted real-Chrome sweep: 16.1.1 green, 16.2.6 and 16.2.10 red. Dev builds and Playwright's bundled Chromium mask the bug, which is how the bump passed review.
- Agent-authored PRs no longer fail the CLA check. Fleet commits are authored as
<Display Name> <slug@roboco.tech>— emails linked to no GitHub account — so CLA Assistant demanded signatures no agent can post. The org's own roster identities are now allowlisted, wildcarded per team family.
Changed
- Fleet-wide subagent ban. No role can spawn sub-agents anymore: every
allows_subagentin the role config flips to False (previously True for the PM, board, prompter, and secretary roles) and the grok path's drifted allowlist empties to match. An invariant test iterates every role config so a single role can't quietly regain it.
[0.20.0] - 2026-07-09
Added
- Panel-demo composition kit (
motion/kit/). Reusablepk-namespaced building blocks that recreate the control panel for HyperFrames compositions — frame chrome, task card, team/priority chips, status pills with an enter/exit swap mechanism, notification toast, kanban column, character-by-character typing reveal with blinking caret, and a cursor sprite with click pulse — color tokens lifted from the panel's dark theme and badge components so kit output reads as the real product. Ships with thepanel-demoreference composition (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, a toast slides in. Release, spotlight, and on-demand videos can now be authored as simulated product demos instead of text cards. - Root-owned acceptance criteria via
declare_coverage. Coordination-root acceptance criteria are root-owned and covered explicitly: the new gateway verb lets a dev declare which parent ACs its work satisfies, coverage rolls up from those declarations, and the orphaned-parent_ac_refsdeadlock class that could wedge parent closure is gone. - On-demand sandboxes via
request_sandbox. Sandbox DB/Redis/Mongo provisioning moved from eager-at-spawn to a role-scoped do-verb: a dev or QA agent requests services when the work actually needs them, the orchestrator provisions the project's whole opted-in set behind a per-agent lock and cache, creds return only in the verb envelope, and the sandbox is released on the successful exit of the engagement-ending verbs instead of waiting for container teardown. A provisioning failure is a retryable envelope on the verb, never a spawn refusal. - Social page. The X post queue, the video post queue, and on-demand video requests now live on one aggregated Social page, with a unified posted/rejected history across both queues.
- Page-scoped refresh in the navbar. One always-visible refresh button that refreshes only the data on the active page (per-page React Query registrations with a disabled state), replacing the inconsistent inline refresh buttons and hard reloads.
Fixed
- FastAPI commit-after-response race.
DbCommitMiddlewarenow commits before the response starts, killing the ok-without-effect e2e flake family, alongside the 2026-07-08 prod-triage batch: slug-vs-UUID 401 residue in the search/optimal/docs MCP servers, audit remediate hints, the commit envelope, an absolute verb-rejection cap, A2A chime-in delivered as an interjection into the conversation the CEO is viewing, and manual-spawn task/message/refusal UX. - Working exits for wedged agents. Developers gained real exit hatches —
unclaimfromverifying/needs_revision,sync_branchwith stash, state-aware auto-block, an orphan warning on cancel, remediate chains that nameunclaim— plus thedeclare_coverageroll-up unblock that freed the live orphaned-AC deadlock. - Journals and learnings never reached the RAG corpus. A 200-character indexing floor silently excluded nearly every journal entry and learning; per-index floors (40 for journals, 80 for learnings) plus a startup backfill restored the shared corpus, alongside the git-readonly slug 404 fixes.
- CI segfault killed at the root. The e2e harness segfaulted only in CI through uvloop; the API now defaults the event loop to asyncio with a cancellation-safe commit.
- init_db can no longer wedge a boot. The API lifespan's second
init_dbcould hang forever in alembic's nested-asyncio.runthread on a NAS boot;init_dbis now latched per database URL and the alembic runner time-bounded (300s) with a loud failure. - RAG reconcile off the bind path. The startup reconcile runs in the background now, never on the API bind path.
- Git ownership repair scoped. The post-operation
chownstorm (5-15s added to every git op) is gone — ownership repair is tiered and scoped to what the operation could actually change. - Video renderer aligned with
@hyperframes/producer0.7.36. The sidecar's render core rides the producer API (createRenderJob/executeRenderJob), reading each cut's dimensions from the composition HTML itself. - roboco-api CI regression fixed through the CI-watch flow. The engine opened the fix task and the fleet delivered it through the normal lifecycle.
- Release manager publish no longer depends on a
ghbinary that was never installed.ReleaseExecutor.publish_releaseshelled out togh release create, but no image ships the gh CLI — withROBOCO_RELEASE_MANAGER_ENABLEDarmed, every publish would have died on a missing binary after the release commit was already pushed (verified against the live 0.19.0 orchestrator container). The publish is now a GitHub RESTPOST /repos/{owner}/{repo}/releasesauthenticated with the project's decrypted token — the same auth + httpx pattern as PR creation — with the same fail-closed semantics (non-201 → structuredpublish_failed, CEO retries; the 300s deadline is now the HTTP client timeout).
Changed
- Decomposition minimalism in the planning prompts. PM roles now decompose into the fewest subtasks the work actually needs instead of ritual splits.
- Panel dependencies: Next 16.2.6, axios 1.16.0. External PRs #343/#344 were superseded, finished, and hardened in-house through the org's external-PR takeover flow.
- The PR-gate turn cut is now unconditional — the kill-switch is gone.
ROBOCO_PR_GATE_AUTO_SUBMIT_ENABLED(0.17.0) has been removed fromroboco/config.py;_auto_submit_targetno longer checks a flag at all. The turn cut IS the flow now: once every child of an assembled parent is terminal, the orchestrator always runssubmit_up/submit_rootsystem-side as the owning PM before ever considering a PM spawn. The existing gate-rejection fallback (branchless/umbrella parent, a gate refusal, a transport error) is the sole safety net — no config switch sits on top of it. The fallback closure prompt now also carries the exact refusal reason (_build_pm_closure_prompt's newauto_submit_reason), so a PM that does get spawned isn't re-running evidence-gathering to rediscover why blind. - Leaner agent/orchestrator images (~1.65GB less, cache-stable deploys). Playwright + its Chromium and system libs are gone from
agent-dev-fe/agent-qa-fe(~770MB each — verified unused repo-wide: panel tests are vitest, the e2e harness is scripted Python; browser-based FE QA is a designed follow-up, and the re-add is two lines scoped tochromium-headless-shellin the QA image only). Theagent-grokimage drops a redundantchown -Rthat duplicated the entire 149MB CLI tree into a second layer. The runner-stage/appCOPY inagent-baseandorchestratoris split.venv-first/source-last, so a source-only deploy re-layers ~13MB instead of ~380MB per image, andagent-base's single 813MB apt+node+claude-code RUN is split so a CLI version bump no longer re-downloads the OS/node layer. Hygiene: gitignoreddocs/internal/no longer leaks into the orchestrator image from a working-tree build, and theuvhelper image is pinned (0.11) instead of:latest. All four rebuilt images pass runtime probes (claude/git/jq/node/uv/pnpm/grok binaries,import roboco, docs/alembic/agents trees present); cache-stability proven by rebuild log (.venvlayer CACHED across a source-only change).
[0.19.0] - 2026-07-08
Added
-
Pluggable sandbox engine registry (postgres / redis / mongo). The per-agent-spawn sandbox service set is now a registry instead of hardcoded postgres+redis branches. A new pure module
roboco/models/sandbox.pydefines aSandboxEngineABC plus three concrete engines (_PostgresEnginepostgres:16-alpine,_RedisEngineredis:8-alpine,_MongoEnginemongo:8); each engine declares its image, run args, readiness probe, connection shape, andROBOCO_TEST_*env emission.VALID_SANDBOX_SERVICES = frozenset(SANDBOX_ENGINES)is the single source of truth — derived from the registry, shared by the pydantic validators inmodels/project.pyand the orchestrator-side provisioner, so they can never drift. The provisioner (roboco/runtime/sandbox.py) iterates the registry via a generic_provision_engine; the orchestrator's_append_sandbox_envcollapses tocmd.extend(info.emit_env()); teardown iterates every engine. Adding a sandbox engine is now one class + one registry line — no branch edited in the provisioner or the env emitter, and the panel's edit-project dialog surfaces it via aSANDBOX_SERVICEScatalog. Mongo rides the existingprojects.sandbox_servicesopt-in (migration 057) with no new migration and no new feature flag; it addsROBOCO_TEST_MONGO_*(+ROBOCO_TEST_MONGO_AUTH_DB=admin). Env var namesROBOCO_TEST_DB_*/ROBOCO_TEST_REDIS_*are preserved so existing project conftests need no change. The panel's sandbox toggles became aSet<string>multi-select driven by the catalog. -
RoboCo video engine (default-off). With
ROBOCO_VIDEO_ENGINE_ENABLED, a release/feature-spotlight/on-demand CEO trigger opens a normal, assigned UX/UI authoring task (balanced across the two ux-devs) instead of a held draft — the dev builds a HyperFrames HTML composition undermotion/compositions/<id>/and proposes its composition id + per-platform captions via the team-gatedpropose_videodo-tool, then ships it through the standard commit/PR/QA/doc/review lifecycle. Once that task completes, an orchestrator render loop tars the mergedmotion/source to a new credential-freevideo-renderersidecar, renders both the 9:16 and 1:1 MP4 cuts, and materializes a heldvideo_postdraft (mirroring the X-post/release-proposal shape: Secretary-owned, skipped by every dispatcher). The CEO previews, edits, approves, or rejects each draft in a new panel video queue; approving posts the rendered clip to X (native video, v2 media upload) and/or TikTok (inbox upload) under a heartbeat-renewed lock and is idempotent — an already-posted draft is a no-op.ROBOCO_VIDEO_ON_RELEASE/ROBOCO_VIDEO_ON_SPOTLIGHTgate the two automatic triggers independently of the CEO's on-demandPOST /video/request; TikTok's OAuth2 secrets live Fernet-encrypted alongside the existing X credentials, and every unconfigured leg (renderer, X, TikTok) degrades to a graceful no-op rather than a crash. Rendered MP4s persist underROBOCO_VIDEO_OUTPUT_DIR(bind-mounted in all three compose files so renders survive container recreation). -
Per-project video-engine opt-in.
projects.video_engine_enabled(migration 063, mirroringci_watch_enabled): the globalROBOCO_VIDEO_ENGINE_ENABLEDflag arms the subsystem, the per-project flag opts a repo into authoring against itsmotion/dir —VideoEngine._opted_in_projectno-opsopen_video_taskuntil the operator flips it in the panel's edit-project dialog. Existing projects stay opted out. -
MinIO object storage scaffolding (default-off).
ROBOCO_MINIO_*config (minio_endpoint,minio_access_key,minio_secret_key,minio_bucket,minio_region) + aminioservice and a one-shotminio-init(idempotent bucket create) in the NAS compose files, on thedatanetwork with a namedminio-datavolume;minio(minio-py) added as a dependency. Emptyminio_endpoint= disabled and the existingFileResponsemedia-serve path is byte-for-byte unchanged — this is scaffolding; the write path (PUT after local save) and serve path (StreamingResponsewithFileResponsefallback) land in later chunks. The registry compose omits MinIO entirely (NAS default-on, registry default-off). -
MinIO storage client.
roboco/services/minio_client.py— a singleton minio-py client with an unconfigured guard (get_client()returnsNonewhenminio_endpointis empty), plusput_objectandget_object_stream. Sync; call sites wrap inasyncio.to_thread. Not yet wired into the write/serve paths (chunks 3–4). -
MinIO write path.
video_renderer_client._savenow PUTs each rendered MP4 to MinIO (key = the basename{render_key}-{orientation}.mp4) after the local write, guarded byminio_endpoint. Local disk stays the source of truth for the poster publish path; the PUT is additive and non-fatal — a failed PUT (MinIO down, transient 5xx) is logged and the render still succeeds, since the serve route falls back toFileResponseonS3Error. No schema, marker, ormp4_pathschange. Disabled (local-only) when MinIO is unconfigured. -
MinIO serve path (the user-visible switch). The panel video-preview media route (
GET /api/video/posts/{id}/media) now streams the MP4 from MinIO (StreamingResponseoverminio_client.get_object_stream, key = the basename) whenminio_endpointis set, keeping_require_ceoso auth stays end-to-end (no presigned URLs). Falls back toFileResponsefrom the local video-renders dir when MinIO is unconfigured OR onS3Error(old renders not yet in MinIO / MinIO down). The panel's axios-blob flow is unchanged — same URL, headers, body. SetROBOCO_MINIO_ENDPOINTand renders start serving from MinIO. -
Ponytail build-laziness doctrine (bundled with Fable-mode, default-off).
ROBOCO_FABLE_MODE_ENABLEDnow also composes the vendored ponytail "lazy senior dev" doctrine (agents/prompts/doctrine/ponytail.md+ponytail-ethos.md, MIT, Copyright (c) 2026 DietrichGebert — trimmed, YAML frontmatter stripped) into every agent's system prompt viaponytail_doctrine_layer(roboco/agents/factories/_base.py), slotted immediately after the Fable doctrine layer and gated on the same flag. Role-scoped: developers (AgentRole.DEVELOPER) get the full ladder (YAGNI → reuse-in-this-codebase → stdlib → native-platform → installed-dep → one-line → minimal); every other role gets the ethos-only cut (ponytail-ethos.md) so the code-mechanics rungs can't leak into prose artifacts (task plans, review notes, docs). Both files carry a 5-point RoboCo preamble (the ethos sibling adds a 6th: free-text field obligations) that makes the ladder yield to the Architectural Conventions Standard (placement), the 80% coverage gate + QA review + self-verification, the per-team design bar, task hygiene (everything-is-a-task / commits-linked / state-is-sacred), and reviewer feedback (needs_revision/pr_fail/request_changes) — overlap with established guardrails is mitigated by scoping, not deletion, riding ponytail's own "when NOT to be lazy" clause. Developer intensity is tunable viaROBOCO_PONYTAIL_INTENSITY(lite/full/ultra, defaultfull;roboco/config.pyponytail_intensity, a string value — not a feature flag); non-developers get no dial (ultrais wrong for prose artifacts). Prompt-only — no hooks, no grok-path changes; a flag-off spawn is byte-for-byte unchanged.
Fixed
-
Mongo sandbox image tag existed nowhere (
mongo:8-alpine→mongo:8). MongoDB has never published an Alpine variant, so the mongo engine's pre-pull always failed and — because provisioning failure refuses the spawn by design — a project opted into a mongo sandbox could spawn no agents at all. The registry now pinsmongo:8, the deadMONGO_INITDB_DATABASEenv is dropped (nothing consumed it; the connection already hands agents theadminauth DB), and a new network-gated e2e test (tests/e2e_smoke/test_sandbox_image_tags.py) asserts everySANDBOX_ENGINESimage:tag actually exists on Docker Hub — the check the fully-mocked provisioner unit tests structurally cannot make. -
Flow-verb timeouts now match what the verbs actually do — at both walls. Live agents were abandoning tool calls at the MCP client's flat 30s
httpxtimeout (flow_server.py) while the server kept executing:i_am_donedied mid-quality-gate,i_will_work_ondied mid-workspace-clone, and planning verbs reported "timed out but succeeded". A new pure policy module (roboco/foundation/policy/flow_timeouts.py) defines the slow-verb set{i_am_done, submit_up, submit_root, open_pr, i_will_work_on}shared by both sides: theFlowVerbTimeoutMiddlewaregives slow verbsflow_verb_slow_timeout_seconds(default 900) instead of the 120s default, and the agent-side client reads orchestrator-injectedROBOCO_FLOW_VERB_TIMEOUT_SECONDS/ROBOCO_FLOW_VERB_SLOW_TIMEOUT_SECONDSand always outlasts the server budget by 10s — so an agent receives the middleware's clean 504 envelope, never a raw client timeout.i_will_plan/delegatedeliberately stay on the default budget: the slow wall also bounds how long a wedged verb can hold theSELECT FOR UPDATEtask row, andi_will_planis exactly the verb the row-lock wedge fix targets. The do-servercommittool similarly outlasts its server-side 180s git budget. -
Cancellation no longer orphans gate subprocesses or drops PR records.
quality_gate._run_onekilled its child only on its ownTimeoutError; an outer cancellation (the flow-verb middleware firing mid-gate) skipped the kill and left mypy/ruff running in the workspace while the agent retried into a second concurrent gate run — it now kills and reaps onCancelledErrortoo. AndGitService.create_prshields the local_record_pr_atomicallycommit, so a cancellation landing after the PR exists on GitHub can no longer leave it locally unrecorded. -
Video engine hardening (scan follow-ups). The
video-renderersidecar moves off the agent-mesh network onto a dedicatedrenderbridge reachable only by the orchestrator (its headless Chrome executes agent-authored composition JS), gainsmem_limit: 2g/cpus: 2, a server-side render watchdog (RENDER_TIMEOUT_SECONDS, default 570 — under the orchestrator's 600s client budget) that flushes a 500 and exits so Docker revives a clean container instead of accumulating wedged Chrome trees, and a 512MB tar decompression cap (MAX_EXTRACTED_BYTES) alongside the existing compressed-size limit. A terminally-failed render (all retry attempts spent) now sends the CEO an ack-required notification instead of dying as a log line, andVideoPostService.rejecttakes the same heartbeat mutex asapprove, closing a narrow status-clobber race between a concurrent approve and reject. -
Dead
python-josedependency removed (closes PYSEC-2026-1325 exposure).python-josewas declared but imported nowhere — the auth stack uses fastapi-users' pyjwt — and it transitively pinnedecdsa, whose Minerva timing-attack advisory (CVE-2024-23342) has no fix and never will (the maintainers consider side-channel resistance out of scope). Removing the dead dependency (+ itstypes-python-josestubs and deptry whitelist entry) dropsecdsafrom the tree entirely, sopip-auditgoes green by deletion instead of by waiver. -
Panel
--font-monoresolved to Inter. The mono CSS var pointed at the proportional Inter font, so SHAs, task IDs, and code snippets rendered proportional; it now uses a real system monospace stack. -
Sandbox cold-pull loop + empty provisioning error string.
docker runpulled the sandbox image inline under a 20s run deadline, so a NAS cold pull was killed, the pull cancelled, and every retry re-pulled from scratch — a persistent loop that stranded v0.19.0 board-agent spawns with"error": ""(a bareTimeoutErrorstringifies to"")._ensure_imagenow inspects the image and pulls it under a 300s deadline beforedocker run, and provisioning failures logf"{type(e).__name__}: {e}"so the error is never an empty string. -
Conventions + release-readiness I/O no longer blocks the API event loop.
ConventionsService.get_map/health/restoreandReleaseManagerEngine._production_assessran syncgit rev-parse, filesystem walks, and yaml parses inline on the orchestrator's shared uvicorn event loop, stalling API responsiveness during conventions reads (reachable fromGET /api/projects/{id}/conventionsand the agent spawn-prepare path) and the release-manager background loop. Each blocking call is now wrapped inasyncio.to_threadat the async boundary; no signature changes. A concurrency audit confirmed the rest of the heavy paths (agent spawn viadocker run -d, the video render loop, git ops via the 16-worker ThreadPoolExecutor, workspace subprocess calls) already offload correctly — no API/worker container split is warranted. -
Deleted journals de-indexed from RAG (C3).
JournalsIndexPlugin.delete_entrynow callsOptimalService.unindex_journal_entry(entry_id), which removes the entry's embedded chunks from thechunks_journalsvector store AND drops itsindexed_documentstracking row — a deleted (or private) entry stops surfacing in RAG answers and agent briefings. Pre-fix the chunks were orphaned, so deleted/private content bled into briefings. -
Learning
learning_idhashes full content (M25). The deduplearning_idwas derived from a thin slice of the lesson, so two distinct lessons that shared the prefix collided and the second silently overwrote the first. The id now hashes the full content fields, so each distinct lesson persists as its own row. -
Learning broadcast bulk-inserts (M28).
_broadcast_learninginserted one notification row per recipient agent (~25 agents) — an N+1 that grew with the fleet. It now bulk-inserts all recipient rows in a single statement, killing the N+1. -
A2A
mark_read/mark_all_readstamp only the unread rows seen at call time (M27). The read-stamp UPDATE filtered by recipient but not by the unread-at-call-time set, so a message arriving mid-call could be consumed as read without the recipient ever seeing it. The stamp now captures the unread ids at call time and updates only those rows. -
Notification dedup no longer drops real notifications (H12).
_duplicate_unacked_existsmatched on an OVERLAPPING recipient set instead of an exactto_agentspredicate, so a second notification whose recipients overlapped-but-were-not-equal to an existing unacked one was dropped for ALL recipients (a blocker sent to {be-pm, main-pm} after one to {be-pm} alone never reached main-pm). The predicate now requires exactto_agentsequality plus apurposediscriminator, and acks DEL the dedup key — overlap≠equal no longer suppresses. -
Playbook
indexed_ok/indexed_at+ startup reconcile (M23). A playbook approved mid-Ollama-outage was leftindexed_ok=Falsewith no retry path, so an approved-but-unindexed playbook never reached the knowledge base. The row now carries durableindexed_ok/indexed_atflags, andPlaybookService.reconcile_unindexed_approvedruns at startup to reclaim approved-but-unindexed rows through the realindex_playbookpath and stamp the flags. -
RAG indexing dead-letter + janitor reclaim +
failed_index_counthealth (M24). A failed index attempt was silently dropped — the document was stuck un-indexed forever with no signal. Failed attempts now land in a dead-letter ledger; a janitor reclaim retries them, andfailed_index_countsurfaces in the health check so failed indexes are retried, not dropped. -
institutional_memory_statussentinel (L23). A claim briefing'sinstitutional_memorywas either present or absent with no reason, so an agent could not tell "searched and found nothing" from "the search broke" from "the subsystem is off". The briefing now carries aninstitutional_memory_statussentinel (below_floor/empty/error/disabled) alongside the payload. -
sweep_expired_notificationsre-escalates stale unacked ack-required (L26). An ack-required notification that expired unacked was silently dropped — a missed blocker never re-surfaced. The sweep now re-escalates a stale unacked ack-required notification instead of letting it expire silently, so a missed blocker re-surfaces instead of vanishing. -
merge_prserializes concurrent calls with a row lock (M37). Two concurrentmerge_prcalls on the same ACTIVE work_session (a retried CEO double-click racing PM auto-complete) each saw ACTIVE and both wrotemerged_by/pr_merged_at, corrupting the merge audit trail. The work_session row is now lockedFOR UPDATEbefore the status check, so the second caller blocks, then sees COMPLETED and no-ops — exactly one merger is recorded. -
Indeterminate
_pr_is_mergedno longer respawns the PM (M38). On anhttpx.HTTPErrorthe lookup returnedFalse, so a network blip during an already-merged PR's disambiguation raisedMergeConflictErrorand respawned the PM against an already-merged PR. The lookup now returnsNoneonhttpx.HTTPErrorand the caller treatsNoneas "assume merged", falling through to the already-merged cleanup path instead of conflict-looping. -
_cherry_unmerged_entryno longer hides a child's unmerged commits behind a "Reverts" commit (M39). The marker grep matched any[ID]substring, so aReverts [ID]commit satisfied the predicate and hid a child's unmerged commits from assembled-PR integrity. The grep is now anchored to^\[{id[:8]}\]so only a real commit-prefix match counts. -
update_pr_for_taskresolves the workspace agent from the actor, notcreated_by(L1).actor_agent_idis now threaded throughupdate_pr_for_taskand_resolve_workspace_agent_idis narrowed to actor → assigned_to → None (created_by removed), so a PR update routes to the acting dev's workspace instead of the task creator's. Stale docstrings refreshed. -
push(force=True)uses--force-with-lease(L2). A bare--forcesilently clobbered a concurrent remote advance. The push now uses--force-with-lease, so a concurrent push fails fast instead of being overwritten. -
wait_for_cipolls through the window on a non-success (H24). A failed first attempt while a re-run was still in_progress blocked a real release. The poll now keeps going through the window; onlysuccessreturns True and loop exhaustion returns False, so a re-run that flips the same commit green still publishes. -
Release mutex orphan-sweep on startup (H25). A restart mid-execute orphaned the Redis mutex (TTL 3000s, no heartbeat); a CEO retry got
already_in_progressfor up to 50 min.Orchestrator.startnow sweepsroboco:release_proposal:*keys whose owners aren't in the in-flight registry. -
TikTok rotated refresh-token survives a lock-loss rollback (M1).
_refreshnow commits the rotated tokens in an independent session before returning; a lock-loss rollback of the caller's txn no longer discards them (TikTok already invalidated the old refresh_token → was a permanent credential lockout). -
X feature-spotlight re-arms past a stale exploration (M2). An exploration left PENDING with no live HoM spawn no longer gates the engine silent; past
2*interval+ spawnless the stale one is cancelled and a fresh one originates. -
reject()refuses an already-COMPLETED held draft (M3). Rejecting an already-posted X/video draft would set CANCELLED while the tweet/video is live — a lie. Both services now raise on COMPLETED. -
list_completed_video_tasksbounded (M4). The render loop's scan is now.order_by(created_at.desc()).limit(video_render_scan_limit)(default 200) backed byix_tasks_source_status_created(migration 066), instead of re-reading every completed video task ever each cycle. -
X
edited_bodywrite deferred into the single-flight lock (M5). The CEO's edit no longer flushes before the COMPLETED check; it's applied to the re-read row inside the lock, so a concurrent approve that already posted can't have the edit land on the just-posted task. -
X
_mark_seenafter the meaningful + project checks (M6). A low-engagement mention is no longer permanently marked seen; one that later goes viral is still draftable. -
X mentions persist
since_id(M7). The engine persists the highest fetched mention id in Redis and passes it on the next fetch, so a burst >50 between ticks isn't silently dropped. -
Release gate scoped to the read-clone HEAD (M8).
_production_assesspasseshead_sha=<read-clone HEAD>so a stale older-commit CI run can't read "green" while HEAD's CI is in_progress; a missing HEAD falls to unknown (no proposal). -
_run_git30s timeout (M9). A hung git child in the release-readiness sweep now bubbles a clear error instead of hanging the thread silently. -
dep_update dedupe by
(git_url, dep_update_command)(M10). A monorepo with two distinct commands gets one open task per command, not one for the repo blocking the second ecosystem's drift. -
Engine-loop liveness watchdog (M11). Each engine loop records a heartbeat;
_check_healthalerts when last-success stalls past2*interval, so a silently-died cycle task is diagnosable. -
Video render loop commits per-task (M21). A raise mid-cycle no longer rolls back prior renders or re-originates a second held video_post draft on the next pass.
-
_detect_stuck_tasksskips held-CEO-source tasks (M22). A held release/x/video draft sitting PENDING is no longer auto-blocked, wedging the held-artifact flow. -
video_renderer_client._savetemp + atomic rename (L6). A re-render can no longer clobber the MP4 the panel is streaming mid-read. -
_commits_sincesplit maxsplit 2 (L9). A\x1fembedded in a commit subject/body no longer garbles the CHANGELOG line. -
self_healfingerprint documented as by-design per-signal (L11). The per-signal (not per-run) fingerprint is the dedup's intent; a documenting comment records the acceptance so a future reader doesn't "fix" it into a bug. -
Release heartbeat shares one redis client (L34). The ~40
redis.from_urlpools per release (one per heartbeat tick) collapse to one client per approve. -
dep_update folds redundant per-project queries (L35).
run_cyclefetches open tasks once;_eligiblechecks membership in-memory. -
CI-watch telemetry sweep parallelized (L36).
MultiProjectCITelemetrySource.fetchgathers per-project samples so one slow GitHub 429 doesn't stall the sweep. -
Panel
/ws/systemWebSocket no longer goes stale silently (C4). The socket is now shared across consumers (one per URL, ref-counted) so the A2A stream and rate-limit banner no longer double-subscribe; reconnect uses exponential backoff capped at 30s with no max-attempts freeze, and a pong-timeout watchdog force-closes a half-open connection within 60s — the "had to reload the page" symptom. -
Video-post-queue caption tracks the in-flight edit (H15). Captions now derive per render as
edited ?? serverValue, so the CEO's unsaved edit isn't clobbered by a background refetch pulling the older server draft back in. -
Settings page Save persists (H16). The Save button is wired to
settingsApi(persist + read back); each control tracksedits ?? server ?? defaultafter the transcript-retention-card pattern, so a changed value survives a page reload instead of being silently dropped. -
Tasks page filters server-side for status/team (H17). Single-select status and team now ride
useTaskswithlimit: 500and reach/tasks/summaryfor server-side filtering; multi-select + task_type/project/product stay client-side. Stops the 2MB unbounded fetch. -
useAgentsroster re-derives on live-status change (H18). AstatusEpochderived from the orchestrator-status snapshot joins theuseAgentsqueryKey, so the 10s poll invalidates the cached roster the moment a live status flip lands instead of serving a stale list. -
useMetricsreads agent counts from the existing status cache (M40). Agent counts now come from theuseAgentStatus10s poll cache (getQueryData+fetchQuerycold fallback) instead of issuing a redundant 60sgetAgentStatusfetch — one fewer network call per metrics render. -
Scorecard
refetchInterval60s → 5min (M41).SCORECARD_REFETCH_INTERVALextracted; scorecard data changes slowly, so the panel stops re-fetching it every minute. -
Feature-flag off-transitions confirm (M42). Turning a flag off now confirms via AlertDialog; pending state tracks all in-flight toggles in a
Set, so each row locks independently and a slow toggle can't leave the row in an ambiguous state. -
X/TikTok credentials clear-behind confirms (M43). Emptying all fields when credentials exist now confirms via AlertDialog before the destructive clear, so an accidental wipe of live OAuth secrets is caught first.
-
Rate-limit
syncFromApimerges by freshesthitAt(M44). An out-of-order older snapshot no longer regresses the displayed hit; the A2A page invalidates its list on/ws/systemreconnect (false → true), so a reconnect doesn't show a stale message list. -
DelegateRequest.estimated_complexitytyped asComplexity(H21). The field wasstrplus a hand-rolled int-rejector, so an invalid complexity string slipped past the schema boundary into the delegation flow. It is now theComplexityenum with a whitelisting@field_validator, so a bad value is rejected at the schema edge with no redundant int guard. -
SoftBlockRequest.resolver_typetyped asBlockerResolverType(H22). The field wasstrwith atry/except → AGENTsilent downgrade, so a typo silently fell back to the AGENT resolver instead of 422-ing. It is now the enum (422 on a bad value), the route passes the enum through, and theresolver_type_rawshadow field is deleted — no silent downgrade path remains. -
TaskResponse.documentsfield + serialization (H23). The task response dropped thedocumentslist entirely, so the panel/clients never saw a task's linked docs.TaskResponse.documents: list[DocRefResponse]is now populated via aconvert_documentshelper (defensive.get, mirroringconvert_commits), with a malformed-row defensive default. -
Deleted
SubstituteRequest.suggested_role/suggested_team(L27). The two fields had zero consumers (re-verified) — dead schema surface. Removed;reason/detailsuntouched. -
Envelope.not_foundgains a defaultremediate(L14). The error envelope'snot_foundreturned an emptyremediate(unlikenot_authorized, which carries a default), so ~35 message-only call sites gave the agent no fix hint. It now mirrorsnot_authorized: keyword-only*, message, remediate=<default>, context_briefing— the default applies for free at every call site, overrideable where a specific hint exists. -
Deleted unused
ListResponse[T]generic (L28). The generic had zero bare-ListResponseconsumers (only per-domain*ListResponsesubclasses likeNotificationListResponseremain) — dead schema surface. Removed with its__init__import /__all__entry; a negative test pins its absence. -
_delegate_static_guardsallows a cell-projects coordination root (H19). The delegate guard rejected any root with noproject_id, including a branchless coordination root that carriescell_projects(the cell-map case), so a legitimate cell-mapped root was blocked at delegation. The guard now adds a narrowand not getattr(parent, "cell_projects", None)clause — the cell-map root is admitted, while a bare no-project/no-product/no-cell-map root is still rejected (NOT the broadis_branchless_coordinationpredicate, which would admit shapes the guard should still stop). -
MegaTask
confirm_live_batchidempotency guard (M13). A double-submit (network blip / retry) onPOST /live/{session_id}/confirm-batchcreated a second umbrella + root-subtasks.confirm_live_batchnow takes asession_idparam (threaded from the route) and acquires a Redis SETNX guardroboco:megatask_confirm:{session_id}(TTL 3600s) with a result sidecar, fail-closed on redis-unreachable. A retry within the window gets the persisted result; a guard-held in-progress call raises "retry shortly" with no poll loop. The guard is placed after validation, beforebatch_id = uuid4(), so a validation-failure retry never consumes the key. -
MegaTask root-subtask draft strips
assigned_to(M14). An LLM-authoredpropose_batchdraft carrying a hallucinated or injected board-role uuid could create a board-owned CODE root-subtask, and board roles have no delivery verbs (empty claimable-status frozenset) → the umbrella deadlocks atsubmit_up. The root-subtask create loop now popsassigned_tofrom each per-iteration draft copy beforecreate_task_from_draft(the shared single-task_resolve_draft_assigneeis untouched — the explicit route-based assignee still wins there). Pop-on-copy so the caller'sdraftsdicts are unchanged. -
thin_routesreceiver-gatesadd/add_all/merge(H20). Thethin_routesconvention flagged any call whose attribute was in_DB_METHODSas a DB hit, butadd/add_all/mergeare ambiguous —seen_tags.add(tag)/cache.add(k, v)are not DB calls, yet they tripped the rule and blockedi_am_done/pr_pass._body_hits_dbnow extracts the receiver as a bare identifier for the ambiguous methods and only trips when it is a session handle (db/session/conn/s); unambiguous methods (execute/scalar/stream/query) stay on the attribute-only path. Mirrors the existing_is_router_callreceiver-extraction shape. -
_NOQA_CODEScapture tightened to[A-Z0-9, ]+(M16). The noqa-code regex[A-Za-z0-9, ]+swallowed lowercase prose, so# noqa: TC001 because pydantic needs thiscapturedTC001 because pydantic needs this→ the sanctionedTC001was buried under prose →_suppression_allowedreturned False → a falseno_lint_suppressionsBLOCK (blockingi_am_done/pr_passon a legitimate framework escape). Lowercase prose now terminates the capture soTC001is parsed alone and allowed; regression guards pin that a non-allowed uppercase code (F401) is still flagged. -
Conventions read-clone force-refetch on read (M45).
ensure_read_cloneonly fetched every 30s (TTL), so after a conventions commit MERGED to the default branch the nextget_map → _resolve → ensure_read_cloneskipped the fetch (within the window) → the read clone's HEAD was stale → a stale conventions map served for up to 30s (a correctness bug for a gate that blocksi_am_done/pr_pass).ensure_read_clonenow takes a keyword-onlyforce: bool = False;ConventionsService.resolve_workspacepassesforce=True(the only conventions-read entry point). The sha-keyed cache handles the rest — same sha → hit, new sha → re-derive — so no cache-invalidation logic or migration. Theforce=Falsedefault preserves the 30s TTL for the other three callers. -
ConventionsService._resolveno longer mutates ORM off the event loop (L25)._resolverangit rev-parse+ filesystem walks on a worker thread (asyncio.to_thread) and, while on that thread, mutated the SQLAlchemy ORMProjectTable(workspace_path/head_commit) — unsafe under SQLAlchemy 2 async. It now returns raw(root, sha)(None for non-git paths), and the three callers (get_map/restore/health) mutate on the event loop after theawaitreturns, preserving theif sha is not Noneguard (non-git paths keep their persistedhead_commit) and thehead = sha or _head_sha(project)fallback (cache key + healthhead_shabyte-for-byte unchanged). -
open_conventions_prforce-pushes the disposable scaffold branch (M15). A secondopen_conventions_pr(the CEO saves a conventions edit twice, or a restore after a save) re-commits the samechore/roboco-conventions-scaffoldbranch; the second push was non-fast-forward →GitError→ theexcept GitError: return unopenedcatch swallowed it → the method silently returned{"pr_number": None}and the CEO's "open PR" opened nothing. The push now usesforce=True(--force-with-lease, fails fast on a concurrent remote advance); the scaffold branch is disposable (sole consumer =open_conventions_pr), so force-push is safe, and theexcept GitErrorcatch stays for auth/network failures. Switching to_find_existing_prwas deliberately avoided — it would return the already-open PR pointing at the first commit's stale content. -
Roadmap cycle-completion emits a status-transition audit row (L24).
RoadmapService._maybe_complete_cyclesettask.status = COMPLETEDdirectly with notask.<status>audit row, so the cycle-exploration task's completion was invisible to the Delivery observability metrics that reconstruct cycle time from audit events. It is now an instance method that capturesfrom_statusbefore the set and emits via the canonicalTaskService._emit_status_transition_auditchokepoint (mirroringapply_escalation); the audit row flushes atomically with the status set in the caller'sflush(). The two callers needed no change (alreadyself.-bound); the siblingx_engine/video_postcompletion sites are out of scope. -
Enable the GROK provider row in grok mode (H14).
_apply_groknow enables the seeded GROK provider soresolve_for_agentroutes to the GrokCliProvider (SuperGrok auth), not just sets the global default. -
Route GROK active-token resolution to usage.json (M31). The live USAGE_SNAPSHOT sweep now reflects grok agents mid-run, not only at finalize.
-
Pass cache tokens to the usage-sweep cost (M32). Live cost estimates include Anthropic cache read/write spend, matching the finalize path.
-
Park Ollama-Cloud rate limits (M33). A
glm-5.2:cloudollama.com 429 now parks the provider via a marker map instead of crash-respawning into the weekly limit. -
Sweep orphan spawn sessions at startup (M34).
agent_spawn_sessionsrows left open by a crash are closed so their tokens roll into usage/cost summaries. -
Persist the revisit_resets counter (L12). Migration 067 adds the column so the PM-respawn breaker's revisit counter survives a restart (mirrors tracing_resets).
-
Date-gate the Sonnet-5 promo revert (L18). Billing returns to Sonnet-5 list rates after 2026-08-31 automatically.
-
Warn on grok run-log parse failure (L20). A malformed
ROBOCO_GROK_RUN_LOGnow warns instead of silently recording a zero-cost run. -
Bound agent HMAC tokens with iat/exp (M35). The agent token was a static
HMAC(secret, "id:role:team")hex with no expiry — a leaked token was valid forever.issue_agent_tokennow mints a{base64url(payload)}.{sig}token carryingiat/exp(TTLagent_token_ttl_seconds, default 7d) when a TTL is passed, and the orchestrator passes one at spawn;verify_agent_tokenrejects an expired token (the static hex form still verifies, so a no-TTL caller is byte-for-byte unchanged). Backward-compatible: the format is detected by the.separator. -
Re-mint the sliding session cookie only near expiry (M36a).
get_agent_contextre-minted + re-set the cookie on every authenticated request, so a stolen cookie'sexprolled forward with the legitimate user. The cookie is now re-minted only when the current token is withincloud_auth_remint_threshold_seconds(default 1d) of expiry; far-from-expiry requests pass through with no Set-Cookie, so a stolen cookie's lifetime stays fixed. -
Redis
jtirevocation for cloud-auth logout (M36b). FastAPI Users' built-in/logoutonly cleared the cookie — a stolen copy stayed valid forcloud_auth_cookie_max_age. JWTs now carry ajti, a custom/logoutadds the current cookie'sjtito a Redis revocation set (TTL = the cookie's remaining life), andread_tokenrejects any token whosejtiis in the set. Fail-OPEN on Redis unavailable — thepwd_fppassword-rotation check remains the strong user-wide revocation;jtiis the per-session logout kill and a Redis hiccup never locks out the CEO.
Security
- Agent-token 401 signature-mismatch loop (root cause). The agent HMAC token was signed over the agent slug while the MCP servers sent
X-Agent-IDas the UUID (since 2026-05-02 /453a7ae2), so once MCP servers started forwarding the token (6ed4e139/53391f22) every agent→API call failedverify_agent_tokenwith "signature mismatch" and thepr_reviewer/agent fleet 401-looped. The token is now signed over the UUID, the container env setsROBOCO_AGENT_IDto the UUID, and the orchestrator self-heal resolves slug→UUID before verifying — so a stale slug-signed token is correctly rejected and reissued. Closes the 0.19.0 prodpr-reviewer-1401 class. - Cloud-auth header-spoof hole closed for every role (C1).
get_current_agent_id/get_current_agent_slug(the A2A, notification, and stream endpoint deps) routed through header-trust even under cloud auth, so a bareX-Agent-IDspoofed any non-CEO role. Both now delegate to_cloud_auth_agent_contextwhencloud_auth_enabledis on — a verified agent HMAC token resolves the real identity, a CEO session cookie resolves toceo, and a bare header is 401. Dev mode (cloud auth off) is byte-for-byte unchanged. - UNSIGNED agent token omitted at the sixth header builder (H1).
mcp/utils._get_agent_headersstill sentX-Agent-Token: UNSIGNED(the dev-mode sentinel) — the2b8bc10dfix covered the other five builders. The sentinel is now omitted, matching the rest, so an UNSIGNED token never reaches the API middleware to be rejected as a signature mismatch. - Startup guard: cloud auth + nginx CEO-token is a misconfig (H2).
ROBOCO_CLOUD_AUTH_ENABLED=truewith a setROBOCO_PANEL_AGENT_TOKEN(nginx CEO-token injection) now fails loud at startup — the injected token is an alternative human-auth tier that bypasses the login cookie, so layering both is a public-exposure footgun the operator must resolve by unsetting the token. - Panel auth-probe last-known-good cache (C2).
panel/src/proxy.tsre-probed/api/auth/statuson every request and failed open tofalse(cloud auth off) on any probe error, so a transient orchestrator hiccup could silently un-gate the dashboard. The probe result is now cached for 30s and reused on a probe failure; only when there is no fresh cache does it fail open to the safefalsedefault. - Per-IP rate limit on
POST /auth/loginunder cloud auth (L31). ALoginRateLimitermiddleware (mounted only when cloud auth is on) increments a Redis keyauth:login:rl:{ip}with a 60s TTL and returns 429 pastROBOCO_LOGIN_MAX_ATTEMPTS(default 10) so the CEO login endpoint can't be brute-forced. Non-login paths pass through; on Redis-down it fails open (login is already password-gated). Production opens a per-request Redis connection;app.state.login_redisis a test seam. - Auth gate coverage under cloud_auth. v1 flow/do role guards, orchestrator
_require_ceo, and HTTPrequire_panel_tokennow require a verifiable HMAC token (agents) or CEO session cookie (panel) whenROBOCO_CLOUD_AUTH_ENABLED; unauthenticated/api/settings,/api/agents,/api/a2a/tasks,/api/kanban,/api/usage,/api/system/rate-limitsgated. local_llm_base_urlrejects non-internal hosts (H13). The fire-and-forget hot path (learning distill, memory indexing, X/TikTok drafting) routes tolocal_llm_base_url, so an env mistake pointing at a paid cloud LLM endpoint would silently route a hot-path call to a metered API. The setting now validates at config load — host must belocalhost/127.0.0.1/::1/roboco-ollama, end in.svc.cluster.local, or resolve to a private/loopback IP — and fails loud on any other host, so a misconfigured paid endpoint can't slip in by env mistake.- Clone/fetch PAT injected via
http.extraheader, not URL-embedded (H11). The clone/fetch/ls-remote/push argv URL-embedded the GitHub PAT, so/proc/<pid>/cmdlineexposed it to any process on the host. The PAT now rides a per-callgit -c http.extraheader=Authorization: Basic <base64>config acrossWorkspaceService(clone + read-clone fetch) andrelease_executor(clone + push), so the token never lands in argv. rebase_onto_basegates on a clean tree (H8). A rebase ran without a clean-tree check, so uncommitted agent edits could be silently discarded by the rebase. The gate now mirrorspull—git status --porcelainmust be empty elseValidationError(DIRTY_WORKSPACE)— so a rebase never silently discards uncommitted work._link_commit_to_taskflushes, doesn't commit out-of-band (H9). The link call usedcommit(), so it committed the verb runner's savepoint and dragged in pending orchestrator state mid-verb. It now callsflush()(the savepoint stays open), so linking a commit can't persist unrelated in-flight state.
[0.18.0] - 2026-07-04
Added
- A2A is now a delivered inbox, not a write-only log. The new
read_a2averb returns an agent's unread message bodies (atomic, own-sends excluded) and is granted to every delivery role, and the claim briefing'slist_unread_a2acarries an incoming-onlylast_message_preview(single correlated query, no N+1). A peer'sdmnow actually reaches the recipient's reasoning instead of sitting unread — the gap that motivated retiring the channel/session backbone in the first place. - Fable-mode (default-off): opus-fable-playbook adoption.
ROBOCO_FABLE_MODE_ENABLEDgates two additive levers that make the fleet behave more like Fable 5 on the existing model tiers. The doctrine layer composes the vendored behavioral doctrine (agents/prompts/doctrine/fable.md, fromgithub.com/rennf93/opus-fable-playbookMIT, frontmatter stripped) into every agent's system prompt right after the universal base rules. The hook layer installs 5 vendored turn-discipline/honesty/verification scripts (docker/scripts/fable-*.sh) alongside RoboCo's own hooks on the Claude runtime, appended after (never replacing) the existing per-event entries; the grok runtime gets only the non-denying honesty-nudge hook in this V1 — a grokPreToolUse/Stophook deny cancels the entire run, so denying hooks are deliberately not ported there yet. Off by default: the composed prompt, generated settings.json, and grok hooks are byte-for-byte unchanged when the flag is off. No new eval harness — watch the existing rework/spawn-waste dashboard instead. - X feature-spotlight marketing (default-off). A second, independent sub-switch on top of the X engine:
ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED(needsROBOCO_X_ENGINE_ENABLEDtoo) spawns the Head of Marketing on a periodic interval (default 3 days,ROBOCO_X_FEATURE_SPOTLIGHT_INTERVAL_SECONDS) to investigate RoboCo's own shipped capabilities — CHANGELOG.md, the feature-flags ledger, docs/map, the charter, the knowledge base — and draft ONE spotlight for an under-publicized feature via a newpropose_feature_spotlightdo-tool (Head-of-Marketing-only), deduped against ax_seen_featuresledger (migration 061) so a feature is never re-covered. The draft materializes as asource=x_featureheld task — mirrorsx_post/x_replyexactly, rendered in the same panel X Post Queue with its own "Feature spotlight" label/badge, reviewed/approved/rejected by the sameXPostService— no new surface. The CEO's real voice now has a home: a newcompany_goals.brand_voicecharter field (migration 061, panel-editable in Business → Goals) feedsXEngine._voice_guide, applied to ALL three draft kinds (release, reply, spotlight); falls back to a generic baseline until the CEO fills it in. - FE/UXUI design bar. Frontend and UX/UI agents now carry a distilled design-taste bar in their team prompts (from
Leonxlnx/taste-skill, MIT): the three dials (design variance / motion intensity / visual density) with dense-product-UI defaults, plus typography/hierarchy, spacing/layout, motion, and AI-tells-to-avoid rules, scoped to respect a project's existing design system rather than override it. Prompt-only —compose_promptis unchanged; a composition test guards it, and the developer-role pointer heading is deliberately worded so the backend-dev negative test still holds.
Changed
- A2A + Notifications are the two comms primitives. With the channel/session backbone gone, coordination rides the task state machine + task details, direct peer contact rides A2A (
dm+read_a2a, same-cell), and formal ack-required signals ride Notifications. The Secretary's company-wide announce reroutes from the retired broadcast channel to a Notification fanned to every agent's inbox. Docs, panel, and CLAUDE.md are rewritten A2A-primary.
Removed
- The channels / groups / discussion-sessions / messages subsystem is retired. Direction was validated first — agents barely read channel/session messages, and A2A bodies never reached the recipient's reasoning — so the whole backbone is gone. Removed the
say,open_session,link_session, andchannels()verbs;MessagingService, the five tables + models, and theChannelType→agents_config→ permissions →stream.pypolicy cascade; theCONVERSATIONSRAG index and channel seeding; the channel/session API routes + the/ws/channels//ws/sessionsWebSocket streams; and the panel's Communications surface (channel/session views + the auditor channel-feed + a dead communication-metric route). Migration 060 drops the five tables (FK-safe order), thejournal_entries.session_idcolumn,chunks_conversations, and the four enum types — verified 001 → 060 against a real Postgres, single head.ExtractedMessage(the extraction pipeline) is retained and confirmed never persisted to a dropped table.
Fixed
- Release-manager read clone no longer walks the entire history. The release-readiness read clone was tagless, so the diff-since-last-tag walk saw no tags and classified all history as unreleased (the observed 729-commit blow-up). The clone now carries tags, so the semver bump and CHANGELOG-completeness checks assess only the real delta.
- Panel RAG-health errors are labelled by subsystem. Health error lines in the panel now name which subsystem failed instead of rendering an unattributed error.
- The CEO's brand_voice/north_star charter now reaches the board exploration spawns.
board_triage's idle branch (hit whenever the Product Owner's roadmap-exploration or Head of Marketing's feature-spotlight-exploration one-shot spawn finds no strategic root to review — their directly-assigned exploration task is never itself a "strategic root awaiting PM review") built its briefing withfull=False, socompany_goals— and thereforebrand_voice— never reached either spawn despite both prompts claiming the charter is "already in your briefing." A new narrowinclude_company_goalsopt-in on_briefing_for(_resolve_company_goals) fetches just the cheap company_goals singleton on that path, without pulling infull's other heavy sections (team activity, blockers, an institutional-memory RAG search) — every other briefing consumer is unchanged.
Security
- Three production agent hooks repaired — the prompt-injection guard is functional again. A shell stdin bug (
<pipe JSON> | python3 - <<'PY', where bothpython3 -and the heredoc claim stdin, so the piped JSON was silently discarded) had left three hooks reading empty input and never firing:user-prompt-hook.sh(the prompt-injection guard) allowed injection strings instead of denying them,post-tool-budget-hook.shhashed every tool call to{tool:unknown}(blinding per-tool loop detection), andusage-report-hook.shnever synced token usage. Fixed topython3 -c "$(cat <<'PY')"(verified before/after: the guard now denies injection strings, exit 2); the already-correctfable-*hooks were untouched.
[0.17.0] - 2026-07-03
Added
- Sandboxed per-agent test DB/Redis. A dev agent's gate can now run against a throwaway database instead of RoboCo's own production Postgres. When
ROBOCO_SANDBOX_DB_ENABLEDis armed, each spawn of an opted-in project (itsprojects.sandbox_servicescolumn set, migration 057) gets orchestrator-provisionedpostgres:16-alpine/redis:8-alpinesibling containers with random per-sandbox credentials and a tmpfs data dir, injected asROBOCO_TEST_DB_*/ROBOCO_TEST_REDIS_*in place of the legacy prod-creds gate-env injection. Lifetime tracks the agent container 1:1 (teardown at every removal path + an orphan janitor, grace-windowed so a sweep can't reap a mid-flight spawn's sandbox); provisioning failure fails the spawn loud, and docker-in-agent stays structurally absent (SandboxProvisioner). - Full mobile UI pass. The control panel is now usable from a phone end to end: a
useIsMobilehook, a shared table→card transform belowmd(tasks/projects/products/sessions/metrics), a scrollable snapTabsListin the base primitive, stacked action rows on the CEO approval queues, and a persistent bottom tab bar alongside the existing drawer. The Communications and A2A master-detail views become single-pane drill-downs belowlg(fixing an unconstrained-height scroll bug), charts and the git diff viewer adapt, andvhheights move todvhfor the mobile-Safari toolbar. - Cloud auth via FastAPI Users (default-off).
ROBOCO_CLOUD_AUTH_ENABLEDlets the panel/API be exposed beyond localhost without changing the CEO's local no-login flow while off. A single seeded user (no registration router; migration 058) authenticates via a cookie session that is sliding — every authenticated request re-mints the 30-day cookie, so an active session never expires — and aJWTStrategybound to a fingerprint of the current password invalidates every prior session on a password change. When armed, header-trust dies for humans: any agent-role claim (including a privileged PM/board role) without a valid HMAC token or session cookie is 401, closing the header-spoof exposure on the host-published:8000port for every role; the agent-fleet HMAC path and the orchestrator'ssystemself-PATCH are unchanged in both modes. - RoboCo X (Twitter) account (default-off). With
ROBOCO_X_ENGINE_ENABLED, the Head of Marketing drafts a post when a release publishes and drafts replies to meaningful mentions — but nothing auto-posts: every tweet is held in a panel queue for the CEO to edit and approve. Drafting is local-model-only, clamped to 280 chars; the four OAuth 1.0a secrets live Fernet-encrypted in a singleton row (migration 059, the API only reportshas_credentials) and agents never hold credentials or make outbound calls. Approving posts under a Redis single-flight lock that re-reads committed state so a concurrent approve can't double-post. - Board roadmap engine (default-off). With
ROBOCO_ROADMAP_ENGINE_ENABLED, on a weekly interval the Product Owner is spawned one-shot to explore the company's repos, KB, metrics, and charter and propose a themed cycle — a one-line goal plus 3–7 roadmap-item drafts — via a Product-Owner-onlypropose_roadmapverb. The CEO acts on each item in a panel queue: approve materializes it as aBACKLOGtask (never auto-started), reject records a reason. The cycle rides as a marker on the exploration task (no new table).
Security
-
Production Postgres/Redis isolated from agent containers. A second user-defined bridge (
roboco_data) now carriespostgresandredisonly, with the orchestrator the sole multi-homed service. Spawned agent containers and their sandbox sidecars stay onroboco_defaultand can no longer resolve or reachroboco-postgres:5432/roboco-redis:6379— network membership is the containment (redis has no auth). Host-published ports and all legitimate paths (agent↔agent A2A, orchestrator→agent SDK polls, MCP→orchestrator, ollama,docker exec/inspect) are unaffected.ROBOCO_DB_NETWORK_ISOLATEDtravels with the topology and suppresses the legacy prod-creds gate-env injection. -
The A2A switchboard — the org chart as pair cards.
/a2a's desktop default is now a grid of agent-pair cards (every pair the permission matrix allows — 70 pairs across cell/PM-chain/board/cross sections, derived statically fromcan_a2a_directat import time), each lighting up when either side messages the other (45s CSS fade, driven purely by the livea2a.messageframes — A2A only, never verbs, by CEO ruling). Clicking a card opens the existing transcript + chime-in drawer; never-talked pairs render dimmed with an explicit empty state; the v1 list stays as the mobile/compact fallback. Backed by one CEO-gatedGET /a2a/chat/admin/pairsroute joining the static matrix against conversations in a single bulk query. -
Secretary full task access; PM lighter editing — and a closed over-permission hole. The Secretary's CEO-gated
editdirective now covers the full content surface (title/description/AC/priority/team/complexity/nature plus claim-aware reassignment through the real reassign paths), andread_taskreturns full detail. Scouting the PM side foundhas_higher_permsgave PM identities UNRESTRICTED admin onPATCH /tasks/{id}(ASSIGN is not team-scoped) — now cell PMs hard-403 outside their team and both PM roles are capped to the content allowlist with zero status changes via that surface; CEO/Board/Auditor keep full admin. -
Prompter memory — intake remembers the task history. An intake session's prompt now carries a compact chronological digest of the scoped project's recent tasks (per-project for MegaTask scopes; hard-capped at ~1,000 tokens worst case, typically ~300), and the interviewer gains a
search_past_taskstool (bounded, both runtimes share one implementation) to check precedent mid-conversation — so a new task can be described and sequenced against what actually happened before. Informational only: the sequencing analyzer keeps ownership of ordering. -
A2A live view — watch the fleet talk, and chime in. New panel page (
/a2a): live conversation list + transcript, updated in real time via a newA2A_MESSAGE_SENTevent fanned through the existing/ws/systembridge (frames carry capped excerpts; full bodies stay on REST). The CEO can reply into any task-linked conversation as themselves. Agent→CEO communication is reply-only and hard-budgeted in code: no agent may initiate toward the CEO (stateless matrix block), and inside a conversation the CEO has posted in, each agent may send at most one message per CEO message (per-conversation, per-agent — 1:1:1 with multiple agents), with a rejection envelope that says to wait rather than retry. CEO→agent stays unrestricted — the one asymmetric rule in the matrix. -
e2e scenario 4 — the MegaTask umbrella. Seeds an umbrella + two dependency-linked root-subtasks; proves the sequencing hold (RS2's
i_will_planrejectedunmet_dependencywhile RS1 is live), completes RS1 through the entire real chain (dev→QA→doc→PM→gate→CEOapprove-and-mergeto master), verifies the hold lifts, completes RS2, and closes the umbrella through its branchless path — Main-PMcompleteescalates,POST /tasks/{id}/ceo-approve(notes ≥ 20) finishes it, and the umbrella never carries a PR. Six scenarios now cover the full company loop in ~50s. -
The PR-gate turn cut — assembled parents auto-submit to the reviewer. When every child of an assembled parent is terminal, the orchestrator used to spawn the PM just to call
submit_up/submit_root— a whole agent turn whose substance (freshness rebase, integrity check, PR open) is deterministic gate code. The closure dispatcher now runs the REAL submit verb through the internal API as the owning PM (_try_auto_submit); the task lands inawaiting_pr_reviewand the reviewer dispatch takes it with no PM turn spent. Every gate is intact: a submit rejection (freshness/integrity — the case that genuinely needs judgment) falls back to the classic PM closure spawn,pr_failstill routesneeds_revisionto the PM, and the PM keeps the final merge turn. Branchless coordination parents (MegaTask umbrellas) never auto-submit. Gated byROBOCO_PR_GATE_AUTO_SUBMIT_ENABLED(default on); each auto-submit leaves atask.auto_submittedaudit row. -
Task search that actually searches. The task list's search box only matched titles client-side — and the trimmed summary payload deliberately carries no description, so keyword/details/id search was impossible in the browser by design.
GET /tasks/summarygainsq(ILIKE over title + description, id-prefix match, composed with team/status filters and view-permission scoping); the panel debounces the box into the fetch and drops the title-only client filter that would have hidden description matches. -
Trace timestamps on structured notes. Note sections (dev/qa/doc/reviewer/handoff) are overwrite-in-place with no stamp, so there was no way to reconstruct WHEN a note landed.
apply_structured_notenow stamps ISOwritten_atbeside the model fields and the panel notes tab renders it next to each card title. Progress updates, commits, and journal entries already carried timestamps — this closed the one gap. -
The Secretary can modify tasks (CEO-gated). The
control_taskdirective gains aneditaction restricted to the content allowlist (title, description, acceptance criteria, priority) — status/ownership/git fields keep their own audited paths — andGET /secretary/tasks?q=resolves task NAMES to ids (Secretary/CEO only), so a CEO chat instruction like "sharpen the greeting task's title" can target the right task without a UUID. -
e2e scenario 3 — the pr_fail revision loop and the root → CEO chain. 3a walks the honest revision loop: the reviewer
pr_fails the assembled cell PR with a concrete issue →needs_revision→ the PM re-enters viai_will_plan(full plan gates), real fix work lands on the cell branch (the 0.14.0 unchanged-PR hard gate correctly refuses resubmission until it does), and the second gate pass rides through to the merge. 3b closes the whole company loop with no LLM anywhere:submit_rootopens the root→master PR, the reviewer gate-passes it, the Main PM'scompleteescalates the root parent to the CEO, and the REALPOST /tasks/{id}/approve-and-mergeendpoint squash-merges to the origin's master. The fake GitHub now recomputeshead.shalive from the branch (real-GitHub semantics the unchanged-PR gate depends on). -
e2e scenarios 2 + 2b — the PM merge chain, before and after the cut. Shared scripted-agent arcs (
tests/e2e_smoke/arcs.py) drive a root→cell→dev hierarchy: the child lands via the scenario-1 arc (real squash through the fake GitHub), then scenario 2 walks the classic PMsubmit_up→ reviewerpr_pass→ dispatcher re-claim → PM merge chain, and scenario 2b proves the turn cut end-to-end — no agent calls submit;_try_auto_submitdrives the real verb through the real API and the reviewer→PM tail runs unchanged, landing the child's file on the root branch.
[0.16.0] - 2026-07-02
Added
-
The e2e lifecycle smoke harness — scripted agents drive the REAL gates, no LLM anywhere.
tests/e2e_smoke/stands up the real API (v1 flow/do routers + middleware served by uvicorn) over the ephemeral test Postgres, a local bare git origin standing in for GitHub, and a fake GitHub REST layer whose PR merges are REAL git merges (squash included) on that origin. A deterministic driver reloads the REAL MCPflow_server/do_servermodules per agent (role-scoped manifests from the realrole_config) and walks the lifecycle through every gate: claim (real clone + per-task worktree) → tracing-gap → claim-note → plan gate → commit → PR → the fulli_am_doneladder (during-work journal, handoff section, reflect, per-AC artifacts) → QA (learning note + per-criterion verdicts) → documenter →awaiting_pm_review, in ~5s. Seam bugs — tool↔gate schema drift, squash merges, stale refs, worktree routing — now die in CI (make e2e-smoke, its own workflow) instead of in a live run. Scenario 1 covers the leaf-dev arc; PM-merge/CEO chains extend the same harness. -
HTTP security hardening — a fastapi-guard layer for public/cloud exposure, default-off and calibrated for RoboCo's own traffic. RoboCo can now front its API with a fastapi-guard
SecurityMiddleware+ per-route decorator layer (roboco/security.py), gated behindROBOCO_GUARD_ENABLED(default off) — when off,create_appnever mounts the middleware and the request path is byte-for-byte unchanged, so the decorators are harmless no-ops. Armed, it adds IP/rate controls, a signature WAF, security headers, cloud-provider/honeypot checks, and an emergency-lockdown kill switch (ROBOCO_GUARD_EMERGENCY), plus three RoboCo-specific custom validators the stock WAF cannot cover: prompt-injection, secret-exfil, and internal-SSRF scanning on the prompt-facing and agent-content surfaces. Nine distinct decorators are applied thoughtfully per-surface across ingress and sensitive routes (rate-limit, size caps, content-type, behavior analysis, cloud blocking, honeypot form-traps, usage monitoring, suspicious detection, custom validation). Exposure is env-driven —enforce_httpsfollowsROBOCO_ENVIRONMENT, so a personal NAS deploy stays relaxed while a cloud host enforces TLS — and telemetry to a guard-core platform is separately gated (ROBOCO_GUARD_TELEMETRY_ENABLED, no data leaves the box while off). -
Scanner honeytrap & auto-ban (Surface N) — two layers, matched to where traffic actually lands. Behind nginx only
/api,/ws,/health,/readyreach the orchestrator, so guard can only see (and ban) scanner probes on those paths — the classic root probes (/.env,/wp-login.php,/phpmyadmin,/.git/config) hit the panel. So Surface N is split: (1) the guardthreat_ban_confignow carriesrecon/sensitive_file/cms_probingcategories, turning repeated scanner probes on/apipaths into an adaptive per-IP auto-ban (redis-backed, 24h) once enforcement is active; and (2) nginx drops the classic root scanner paths at the edge with444(connection closed, no response) before they reach the panel, anchored to known scanner fingerprints so/.well-knownand every real route are untouched. The auto-ban only fires in active mode (passive logs the recon hit) and needs redis; the nginx edge-drop is always on. -
Guard WAF calibration — active enforcement no longer false-positives on RoboCo's own traffic. The first end-to-end run of the guard surfaced that active enforcement would block ~50% of legitimate agent traffic: RoboCo's request bodies are code, SQL, unified diffs, file paths, HTML, and URLs (task specs, agent notes/commits, RAG queries, git bodies, chat), and the stock signature WAF (SQLi/XSS/path-traversal/URL detectors) flagged them as attacks.
build_security_confignow excludes RoboCo's free-text top-level body fields from WAF scanning (excluded_detection_body_fields, derived from the real request models — including the free-form container fields whose nested prose is stringified and scanned), which drops the false-positive rate to zero while keeping the WAF active on every structured (id/enum/slug/branch) field and leaving the custom prompt-injection / secret-exfil / SSRF validators — which run independently of the exclusion — fully in force. A new end-to-end integration test (tests/unit/test_security_middleware.py) mounts the real middleware, drives guard's lifespan, and fires real requests to prove: passive mode is genuinely log-only (never blocks), active mode does not false-positive on realistic agent payloads, threats are still blocked even inside excluded fields, and the WAF still fires on non-excluded fields. The NAS composes arm the guard in passive/log-only mode (ROBOCO_GUARD_PASSIVE_MODE=true,ROBOCO_GUARD_FAIL_SECURE=false) so a deploy calibrates against real traffic before any flip to active enforcement. -
Per-role token observability + a live spawn-waste signal. Usage analytics now break down by role, not just agent/team/model. Every usage breakdown (
/usage/by-agent|by-team|by-model) additionally carriestokens_cache_read,tokens_cache_write, and a per-groupcache_hit_rate(the query already summed the cache columns; they were being dropped from the response). A newGET /usage/by-rolereports per-role cost + cache-hit-rate, and a newGET /usage/spawn-wastesurfaces spawn churn — the per-role rate of unproductive spawns (zero output tokens: system prompt loaded, nothing delivered) plus the current respawn-tracker strikes (wedged agent/task pairs the circuit breaker is counting). The Metrics → Token Usage panel gains a "Cost & Cache by Role" table and a "Spawn Waste" card. -
Per-role compute policy — model tier + reasoning effort. Each role can carry both a model tier (
ROLE_MODEL_MAP) and a per-role reasoning-effort level (ROLE_EFFORT_MAP→CLAUDE_CODE_EFFORT_LEVELinjected into the agent container). The effort map ships empty (inert) — populating a role is opt-in, only after verifying on one spawn that the level actually moves token usage (the CLI config key is version-dependent, and a wrong value is a silent no-op). Roles routed to Haiku ignore effort (Haiku has no effort control). -
Spawn preflight (default-off,
ROBOCO_SPAWN_PREFLIGHT_ENABLED). A flag-gated guard that refuses to spawn a non-human delivery role absent fromGATEWAY_ENABLED_ROLES: such a role gets no gateway manifest and can never claim its work, so the dispatcher would respawn it on the same task forever. Instead of burning the full system prompt on each futile retry, the spawn is refused (AgentReadinessError) and the overseer is alerted once. Inert in practice — every real delivery role is gateway-enabled — so it's a misconfiguration guardrail. Armed on the NAS composes, off in the published registry compose. -
GET /api/tasks/summary— a trimmed task list for panel views. The panel fetched/api/tasksunbounded and full-fat (~2MB per refresh measured live, ~21KB/task); the new route returns exactly the fields list views render (~50× lighter, includingcompleted_at+board_review_completefor the CEO queue), and the eleven previously-unbounded task list routes (/my,/pending,/blocked, the awaiting-* queues, …) now take a cappedlimitparam. -
Notification-spawn cooldown — the missing loop-breaker on task-less dispatches. The escalation / approval / audit / a2a dispatchers spawn agents from unacknowledged notifications with no task attached, so neither the readiness gate nor the PM respawn circuit breaker (both task-keyed) ever saw them: an unacked notification respawned its recipient on every dispatcher tick, unbounded. A cross-tick damper (
ROBOCO_NOTIFICATION_SPAWN_COOLDOWN_SECONDS, default 600) now allows one spawn per (agent, notification) per window — the notification stays pending, so the next window retries if it is still unacknowledged;0restores the legacy every-tick behavior. -
request_changes— the PM's merge-level reject atawaiting_pm_review. A PM that caught a genuine AC/scope violation at merge review previously had no in-band way to send the work back (its only verbs there werecompleteand escalate), so it loopedi_am_blocked→ escalate → unblock → re-block — the live fe-pm loop from the S6 MegaTask run. The new PM verb (cell_pm + main_pm) transitionsawaiting_pm_review→needs_revisionwith at least one concrete issue, appends the issues to the dev's notes, routes the revision like a QA fail (original developer for a leaf, the revision PM for an assembled task), and a2a-delivers the reject reason to the new owner so it is never stranded.
Changed
- Claim-scoped context briefing — the heavy sections ride only on context-acquisition verbs. Production transcripts showed every flow-verb response (success and rejection,
i_am_idleincluded) carryingcompany_goals+recent_team_activity(+ task handoff) — a ~500–600-token constant floor re-sent on every verb call and re-read at cache-read price on every later turn (a failedi_will_planwas observed shipping a 24.8KB handoff on an error envelope). The full briefing now attaches only to context-acquisition verbs (give_me_work, claims,i_will_plan,resume,triage); every other verb gets a slim signals-only briefing (unread A2As/mentions/notifications + metadata gaps — the partsi_am_idle's unread check actually consumes). Slim responses also skip the heavy DB queries entirely. - LLM-facing payloads are capped, with explicit truncation markers. Embedded diffs (QA
claim_reviewevidence, theevidenceverb,roboco_git_diff) cap at 20K chars with a pointer to the full diff (the PR / file-scoped diff — the panel's HTTP diff route stays uncapped); notification bodies, handoff journal content, and the company-charter north star clip to briefing-sized excerpts (full texts stay readable vianotify_getand the panel); kb/error/learning search results, mentor sources, and RAG citations cap per-item content. Median payloads are untouched — these bound the tail (observed p90s of 34–37KB per single tool result). - The optimal MCP server is role-scoped, like flow/do already were. Agents no longer carry all 20 optimal tool schemas (~3.7K tokens of every turn's context): error tools go to developer/QA, standards/review to the delivery+review roles, decisions to PMs/Board, doc indexing to the documenter; search/mentor/learnings stay universal. Destructive index management (
clear_index,reindex_all) no longer registers for any agent — dev/test only viaROBOCO_ALLOW_FULL_TOOLSET. Unknown roles fail open to the full set. - Agent Bash output is explicitly capped (
BASH_MAX_OUTPUT_LENGTH=20000in the generated agent settings) so a giant gate/test dump can't flood the session context, and the spawn-waste metric counts Anthropic sessions only (basis: anthropic_sessions) — non-Anthropic transcripts don't reliably populate output tokens and were reporting phantom waste. - Panel: request flood and fat payloads killed. Prefetch is disabled on every
Link(hovering the sidebar no longer fires a page-data fetch per item), the task list and CEO approval queue consume the trimmed/api/tasks/summaryroute, the logo/icon images are slimmed (446KB → 7KB logo), and React Query DevTools loads in dev builds only. - Model-tier routing rebalanced for cost. QA now routes to Haiku (mechanical gate work whose cost is cache-dominated) and Main PM moves off Opus to Sonnet 5 — coordination cost is dominated by cache read/write, and Sonnet 5's cache-write is ~12× cheaper than Opus. The Main PM move is a watched experiment, revertible via
ROLE_MODEL_MAPor a per-slug model override without a code change. (Builds on the earliersonnet→Sonnet 5 alias adoption and the PR-reviewer→Opus tiering.)
Fixed
-
Claude Code capability lockdown — shared credential mount + curl|sh RCE closed. An audit of every capability reachable inside an agent container found the fleet-shared Claude OAuth credentials (
~/.claude/.credentials.json,~/.claude.json) readable by every role and exfiltrable through externally visible surfaces (a PR body, an agent note) — now denied at both thesettings.jsonpermission layer (correct//-absolute form) and the bash-guard hook. Also closed: arbitrarycurl|wget | sh-shaped remote-code execution (the old rule only denied github.com; now any-host pipe/<(…)/eval $(…)into a shell is blocked, scoped to shells so data pipelines are untouched), and skill/slash-command loading (--disable-slash-commands) as an ungated capability channel. Conservative bar throughout — every deny carries a test proving legit flows still pass; four deeper hardenings (docker egress allowlist, read-only~/.claudecarve-out, per-agent workspace isolation, SDK turn caps) are spec'd for follow-up rather than guessed at, given the venv-brick precedent. -
MegaTask intakes get their architectural-conventions block.
_resolve_intake_ambientthreaded the multi-projectproject_idsscope to the history digest but not to the conventions resolver, so a MegaTask intake saw no conventions ambient even with the flag on. Both sub-resolvers now share one id→project resolution path and cover all three scopes; regression test pins the threading to both. -
MegaTask root-subtasks can complete through the Main-PM path.
_main_pm_complete_guardandTaskService.escalate_to_ceorefused ANY parented task as "not a root" — but a batch root-subtask is parented (the umbrella) BY DESIGN while carrying its own project/branch/PR. Both sites now consultis_batch_root_subtask(the single-source identity predicate the other exemption sites already use), socomplete→ CEO escalation works for batch roots while plain subtasks stay refused. Found by e2e scenario 4 on its first run; live root-subtasks previously needed CEO god-mode to close. -
Every
agent.spawnedaudit row names its dispatcher. A rogue spawner could not be identified live — the audit row carried container/model but not which of the ~27 dispatch loops launched it.spawn_agentnow takesspawned_by, stamps it into theagent.spawned/agent.spawn_faileddetails ("unspecified"when absent so audit queries never miss the field), every call site passes its loop name, and a whole-package AST sweep test fails any future caller that omits it. -
Admin-complete refuses while the task's PR is still open.
PATCH status=completedon a task whose work session records an OPEN PR stranded its commits unmerged (bit the CEO twice live). The override now refuses with the PR number/URL and the concrete consequence — merge first, or approve viaPOST /tasks/{id}/ceo-approve— checked before the generic hatch text;force: truestays the deliberate, audited escape, and a merged/closed PR changes nothing. -
The panel's CEO-approve button works on every gated task. On
awaiting_ceo_approvalthe task header offered only "Approve & Merge" (POST /approve-and-merge, no notes), which 400sNO_PRon a branchless MegaTask umbrella — the CEO's approve just failed. The primary action is now "Approve & Complete" through theCeoApproveDialog(POST /ceo-approve, notes ≥ 20 chars); "Approve & Merge" remains, but only when the task actually carries a PR. -
Tests no longer leak state into a developer's live Redis. The self-heal originate tests wrote
self_heal:notified:*dedupe keys (2h TTL), thei_am_blockedrate-limited tests wrote a NO-TTL "anthropic rate-limited" tracker blob, and notification tests left short-TTL purpose-dedupe keys in whatever Redis listens on localhost — order/state-dependent poison for any test (or local orchestrator run) reading the real instance. A root-level autouse fixture now points the computedredis_urlat an unreachable port for every test (no test uses a real Redis; every production Redis path is fail-open by design), with explicit per-file guards kept at the two proven writers. -
The quality gate is fully green again — ten latent xenon C-ranks cleared. Master CI has been red at a smoke test whose mock predated the armed team-match gate, so neither CI nor a local full gate had reached the xenon step — hiding that the team-match sweep's inline
agent_team=str(agent.team) if …kwarg had pushed nine gateway verb bodies (i_am_done,resume,unclaim,submit_up,submit_root,complete,escalate_up,escalate_to_ceo,fail_review) from B to C unseen. A sharedactor_context_fields()helper computes(actor_slug, agent_team)once per verb — zero behavior change — and the new admin-complete open-PR check is likewise extracted to a helper so the override function stays under the threshold. -
Declared dependencies become real edges (MegaTask + delegate). The live S6 out-of-order break, both halves. Batch intake: each draft's
depends_on(the CEO's declared "Depends on" list, batch indices) is now wired verbatim into the sequencing DAG —SequencingService.analyzeunions declared edges with the derived collision rules (self/out-of-range references rejected, cycles caught by the existing toposort); previously only analyzer-derived file-overlap edges were wired and a declared wave could be silently dropped. Delegate: acodesubtask now REQUIRES a non-emptyintends_to_touchcollision surface (newTASK_AT_DELEGATEcompleteness spec) — a no-surface code sibling is "parallel to everything" by analyzer design, which is how two devs ran explicitly-sequenced work out of order on divergent branches. Non-code delegations and REST/manual creation are unchanged. The MCPdelegatetool now actually carriesintends_to_touch/adds_migration/touches_shared/depends_onand forwards them to the gateway — the gate demanded a field the tool could not send, so every code delegation was rejectedincomplete_inputwith no way to comply (live fleet-wide delegation wall); a parity test locks plan-gate fields to tool parameters. -
Respawn circuit breaker now guards every task-keyed spawn path. The progress-aware breaker (strike counting with status-advance reset, tracing-gap budget, DB durability, one-shot CEO notification) was consulted by only 3 dispatch paths; doc/QA/dev/PR-review/PR-gate/revision/board spawns ran unguarded at fixed cadence — a documenter with no valid verb respawned 26× in ~100 min on one task. The gate is now consulted at all 14 task-keyed spawn sites. It also catches status ping-pong: any status change used to fully reset the strike counter, so a
blocked↔in_progressoscillation — which changes status on every spawn while advancing nothing — never tripped the gate (8 spawns over two hours, live). A never-seen status still fully resets; a REVISITED status gets a bounded reset budget (pm_respawn_max_revisit_resets, default 2), after which strikes accrue and the gate fires. -
Assembled-PR freshness + integrity at submit_up / submit_root. Freshness: the assembled cell/root branch is auto-rebased onto its base when behind (children are terminal at submit time; master is never written); a rebase conflict is a clean rejection naming the files — ends the needs_revision ↔ awaiting_pr_review ping-pong of re-reviewing a stale head. Integrity: every completed child's commits must be patch-present (
git cherry, rebase-safe) in the assembled branch before review — a completed revert whose merge was lost re-spawned the exact violation it fixed. The guard now also recognizes squash-merged children:git cherrycan't patch-match N child commits against the one squashed commit, but every commit carries the[taskid8]prefix, so a parent commit bearing the child's marker proves the child landed (three squash-merged children read as "work missing" and every legitimatesubmit_upwas refused, live). Markerless children stay flagged — the original incident the guard exists for. -
Review-evidence diffs no longer read a stale local ref. An assembled branch advances on ORIGIN as child PRs squash-merge on GitHub, but the diff-head resolver preferred the inspecting clone's parked local ref — the PR-gate reviewer's evidence diff was built from a pre-merge snapshot and re-flagged work that had already landed (two false
pr_failverdicts on one cell PR, live). When both refs exist and the local ref is strictly behind origin, the diff now resolves toorigin/<branch>; a local ref that is ahead (unpushed) or diverged keeps priority. -
Team-match enforcement armed. The spec-gate team check sat in its permissive fallback since shipping (no caller supplied the agent's team) and three PM verbs opted out entirely — a misrouted frontend cell PM blocked, escalated, and held a backend task through exactly that gap (live). Cell-scoped roles (developer, QA, documenter, cell PM) are now rejected
not_authorizedon cross-team verbs,resume/unblock/activateare team-matched, org-wide roles (Main PM, Board, CEO, PR reviewer) stay exempt so escalation handling and root-PR gating keep working, and the gateway threads the agent's team through all 27 lifecycleContextsites so the gate actually sees it. -
Spawn manifest
workspace_pathfollows the task's project. The manifest hardcoded the agent's roboco-project workspace for every spawn, so an agent working another project's task was told one directory while its shell sat in the task worktree. The manifest now uses the same resolver as the container-w— both surfaces agree by construction. -
Unassigned-QA dispatch no longer pre-claims. The dispatcher claimed the
awaiting_qatask for the QA agent before it existed, moving it toclaimed— but the spawned agent's ownclaim_review/pass_reviewdemandawaiting_qa, so it bounced twice and unclaimed. It now matches the assigned-QA and external-PR-reviewer dispatches: spawn without claiming, the agent claims itself viaclaim_review. -
Documenter revision-pass dead end. A task re-entering
awaiting_documentationwith docs already written left the documenter no move it recognized:i_am_blocked/unclaimare invalid there, and the generic rejection never named the exit. Both rejections now point at the actual exit (i_documentedre-affirming the existing docs) and the documenter prompt gains an explicit revision-pass rule. -
Dispatcher heartbeat. The dispatch loop can die silently (a 4h25m fleet-wide outage left no log line or audit row — its stdout died with the container). A
dispatcher.aliveaudit row every 5 minutes makes a dead loop detectable from the DB and distinguishable from "no work". -
Admin status override now reconciles claim ownership. Forcing a
blockedtask into a review/queue state (needs_revision,awaiting_qa,awaiting_documentation,awaiting_pr_review,awaiting_pm_review) previously left the stale escalation claim in place, so the next claimant was handed the task bygive_me_work/triagewhile itsnote()writes bouncednot_authorized "you do not hold the claim"— it re-blocked immediately. The override now clears the claim (claimed_by/claimed_at/active_claimant_id) and consumes the pre-block snapshot for review-state targets; the pending/in_progress owner-restore path additionally syncsactive_claimant_idso the restored owner's content writes don't bounce either. A REST PATCH that unassigns a task (assigned_to: null) now releases the claim with it.
Removed
- The never-wired dispatch-time spawn-cooldown path.
_safe_spawn/gateway_pre_spawn_check/trigger_filter.decide_spawnhad no caller anywhere in the repo's history — the "enable gateway cooldown logic in production" commit only flipped its flag, and the protections it promised have since shipped better elsewhere: provider parking lives insidespawn_agentitself, claim freshness is enforced by the claim guards + reaper, and runaway respawns are bounded by the progress-aware circuit breaker (all 14 task-keyed sites) plus the notification-spawn cooldown. Wiring it now would have re-introduced a per-task cooldown that queue-stalls every normal stage handoff (dev→QA→doc→PM spawn the same task within one window). Deleted: the orchestrator block,trigger_filter.py, its tests, and the deadspawn_cooldown_seconds/role_spawn_rate_per_minutesettings. Thegateway_triggerstable is kept (inert; dropping it is a migration decision).
[0.15.0] - 2026-07-01
Added
- Metrics granularity — the company is now measurable per member, per task, and as a whole, with the CEO measured as a member too. Every agent spawn session now captures its operational shape, not just its token/cost total: LLM iterations (
turns) and tool invocations (tool_calls) are parsed from the transcript, exposed over the SDK/usage/status+/usage/sync, and persisted onagent_spawn_sessions(migration 055) alongside the existing 4 token dimensions. A new per-task metrics endpoint (GET /dashboard/metrics/task/{id}) decomposes a task's lifetime into active vs. wait effort — merged-overlap active-runtime across every spawn stint mapped onto the audit-log stage windows (the purecompute_stage_efforthelper) — so a slow task can be read as "the work was hard" vs. "it sat in a queue." A nightly rollup tablemember_performance_daily(migration 056) + orchestrator sweeper aggregate each member's day from data already captured: tasks completed, first-pass yield, active-effort throughput/hr, turns & tool-calls per task, revisions caused/received, QA pass-rate, escalations raised, times-this-member-blocked-others, and idle/utilization (idle paired fromagent.idleaudit marks to the next spawn), plus derived ratios. Member / team / org scorecards read the rollup with a live in-flight overlay (today's not-yet-rolled sessions are folded in so the numbers aren't a day stale), served byGET /dashboard/metrics/member/{id},/dashboard/metrics/member/ceo, and/dashboard/metrics/org?team=. The human CEO is a first-class measured member: the CEO scorecard reports approval-decision and unblock latency (p50/p90 from the audit journey) and god-mode override count — reconstructed entirely from the audit log, no new hot-path writes. - A granular completion notification. When a task completes, the CEO now gets a notification carrying the task's metrics breakdown (active vs. wait effort, turns, tool calls, revisions, cost) instead of a bare "done" — the completion signal doubles as a per-task scorecard.
- Panel: a Scorecards tab and a dashboard Performance card. The Metrics page gains a Scorecards tab — an org rollup headline, the CEO-as-member card (approval/unblock dwell + god-mode count), and a per-member table (completed, first-pass yield, active effort, turns/task, QA pass-rate, escalations, blocked-others, utilization) where each row self-fetches its rollup and live in-flight rows carry a "live" badge. The dashboard gains a Performance overview card (org-wide 30-day completed / first-pass-yield / throughput-per-hour / active-effort / cost) that deep-links into the Scorecards tab.
Fixed
ceo_rejectnow leaves an audit trail. The CEO's reject-with-changes and cancel decisions emitted no named audit event, so rework and decision-latency attribution had a hole at the CEO chokepoint. Both now emit the transition audit event at the single_emit_status_transition_auditchokepoint, so the CEO's decisions are reconstructable alongside every other role's.- Stream pending-message recovery no longer fails on every reclaim tick.
StreamEventBus._recover_streamdecoded a Redis message id withstr()on the raw bytes the client returns (nodecode_responses), producing"b'…-0'"— which Redis rejects with "Unrecognized XCLAIM option", so unacknowledged messages from crashed/slow consumers were never reclaimed and leaked in the pending-entries list on every stream (roboco:stream:usageand others), spamming the error log each interval. It now decodes the id via the existing_to_strhelper before XCLAIM.
[0.14.0] - 2026-06-29
Added
- Multi-level MegaTask sequencing — a batch now runs in the right order, structurally, not by luck. A MegaTask that spans several cells (and may mix per-cell projects from different products or OSS libraries) can now be routed per-cell without standing up a Product for it: each root-subtask carries an ad-hoc per-cell project map (
task_cell_projects, migration 052, with a panel per-cell project picker), then cutsfeature/main_pm/{root}and opens a root→master PR per repo exactly like a Product fan-out. On top of that map the dependency graph now carries the sequencing edges that collision and migration ordering need, enforced in the DAG rather than hoped for in the prompt: a dev task declares its collision surface (intends_to_touchglobs,adds_migration,touches_shared, migration 046) ondelegate; file-overlap serializes (more-important first), migration-adders chain serially, and a shared-surface edit runs after each non-shared task it overlaps — independent tasks still run in parallel — with cell-task wave chains and a by-osmosis edge completing the multi-level chain. Two new gate-level verbs close the "agent started out of order / drifted behind base" hole that no amount of prompting fixed:sync_branch(a dev gate verb that rebases the task branch onto its base and force-pushes, through the gate — raw git stays denied), and ani_am_donebehind-base submit gate that structurally refuses to submit a task whose branch has fallen behind its base. Single-task intake is byte-for-byte unchanged.
Fixed
-
Per-task git worktrees — a coordinator PM's multiple in-progress roots no longer clobber each other on one shared checkout (F123). A coordinator PM (Main / Cell) legitimately holds several in-progress roots at once, but its clone is a single checkout — so every fresh claim ran
git reset --hard+checkout -bto the new branch and destroyed uncommitted tracked changes on the still-active first root (a live run showedmain-pmping-ponging two roots on one clone for ~13h). The reset's own comment assumed it was discarding "abandoned cruft from a finished task," but neither root was finished, and the git mutation was non-transactional with the DB claim (rollback restored DB fields, not the working tree). Each task now gets its own working tree viagit worktree addunder{clone_root}/.worktrees/{task-short}/on the same underlying clone, so a PM's roots each have an independent checkout and the F123reset --harddissolves entirely (a fresh worktree is clean by construction). The shared clone keeps the real.gitobject store, the per-project.venv, and.uv-python; each worktree gets a.venv → ../../.venvsymlink souvresolves the shared clone-root venv (no per-worktree re-sync), and.uv-pythonis now gitignored so every worktree inherits it. Branch-by-name git ops (push,pull,fetch,pr_merge,diff) run from the clone root as before; checkout/HEAD-moving ops (create_branch/commit/rebase/checkout) target the worktree. Spawn resolves the worktree fromcurrent_task_idon every spawn (never cached) and-w's the container there; a resume/respawn re-attaches a pruned worktree before launch; claim-rollbackworktree remove --forces on a mid-claim failure so a retry doesn't collide with a stale worktree; terminal cancel removes the worktree (the stale-claim reaper does not — it routes topendingfor a re-claim that reuses it). The destructivereset --hard origin/<head>in rebase recovery is pre-existing semantics, preserved. Invariants untouched: only the CEO merges master (no merge/release path touched),/app/.venv(the image-baked MCP-gateway venv) stays sacred, and the coordinator-PM concurrency exemption is unchanged — only the workspace resolution underneath became per-task. A real-git+uvintegration test proves the clone root stays onmainwhile two task worktrees each hold their own branch, and thatuv runfrom a worktree resolves the clone-root venv through the symlink. -
The worktree switch's two missed cwd-dependent git ops now route to the worktree (F123 followup, both deploy-blockers). The worktree switch wired
create_branch+committo the worktree but left two checkout-dependent ops resolving the clone root, both of which would have broken live. (1)rebase_onto_basedoesgit checkout <head>+git reset --hard origin/<head>in the resolved workspace — but post-worktree the branch is checked out in the linked worktree, so acheckoutin the clone root is refused ("already checked out at ''"), wedging thesync_branchbehind-base recovery and the PM'srebase_pr_for_taskwedged-PR recovery with a fatalGitCommandError.sync_task_branchandrebase_pr_for_tasknow resolve the worktree via_worktree_for_taskand rebase there (thecheckoutbecomes a no-op on the already-checked-out branch). (2)conventions_check_for_taskran the validator with--root <clone root>, and the validator reads(root/rel).read_bytes()— so it analyzed default-branch content, not the dev's worktree changes: newly-added files were absent from the clone root (false pass, the conventions block gate silently disabled) and modified files were validated at stale content. It now resolves the worktree and runs the validator there, soi_am_done/pr_passgate against the real diff. -
Completed/merged tasks now clean up their per-task worktree (F123 followup). Only
cancel()and thecreate_branchrollback removed per-task worktrees, so every completed/merged task leaked its{clone_root}/.worktrees/{task-short}/on disk until the whole agent or project was deleted — accumulating clutter live (a PM doing many roots left N stale working trees). The two terminal→completed paths now remove the assignee's worktree best-effort: cell-PMcomplete(after the leaf PR merges) and CEOceo_approve(after root→master merges). Removal is terminal-only — a dev task bouncesneeds_revisionoff the earlier review states and needs its worktree back, so cleanup fires only atcompleted(post-merge, branch truly done), never atawaiting_qa/awaiting_documentation/awaiting_pm_review/PR-merge. No-op for branchless/umbrella tasks (no worktree was ever cut). Best-effort (check=False, wrapped in try/except), so a removal failure never blocks completion. The stale-claim reaper's "don't remove, reuse on re-claim" rule is unchanged — only the terminal path is new. No merge/release path touched. -
The give_me_work → claim path now enforces the per-dev lane barrier. The lane order check (
has_earlier_incomplete_code_sibling: a code leaf may not start while an earlier same-assignee sibling is still open) lived only on the orchestrator's spawn path andi_am_idle, so a developer who asked for work throughgive_me_work— or claimed a task directly viai_will_work_on— bypassed it and could start a later code leaf before the earlier one's PR merged, cutting a branch from a base that predates the sibling's unmerged changes.give_me_work's pre-assigned path now filters through_pending_not_lane_held(a lane-held leaf is dropped, not offered), and_run_claim_guardsrefuses a direct claim of a lane-held code task (invalid_state, parked back topendingviarelease_dependency_blocked_claim). The predicate is CODE-only so coordinator PMs are naturally inert; the claim guard is fail-closed on a lookup error so a DB hiccup never lets an out-of-order start through.is not Truekeeps both paths inert under partial test mocks. -
Dev-task sequencing now chains undeclared-surface siblings on the same assignee. The collision DAG only wired edges for dev tasks that declared a surface (
intends_to_touch/adds_migration/touches_shared); a PM that delegated two dev tasks to the same developer without declaring surfaces wired no edge, so the later task could start while the earlier one's PR was still unmerged — the out-of-order start that wedged the merge.wire_sibling_collision_dagnow falls back (only when no declared-surface collision edges exist) to chaining each same-(project, assignee)lane by(priority, sequence): same-assignee siblings share a working tree, so the later one waits for the earlier. The lane is same-assignee scoped so cross-dev parallel work is untouched, and the edge lives independency_idsso it survives reassignment. Idempotent + incremental by construction (stable sort,add_dependencydedupes). -
Loop-prone notifications now have a bounded re-fire guard.
TASK_ASSIGNMENT/REVIEW_REQUEST/DOCUMENTATION_REQUEST/BROADCASTcan be re-fired by a coordinator PM every tick while a task sits in a state, flooding inboxes. The existing DB purpose-dedup never fires for these four (ACK_REQUIRED_BY_TYPEmarks themrequires_ack=False, so the dedup is gated off), and the delivery path (_persist_and_deliver) had no dedup at all — so a wedged task re-sent the same signal every cycle, inflating each recipient's unacked set and driving respawn churn. A 60s RedisSET NXwindow per(type, sender, recipient, task)now coalesces the re-fire on both creation chokepoints (NotificationService._create_notificationandNotificationDeliveryService._persist_and_deliver): the first fire acquires (marks) keys for fresh recipients, subsequent fires within the window are suppressed when no recipient was fresh, and the storm converges. Fail-open: Redis unavailable → never suppress (a notification is never dropped over dedup infra). One-shot types (KNOWLEDGE_SHARE/MENTION/A2A_REQUEST) bypass entirely (distinct content per send, no dedup key). -
A whole-codebase logic-gap sweep — 230 deduped regression risks plus the PM/code-task creation guard, every one dispositioned against the live tree. The dominant body of this release. Each item was read against the real code first (four of the prior batch's Highs had been false alarms, so the inventory was never trusted blindly), then TDD-fixed; the dispositions ran 86 FIX, 78 BY-DESIGN (intentional/documented tradeoffs, with the silent-swallow-only cases reclassified to FIX with logging added), 18 REFUTED (the cited code already guards it), and 8 DOCS. The categories: cross-repo PR scoping —
pr_numberandbranch_nameare per-repo but were stored and looked up unscoped, so two tasks on different repos sharing a PR number could merge the wrong repo's PR or skip the org's own in-flight integration PR; every PR-merge and branch-ownership lookup is nowproject_id-scoped, andclose_pull_request/pr_targetmakeproject_idmandatory. Advisory locks closing TOCTOU races — per-agent on claim, per-parent ondelegate, per-task onopen_pr(preventing a milestone double-emit), plus an atomic server-side Redis probe-failure counter and a single-transactionreplace_chunks(delete+insert) closing a reindex race. Audit-row transactionality — status-transition audit rows and the rework counter are written in-session in the caller's transaction (the old fire-and-forget path is gone), so the audit trail can't diverge from the state change. Signal gaps —pr_failnow pushes the reviewer's issues to the owning cell PM (the re-submit loop where a PM respawned intoneeds_revisionblind and re-submitted the same PR is closed), andfail_qaroutes aneeds_revisiondev task back to the dev, never the pool. Asyncio cleanup —OptimalService.close()cancels its startup indexing task before the periodic task and the plugin clear, so it can't write against closed plugins. Conventions standard — the validator now times out and reaps on hang, and the gate fails closed on resolution errors (a broken standard can no longer silently disable the gate). WebSocket — fan-out is non-blocking with finally-disconnect, idle-timeout, and dead-socket reaping on send error. Orchestrator runtime — it drains its fire-and-forget background set on shutdown and stops in lifespan shutdown before closing the DB; the probe-resume loop actually revives parked agents; the grok auth token is refreshed before expiry and parked (not crash-retried) when missing. Release executor — every subprocess (git/make/gh/clone) is deadline-bounded and it fails closed on a git add/commit before push. Dozens more across org-memory (private-leak closures, playbook index/unindex as a post-commit step so the RAG corpus never leads the status transaction), the reaper, the provider-park/overload break, and the live-chat bridges. The single HIGH was the release-mutex TTL race (its own bullet below). The full readjusted mapping — every gap → disposition →file:line→ how it works now — lives indocs/internal/how-it-works-now-2026-06-30.md(gitignored). -
The release mutex is now fenced + heartbeated (the sweep's single HIGH).
ReleaseProposalService.approveguarded the fail-closedReleaseExecutorwith a RedisSET NX EXlock, but the lock held a static value (no fencing token), had no heartbeat, and its TTL (~50 min) was shorter than the worst-case clone+gate+CI+publish run (~90 min). On TTL expiry a second CEO approve re-acquired and_prepare_release_clonerm -rf'd the in-flight shared clone. The lock now carries a uuid4 fencing token; release is a Lua compare-and-del that only fires whenGET == token(a late first-finally cannot delete a usurper's lock); a background heartbeat refreshes the TTL every 60 s while execute owns it. A Redis outage is distinguished from a held lock and both stay fail-closed (redis_unavailablevsalready_in_progress). -
The 2026-06-27 live-run meltdown cluster — root-caused and closed. A run hit several compounding wedges at once, each TDD-fixed and verified green: a
main_pmassigned acode-typed task is a structural impossibility (a coordinator PM does no coding) and is now hard-rejected at the gate;cell_pm_completeresolved a merge by globalpr_numberand merged the wrong repo's PR (closed by the cross-repoproject_idscoping above);submit_rootre-submitted an unchanged PR into an infinitepr_failloop (now hard-gated);fail_qabounced a dev task to the pool instead of back to the dev; anote(scope='handoff')with an empty section crashed the note path and tripped a PM respawn loop; the MegaTask four-layer hierarchy (umbrella → root → cell → dev) hit a depth cap sized for three layers; and the durable respawn counter's persist raced under fire-and-forget (an atomic upsert closes it). -
The CEO, prompter, and secretary can no longer be spawned as agent containers. These are human-only roles (the CEO is the human; the prompter is the on-demand intake interviewer; the secretary is the on-demand chief-of-staff) with no delivery lifecycle, yet a
_dispatch_a2a_workpath that spawned any notification target — plus an_is_agent_active('ceo')that always returned false — could nonetheless launch them and burn a container on a role that has no work to do. A chokepoint inspawn_agentplus a dispatcher skip on human-only assignees closes it at both the spawn and the dispatch layer. -
The PM-respawn loop breaker now survives an orchestrator restart. The circuit breaker that stops RoboCo from respawning the same PM on the same wedged task forever (
_pm_respawn_tracker) lived only in memory, so a deploy/crash/OOM reset a task's strike count to 1 and re-burned the whole threshold — four full agent spawns × container cost — against the still-broken task before the gate fired again. The counter is now write-through-persisted to a newrespawn_trackertable (migration 051) on every mutation and restored at startup, validated against live tasks so a stale counter can't resurrect against a fixed one. Best-effort and inert when empty (a DB hiccup degrades to exactly the prior in-memory behaviour); it can only ever suppress a spawn, never manufacture one. -
The
mypy/ruffquality gate is green again, with notype: ignoresuppressions intests/. A round of pre-existing type errors in the test suite (ORM<row>.idpassed whereuuid.UUIDwas expected, missing annotations,None-attribute accesses) and every remaining# type: ignoreintests/are cleared, somake qualitypasses cleanly and the no-suppression convention holds.
Security
-
Phase 5 — the live-chat bridges now enforce the CEO-signed panel token. The intake (
prompter_live) and secretary (secretary_live) panel-facing endpoints were the only API surface that ran unauthenticated at the route layer — their SSE stream (GET /stream) carried no identity at all (browserEventSourcecannot set headers), and the start / status / messages / stop endpoints took no auth dependency. They now require the existing CEO-signed HMAC panel token (require_panel_token, the HTTP sibling of the WS_require_panel_token): nginx already injectsX-Agent-Tokenon/api/in prod, so the browser never holds the secret and no panel/nginx change was needed; in dev a missing token is allowed but a forged one is still rejected. This closes the last ungated panel-facing surface using the existing scheme verbatim — no new auth, no client changes. -
Agent-token gates and secret-scrubbing hardened across the API. The HMAC agent-token gate is now enforced on the
docontent routes and the WebSocket streams (not just the a2a message routes); the orchestrator signs its ownX-Agent-Tokenon self-API calls; 422 error logs are scrubbed of secrets; the a2a / dashboard / orchestrator routes are gated; and SSE runs one session per query. With the Phase 5 bridge gate above, no panel-facing or inter-agent HTTP surface is now unauthenticated when auth is required.
Changed
-
The local LLM was bumped to
glm-5.2and the Ollama fleet defaults swapped off minimax. The in-house RAG / hybrid-retrieval model and the default fleet model assignment move toglm-5.2:cloud; a stale minimax default that no longer matched the running fleet is cleared. -
Agent-facing RAG docs and generated prompts readjusted to the post-fix behavior. The per-task worktree model (F123), the
/app/.venvis-sacred rule, the PM/code-task invariant, and theMAX_TASK_DEPTH=4MegaTask hierarchy are now documented in the RAG corpus (docs/rag/architecture/workspaces.md,workflows/task-claiming.md,workflows/git-commits.md,workflows/task-planning.md,roles/developer.md) and the generated verb/status tables, so a respawned agent resumes against current guidance instead of the pre-fix model.
[0.13.0] - 2026-06-26
Added
- Gated release manager — RoboCo prepares its own releases, you approve them. Cutting a release was a manual, error-prone checklist (enumerate changes, derive the semver bump, update the CHANGELOG, bump eight version refs, gate, tag, publish). A default-off background loop now runs a fully deterministic readiness sweep — diff since the last tag, conventional-commit classification, the semver bump, version-reference completeness (the "you forgot to bump file X" guard), CHANGELOG completeness, docs drift, migration single-head, and the CI gate state — and, past a threshold with a green gate, opens ONE release proposal held for the CEO. The proposal is held (never dispatched to an agent); you approve or reject-with-changes in the panel, and only on approval does a fail-closed executor write the bumps + CHANGELOG, run
make quality(aborting before any commit on red), commit + push, wait for green CI (aborting before publish on red), then publish the GitHub release. Correctness is code, not agent judgment; the only generative step is the CHANGELOG prose, which you review; it never publishes without you. Default-off (ROBOCO_RELEASE_MANAGER_ENABLED). - Organizational memory loop — agents stop re-learning what the company already knows. Three parts behind one default-off flag (
ROBOCO_ORG_MEMORY_ENABLED). ① At task completion the company distills ONE high-signal lesson (Problem → Approach → Gotcha, ≤120 words) via the local model instead of dumping noisy raw notes, and private journal reflections are kept out of the shared knowledge corpus. ② The keystone: when an agent claims a task, the briefing is auto-injected with the top relevant past lessons and approved playbooks for work like this (role-shaped query, relevance-floored so nothing low-signal is added) — the agent never has to think to ask. ③ A first-class, curated playbook library: delivery agents draft playbooks via a newdraft_playbookgateway verb, the Auditor approves / rejects / archives them (a bounded, deliberate expansion of its surface — curation, not agent comms), and approved playbooks are embedded into a newPLAYBOOKSknowledge index and surfaced in a panel review queue. Adds theplaybookstable (migration 050). Distillation and retrieval run on the local model only and are best-effort — a failure never blocks a completion or a claim.
Fixed
- Pitch auto-provisioning is now idempotent — a re-approval no longer collides. When a pitch's approval partially failed and its DB writes rolled back while the created GitHub repos survived, re-approving it tried to re-create the repos and re-insert the product → project cell mappings, hitting a duplicate-key crash on
(product_id, team)and leaving an orphaned product that could not be cleaned up. Provisioning now reuses an existing Project (by slug) and an existing Product (by slug, refreshing its cell map with delete-before-insert ordering) instead of re-creating them, so a re-approval converges cleanly. First-time provisioning is unchanged. - The
mypy roboco/ tests/quality gate is green again. A batch of test files carried type errors that turned the gate red (SQLAlchemy<row>.idpassed whereuuid.UUIDwas expected, a couple of missing return annotations, an invariant-listargument, and aNone-attribute access). Each is now typed correctly so the full gate passes. (The deeper cause — many ORM columns annotatedMapped[UUID]against SQLAlchemy'sUUIDtype rather thanuuid.UUID— is noted for a separate, dedicated cleanup.) - An external-PR review can no longer record a verdict that contradicts its own summary. The inbound-PR reviewer verb (
post_pr_review) derived both the recorded verdict and the posted GitHub review event solely from itseventargument, which defaults toREQUEST_CHANGES— and, unlike the in-path gate'spr_fail, it never required any findings. So a reviewer that concluded "approve" in the summary but lefteventat its default filed (and posted to the contributor's PR) a blocking "changes requested" with nothing cited. The verb now enforces a verdict↔findings invariant before any record or post:REQUEST_CHANGESmust cite at least one finding (almost always a forgottenevent='APPROVE'), andAPPROVEmay not carry a blocker/major finding — rejected with a clear remediation otherwise.
[0.12.0] - 2026-06-25
Added
-
Dependency-update bot — the company keeps its own dependencies current. A default-off, per-project engine that periodically (weekly by default) checks whether a dependency upgrade would change a project's lockfiles and, if so, opens one "update dependencies" task into that project — which flows through the normal dev → QA → PR-review → CEO-merge pipeline and never auto-merges. Detection is read-only: it runs the project's configured
dep_update_command(e.g.uv lock --upgrade/pnpm update) in a throwaway clone of a read-only copy and checks whether any lockfile path got dirty — the read clone is never mutated and nothing is committed or pushed. Fail-safe: a missing or failing command opens nothing. Bounded and deduped per repo (one open update task per git URL) with per-cycle and rolling caps. A project participates only when itsdep_update_commandis set (panel → project settings). Default-off (ROBOCO_DEP_UPDATE_ENABLED). Addsprojects.dep_update_command/dep_update_paths(migration 049), theWorkspaceService.dry_upgrade_changes_lockfileprobe,DepUpdateEngine, and a dedicated orchestrator loop. -
Multi-repo CI-watch — the company watches every repo it owns, not just its own. Self-heal already watched RoboCo's own CI and opened a fix task when it went red; CI-watch generalizes that to any project the operator opts in. Flip
ci_watch_enabledon a project (panel → project settings) and, on each pass, RoboCo checks that project's latest CI conclusion on its default branch; if it's red it opens one fix task into that project (and notifies that project's cell PM) — which flows through the normal dev → QA → PR-review → CEO-merge pipeline and never auto-merges. It reuses the same hardened per-project CI lookup self-heal uses, so a missing signal is treated as "unknown" (never a false green) and one project's GitHub error never aborts the sweep. Bounded and deduped per repo (a monorepo's several cell-projects share one fix task, keyed on the git URL), with per-cycle and rolling open-task caps. Default-off (ROBOCO_CI_WATCH_ENABLED), and the single-repo self-heal loop is untouched. Addsprojects.ci_watch_enabled/ci_watch_workflow(migration 048) and theMultiProjectCITelemetrySource+CiWatchEngine+ a dedicated orchestrator loop. -
The orchestrator now reclaims dangling Docker images on its own. Every rebuild of an agent image orphans the prior build's layers as an untagged
<none>image; across many deploys these pile up (the operator hit ~80). The background sweeper now runsdocker image prunefor dangling images only — throttled to roughly every six hours — so they don't accumulate. It is deliberately conservative: only dangling images are removed (a tagged image, or one backing a running container, is never dangling), it is best-effort (a failure is logged, never raised), and it can be turned off withROBOCO_IMAGE_PRUNE_ENABLED=false.
Fixed
-
An external PR on a monorepo is no longer reviewed twice. Inbound external/internal PR review de-duplicated per
(project_id, pr, head_sha), but several cell-projects can map to one repo (a monorepo) — and the poll already collapses to a single canonical project per repo, so the dedupe and the poll disagreed once a review task was re-pointed to a sibling project: the next poll, checking the canonical project, no longer saw it and opened a second review of the same PR. The dedupe is now scoped to the repo (git_url) rather than a single project, so the same PR on any project sharing the repo is reviewed once; re-review on a new head commit still works, and genuinely different repos that happen to share a PR number are reviewed independently. -
Completing a task whose PR is already merged no longer loops. A merge request against an already-merged PR returns the same
405from GitHub as a genuine "not mergeable" conflict, so the completion path treated an already-landed PR as a conflict and tried to rebase / close-superseded / escalate it — bouncing the task between blocked and unblocked forever (the case where a prior cycle, a sibling, or the CEO had already merged it). The merge now disambiguates: if the PR reports as merged, the merge is treated as idempotent success and completion proceeds; only a PR that is genuinely unmerged raises the conflict. -
After an orchestrator restart, a still-running agent is no longer double-spawned. The orchestrator's in-memory instance registry is lost on a restart while the agent containers keep running. The stale-claim reaper already had a Docker-liveness fallback for that, but the spawn gate (
_is_agent_active) did not — so right after a restart it saw a live agent as inactive and could launch a second container onto the work the forgotten-but-running one was already doing. Startup now re-adopts surviving containers: it probes each known agent slug's container (the samedocker inspectthe reaper uses) and re-registers a minimal active instance for any that is running, before the dispatcher and reaper loops start. Inert when nothing is running, and best-effort (a probe error just leaves that slot for the reaper's own fallback to cover). -
A resumed agent on a drifted shared clone no longer wedges with
BRANCH_MISMATCH. A dev/documenter/QA clone is shared across that agent's tasks; on a respawn/resume it can sit on a sibling task's branch, or a re-provisioned clone can lack the task branch as a local ref (its commits are only on origin). The fresh-claim path git-resets the clone clean, but resume deliberately short-circuits before it — so the agent's nextcommithit the branch-mismatch guard, failed, and the task wedged in a blocked respawn loop (e.g. the documenter that could never land its doc commit). The guard now recovers instead of only rejecting: it fetches and checks out the task's branch (recreating a missing local ref from origin) and only raises when it genuinely cannot switch — i.e. uncommitted changes block it. It never discards work (checkout, not reset), so a resumed agent's unpushed commits are preserved. -
An integration branch is no longer deleted out from under in-flight work (the "branch gone from origin" zombification). After a PR merged, the post-merge cleanup deleted its head branch unconditionally — so merging a cell→root PR deleted the cell branch while a sibling leaf PR was still targeting it as its base, and the CEO's root→master merge deleted the
feature/main_pm/{root}integration branch. The dependent PRs then had no base, every later git op against the vanished branch failed, and the task zombified (the symptom an earlier fix only made non-fatal). The remote-branch delete chokepoint now first checks whether any open PR still targets the branch as its base — an active integration target — and preserves it if so; it fails safe (on any error it keeps the branch, since cleanup is best-effort but stranding is not). True leaf branches with no open dependents are still cleaned up as before. -
Mypy [unreachable] error in test_pr_gate_records_verdict resolved. A test assigned
t.notes_structured = Nonein the function body, causing mypy to narrow the attribute type toNone. Since the test's helper function took the object asAny, mypy did not reset its narrowing after the call, treatingassert t.notes_structured is not Noneas statically always-False and marking the next line as[unreachable], failing the quality gate. Fixed by introducing_TaskWithNoNotes— a helper class that declaresnotes_structured: dict[str, Any] | None = Nonein__init__— so mypy uses the declared union type rather than a narrowed literal. All tests pass with no suppressions. This pattern is documented in the testing standards for future reference. -
Ruff lint errors from autonomous-maintenance PR (#264) resolved. The Feat/autonomous-maintenance merge introduced 4 ruff lint errors that broke the quality gate: (1) unused
castimport inroboco/api/routes/project.py(F401), (2) unusedcastandUUIDimports inroboco/services/self_heal_engine.py(F401), and (3)Sequenceimport inroboco/services/telemetry/source.pyplaced at module level instead of in TYPE_CHECKING block (TC003). Root cause: the autonomous-maintenance refactoring orphaned these imports (cast was imported but never called; UUID was in TYPE_CHECKING but not referenced; Sequence was used only in annotations and must be in TYPE_CHECKING whenfrom __future__ import annotationsis present for proper runtime safety). Fixed by removing the unused imports and moving Sequence to TYPE_CHECKING. The TC003 pattern is a best practice: all imports used only in type annotations should reside in TYPE_CHECKING to avoid circular imports at runtime and reduce module startup cost. No suppressions added; all quality gates pass (10197 tests, 95.51% coverage).
[0.11.1] - 2026-06-25
Fixed
-
A PM no longer respawn-loops on its own coordination root after it is bounced back for revision. The lifecycle spec lets a cell/main PM re-claim a
needs_revisioncoordination root — so a root rejected bypr_fail/qa_fail/ceo_rejectcan be re-planned and re-delegated viai_will_plan— but the runtime's claim-status map omittedneeds_revisionfor the PM roles. The spec gate allowed the verb while the composedclaim()underneath rejected it, returned nothing, and surfaced as a crypticINVALID_STATE: the PM could neither plan nor idle its own rejected root and respawn-looped (one live run logged ~143 such rejections across 11 PM sessions — the tail of the 2026-06-24/25 firefight). The runtime claim statuses now includeneeds_revisionfor the PM roles, and a parity test locks the runtime map to the lifecycle spec so the two can't drift apart again. -
A finished merge no longer respawn-loops the PM when its target branch has been deleted from origin. When an integration (cell/root) branch is removed from origin — e.g. a sibling cell→root merge that strands a late straggler leaf — the post-merge
_sync_target_branchrangit fetch origin <branch>and raised "couldn't find remote ref". Butpr_mergeonly reaches that sync after the authoritative GitHub merge has already succeeded, so refreshing the local copy of the now-gone target branch is purely cosmetic — yet the raise surfaced as a retryableSERVICE_ERROR, socomplete()re-blocked the task and respawn-looped the PM on an already-landed merge (observed live blocking a cell PM'scomplete()for 5+ cycles). The post-merge sync is now best-effort (it logs and returns instead of raising); the CEO merge path keeps the strict sync, since its target is the always-present default branch. -
A PM no longer re-delegates already-finished work as an empty phantom subtask. A parent's acceptance-criteria coverage is matched by stable criterion id, but a PM may declare
covers_parent_criteriaon a child by either the criterion's id or its full text (both happen in practice), and the coverage matcher only counted id matches. So a completed child that had declared its coverage by text was invisible to the roll-up: the criterion read "uncovered", the gate refused to close the parent, and the PM re-delegated the already-merged work as a brand-new empty subtask (zero commits, no PR) that can never close — looping for hours and burning tokens (observed live: a parent's work completed and merged via one child, then re-delegated two hours later as an empty phantom). Every child ref is now normalized to the criterion id (text → id via the parent's own criteria) before counting, so coverage is recognized however it was declared; an unknown ref still matches nothing, exactly as before. -
An ownership failure now reads as an authorization error instead of a fixable tracing gap. A
PRECONDITION_OWNERSHIPrejection (a non-owner invoking an owner-only verb) was dispatched as a generictracing_gap— which looks like a recoverable missing-artifact precondition, so a superseded agent kept retrying the same verb instead of fetching new work. Preconditions now carry arejection_kind, andPRECONDITION_OWNERSHIPis taggednot_authorized, so an ownership failure surfaces as the clear identity/role boundary it is (and the choreographer and lifecycle spec now agree on the kind across the parity suite). This generalizes, at the spec layer, the samenot_authorizedsteer the reassigned-developeri_am_done/open_prshort-circuit already gives. -
The spawn gate now suppresses respawns for every parked provider, not just Grok. When a provider is parked — a rate-limit 429, a persistent overload, or the Claude session limit — the dispatcher must stop launching new agent containers until it recovers, or it just re-spawns agents every tick straight back into the wall. That guard was Grok-only, so an Anthropic park still let the dispatcher churn. The spawn gate now consults the rate-limit tracker for every provider (failing open if the tracker itself errors), so any provider's park actually quiets dispatch.
-
A Claude session-limit hit is now detected from the agent's transcript, so the park actually fires. Parking the workforce on the Claude "5-hour" session limit (added in 0.11.0) read the session-limit 429 markers from the agent container's
docker logs— but the Claude SDK server writes its runtime output to a log file inside the container, so those markers never reached docker logs and the detector silently missed them, letting the whole fleet crash-respawn back into the limit. The detector now also reads the tail of the newest durable Claude transcript as a fallback, so a session-limit exit parks the provider and the background probe loop auto-revives the agents when the window resets. -
A failed in-path PR-review gate now actually leaves its verdict on the PR. The gate posts its pass/fail review to the assembled PR so the decision is visible where the PM (or CEO) merges — but it could only resolve the PR's repo from the task's
project_id, and a Main-PM coordination root (the only task a root→master PR ever sits on) usually carries just aproduct_id(the cell→repo map) and no project of its own. So the slug resolved to nothing and the post silently no-op'd: a root→master PR could be failed back toneeds_revisionwith no comment on the PR explaining why. The gate — and the external-PR reviewer's read-only diff fetch — now fall through to the product's repo when the task has no direct project, so the verdict reaches the PR. -
A task's PR-reviewer notes no longer show "passed" after the gate failed it.
pr_pass/pr_failonly threaded their notes through the tracing-gate check and posted to GitHub — neither wrote the task's structuredpr_reviewslot. So a task passed once and later failed kept displayingverdict: passed(green card and all) while its real transition waspr_fail→needs_revision. The gate now authors the canonicalpr_reviewnote on every decision —pr_passrecords passed,pr_failrecords failed with the issues — so the panel's PR-Reviewer card always matches the actual outcome (best-effort: a malformed note is skipped, never rolling back the gate decision).
[0.11.0] - 2026-06-24
Added
-
MegaTask — describe several tasks in one intake chat and ship them as one sequenced batch. When the CEO wants several pieces of work at once — even across projects that don't share a codebase (e.g. a SaaS app, its open-source core engine, and a framework adapter) — the intake modal now offers a third scope, MegaTask, beside Single cell and Board-led. You pick the repos it spans; the intake agent reads them all and proposes the whole batch in one hand-off (the new
propose_batchtool), one draft per task, each carrying its own project plus a collision surface (which files it touches, whether it adds a migration, whether it edits a widely-shared component). A deterministic analyzer (SequencingService) turns those surfaces into conflict-free waves — file-overlap and migration-adding tasks are serialized, a shared-surface edit runs after what it overlaps, independent tasks run in parallel — and the Board reviews the batch once. On confirm RoboCo creates a branchless umbrella task (the Main PM's coordination + board-review + CEO-approve unit) over N root-subtasks, each a real coordination root with its own project, branch, and PR, wired with the analyzer's dependencies so the existing dependency-gate dispatches the waves in order. The umbrella assembles no PR of its own, is exempt from the branch gate, and completes only when every root-subtask is terminal (then it escalates to the CEO). On the Board route the root-subtasks are held until the umbrella is approved, then released. Surfaced as a core capability — no feature flag — branded "MegaTask" across the panel, prompts, and docs; internal names stay technical (batch_id,SequencingService). Addstasks.batch_id+ the three collision-surface columns (migration 046),confirm_live_batch+POST /prompter/live/{session}/confirm-batch, multi-project intake spawn (project_ids), thepropose_batchtool on both intake runtimes (Claude SDK driver + grok CLI server), and the panel's MegaTask scope + Review-MegaTask card. -
New Ollama Cloud models in the LLM catalog. Added
kimi-k2.7-code:cloudandnemotron-3-ultra:cloudto the Settings model picker.north-mini-code-1.0is available only as a self-hosted Ollama tag, so it is left out of the cloud catalog and will appear automatically in the self-hosted picker when pulled locally.
Fixed
-
A PM that forgot to journal its decision no longer stalls a finished task forever. Every PM decision-point verb (
unblock,complete,submit_up/submit_root,escalate_up/escalate_to_ceo) required a separatenote(scope='decision')call to be made before the verb — and loaded or weak models reliably forget to chain that prior side-effect, so the verb hit atracing_gap(journal:decisionmissing), the agent retried, and a task whose work was actually done sat stranded in a reject → respawn loop (the dominant remaining completion-path blocker in a 24h run; one live case: a corrected PR that could not be merged because the Main PM'sunblockkept failing the gate). The verb now auto-records its own rationale as thejournal:decisionthe gate needs, before the gate runs — the same write-then-gate pattern already used fori_am_blocked → write_struggleand for theqa_notes/pr_reviewer_notessections — so the gate passes off real, persisted reasoning instead of demanding a redundant bookkeeping call.complete/submit_up/submit_root/escalate_*reuse thenotes/reasonthe PM already passes;unblocknow takes a requiredreason(threaded MCP tool → request schema → routes → choreographer);delegatederives the decision from the subtask's title + description. The gate still runs as defense-in-depth, the auto-record is idempotent within the decision window and best-effort (a journal hiccup falls back to the prior reject, never a crash), and the recorded decision is the PM's real words — so accountability is preserved, not bypassed. -
An empty-diff subtask no longer loops a developer on "open a PR". When overlapping decomposition leaves a leaf branch with zero commits relative to its base (its work was actually delivered by the parent or a sibling),
open_prpushed the branch and GitHub refused the PR with a422 "No commits between …". That was surfaced as a genericinvalid_statewhose remediation said "retry", so the developer re-issuedopen_prover and over (observed 15× on a single task) and never progressed.open_prnow recognizes the empty-diff 422 and returns a terminal hand-off instead: it tells the developer not to retry — the branch has no diff, so the work was delivered by the parent — and to calli_am_blockedso the PM can complete or cancel the redundant leaf. -
A reassigned developer no longer retries
i_am_done/open_prforever. When a task is reassigned out from under a still-running agent (a pool release, reaper unclaim, or escalation redirect), the agent's lateri_am_doneoropen_prfailed the spec's ownership precondition as atracing_gap(owns_taskmissing) — which reads like a fixable precondition, so the superseded agent kept retrying the same verb (41 such rejections in one run). Both verbs now short-circuit a non-owner with the same clearnot_authorized"this task is no longer yours — callgive_me_work()" steer thatresumeandunclaimalready use, so a superseded agent is told plainly to fetch new work or go idle instead of looping. -
A Main PM blocking its own coordination root no longer hands the whole root to the Board (a respawn catch-22). Root cause: the generic escalation chain points
main-pm → product-owner, andi_am_blocked/ escalate REASSIGNS the task to that chain target. The board-advisory guard that refuses such a hand-off only covered descendant cell tasks (it requiredparent_task_id), so a top-level Main-PM coordination root slipped through and the entire root was reassigned to the Product Owner and marked blocked. A Board role has nounblockverb at all (only notify / note / triage / i_am_idle) and the unblock gate is assignee-only, so it could neither resolve the blocker nor hand it off — it just spam-notified the CEO while the blocked-task dispatcher respawned it every tick (one live incident burned an estimated 6400+ tool calls on a single root). Fixed at both layers: the escalation / reassign / revival guard now also refuses a Board owner for amain_pmcoordination task (root or MegaTask root-subtask) and diverts it to the pool for a role-matched (Main-PM) re-claim — the upstream cure — via a single shared_board_cannot_ownpredicate; and, as a defense-in-depth backstop, the orchestrator's blocker dispatcher no longer treats a Board role as a blocker resolver (it returns no resolver, so a mis-owned blocked task is skipped rather than respawned onto a role that physically cannot act). -
A racing state change mid-verb no longer crashes a PM into a respawn loop. The gateway's verb runner guards the initial task/agent against
None, but its composed atomic actions reassign the working task from each step (i_will_planruns claim → set_plan → start). When a concurrent agent transitioned the row between the verb's precondition gate and execution — e.g. a racingi_am_blockedmoved a coordination root fromneeds_revisiontoblocked—claim()found no valid transition and returnedNone, then the next step dereferencedNone.idand crashed with the opaque'NoneType' object has no attribute 'id', surfaced to the agent as a cryptic "verb runner failed" so the PM respawn-looped on the wedged root. The runner now re-checks after each composed action and fails fast with an actionableINVALID_STATEthat tells the agent the row changed under it and to re-fetch and re-issue its verb (the savepoint rolls the partial sequence back). -
A completed task no longer wedges when its branch is missing from a re-provisioned clone. Push-by-name (the fix that decoupled the push from the workspace checkout) still requires the named task branch to exist as a local ref — but a developer's shared clone can be freshly re-provisioned (the per-task workspace-collision recovery re-clones it), leaving the task branch absent locally even though its commits are safely on
originand the clone is parked on a different task's branch.git push origin <branch>then died with the crypticsrc refspec <branch> does not match anyand the task blocked-looped ati_am_done. The push now recovers a missing local ref fromorigin/<branch>first (a clean no-op when the work is already on origin); if the branch exists on neither the clone nor origin the commits are genuinely gone from this clone, so it fails loud with a recoverable "unclaim the task and re-claim it to rebuild the branch, then replay your commits" instruction instead of the raw refspec error. -
The orchestrator's own recovery actions now actually run. Its background dispatcher made internal HTTP calls to its own API without an agent identity, so every self-
PATCHto a task — auto-blocking a task with missing prerequisites, auto-resuming a PM's paused parent, auto-recovering a stale-blocked parent, annotating an SLA breach — was rejected with401 Missing X-Agent-IDand silently dropped. The visible effect was paused/blocked parent tasks staying wedged and their dependent work stranded (with the dispatcher logging a "respawning assignee" loop). Header propagation was inconsistent across the orchestrator's separate HTTP-client call-sites — only the main dispatch loop sent the identity. The system identity is now hoisted into one shared constant and applied to every API-facing dispatcher client (the external provider-recovery probe is intentionally excluded); thesystemrole holds the permission required for the audited status-override path those routes use. -
A developer's completed work no longer silently fails to reach GitHub ("No commits between"). A developer's single git clone is shared across all of their tasks, so by the time a task's PR is opened the clone has usually moved on to a later task's branch. The push at the QA-submission /
open_prboundary, and the PR's head branch, were both taken from the clone's current checkout — so the push was rejected (the workspace was parked on another task's branch) and the locally-committed work never reachedorigin, leaving the task branch empty andopen_prfailing with GitHub's "No commits between" 422. The work was on disk and correct, just never pushed. Both the push and the PR head now operate on the task's recorded branch by name, independent of the checkout (push(branch=…)targets the named ref; the PR head is the task'sbranch_name). Work committed on any of a shared clone's task branches now pushes and opens its PR correctly. -
Hitting the Claude session limit now parks the workforce instead of crash-looping it. When the org's Claude usage ("5-hour") limit is reached, each agent container exits with a 429 rejection; the orchestrator was treating that like any crash and immediately respawning the agent straight back into the limit, over and over, across the whole fleet. It already parks the provider on a persistent server overload (529/500/503) and revives the parked work once it recovers — but that detection only matched the overload signatures, not the session-limit 429. The same park-and-resume break now also recognizes the session limit: the provider is parked, dispatch goes quiet, and the background probe loop brings the agents back automatically when the window resets — no churn, no wasted respawns.
-
A failed PR review no longer looks green. On a task's detail page, the "PR Reviewer Notes" card was painted a fixed teal/green background regardless of the review verdict, so a
Failedreview — red badge and all — sat inside a green card and could read as passing at a glance. The card background now mirrors the verdict the way the QA Notes card already does: red on a failed review, green on approved/passed, amber on changes-requested, and neutral before a verdict is in. -
The CEO and other human roles no longer get spammed with agent "learnings." Whenever an agent recorded a learning, RoboCo broadcast it as a knowledge-share notification — and the recipient query swept in the human roles too (the CEO, plus the human-driven prompter and secretary). Agent knowledge-sharing is a signal for agents; in a human's inbox it is just noise. Those roles are now excluded from learning broadcasts.
-
A gateway verb on a vanished task/agent fails cleanly instead of crashing cryptically. The verb runner's atomic steps dereference
task.id/agent.idwith no guard, so a verb invoked when the task or agent could not be resolved (e.g. a task forced into an unexpected state out-of-band) crashed with an opaque'NoneType' object has no attribute 'id'. The runner now fails fast with an actionableINVALID_STATEerror that tells the agent to re-fetch and re-issue its claim verb. -
An agent could be permanently wedged in a respawn loop by duplicate work sessions on one task. A task is owned by one agent at a time, so it must have at most one active git work session — but nothing enforced that: when a task was re-claimed by a different agent (after a pool release, reaper unclaim, or escalation redirect) the prior holder's active session was left open.
WorkSessionService.get_active_for_taskthen ran a one-row query across the duplicates and raisedMultipleResultsFound; the caught failure surfaced as the cryptic'NoneType' object has no attribute 'id'that crashed the claim/plan/start flow, so the task could never advance — the orchestrator re-spawned its PM every ~30s forever and the task's dependents stayed blocked. (This was the real root cause behind the verb-runnerINVALID_STATEguard above, which only made the crash legible.) Fixed at three layers: the active-session lookups now return the most-recent session instead of raising; claiming a task supersedes any other agent's stale active session (the single-active-per-task invariant); and a partial unique index — migration 047, which first de-duplicates existing rows, keeping the most recent — enforces it at the database level so it can never recur. -
A dev claiming a new task no longer gets stuck on
BRANCH_MISMATCH. Each developer has one persistent clone shared across all their tasks, so a finished or abandoned prior task could leave the clone dirty and sitting on a sibling task's branch. The claim's git work (creating/checking out the new task's branch) runs as a side-effect after the claim's DB transition commits — so when the checkout failed on that dirty tree, the task was already marked assigned while the workspace stayed on the wrong branch, and the dev's next commit was rejected withBRANCH_MISMATCH(stalling, then blocking, the task). The claim now does agit reset --hardto clean the tree before the checkouts. It runs only on a fresh claim (resume short-circuits earlier), so the discarded changes are abandoned cruft from a finished task — never committed work, and never the gitignored.venv. -
The
notetool no longer times out under load. Writing a journal entry / note synchronously waited on RAG indexing, which embeds via Ollama — and Ollama is CPU-bound, so under concurrent load that embed slowed enough to time thenotegateway tool out entirely (despite a "non-blocking" comment on the code). The entry is already persisted before indexing, so indexing is pure best-effort enrichment: it now runs fire-and-forget on the event loop, and the note/journal write returns immediately. -
A feature flag stopped showing its raw internal key. In Settings → Feature Flags, the "Gateway-health recovery" toggle displayed its raw key
gateway_health_enabledas its description (the only flag missing a human blurb). Added the description, and changed the fallback so a future flag without one renders nothing rather than leaking a snake_case key. -
A missing local parent branch no longer blocks every leaf PR merge. When a cell PM completes a leaf task,
_sync_target_branchchecked out the parent/cell branch with a baregit checkout <branch>and no fallback — but the agent's shared clone often only has the leaf's own task branch locally, while the parent branch exists only onorigin. That produced a "git workspace state inconsistent" SERVICE_ERROR that cycled the task back toblockedeach time the PM retried. The merge path now fetches the target branch from origin and creates a tracking branch when the local ref is missing, then pulls and returns the merge commit the same as before.
[0.10.0] - 2026-06-23
Added
- Delivery observability dashboards — cycle-time, bottlenecks, rework rate, and per-agent/per-cell scorecards. A new "Delivery" tab on the Metrics page surfaces how work flows, built on data RoboCo already captures: per-stage cycle time reconstructed from the
audit_logtransition journey, a bottleneck view (which lifecycle stage holds the most cumulative time + how many tasks are parked there now), a rework view (how often work bounces toneeds_revision, by team and by agent, with the rejection attributed to the QA / PR-reviewer who made it, plus the rework's token cost), and fused per-agent / per-cell scorecards. Backed by new read-onlyMetricsServicemethods and/dashboard/metrics/{cycle-time,bottlenecks,rework,scorecard}endpoints. To make rework correct and O(1), each task now carries arevision_countincremented at the single transition chokepoint (migration 045, with a compositeaudit_log(target_id, event_type, timestamp)index for the reconstruction queries), and QA/PR-review bounces emit rejector-attributedtask.qa_fail/task.pr_failaudit events. No feature flag — it reads the always-on metrics surface. - Gateway-health recovery — a broken-but-alive agent is recovered instead of protected forever. The verb-driven heartbeat cannot distinguish a healthy agent quiet during a long edit/test cycle from one whose MCP gateway is broken (e.g. a corrupted
/app/.venvso every gateway tool import raises) while its container stays up — and the reaper's live-skip would shield that broken agent indefinitely. The reaper now probes the gateway out-of-band (docker exec: does the gateway venv import its deps?) and, once it has been broken longer thanROBOCO_GATEWAY_HEALTH_GRACE_SECONDS(so a transient probe miss is tolerated), kills + evicts the container so it falls through to release + respawn; a healthy or inconclusive probe spares it. Gated byROBOCO_GATEWAY_HEALTH_ENABLED(default-on reliability fix, in the panel Feature Flags). Builds on the shipped bash-guard/appblock and reaper Docker-liveness fallback — together the third leg the live incident exposed. - Edit a task's sequence from the task details page. A task's
sequence(its order within siblings — lower runs first) was display-only with no way to change it from the UI. The details page's Dependencies tab now carries an inline sequence editor alongside the parent / dependency editors, andPATCH /tasks/{id}accepts asequencefield (owner or privileged role), so an operator can re-order sibling work directly.
Fixed
- Metrics "hours" fields serialized as JSON strings, crashing the panel.
EXTRACT(epoch …)returnsnumericon PostgreSQL 14+, which asyncpg surfaces as aDecimal— and aDecimalserializes to a quoted JSON string. Every SQL-averaged hours field —avg_cycle_hourson the new Delivery scorecards, plus the pre-existingavg_completion_hours/avg_blocked_hours/longest_blocked_hours— was therefore a string, so the panel'svalue.toFixed(…)threwtoFixed is not a functionand blanked the tab. A single_as_hourscoercion now rounds each to a realfloat, so every hours field is a JSON number. (Token/cost fields were alreadyfloat()-cast and unaffected.) - The Main PM could not advance past its first coordination task — the developer single-task concurrency guards were deadlocking the coordinator. A PM plans and delegates many root tasks in parallel; the real work then runs in the delegated cells, not in the PM's own hands. But the claim-time guards that correctly keep a developer to one task at a time —
already_active(you have another claimed / in-progress task) andpaused(you have a paused task, resume it first — which fires afteri_am_idleauto-pauses the PM's own umbrella) — were applied to the PM as well, so once it held one root it could never plan a second: it thrashed between its claimed roots and respawned every few minutes, burning tokens for zero progress. These two guards are now skipped for the coordinator PM roles (main_pm/cell_pm): a PM may hold any number of roots in parallel, gated only by a genuine upstream sequence dependency (unmet_dependency), which still parks the task topendinguntil its dependency reaches a terminal state. As defense-in-depth thepausedguard now also excludes the target task itself, so a PM re-entering its own paused umbrella can never self-block. - Task notes were invisible in the panel — the API response dropped them. The
task_to_responseserializer (used by the task list and detail endpoints the panel reads) setdev_notes/qa_notes/quick_contextbut omittedpr_reviewer_notes,doc_notes, andnotes_structured, andTaskResponsedidn't even declarenotes_structured— so the PR-reviewer's notes, the documenter's notes, and the structured PR-review verdict were always blank in the UI no matter what the agents wrote to the DB (the structured-content write-path and obligation gates work; the data simply wasn't being serialized). The builder now returns all note sections plus the structured source of truth. (dev_notes/qa_noteson an in-flight task are still legitimately empty until the developer submits / QA reviews.)
[0.9.0] - 2026-06-23
Added
- Architectural Conventions Standard — a per-project, repo-canonical architecture map that gates where code may live. Beyond the
make-style checks (syntax, types, tests), each project can carry a.roboco/conventions.ymldeclaring which definition kinds belong in which modules, a toggleable rule set, custom regex rules, and waivers — so an agent can no longer land a Pydantic model inside a router or a lint suppression (a misplaced helper — any top-level function — warns rather than blocks). A tree-sitter validator CLI (Python + TypeScript) classifies every changed definition and emits findings; ablock-level finding refuses a developer'si_am_doneand the in-path PR gate'spr_passwith the offendingfile:lineand a fix hint, and findings surface in QA's review evidence. The auto-derived defaults exclude test and documentation trees, count an explicitdb.commit()in a route as legitimate (not a fat-route violation), and exempt a small allowlist of structurally-unavoidable framework suppressions (ruffTC001–TC003, pydanticprop-decorator). The committed file and repo scan are read from a dedicated project-level read clone the service ensures on demand — so the standard resolves even for a project created before it existed, with no manual workspace configuration. The file is auto-scaffolded on first clone, editable from a per-project Conventions tab in the panel, and a false positive is cleared by a waiver committed in the branch and reviewed in the PR. Gated byROBOCO_CONVENTIONS_ENABLED(default off) and fully inert when off. - Agent runtime toolchain matching — agents build each target project under the Python that project actually requires. The agent image bakes one interpreter, but the projects RoboCo builds don't all share it, so a self-gate could pass against the wrong runtime. The workspace now resolves each target's Python from its
requires-python/.python-version, provisions the clone withuv sync --extra dev --python <version>(fetching the interpreter on demand), and records a.git/.roboco-toolchainmarker. A guard refuses a developer'si_am_done, QA'spass_review, and the PR gate'spr_passwhen the suite cannot be collected under the provisioned interpreter, so "verifying by reading source" can't masquerade as a passing gate. Gated byROBOCO_TOOLCHAIN_MATCH_ENABLED(default off). - Provider overload circuit-break — a persistent model-API overload parks the provider instead of crash-retrying into it. A sustained 529/500/503 (the SDK already retries transient ones) now trips the same park-and-probe break as a rate limit: the spawn gate queues further work for that provider and a background loop revives it when the overload lifts, instead of respawning the agent straight back into the failure and burning tokens. Gated by
ROBOCO_OVERLOAD_BREAK_ENABLED(default on). - Structured content standard with obligated note sections. Every agent-authored handoff (developer, QA, documenter, PR-reviewer, auditor, PM resumption) is now a validated structured model persisted as the source of truth, with the legacy text column derived from it through a single chokepoint. An anti-soup guard rejects filler and all-token-noise free-text across the flow and content verbs, structured PR-review findings render a generated GitHub comment, and each role's note section is obligated at its lifecycle transition the way journals already were.
- User-facing documentation site. A MkDocs Material site (source under
docs/) is now built and deployed to GitHub Pages, publishing the organizational blueprint, role descriptions, task lifecycle, and how-to guides at the project's github.io site; the agent-facing RAG corpus underdocs/rag/stays excluded from the published site. Documenter output is also committed into the project repository (not only the RAG knowledge store) so it ships through the open PR.
Changed
- RoboCo adopts its own architectural standard. The repo now ships a canonical
.roboco/conventions.yml, and the inline request/response models that lived in thesystemand*_liveroute modules were relocated toroboco/api/schemas/so the codebase passes its own placement gate (no_models_in_routes/modular_cohesionare now clean and enforced atblock). - RoboCo's own
requires-pythonfloor is raised to>=3.13. The codebase importstomllib(3.11+) and runs on 3.13; the previous>=3.10floor made the toolchain resolver provision the self-hosted build at 3.10, where the suite cannot even be collected. Agent gate containers now also receive the test-database connection, so an agent'smake qualityruns the real, DB-backed suite instead of a coverage-collapsing unit-only subset.
Fixed
- Documentation now actually lands in the project repo. A documenter's output reached a host-mounted, RAG-indexed knowledge store and (more recently) was committed onto the task branch — but in the documenter's own workspace clone, and nothing ever pushed that commit, so the PM merged the already-open PR without the docs and the deliverable vanished on merge. The documenter's
i_documentednow pushes the task branch before handing off (mirroring the developer's pre-QA push), so the doc commit rides the open PR into the repository; a push failure holds the task inawaiting_documentationfor a retry instead of silently dropping the docs. - The conventions standard now resolves for projects created before it existed. It previously read the committed
.roboco/conventions.ymland the repo scan fromproject.workspace_path— a field only a manual API call ever set — so an older project (or one whose workspace was cleared) showed an empty "missing" map no matter what was pushed. The service now ensures a dedicated, default-branch read clone on demand and reads from it, persisting the resolved path + HEAD (the backfill). The panel tab, the spawn-time ambient block, and the per-task constraints all resolve the committed standard with no manual setup. - The conventions ambient prompt block no longer truncates mid-line. It now lists only modules that actually constrain a kind, and when the list would exceed its budget it trims at a line boundary with a
+N morepointer instead of cutting a module in half. - The conventions read clone now stays current on a private repo. Its refresh reused the orchestrator's token-less best-effort fetch, but the clone's remote URL is credential-stripped — so on a private repo the refresh fetch failed silently and the clone stayed frozen at clone-time, never seeing commits merged afterwards (the panel showed "auto-derived defaults" even after the standard was merged to the default branch). The refresh now performs a token-authenticated fetch + hard-reset, mirroring the clone.
- Self-heal fix tasks dispatch autonomously instead of being stranded. A self-heal task was opened
confirmed_by_human=falseand held out of dispatch until an "Approve & Start" — but that button only renders for board-reviewed Intake tasks, never for a self-heal task (team=main_pm, no board review), so there was no way to start it and it sat inpendingforever. Self-heal now opens the fix task confirmed + assigned to the Main PM, so the dispatcher picks it up immediately. The fix still ships through the normal gates (dev → QA → PR review → the CEO's merge); the loop never starts, merges, or deploys. - Self-heal no longer reads the wrong branch and fails silently. The CI-signal fetch filtered runs by
project.default_branch or "main"— the only"main"fallback in the codebase (everywhere else falls back to"master") — so a project whose default branch ismaster(like RoboCo) with an unsetdefault_branchmatched zero runs and the signal silently went dark: no fix task, no notification. The fallback now matches the rest of the codebase, and an armed self-heal that reads no CI signal (no/expired token, wrong branch, or a GitHub error) now logs a loud warning instead of an invisible no-op. - The toolchain gate no longer passes silently on an unverifiable workspace. A
brokeninterpreter still blocks; anunknownstatus — the smoke could not confirm the suite is collectable — now emits a warning when the gate proceeds, instead of slipping through unseen. - The crypto tests are hermetic. The Fernet round-trip tests supply their own key instead of depending on
ROBOCO_ENCRYPTION_KEYin the environment, so they pass in any gate container without the production secret being injected. ollama-initis best-effort and gates startup on the models being present, so a slow or unreachable model registry can no longer down a fully-cached deployment.- A PM can recover its own coordination task from
needs_revision, and lifecycle-transition notes are kept off the human-facingquick_context/dev_notescolumns. - Panel: a copyable task-id chip with a stable, non-shifting task header, clickable Branch / PR links with a branch-copy button, and clearer agent status badges.
- Panel: the per-project Conventions editor lays out in a responsive two-column grid (Module boundaries | Rules, then Waivers | Custom rules) with Recent violations full-width, inside a wider modal on large viewports — instead of one long single column. Each row's two cards share an equal height, and the Module-boundaries list scrolls internally so it matches the Rules card instead of running long. It collapses to a single column on mobile and is capped so it stays sane up to a 27" display.
[0.8.0] - 2026-06-20
Added
- In-path PR-review gate — every assembled PR is reviewed before the PM merges. A new
awaiting_pr_reviewstatus sits between the work and the PM merge: the cell PM'ssubmit_upopens the cell→root PR and the Main PM'ssubmit_rootopens the root→master PR, and each entersawaiting_pr_review, where a reviewerpr_passes it on to PM review orpr_fails it back for revision — the merge-level reject the PM previously lacked (motivated by a front-end/back-end seam bug that slipped straight through to master). Three new team-scoped cell PR-reviewers (backend, frontend, UX/UI) join the existing main reviewer, taking the company to 25 agents, each with its own first-class image and spawn manifest; leaf developer tasks and branchless coordination roots skip the gate. Ships migration 040 (theawaiting_pr_reviewenum value) plus the panel surfacing: a legible PR-review status badge, a dedicated "PR Review" kanban tab, and a PR-review column on the management board. - Panel test gate. The Next.js panel gains a baseline vitest suite over its lib and stores, a
pnpm teststep enforced in the CI panel job, and amake panel-gatetarget, so panel changes are quality-gated the way the Python side already is.
Changed
get_team_metricsreuses the sharedACTIVE_STATUSESconstant instead of re-listing the active task statuses inline, keeping the definition in one place.
Fixed
- The self-healing CI signal is now deterministic. The regression watch defaulted to the latest completed run across all of the repo's workflows, so on a multi-workflow repo an unrelated green run — or a green run on an older commit — could mask a red CI run and the loop fired only intermittently. The signal is now scoped to the
ci.ymlworkflow by default, pulls a window of recent completed runs and resolves the conclusion against the branch's current HEAD (a green re-run supersedes the failure; a stale green run can't hide it), and retries transient GitHub errors instead of reading one network blip as all-green. - Self-heal fix tasks are assigned to the Main PM agent, not just the
main_pmteam. A team-only task fell to slow unassigned-team routing after the CEO approved it; it is now assigned to the Main PM agent up front so the orchestrator dispatches it straight away once approved. The confirmed-by-human hold that keeps the task inert until CEO approval is unchanged.
Security
- bash-guard denies git verbs hidden in command substitutions — a
$(...)- or backtick-wrapped git command could previously slip past the guard. - Transcript retention matches the encoded workspaces root at a path boundary, so a sibling directory sharing a name prefix is no longer mistaken for the workspaces root during pruning.
- The v1 role guard binds to a verified agent token before trusting a role claim, so the role a request asserts is checked against its signed token rather than taken at face value.
- pydantic-settings upgraded to 2.14.2 to pull in the fix for GHSA-4xgf-cpjx-pc3j.
[0.7.0] - 2026-06-19
Added
- Grok agents on xAI's official
grokCLI, on a SuperGrok subscription. A newroboco/llm/providers/seam (anAgentProviderlifecycle ABC + aProviderRegistrykeyed byModelProvider) lets the orchestrator drive agent backends other than Claude Code, and the first is Grok — running xAI's officialgrokCLI authenticated by a SuperGrok subscription rather than a metered API key, so a Grok workforce can't stall mid-task on out-of-credits. It reaches parity with the Claude path by construction: the same MCP gateway + tool-manifest wiring, per-role tool removal and git-operation deny rules, a prompt-injection guard on the task prompt, headless tool auto-approval, and per-agent token/cost capture from the grok session store. It covers both one-shot delivery roles and the interactive Intake (Prompter) and Secretary chats (per-turngrok -pwith session resume, streamed turn-by-turn). The change is purely additive — onlyGROKroutes through the registry; Anthropic / Ollama Cloud / self-hosted spawns are untouched — and ships migration 038 (thegrokenum) + 039 (the seeded provider row), first-classroboco-agent-grok/-prompter/-secretaryimages wired into all three compose files and the release workflow, and a Settings provider card. - SuperGrok token auto-refresh. The grok access token has a fixed ~6h server-set TTL and the CLI cannot refresh it headlessly — on an expired token it hangs forever at an interactive login prompt — so the orchestrator now mints a fresh token from the offline-access refresh token (xAI's OIDC
refresh_tokengrant) before expiry and rewrites the sharedauth.jsonin place, keeping every Grok agent's credential live with no recurring manualgrok login. As a backstop the agent entrypoint refuses to start (exit 78) on a missing or expired token instead of hanging. - Self-healing CI loop (default-off). RoboCo can now watch its own repository's CI and, on a detected regression, open a fix task that is held out of dispatch until the CEO approves it — then dispatch it through the normal delivery flow, so the company repairs its own breakages. It is dormant by default and armed from two Feature-Flags panel toggles; the CI signal is scoped to a single named workflow, and task origination is bounded by rolling and per-cycle caps so it can't flood the backlog.
- Company Scorecard. A company scorecard on the panel's Business Goals tab.
Fixed
- The PR-reviewer is no longer wedge-killed before it can post a review.
pr_review_claimnow seeds the claim heartbeat like every other claim path; without it a Grok reviewer was treated as a silent (NULL-heartbeat) wedged container and killed before it could callpost_pr_review, churning the task back to pending in a respawn loop. - Grok one-shot runs are observable, and their usage is captured. The entrypoint streams agent activity to the container log live (
--output-format streaming-json) instead of buffering it to a file until the run ends, and per-agent token/cost is read from the grok session store's actual cumulative-total field (it was silently reading$0). - Path-injection hardening of the Grok usage directory. The agent id is validated and reduced to a single safe path component before it is used to build the per-agent usage path, on both the write/mount and finalize-read sides.
[0.6.0] - 2026-06-17
Added
- Inbound PR review — the org reviews, and can take over, pull requests it didn't open. A new read-only
pr_reviewerrole (a 22nd agent, its own first-class image and spawn manifest, migration 037) discovers inbound PRs, reviews the diff adversarially, and posts a single complete change-request as a real GitHub review on the PR itself — no agent-to-agent chatter. It covers external / fork PRs, gated by a configurable author allowlist, and — behind a second flag — internal org-repo PRs opened outside the agent task-flow (the org's own in-flight integration PRs are skipped, since a live task already owns their branch and they pass QA + PM review). Re-review is driven by the PR's head commit (an unchanged PR is skipped, new commits open a fresh review), and polling is repo-aware so a monorepo is no longer reviewed several times over. External-PR review is enabled by default in the shipped compose (with human-confirm on); internal-PR review is off by default. Both are flippable from the panel. - CEO decision queue + supersede for reviewed PRs. Completed reviews surface in a PR-review queue in the panel — in-flight reviews are shown too, linking to the PR, so it never goes dark. From there the CEO can dismiss a review, or supersede the PR: the system cuts a roboco-owned branch off the contributor's commits and opens a Main-PM coordination task to finish and harden the work to our standards on that branch, open our own PR, and — once that replacement actually merges — close and link the contributor's PR. We never push to a contributor's fork.
- Feature-flags panel. A Settings → Feature Flags card toggles env-gated subsystems (external / internal PR review, web research, the strategy engine, pitch provisioning, RAG auto-update, transcript pruning) from the panel instead of hand-editing environment variables. A toggle persists in the existing settings store and takes effect on the next backend restart; an unset flag falls back to its environment / config default, and secrets (API keys, tokens) are never surfaced to the client.
- Required-cells decomposition gate. When a coordination task names the cells that must deliver it, the Main PM can no longer go idle having silently dropped one —
i_am_idleis rejected until every named cell has a subtask, and the Main-PM prompt now insists on honoring explicitly-named cells rather than quietly dropping them. Inert until the marker is set, so existing flows are unaffected.
Changed
- Run from pre-built registry images. A standalone registry compose runs the full stack — including every per-agent image, now extended to the Secretary and the PR-reviewer — from published images rather than a local build. Both deploy paths, the registry knobs, and measured idle / under-load resource usage are documented.
- Documentation, for humans and agents. The how-to guide is now a structured, multi-chapter walkthrough under
docs/how-to/with a new business-workflow chapter (charter → Cockpit → Secretary, and the research / strategy / PR-review toggles); the published reference docs (README, usage, deployment, CLAUDE) were refreshed against the current code, with a CI guard that keeps documentation prose single-line. Agents also get richer in-context guidance: new RAG role docs for the Prompter, Secretary, and PR-reviewer, the 0.4.0 company layer (goals / research / strategy / provisioning) documented for them, and a refreshed guardrails surface.
Fixed
- The PR-reviewer no longer respawns in a loop. Without a spawn manifest the reviewer had no flow verbs, so it could never claim its review and was respawned over and over (burning tokens); it now ships a role-scoped manifest and reliably claims its work. The supersede close-on-land path was also hardened — the contributor PR is retired only once our replacement PR has actually merged, not merely when the umbrella task completed.
- A CEO-rejected coordination root no longer deadlocks. A product-linked coordination task the CEO sends back to needs-revision is re-dispatched to its owning PM (and the readiness gate now accepts a PM on a coordination root in that state), instead of sitting unowned forever because the developer dispatcher skipped it.
- Panel UI standardization + usability pass. A panel-wide pass plus targeted fixes: the Settings grid layout, the Journals and Kanban scroll regions, the agent-list item, the Projects table, kanban cards whose text overflowed, the PR-review queue's empty state, and the Secretary chat composer buttons.
Internal
- DB-backed and httpx-mocked test coverage for the inbound-PR read and lifecycle paths (ingest / dedup / classify / claim / complete / supersede); a cyclomatic-complexity refactor of the git PR-creation and lifecycle-validation code to clear the xenon gate; and the cell-PM / main-PM role docs corrected to the real
delegatesignature and cross-linked to each other.
[0.5.0] - 2026-06-16
Added
- Acceptance-criteria & decomposition guardrails. Every task's acceptance criteria now carry stable per-criterion ids, and each decomposed subtask records which parent criteria it is responsible for (
covers_parent_criteria). Two gates build on that linkage: a PM can no longer go idle leaving a parent criterion with no subtask responsible for it (the decomposition floor), and a parent can no longer complete / submit up / escalate to the CEO unless every one of its criteria traces to a child that passed QA on it (the roll-up gate). PMs see live coverage in their briefings (parent_ac_coverage,unclaimed_parent_acs) after eachdelegate. Safe-by-construction: every gate stays inert until a PM starts declaring coverage, so existing decompositions are never blocked. (Migration 036.) - Per-dev sequenced code queues. A cell PM now delegates each developer its full queue of code subtasks up front instead of one task at a time. Both cell developers build in parallel, and each works its own queue one task at a time, in order — enforced by a per-lane dispatch barrier, with leaf PRs still merged in sequence into the shared cell branch. The old "two code subtasks per parent" ceiling is removed; the 12-subtask hard cap and a same-title duplicate guard remain.
- Unified Business page. The Company Goals, Secretary, and Pitches pages are consolidated into one tabbed Business page (Goals / Secretary / Pitches), modeled on the Knowledge Base page with deep-linkable
?tab=URLs. A single sidebar entry replaces four.
Changed
- Company Goals, Secretary, and Pitches brought to the panel's standards. Skeleton loading and offline/error states, structured fields instead of raw JSON dumps, required-note confirmation dialogs for pitch and directive decisions, and markdown rendering in the Secretary chat.
Removed
- The standalone Cockpit page. Its data duplicated the Dashboard and Metrics; its one unique element — the strategy-engine "needs your attention" signals — was relocated to the Dashboard, served by a new lightweight
GET /api/cockpit/signalsendpoint. The/cockpit,/company-goals,/secretary, and/pitchespanel routes are all retired (404); the Goals, Secretary, and Pitches views now live under/business?tab=….
Fixed
- Agent MCP/SDK servers no longer stall on spawn. They launch with
uv run --no-sync, so a workspace clone whose lockfile has drifted from the baked image no longer triggers a multi-minute dependency re-sync that left the gateway tools stuck "pending" and the developer respawning in a loop. open_prno longer fails on a missing base branch.create_prauto-creates and pushes the PR's base branch off the default branch when it is not yet on the remote, instead of returning a GitHub 422.- Admin status overrides restore task ownership. Forcing a blocked task back to pending / in_progress now restores its pre-block assignee, so an escalated code task no longer re-enters the pool still owned by a PM and is dispatched to that PM as if it were a developer.
- A developer can idle past its own queued work. With per-dev queues, a dev whose current leaf has moved to QA now idles cleanly while its later queue items wait their turn (the orchestrator respawns it when the lane clears), instead of looping on the idle guard or claiming the next leaf out of order.
- 26 verified panel UI bugs across the dashboard, kanban, task detail, and API layer: consistent priority labels and badge sizing, dark-mode coverage, kanban drag-and-drop that prompts for the required audit note, auto-scroll in the message and mentor-chat views, corrected WebSocket reconnect counting,
PATCH(notPUT) for partial task updates, working "Activate Task" and "Start Revision" actions for backlog and needs-revision tasks (no more dead-end menus), the previously-dead "New / Generate Report" buttons, a duplicate agent id, a "0h ago" timestamp, and more.
Internal
- Verb-table generation no longer emits tables for the driver-based roles (prompter, secretary), whose real tools live in their SDK drivers rather than the gateway verb surface; and the
_briefing_fortyped stub was aligned with its implementation so the composed choreographer type-checks under full mypy.
[0.4.0] - 2026-06-15
Added
- Business Goals — the company charter. A single CEO-owned charter (north star, prioritized objectives, constraints, operating policy) injected compactly into every agent's briefing so all work is goal-aware.
GET /api/company-goals(any agent) /PUT(CEO-only), with a panel editor. - Web research for the Board and PMs. Pluggable
web_search/web_fetchexposed through aroboco-searchMCP server backed by/api/research/*, with Tavily / Brave / Exa adapters and a graceful no-op when no provider is configured. The provider key stays server-side — agent containers never make the external request themselves — and a per-agent daily quota (Redis, fail-open) bounds cost. - Pitch → approve → provision. The Board proposes a product (a "pitch"); on CEO approval the system provisions a GitHub repo per target cell, registers a Project for each (and a Product when multi-cell), and seeds one Main-PM delivery task — reusing the existing Product / coordination-task machinery. Default-off: with no provisioning token configured, approval is refused and nothing is created.
- Autonomous strategy engine (dormant). An optional second engine that watches the company against its standing goals and surfaces drift, idle, and long-stranded blocked work to the CEO (notify-only — it never spends, builds, or auto-approves). Off by default; the delivery lifecycle is unchanged.
- The Secretary — the CEO's chief-of-staff. A live conversational agent (its own role, distinct from the Prompter) the CEO chats with in the panel. It acts only under the CEO's command: it reads company state and relays dictated messages directly, but high-impact actions — editing the charter, starting / cancelling / overriding tasks, approving a pitch, announcements — are queued and run only after the CEO's explicit confirmation (the gate list). Its authority is HMAC-scoped to the secretary role and routed through the existing enforcement, never a parallel permission model.
- The Cockpit. A read-only
/cockpitview answering "is the business winning, what's happening, what needs me" — the charter, delivery counts, 30-day spend vs the budget cap, pending pitches, and the strategy engine's signals. Honestly stampedbasis: proxy(a proxy until real launches).
All of these are additive and opt-in or default-off — an unconfigured deployment behaves exactly as before.
[0.3.0] - 2026-06-15
Added
- In-house RAG engine. Replaced the piragi/torch retrieval stack with an in-house pgvector engine (asyncpg), then added hybrid retrieval — pgvector cosine fused with Postgres full-text ranking — retiring HyDE, plus an embed-once / concurrent-search pass that cut multi-index query latency.
- Self-hosted LLM provider with dynamic model discovery, so agents can run against a local or self-hosted model endpoint.
- Quality gates at the source. Developers run a fast quality gate at
i_am_doneand the full fast gate (including complexity) at their desk; QA requires a per-acceptance-criterion verdict before passing; cells run two developers in parallel with split-before-claim sizing. - Board redraft loop — the Board can send a drafted task back to intake for an in-context re-draft before it starts.
- Transcript retention — a background sweep prunes old agent transcripts, with a panel-tunable retention window.
tests/type-gated under mypy — the whole test suite now type-checks in CI.
Fixed
- PR-divergence respawn-loop meltdown. Capped the PM respawn loop-gate, added CEO god-mode status override, a PR-conflict auto-resolver (rebase → close-superseded / re-merge / escalate), and sequence-ordered sibling merge; the dispatcher can now claim an ownerless
awaiting_pm_reviewtask without transitioning it. - Git robustness. Fall back to a permitted merge method when the repo refuses the requested one, and retarget a PR's base to the default branch when the resolved base is missing on the remote.
- RAG outage. Migrated the live
chunks_*tables to the in-house schema (offline-renderable migration), closed engine audit gaps, decoded jsonb metadata returned as a string by asyncpg, and kept the embedding model resident to stop ingest timeouts. - Panel. Fixed task lifecycle (updates, merge, reassignment, copy), responsive grids + mobile overflow, the status dropdown duplicating the current status, the orchestrator-status reachability signal, and surfaced the CEO "Approve & Start" gate so it can't be missed.
- Usage attribution. Agent transcripts are attributed by an orchestrator-assigned session id, fixing zeroed token/cost capture for review-role agents.
- Composed the prompter role layer for the intake agent; aligned auditor channel permissions; made the app route-registration test robust to FastAPI 0.137; cleared an xenon complexity failure and fixable test warnings.
Security
- Documented that WebSocket authentication is REST-only and
/ws/systemis unauthenticated.
[0.2.0] - 2026-06-11
Added
- Provider rate-limit handling. End-to-end backpressure for LLM-provider 429s: a Redis-backed
RateLimitStateTracker, a spawn gate that queues (never drops) work while a provider is rate-limited, agent parking viai_am_blocked(reason="rate_limited"), and a background probe-and-resume loop that auto-revives parked agents when the limit lifts — escalating to the CEO after repeated failed probes. Surfaced live in the panel via a rate-limit banner. - Token usage & cost analytics. Per-agent-session token capture read from the Claude Code transcript (
/usage/sync), persisted to spawn-session rows and daily rollups, with provider-aware pricing (Anthropic models priced; local/Ollama models intentionally $0). Visible on the usage dashboard. /ws/systemoperator WebSocket stream with awebsocket_bridgethat forwards system events from the event bus to panel clients in real time — the rate-limit lifecycle and live token/cost usage (USAGE_UPDATE/USAGE_SNAPSHOT), so the dashboard's "Token Usage & Cost" panel updates over the socket and falls back to HTTP polling when it drops.
Fixed
- Agent workspaces now install the project's
devextra (uv sync --extra dev) so spawned agents have the fullmake qualitytoolchain (ruff/mypy/xenon) and can gate their own work — closing the gap that let lint/type/complexity debt merge unchecked. - Token-usage capture: the dashboard previously recorded zeros because nothing populated the per-session counters.
- Panel rate-limit endpoint shape (
/api/system/rate-limitsreturns the{ entries: [...] }envelope the dashboard expects) and the doubled/ws/ws/systemWebSocket path. - Control-panel logo and all
/publicassets returning 500 — the panel image copied them without chowning to the non-root runtime user. - Provider-aware pricing (Opus corrected to $5/$25 per 1M; non-Anthropic models no longer warn or mis-price).
[0.1.0] - 2026-06-09
Added
- Initial public release of RoboCo — an open-source AI agent "company": a virtual organization of 20 AI agents and 1 human CEO that plans, builds, reviews, documents, and ships software.
- Organizational hierarchy: on-demand Intake, Board (Product Owner, Head of Marketing, Auditor), Main PM, and Backend / Frontend / UX-UI cells.
- Task Assistant (the intake Prompter): a live, codebase-aware chat that interviews the CEO and drafts a well-formed, board-ready task — objective, per-cell breakdown, and acceptance criteria — then launches it into the lifecycle (Board review, or straight to the Main PM).
- Agent gateway (
roboco-flow,roboco-do) backed by the server-side Choreographer; intent-verb tool surface per role. - Task lifecycle state machine with role-based transitions and git workflow (PR-before-QA, CEO approval for major work).
- A2A protocol, journals, channels/notifications, kanban, and RAG (piragi + pgvector) knowledge base.
- Next.js control panel (
panel/) behind a single nginx entry point. - Multi-agent workspace management with per-project encrypted git tokens.
[0.5.0]: https://github.com/rennf93/roboco/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/rennf93/roboco/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/rennf93/roboco/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/rennf93/roboco/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/rennf93/roboco/releases/tag/v0.1.0