Skip to content

fix(orchestrator): don't synthesize the teardown stopped that follows a router-ceded terminal (#11689 residual) - #11812

Merged
lalalune merged 1 commit into
developfrom
fix/rh-0
Jul 3, 2026
Merged

lalalune merged 1 commit into
developfrom
fix/rh-0

Conversation

@NubsCarson

Copy link
Copy Markdown
Member

Problem

Since fd2e343 (#11689), every routed one-shot completion posts a spurious second message to the origin channel: the router posts the real completion, then seconds later swarm synthesis posts <label> stopped. (or a sanitized chunk of the session's raw lastOutput) to the same room, threaded to the user's message.

Mechanism, on current develop:

  1. Every planner-spawned one-shot (TASKS op=createrunPromptAndClose / runPromptViaSmithers, actions/tasks.ts) emits task_complete on success, then in its finally always calls service.stopSessionAcpService.closeSession emits a stopped carrying response = lastOutput (raw agent output), plus a second explicit stopped. This sequence is deterministic — it happens on every routed success, not as a rare race.
  2. fix(orchestrator): router owns origin-routed session completions — stop swarm-synthesis double-posts and suppressed-error leaks (#11634) #11689's router-owned task_complete skip in SwarmCoordinatorService.runSwarmComplete deliberately returns without claiming the synthesizedCompletionSessions dedupe slot (so a later genuine stop on a reused session isn't swallowed), and stopped is deliberately not router-owned (shouldInject in sub-agent-router.ts excludes it).
  3. So the teardown stopped falls through every guard and synthesizes. server-helpers-swarm.ts posts it unconditionally (swarm_synthesis is EPHEMERAL, so the roomId+text delivery dedupe is bypassed) to originRoomId with replyToExternalMessageId — i.e. the user's Discord channel.

The same mechanism leaks a terminal stopped mid-respawn on error paths the router deliberately suppresses (account failover / state-lost): tasks.ts stops the session before the router stamps handedOffToSuccessorSessionId, so the #11720/#11721 handoff skips can't catch it (they help cache staleness, not a not-yet-stamped store).

Pre-#11689 the top-of-function dedupe claim made these teardown stops invisible; the commit converted the #11634 duplicate-completion into a spurious stop-post instead of eliminating the double post. Tests at swarm-coordinator-service.test.ts pinned the wrong behavior as expected (modeled as a rare user-stop race, but the one-shot runners produce the identical sequence deterministically on every routed success).

Fix

Track the cession in memory: when the router-owned skip cedes a task_complete/error, record the session in a new routerCededTerminalSessions set. A subsequent stopped on a ceded session is recognized as teardown plumbing of that same turn and skipped — without claiming the synthesis dedupe slot.

The marker is strictly turn-scoped, so nothing goes silent:

Per-session terminal serialization (maybeFireSwarmComplete) guarantees a same-tick teardown stopped observes the cession recorded by the terminal that preceded it.

Tests

Reproduced first (all three fail on unfixed develop, with the exact spurious payload completionSummary: "build-site stopped." targeted at the origin room):

  • does NOT synthesize the teardown stopped that follows a router-owned task_complete (#11689 residual) — the deterministic one-shot success sequence (task_complete → closeSession stopped w/ raw lastOutput → explicit stopped) produces zero synthesis.
  • does NOT leak a teardown stopped after a router-owned error the router suppresses (failover/state-lost) — no terminal <label> stopped. mid-respawn.
  • suppresses the stopped racing a router-owned terminal on the same session (same-tick teardown) — rewrites the old race test that pinned the wrong behavior.
  • a stopped AFTER the session resumes still synthesizes (cession is per-turn; sessions are reused) — preserves the reuse guarantee the old test at :842 guarded.
  • The two swarm-synthesis posts one message per verify-retry lineage session teardown (stopped) — user sees 3 completions for 1 task #11711 tests now warm the enrichment cache via a non-terminal event so they keep exercising the store-backed handoff re-read layer (previously warmed via a router-owned task_complete, which now also sets the in-memory marker).

Unchanged and still green: genuine user stopped with no preceding terminal posts; router-inactive/no-origin sessions still synthesize; custom-validator completions still post.

Verification

plugins/plugin-agent-orchestrator: bun run test:unit  → 118 files, 1273 tests passed
plugins/plugin-agent-orchestrator: bun run typecheck  → clean
packages/agent server-helpers-swarm.test.ts           → 13 passed

Refs #11689 (fd2e343), #11634, #11711, #11720, #11721.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e85ca4a-24f6-4867-972f-2eed8276befb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rh-0

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.

@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member

Validation pass after rebasing onto current origin/develop (includes #11811):

  • Generated standard keyword artifacts with node packages/shared/scripts/generate-keywords.mjs --target ts for test imports.
  • bunx vitest run plugins/plugin-agent-orchestrator/__tests__/unit/swarm-coordinator-service.test.ts --reporter=dot -> 49 tests passed.
  • bun run --cwd plugins/plugin-agent-orchestrator typecheck -> passed.
  • bunx biome check plugins/plugin-agent-orchestrator/src/services/swarm-coordinator-service.ts plugins/plugin-agent-orchestrator/__tests__/unit/swarm-coordinator-service.test.ts -> passed.
  • bunx vitest run packages/agent/src/api/server-helpers-swarm.test.ts --reporter=dot -> 13 tests passed.
  • bun run --cwd plugins/plugin-agent-orchestrator test:unit -> 118 files / 1281 tests passed.
  • git diff --check origin/develop..HEAD -> passed.

Reviewed the session marker lifecycle: router-ceded task/error terminals mark the current turn, teardown stopped events are suppressed, non-terminal resume clears the marker, and genuine stops with no preceding ceded terminal still synthesize.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants