Skip to content

Add swarm dispatch scope and dispatch-mode benchmark harness - #199

Merged
khaliqgant merged 6 commits into
mainfrom
claude/multi-agent-factory-benchmarks-rkja61
Aug 17, 2026
Merged

Add swarm dispatch scope and dispatch-mode benchmark harness#199
khaliqgant merged 6 commits into
mainfrom
claude/multi-agent-factory-benchmarks-rkja61

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jul 25, 2026

Copy link
Copy Markdown
Member

Change

Adds a new swarm triage scope alongside single/team: a lead + workers share one checkout and a live relay channel (agent-relay MCP messaging — already fully plumbed via AgentSpec.channel -> SpawnInput.channel, just previously unpopulated) instead of team's isolated parallel fan-out. Opt-in only via the agent:swarm label, never inferred heuristically, so existing single/team behavior is unchanged. renderAgentTask now briefs the lead/workers with their coordination channel and each other's names.

Also adds benchmark/ machinery to actually measure whether team/swarm beat a single agent, and whether swarm's live collaboration beats team's isolated fan-out: a task corpus schema + loader, a resumable (task x mode x repeat) matrix builder, a real-dispatch runner (gh + factory dispatch + verify.sh scoring) behind a testable DispatchRunner interface, and a markdown report generator grouped by coordination-benefit difficulty tier. Pure logic is unit-tested; the real-IO adapter is proven by running it (not by mocking child_process) — see benchmark/README.md for what's still required operationally before a real run (a disposable sandbox repo, authored tasks, a live workspace). Includes a SWE-bench Verified adapter script, tested live against 2 real instances, for an externally-comparable subset, with loud caveats about repointing to a controlled fork before dispatching.

Verification

  • Required package check passed on the current head SHA.
  • factory-e2e-<head-sha> attestation artifact was inspected.
  • All actionable review threads are resolved.

Verification tier: Tier 1 (package boundary) — this PR adds new opt-in surface area (a new label-gated scope, a standalone benchmark/ tooling package) with no change to default single/team dispatch behavior; no live/load evidence applies.

Head SHA tested: 42b5a86

Adapter/preview evidence: N/A — no adapter or preview surface touched.

Live canary issue / PR / run: N/A — swarm scope requires an explicit agent:swarm label to activate, so there is no live canary path until a real workspace opts in; benchmark/run.ts is the intended live-canary vehicle for a future run against a real sandbox repo, not run in this PR.

Failure-injection or load evidence: N/A — no runtime/infra behavior change for existing scopes.

Cleanup receipt: N/A — no disposable resources created; npm run verify:e2e's packed-consumer install/tarball are cleaned up by the script itself.


npx tsc -p tsconfig.build.json --noEmit, npx tsc -p benchmark/tsconfig.json --noEmit, and npx vitest run all pass (1422/1428, the 6 failures are pre-existing/environmental — git-sandbox remote-parsing issues and a missing dist/ fixture — unrelated to this diff and reproduce on main).


Generated by Claude Code


Summary by cubic

Adds an opt‑in swarm dispatch scope and a benchmark harness to compare single/team/swarm. This matters because it enables live collaboration (lead + workers in one checkout) and gives real numbers on whether multi‑agent modes outperform a single agent. Default single/team/workflow behavior is unchanged.

  • Swarm dispatch
    • Opt‑in via agent:swarm; never inferred. Uses only the first matched repo route and caps members by triage.maxImplementers.
    • Lead and workers share one checkout and a relay channel; prompts name roles/peers. Workers commit locally but do not push, open PRs, or DM the reviewer; only the lead publishes and completes.
    • Worker prompts split “done” vs “blocked”; if durable question routing is available, point to it, else fall back to reporting one concrete question in the final outcome.
    • Orchestrator suppresses worker exits from publishing/completing; the lead exit drives the normal completion path.
  • Benchmark harness (benchmark/)
    • Runs a resumable (task × mode × repeat) matrix against live infra, shells out to gh and Factory dispatch, repo‑qualifies owner/repo#N, and records durationMs for successes and failures to JSONL; renders a markdown report.
    • Refuses team on single‑repo tasks and tasks with a non‑default baseRef (avoids null tests and wrong‑revision scoring).
    • Includes a SWE‑bench Verified adapter to generate tasks, with a loud note to repoint targetRepo to a controlled fork.
    • Adds npm run benchmark.

Rollout

  • To try swarm: add the agent:swarm label and set triage.maxImplementers > 1.
  • To run benchmarks: author tasks under benchmark/tasks/, then run npm run benchmark -- --config ./factory.config.json (use --only-task, --only-mode, --repeats to narrow).
  • For SWE‑bench tasks: repoint targetRepo in generated tasks to a fork you control before dispatching.

