mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
* feat(lifecycle): revision findings ledger — structured QA/PR/PM/CEO failure feedback, persisted and delivered down the chain Every bounce used to survive only as flattened prose: rounds overwrote each other in notes_structured, request_changes persisted nothing, two raw dev_notes appends were silently destroyed by the next handoff note, and the dev prompt pointed at fields (qa_notes via evidence(), pm_notes) the API never delivered. Agents re-interpreted and re-discovered every failure before they could start fixing it. - task_review_findings (migration 071, append-only): file/line/severity/ criterion(AC-id-validated)/expected/actual/fix/evidence per finding, with origin (qa|pr_gate|pm|ceo), round, and an open->addressed->verified lifecycle (waived reserved); new tasks.pm_notes + PmReviewContent give request_changes a structured home - producers: fail_review/pr_fail/request_changes take findings=[...] (prose issues shimmed+merged for one release, deprecation-logged); ceo_reject validates its reason (no 500), lands an origin=ceo finding, and bumps round+audit on branchless coordination roots; guardrails at the verb chokepoint (nudge >5, hard reject >10, field caps, traversal-safe file); the dev_notes data-loss appends are removed; new task.request_changes + task.ceo_reject audit events close rework attribution - delivery: qa_notes/pr_reviewer_notes/pm_notes carry the deterministic [F-id8] rendering; claim briefings, evidence(), the REVISION_REQUIRED spawn prompt, PM triage bounced-blocks, and A2A bodies deliver open findings; round-N+1 QA and gate reviewers get the full prior ledger; panel Findings tab + bounced-xN chip; metrics pm_rejects/ceo_rejects + findings counts; vault task notes render a Findings section (fail-open) - resolution closes for every origin: i_am_done and submit_up/submit_root take resolved_findings gated by FINDINGS_ADDRESSED (owner-gated so a stale non-owner PM can never mutate the ledger); pass_review/pr_pass/ complete verify-stamp same-transaction; ceo_approve stamps best-effort - 24 real-DB integration tests drive the full loop through the real choreographer; full suite 12856 green * docs: revision findings ledger sweep — CLAUDE.md, map, RAG corpus - CLAUDE.md: new ledger section + corrected request_changes row - docs/map/review-findings.md (new subsystem map) + surgical updates to task-service/pr-gate-review/metrics-observability/vault/panel maps - docs/rag: producers' findings contract across qa/pr-reviewer/developer/ cell-pm/main-pm/ceo role docs (the PM docs were missing request_changes entirely), verb references, and a new architecture/review-findings.md disambiguating ledger findings from convention findings * test(e2e): resubmit resolves the pr_fail finding per the ledger contract The scripted pr_fail revision loop resubmitted submit_up without resolved_findings — correctly rejected now that FINDINGS_ADDRESSED gates the PM resubmit verbs (green locally, red only in CI since the e2e suite skips without ROBOCO_E2E_SMOKE=1). The scripted PM now reads the open ledger row pr_fail persisted (new open_finding_ids arc helper) and resolves it on resubmit, asserting the open set drains — exercising the coordinator half of the new contract end to end. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
18 KiB
18 KiB
Per-role verb shapes (autogenerated)
Run uv run python scripts/regenerate_verb_tables.py after changing
any role config or schema. Role prompts reference this file's sections
as the source of truth for verb signatures.
Driver-based roles (prompter, secretary) are intentionally omitted — their real tools live in their agent_sdk drivers, not role_config.
developer
Flow verbs
| Verb | Body schema |
|---|---|
give_me_work |
give_me_work() |
i_am_blocked |
`i_am_blocked(task_id: UUID, reason: str, blocker_type: str |
i_am_done |
i_am_done(task_id: UUID, notes: str = '', resolved_findings: list[ResolvedFindingInput] = PydanticUndefined) |
i_am_idle |
i_am_idle() |
i_will_work_on |
`i_will_work_on(task_id: UUID, plan: str |
open_pr |
open_pr(task_id: UUID) |
resume |
resume(task_id: UUID) |
sync_branch |
sync_branch(task_id: UUID, stash: bool = False) |
unclaim |
unclaim(task_id: UUID) |
Content (do) tools
| Tool | Body schema |
|---|---|
commit |
`commit(message: str, files: list[str] |
note |
`note(text: str, scope: str = 'note', task_id: UUID |
dm |
`dm(recipient: str, text: str, task_id: UUID |
evidence |
evidence(task_id: UUID) |
progress |
`progress(task_id: UUID, message: str, plan_step: str |
pr_update |
pr_update(see do_server) |
draft_playbook |
`draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID |
propose_video |
`propose_video(composition_id: str, x_caption: str, tiktok_caption: str, platforms: list[str], input_props: str |
request_sandbox |
`request_sandbox(services: list[str] |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
notify_ack |
notify_ack(notification_id: UUID) |
read_messages |
read_messages() |
read_a2a |
read_a2a(see do_server) |
qa
Flow verbs
| Verb | Body schema |
|---|---|
claim_review |
claim_review(task_id: UUID) |
fail_review |
`fail_review(task_id: UUID, issues: list[str] = PydanticUndefined, findings: list[str |
give_me_work |
give_me_work() |
i_am_blocked |
`i_am_blocked(task_id: UUID, reason: str, blocker_type: str |
i_am_idle |
i_am_idle() |
pass_review |
`pass_review(task_id: UUID, notes: str, ac_verdicts: list[str] |
resume |
resume(task_id: UUID) |
unclaim |
unclaim(task_id: UUID) |
Content (do) tools
| Tool | Body schema |
|---|---|
note |
`note(text: str, scope: str = 'note', task_id: UUID |
dm |
`dm(recipient: str, text: str, task_id: UUID |
evidence |
evidence(task_id: UUID) |
draft_playbook |
`draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID |
request_sandbox |
`request_sandbox(services: list[str] |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
notify_ack |
notify_ack(notification_id: UUID) |
read_messages |
read_messages() |
read_a2a |
read_a2a(see do_server) |
documenter
Flow verbs
| Verb | Body schema |
|---|---|
claim_doc_task |
claim_doc_task(task_id: UUID) |
give_me_work |
give_me_work() |
i_am_blocked |
`i_am_blocked(task_id: UUID, reason: str, blocker_type: str |
i_am_idle |
i_am_idle() |
i_documented |
i_documented(task_id: UUID, notes: str, files: list[str]) |
resume |
resume(task_id: UUID) |
unclaim |
unclaim(task_id: UUID) |
Content (do) tools
| Tool | Body schema |
|---|---|
commit |
`commit(message: str, files: list[str] |
note |
`note(text: str, scope: str = 'note', task_id: UUID |
dm |
`dm(recipient: str, text: str, task_id: UUID |
evidence |
evidence(task_id: UUID) |
progress |
`progress(task_id: UUID, message: str, plan_step: str |
pr_update |
pr_update(see do_server) |
draft_playbook |
`draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
notify_ack |
notify_ack(notification_id: UUID) |
read_messages |
read_messages() |
read_a2a |
read_a2a(see do_server) |
cell_pm
Flow verbs
| Verb | Body schema |
|---|---|
complete |
complete(task_id: UUID, notes: str) |
declare_coverage |
declare_coverage(task_id: UUID, criteria: list[str]) |
delegate |
`delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: Complexity, acceptance_criteria: list[str], project_id: UUID |
escalate_up |
escalate_up(task_id: UUID, reason: str) |
give_me_work |
give_me_work() |
i_am_idle |
i_am_idle() |
i_will_plan |
i_will_plan(task_id: UUID, plan: str, approach: str, sub_tasks: list[SubTaskCreate] = PydanticUndefined, technical_considerations: list[str] = PydanticUndefined, risks: list[RiskCreate] = PydanticUndefined, open_questions: list[OpenQuestionCreate] = PydanticUndefined) |
reassign |
reassign(task_id: UUID, new_assignee: str) |
request_changes |
`request_changes(task_id: UUID, issues: list[str] = PydanticUndefined, findings: list[str |
resume |
resume(task_id: UUID) |
submit_up |
submit_up(task_id: UUID, notes: str, resolved_findings: list[ResolvedFindingInput] = PydanticUndefined) |
triage |
triage() |
unblock |
unblock(task_id: UUID, reason: str, restore: bool = True) |
unclaim |
unclaim(task_id: UUID) |
Content (do) tools
| Tool | Body schema |
|---|---|
note |
`note(text: str, scope: str = 'note', task_id: UUID |
dm |
`dm(recipient: str, text: str, task_id: UUID |
notify |
`notify(target: str, text: str, priority: str = 'normal', task_id: UUID |
evidence |
evidence(task_id: UUID) |
pr_update |
pr_update(see do_server) |
draft_playbook |
`draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
notify_ack |
notify_ack(notification_id: UUID) |
read_messages |
read_messages() |
read_a2a |
read_a2a(see do_server) |
main_pm
Flow verbs
| Verb | Body schema |
|---|---|
complete |
complete(task_id: UUID, notes: str) |
declare_coverage |
declare_coverage(task_id: UUID, criteria: list[str]) |
delegate |
`delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: Complexity, acceptance_criteria: list[str], project_id: UUID |
escalate_to_ceo |
escalate_to_ceo(task_id: UUID, reason: str) |
escalate_up |
escalate_up(task_id: UUID, reason: str) |
give_me_work |
give_me_work() |
i_am_idle |
i_am_idle() |
i_will_plan |
i_will_plan(task_id: UUID, plan: str, approach: str, sub_tasks: list[SubTaskCreate] = PydanticUndefined, technical_considerations: list[str] = PydanticUndefined, risks: list[RiskCreate] = PydanticUndefined, open_questions: list[OpenQuestionCreate] = PydanticUndefined) |
request_changes |
`request_changes(task_id: UUID, issues: list[str] = PydanticUndefined, findings: list[str |
resume |
resume(task_id: UUID) |
submit_root |
submit_root(task_id: UUID, notes: str, resolved_findings: list[ResolvedFindingInput] = PydanticUndefined) |
triage |
triage() |
triage_all |
triage_all() |
unblock |
unblock(task_id: UUID, reason: str, restore: bool = True) |
unclaim |
unclaim(task_id: UUID) |
Content (do) tools
| Tool | Body schema |
|---|---|
note |
`note(text: str, scope: str = 'note', task_id: UUID |
dm |
`dm(recipient: str, text: str, task_id: UUID |
notify |
`notify(target: str, text: str, priority: str = 'normal', task_id: UUID |
evidence |
evidence(task_id: UUID) |
pr_update |
pr_update(see do_server) |
draft_playbook |
`draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
notify_ack |
notify_ack(notification_id: UUID) |
read_messages |
read_messages() |
read_a2a |
read_a2a(see do_server) |
product_owner
Flow verbs
| Verb | Body schema |
|---|---|
escalate_to_ceo |
escalate_to_ceo(task_id: UUID, reason: str) |
i_am_idle |
i_am_idle() |
triage |
triage() |
Content (do) tools
| Tool | Body schema |
|---|---|
note |
`note(text: str, scope: str = 'note', task_id: UUID |
pitch |
pitch(title: str, slug: str, problem: str, proposed_solution: str, target_cells: list[str]) |
dm |
`dm(recipient: str, text: str, task_id: UUID |
notify |
`notify(target: str, text: str, priority: str = 'normal', task_id: UUID |
evidence |
evidence(task_id: UUID) |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
notify_ack |
notify_ack(notification_id: UUID) |
read_messages |
read_messages() |
read_a2a |
read_a2a(see do_server) |
propose_roadmap |
propose_roadmap(cycle_goal: str, items: list[RoadmapItemInput]) |
head_marketing
Flow verbs
| Verb | Body schema |
|---|---|
escalate_to_ceo |
escalate_to_ceo(task_id: UUID, reason: str) |
i_am_idle |
i_am_idle() |
triage |
triage() |
Content (do) tools
| Tool | Body schema |
|---|---|
note |
`note(text: str, scope: str = 'note', task_id: UUID |
pitch |
pitch(title: str, slug: str, problem: str, proposed_solution: str, target_cells: list[str]) |
dm |
`dm(recipient: str, text: str, task_id: UUID |
notify |
`notify(target: str, text: str, priority: str = 'normal', task_id: UUID |
evidence |
evidence(task_id: UUID) |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
notify_ack |
notify_ack(notification_id: UUID) |
read_messages |
read_messages() |
read_a2a |
read_a2a(see do_server) |
propose_feature_spotlight |
propose_feature_spotlight(feature_slug: str = '', feature_title: str = '', body: str = '', wants_video: bool = False, video_script: str = '', skip: bool = False, skip_reason: str = '') |
auditor
Flow verbs
| Verb | Body schema |
|---|---|
i_am_idle |
i_am_idle() |
triage |
triage() |
Content (do) tools
| Tool | Body schema |
|---|---|
note |
`note(text: str, scope: str = 'note', task_id: UUID |
evidence |
evidence(task_id: UUID) |
approve_playbook |
approve_playbook(playbook_id: UUID) |
reject_playbook |
reject_playbook(playbook_id: UUID, reason: str) |
archive_playbook |
archive_playbook(playbook_id: UUID) |
curate_vault |
curate_vault(task_id: UUID, narrative: str) |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |
pr_reviewer
Flow verbs
| Verb | Body schema |
|---|---|
claim_gate_review |
claim_gate_review(task_id: UUID) |
claim_pr_review |
claim_pr_review(task_id: UUID) |
give_me_work |
give_me_work() |
i_am_idle |
i_am_idle() |
post_pr_review |
`post_pr_review(task_id: UUID, body: str, event: str = 'REQUEST_CHANGES', findings: list[str |
pr_fail |
`pr_fail(task_id: UUID, issues: list[str] = PydanticUndefined, findings: list[str |
pr_pass |
pr_pass(task_id: UUID, notes: str) |
unclaim |
unclaim(task_id: UUID) |
Content (do) tools
| Tool | Body schema |
|---|---|
note |
`note(text: str, scope: str = 'note', task_id: UUID |
evidence |
evidence(task_id: UUID) |
notify_list |
notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20) |
notify_get |
notify_get(notification_id: UUID) |