Skip to content

feat(demo): parallel five-system subagent fan-out in activity panel - #3

Merged
GautamTalksDev merged 2 commits into
mainfrom
feat/multi-system-fanout
Aug 30, 2026
Merged

feat(demo): parallel five-system subagent fan-out in activity panel#3
GautamTalksDev merged 2 commits into
mainfrom
feat/multi-system-fanout

Conversation

@GautamTalksDev

Copy link
Copy Markdown
Owner

Makes subagent orchestration visible in the demo. Five systems (Google Workspace,
GitHub, Slack, AWS, Notion) fan out in parallel with per-system status and live
grant counts, staggered deterministically. Ada recording regenerated; 7 cards, CI
trap and unattributed grants unchanged. Replays offline with zero API calls.

Co-authored-by: Cursor <cursoragent@cursor.com>
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Show parallel five-system scan fan-out in demo activity

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Runs five fixture inventory subagents concurrently with deterministic recording-only completion
 staggering.
• Streams queued, scanning, reconciling, and live grant-count states into the activity panel.
• Regenerates Ada recording and validates bounded cards, full fan-out, and offline replay.
Diagram

sequenceDiagram
  participant UI as Activity Panel
  participant Bus as SSE Bus
  participant Runner as Scan Runner
  participant AWS as AWS
  participant Notion as Notion
  participant Slack as Slack
  participant GitHub as GitHub
  participant Google as Google Workspace
  UI->>Bus: Subscribe to scan
  Runner->>Bus: Queue five systems
  par AWS inventory
    Runner->>AWS: Scan grants
    AWS-->>Bus: Live counts
  and Notion inventory
    Runner->>Notion: Scan grants
    Notion-->>Bus: Live counts
  and Slack inventory
    Runner->>Slack: Scan grants
    Slack-->>Bus: Live counts
  and GitHub inventory
    Runner->>GitHub: Scan grants
    GitHub-->>Bus: Live counts
  and Google inventory
    Runner->>Google: Scan grants
    Google-->>Bus: Live counts
  end
  Runner->>Bus: Reconcile then finish
  Bus-->>UI: Render lifecycle
Loading
High-Level Assessment

Promise.all is appropriate for the fixed five-system demo fan-out, while collecting results by system preserves deterministic downstream grant ordering. A concurrency pool would add complexity without practical benefit at this bounded scale; recording-only staggering keeps production fixture scans fast while producing a legible replay.

Files changed (10) +625 / -420

Enhancement (7) +251 / -230
client.tsSubscribe to queued subagent progress events +4/-13

Subscribe to queued subagent progress events

• Adds 'subagent.queued' to the scan SSE event subscription so the UI can display all systems before inventory begins. Remaining edits only normalize formatting.

apps/web/src/api/client.ts

types.tsExpand subagent lifecycle status types +1/-1

Expand subagent lifecycle status types

• Replaces the binary running/done state with queued, scanning, reconciling, and done states required by the activity panel.

apps/web/src/api/types.ts

AgentActivity.tsxRender detailed subagent phases and live counts +18/-18

Render detailed subagent phases and live counts

• Displays queued, scanning, reconciling, and done labels with phase-specific colors. Grant totals now include a clear 'found' suffix.

apps/web/src/components/AgentActivity.tsx

useScanSession.tsReduce queued and reconciliation lifecycle events +59/-46

Reduce queued and reconciliation lifecycle events

• Creates queued subagents, advances progress events into scanning, marks every subagent reconciling during sandbox work, and completes them afterward. This keeps activity state synchronized with the expanded server event lifecycle.

apps/web/src/hooks/useScanSession.ts

scan.tsReport fixture grants incrementally +11/-16

Report fixture grants incrementally

• Adds an optional 'onGrant' inventory callback and invokes it after each fixture grant is collected. The scan runner uses this callback to publish live per-system counts.

packages/server/src/agent/scan.ts

progress.tsDefine queued subagent progress events +9/-8

Define queued subagent progress events

• Extends the server progress-event union with a typed 'subagent.queued' event containing system identity and display metadata.

packages/server/src/api/progress.ts

scan-runner.tsRun fixture inventories concurrently with visible progress +149/-128

Run fixture inventories concurrently with visible progress

• Queues every system, executes inventories through 'Promise.all', emits incremental grant counts, and merges results back in stable system order. Record mode applies deterministic per-system delays, while replay respects bounded recorded timing gaps for visible offline animation.

packages/server/src/services/scan-runner.ts

Tests (1) +15 / -6
recording.integration.test.tsVerify complete staggered fan-out and replay parity +15/-6

Verify complete staggered fan-out and replay parity

• Asserts recordings contain all five systems, exactly five queued and completed events, staggered completion timestamps, and no more than eight cards. Existing record-to-replay card-count parity remains covered.

