Skip to content

Commit e202579

Browse files
committed
Clear sticky salvage hard-block after parallel sibling succeeds
Two concurrent identical-brief task calls can both admit. If one salvages and the other succeeds, recordOutcome kept the hard-block lastSalvage sticky through the success, so the failing sibling's sticky block later refused a brief that already produced a good report in the same wave. A successful complete now always clears lastSalvage, since the success itself proves the brief is re-dispatchable.
1 parent 722a228 commit e202579

3 files changed

Lines changed: 24 additions & 19 deletions

File tree

src/subagent/brief-dispatch.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,16 +178,11 @@ export function createBriefDispatchLedger(): BriefDispatchLedger {
178178
return;
179179
}
180180
if (salvage === null) {
181-
// Successful complete resets the same-brief retry budget. Hard-block
182-
// lastSalvage is sticky for the session and must not be cleared by a
183-
// concurrent twin that finishes after thrash was already recorded.
184-
if (existing.lastSalvage !== undefined && isHardBlockSalvage(existing.lastSalvage)) {
185-
byFingerprint.set(fingerprint, {
186-
dispatchCount: existing.dispatchCount,
187-
lastSalvage: existing.lastSalvage,
188-
});
189-
return;
190-
}
181+
// CL-6710: a successful complete clears the sticky hard-block too.
182+
// Two concurrent identical-brief dispatches can both admit; if one
183+
// salvages and the other succeeds, the success proves the brief is
184+
// re-dispatchable, so it must not leave the sibling's hard-block
185+
// standing for the rest of the session.
191186
byFingerprint.set(fingerprint, { dispatchCount: 0 });
192187
return;
193188
}

src/subagent/index.test.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2183,7 +2183,7 @@ describe("brief re-dispatch ledger (CL-4343 / CL-5203)", () => {
21832183
expect(second.dispatchCount).toBe(2);
21842184
});
21852185

2186-
test("successful complete resets retry budget; hard-block is sticky", () => {
2186+
test("successful complete resets retry budget and clears soft salvage", () => {
21872187
const ledger = createBriefDispatchLedger();
21882188
const fp = fingerprintTaskBrief({ prompt: "ok job" });
21892189
expect(ledger.admit(fp).ok).toBe(true);
@@ -2195,13 +2195,24 @@ describe("brief re-dispatch ledger (CL-4343 / CL-5203)", () => {
21952195
expect(afterSuccess.ok).toBe(true);
21962196
if (!afterSuccess.ok) throw new Error("expected admit");
21972197
expect(afterSuccess.dispatchCount).toBe(1);
2198+
});
2199+
2200+
test("CL-6710: a parallel sibling success clears a hard-block salvage on the same fingerprint", () => {
2201+
const ledger = createBriefDispatchLedger();
2202+
const fp = fingerprintTaskBrief({ prompt: "parallel identical brief" });
2203+
2204+
// Two concurrent identical-brief dispatches both admit before either finishes.
2205+
expect(ledger.admit(fp).ok).toBe(true);
2206+
expect(ledger.admit(fp).ok).toBe(true);
21982207

2199-
// Hard-block salvage is sticky for the session — success on a concurrent twin must not clear it.
2200-
const stickyFp = fingerprintTaskBrief({ prompt: "no-progress sticky" });
2201-
ledger.admit(stickyFp);
2202-
ledger.recordOutcome(stickyFp, "no-progress");
2203-
ledger.recordOutcome(stickyFp, null);
2204-
expect(ledger.admit(stickyFp).ok).toBe(false);
2208+
// One sibling salvages (hard-block class)...
2209+
ledger.recordOutcome(fp, "no-progress");
2210+
// ...but the other sibling succeeds in the same wave.
2211+
ledger.recordOutcome(fp, null);
2212+
2213+
// The brief already produced a good report this wave — it must stay
2214+
// re-dispatchable, not stuck behind the losing sibling's hard-block.
2215+
expect(ledger.admit(fp).ok).toBe(true);
22052216
});
22062217

22072218
test("release undoes admit when run never produces a body", () => {

src/subagent/stop-policy.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,7 @@ const FORCED_STOP_SUMMARIES: Record<ForcedStopReason, string> = {
452452
deadline: "Stopped: wall-clock deadline reached before finishing.",
453453
stalled:
454454
"Stopped after a long silence with no tool activity. The parent can re-dispatch or check the background work directly.",
455-
repetition:
456-
"Stopped: degenerate repetition in streamed output (same window looping mid-turn).",
455+
repetition: "Stopped: degenerate repetition in streamed output (same window looping mid-turn).",
457456
"incomplete-report": "Stopped: worker narrated instead of writing a report envelope.",
458457
"turn-budget": "Turn budget reached before finishing.",
459458
};

0 commit comments

Comments
 (0)