removeModifier deletes the whole pill — correct — but its result names only the id it was handed. When the agent created several adjacent regions as distinct edits, deleting one silently destroys the others and the model is told one thing was removed.
Found by the workbench: wizard-enhance/dsl.effect.honest reported an unexpected failure on a live run (label transcript-mirror, deepseek-v4-flash), on addZooms#call_01_qsaEbSKZtQLjMHJtd52k2206. Not model variance — deterministic, and reduced to a minimal repro below.
What happens
addZooms → 3 zooms: 0–10, 10–28.5, 28.5–54.3 (three ids, three distinct intents)
removeModifier { id: <the first> }
→ result: {"removed":"zoom_affb…","kind":"zoom"}
→ document: 0 zooms left
The three spans touch exactly, so under the merge rule they render as one stripe — one pill. removeRegion drops the pill, which is what its docstring says it must: "EVERY kind deletes the whole pill, i.e. every row that renders as one stripe with id under the merge rule."
That part is right and should not change. It is the UI's delete-key recipe, deliberately shared so there is one rule in one place; the docstring records that trims used to be the exception and that being the exception was the bug.
Minimal repro
Contiguity is the discriminator:
| zooms created |
removeModifier on one id |
result says |
0–10, 10–20, 20–30 (touching) |
3 removed |
one id |
0–5, 15–20, 30–35 (separated) |
1 removed |
one id — correct |
let doc = recordingWithSilences({ durationSec: 62, silences: [[10, 12.5]] });
const r = executeAgentTool(doc, "addZooms", JSON.stringify({ regions: [
{ startSec: 0, endSec: 10, depth: 3 },
{ startSec: 10, endSec: 20, depth: 3 },
{ startSec: 20, endSec: 30, depth: 3 },
]}), {});
doc = r.document!; // 3 zooms
const ids = JSON.parse(r.resultJson).applied.map((a: any) => a.zoomId);
doc = executeAgentTool(doc, "removeModifier", JSON.stringify({ id: ids[0] }), {}).document!;
// doc.zoomRanges.length === 0, and the result named one id
Why it matters on the agent path specifically
A human clicking a stripe sees what disappears. The model does not: it gets {"removed": "<one id>"}, keeps believing its other two zooms exist, and any later reasoning about them is built on a document that no longer matches what it was told.
This is the same class of defect that landingReport fixed for addZoom / setZoom, and that workbench/README.md records: report what you OBTAINED, not what you were ASKED. The single-call add*/set* path was corrected to report clamped and fragments rather than the requested span. removeModifier still reports the request.
The UI is not affected — useTimeline's removeRegion does not consume this payload.
Suggested shape, not prescribed
Report the regions actually dropped, e.g. removedIds: string[] beside removed, and say so in the tool description so the model knows a delete can take neighbours with it. The interesting design question, which this issue does not settle: should the agent be able to delete a single fragment of a pill at all, or is pill-granularity the right unit for it too? Reporting the truth is worth doing either way, and does not depend on that answer.
Confirming the finding
workbench/scenarios/wizard-enhance.scn.ts / dsl.effect.honest caught this correctly — the oracle compares each mutating call's own resultJson against what the document actually carries, and it was right that the report did not survive. Nothing has been added to expectedFailures: per workbench/README.md § "Le ratchet tourne dans les deux sens", a single observation does not characterise an intermittent check, and here the intermittency is the model's (it has to create adjacent regions and then delete one, which it does on some runs), not the defect's.
Two hypotheses were checked and eliminated before reaching this one, worth recording so nobody re-treads them:
- The batch tools report requested rather than obtained bounds. They do not.
applyBatch replays the single-call path per entry and carries its payload through, so addZooms inherits landingReport unchanged.
diffMatches cannot read the batch envelope. It can — workbench/lib/oracles.ts has an explicit applied branch, with a comment explaining that reading only the envelope would make the check go dark on exactly the calls that write the most.
removeModifierdeletes the whole pill — correct — but its result names only the id it was handed. When the agent created several adjacent regions as distinct edits, deleting one silently destroys the others and the model is told one thing was removed.Found by the workbench:
wizard-enhance/dsl.effect.honestreported an unexpected failure on a live run (labeltranscript-mirror, deepseek-v4-flash), onaddZooms#call_01_qsaEbSKZtQLjMHJtd52k2206. Not model variance — deterministic, and reduced to a minimal repro below.What happens
The three spans touch exactly, so under the merge rule they render as one stripe — one pill.
removeRegiondrops the pill, which is what its docstring says it must: "EVERY kind deletes the whole pill, i.e. every row that renders as one stripe withidunder the merge rule."That part is right and should not change. It is the UI's delete-key recipe, deliberately shared so there is one rule in one place; the docstring records that trims used to be the exception and that being the exception was the bug.
Minimal repro
Contiguity is the discriminator:
removeModifieron one id0–10,10–20,20–30(touching)0–5,15–20,30–35(separated)Why it matters on the agent path specifically
A human clicking a stripe sees what disappears. The model does not: it gets
{"removed": "<one id>"}, keeps believing its other two zooms exist, and any later reasoning about them is built on a document that no longer matches what it was told.This is the same class of defect that
landingReportfixed foraddZoom/setZoom, and thatworkbench/README.mdrecords: report what you OBTAINED, not what you were ASKED. The single-calladd*/set*path was corrected to reportclampedandfragmentsrather than the requested span.removeModifierstill reports the request.The UI is not affected —
useTimeline'sremoveRegiondoes not consume this payload.Suggested shape, not prescribed
Report the regions actually dropped, e.g.
removedIds: string[]besideremoved, and say so in the tool description so the model knows a delete can take neighbours with it. The interesting design question, which this issue does not settle: should the agent be able to delete a single fragment of a pill at all, or is pill-granularity the right unit for it too? Reporting the truth is worth doing either way, and does not depend on that answer.Confirming the finding
workbench/scenarios/wizard-enhance.scn.ts/dsl.effect.honestcaught this correctly — the oracle compares each mutating call's ownresultJsonagainst what the document actually carries, and it was right that the report did not survive. Nothing has been added toexpectedFailures: perworkbench/README.md§ "Le ratchet tourne dans les deux sens", a single observation does not characterise an intermittent check, and here the intermittency is the model's (it has to create adjacent regions and then delete one, which it does on some runs), not the defect's.Two hypotheses were checked and eliminated before reaching this one, worth recording so nobody re-treads them:
applyBatchreplays the single-call path per entry and carries its payload through, soaddZoomsinheritslandingReportunchanged.diffMatchescannot read the batch envelope. It can —workbench/lib/oracles.tshas an explicitappliedbranch, with a comment explaining that reading only the envelope would make the check go dark on exactly the calls that write the most.