packages/server/src/recording/recording.integration.test.ts

Other (2) +359 / -184
ada-lovelace.jsonRegenerate Ada recording with staggered five-system fan-out +336/-173

Regenerate Ada recording with staggered five-system fan-out

• Refreshes the offline recording with five queued systems, parallel starts, incremental per-grant progress, staggered completions, and updated reconciliation output. The fixture retains seven approval cards while reflecting the latest deterministic scan results.

fixtures/recordings/ada-lovelace.json

record-scan.tsSupport embedded PGlite recording generation +23/-11

Support embedded PGlite recording generation

• Uses the embedded test database for demo, PGlite, or database-less runs and retains PostgreSQL when explicitly configured. Database cleanup is abstracted so recording and offline replay work in either environment.

scripts/record-scan.ts

@qodo-code-review

qodo-code-review Bot commented Aug 30, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Cancel siblings on cost cap ✓ Resolved 🐞 Bug ☼ Reliability
Description
When one parallel subagent throws CostCapExceededError, Promise.all rejects immediately, but
already-started sibling inventories are neither cancelled nor awaited before the outer handler
persists and publishes scan.cost_capped. Those siblings can continue connector and inventory work,
mutate scan state or recorder data, and emit progress or completion events after the scan has
entered a terminal state.
Code

packages/server/src/services/scan-runner.ts[R270-271]