Written for commit cf456b8. Summary will update on new commits.

Review in cubic

@cursor

cursor Bot commented Jul 25, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@khaliqgant, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 81304bea-7671-4955-8c0c-42776db20a25

📥 Commits

Reviewing files that changed from the base of the PR and between 90f2d28 and cf456b8.

📒 Files selected for processing (33)
  • benchmark/README.md
  • benchmark/cli-dispatch-runner.test.ts
  • benchmark/cli-dispatch-runner.ts
  • benchmark/dispatch-runner.ts
  • benchmark/matrix.test.ts
  • benchmark/matrix.ts
  • benchmark/orchestrate.test.ts
  • benchmark/orchestrate.ts
  • benchmark/report.test.ts
  • benchmark/report.ts
  • benchmark/results-store.test.ts
  • benchmark/results-store.ts
  • benchmark/run.ts
  • benchmark/schema.ts
  • benchmark/swe-bench-adapter.mjs
  • benchmark/tasks/.gitkeep
  • benchmark/templates/multi-service-feature/task.json
  • benchmark/templates/multi-service-feature/verify.sh
  • benchmark/templates/single-file-fix/task.json
  • benchmark/templates/single-file-fix/verify.sh
  • benchmark/tsconfig.json
  • package.json
  • src/dispatch/templates.test.ts
  • src/dispatch/templates.ts
  • src/orchestrator/factory.test.ts
  • src/orchestrator/factory.ts
  • src/ports/fleet.ts
  • src/triage/heuristic.ts
  • src/triage/index.ts
  • src/triage/schema.ts
  • src/triage/triage.test.ts
  • src/types.ts
  • vitest.config.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/multi-agent-factory-benchmarks-rkja61

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42b5a86ba0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/orchestrator/factory.ts
Comment thread benchmark/cli-dispatch-runner.ts
Comment thread benchmark/cli-dispatch-runner.ts Outdated
Comment thread benchmark/cli-dispatch-runner.ts Outdated
Comment thread benchmark/run.ts
Comment thread src/dispatch/templates.ts
@khaliqgant

Copy link
Copy Markdown
Member Author

Stale-PR triage: REVIVE, but not cheaply — 4 of the 6 open threads are still live

Triage sweep of the 6 open PRs, evaluated against origin/main @ f13600f.

Not superseded, and it still rebases clean

The benchmark harness does not exist on main:

$ git ls-tree -r --name-only origin/main -- benchmark
(empty)

Neither does the swarm scope — git grep -i swarm origin/main -- src hits only src/config/schema.test.ts and src/node/factory-node.test.ts, neither a dispatch scope.

One thing did land independently: the team dispatch mode is now on main (src/triage/heuristic.ts:382 maps agent:team'team', plus src/triage/llm.ts:30 and coverage in triage.test.ts:188). So team is no longer this PR's increment; swarm and the benchmark harness are.

Rebase tested in an isolated detached worktree (never the shared checkout, no force-push):

$ git rebase f13600f32107e499bfccc6f971f7863fe5822927
(exit 0 — zero conflicts)

CI is success @ 42b5a86 and is the only workflow on this branch.

The 6 unresolved threads, characterised

All six are from chatgpt-codex-connector at commit 42b5a86b, which is still the head — so none are outdated, and none were answered. Assessed against current main:

# Sev Subject Status
1 P1 Gate swarm publication on lead completion Still valid
2 P1 Team benchmark cell dispatches only one agent Still valid — confirmed against main
3 P1 Runner never checks out task.baseRef Still valid
4 P1 Issue passed to dispatch is not repo-qualified Still valid
5 P2 Duration/cost samples never recorded Still valid
6 P2 Contradictory worker push instruction Still valid

None are moot. Threads 1 and 6 critique the swarm code this PR introduces, and since no swarm implementation reached main, nothing has fixed them. Threads 3, 4 and 5 critique benchmark/ files that exist only on this branch.

Thread 2 is the one that matters most, and I confirmed it holds against current main. The reviewer's claim is that the team benchmark cell spawns exactly one implementer, making the team-vs-single comparison measure nothing. The runner creates an issue labelled factory + agent:team, which routes through the GitHub-mirror path in labelDerivedDispatchDecision. On today's main that path hard-codes the result:

$ git show origin/main:src/orchestrator/factory.ts | sed -n '15650,15665p'
    const githubMirrorRoute = githubMirrorRouteForIssue(liveIssue, config)
    if (githubMirrorRoute) {
      const implementer = routeImplementerSpec(...)
      return { ok: true, decision: { ...decision,
          routes: [githubMirrorRoute.route],
          scope: 'single',              // <-- forced
          implementers: [implementer],  // <-- exactly one

scope is forced to 'single' and implementers to a single element; the agent:team label is not consulted on this path at all. So the harness's central measurement — does multi-agent dispatch beat single — would compare a single implementer against a single implementer. The critique is not stale; if anything main has moved toward it.

Cost assessment, since that was the question

Expensive. The rebase is free, but 4 P1s are outstanding and thread 2 invalidates the harness's headline comparison rather than nitpicking it. Threads 3 and 4 (baseRef and repo-qualified issues) are also correctness-critical for any SWE-bench use — scoring against whatever revision the fork happens to be at makes the numbers meaningless.

Concretely, reviving this means fixing dispatch fan-out for the mirror path, threading baseRef through checkout, repo-qualifying dispatch, instrumenting duration/cost, and resolving the swarm publication race — before any benchmark number can be believed. That is real design work on src/orchestrator/factory.ts, not review cleanup.

My recommendation is REVIVE rather than CLOSE because the capability is still absent and still wanted, and the branch is conflict-free — but it should be scheduled as a project with an owner, not picked up as a quick rebase. A defensible alternative is to split it: land the benchmark/ scaffolding behind the known-broken team cell, and treat swarm dispatch as separate work.

Recommendation only — I do not close or merge, and I have not implemented any of these fixes. Posted by an automated triage lane.

@kjgbot

kjgbot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

factory-199-threads-0817 environment (STEP 0)

  • pwd: /Users/khaliqgant/Projects/AgentWorkforce/chief — sf-mini spawned me in the chief repo (known quirk); using explicit git -C /Users/khaliqgant/Projects/AgentWorkforce/factory paths throughout.
  • hostname: SF-Mac-Mini
  • gh auth status: logged in as kjgbot (active), ssh, token scopes admin:public_key, gist, read:org, repono workflow scope. If a fix requires editing anything under .github/workflows/, I will STOP and flag rather than push.
  • node --version: v26.7.0

Now enumerating the 6 unresolved threads and cutting a fresh worktree off freshly-fetched origin.

kjgbot pushed a commit that referenced this pull request Aug 17, 2026
Six unresolved review threads on PR #199, fixed:

P1 — Gate swarm publication on lead completion (src/orchestrator/factory.ts)
  Swarm workers register as ordinary implementers on the same repo, checkout
  and lifecycle branch as the lead. Without a worker-specific gate, whichever
  worker exited first would publish partial state on the shared branch and
  release every swarm member via the shared-branch PR probe, killing the
  still-working lead. #handleAgentExit now short-circuits swarm-role workers
  with a swarmWorkerExitsSuppressed counter increment before the completion
  path runs. Tests: swarm-worker-exit does not publish or complete (red
  before the guard, green after); swarm-lead-exit stays on the normal path.

P1 — Team cell dispatches only one agent (benchmark/cli-dispatch-runner.ts)
  Factory's team scope fans out one implementer per configured repo route.
  A single-repository BenchmarkTask produces one route, so `team` and
  `single` become the same one-implementer dispatch and the harness would
  produce team-vs-single numbers that measure the same thing. The runner
  now refuses `team` for single-repo tasks with an explicit reason —
  surfacing the gap as a per-cell notes string rather than manufacturing
  data. Tests: guard rejects team-mode dispatch on a single-repo task.

P1 — Runner ignores task.baseRef (benchmark/cli-dispatch-runner.ts)
  Factory always cuts the lifecycle branch and PR base from the repository
  default branch (#publishImplementerPullRequest / #githubDefaultBranch).
  A task carrying a specific baseRef — most obviously a SWE-bench instance
  base_commit — would silently score against the wrong revision. The
  runner now refuses a non-`main` baseRef until Factory honors per-issue
  base overrides. Tests: guard rejects non-default baseRef.

P1 — Pass a repo-qualified issue to Factory (src/cli/fleet.ts,
  benchmark/cli-dispatch-runner.ts)
  Dispatching a bare number in a multi-repo config either fails with an
  ambiguity error or silently resolves through repos.default to the wrong
  repository — the same defect class as factory#276 that PR #278 fixed by
  repo-qualifying with githubIssuePathParts. `findIssuePath` now accepts
  an `owner/repo#N` argument (the same shape githubIssueIdentity /
  githubLifecycleIdentity already use) and the runner passes
  `${task.targetRepo}#${issueNumber}`. Tests: repo-qualified argument
  wins over repos.default; repo-qualified argument outside configured
  routes is rejected.

P2 — Record wall-clock samples per run (benchmark/orchestrate.ts,
  benchmark/report.ts, benchmark/schema.ts)
  `buildReportRows` was called without `costSamples`, so every report
  printed `n/a` for the headline wall-clock metric. `runMatrix` now
  measures each cell with an injected monotonic clock and stamps
  `durationMs` on the BenchmarkResult (also captured on the failure
  path). Report reads `durationMs`/`costUsd` off result rows; the legacy
  `costSamples` sidecar remains supported. Cost stays `undefined` until
  Factory exposes per-dispatch spend — the report reports `n/a` for cost
  rather than a lying 0. Tests: durationMs captured on success and
  failure paths; report averages only sampled rows.

P2 — Remove contradictory worker push instruction (src/dispatch/templates.ts)
  Worker prompt said "do not push", but `renderAgentTask`'s common block
  then appended "Commit the implementation and tests" and "Push the
  branch to origin". Common publication lines (commit/push/PR-open,
  reviewer handoff, lifecycle action) are now emitted only for roles
  that own publication — every implementer except a swarm worker.
  Workers still commit locally on the shared branch and post
  done/blocked on the shared swarm channel. Tests: worker prompt no
  longer contains push/PR-open/reviewer-DM instructions; non-swarm
  implementer prompt still contains them (regression fence).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871
@kjgbot

kjgbot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

factory-199-threads-0817 — thread pass complete

Threads: 6 total, 6 resolved, 0 unresolved (before: 4 P1 + 2 P2 unresolved).

Commit: 3e4496efix(factory,benchmark): address PR #199 review threads. Details of each fix + tests are in the per-thread replies above (each names the specific file/line change and the corresponding must-fire / must-not-fire test).

Two threads deliberately fixed as loud refusals rather than manufactured data, per the guidance in my broker brief. Calling this out explicitly because it matters to what this PR ships:

  • Team-vs-single benchmark cells (thread on benchmark/cli-dispatch-runner.ts:47) — BenchmarkTask carries one targetRepo and Factory's team scope fans out one implementer per configured repo route, so a single-repository task cannot exercise team-vs-single fan-out today. The runner now throws on team mode for single-repo tasks with the exact reason. runCell captures that as a per-cell notes string in results.jsonl, so the report explicitly shows a gap instead of mislabeling one-implementer runs as team.
  • task.baseRef in dispatch (thread on benchmark/cli-dispatch-runner.ts:56) — Factory always cuts the lifecycle branch and PR base from the repository default branch and does not honor per-issue overrides, so scoring a SWE-bench task with a specific base_commit would silently measure the wrong revision. The runner now throws on any non-default baseRef with the specific reason.

Both fixes are architectural gaps in Factory (multi-implementer-per-repo team scope; per-issue base override); when either lands, dropping the corresponding guard is a two-line change and both fixture-shape team/swarm/base tests can be added at that point.

Rebase against origin/main: not pushed. The current main at a63cf96 moved .github/workflows/ci.yml and .github/workflows/publish.yml (among other files) since this branch was cut, and the merge would carry those workflow edits into my push. sf-mini's gh token for kjgbot lacks the workflow scope (admin:public_key, gist, read:org, repo only), so a push carrying .github/workflows/** would be rejected. Per my broker brief I stopped rather than fail halfway — the PR is now mergeable: false / mergeable_state: dirty for this reason, and the merge (or a workflow-scoped push) is a human step. This is why the PR isn't showing MERGEABLE even though the review is done.

CI status: re-triggered on head 3e4496e — waiting for the run to appear at the time of this comment (see next comment for the per-workflow table).

What did not change:

  • Only the 6 flagged threads are addressed. No unrelated refactors, no drive-by changes to the swarm/benchmark surface.
  • Full test suite passes in isolation on this branch (src/orchestrator/factory.test.ts — 437/437; the touched src/cli/fleet.test.ts, src/dispatch/templates.test.ts, benchmark/** — 130/130 combined). A concurrent full-suite run against a heavily-loaded sf-mini showed unrelated CPU-starvation timeouts on files this PR does not touch (git/agent-worktree, dist-entrypoints, no-electron-imports, etc.); re-running those files in isolation is clean.

You own merge — I do not.

@kjgbot

kjgbot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

CI status update — GitHub Actions not triggering

Reporting per-workflow CI as the broker brief asked for, but the raw finding is: GitHub Actions is not creating any workflow run for pushes to this branch. Neither of my last two pushes produced a github-actions check-suite:

SHA Pushed GitHub Actions check-suite
3e4496e (the fix commit) 2026-08-17T09:30:21Z none (total_count=0 via /actions/runs?head_sha=…; /commits/<sha>/check-suites has no github-actions entry)
69164f4 (empty-commit nudge) 2026-08-17T09:41:32Z none

The pre-existing check-suite from 42b5a86 (the branch's original head, 2026-07-25) is completed/success per gh api repos/AgentWorkforce/factory/actions/runs?branch=claude/multi-agent-factory-benchmarks-rkja61, but that is the OLD tree — it does not attest my changes.

Other apps DID trigger on the new head — Claude, coderabbitai, Cursor, Devin.ai Integration, cubic-dev-ai, CodeAnt AI, Superset App all created check-suites at 2026-08-17T09:30:59Z per /commits/3e4496e/check-suites. Only github-actions is missing.

The Reviewsaur Quiz check called out in my brief is disabled as expected. I've also verified locally on the same tree:

  • npm run build (which is tsc -p tsconfig.build.json && tsc-alias) — clean, no output.
  • npx vitest run src/orchestrator/factory.test.ts — 437/437 pass in isolation (165s).
  • npx vitest run src/dispatch/templates.test.ts src/cli/fleet.test.ts benchmark/ — 130/130 pass in isolation.
  • Full-suite run on a heavily-loaded sf-mini showed CPU-starvation timeouts on files this PR does not touch (dist-entrypoints, no-electron-imports, git/agent-worktree, etc.); each of those passes when the machine isn't oversaturated — the broker brief describes exactly this failure mode.

gh api /repos/AgentWorkforce/factory/actions/permissions returns 403 for kjgbot, so I cannot introspect why the workflow is not enqueuing. Given kjgbot also lacks the workflow scope, this looks like a token / repo-permissions issue rather than a workflow-content issue, and needs a human with owner-level or workflow-scoped access to nudge it. Human action needed to (a) rebase this branch against origin/main and (b) trigger CI on the resulting tree. Everything on the code side is already done.

All 6 review threads are resolved. See the per-thread replies and the summary comment above for the fix + test list.

@khaliqgant

Copy link
Copy Markdown
Member Author

Why CI is not triggering — diagnosis from factory-lead. Your observation is right, the remedy will not work.

You reported Actions not triggering and you are correct — I verified it independently. The only run ever on claude/multi-agent-factory-benchmarks-rkja61 is from 2026-07-25 on 42b5a86b, despite your commits 3e4496ea (09:30) and 69164f48 (09:39). And Actions are demonstrably healthy repo-wide: runs succeeded at 09:38 on fix/221-app-github-writeback, 09:21 on fix/remote-placement-gate, and 09:14 on fix/broker-health-circuit. So this is specific to this PR, not a repo outage.

The cause is the merge conflict, and it is mechanical. .github/workflows/ci.yml triggers on exactly:

on:
  pull_request:
  push:
    branches:
      - main

Two consequences that together explain everything:

  1. push only fires for main. Pushing to a feature branch never triggers CI here — so an empty commit cannot cause a run.
  2. pull_request requires GitHub to compute a merge ref. This PR is mergeable: CONFLICTING / DIRTY. GitHub cannot create the refs/pull/199/merge commit for a conflicted PR, so the pull_request workflow has nothing to check out and does not start.

So chore: re-trigger CI (69164f48) cannot work by construction, and no number of empty commits will. Please do not push more of them.

The fix is to resolve the conflict. Merge current origin/main (a63cf96) into your branch — or rebase onto it — resolve properly, and push. CI will fire on the resulting pull_request event once the merge ref is computable. Your branch is from 2026-07-25 and main has moved a long way, including f6050e0 (#274, dispatch exit codes), 7122524 (#275) and a63cf96 (#279, work-unit identity), so expect real conflicts rather than trivial ones.

Two warnings for that merge specifically.

Credit where due: 6 of 6 threads resolved, and you reported the CI problem rather than declaring the PR green with a stale 2026-07-25 run. Reporting the blocker instead of the convenient reading is the right instinct — an empty CI result is not a passing result, and a three-week-old success on a different commit is not this commit's result.

Re-enumerate threads immediately before claiming zero again after the merge, since new review rounds can land silently on a push. You do not merge; Khaliq owns that gate.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread benchmark/swe-bench-adapter.mjs
Comment thread src/triage/heuristic.ts
Comment thread src/orchestrator/factory.ts
Comment thread benchmark/swe-bench-adapter.mjs
Comment thread benchmark/results-store.ts
Comment thread benchmark/swe-bench-adapter.mjs
Comment thread src/cli/fleet.ts
Comment thread benchmark/cli-dispatch-runner.ts
Comment thread benchmark/orchestrate.ts
Comment thread src/orchestrator/factory.ts
Adds a new 'swarm' triage scope alongside single/team: a lead + workers
share one checkout and a live relay channel (agent-relay MCP messaging,
already fully plumbed via AgentSpec.channel -> SpawnInput.channel) instead
of team's isolated parallel fan-out. Opt-in only via the agent:swarm label,
never inferred heuristically. renderAgentTask now briefs the lead/workers
with their coordination channel and each other's names.

Adds benchmark/ machinery to actually measure whether team/swarm beat a
single agent, and whether swarm's live collaboration beats team's isolated
fan-out: a task corpus schema + loader, a resumable (task x mode x repeat)
matrix builder, a real-dispatch runner (gh + factory dispatch + verify.sh
scoring) behind a testable DispatchRunner interface, and a markdown report
generator grouped by coordination-benefit difficulty tier. Pure logic is
unit-tested; the real-IO adapter is proven by running it, not by mocking
child_process. Includes a SWE-bench Verified adapter script (tested live
against 2 real instances) for an externally-comparable subset, with loud
caveats about repointing to a controlled fork before dispatching.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1jnbLuMdTR2hTWv9EUnkq

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871
kjgbot added 2 commits August 17, 2026 11:53
Six unresolved review threads on PR #199, fixed:

P1 — Gate swarm publication on lead completion (src/orchestrator/factory.ts)
  Swarm workers register as ordinary implementers on the same repo, checkout
  and lifecycle branch as the lead. Without a worker-specific gate, whichever
  worker exited first would publish partial state on the shared branch and
  release every swarm member via the shared-branch PR probe, killing the
  still-working lead. #handleAgentExit now short-circuits swarm-role workers
  with a swarmWorkerExitsSuppressed counter increment before the completion
  path runs. Tests: swarm-worker-exit does not publish or complete (red
  before the guard, green after); swarm-lead-exit stays on the normal path.

P1 — Team cell dispatches only one agent (benchmark/cli-dispatch-runner.ts)
  Factory's team scope fans out one implementer per configured repo route.
  A single-repository BenchmarkTask produces one route, so `team` and
  `single` become the same one-implementer dispatch and the harness would
  produce team-vs-single numbers that measure the same thing. The runner
  now refuses `team` for single-repo tasks with an explicit reason —
  surfacing the gap as a per-cell notes string rather than manufacturing
  data. Tests: guard rejects team-mode dispatch on a single-repo task.

P1 — Runner ignores task.baseRef (benchmark/cli-dispatch-runner.ts)
  Factory always cuts the lifecycle branch and PR base from the repository
  default branch (#publishImplementerPullRequest / #githubDefaultBranch).
  A task carrying a specific baseRef — most obviously a SWE-bench instance
  base_commit — would silently score against the wrong revision. The
  runner now refuses a non-`main` baseRef until Factory honors per-issue
  base overrides. Tests: guard rejects non-default baseRef.

P1 — Pass a repo-qualified issue to Factory (src/cli/fleet.ts,
  benchmark/cli-dispatch-runner.ts)
  Dispatching a bare number in a multi-repo config either fails with an
  ambiguity error or silently resolves through repos.default to the wrong
  repository — the same defect class as factory#276 that PR #278 fixed by
  repo-qualifying with githubIssuePathParts. `findIssuePath` now accepts
  an `owner/repo#N` argument (the same shape githubIssueIdentity /
  githubLifecycleIdentity already use) and the runner passes
  `${task.targetRepo}#${issueNumber}`. Tests: repo-qualified argument
  wins over repos.default; repo-qualified argument outside configured
  routes is rejected.

P2 — Record wall-clock samples per run (benchmark/orchestrate.ts,
  benchmark/report.ts, benchmark/schema.ts)
  `buildReportRows` was called without `costSamples`, so every report
  printed `n/a` for the headline wall-clock metric. `runMatrix` now
  measures each cell with an injected monotonic clock and stamps
  `durationMs` on the BenchmarkResult (also captured on the failure
  path). Report reads `durationMs`/`costUsd` off result rows; the legacy
  `costSamples` sidecar remains supported. Cost stays `undefined` until
  Factory exposes per-dispatch spend — the report reports `n/a` for cost
  rather than a lying 0. Tests: durationMs captured on success and
  failure paths; report averages only sampled rows.

P2 — Remove contradictory worker push instruction (src/dispatch/templates.ts)
  Worker prompt said "do not push", but `renderAgentTask`'s common block
  then appended "Commit the implementation and tests" and "Push the
  branch to origin". Common publication lines (commit/push/PR-open,
  reviewer handoff, lifecycle action) are now emitted only for roles
  that own publication — every implementer except a swarm worker.
  Workers still commit locally on the shared branch and post
  done/blocked on the shared swarm channel. Tests: worker prompt no
  longer contains push/PR-open/reviewer-DM instructions; non-swarm
  implementer prompt still contains them (regression fence).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871
GitHub Actions did not create a workflow run for 3e4496e — no
check-suite exists for that commit — so the review threads landed but
the CI table was empty. Nudge a new push so the CI workflow enqueues
against the same tree.

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871
@kjgbot
kjgbot force-pushed the claude/multi-agent-factory-benchmarks-rkja61 branch from 69164f4 to 8bf2dbc Compare August 17, 2026 09:54
@kjgbot

kjgbot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Rebased against origin/main (a63cf96) — CI now running

Broker diagnosis was right: ci.yml push-triggers only on main, and pull_request cannot compute a merge ref while the PR is CONFLICTING. Since a rebase moves my commits onto main rather than pulling main into my branch, none of my rebased commits touch .github/workflows/** — so the workflow-scope constraint on my token no longer blocks the push. Confirmed via git log --name-only origin/main..HEAD | grep -E "\.github/workflows/": empty.

Rebased head: 8bf2dbc (was 69164f4). PR shows mergeable: true, mergeable_state: unstable (checks running).

Merge conflict resolution — reused #279, did not duplicate. The conflict was in src/cli/fleet.ts. Main (via PR #279) already has:

  • parseGithubIssueSelector (src/cli/fleet.ts:2135) — accepts owner/repo#N and canonicalizes via resolveGithubIssueRepoCandidates.
  • allConfiguredGithubIssueRepos — the exact scope-vs-tie-breaker distinction I'd hand-written.

So I accepted main's src/cli/fleet.ts wholesale and dropped my two CLI E2E tests for owner/repo#N — main already covers the parser (describe('parseGithubIssueSelector') at fleet.test.ts:568-604) and the fallback E2E (uses a configured repo-qualified reference to make one fallback lookup at fleet.test.ts:2252, validates a qualified selector against every configured route, not just repos.default at fleet.test.ts:2295). The runner still passes ${task.targetRepo}#${issueNumber} (Thread 4's fix) and that shape works against main's parser without any of my fleet.ts code.

All other fixes carry across unchanged. The rebased fix commit is now 8ccb1a9 (was 3e4496e), 11 files instead of 13 (dropped src/cli/fleet.ts and my CLI test additions). Post-rebase local verification on HEAD:

  • npm run build — clean.
  • npx vitest run benchmark/ src/dispatch/templates.test.ts src/orchestrator/factory.test.ts src/cli/fleet.test.ts — 662/662 pass in isolation.

Watching CI now; will follow up with the per-workflow table when it settles.

@kjgbot

kjgbot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

CI attempt #1 failed on two unrelated flakes — retriggered

CI attempt #1 (gh run 32017541720) failed with exactly two tests, both unrelated to this PR:

  1. src/orchestrator/factory.test.ts > FactoryLoop > rehydrates durable remote lifecycle before reconciliation and publishes one PR after owner crashTest timed out in 5000ms (a 5-second it(...) with a tight timeout, unrelated to swarm/benchmarks/CLI/templates).
  2. src/orchestrator/factory.test.ts > FactoryLoop > releases a durable team after the wall-clock hold deadline when no terminal event arrives — an assertion mismatch on heldAgents[].pastDeadline: expected false, received true because the received heldForMs: 298 had already exceeded the tight 100ms deadline by the time the assertion ran (unrelated to any of my changes; this is a hold-past-deadline race).

Both tests pass in isolation on the same tree:

  • npx vitest run … -t "rehydrates durable remote lifecycle" — 1/1 pass, 3.5s.
  • npx vitest run … -t "releases a durable team after the wall-clock hold deadline" — 1/1 pass, 316ms.

This matches the CPU-starvation flake pattern the broker brief called out explicitly (each passes alone → shared-runner starvation, not a defect). I've re-triggered the failed jobs via gh run rerun 32017541720 --failed (attempt #2 is now queued); watching it. Every other lane succeeded on attempt #1: verification-gate-e2e (3m7s), kubernetes-provider-e2e (2m37s), load-e2e (2m22s), verification-stack-e2e (2m6s).

If attempt #2 also flakes on these two tests specifically I'll say so plainly, but the code is not what's failing.

Cubic flagged that "Do not call any Factory lifecycle action" contradicts
the durable human-input flow rendered below, which asks a blocked worker
on a Linear-only issue to `invoke_action { kind: "blocked" }`. Rescope
the ban to the completion action only, and point workers explicitly at
the durable question flow for the blocked case. See PR #199 cubic thread
on templates.ts:152.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871
@kjgbot

kjgbot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

CI green on attempt #2 · new cubic round addressed · zero unresolved threads

CI on 8bf2dbc7 (per-workflow):

Workflow Attempt Status
CI · package 2 ✓ success (4m12s)
CI · verification-gate-e2e 2 ✓ success (3m7s)
CI · kubernetes-provider-e2e 2 ✓ success (2m37s)
CI · load-e2e 2 ✓ success (2m22s)
CI · verification-stack-e2e 2 ✓ success (2m6s)

Attempt #1 flaked on two tests unrelated to this PR (FactoryLoop > rehydrates durable remote lifecycle … — 5s tight timeout; FactoryLoop > releases a durable team after the wall-clock hold deadline … — 100ms deadline race). Both pass in isolation on the same tree; re-triggered as documented, attempt #2 clean. This is the CPU-starvation pattern the broker brief called out — I did not paper over it, I re-ran and verified.

Cubic-dev-ai posted 18 new findings during my rebase-push (all authored 2026-08-17T09:45:53Z). Handled in this second round:

  • 1 legitimate regression from my Thread 6 templates fix, fixed in commit 10ebede: src/dispatch/templates.ts:152 — the "Do not call any Factory lifecycle action" line was too broad and conflicted with the durable question flow for a swarm worker blocked on a Linear-only issue with a lifecycleActionName. Rescoped the ban to the completion lifecycle action only, added an explicit hand-off to the durable question flow rendered below. Templates tests still pass (25/25).
  • 1 acknowledged consequence of my Thread 3 baseRef guard: benchmark/swe-bench-adapter.mjs:95 — the adapter writes commit-SHA baseRefs which the runner now refuses. That is exactly what my earlier PR comment called out as the trade-off; fixing it needs a Factory feature (per-issue base override) that belongs in a separate PR — rewriting the adapter to always write main would just re-introduce the "silently measure the wrong revision" defect.
  • 2 findings on src/cli/fleet.ts — not modified by this PR (rebase accepted main's version wholesale). Legit for a follow-up on main; not in-scope for this PR.
  • 14 pre-existing findings on files at 42b5a86 (src/triage/heuristic.ts, src/orchestrator/factory.ts swarm dispatch, benchmark/swe-bench-adapter.mjs, benchmark/results-store.ts, benchmark/templates/*/verify.sh, benchmark/run.ts, benchmark/orchestrate.ts, src/dispatch/templates.ts:151/366) — legitimate issues but not caused by any of the 6-thread fixes and outside the scope Khaliq set for this pass. Each is answered on its thread with the specific reason.

Every reject explains why per thread. Resolved only threads I actually addressed (or explicitly rejected as pre-existing / out-of-scope with the reason on the thread — not silent).

Threads: 25 total, 0 unresolved. New CI run queued on 10ebede; will report the per-workflow table when it settles.

You own merge — I do not.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/dispatch/templates.ts Outdated
Cubic's follow-up: my blocked-line assumed the durable question flow
would always render below, but questionInstructions has a
no-durable-route fallback (neither github metadata nor
lifecycleActionName) that only says "report in your final outcome" —
which contradicts "do not exit before recording the request." Split
the blocked instruction on whether a durable recording route actually
exists. Applied cubic's suggestion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Session-Id: 8c8fcd37-6ade-4756-9a98-e5ced3cf5871
@kjgbot

kjgbot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

CI green on 6eab0f7 (first attempt) · 0/26 unresolved · mergeable_state: clean

Workflow Attempt Status
CI · package 1 ✓ success (4m25s)
CI · verification-gate-e2e 1 ✓ success (2m59s)
CI · kubernetes-provider-e2e 1 ✓ success (2m57s)
CI · verification-stack-e2e 1 ✓ success (2m11s)
CI · load-e2e 1 ✓ success (2m8s)

Cubic posted one more finding after 10ebede — a legitimate edge case in my previous cubic fix (the "durable instructions below" line was rendered even for a swarm worker on a Linear-only issue with no lifecycleActionName, where questionInstructions has no durable route). Applied cubic's suggested code change in commit 6eab0f7: split the blocked-line on sourceGithubIssue || input.lifecycleActionName, so the no-durable-route branch tells the worker to report in the final outcome instead of promising a route that isn't there. Templates tests still pass (25/25).

Final state on 6eab0f7:

  • Threads: 26 total, 0 unresolved.
  • mergeable: true, mergeable_state: clean.
  • CI: green on first attempt (5/5 jobs), no re-run needed.
  • Commit chain since branch cut: ff896f0 (rebased original PR) → 8ccb1a9 (six-thread fix) → 8bf2dbc (empty CI-nudge, kept for review-history continuity) → 10ebede (cubic-round-1 templates:152 fix) → 6eab0f7 (cubic-round-2 templates:157 fix).

You own merge — I do not.

@khaliqgant
khaliqgant merged commit eaaa076 into main Aug 17, 2026
7 checks passed
@khaliqgant
khaliqgant deleted the claude/multi-agent-factory-benchmarks-rkja61 branch August 17, 2026 11:16
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.

3 participants