Skip to content

feat(loop): merge-poll Done-edge fallback + parallel drives (max_concurrent>1) - #6

Merged
mabry1985 merged 1 commit into
mainfrom
feat/merge-poll-and-parallel-drives
Jun 13, 2026
Merged

mabry1985 merged 1 commit into
mainfrom
feat/merge-poll-and-parallel-drives

Conversation

@mabry1985

Copy link
Copy Markdown
Member

What

Two autonomy improvements to the orchestration loop — the genuine feature gaps left after the test/cleanup PRs.

1. Merge-poll fallback for the Done edge

The merge webhook (/webhook/pr) stays the primary path, but many deployments — local agents, NAT'd, tailnet-only — have no public URL GitHub can post to, so a merged feature would sit in in_review forever. The loop now polls each in_review PR via gh pr view … --json state and runs the same idempotent record_merge for any that merged.

  • On by default; rate-limited to merge_poll_interval_s (60s); only probes in_review PRs → cheap.
  • worktree.pr_is_merged() — the probe (a gh failure returns False, so the next poll just retries; never raises into the loop).
  • worktree.reap_feature_worktree() — a shared id → worktree/branch reap; the webhook reap in api.py now uses it too (DRY).

2. Lift the max_concurrent cap

The loop drove exactly one feature at a time (the config knob existed but was ignored). It now drives up to max_concurrent features concurrently, each in its own worktree — the puller tops up to the cap each tick and re-checks promptly while busy. Per-feature in-flight worktrees are tracked (self._inflight) so shutdown cancels every drive and reaps every orphaned worktree. Default stays 1 (serial) — fully backward compatible.

Config (manifest + README updated)

max_concurrent: 1          # >1 builds features in parallel (each its own worktree)
merge_poll: true           # poll merged PRs as a fallback to the webhook Done edge
merge_poll_interval_s: 60  # how often the loop polls in_review PRs

Version → 0.3.0 (manifest + pyproject, kept in lockstep).

Tests

+11 (72 total), all green: pr_is_merged (merged/open/closed/gh-fail), reap_feature_worktree, _spawn_ready concurrency cap + claim accounting, _poll_merges (merged-only + reap), poll rate-limit + merge_poll: false, and the _active → _inflight tracking. ruff check . + ruff format --check . clean.

🤖 Generated with Claude Code

…urrent>1)

Two autonomy improvements to the orchestration loop, both off-by-default-safe:

1. Merge-poll fallback for the single Done edge. The merge webhook stays the
   primary path, but many deployments (local / NAT / tailnet-only agents) have no
   public URL GitHub can post to, so a merged feature would sit in_review forever.
   The loop now polls each in_review PR via `gh pr view ... --json state` and runs
   the same idempotent record_merge for any that merged. On by default; rate-limited
   to merge_poll_interval_s (60s); only probes in_review PRs, so it's cheap.
   - worktree.pr_is_merged() — the probe (a gh failure → False, next poll retries).
   - worktree.reap_feature_worktree() — shared id→worktree/branch reap; the webhook
     reap in api.py now uses it too (DRY).

2. Lift the max_concurrent cap. The loop drove exactly one feature at a time; it now
   drives up to max_concurrent concurrently, each in its own worktree (the puller
   tops up to the cap each tick, re-checking promptly while busy). Per-feature
   in-flight worktrees are tracked (self._inflight) so shutdown cancels every drive
   and reaps every orphaned worktree. Default stays 1 (serial) — fully backward
   compatible.

Config: max_concurrent (now honoured; floors at 1), merge_poll (default true),
merge_poll_interval_s (default 60). Manifest + README updated; version → 0.3.0.

Tests: +11 (72 total) — pr_is_merged, reap_feature_worktree, _spawn_ready cap +
claim accounting, _poll_merges (merged-only + reap), poll rate-limit + off paths,
and the _active→_inflight tracking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@protoquinn

protoquinn Bot commented Jun 13, 2026

Copy link
Copy Markdown

👀 Quinn is reviewing — verdict (PASS / WARN / FAIL) + findings to follow.

@mabry1985
mabry1985 merged commit 6d31192 into main Jun 13, 2026
1 check passed
@mabry1985
mabry1985 deleted the feat/merge-poll-and-parallel-drives branch June 13, 2026 04:52

@protoquinn protoquinn Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA Audit — PR #6 | feat(loop): merge-poll Done-edge fallback + parallel drives (max_concurrent>1)

VERDICT: WARN


CI Status

  • test: queued ⏳

Diff Review

  • Clean DRY refactor: api.py webhook reap delegates to shared worktree.reap_feature_worktree()
  • Concurrency model (_active → _drives set + _inflight dict) is well-structured; add_done_callback(self._drives.discard) is an elegant auto-clean pattern
  • stop() properly cancels all drives, gathers with return_exceptions=True, then reaps every inflight worktree — no orphan risk
  • Config parsing is defensive: max(1, int(...)), bool(merge_poll), adaptive sleep (idle=interval / busy=3s)
  • _spawn_ready() is sync (correct — claim_next_ready is not async) and tops up to cap cleanly

Observations

  • LOW: clawpatch structural review unavailable — repo not in project registry; 547-line diff reviewed via diff_summary only
  • LOW: diff truncated at ~200/547 lines — _maybe_poll_merges() body, worktree.pr_is_merged(), worktree.reap_feature_worktree(), and all 11 new tests are not visible to this review pass
  • LOW: CI still queued — tests unverified; formal PASS/FAIL deferred to a re-review once CI is terminal

— Quinn, QA Engineer

@protoquinn

protoquinn Bot commented Jun 13, 2026

Copy link
Copy Markdown

Submitted COMMENT review on protoLabsAI/projectBoard-plugin#6.

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.

1 participant