Skip to content
This repository was archived by the owner on Jul 16, 2026. It is now read-only.

fix(task-bridge): dedup work() submissions with identical text in 2-min window - #29

Closed
VasiliyRad wants to merge 1 commit into
mainfrom
fix/task-bridge-work-dedup
Closed

VasiliyRad wants to merge 1 commit into
mainfrom
fix/task-bridge-work-dedup

Conversation

@VasiliyRad

Copy link
Copy Markdown

Summary

Bug-by-omission. Upstream sonichi/sutando shipped PR sonichi#820 (fix(task-bridge): dedup work() submissions with identical text within 2-minute window) on 2026-05-18; the merge that pulled the surrounding code into AG2Platform/stando didn't include this branch, leaving voice over-delegation uncontained here.

Symptom: Gemini retrying work() 3-5 times with identical task text in sub-second succession spawns N parallel task files, each runs independently, each ships its own result DM — the "cascade of duplicate result DMs" the upstream commit calls out.

Fix

Mirrors upstream structure:

  • PendingTask grows a taskText field so dedup can match against in-memory state without re-reading task files.
  • DEDUP_WINDOW_MS = 2 * 60 * 1000 module constant.
  • normalizeTask(t) = lowercase + collapse whitespace + trim + cap at 150 chars. Catches the rewording-noise that triggers the over-delegation pattern (Gemini retrying with extra spaces, casing changes, trailing punctuation past the cap).
  • workTool.execute scans _pendingTasks for a normalized-match entry submitted within DEDUP_WINDOW_MS. On hit, returns { status: "duplicate", taskId, message } and skips the task-file write. The message explicitly tells the agent NOT to retry.

Tests

tests/task-bridge-work-dedup.test.ts — 3 integration cases. Invokes the real workTool.execute(...) against the real SUTANDO_WORKSPACE/tasks/ dir; uses the same task-file leak-check harness as tests/task-bridge-format.test.ts so any future regression that writes orphan files is caught.

Case Pins
second identical call within window returns duplicate with first taskId Core regression guard — over-delegation containment.
dedup ignores whitespace and case differences normalizeTask pin — Gemini's retry rewording variants must still collide.
different task texts each produce a fresh taskId No-false-positive — distinct work doesn't collapse.

3/3 pass locally; existing 12 task-bridge tests (*-format, *-isvoice) still pass.

Bug-finding calibration

1 real bug (port-gap) fixed across 3 tests. Total this round: 7 bugs / ~90 tests ≈ 1:13.

Test plan

  • npx tsx --test tests/task-bridge-work-dedup.test.ts — 3/3 pass
  • npx tsx --test tests/task-bridge-format.test.ts tests/task-bridge-isvoice.test.ts — 12/12 pass (no regressions)
  • npm run typecheck — clean
  • Manual: simulate voice over-delegation by calling workTool.execute rapidly with identical text; verify only ONE task file lands in tasks/

🤖 Generated with Claude Code

…in window

Port upstream PR sonichi#820 (sonichi/sutando) into this fork. AG2Platform/stando
never received the fix; the merge that brought the surrounding code did
not include the dedup branch, leaving voice over-delegation uncontained:
Gemini's tendency to retry-call work() with the same task text 3-5 times
in sub-second succession was spawning that many parallel task files,
each running independently and shipping its own result DM (the "cascade
of duplicate result DMs" symptom called out in sonichi#820's commit message).

Fix structure mirrors upstream:

- `PendingTask` grows a `taskText: string` field so the in-memory map
  can detect duplicates without re-reading task files from disk.
- `DEDUP_WINDOW_MS = 2 * 60 * 1000` module constant.
- `normalizeTask(t)` = lowercase + collapse whitespace + trim + cap at
  150 chars. Catches the rewording-noise that triggers the over-
  delegation pattern (Gemini retrying with extra spaces, different
  casing, trailing punctuation past the cap).
- `workTool.execute` scans `_pendingTasks` for a normalized-match
  entry submitted within DEDUP_WINDOW_MS. On hit: return
  `{ status: "duplicate", taskId, message }` instead of writing a new
  task file. The message explicitly tells the agent NOT to retry.

Tests: `tests/task-bridge-work-dedup.test.ts` (3 cases) — true
integration via `workTool.execute(...)` against the real
`SUTANDO_WORKSPACE/tasks/` dir. Uses the same task-file leak-check
harness as `tests/task-bridge-format.test.ts` so a future bug that
writes orphan files is caught.

  - Second identical call → duplicate response with first's taskId.
  - Whitespace + case variation still dedupes (normalizer pin).
  - Distinct task texts each produce fresh taskIds (no false positive).

Local test run: 3/3 pass + 12/12 existing task-bridge tests
(`tests/task-bridge-format.test.ts`, `tests/task-bridge-isvoice.test.ts`)
still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@bassilkhilo-ag2 bassilkhilo-ag2 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.

Clean port. A few observations:

Logic: The dedup scan is O(n) over _pendingTasks. In practice this map stays small (tasks expire within their timeout window, typically ≤10 min), so no concern. One micro-nit: normalizeTask(pending.taskText) is recomputed on every loop iteration. Could cache as pending.normalizedText at insertion time, but the savings are negligible here.

Return value: The message field explicitly tells the model not to retry — good anti-footgun. The caller at workTool.execute returns { status: 'duplicate', taskId, message } while a fresh task returns just { taskId, ... } (no status field). Make sure all callers that pattern-match on status === 'duplicate' are covered — the test checks this correctly.

Test isolation gap (non-blocking): afterEach cleans up task files but doesn't reset _pendingTasks (module-level). Each test's first submission stays in the map for subsequent tests. The three cases here use distinct texts so no collision, but future test authors who reuse a prior case's text will see unexpected dedup. Worth adding _pendingTasks.clear() in afterEach — requires exporting the map or a _resetForTest() helper from task-bridge.ts.

Tests: Core regression case, normalization variant, and non-collision case all covered. Leak harness borrowed from the existing format-test pattern — good consistency.

@rickchen007

Copy link
Copy Markdown

Code review: LGTM. Clean port of upstream PR sonichi#820 — normalizeTask collapses the rewording-noise that drives voice over-delegation, the dedup scan + status: 'duplicate' short-circuit is correct, and the 3 integration tests use the real workTool.execute with the leak-check harness.

Not merged — PR is CONFLICTING with main after today's 9 merges. Needs a rebase onto main; once it's mergeable I'll merge it (no code changes needed).

@VasiliyRad

Copy link
Copy Markdown
Author

Closing — current 's src/task-bridge.ts already has the dedup logic (DEDUP_WINDOW_MS, normalizeTask, and the dedup check in workTool.execute). The two implementations are functionally equivalent; main's version landed first via a parallel route. This PR is now redundant.

@VasiliyRad VasiliyRad closed this May 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants