Commit Graph
366 Commits
Author SHA1 Message Date
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
38db49c5ea chore(deps): bump docker/login-action from 3 to 4 (#66)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 03:35:10 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bdf4b3a96e chore(deps): bump actions/first-interaction from 1 to 3 (#65)
Bumps [actions/first-interaction](https://github.com/actions/first-interaction) from 1 to 3.
- [Release notes](https://github.com/actions/first-interaction/releases)
- [Commits](https://github.com/actions/first-interaction/compare/v1...v3)

---
updated-dependencies:
- dependency-name: actions/first-interaction
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 03:34:55 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5c1da3e36c chore(deps): bump docker/build-push-action from 6 to 7 (#51)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 17:00:40 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
208b955caa chore(deps): bump github/codeql-action from 3 to 4 (#50)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 17:00:21 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
74f4642cc4 chore(deps): bump actions/labeler from 5 to 6 (#49)
Bumps [actions/labeler](https://github.com/actions/labeler) from 5 to 6.
- [Release notes](https://github.com/actions/labeler/releases)
- [Commits](https://github.com/actions/labeler/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/labeler
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 17:00:02 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9c3d87d2b1 chore(deps): bump actions/stale from 9 to 10 (#48)
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 16:59:42 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e3777d29a0 chore(deps): bump actions/setup-node from 4 to 6 (#47)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 16:59:25 +02:00
Renn F 86d2ce7d2f Screenshot + Readme notes 2026-06-04 16:43:27 +02:00
Renn F 57bea01c70 Updated CLAUDE.md 2026-06-04 16:36:40 +02:00
Renn F 14746cd391 Update CLA.md 2026-06-04 16:34:30 +02:00
Renn F d25994f87b fix(panel): dedupe notifications by id in the store
addNotification prepended every delivery and re-incremented unreadCount /
pendingAckCount, so a re-fetched or replayed notification stacked duplicates
and re-inflated the counts — already-acknowledged notifications re-surfaced to
the CEO and the pending badge kept climbing. Dedupe by id: update in place on
re-delivery; only add + count a genuinely new notification.
2026-06-04 06:47:30 +02:00
Renn F 2db9741ff6 fix(messaging): reuse a group's live session instead of recreating per open
create_session and create_session_with_access_check closed the group's active
session and opened a new one on every call. A group is meant to have ONE live
session (groups.active_session_id) that all participants post into, so this
churned a single conversation across many sessions — the smoke run showed ~one
session per message, and the CEO could not hold a conversation in a channel.

Both now reuse the live session when one is active and only open a fresh one
when none is (closed via timeout / boundary / merge), matching the existing
get_or_create_active_session contract.
2026-06-04 06:42:21 +02:00
Renn F 0ac3e9e233 feat(git): CEO merge resolves a coordination root's repo server-side
merge_pr_for_task merged using the client-provided project_slug, which a
coordination root (no project of its own) cannot supply. For a root, resolve
the repo from its product server-side so the CEO's approve-&-merge of the
root->master PR works without the panel knowing the repo. Non-root tasks keep
the client slug. Completes the root->master->CEO chain for the monorepo case
(multi-repo N-PR fan-out remains a follow-up).
2026-06-04 06:25:45 +02:00
Renn F b99c2f5366 feat(git): create_pr resolves a coordination root's repo via the product
main_pm_complete opens the root->master PR via create_pr(t.branch_name).
create_pr resolved the repo from task.project_id (null for a root) and would
raise. Route it through _project_for_task so the root's PR resolves the
product's repo. Additive — non-root tasks unchanged.
2026-06-04 06:24:22 +02:00
Renn F 2ee610f6cf feat(git): resolve a coordination root's repo from its product
Branch->project resolution (_project_slug_for_branch, _workspace_for_branch)
read task.project_id, which is null for a coordination root — so root-level
git ops (the root->master PR, the CEO merge) could not resolve a workspace.
A new _project_for_task falls through to the product's first distinct repo
(monorepo => the single repo) when project_id is null. Purely additive: a
task with a project_id resolves exactly as before; only the previously-
unresolvable root case changes.
2026-06-04 06:22:01 +02:00
Renn F ef72e72bf7 fix(git): raise slow-git-op warning threshold 1s -> 5s
Every push/fetch to a private monorepo from a self-hosted runner takes
~1-2s, so a 1s threshold tagged routine ops as 'slow git op' on every
operation — pure noise. 5s only fires on genuinely slow ops.
2026-06-04 06:03:36 +02:00
Renn F 172d639ba6 fix(api): initialize LearningPropagationService in the app lifespan
The learning singleton was created on demand but initialize(optimal_service)
was never called, so record_learning() always raised "not initialized" and
every task completion logged "Failed to extract learnings". The lifespan now
wires it to OptimalService once RAG is up (skipped when RAG is disabled).
2026-06-04 06:02:52 +02:00
Renn F d47adfe027 fix(gateway): rejection envelopes always carry a human-readable message
tracing_gap and incomplete_input rejections set missing and remediate but
left message null. The audit log records message (not remediate) and agents
keyed on message, so a rejected agent saw a null reason and retried the same
verb until it burned out instead of reading remediate and self-correcting.

Both builders (and from_decision) now derive a non-null message that folds
the missing tokens and the actionable remediate into one line, so the agent
and the audit trail always see what was missing and how to fix it.
2026-06-04 06:01:06 +02:00
Renn F 3cfac2e503 feat(git): forbid the agent merge path from merging into master
pr_merge (the gateway path a cell PM uses to merge a leaf/cell PR up the
hierarchy) accepted any target, including a repo's default branch — the
hole that let cell completion land on master. It now refuses any target
equal to the project's default branch with a CEO_ONLY error: a root→master
PR is merged solely by the CEO via approve-&-merge (merge_pr_for_task,
already CEO-gated from awaiting_ceo_approval). Agents open the master PR
and escalate; they never merge it.

Belt-and-suspenders to the integration-branch routing: even if a target
ever resolved to master, this blocks the merge at the GitHub-API boundary.
2026-06-04 05:55:03 +02:00
6cce556536 feat(git): coordination root cuts a Main-PM integration branch per repo (#58)
The coordination/fan-out root carries a product (cell->repo map) but no
project of its own, and was forced branchless — so a cell's parent-branch
resolution fell back to the project default (master), and cell completion
merged each cell straight to master, bypassing the Main-PM integration
point and the CEO merge gate.

Per the locked branch model (master <- feature/main_pm/{root} <- cell <-
dev), the root is now the Main-PM integration point: on claim it cuts
feature/main_pm/{root} off master in EACH distinct repo the product spans
(monorepo => 1, multi-repo => N). Cells then branch off it via the existing
ancestor-branch resolution, so cell work never targets master.

- ProductService.distinct_project_ids: enumerate the repos a product spans
- TaskService._create_branch_in_project: project-parameterized branch
  creation split out of _auto_create_branch
- TaskService._ensure_coordination_root_branches: cut the integration
  branch in each repo; graceful empty when the product has no cell map yet
- _ensure_branch_for_task routes a product-backed root here, not to no-op

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-06-04 05:48:21 +02:00
Renn F f36fb67bec refactor(workspace): split _ensure_agent_owned into rank-A helpers
Extract per-entry chown+chmod into _own_and_grant_rw and the pruned
workspace walk into _iter_ownable_entries. The single function carried a
no-op guard, an explicit-root chown, an os.walk with in-place pruning, two
path comprehensions, an inner entry loop, and a failure tally — cyclomatic
rank C. Behaviour is identical (root + every non-pruned entry chowned and
granted owner/group rw); the main function is now a guard, a sum() over the
entry iterator, and the warning, all rank A.
2026-06-04 03:02:35 +02:00
Renn F bbe9ac7c23 test: unblock-resume tests need a claimed task (branch_name)
The lifecycle fix routes a never-claimed (no-branch) blocked task to pending on
unblock; these two tests asserted in_progress on a no-branch task. Give them a
branch so they exercise the claimed-task resume path they intend (no-branch ->
pending is covered by new unit tests).
2026-06-04 02:41:19 +02:00
Renn F 0dc772a97f style: hoist test import to module top (PLC0415) 2026-06-04 02:27:12 +02:00
Renn F 48d6e99fa4 style: ruff-format the branch-idempotency test 2026-06-04 02:25:32 +02:00
Renn F 555c33ad7d fix(gateway): let a dependent agent read its dependency via evidence
evidence is read-only, but the cross-agent ownership gate blocked a caller from
inspecting a task its own work depends on (a frontend cell could not read the UX
task it was waiting on). Exempt reads where the target is a dependency of a task
assigned to the caller. Also dropped stale internal refs from the docstring.
2026-06-04 02:12:45 +02:00
Renn F 16982ce887 fix(git): idempotent branch creation — checkout existing branch instead of failing 128
A prior claim attempt can create the branch on disk before the DB records
branch_name (the claim rolls its fields back, but the on-disk branch persists).
A plain checkout -b then fails 'already exists' (exit 128), and the resulting
error-handling cascade is how branch creation spiraled into INTERNAL_ERROR.
Fall back to checkout <branch> when checkout -b returns non-zero.
2026-06-04 02:08:36 +02:00
Renn F 2631f2647c fix(lifecycle): unblock a never-claimed task to pending, not branchless in_progress
A task blocked before it was ever claimed (a dependency-gated claim that got
escalated) has no branch. Legacy unblock() forced in_progress, which the
dispatcher refuses (state=in_progress but branch_name unset) -> a spawn-refused
loop. Add the blocked->pending transition and route no-branch tasks there so
they are freshly claimed (the claim gate then holds them cleanly while the
dependency is unmet). Branched tasks still resume in_progress. Artifacts
regenerated.
2026-06-04 02:01:09 +02:00
Renn F 6d1b56b5b0 revert(briefing): drop the verb->MCP-server map from the agent briefing
The map told agents to hand-construct mcp__<server>__<verb> tool names, which
do not match what their runtime exposes — agents fumbled (No such tool
available: mcp__roboco-do__evidence) and had to retry the bare verb. It also
did not reduce the opening-move fumbling it targeted; agents recover via the
gateway's own remediate hints regardless. Net-negative. Reverts 3d04943 and
its follow-up 5462fe3.
2026-06-04 01:51:26 +02:00
Renn F ab687915f5 fix(docker): drop no-op userns_mode (NAS has no userns-remap)
The chown failures were never a userns-remap issue (the NAS daemon has no
userns-remap configured) — they were the .git-only chown leaving the working
tree root-owned, fixed separately. userns_mode:host on the orchestrator alone
was a no-op at best and a latent footgun (orchestrator un-remapped while agents
are not) if remap were ever enabled. Keep the uv-in-runner + env=production.
2026-06-04 00:40:28 +02:00
Renn F 4374cdbf63 fix(workspace): chown the working tree (pruning node_modules), not just .git
The .git-only walk left the working tree root-owned, so agents (uid 1000)
could not write any file — every mkdir/open/commit failed with EACCES and the
run died. Walk the whole workspace, chowning the root + tracked files + .git,
while pruning the heavy gitignored trees (node_modules/.venv/dist/...) that
made the full walk slow. Verified on the host: uid-1000 write succeeds after.
2026-06-04 00:38:42 +02:00
Renn F 289d5e4972 fix(scripts): reset workspaces no longer aborts when a private clone can't reach origin 2026-06-03 23:31:47 +02:00
Renn F d88a8df9f4 test: drop dead MAIN_PM delete that broke lifecycle isolation across test files 2026-06-03 22:06:32 +02:00
Renn F 3f1904825f fix(gateway): hold pre-assigned dev at give_me_work's list_assigned fallback when deps are unmet 2026-06-03 21:13:09 +02:00
Renn F a14e870ebb build(deps): raise pyjwt floor to >=2.13.0 and format bump_version
pyjwt 2.12.1 (pulled transitively by mcp and msal) carries four disclosed CVEs fixed in 2.13.0; add a uv constraint-dependencies floor and refresh the lock. Also apply ruff formatting to the changelog scaffold f-string in the version-bump script.
2026-06-03 20:45:32 +02:00
Renn F 49fddd1dc1 fix(project): default_branch defaults to master across the API create path
The request and service models defaulted default_branch to "main" and the
response converters fell back to "main", so omitting the field on the create
route persisted "main" instead of the DB column default of master. Flip every
default_branch default and fallback to master.
2026-06-03 20:42:15 +02:00
Renn F 93b5197613 refactor(orchestrator): extract code-task classification helpers to lower complexity 2026-06-03 20:39:08 +02:00
Renn F b0a596ecac fix(deps): gate the pre-assigned dev path on unmet dependencies
A dev subtask is always pre-assigned (assigned_to=<dev>), so it never
flows through the unassigned claim pool's dependency filter
(list_pending(filter_by_dependencies=True)). Every path that acts on a
pre-assigned pending dev subtask previously ignored dependency_ids: the
orchestrator spawned the dev container, give_me_work offered the task,
and the claim verb accepted it — letting a frontend dev code ahead of an
unfinished UX/UI design.

Hold the pre-assigned dev at each path it actually arrives by, until
every dependency reaches a terminal state:

- orchestrator _validate_task_for_spawn now consults dependency_ids via
  _check_dependencies_terminal and skips the spawn while any dependency
  is non-terminal (fail-closed on an unreadable dependency);
- TaskService.list_pending_for_agent excludes a pre-assigned task with
  unmet dependencies so give_me_work does not offer it;
- the Choreographer claim guard set rejects the claim with a clear
  remediate via a new unmet_dependency_guard.

Add TaskService.unmet_dependency_ids as the single source of truth for
"which dependency IDs are not yet terminal" and route the existing
inherit_unmet_dependencies through it.
2026-06-03 20:36:35 +02:00
Renn F 5462fe3ae6 fix(briefing): developer claim precondition is note(scope='note'), not 'decision'
The generated session briefing told every role with i_will_work_on that
note(scope='decision') is required before claiming. That is wrong: the
i_will_work_on gate is journal:note_at_claim, satisfied by
has_note_for_task, which queries JournalEntryType.GENERAL. Only
scope='note' maps to GENERAL; scope='decision' maps to DECISION_LOG and
is the PM's i_will_plan gate. Emit scope='note' on the dev-claim branch
and keep scope='decision' on the i_will_plan branch.
2026-06-03 20:18:47 +02:00
Renn F 1d2e22b586 test(board): drive the board-review CEO gate over a real spawn->active->idle->tick timeline
The existing gate test mocks both board reviewers as already-idle, so it never
exercises the spawn -> active -> exit -> idle -> next-tick transition — the
boundary the gate actually guards. Add a real-DB integration test that seeds a
board/coordination task plus the agents the handoff resolves against, dispatches
both reviewers through the real handler (the spawn stub leaves each ACTIVE in
_instances exactly as a real container spawn does), asserts board_review_complete
stays False while either reviewer is active, then marks both idle and runs the
next tick to assert the flag flips True and the formal CEO approval notification
is persisted.
2026-06-03 19:48:10 +02:00
Renn F 45836b4d6e fix(gateway): docs/optimal RBAC denials return Envelope with remediate
The docs and knowledge-base HTTP routes enforced authorization inline and
raised raw HTTPException(403) with no recovery hint, so agents received
remediate=null and an un-actionable error, and the optimal route held the
RBAC decision itself (a layer-separation violation).

Move the knowledge-base authorization decision into a gateway module
(services/gateway/kb_authz) that returns an Envelope.not_authorized with a
non-null remediate naming the roles allowed to perform the action. The docs
RBAC already lives in DocsService; render its UnauthorizedError through the
same gateway helper. Both route groups now return the Envelope wire-dict at
top level (HTTP 403) instead of a bare detail string, keeping the routes
thin (HTTP translation only). Permitted callers are unaffected.
2026-06-03 19:41:10 +02:00
Renn F 16c425799c fix(gateway): dev subtask inherits the cell task's unmet cross-cell dependency
A frontend cell task waits on the UX/UI design, but a dev/code subtask
delegated under it did not inherit the unresolved dependency, so the
developer became dispatchable and coded ahead of the design. Propagate
the parent cell task's still-unresolved dependencies onto the new subtask
on delegate, reusing the existing dependency model: the subtask is held by
list_pending(filter_by_dependencies=True) until the UX task is terminal.
2026-06-03 19:28:56 +02:00
Renn F 7c91a85e7e fix(project): include default_branch in project list summary response 2026-06-03 19:20:54 +02:00
Renn F 5afe87716c fix(git): default branch convention main -> master 2026-06-03 19:18:42 +02:00
Renn F 2bb83c96a3 fix(docs): grant head_marketing read-only documentation access
The spawn manifest mounts the roboco-docs MCP for head_marketing, but
the service READ_ROLES omitted the role, so list/read 403'd against a
tool the agent was handed. Add head_marketing to READ_ROLES so the
manifest and permissions agree (read-only; not added to WRITE_ROLES).
2026-06-03 19:13:17 +02:00
Renn F 5184d08287 fix(messaging): thread say() by task_id into one per-channel group and enforce channel access first 2026-06-03 19:08:59 +02:00
Renn F 3d04943bf3 feat(orchestrator): embed verb->MCP-server map + key preconditions in agent briefing
Agents fumble their first move — raw bash/http/shell-git, calling
evidence on roboco-flow when it lives on roboco-do, omitting the nature
argument on delegate, or skipping the required journal note before
claiming. The role docs cover this but agents cannot read them at spawn.

Generate a concise, role-accurate block from the role's actual manifest
(get_role_config): a verb->server map (roboco-flow / roboco-do /
roboco-git-readonly / roboco-optimal / roboco-docs) plus key
preconditions (note(scope='decision') before i_will_work_on / i_will_plan;
delegate requires nature; evidence is on roboco-do; never use raw
bash/http/shell-git). Embed it into the written session briefing.
2026-06-03 19:03:22 +02:00
Renn F 78faf6f4da fix(docker): ship uv in orchestrator runner, set production env + host userns
The orchestrator runner stage lacked uv, so workspace dep pre-install
(`uv sync`) and `uv run` CI commands failed at runtime; copy uv from the
builder stage like agent-base does. Set ROBOCO_ENVIRONMENT=production on
the orchestrator service so structlog emits JSON instead of console output.
Add userns_mode: "host" so the orchestrator's chown of cloned workspaces to
the agent uid isn't blocked by docker's user-namespace remap.
2026-06-03 18:57:49 +02:00
Renn F b18bdcd41a fix(optimal): serialize singleton init, normalize kb_search index_types, surface mentor errors
The OptimalService singleton published the instance before initialize()
finished, so a concurrent caller could observe _initialized=False and hit
"OptimalService not initialized" during RAG indexing. Build the instance,
initialize it, then publish under a lazily-bound asyncio lock so all callers
share a fully-initialized singleton.

roboco_kb_search forwarded the legacy alias index_types=['docs'], which is
not a valid IndexType value (the enum value is 'documentation'), producing a
400 at the route. Normalize the alias in the client before the request is
sent and fix the misleading tool docstring.

The mentor route let exceptions from mentor.ask escape as a bare 500 that
masked the real cause. Catch, log the true upstream error with stack, and
surface it in the response detail so failures are diagnosable.
2026-06-03 18:54:07 +02:00
Renn F 2c96bd09f6 fix(gateway): heartbeat on content-write success, single-claimant gate, progress soft-warn
commit()/progress()/note() now refresh last_heartbeat_at on the success
path (best-effort, suppressed), not only on rejection — an actively
writing agent no longer looks idle to the reaper between verb successes.

commit()/progress() verify the caller holds the active claim
(active_claimant_id), not merely the historical assigned_to, so a reaped
or handed-off assignee can no longer write onto a freed task; a non-holder
gets a not_authorized envelope with a clear remediate.

progress() with no plan_step on a task that has steps is accepted (product
decision for narrative mid-step updates) and logs a soft warning instead of
rejecting.
2026-06-03 18:44:33 +02:00
Renn F 61e80495c3 fix(workspace): scope _ensure_agent_owned walk to .git subtree only
Walking the entire workspace (incl node_modules) to chown+chmod every
entry cost 2.7-15.5s per git op. The agent only needs write ownership on
.git/ during git ops; working-tree files don't need chowning. Restrict
the walk to .git, and no-op when .git is absent.
2026-06-03 18:34:31 +02:00