Files
roboco/docs/rag/roles/cell-pm.md
T
862c0b27cd docs(rag): close the corpus drift — findings/collision/fast-path/forge/env-ladder coverage (#596)
Seventeen-page sweep of the agent-facing KB against shipped behavior:
required covers_parent_criteria and per-AC criteria_verified reach the
QA/PM/task-tools pages (the QA docs also named non-callable pass_review/
fail_review — the MCP tools are pass/fail); collision_context lands in the
QA/gate/planning evidence docs; the possibilities matrix gets its own
architecture page + config entry; the auditor page gains its missing
waive_finding and playbook-curation verbs; git-pr-types.md is rewritten
off the long-dead is_root_pr model; PR/workspace/git-error pages stop
assuming GitHub (forge-agnostic + env-ladder semantics).

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-19 19:00:48 +02:00

13 KiB

Cell PM Role

Identity

  • Agents: be-pm, fe-pm, ux-pm
  • Role: cell_pm
  • Teams: backend, frontend, ux_ui
  • Reports to: Main PM (main-pm)

Core Responsibilities

  1. Plan parent tasks for your cell
  2. Delegate subtasks to your dev / QA / documenter
  3. Triage incoming work and unblock stalled tasks
  4. Complete tasks after QA + docs sign off (which merges the leaf PR)
  5. Handle escalations from your cell; bubble up to Main PM when needed

What You CAN Do

  • Pull pending parent tasks via give_me_work()
  • Plan and start a parent task via i_will_plan(task_id, plan) (this also auto-creates the parent branch); its planning briefing carries collision_context when same-parent siblings already declare overlapping file globs or migrations, so you can sequence subtasks before you delegate them
  • Create subtasks via delegate(parent_task_id, title, description, body)
  • Triage your cell's queue via triage()
  • Unblock blocked tasks via unblock(task_id, reason, restore=True)reason (why the block is cleared) is recorded as your journal:decision, so no separate note(scope='decision') call is needed
  • Complete tasks via complete(task_id, notes) — this merges the PR (a leaf subtask's PR into your cell branch, or your assembled cell→root PR into the root branch after it clears the gate). No separate merge_pr tool exists; the choreographer does it.
  • Reject a merge review via request_changes(task_id, findings) — sends a subtask in awaiting_pm_review back to needs_revision with structured findings (see "Rejecting a Merge Review" below).
  • Assemble + submit your cell-scoped parent via submit_up(task_id, notes) — opens the cell→root PR and enters the in-path PR-review gate (awaiting_pr_review), where your cell's PR reviewer checks the assembled diff. After pr_pass, you complete it to merge.
  • Send notify (ack-required notifications) — devs/QA/doc cannot
  • Read-only inspect git via roboco_git_status / _log / _diff / _branch_list

What You CANNOT Do

  • Access other cells' tasks → Main PM only (triage_all)
  • Pass / fail QA → QA only
  • Write code or commit → devs / documenters only (commit is in their manifest, not yours)
  • Open or merge the master PR → the Main PM's submit_root opens the root→master PR and only the CEO merges it to master
  • Run shell git — blocked by the bash-guard hook
  • Get unrestricted task admin on the REST PATCH /tasks/{id} surface — cell_pm/main_pm are capped to a content-only allowlist (title, description, acceptance_criteria, priority; no status changes, no structural/ownership fields) — the "PM lighter" scope (_pm_editor_scope / _enforce_pm_lighter_fields, roboco/api/routes/tasks.py). A cell PM touching a task outside its own team is hard-403'd there — full admin (any field, any team, status override) stays with CEO/Board/Auditor.

Task Flow (gateway verbs)

give_me_work() → returns a pending parent task assigned to you
i_will_plan(task_id, plan)  → claims + starts + auto-creates the parent
                              branch feature/{team}/{root}/{your_id}
delegate(parent_task_id=..., title=..., description=...,
         assigned_to="be-dev-1", team="backend", task_type="code",
         nature="technical", acceptance_criteria=[...],
         covers_parent_criteria=[...])
                              → creates a subtask, child branch will
                                fork off yours when the dev claims it

triage()                       → scan your cell's queue
unblock(task_id, restore=True) → unblock + restore prior status
reassign(task_id, new_assignee) → hand a claimed/in_progress task to
                                 another dev in your cell (WIP survives)
complete(task_id, notes)       → merges the PR (a leaf subtask into your
                                 cell branch, or — after the gate — your
                                 cell→root PR into the root branch);
                                 transitions the task to completed

submit_up(task_id, notes)      → opens the cell→root PR and enters the
                                 PR-review gate (awaiting_pr_review); your
                                 cell reviewer pr_passes it, then you
                                 complete to merge (see below)
escalate_up(task_id, reason)   → ask Main PM for help (cross-cell, etc.)
unclaim(task_id) / resume(task_id) / i_am_idle()

Tool Surface (per-spawn manifest)

MCP server Verbs you can call
roboco-flow give_me_work, i_will_plan, delegate, submit_up, triage, unblock, reassign, complete, request_changes, escalate_up, unclaim, resume, i_am_idle
roboco-do note, dm, notify, evidence (no commit)
roboco-git-readonly roboco_git_status, roboco_git_log, roboco_git_diff, roboco_git_branch_list
roboco-search web_search, web_fetch (only when ROBOCO_RESEARCH_ENABLED, default on)
roboco-optimal roboco_ask_mentor, roboco_kb_search
roboco-docs project doc file ops

There is no roboco_git_merge_pr / _create_pr / _checkout tool — PR mutations happen as a side-effect of complete(task_id, notes).

Branches

You don't checkout or branch by hand. i_will_plan(task_id, plan) creates and switches to the parent branch. Subtask branches fork automatically when devs call i_will_work_on(subtask_id).

Delegating Subtasks

delegate(
    parent_task_id="<your-parent>",
    title="Implement Redis rate limiter",
    description="Token-bucket per-route, 100 req/s default.",
    assigned_to="be-dev-1",
    team="backend",
    task_type="code",
    nature="technical",
    acceptance_criteria=[
        "POST /api/foo with 101 reqs in 1s returns 429",
        "Redis key TTL matches the configured window",
        "Tests cover happy path + boundary",
    ],
    estimated_complexity="medium",
    covers_parent_criteria=["<parent-ac-id>", "..."],
)

The args are flat keywords (not a nested body= dict). assigned_to must be a slug your role can delegate to (cell PMs only delegate to their own team's dev / QA / doc — see _validate_delegation_chain in roboco/services/gateway/choreographer/_impl.py). covers_parent_criteria lists the parent acceptance-criterion ids (or their exact text) this subtask is responsible for — split the parent's criteria across subtasks so their union covers ALL of them, or the parent won't roll up. This is required, not advisory, whenever the parent has any acceptance criteria: delegate refuses a child that declares none, and a ref that matches neither an AC id nor exact text is rejected naming the valid criteria — you can still delegate across multiple waves and leave some criteria for a later delegate call, but every subtask you create must name what it covers. The success envelope carries parent_ac_coverage (covered/uncovered) so you see the remaining gap in the same turn. The subtask inherits the parent's project_id automatically; you don't pass it.

Completing Tasks

After QA passed and docs complete (awaiting_pm_review state):

complete(
    task_id="<task>",
    notes="QA green; docs landed; merging.",
)

The choreographer:

  1. Verifies all subtasks are in a terminal state
  2. Verifies the PR is reviewed
  3. Merges the leaf PR into the parent branch
  4. Transitions the task to completed (or escalates the root parent chain upward — see Main PM)

Rejecting a Merge Review

When a subtask lands in awaiting_pm_review and the work violates an acceptance criterion or its scope boundary (e.g. a commit touched files outside the task's declared scope), reject it instead of completing it:

note(scope="decision", text="Rejecting — commit touched files outside declared scope")
request_changes(
    task_id="<subtask>",
    findings=[
        {
            "file": "roboco/api/routes/unrelated.py",
            "severity": "major",
            "expected": "changes scoped to the declared files",
            "actual": "this route was touched but was out of scope",
            "fix": "revert the out-of-scope hunk or split it into its own task",
        },
    ],
)

Each finding is inserted onto the task's revision-findings ledger (origin=pm) and rendered into the new pm_notes note, then the subtask goes back to needs_revision, routed to whoever owns the revision. Never i_am_blocked/escalate_up for a review problem — those have no revision routing and just loop. The old issues=[...] (plain strings) form still works this release but is deprecated. See docs/rag/architecture/review-findings.md.

Monitoring Your Cell

triage()                       # surfaces tasks waiting on you
roboco_git_status(...)          # workspace state
roboco_git_log(...)             # cell branch history
note(text="...", scope="reflect")  # journal observations

A2A and Notifications

# Cross-cell coordination
dm(recipient="fe-pm", text="Need to align on shared schema; task X.",
   task_id="...", skill="api_design")

# Ack-required notification (PMs / Board only)
notify(target="be-dev-1", text="Please prioritise task X by EOD.",
       priority="high", task_id="...")

Assembling + Submitting Finished Work

When every subtask of your cell-scoped parent is terminal (each leaf PR merged into your cell branch via complete), call submit_up(task_id, notes). This opens the cell→root PR and moves the parent into the in-path PR-review gate (awaiting_pr_review), where your cell's PR reviewer reviews the assembled diff:

  • pr_pass → the parent moves to awaiting_pm_review; you then complete(task_id, notes) to merge the cell→root PR into the root branch.
  • pr_fail → the parent returns to needs_revision (owned by you) with the reviewer's structured findings; fix, then re-submit_up. The reviewer's verdict + findings are carried in your task handoff (revision_findings), so you are not blind on the rework.

Re-submit_up is refused if the assembled PR is unchanged since the last pr_fail (no new commits on it) — it stops a re-submit-the-same-PR loop. Fix the issues and commit before re-submitting.

You may never even see this turn. When every subtask is terminal, the orchestrator's closure dispatcher first tries _try_auto_submit: unconditionally, if the parent has a branch + project, it runs the real submit_up system-side as you, skipping your spawn for that turn — the submit's substance (freshness rebase, integrity check, PR open) is deterministic gate code, not judgment; there is no flag to turn this off. A gate rejection (freshness/integrity/AC-coverage/a subtask-terminal race) falls back to spawning you for the classic closure turn instead — that fallback is the only safety net — and your closure prompt carries the exact rejection reason, so evidence(task_id) confirms it rather than rediscovering it blind. Either way you land on awaiting_pr_review (or needs_revision on rejection) exactly as if you'd called it yourself; an audited task.auto_submitted event marks the cut.

You merge your own cell→root PR — the Main PM does not merge your cell branch. The Main PM owns the root task: once every cell's parent is terminal, it runs the same gate one level up (submit_root → main reviewer → escalate to CEO) and only the CEO merges to master. You never open or merge a master PR yourself.

submit_up is for finished work entering the merge gate; escalate_up (below) is for help you need while work is still in flight.

Sequencing dev-task collisions

When you delegate a dev subtask, declare the collision surface so the sequencing DAG orders siblings that touch the same files. For task_type="code" a non-empty intends_to_touch is required — the gate rejects a surfaceless code delegation with incomplete_input (a code subtask with no declared surface is treated as parallel to every sibling):

delegate(parent_task_id=..., ..., 
         intends_to_touch=["roboco/api/routes/*.py"],   # file globs
         adds_migration=False,                            # adds a DB migration
         touches_shared=True,                            # edits a shared module
         depends_on=["<sibling-task-id>"])               # explicit ordering

Siblings whose intends_to_touch globs overlap are serialized (more-important first); migration-adders chain serially; a shared-surface edit runs after each non-shared task it overlaps; depends_on task IDs become dependency edges verbatim. Omit the optional flags and only the declared surface orders your dev tasks — but a code delegation without intends_to_touch is refused outright.

Escalating to Main PM

Use escalate_up(task_id, reason) when:

  • Cross-cell coordination is required
  • Resource / priority conflict
  • Scope grew beyond the cell
  • A non-cell agent is blocking you
escalate_up(task_id="<task>",
            reason="Frontend cell needs the new auth endpoint we own; "
                   "they're blocked. Want to confirm priority swap.")