Conversation
…tion Consecutive host user records collapse into one wire user message, so a single boundary can legitimately carry several transition markers. The per-message count guard rejected that shape, killing the turn with a synthetic 400; a failed turn leaves a contentless assistant record that cannot separate the user records around it, so the fault self-amplified. Drop the redundant count guard — the flat checks already pin the ordered token identity across all messages. Scope-check every transition on a boundary, not just the first, and emit the last transition's effort for a merged boundary (the first would apply a stale value).
This was referenced Sep 18, 2026
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.
Fixes the
Multiple internal Fable 5.1 effort markers on one user boundaryvariant of #237. Live failure — four occurrences, each a synthetic HTTP 400 that kills the turn:The last two are on a build carrying #238, which fixed the other variant (
anchor_placement). Separate mechanism, same file.Root cause: two sides disagree about what a boundary is
Markers are attached per host record —
effort-history.ts:391:Markers are validated per wire message —
effort-history.ts:611:consumeInternalMarkersgroups by wire message. Anthropic requires strictly alternating roles, so consecutive host user records collapse into one wire user message and both markers land on a single boundary. The invariant is broken by construction.Why it clusters
Measured from the host DB, not inferred:
parts=0, textparts=0for every errored message id checked. A contentless assistant lowers to nothing, so it cannot separate the user records on either side of it.So each failure widens the next request's merge:
Self-amplifying, which is why every occurrence sits in a run rather than alone.
The change
1. Dropped the per-message count guard. Duplication and ordering are already enforced globally on the flat array: the count check (
:690), the orderedplanDigest(:695-706), and positional token comparison in the trimmed path (:666-675). The per-message check added no protection those lack — it encoded a structural assumption the lowering violates.2. Scope-check every transition on a boundary, not just
[0]. With N>1 permitted, a foreign-scope marker at index 1 previously bypassed the explicit check.3. Emission takes the last transition, not the first —
:724,transitions[0]→transitions.at(-1).Point 3 is the one that makes this safe. A marker means "effort became X at this boundary"; when two records merge, the effort going into that wire message is the last one's. Relaxing the guard without this would turn a loud 400 into a silent wrong-effort request — strictly worse than the crash it replaces.
Verification
Three mutations, re-run independently of the implementer's report, tree restored to clean between each:
> 1throwaccepts a merged boundary whose transitions are a correctly-ordered run and emits the last effort— throwsMultiple internal Fable 5.1 effort markers on one user boundarytransitions[0]effort: "high"received where"max"expected[0]rejects a foreign-scope marker at index 1 of a merged boundary—Expected substring: "…scope mismatch"/Received: "…non-prefix loss"The second is the important one: it proves the test asserts the emitted effort value, not merely that nothing threw. Without that assertion the
[0]→at(-1)fix would be untested and the silent-wrong-value path would ship green.On the third — the explicit scope check is defense-in-depth, not the only line. With it narrowed back to
[0], the foreign-scope marker at index 1 is still caught, by the flat check, asnon-prefix loss. The mutation changes which error fires, not whether it fires. Worth stating plainly rather than overclaiming: the check buys a precise diagnostic, not a closed hole.Gates: core 199/0 · opencode 1898/0 · pi 114/0 · typecheck clean.
Scope
Untouched: the anchor logic (
:623-656; anchors are placed on the current boundary only and re-stripped every request, so merging cannot produce two), the attach site (:362-395), and every flat check. Holes, reordering, mutation, duplication, and scope mismatch all stay fail-closed.One existing test in
index.test.tsasserted the removed guard's message for a duplicated marker. It now includes the anchor so it exercises the flat count check, and expectscorrelation failed: expected 1, found 2. Duplication still fails closed — 400, request never sent.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes the crash when multiple effort markers land on one merged user boundary, so those requests no longer die with a synthetic HTTP 400. Consecutive host user records collapse into one wire message, so a boundary legitimately carries several markers; the old per-message guard rejected that shape. A failed turn leaves a contentless assistant record that cannot separate the user records around it, which is why the fault self-amplified.
Key changes
transitions.at(-1)) so a merged boundary applies the correct effort value instead of a stale one.Duplication, reordering, and scope mismatch still fail closed with a 400 before the request is sent. The existing duplicate-marker test now exercises the flat count check and expects the correlation error message.
Written for commit 72618a7. Summary will update on new commits.