fix(broadcast): close cost-guard gate gap (#24 follow-up — broadcast surface)#58
Merged
Conversation
…surface) PR #57 (#24 fix) gated `sendToInstance` and (transitively via wrapAsSend) `delegate_task` and `request_information`, but `broadcast` runs its own per-target send loop without funnelling through `sendToInstance` — so a limited instance could still be flooded via broadcast with task/query/ update kinds. ts-reviewer flagged this as Out-of-scope Observation 1 on PR #57. This PR closes the gap. Behaviour: per-target skip with structured warning, never short-circuits the whole broadcast. Other targets still receive normally. - broadcast loop: when `ctx.costGuard?.isLimited(target)` is true, push the target onto a new `costLimited` array with the same warning string format used by sendToInstance and continue the loop. - broadcast result gains a `cost_limited: [{ target, warning }]` field alongside `sent_to` and `failed`. Callers see exactly which targets were skipped and why. - BroadcastRequestKind already excludes `"report"` (broadcasts don't carry correlation_id), so no kind-bypass is needed here. - Session-routing edge case (Out-of-scope Observation 2) explicitly deferred per ts-lead — gate keys by `targetName` exactly like the rest of the broadcast loop. Tests cover the operator-mandated three states for broadcast: - (a) mixed → limited targets warned + skipped, others delivered - (b) all limited → every target warned, zero IPC sends - (c) no targets limited → identical to pre-fix, cost_limited empty `npx tsc --noEmit` clean. `npx vitest run` 63 files / 527 tests pass (+3 over post-#57 baseline; no regressions). Closes #24 issue intent (now covers all outbound-dispatch surfaces). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
suzuke
added a commit
that referenced
this pull request
Apr 27, 2026
Sprint 0 outcome: 0 open issues, 0 open PRs. Three PRs shipped (#56 #55 fix; #57 #24 fix; #58 #24 follow-up broadcast surface), four issues closed-with-migration to agend-terminal (#52 #53 #54 #8). Adds: - docs/ts-team-sprint-0-2026-04-27-wrap.md — sprint outcome, fix details, process notes, next-sprint pointer - docs/ts-team-backlog.md — non-blocking gaps surfaced during deprecated-mode maintenance. B-001 = session-routing edge case in cost-guard gate (deferred from PR #57 review) No production code changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR #57 (the #24 fix) gated
sendToInstanceand — transitively viawrapAsSend—delegate_taskandrequest_information. Butbroadcastruns its own per-target send loop without funnelling throughsendToInstance, so a limited instance could still be flooded with broadcasttask/query/updatemessages. ts-reviewer flagged this on PR #57 as Out-of-scope Observation 1. This PR closes the gap so the #24 issue intent ("notify sender on cost-guard limit") covers all outbound-dispatch surfaces an agent can use.Changes
src/outbound-handlers.ts.broadcast(~10 LOC): inside the per-target loop, whenctx.costGuard?.isLimited(target)is true, append the target to a newcostLimitedarray with the same warning string format used in feat(#24): cost-guard pre-check at outbound dispatch #57 (cost-guard: instance '...' has reached its daily cost limit ($X.XX). ...) andcontinueinstead of dispatching. Other targets keep flowing normally — broadcast is never short-circuited as a whole.cost_limited: [{ target, warning }]alongsidesent_to,failed,count. Callers can see exactly which targets were skipped and why.Out of scope (per ts-lead)
targetNamedirectly, not by the resolved host. Deferred per dispatch.request_kind: "report"bypass: not applicable here.BroadcastRequestKind(outbound-schemas.ts) already restricts to"query" | "task" | "update"— broadcasts never carry reports because they have no correlation_id to terminate.Test plan
npx tsc --noEmit— cleannpx vitest run— 63 files / 527 tests pass (+3 over post-feat(#24): cost-guard pre-check at outbound dispatch #57 baseline; no regressions)tests/outbound-cost-guard-precheck.test.ts— three operator-mandated states for broadcast:cost_limitedcontains exactly the limited target with the standard warning string.cost_limitedlists all,sent_toempty,count: 0.cost_limited: [],sent_tomatches all targets).Risks considered
cost_limitedfield. Existing callers readingsent_to/failed/countsee no change. New field default is[], never null. No migration needed.isLimited()is O(1) (Map lookup + arithmetic). Per-target overhead negligible.Files
src/outbound-handlers.ts— broadcast loop gate + result fieldtests/outbound-cost-guard-precheck.test.ts— 3 broadcast tests appended🤖 Generated with Claude Code