Skip to content

fix(review): bound in-flight conversations across scheduled scans #689

Description

@neubig

Problem

The scheduled GitHub PR reviewer limits new conversations per scan, but does not cap conversations that remain active across scans. On the OSS Agent Canvas VM, scans accumulated enough Docker runtimes to cause repeated container OOM kills, one host-wide OOM, 159 GB of retained runtime data, and eventual loss of guest networking.

Acceptance criteria

  • The reviewer counts its nonterminal dispatched conversations before starting more.
  • A configurable global in-flight limit bounds active review conversations across scheduled scans.
  • Completed, errored, stale, and missing conversations release capacity.
  • Existing per-scan ordering, deduplication, and max-new-per-run behavior remain unchanged.
  • Tests cover capacity across multiple scans and capacity recovery.

OpenHands AI triage

The following comments and acceptance criteria were added by the OpenHands AI agent.

Triage

The scheduled reviewer already bounds how many conversations a single scan starts (ReviewIntake / max_new_per_run, #659), but it does not bound conversations that remain nonterminal across scans. Repeated scans therefore stack Docker runtimes until the OSS Agent Canvas VM exhausts memory. This is bounded scheduling work in the existing reviewer automation, so no further design decision is needed.

Scope is skills/github-pr-reviewer/scripts/worker.py, its rendered config.json, and the agent-driven setup path, reusing the machinery #654/#659 already added: the KV-backed AgentConversationDispatcher (per-subject records under agent-conversation-{conversation_id}), the exact-head check gate, _finish_completed_review, run_repositories, and the agent server's per-conversation execution status. The count is the reviewer's own dispatched conversations across all configured repositories, not a new queue or runtime layer.

Counting is reviewer scheduling behavior, so it stays in the reviewer worker rather than in the shared agent_conversation.py dispatcher that github-issue-to-pr, gitlab-issue-to-mr, and github-issue-triage also import.

Non-goals: no repository-specific policy, VM tuning, or deployment control; no second automation record; no new secret, token scope, or GitHub permission; no change to the review prompt, verdict parsing, maintainer handoff, secret selection, per-scan ordering or deduplication, the trigger-label scan, or the explicit review_requested event path.

Desired Behavior

A scheduled scan determines how many of its dispatched review conversations are still nonterminal before it starts new ones, and starts no more than a configurable global in-flight limit allows. Capacity is released as conversations complete, error, go stale, or disappear, so later scans resume starting reviews. Existing per-scan ordering, deduplication, and max_new_per_run behavior are unchanged.

Acceptance Criteria

  • Before starting any new review conversation, a scheduled scan counts the reviewer's own nonterminal dispatched conversations across all configured repositories (not per repository), and starts only as many new ones as remain below the configured global in-flight limit; when the count is already at or above the limit it starts none.
  • The global in-flight limit is separate from max_new_per_run: both apply to a scheduled scan, max_new_per_run still bounds new starts per scan, and the number started is the smaller of the two remainders.
  • Capacity is released when a conversation is completed, errored, stale, or missing: a conversation whose execution status is terminal (finished, idle, error, or stuck), one whose reviewed head is no longer the pull request's current head, one abandoned past the existing maximum-active-age bound, and one the agent server reports as missing (404) are all not counted, so a later scheduled scan can start again.
  • An unreadable conversation status (a status lookup that raises) does not allow the limit to be exceeded: the scan treats the unknown conversation as occupying capacity and reports it.
  • Reaching the limit does not cut the scan short: the remaining eligible candidates still get their exact-head check evaluation and waiting/blocked dispositions, completed reviews are still reconciled, and maintainer handoff still runs.
  • A dispatch that raises is reported and consumes no capacity, and does not abort the run or the other repositories.
  • Existing behavior is unchanged: eligible-candidate ordering, per-(request event, head SHA) deduplication so a repeated scan creates no second conversation or native review, the trigger-label scan, and the explicit review_requested event path. The global in-flight limit bounds only the scheduled scan's new conversations.
  • The limit is configurable through the reviewer's rendered config.json and the agent-driven setup substitutions alongside max_new_per_run; an unset value applies a default documented in SKILL.md and README.md; a non-positive, non-integer, or boolean value fails the run at config load, matching max_new_per_run validation.
  • No new secret, token scope, or GitHub permission is required, and no secret value appears in run logs, comments, or the PR.
  • Focused automated tests driving the shipped reviewer entrypoint through the catalog-bundle test helper cover: (a) capacity persisting across scans, where a first scan fills the limit and a second scan with those conversations still nonterminal starts none; (b) capacity recovery, where once those conversations are terminal, stale, or missing a later scan starts the next eligible conversation; (c) the limit being shared across two repositories; and (d) config validation rejecting a non-positive or boolean value.
  • SKILL.md and README.md document the global in-flight limit, its config key, its default, and how it composes with max_new_per_run; the catalog entry and bundle versions are bumped and generated catalogs regenerated so python scripts/sync_extensions.py --check and npm run build report no drift.
  • Where the OSS Agent Canvas deployment is reachable, the PR's evidence links the scheduled run's conversations showing the in-flight count was not exceeded; if it is not reachable, the PR states that explicitly instead of claiming live validation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority:highHigh priority for triage against backlogready-for-devScoped for contribution; managed by repository readiness checks.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions