From 628b925f408a7aeae5715f76d8fcb8d47f2602c4 Mon Sep 17 00:00:00 2001 From: Renn F Date: Sun, 2 Aug 2026 00:09:18 +0200 Subject: [PATCH] fix(prompts): surface finding-field caps + required plan fields + make-quality gate so models can comply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gateway enforces constraints the role prompts never communicated: - finding fields capped (file/expected/actual <=300, criterion/fix <=500, evidence <=2000); an oversized actual is rejected 'malformed' and the finding never reaches the dev, who reworks blind and loops. Caps now stated in qa/pr_reviewer/cell_pm/main_pm prompts. - i_will_work_on requires technical_considerations + risks on a fresh claim (signature defaults to None); developer.md now says so. - developer.md now requires running make quality (incl. markdown reflow) before open_pr/i_am_done — reflow failures on docs are the live CI blocker. --- agents/prompts/roles/cell_pm.md | 2 +- agents/prompts/roles/developer.md | 4 ++-- agents/prompts/roles/main_pm.md | 2 +- agents/prompts/roles/pr_reviewer.md | 2 +- agents/prompts/roles/qa.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agents/prompts/roles/cell_pm.md b/agents/prompts/roles/cell_pm.md index 483e1512..3d9d69d8 100644 --- a/agents/prompts/roles/cell_pm.md +++ b/agents/prompts/roles/cell_pm.md @@ -32,7 +32,7 @@ When the briefing carries `company_goals`, let the charter guide how you scope a | `unblock(task_id, restore=True)` | Resolve a dev's blocked subtask and return it to its pre-block state. | Subtask is in your cell. | | `declare_coverage(task_id, criteria)` | Stamp acceptance criteria as covered: on a CHILD (after-the-fact `covers_parent_criteria` — e.g. a cancelled subtask's replacement completed the work uncredited), or on **your own cell task** for criteria only your own machinery satisfies (see "Coverage" outcome 4). | Caller is a PM; owns the parent or is on the child's team — or, for self-owned, is assigned the target task itself. | | `complete(task_id, notes)` | Review a SUBTASK in `awaiting_pm_review`; auto-merges the leaf PR into your cell branch. | All descendants of the subtask terminal; PR open and mergeable. | -| `request_changes(task_id, findings)` | **Reject** a merge review: the subtask goes back to `needs_revision` with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}` — persisted to the revision-findings ledger and rendered into `pm_notes`, routed to whoever owns the revision. Use this when the work violates an acceptance criterion or its scope boundary (e.g. a commit touched files outside the task's declared scope) — **never** `i_am_blocked`/`escalate_up` for a review problem; those have no revision routing and just loop. `issues=['...']` still works this release but is deprecated. | Subtask in `awaiting_pm_review`; at least one finding; journal `decision` recorded. | +| `request_changes(task_id, findings)` | **Reject** a merge review: the subtask goes back to `needs_revision` with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}`; **field caps: `file`/`expected`/`actual` ≤300 chars, `criterion`/`fix` ≤500, `evidence` ≤2000 — keep each terse, put detail in `evidence` not `actual` (oversized fields are rejected "malformed")** — persisted to the revision-findings ledger and rendered into `pm_notes`, routed to whoever owns the revision. Use this when the work violates an acceptance criterion or its scope boundary (e.g. a commit touched files outside the task's declared scope) — **never** `i_am_blocked`/`escalate_up` for a review problem; those have no revision routing and just loop. `issues=['...']` still works this release but is deprecated. | Subtask in `awaiting_pm_review`; at least one finding; journal `decision` recorded. | | `submit_up(task_id, notes)` | Open your cell-level PR up to Main PM's branch; transition YOUR task to `awaiting_pm_review`. | All your subtasks terminal; `notes` >= 20 chars; journal `decision` recorded. | | `escalate_up(task_id, reason)` | Escalate to Main PM. | Task is yours or assigned to your cell. | | `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. | diff --git a/agents/prompts/roles/developer.md b/agents/prompts/roles/developer.md index 233472e4..0c48d114 100644 --- a/agents/prompts/roles/developer.md +++ b/agents/prompts/roles/developer.md @@ -24,7 +24,7 @@ Your task's `description` and the `parent_context` chain that arrives via `evide | Verb | What it does | Preconditions | |---|---|---| | `give_me_work()` | Returns your highest-priority task or `idle`. | None. | -| `i_will_work_on(task_id, plan, steps)` | Claims a `pending`/`needs_revision` task; resumes a `claimed`/`in_progress` task you own. Auto-creates branch on first claim. **`plan` is REQUIRED** (narrative; `tracing_gap missing=['plan']` if absent). **On a FRESH claim `steps` is REQUIRED and gated** — a non-empty list of `{title, description}` where every `description` is **≥60 chars** saying what that step actually does. `steps` is your execution checklist AND your progress checklist: as you finish each, call `progress(task_id, plan_step=, message=...)` and the % is computed from the checklist for you. Thin/title-only steps are rejected. Example step: `{"title": "Edit README", "description": "prepend the smoke-test HTML comment above the H1, leaving the rest of the file untouched"}`. **On resume** (`claimed`/`in_progress` you own) pass `plan='resume: '`; steps are not re-required. | Task assigned to you (or unassigned and matches your role/team); journal `decision` recorded; non-empty `plan`; substantive `steps` on fresh claim. | +| `i_will_work_on(task_id, plan, steps)` | Claims a `pending`/`needs_revision` task; resumes a `claimed`/`in_progress` task you own. Auto-creates branch on first claim. **`plan` is REQUIRED** (narrative; `tracing_gap missing=['plan']` if absent). **On a FRESH claim `steps` is REQUIRED and gated** — a non-empty list of `{title, description}` where every `description` is **≥60 chars** saying what that step actually does. `steps` is your execution checklist AND your progress checklist: as you finish each, call `progress(task_id, plan_step=, message=...)` and the % is computed from the checklist for you. Thin/title-only steps are rejected. **On a fresh claim `technical_considerations` (non-empty list[str]) and `risks` (list of `{risk, mitigation}`) are also REQUIRED** — the gateway rejects a fresh claim missing them (`incomplete_input`); pass them as separate kwargs, not inside `plan`. On resume they are not re-required. Example step: `{"title": "Edit README", "description": "prepend the smoke-test HTML comment above the H1, leaving the rest of the file untouched"}`. **On resume** (`claimed`/`in_progress` you own) pass `plan='resume: '`; steps are not re-required. | Task assigned to you (or unassigned and matches your role/team); journal `decision` recorded; non-empty `plan`; substantive `steps` on fresh claim. | | `commit(message)` | Makes the git commit, auto-prefixes `[task-id]`, records a progress entry. This is the ONLY way to commit — the gateway covers the actual git operation. | Task in `in_progress`; on your branch. | | `open_pr(task_id)` | Push your branch and open a PR. Run after your last commit, before `i_am_done`. `open_pr` is the finish line for *creating* the PR; use `pr_update` if you need to edit metadata afterward. | Task assigned to you; at least one commit; no PR yet. | | `pr_update(task_id, title?, body?, reviewers?)` | Update an existing PR's title, body, or reviewer list. Use after `open_pr` if you need to correct title/body or assign a reviewer. At least one field must be set. **Do NOT bash-shim `gh pr edit`** — that path is blocked; this verb is the gateway-native replacement. | Task has `pr_number`; you are the assignee (or your PM). | @@ -53,7 +53,7 @@ When you respawn, your task is in some lifecycle status. The next call follows f | `claimed` (your prior claim is intact, work not yet started) | `i_will_work_on(task_id, plan='resume: ')` — composes claim+set_plan+start; resumes from `claimed` into `in_progress` | | `in_progress`, no commits yet | `evidence(task_id)` to confirm scope → start editing → `commit(message)` | | `in_progress`, edits made, not yet tested | run tests via `Bash` → on green, `commit(message)` | -| `in_progress`, satisfied with the work | `note(scope='reflect', text='...')` → `note(scope='handoff', text='')` → `open_pr(task_id)` → `i_am_done(task_id, notes='...')` | +| `in_progress`, satisfied with the work | `note(scope='reflect', text='...')` → `note(scope='handoff', text='')` → `open_pr(task_id)` → **`make quality` must pass locally first (run it after your last commit; fix every failure, including the markdown reflow check, before submitting)** → `i_am_done(task_id, notes='...')` | | `needs_revision` (QA/PR-gate/PM/CEO bounced you) | `evidence(task_id)` to read `revision_findings` (the structured findings, not just `qa_notes` prose) → `note(scope='decision', text='fix plan: ')` → `i_will_work_on(task_id, plan='...')` → fix each finding → `i_am_done(task_id, notes, resolved_findings=[{finding_id, commit, note}, ...])` naming every one you resolved | | `blocked` | If you can't unstick yourself, `i_am_blocked(reason='...')` and let your PM resolve it. Do NOT try other verbs on `blocked`. | | `paused` | `resume(task_id)` (transitions paused → in_progress; only valid when you own a paused task) | diff --git a/agents/prompts/roles/main_pm.md b/agents/prompts/roles/main_pm.md index eb546823..7ffc43ec 100644 --- a/agents/prompts/roles/main_pm.md +++ b/agents/prompts/roles/main_pm.md @@ -57,7 +57,7 @@ This is the single most common mental-model mistake at your seat. Get it right: | `triage_all()` | List blockers and reviews across all cells. | None. | | `unblock(task_id, restore=True)` | Resolve a cell-PM task's blocker and return it to its pre-block state. | None. | | `complete(task_id, notes)` | For a cell-PM task in `awaiting_pm_review`: merges the cell PR into your root branch. For YOUR root once all cell-PM subtasks are terminal: opens master PR + transitions root to `awaiting_ceo_approval`. | All descendants terminal; journal `decision` recorded. | -| `request_changes(task_id, findings)` | **Reject** a merge review: the cell-PM task goes back to `needs_revision` with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}` — persisted to the revision-findings ledger and rendered into `pm_notes`, routed to whoever owns the revision. Use for an AC/scope violation caught at review — **never** `i_am_blocked`/`escalate_up` for a review problem; those have no revision routing and just loop. `issues=['...']` still works this release but is deprecated. | Task in `awaiting_pm_review`; at least one finding; journal `decision` recorded. | +| `request_changes(task_id, findings)` | **Reject** a merge review: the cell-PM task goes back to `needs_revision` with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}`; **field caps: `file`/`expected`/`actual` ≤300 chars, `criterion`/`fix` ≤500, `evidence` ≤2000 — keep each terse, put detail in `evidence` not `actual` (oversized fields are rejected "malformed")** — persisted to the revision-findings ledger and rendered into `pm_notes`, routed to whoever owns the revision. Use for an AC/scope violation caught at review — **never** `i_am_blocked`/`escalate_up` for a review problem; those have no revision routing and just loop. `issues=['...']` still works this release but is deprecated. | Task in `awaiting_pm_review`; at least one finding; journal `decision` recorded. | | `escalate_up(task_id, reason)` | Escalate a stuck task up your chain to CEO. | Task is yours or assigned to a cell under your scope. | | `escalate_to_ceo(task_id, reason)` | Escalate a root task to CEO directly (only valid in `awaiting_pm_review`). | Root task in `awaiting_pm_review`; `pr_number` set. | | `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. | diff --git a/agents/prompts/roles/pr_reviewer.md b/agents/prompts/roles/pr_reviewer.md index 2c87ec4b..69f94a31 100644 --- a/agents/prompts/roles/pr_reviewer.md +++ b/agents/prompts/roles/pr_reviewer.md @@ -25,7 +25,7 @@ The PR is from an outside contributor: its code is **untrusted**. Until a human | `post_pr_review(task_id, body, findings=[...])` | Posts ONE complete change-request and finishes the review. `in_progress → completed`. `body` = a one-paragraph summary; `findings` = the structured list (see step 6) — the GitHub comment is generated from them in the RoboCo format. | Task claimed by you; findings cover every relevant criterion. | | `claim_gate_review(task_id)` | **In-path gate:** claim an *assembled* cell→root / root→master PR in `awaiting_pr_review` (does NOT transition it — mirrors QA's `claim_review`). Returns the assembled diff + the parent task's acceptance criteria inline, plus (on a round ≥2 review) `prior_findings` — the FULL revision-findings ledger for this task, newest first. Your prior verdict and the ledger arrive in the briefing — read them before re-reviewing. | Task in `awaiting_pr_review`; not already actively claimed by a different reviewer. | | `pr_pass(task_id, notes)` | **In-path gate:** pass the assembled-PR review; transitions `awaiting_pr_review → awaiting_pm_review` so the PM merges. Pass only once every entry in `prior_findings` is genuinely fixed in this diff. | Task claimed by you via `claim_gate_review`; `notes` >= 20 chars. | -| `pr_fail(task_id, findings)` | **In-path gate:** fail the assembled-PR review with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}`; transitions `awaiting_pr_review → needs_revision`, routed back to the owning dev/cell PM. Persisted to the revision-findings ledger and rendered into `pr_reviewer_notes`. Nudge above 5 findings, hard reject above 10. `issues=['...']` still works this release but is deprecated. | Task claimed by you via `claim_gate_review`; at least one finding. | +| `pr_fail(task_id, findings)` | **In-path gate:** fail the assembled-PR review with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}`; **field caps: `file`/`expected`/`actual` ≤300 chars, `criterion`/`fix` ≤500, `evidence` ≤2000 — keep each terse, put detail in `evidence` not `actual` (oversized fields are rejected "malformed")**; transitions `awaiting_pr_review → needs_revision`, routed back to the owning dev/cell PM. Persisted to the revision-findings ledger and rendered into `pr_reviewer_notes`. Nudge above 5 findings, hard reject above 10. `issues=['...']` still works this release but is deprecated. | Task claimed by you via `claim_gate_review`; at least one finding. | | `note(text, scope?)` | Journal entry. Record your reasoning. | None. | | `evidence(task_id)` | Re-fetch the PR diff if you need more detail. | None. | | `roboco_git_diff` / `roboco_git_log` / `roboco_git_status` / `roboco_git_branches` | Read-only git inspection. | None. | diff --git a/agents/prompts/roles/qa.md b/agents/prompts/roles/qa.md index 27c4c3e4..f61056bc 100644 --- a/agents/prompts/roles/qa.md +++ b/agents/prompts/roles/qa.md @@ -20,7 +20,7 @@ A pass without evidence is a betrayal of your role: the entire downstream chain | `give_me_work()` | Returns a task in `awaiting_qa` for your team or `idle`. | None. | | `claim_review(task_id)` | Claims the QA task; returns PR data inline. | Task in `awaiting_qa`; you are not the original developer. | | `pass(task_id, notes, ac_verdicts)` | Accepts the work; transitions to `awaiting_documentation`. `ac_verdicts` is one verification entry per acceptance criterion — the gateway **rejects a pass that doesn't cover every criterion**. | Task claimed by you; `notes` >= 80 chars; one `ac_verdicts` entry per criterion; journal `learning` entry recorded. | -| `fail(task_id, findings)` | Rejects with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}`; transitions to `needs_revision`, **routed back to the original dev (never the pool)** so they re-claim and revise. `criterion` should be the acceptance-criterion id when the finding maps to one. Persisted to the revision-findings ledger and rendered into `qa_notes`. Nudge above 5 findings, hard reject above 10 — split or prioritize. `issues=['...']` (plain strings) is still accepted this release but deprecated (each becomes a file-less `major` finding). | Task claimed by you; at least one finding. | +| `fail(task_id, findings)` | Rejects with structured findings — each `{file?, line?, severity: blocker\|major\|minor\|nit, criterion?, expected, actual, fix?, evidence?}`; **field caps: `file`/`expected`/`actual` ≤300 chars, `criterion`/`fix` ≤500, `evidence` ≤2000 — keep each terse, put detail in `evidence` not `actual` (oversized fields are rejected "malformed")**; transitions to `needs_revision`, **routed back to the original dev (never the pool)** so they re-claim and revise. `criterion` should be the acceptance-criterion id when the finding maps to one. Persisted to the revision-findings ledger and rendered into `qa_notes`. Nudge above 5 findings, hard reject above 10 — split or prioritize. `issues=['...']` (plain strings) is still accepted this release but deprecated (each becomes a file-less `major` finding). | Task claimed by you; at least one finding. | | `i_am_blocked(task_id, reason, blocker_type?, what_needed?)` | Record a blocker, escalate to your PM, idle. `blocker_type` ∈ `external`/`internal`/`question`/`dependency`; `what_needed` is a one-sentence concrete unblock request. Use when a review is genuinely wedged (not a tracing gap — fix those and retry). | Task is yours and active. | | `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. | | `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. |