Skip to content

Let CoS tasks cap review rounds per reviewer via slashdo's ~max=<n> suffix#3146

Merged
atomantic merged 3 commits into
mainfrom
claim/issue-3123
Jul 27, 2026
Merged

Let CoS tasks cap review rounds per reviewer via slashdo's ~max=<n> suffix#3146
atomantic merged 3 commits into
mainfrom
claim/issue-3123

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

slashdo v3.25.0 (the pinned submodule at lib/slashdo) added a per-entry ~max=<n> iteration cap on --review-with tokens — the only way to move the otherwise-hardcoded 3-round caps on the local agent reviewers (codex/claude/agy/grok) and ollama. PortOS emitted the flag but had no way to express a cap, so including a slow local model in a Review Loop chain always cost up to three full review → fix → re-review cycles.

This adds a reviewerMaxRounds token-keyed map ({ ollama: 1, '@flaky-bot': 0 }) alongside the existing optionalReviewers (~opt) plumbing it mirrors:

  • server/lib/cosValidation.jsnormalizeReviewerMaxRounds / resolveReviewerMaxRounds next to their optionalReviewers siblings, plus a shared normalizeReviewerToken so the ~opt set and the cap map can't disagree about a reviewer's identity. markOptional becomes markSuffixes, emitting ~opt then ~max=<n> in slashdo's canonical order from both buildReviewersCsv and buildReviewWithArgs. A lone default copilot carrying a cap now forces the flag on, the same exemption ~opt already had.
  • Schemas — the field lands in createCosTaskSchema, codeReviewSettingsSchema (.strict()), slashdoTaskSchema, and the task-metadata sanitizer, keeping the "explicitly empty overrides the default" semantics optionalReviewers has.
  • Threading — every optionalReviewers call site: codeReview.js (pickCodeReviewDefaults + resolveReviewLoopOptions), agentPromptBuilder.js (all three resolution blocks, both completion-section builders, the slashdo-prune path, and the review-loop follow-up), agentWorktreeCleanup.js (cleanupAgentWorktree + spawnReviewLoopFollowUp), cosTaskGenerator.js (both claim paths + the play-button CSV), cosTaskRoutes.js, prWatcher.js, cosTaskStore.js.
  • UI — a compact numeric cap field on every ReviewerPicker chip (keyed reviewers and @username alike) beside the ~opt badge. Since the picker is shared, the control lands in TaskAddForm, the schedule GlobalConfigControls, CodeReviewDefaultsPanel, and SlashDoRunDrawer at once. Removing a reviewer prunes its cap entry, as withoutToken already does for optionalReviewers.

Absent ≠ 0. slashdo reads ~max=0 as "loop until clean" while a missing key keeps its built-in default of 3 — the opposite meanings. So an entry with no usable cap is dropped rather than coerced to 0, clearing the input deletes the key instead of writing 0, and the builders use a Map whose .get() distinguishes the two.

The review-loop follow-up prompt drives its loop in prose (it isn't slashdo parsing a suffix), so it also spells the caps out — the equiv flag string alone would document intent without binding the agent. 0 renders there as "loop until clean", never as a zero-round budget.

Also fixes a pre-existing drop found while threading: cosTaskStore's addTask never persisted optionalReviewers, though createCosTaskSchema validated it — so the task form's ~opt badges silently fell back to the Code Review Defaults on every task. Without this, the new sibling field would have been the only one of the pair that worked.

Test plan

  • cd server && npm test — the six touched suites pass (lib/validation.test.js, services/codeReview.test.js, services/agentPromptBuilder.test.js, services/cosTaskGenerator.test.js, services/cleanupAgentWorktree.test.js, routes/cos.test.js), covering: normalization (unknown tokens / non-integers / negatives / over-ceiling dropped, an explicit 0 kept, __proto__ key rejected), emission ordering (~opt before ~max), ~max=0 round-tripping distinctly from no cap, @username caps keyed by the @-form token, the lone-default-copilot flag-forcing, task-over-default precedence including an explicitly empty map, follow-up metadata threading, and the follow-up prompt's prose caps.
  • cd client && npm test — 469 files / 5187 tests pass. New ReviewerPicker cases cover the blank-vs-0 render, setting a cap on both chip kinds, clearing deleting the key rather than writing 0, clamping out-of-range input, cap pruning on reviewer/username removal, and ~opt staying independent of the cap.
  • The full server suite has 58 pre-existing failures from Postgres being unavailable in this environment; main shows the same set. The only delta is lib/dataRoot.test.js + services/installState.test.js, which fail identically at origin/main inside this worktree (they assert on the checkout path) and touch nothing in this diff.
  • npx eslint clean on all seven touched client files.

Closes #3123

…3123)

slashdo v3.25.0 added a per-entry `~max=<n>` iteration cap on `--review-with`
tokens, which is the only way to move the otherwise-hardcoded 3-round caps on the
local agent reviewers (codex/claude/agy/grok) and ollama. PortOS emitted the flag
but had no way to express a cap, so including a slow local model in a Review Loop
chain always cost up to three full review -> fix -> re-review cycles.

Adds a `reviewerMaxRounds` token-keyed map alongside the existing
`optionalReviewers` (`~opt`) plumbing: a normalizer/resolver pair in
cosValidation.js, both `createCosTaskSchema` and `codeReviewSettingsSchema`, the
task-metadata sanitizer, every `optionalReviewers` call site, and a numeric cap
control on each ReviewerPicker chip (so it lands in the task-add form, the
schedule config, Code Review Defaults, and the /do:* run drawer at once).

Absent and `0` are deliberately distinct: slashdo reads `~max=0` as "loop until
clean" while a missing key keeps its built-in default, so an entry with no usable
cap is dropped rather than coerced to 0, and clearing the input deletes the key.
`markOptional` becomes `markSuffixes`, emitting `~opt` then `~max=<n>` in
slashdo's canonical order.

The review-loop follow-up prompt drives its loop in prose rather than by handing
slashdo a flag string, so it also spells the caps out — the `equiv` flag string
alone would document intent without binding the agent.

Also persists `optionalReviewers` in cosTaskStore's addTask, which
createCosTaskSchema already validated but the store silently dropped, so the task
form's `~opt` badges never survived to task metadata.
@atomantic
atomantic merged commit e4c5f67 into main Jul 27, 2026
6 checks passed
@atomantic
atomantic deleted the claim/issue-3123 branch July 27, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let CoS tasks cap review rounds per reviewer via slashdo's ~max=<n> suffix

1 participant