Commit Graph
7 Commits
Author SHA1 Message Date
Renn F 5902c0fe38 feat(roles): add the read-only pr_reviewer role end-to-end
A global, read-only PR reviewer agent (pr-reviewer-1) that reviews inbound
external/fork PRs and posts one change-request. Wired end-to-end:

- identity: Role.PR_REVIEWER + agent + ROLE_LEVEL (QA-peer) + REVIEWER_ROLES
- lifecycle: CLAIM_RULES + ROLE_TEAM_RULES + a dedicated claim_pr_review /
  post_pr_review verb pair (distinct from QA's) + the pr_review_done action and
  its in_progress->completed transition; give_me_work / i_am_idle gain the role
- role_config: a read-only RoleConfig (allows_write=False)
- journaling: ALL_CELLS read tier so it can read internal intent like QA
- tracing: post_pr_review requires a learning entry; claim_pr_review is waived
- seeds presentation + factory prompt layer + builtin tools + the agentrole
  enum migration (037) + regenerated verb/lifecycle artifacts

Read-only at /app like QA/auditor; default-off — nothing dispatches review work
until external_pr_enabled. Foundation + role-config + enum suites green; ruff +
mypy clean; orchestrator boots.
2026-06-16 10:37:06 +02:00
Renn F 92543ad593 chore(prompts): stop generating verb tables for driver-based roles
regenerate_verb_tables.py looped every role in ROLE_CONFIGS, emitting a
_generated/<role>.md for prompter and secretary too. Both intentionally keep
only note+evidence in role_config — their real tools live in their agent_sdk
drivers (intake: propose_draft; secretary: read_state/read_task/
submit_directive, the last gated through the backend /directives), and neither
uses the _generated/<role>.md prompt-composition path. So the generated tables
understated those roles and showed up as perpetually-untracked noise.

Skip the driver-based roles (_DRIVER_BASED_ROLES) in both the aggregate verbs.md
and the per-role file output, with a comment pointing at the real surfaces.
Regenerated verbs.md drops the two misleading sections.
2026-06-16 04:50:44 +02:00
Renn F 1fb723174a feat(gateway): decomposition coverage gate + AC visibility (guardrails spec 2)
The decomposition floor that pairs with the roll-up gate (spec 4): a PM
cannot finish decomposing a parent while one of its acceptance criteria has
no subtask responsible for it — the "two leaves, half the ACs silently
dropped" pattern. Three parts:

- Gate: i_am_idle is rejected for a cell_pm/main_pm whose owned parent still
  has criteria in unclaimed_parent_acceptance_criteria (claimed = referenced
  by any live, non-cancelled child). Distinct from the roll-up gate, which
  fires at submit_up/complete and demands a *completed* child; this fires
  earlier and asks only that every criterion be *claimed*. Safe-by-
  construction: inert until a PM declares coverage, so legacy / not-yet-
  adopted decompositions are never blocked.

- Visibility: PM-facing briefings (give_me_work, i_will_plan, submit_up) and
  every delegate response now carry parent_ac_coverage ({id,text,claimed,
  verified} per criterion) + unclaimed_parent_acs, so a PM can map subtasks
  to criterion ids via covers_parent_criteria and see what is still
  uncovered after each delegate. Off for leaf roles, so a developer's own
  criteria never surface as bogus "unclaimed" noise.

- Prompts: cell_pm / main_pm role prompts document covers_parent_criteria and
  the new idle enforcement in the existing Coverage discipline.

TaskService.{parent_ac_coverage,unclaimed_parent_acceptance_criteria} added
beside uncovered_parent_acceptance_criteria; all three refactored onto a
shared _parent_ac_ref_sets helper (keeps each under the xenon B ceiling,
preserves the committed roll-up behavior). Verb tables regenerated for the
new delegate param — the regen also syncs pre-existing table drift that was
never regenerated after earlier merges (read_messages, pass_review
ac_verdicts, board pitch). Two brand-new generated tables (prompter,
secretary) are left untracked pending a separate decision.
2026-06-16 03:49:00 +02:00
3205443119 Fix: dependency spawn gate and cell ownership (#73)
* Cleanup + Missing greenlet error

* fix(messaging): persist a group's active-session pointer so posts reuse it

create_session and create_session_with_access_check set group.active_session_id
from session.id BEFORE the flush that materializes it — the id is a flush-time
uuid4 default, so the pointer was written as NULL and every post opened a fresh
session, fragmenting one conversation across many. Flush first, then link, the
same ordering the seed path already uses.

Two tests fabricated "two distinct sessions" by calling create_session twice on
one group, which only differed because of this bug; switch them to two groups so
they keep testing their real intent. Add a regression guard that the pointer is
actually persisted and a second create reuses the live session.

* fix(orchestrator): gate spawns on dependencies and keep cell tasks in their cell

The cross-task dependency check ran only on the dev dispatch path, so cell-PM,
Main-PM and board agents were spawned onto dependency-blocked tasks and flailed
unblock / escalate / notify against an unfinished upstream — climbing ownership
of cell work up to the board, which cannot drive it, and deadlocking the task.

- Move the dependency gate into the shared spawn readiness check so it covers
  every role, and auto-block the task so it leaves the pending pool until the
  upstream reaches a terminal state (then the existing auto-unblock revives it).
- Cell-ownership invariant: a backend/frontend/ux_ui task may only be worked or
  owned by its own cell. The readiness gate refuses a board or Main-PM spawn
  onto a cell task; reassign refuses and clears such an owner; and on
  dependency-clear a mis-owned cell task is re-homed to its cell's pending pool
  instead of reviving under an owner that cannot progress it.
- A dependency block is never a CEO signal: notify(target=ceo) is refused while
  the task is waiting on an unfinished upstream, with a remediate to idle and
  wait — the block clears on its own.

* Uploading images + Fixing pyproject.toml

* ++

* revert(orchestrator): drop the cell-ownership block pending a tooling audit

The cell-ownership invariant added earlier — a board / Main-PM role may never be
spawned onto or reassigned to a cell task, plus re-homing a mis-owned cell task
on dependency-clear — was too absolute. It forbids a higher role from stepping
in when something genuinely deeper is going on, and contradicts the existing
rule that main_pm may hold a task at awaiting_pm_review. The dependency spawn
gate already prevents the cascade that handed the board cell tasks; the deadlock
it guarded against will be addressed with a return-path approach after auditing
what tools the cell PMs actually need. Keeps the dependency gate and the CEO
dependency-block notify guard.

* docs(prompts): a dependency wait is wait-and-idle, not escalate

The cell-PM and Main-PM prompts told agents to escalate_up / retry unblock on a
blocked task without distinguishing a dependency wait (which auto-clears the
moment the upstream completes) from a real wedge — the source of the
escalate/unblock flail and the CEO-notification spam. Split the blocked-state
guidance: a cross-cell dependency wait = note + i_am_idle (do not escalate,
unblock, or notify the CEO); escalate only a genuinely broken upstream. Fix two
stale references to i_am_blocked, a developer-only verb the PMs do not have,
to escalate_up.

Correct the CLAUDE.md verb-surface table, which understated every role: it
listed 4 cell_pm verbs while the flow manifest derives the full set (11,
including unclaim and i_am_idle) from lifecycle.spec.intents_for_role.

* feat(gateway): cell_pm reassign verb — intra-cell developer hand-off

A cell PM can now hand a claimed/in_progress task to another developer in its
own cell without unclaim (which drops the work back to the pool and loses the
assignee). The branch is keyed to the task, so the work-in-progress is
preserved; the new dev is respawned to continue. Intra-cell only: the task must
be in the caller's cell and new_assignee must be a developer of that same cell.

Wired through every layer: the reassign IntentSpec (composes=(), cell_pm-only),
the choreographer verb + intra-cell guard, a reaper-safe
TaskService.reassign_active_claim (reseeds the claim heartbeat so the new dev
is not immediately reaped), the ReassignRequest schema, the cell_pm flow route,
and the MCP flow-server tool. Tracing-waived like unclaim (mechanical hand-off).
Regenerated lifecycle/verb artifacts; prompt + CLAUDE.md updated.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-06-06 22:10:48 +02:00
Renn F 073c2ec8b5 fix(prompts): repair the verb-table generator and regenerate
regenerate_verb_tables.py imported roboco.api.schemas.v2, which no longer
exists (schemas moved to v1), so it raised on import and the generated
verb/tool tables could never be refreshed — leaving _generated/verbs.md
and the per-role prompts stale (e.g. listing submit_for_qa, omitting the
notify_*/channels/progress/pr_update content tools). Repoint the imports
to v1, fix the renamed schema (OpenPrRequest), and regenerate.
2026-06-05 17:05:45 +02:00
Renn F 6806516015 refactor(gateway): rename submit_for_qa to open_pr; pin atomic preconditions
Pre-fix, submit_for_qa opened a PR (side effect) and returned OK with
next='call i_am_done' — agents read the verb name, assumed they were
done with QA handoff, never called i_am_done, and PRs ended up
orphaned (PR #12 in the 2026-05-08 trace).

Two changes:

1. Rename submit_for_qa -> open_pr so the verb name matches the
   semantic. The PR opens here; the actual QA handoff happens at
   i_am_done. Renamed across:
   - choreographer/_impl.py (method)
   - mcp/flow_server.py (tool registration + _TOOLS dict)
   - api/routes/v2/flow_dev.py (route + handler)
   - api/schemas/v2/flow.py (OpenPrRequest)
   - services/gateway/verb_gates.py (_STATE_VERBS)
   - services/gateway/role_config.py (developer flow manifest)
   - services/gateway/content_actions.py (commit-success next= hint)
   - agent_sdk/server.py (post-tool guidance map)
   - runtime/orchestrator.py (developer prompt)
   - agents/prompts/{base,roles/developer,_generated/*}.md
   - tests/unit/gateway/test_submit_for_qa.py -> test_open_pr.py
   - tests/unit/api/routes/v2/test_flow_dev.py
   - tests/unit/gateway/test_verb_gates.py
   - tests/unit/api/test_correlation_id.py
   - tests/unit/mcp_servers/test_flow_server.py
   - tests/integration/test_full_lifecycle_real_db.py

2. New regression test (test_open_pr_does_not_create_pr_if_no_commits)
   pins the atomic invariant: preconditions (assignee, commits,
   no-prior-PR) must be checked BEFORE git.create_pr/push_branch run.
   Any future re-ordering breaks the test.

Tests: 3128 passing (3127 + 1 new), 100% coverage, ruff clean.

Note: TaskService.submit_for_qa() (the v1-layer service method) is
INTENTIONALLY not renamed — it's a different layer used by the v1
routes. The rename here is only the gateway verb surface.
2026-05-08 11:54:31 +02:00
Renn F 4829f93a68 fix(gateway): unblock task claim; full Phase 0/1/2 remediation
Resolves the 100% claim-failure rate introduced by the gateway rewrite
  (commit 62bda0c plus 78 follow-ups). Live smoke runs hit
  `404 /api/v2/flow/developer/...` on every dev verb plus a manifest
  fallback that silently exposed off-role verbs to PMs — confirmed
  firing simultaneously in NAS agent logs (be-dev-1, be-pm, main-pm).

  Audit reports under docs/internal/audit_2026_05_04/ catalogue 49
  defects across gateway, services, prompts, MCP transport, substrate,
  and tests (8 detail reports + master synthesis). Six smoking guns;
  three proven in production logs.

  Phase 0 — unblock claim:
  - URL prefix /api/v2/flow/dev → /developer; slug-map board roles
    (product_owner, head_marketing) → /board (D-01)
  - _i_will_work_on AttributeError on None across pending /
    needs_revision / claimed re-entry branches (D-02)
  - Seed last_heartbeat_at in _qa_or_doc_claim (D-03)
  - Drop misleading i_have_committed verb; dev flow uses commit() (D-04)
  - Manifest mount via compose; flow_server + do_server fail loud
    instead of exposing all-verbs fallback (D-12)
  - MCP _post() surfaces envelope body on 4xx so agents see remediate
    hints (D-13)
    on git failure so retries aren't blocked by half-state (S-01)

  Phase 1 — lifecycle stability:
  - _resolve_skill falls back to AgentTable.capabilities (D-06)
  - main_pm_complete uses kwargs for escalate_to_ceo (D-07)
  - i_am_done auto-runs submit_verification when in_progress (D-08)
  - active_claimant_id wired in claim/unclaim paths — single-claimant
    invariant now functional (D-05)
  - qa_pass/qa_fail assert claimed_by parity with qa_agent_id (D-18)
  - Prompt-drift sweep: fail() shape, i_am_done(task_id, notes),
    subtask cap (12 hard / 8 soft), error-code symbology rewritten in
    base.md + per-role anti-patterns (D-10/11/29/30/31, D-37)

  Phase 2 — invariants + architecture:
  - Real-DB integration test exercising claim → in_progress → commit
    → submit_for_qa → i_am_done → awaiting_qa (P2-1)
  - choreographer.py → package; 3 of 6 role mixins extracted
    (board, doc, qa). _impl.py 2,526 → 2,080 lines (-18%). Continuation
    plan in docs/internal/audit_2026_05_04/p2_2_decompose_plan.md (P2-2)
  - Closure guards consolidated via _subtasks_not_terminal_envelope (P2-3)
  - TaskService.unclaim_for_reaper routed through canonical
    _validate_and_set_status; in_progress → pending added to
    VALID_TRANSITIONS (P2-4)
  - Dead code removed: i_am_done_with_catchup verb, _run_catch_up helper
    (P2-5)
  - 6 state-machine invariants asserted via property test (P2-6)
  - attempt_id (uuid4) stamped on every gateway.rejected audit row (P2-7)
  - _reconcile_orphan_claims_on_startup rolls back tasks left CLAIMED
    with branch_name=NULL from prior crashes (P2-8)
  - scripts/regenerate_verb_tables.py introspects Pydantic schemas +
    role_config; compose_prompt injects per-role tables as a layer.
    Eliminates the prompt-drift class structurally (P2-9)

  Other:
  - D-48: orchestrator mounts host's ~/.claude.json when present so
    agents don't boot from backup recovery on every spawn
  - D-49: dev dispatcher rejects role-mismatched spawns (e.g. doc task
    assigned to dev agent)

  Tests: 553 pass · ruff + mypy clean. Live NAS smoke verification
  pending — needs the stack brought back up.
2026-05-04 23:43:55 +02:00