+  const grantsBySystem = new Map<string, string[]>();
+  await Promise.all(
Evidence
The changed fan-out launches all system tasks concurrently with Promise.all and rethrows a
cost-cap error from an individual task, causing the caller to terminalize the scan immediately
without a cancellation or completion barrier. Although inventorySystem supports an AbortSignal,
it is invoked without that signal, so successful siblings can continue after their awaits, update
grantsBySystem, append recorder interactions, and publish progress or completion events that the
scan bus buffers and emits.

packages/server/src/services/scan-runner.ts[270-317]
packages/server/src/services/scan-runner.ts[322-365]
packages/server/src/services/scan-runner.ts[157-194]
packages/server/src/costs/ledger.ts[79-105]
packages/server/src/services/scan-runner.ts[271-272]
packages/server/src/services/scan-runner.ts[311-365]
packages/server/src/agent/scan.ts[84-108]
packages/server/src/api/progress.ts[155-162]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A cost-cap rejection from one parallel inventory task exits `Promise.all` while sibling inventories continue running, allowing state mutation and subagent progress or completion events after the scan has been marked `cost_capped`.

## Issue Context
The fan-out now starts all system inventories concurrently, and `Promise.all` propagates the first rejection without cancelling or awaiting the remaining promises. `inventorySystem` already accepts an `AbortSignal`; on a fatal cost-cap error, sibling work should be aborted and settlement awaited before control returns to the outer handler that persists and publishes the terminal scan status.

## Fix Focus Areas
- packages/server/src/services/scan-runner.ts[270-395]
- packages/server/src/services/scan-runner.ts[157-194]
- packages/server/src/agent/scan.ts[84-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Duplicate reconciliation grant ownership ✓ Resolved 🐞 Bug ≡ Correctness
Description
The regenerated recording assigns the same six grant IDs to both Ada Lovelace's human cluster and
the new GitHub Actions service-account cluster, so the reconciliation no longer partitions grants by
owner. Because approval-card construction indexes clusters in array order and later duplicate IDs
overwrite earlier attribution, replay presents Ada's grants and cards as owned by the service
account while the recorded reconciliation also claims they belong to Ada.
Code

fixtures/recordings/ada-lovelace.json[R791-794]

+        "grantIds": [
+          "474b8dc484730147e9016bd5176f2e7bd918c0c614a1f58d96f58cda5ddfb344",
+          "f7029468068e6bbca30d6c833c78613e2e856c2e3d85487662e5f28e89848885",
+          "c42b99954ceb07a3d653032e168ed3d7ea390c3544028b4154b2349c4a70f9e6",
Evidence
The recording places the same six grant IDs in Ada's cluster and the newly added service-account
cluster, which also copies Ada's personal identifiers. buildApprovalCards stores one attribution
entry per grant ID in a map while iterating clusters in order, so the later service-account entries
overwrite the human entries, and replay uses this recorded reconciliation to rebuild persisted
cards; the reconciliation test further enforces that clustered and unknown grants account for every
input grant exactly once.

fixtures/recordings/ada-lovelace.json[717-726]
fixtures/recordings/ada-lovelace.json[759-800]
packages/core/src/approval-build.ts[36-50]
packages/core/src/identity/reconcile.test.ts[96-105]
fixtures/recordings/ada-lovelace.json[686-726]
fixtures/recordings/ada-lovelace.json[760-800]
packages/server/src/services/scan-runner.ts[689-711]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The regenerated Ada recording assigns the same six grants to both Ada Lovelace and the newly added GitHub Actions service-account cluster. This violates the reconciliation partition invariant and causes the later service-account cluster to replace Ada's attribution during replay.

## Issue Context
A grant must belong to exactly one reconciliation cluster or the unknown bucket. The human cluster retains all six IDs, while the service-account cluster repeats those IDs and copies Ada's personal identifiers; because card attribution is built by iterating clusters and mapping each grant ID, the later service-account entries win. Fix the reconciliation output so the CI service account receives only its intended grant, regenerate the Ada recording, and add an exclusivity assertion.

## Fix Focus Areas
- fixtures/recordings/ada-lovelace.json[683-800]
- fixtures/recordings/ada-lovelace.json[717-726]
- fixtures/recordings/ada-lovelace.json[791-800]
- packages/core/src/approval-build.ts[36-50]
- packages/core/src/identity/reconcile.test.ts[96-105]
- packages/server/src/recording/recording.integration.test.ts[70-85]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Failure status depends on timing ✓ Resolved 🐞 Bug ☼ Reliability
Description
Parallel connector catches append to failedSystems in completion order, but the all-failed path
uses element zero to select both the reported connector and HTTP status. Identical failures can
therefore produce either 429 or 401 depending on which subagent happens to fail first.
Code

packages/server/src/services/scan-runner.ts[R367-370]

+        failedSystems.push({
+          systemId: system.id,
+          error: classified.message,
+          errorKind: classified.kind,
Evidence
The new Promise.all tasks concurrently push failures into one array. After all settle, the code
reads failedSystems[0] and returns 429 only if that scheduling-selected failure is a rate limit;
otherwise it assigns 401.

packages/server/src/services/scan-runner.ts[271-272]
packages/server/src/services/scan-runner.ts[364-371]
packages/server/src/services/scan-runner.ts[398-402]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Concurrent completion order controls the all-connectors-failed error message and status code.

## Issue Context
Collect failures keyed by system and derive aggregate status using explicit deterministic precedence, rather than the first push into a shared array.

## Fix Focus Areas
- packages/server/src/services/scan-runner.ts[248-252]
- packages/server/src/services/scan-runner.ts[364-371]
- packages/server/src/services/scan-runner.ts[398-402]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Failed subagents appear reconciling ✓ Resolved 🐞 Bug ≡ Correctness
Description
On reconcile.started, the reducer changes every subagent to reconciling, including connectors
previously handled by subagent.failed. Partial scans therefore show failed systems as actively
reconciling and later as done even though they contributed no inventory.
Code

apps/web/src/hooks/useScanSession.ts[R280-284]

+          subagents: Object.fromEntries(
+            Object.entries(activity.subagents).map(([systemId, subagent]) => [
+              systemId,
+              { ...subagent, status: "reconciling" as const },
+            ]),
Evidence
subagent.failed currently stores the system as done, and the newly added reconciliation mapping
unconditionally rewrites every entry to reconciling. The server intentionally continues to
reconciliation when some systems failed and at least one produced grants, so this path occurs during
supported partial scans.

apps/web/src/hooks/useScanSession.ts[247-284]
apps/web/src/hooks/useScanSession.ts[296-305]
packages/server/src/services/scan-runner.ts[364-395]
packages/server/src/services/scan-runner.ts[398-412]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Reconciliation transitions overwrite failed connector state, making failed systems appear active and then successful.

## Issue Context
Introduce or preserve a distinct failed state and only transition successfully completed subagents into reconciliation.

## Fix Focus Areas
- apps/web/src/api/types.ts[47-53]
- apps/web/src/hooks/useScanSession.ts[247-305]
- apps/web/src/components/AgentActivity.tsx[81-100]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🧠 Deep: This is a bug-dense behavioral change spanning server concurrency, event contracts, replay/recording, cost accounting, persistence, and UI state across many independent edit sites.

Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/web/src/hooks/useScanSession.ts
Comment thread packages/server/src/services/scan-runner.ts Outdated
Comment thread packages/server/src/services/scan-runner.ts Outdated
Comment thread fixtures/recordings/ada-lovelace.json Outdated
Prevent overlapping identity ownership, preserve failed and capped scan state, score cards from reconciled identities, pace replay for live demos, and make held cards explicit in the queue.

Co-authored-by: Cursor <cursoragent@cursor.com>
@GautamTalksDev
GautamTalksDev merged commit 06eb78d into main Aug 30, 2026
1 check passed
@GautamTalksDev
GautamTalksDev deleted the feat/multi-system-fanout branch August 30, 2026 04:42
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