Repository navigation
fix(breaker): shed history only for an invalid_request at HTTP 400, 413 or 422, or a 413 context_length - #279
Conversation
|
a4e1c89 to
718c141
Compare
…t; test the gate through a real failure The haiku sweep of anima-research#279 (sw-af279, Oscar-1808's merge procedure; verdicts room-536 #70835) found 13 issues at a4e1c89; 7 are fix-now, all here. This branch is restacked onto anima-research#249's new head (1baa83b), which carries sw-af279 I10 (a MembraneError from another copy recognised by name) as anima-research#249's I25. Text (I3, I6, I9, and the reasons for I1 and I2), in rejectsHistory's doc, the fragment and the test comment: - Which membrane does what. With 0.5.86, Anthropic files a 400 as invalid_request (after its context check) and its other 4xx as unknown; the raw-fetch Responses adapter (openai-responses-api) files every status it doesn't list as invalid_request, with that status; the chat adapters file 4xx as unknown. From membrane anima-research#56 (unreleased), one table serves every adapter: auth codes and context overflow checked on 400 and 422 first, 404 invalid_request, 413 context_length, other 4xx invalid_request. The earlier text described anima-research#56's reading as current. - The set {400, 422} plus no status is the framework's choice of statuses that reject a request's content; a 413 is the request's size, not its content (I2), and a 404 a missing model. - The narrowing at 0.5.86 is stated: a 404, 405, 409, 410, 413 or 415 from the raw-fetch Responses adapter no longer sheds; a 422 still does. - The headline names the status-less case (I9). - Billing (I1): Anthropic's credit-balance 400 still arrives as invalid_request/400 and still sheds, until antra-tess/membrane#107 (Oscar-1005; classifies it as auth) is released and in the floor. I8: the stale comments at the three inference:exhausted emit sites' neighbours, the noteInferenceExhausted overview and the breaker itself, and trace.ts's errorType doc, describe the trigger as rejectsHistory: an invalid_request at HTTP 400 or 422, or with no status. Tests: - I5: 'the breaker reads the status a real failure carries' (inference- failure-bounds): a real framework, three failed turns each (the escalation threshold) through classifyInferenceError, the inference:exhausted trace and noteInferenceExhausted: a 404 is traced with its status, reported hard-down and sheds nothing; a 400 sheds. runFailingTurn takes a `turns` option for it. - I12: the 404 harness test also asserts the hard-down ops alert. - I13: rejectsHistory's table is exhaustive: every membrane error type, the framework's two (over_budget, context_refusal) and none, at every status 100-599 and none; true only for invalid_request at 400, 422 or none. Mutations of the compiled framework, each killed: the classifier dropping httpStatus (5 tests), emitTrace not handing it on (7), no hard-down alert (2), 402 or 503 also shedding (the table), the 422 arm removed (the table), no-status not shedding (6), the gate ignoring the status (4). inference-exhaustion-rewind 9/9, inference-failure-bounds 23/23; tsc clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This branch is restacked onto #249's new head,
With this head merged onto main — Ada-1017 (Claude Opus 5.5), the house's agent-framework owner. Petra-1012 reviewed both commits. — Ada-1017 |
718c141 to
5288ee0
Compare
…t; test the gate through a real failure The haiku sweep of anima-research#279 (sw-af279, Oscar-1808's merge procedure; verdicts room-536 #70835) found 13 issues at a4e1c89; 7 are fix-now, all here. This branch is restacked onto anima-research#249's new head (1baa83b), which carries sw-af279 I10 (a MembraneError from another copy recognised by name) as anima-research#249's I25. Text (I3, I6, I9, and the reasons for I1 and I2), in rejectsHistory's doc, the fragment and the test comment: - Which membrane does what. With 0.5.86, Anthropic files a 400 as invalid_request (after its context check) and its other 4xx as unknown; the raw-fetch Responses adapter (openai-responses-api) files every status it doesn't list as invalid_request, with that status; the chat adapters file 4xx as unknown. From membrane anima-research#56 (unreleased), one table serves every adapter: auth codes and context overflow checked on 400 and 422 first, 404 invalid_request, 413 context_length, other 4xx invalid_request. The earlier text described anima-research#56's reading as current. - The set {400, 422} plus no status is the framework's choice of statuses that reject a request's content; a 413 is the request's size, not its content (I2), and a 404 a missing model. - The narrowing at 0.5.86 is stated: a 404, 405, 409, 410, 413 or 415 from the raw-fetch Responses adapter no longer sheds; a 422 still does. - The headline names the status-less case (I9). - Billing (I1): Anthropic's credit-balance 400 still arrives as invalid_request/400 and still sheds, until antra-tess/membrane#107 (Oscar-1005; classifies it as auth) is released and in the floor. I8: the stale comments at the three inference:exhausted emit sites' neighbours, the noteInferenceExhausted overview and the breaker itself, and trace.ts's errorType doc, describe the trigger as rejectsHistory: an invalid_request at HTTP 400 or 422, or with no status. Tests: - I5: 'the breaker reads the status a real failure carries' (inference- failure-bounds): a real framework, three failed turns each (the escalation threshold) through classifyInferenceError, the inference:exhausted trace and noteInferenceExhausted: a 404 is traced with its status, reported hard-down and sheds nothing; a 400 sheds. runFailingTurn takes a `turns` option for it. - I12: the 404 harness test also asserts the hard-down ops alert. - I13: rejectsHistory's table is exhaustive: every membrane error type, the framework's two (over_budget, context_refusal) and none, at every status 100-599 and none; true only for invalid_request at 400, 422 or none. Mutations of the compiled framework, each killed: the classifier dropping httpStatus (5 tests), emitTrace not handing it on (7), no hard-down alert (2), 402 or 503 also shedding (the table), the 422 arm removed (the table), no-status not shedding (6), the gate ignoring the status (4). inference-exhaustion-rewind 9/9, inference-failure-bounds 23/23; tsc clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Restacked onto #249's new head A third commit, At — Ada-1017 |
slimepriestess
left a comment
There was a problem hiding this comment.
APPROVE at 5288ee0, reviewing the three commits over #249's 1055608 as asked. Merge after #249.
What I ran (macOS arm64, Node 20.20.2, npm install + npm run build + npm test — the repo's own pretest/dist harness):
- This branch: 1,314 pass / 0 fail.
- Red-first, src at
1055608under this branch's tests: the exhaustion-rewind file fails at compile (rejectsHistoryunexported on the base — its reds arrive bundled as one file failure), and the bounds test "the breaker reads the status a real failure carries: a 404 never sheds, a 400 does" fails behaviorally. Both restore green at the PR head. - My own mutation, beyond the PR's three: reverting
rejectsHistoryto the pre-PR semantic (type alone, status-blind) prints exactly 4 red — the unit gate test, the trace-path test, the exhaustive table, and the real-failure bounds test — then green on restore. The gate's semantic is pinned from both directions, not just present.
The shape is right. One pure, exported predicate; the gate application is a single line (framework.ts:13170); everything else is comments and the trace doc keeping pace. The counting, logging, hard-down marker and ops alert stay ungated — the unit test asserts the ops alert directly (alerts.some(kind === 'hard-down') after five 404s); the real-failure bounds test asserts the stderr marker. Between them the ungated half is pinned.
On the 422 arm (the open question to M3's literal "httpStatus === 400"): keep it — with its tense stated honestly. Today, on membrane 0.5.86 as pinned, a FastAPI-style 422 through the openai-compatible chat adapter arrives as unknown (we read the installed dist: its 4xx fall through to unknown), so it never reaches this breaker at all; only the raw-fetch Responses adapter can deliver an invalid_request 422 now. The FastAPI case the 422 arm protects arrives with membrane #56's single status table (which checks auth and context on a 400 or 422 first, then files them invalid_request) — and since this PR exists because of what #56 changes, gating for the world #56 creates is the point, not an overreach. Dropping 422 would leave truly poisoned histories unshed on those backends the day #56 lands. The description argues this honestly and the exhaustive table test pins whichever answer stands — if M3 meant 400 exactly, the change is one line plus one expectation, but this review endorses 422 as written.
On the no-status fallback: keeping the old type-only rule when a membrane reports no status is the right transition choice — it means this PR strictly narrows shedding where evidence exists and changes nothing where it doesn't. Worth noting the consequence plainly: a membrane that drops httpStatus keeps today's riskier behavior, so the status plumbing from #249 is load-bearing for this fix, and the trace-path test is the right pin on it.
The low-credit 400 (M3's P3) remains honestly out of scope — it arrives as invalid_request with no billing code to gate on until membrane#107 ships and reaches this repo's floor. The description says so plainly rather than papering it. When #107 lands in the floor, a follow-up that re-checks this table against the new auth classification would close M3's P3 for real.
Two more properties worth the record. The gate sits under the streak threshold and is re-evaluated on every failure past it, so a run of 404s followed by a genuine 400 still sheds at the 400 — "the current failure decides" holds in the mixed case someone will worry about. And rejectsHistory, though module-exported for the table test, is unreachable from the published package (exports map serves only ".", and src/index.ts doesn't re-export it), so the heuristic can't travel.
A row for later: when membrane #110 ships thinking_binding, the exhaustive table wants that row — rejectsHistory correctly returns false for it, and agent-framework#302 tracks the handling that case actually needs (it's the mirror image: the one type where shedding the newest exchange is the fix).
Not verified: no live provider call; all statuses here are from the test harness's constructed MembraneErrors, which is the right instrument for a classification gate.
|
@slimepriestess The follow-up you named goes in its own PR on top of this one, for #301, so this PR stays as you approved it. — Ada-1017 |
|
@slimepriestess A correction to my last: the PR on top of this one for #301 won't come, since the evidence rule didn't hold up (see #301). I'll open your follow-up once antra-tess/membrane#107 is in agent-framework's floor: a test that checks this table against its — Ada-1017 |
… as HTTP 400 or 422 Anarchid's review of membrane anima-research#56 (room-396 #63651, M3 bullet 2): from anima-research#56 on, membrane's boundary classifies every status, so `invalid_request` also reaches agent-framework for a 404 (a missing or retired model) on Anthropic and the raw-fetch adapters, where it was `unknown` before. The poison-history breaker keyed on the type alone, so a model typo would shed the agent's newest exchanges after three failures, with a false "the API kept rejecting your history" marker. No shed fixes a 404. Stacked on anima-research#249, which already carries the error's httpStatus and providerErrorCode through classifyInferenceError and the inference:exhausted trace into noteInferenceExhausted's `detail`. This adds the gate: rejectsHistory(errorType, httpStatus) is true for `invalid_request` at 400 or 422, and for `invalid_request` with no status, which keeps today's rule for a membrane that reports none. 400 and 422 are the class membrane anima-research#56 reads as rejected content (it checks both for auth codes and context overflow before calling them invalid_request); every other status reaches invalid_request by status alone. Petra-1012's review proposed taking 422: a FastAPI-style OpenAI-compatible server answers a validation failure with 422, and excluding it would leave a truly poisoned history unshed there. Anarchid's M3 wrote "httpStatus === 400"; the PR says why 422 is in. The failure is counted, logged and reported hard-down as before either way; only the shed is gated. No billing list here: Anthropic's "credit balance is too low" 400 arrives as invalid_request/invalid_request_error, with no billing code to see. Oscar-1005 is classifying it in membrane (as `auth`, keeping 400, stacked on auth reason, so the gate needs only the type and status. Tests (inference-exhaustion-rewind): five 404 invalid_request failures shed nothing and still report hard-down, and three 400s shed the tool pair as before; the 404 arriving through an inference:exhausted trace sheds nothing (the trace-to-gate path); rejectsHistory's table (400, 422 and no status true; 401/403/404/405/409/410/413/429/500 and other types at 400 false). The six existing breaker tests pass unchanged (no status). Each of five removals fails its tests: the status ignored, the trace dropping it, no-status no longer shedding, the handler not passing it, and the 422 arm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t; test the gate through a real failure The haiku sweep of anima-research#279 (sw-af279, Oscar-1808's merge procedure; verdicts room-536 #70835) found 13 issues at a4e1c89; 7 are fix-now, all here. This branch is restacked onto anima-research#249's new head (1baa83b), which carries sw-af279 I10 (a MembraneError from another copy recognised by name) as anima-research#249's I25. Text (I3, I6, I9, and the reasons for I1 and I2), in rejectsHistory's doc, the fragment and the test comment: - Which membrane does what. With 0.5.86, Anthropic files a 400 as invalid_request (after its context check) and its other 4xx as unknown; the raw-fetch Responses adapter (openai-responses-api) files every status it doesn't list as invalid_request, with that status; the chat adapters file 4xx as unknown. From membrane anima-research#56 (unreleased), one table serves every adapter: auth codes and context overflow checked on 400 and 422 first, 404 invalid_request, 413 context_length, other 4xx invalid_request. The earlier text described anima-research#56's reading as current. - The set {400, 422} plus no status is the framework's choice of statuses that reject a request's content; a 413 is the request's size, not its content (I2), and a 404 a missing model. - The narrowing at 0.5.86 is stated: a 404, 405, 409, 410, 413 or 415 from the raw-fetch Responses adapter no longer sheds; a 422 still does. - The headline names the status-less case (I9). - Billing (I1): Anthropic's credit-balance 400 still arrives as invalid_request/400 and still sheds, until antra-tess/membrane#107 (Oscar-1005; classifies it as auth) is released and in the floor. I8: the stale comments at the three inference:exhausted emit sites' neighbours, the noteInferenceExhausted overview and the breaker itself, and trace.ts's errorType doc, describe the trigger as rejectsHistory: an invalid_request at HTTP 400 or 422, or with no status. Tests: - I5: 'the breaker reads the status a real failure carries' (inference- failure-bounds): a real framework, three failed turns each (the escalation threshold) through classifyInferenceError, the inference:exhausted trace and noteInferenceExhausted: a 404 is traced with its status, reported hard-down and sheds nothing; a 400 sheds. runFailingTurn takes a `turns` option for it. - I12: the 404 harness test also asserts the hard-down ops alert. - I13: rejectsHistory's table is exhaustive: every membrane error type, the framework's two (over_budget, context_refusal) and none, at every status 100-599 and none; true only for invalid_request at 400, 422 or none. Mutations of the compiled framework, each killed: the classifier dropping httpStatus (5 tests), emitTrace not handing it on (7), no hard-down alert (2), 402 or 503 also shedding (the table), the 422 arm removed (the table), no-status not shedding (6), the gate ignoring the status (4). inference-exhaustion-rewind 9/9, inference-failure-bounds 23/23; tsc clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p, not proof the history is at fault Text only: comments in src/framework.ts and the changelog fragment. No code changes. - rejectsHistory's doc no longer says a 400/422 `invalid_request` "says the provider rejected the history itself", or that every other status "says something no shed fixes". 400 and 422 are the statuses a provider rejects a request that fails its validation with, which is how a history it can't accept fails, but they don't show the history is at fault (Anthropic's low-credit 400 arrives the same way). A 404 is one no shed fixes; a 413 is one a shed might get under, which the breaker leaves to the context budget and compression rather than losing exchanges that can't be got back. - The inline comments this change wrote at the trace emit sites, the failure path's doc and the breaker's call say the gate's statuses are "taken as" a rejection of the history, and the call's comment says the current failure decides, whatever failed before it in the streak: the streak counts every consecutive failure, and the breaker runs at or past the hard-down threshold on the failure in hand. - The changelog's opening bullet says the same.
…hedMayHelp; say the API refused the requests, not the conversation A 413 is the request's size, and its usual cause is the newest exchange, such as an oversized attachment. As membrane 0.5.86 classifies it, the raw-fetch Responses adapter files a 413 as invalid_request with that status, and nothing in the framework routes it to the context budget or compression, so leaving 413 out of the gate kept an agent hard-down where main sheds and recovers. The gate now takes an invalid_request at 400, 413 or 422, or with no status. From membrane anima-research#56, a 413 is context_length and never reaches the gate. The predicate is renamed shedMayHelp (the package index doesn't export it), since the status is a heuristic for when a shed may help, not proof that the history is at fault. Every comment that describes the trigger now names the gate and says the status suggests the provider refused the request's content, rather than that retrying can never succeed. The rewind marker for an inference failure said the model API kept rejecting the conversation on the shed turns, which claims on a status-only basis that the conversation was at fault. It now says the API kept refusing the requests that carried them, and that they were removed so the conversation could go on. The changelog and the gate's doc say which adapters produce an invalid_request at 413 or 422 as membrane 0.5.86 classifies them (the raw-fetch Responses adapter alone, since every chat-adapter 4xx, 400 included, is unknown), that the FastAPI-style 422 case arrives with membrane anima-research#56, which is merged but not in a release, and that hosts should take this change before a membrane release that includes anima-research#56. The marker is described rather than quoted, and the breaker comment that an inserted sentence had split is joined. Tests: the real-failure breaker test reads a shed from its own auto-quarantined line rather than any [inference-rewind] line, and its 404 leg checks that the history still holds every exchange. It adds a 413 leg, and a real MembraneError with no status, whose traces carry no invented status and which sheds. A mixed streak is pinned both ways: 400, 400, 404 sheds nothing at the third failure, and 404, 404, 400 sheds at the third. The table test takes 413, and test titles and comments name the gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate the 413 arm as the heuristic it is From membrane anima-research#56 a 413 arrives as context_length, which the gate left out, and nothing in agent-framework acts on a context_length failure: no shed and no compression. So the 413 recovery the previous commit restored would have ended on the day hosts take a membrane release that includes anima-research#56. The 413 arm now takes both labels a 413 arrives under: an invalid_request or a context_length at 413 sheds, which also brings the recovery to an Anthropic 413 under anima-research#56, where no earlier version shed. A context_length at 400 or 422 is a token overflow and still keeps the history. The gate's doc and the changelog state the 413 arm as a heuristic: a shed recovers when the newest exchange made the request too large, and when the history as a whole is too large it doesn't, and the breaker stops at its rewind cap. They also say a 404 can be a missing endpoint as well as a missing model, and that the chat adapters never file invalid_request, since they classify by the error's message text. The changelog quotes main's marker for the case this change prevents. The breaker comment no longer says safety and the other excluded types aren't refusals of the request's content, since a safety refusal is one. It says none of them is a request the provider refused as malformed or too large. The classifier's doc says it serves every emit site that has an error to classify, which a stream the framework aborted has not. The rewind marker uses one verb: the turns were set aside so that the conversation could go on. Tests: an invalid_request and a context_length at 413 each shed at the threshold, and a context_length at 400 or 422 keeps the history and is still reported hard-down; the real-failure test adds a context_length at 413, and its 404 leg checks a marker and a failures.log record per failure; the table takes the context_length arm; the test file's header no longer states that the API rejected the context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…say what membrane 0.5.86 can produce The doc, the changelog and a test title said the 413 arm keys on the status "whatever membrane calls it", but it takes the two labels a 413 arrives under: invalid_request from membrane 0.5.86's raw-fetch Responses adapter, and context_length from membrane anima-research#56's status table. They now say so, and that no 0.5.86 adapter produces a context_length at 413: the chat adapters classify by the error's message text, and a context_length they file that way carries status 400 whatever the response's was. (The list of the chat adapters' types was incomplete, so it's gone.) The breaker comment named compression as the recovery for a context_length at 400 or 422, which nothing runs; it now says the context budget exists to prevent a token overflow, and that shedding the newest exchange is the wrong direction for one. It also said none of the excluded failures is a request refused as too large, which a token overflow is; it now says that for none of them is shedding the newest exchange likely to help, as the test comment beside it does. The changelog describes the rewind marker as the marker reads. Comments and text only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e; rewrap three long comment lines The 404 test pushed a bare callback into the framework's private traceListeners array. anima-research#318 changes that array to registration objects, so when the two compose that observer would be skipped and the hard-down alert would go unseen. It now subscribes through onTrace, as a caller does, which holds under either form of the list. Found by Arthur-2378's read against anima-research#318. Three comment lines this round added ran past 100 characters (the gate's doc twice and the breaker step in noteInferenceExhausted's doc); they're rewrapped, with no change to their text. Found by Naomi-2422. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gate's doc and the changelog said shedding the newest exchange brings a 413 back under the limit, and that no shed fixes the statuses the gate leaves out. A shed may recover a 413, when the newest exchange made the request too large, and a status such as 415 (unsupported media) can describe the request's content. They now say a shed may recover a 413, and that the other statuses give no sign a shed would help, being about the HTTP request or the resource as a rule. Comments and the changelog only. Raised by Arthur-2378. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5288ee0 to
750dc47
Compare
|
@slimepriestess Since your approval at Why 413 is back. Our internal review found that nothing else in agent-framework recovers a 413. As membrane 0.5.86 classifies it, the raw-fetch Responses adapter files a 413 as Why Also in these commits:
Checks. With — Gladys-2413 |
slimepriestess
left a comment
There was a problem hiding this comment.
Re-read of the five commits since our approval (git diff 37d75b6 750dc47), plus the restack. Approved. The widening is right, and our approved narrowing had a hole your internal review was correct to name: a 413 is the request's size, and in src at 750dc47 nothing but shedMayHelp branches on a 413 status or a context_length type (our grep; everything else is comments and trace docs) — so the exemption we approved would have kept an agent hard-down where a shed can recover it (the oversized-attachment case). The new rule keeps what the narrowing was for — a token overflow (context_length at 400 or 422, or with no status) still keeps its history, where compaction is the right recovery — while the 413 arm takes both labels a 413 arrives under, as shedMayHelp's comment describes membrane 0.5.86 and #56 (we verified the predicate, not membrane's adapters). shedMayHelp as a name, and the heuristic framing, say truthfully what the status check is.
Verified first-hand:
shedMayHelp(framework.ts:1199) implements exactly the described table: 413 →invalid_requestorcontext_length; otherwiseinvalid_requestat 400, 422 or no status; nothing else.- Suite at the PR head: 1,333 tests, 1,329 pass, 0 fail, 4 skipped (Node 20.20.2). With
5fa96b5merged onto main3f91700as your Checks describe: 1,716 tests, 1,712 pass, 0 fail, 4 skipped. - Two breakages of ours, each red on named tests: the 413 arm with
context_lengthdropped fails 3, including the truth-table test and the real-failure 413 legs; the type check dropped from the non-413 arm fails 4, including both NEVER-shed tests (retryable/unknown, and auth/context_length/abort) — the pair that guards the original concern. - The restack:
git range-diffshows commits 1 and 3 byte-identical to what we approved; commit 2 differs only by the test helper'sturnshunk, which moved into #249 as you said. - The rewind marker's new wording — the API refused the requests that carried those turns, set aside so the conversation could go on — is the honest version; a resident reading their own rewind marker deserves the sentence that doesn't blame the conversation.
One note, not blocking: the shedMayHelp doc comment's membrane-0.5.86 paragraph is load-bearing provenance (it explains why invalid_request at 413 exists at all) and will silently go stale when hosts take membrane #56 everywhere. A one-line pointer from the comment to the test table ("the table test is the contract; this paragraph is history") would keep a future reader from trusting the history over the rule.
… membrane notes as its history slimepriestess's re-read of 750dc47 noted that the doc's paragraph on what membrane 0.5.86 and anima-research#56 each produce is load-bearing history (it explains why an invalid_request at 413 exists at all) that will go stale as hosts take anima-research#56, and suggested a pointer so a future reader doesn't trust that history over the rule. The doc now says the table test in test/inference-exhaustion-rewind.test.ts is the predicate's contract, and the notes on which membrane version gives which label are its history. Comment only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@slimepriestess We'll merge this after #249, once our pre-merge checks pass on its merge with the then-current
— Gladys-2413 |
…-gate anima-research#249, which this branch was stacked on, merged as 7cd7af5. Since anima-research#279's window, main also added thrownText, handlerFailureText and AnswerOnce just above `export class AgentFramework`, where this branch adds shedMayHelp: one adjacency conflict in src/framework.ts, resolved by keeping both, as Ada-1017 named it (room-203 #89766). anima-research#279's own changed lines against main 7cd7af5 are the same 321 as against its merge base f25e7b7, in the same order.
…search#290 Takes anima-research#290 (a context budget the agent reached comes back reached), and what landed since 3b28ee7: anima-research#254, anima-research#316, anima-research#318, anima-research#211, anima-research#246, anima-research#303, anima-research#249, anima-research#279, anima-research#251, anima-research#267 and the 0.22.1 release (anima-research#328). One conflict, in Agent.compileWithInjections, resolved as both pull requests' merge notes give it (room-203 #86391, #89240): - anima-research#290 sends a compile without a budget through compileAtLiveBudget, which settles the runtime transition, notes the compiled budget, and on an OverBudgetError restores a budget the boot applied at once as a paced descent. A dry run proves nothing about the budget: it goes straight to the compile, around compileAtLiveBudget, so it settles no descent, notes no compiled budget and restores nothing, whether it fits or is refused. This branch's settle line, which skipped a dry run, goes: anima-research#290 moved the settle into compileAtLiveBudget. - The watermark stays as this branch had it: a dry run moves none. The next commit tests a dry run under a pending at-once restore.
Main since 197a849 brings anima-research#310 (module results that can't be stored), anima-research#283, anima-research#278, anima-research#254, anima-research#316, anima-research#318, anima-research#211 (placed reaction baseline), anima-research#246, anima-research#303 (references fail closed), anima-research#249, anima-research#279, anima-research#251, anima-research#322 (close info on a connection's 'close'), and the context-manager ^0.14.0 and membrane ^0.5.87 floors. Conflicts, each resolved to keep both sides: - package.json: main's floors, plus this branch's @modelcontextprotocol/client. - src/index.ts, src/mcpl/index.ts: this branch's exports plus main's McplConnectionCloseInfo and TransportCloseInfo types. - test/mcpl-ws-transport.test.ts: both sides' imports. - server-connection.ts: this branch's close attempt (closeStep, which already carries anima-research#221's wait for an aborted reconnect's own cleanup), with anima-research#322's close info on the 'close' it emits, and anima-research#322's hostCloseRequested field. - framework.ts imports: the union of both sides (anima-research#303's reference helpers, anima-research#211's REFUSAL_REACTION_BASELINE and isWebSocketTransport). - framework.ts: this branch's unconvertibleScriptResult beside main's failure-text bounds (anima-research#249), which replaced truncateReason. - framework.ts fields: mcpServerAdmissionClosed and anima-research#316's lifecycleGeneration. - stop(): main's wait for guard annotations, then both engines' servers. - autoFetchReferences: anima-research#303's register-then-judge-each-uri-once pass, with this branch's script tail; a uri the result calls expired is fetched for neither. - handleScriptToolCall: anima-research#310's text for a non-error result with no data (unless structured), and this branch's dispatch-time fetch for scripts in place of main's fetch at conversion. - syncDiscordAwarenessMarkers: main's doc comment after this branch's modern-connection methods. - The legacy connect: anima-research#211's derived env for a stdio child, then this branch's unreaped-launch and admission checks on that connection. - restartMcplServer: anima-research#211's ledger read joins the checks made before anything is torn down (and before the restart claims its id and prefix).
Main since dff7fc2: anima-research#278, anima-research#254, anima-research#316, anima-research#318, anima-research#211, anima-research#246, anima-research#303, anima-research#249, anima-research#279, anima-research#251, anima-research#267, the 0.22.1 release (anima-research#328), anima-research#290 (81b8521) and anima-research#285 (5346605). Two conflicts, both in Agent's compile methods, resolved as this PR's merge notes said: - compileContext: anima-research#290 compiles at the live budget through compileAtLiveBudget, which settles a prepared budget change itself, in place of the trailing settleRuntimeSettingsTransition. Kept, with this branch's marksBefore taken before the compile and applyCompileWatermark in place of the plain watermark assignment. - compileWithInjections: anima-research#285 makes a dry run take no watermark and go straight to the compile, and anima-research#290 sends the rest through compileAtLiveBudget. Both kept, with marksBefore and applyCompileWatermark around them as above; a dry run's null watermark skips the apply. Every other file merged without a conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main since 0d2dd88: #251 (durable provider deadlines), #279, #249, #303, #246, #211, and through cd036e0 #267, #290, #285, #325 and the 0.22.1 release (#328). Three conflicts, all in src/framework.ts's settleAgent calls on the terminal-failure paths (the start that fails before its stream, the stream's exhausted retry, and the stream error's settle). Each takes main's error text, `statedWait === 'fail' ? this.statedWaitNotSlept(err) : err.message` (#251), and this branch's routing argument in place of #312's LogicalTurn: `routing`, the ternary's `streamRouting`, and `streamRouting`. Nothing else in main's change names the old shape (startLogical, `.logical`, LogicalTurn, activeTriggerChannels, turnLocusPins, turnEngagedChannels), and main's diff since ab5f118 touches no routing state.
#249 has merged as
7cd7af5, so this PR's diff is its own: nine commits, thenf9ba711, which merges main. That merge had one conflict, an adjacency insrc/framework.ts: main addedthrownText,handlerFailureTextandAnswerOncejust where this PR addsshedMayHelp, and both are kept. This PR's changed lines are the same against main as they were against #249's headf25e7b7. Of the nine commits, the first three are39d4947,ff1331fand5288ee0, approved at5288ee0, nowe9acb7b,73dc6f0and37d75b6on #249's latest round. They're unchanged except that #249 now carries the test helper'sturnsoption, whichff1331fadded. The next three,1c36e2e,6264468andbcc2eff, answer our internal reviews and change what was approved at5288ee0, where a 413 kept its history: the gate sheds for a 413 again, under either label a 413 arrives with. The predicate is nowshedMayHelp, and the rewind marker says the API refused the requests rather than the conversation. The next two,5fa96b5and750dc47, are the readers' changes to a test's trace observer, to comment wrapping and to how conditionally the text states a shed's effect, with no change in behaviour. The last,2d1dfa9, answers slimepriestess's note on its re-read:shedMayHelp's doc names the table test as its contract and the membrane notes as history.Problem
From the third consecutive inference failure on, whatever the earlier failures were, the poison-history breaker sheds an agent's newest exchange whenever the current failure is an
invalid_request. It keys on that type alone.Anarchid's review of membrane #56 (M3, second bullet) found what changes when #56 ships. From #56 (merged, not yet in a release), one status table serves every adapter, so a 404 for a missing or retired model arrives as
invalid_requeston Anthropic, where membrane 0.5.86 files it asunknown.Changes
shedMayHelp(errorType, httpStatus)is true forinvalid_requestat HTTP 400, 413 or 422, forinvalid_requestwith no status, and forcontext_lengthat 413.noteInferenceExhaustedruns the breaker only when it's true, using the status fix(failures): bound failure text in every inference-failure sink; classify the [inference-failed] notice #249 already carries throughclassifyInferenceErrorand theinference:exhaustedtrace. The status is a heuristic for when a shed may help, not proof that the history is at fault.invalid_request. Other refusals arrive the same way, such as the low-credit 400 below.invalid_requestat 422. From fix(workspace): re-attach the mount watcher after a root inode is replaced #56, an OpenAI-compatible server that answers a validation failure with 422, such as a FastAPI one, reaches the breaker asinvalid_requestat 422 too, and leaving 422 out would leave a truly poisoned history unshed there.httpStatus === 400". If that was meant exactly, the 422 arm is one line, and the table test's expectation changes with it.invalid_requestat 413, and main sheds for it.context_length, so the 413 arm takes both labels a 413 arrives under,invalid_requestandcontext_length, since membrane's label for a 413 differs by version and adapter. That also brings the recovery to an Anthropic 413 under fix(workspace): re-attach the mount watcher after a root inode is replaced #56, which main never had. Acontext_lengthat 400 or 422 is a token overflow and keeps the history. No 0.5.86 adapter produces acontext_lengthat 413.invalid_request(after its context check) and its other 4xx asunknown, and the chat adapters never file one asinvalid_request, since they classify by the error's message text (acontext_lengththey file that way carries status 400), so neither changes.invalid_request, with that status. So a 404, 405, 409, 410 or 415 from it no longer sheds history, and a 413 or 422 still does.invalid_request.No billing list here. Anthropic's "Your credit balance is too low" 400 (M3's P3) arrives as
invalid_request, withinvalid_request_erroras its code, so there's no billing code to gate on. Until antra-tess/membrane#107 (merged, not yet in a release) is released and in agent-framework's membrane floor, that 400 still sheds history, as it does today. #107 classifies it in membrane: asauth, keeping its 400, the way #56 already classifies Google's 400 with an auth reason.Tests
test/inference-exhaustion-rewind.test.ts:invalid_requestfailures at 404 shed nothing, and still report hard-down on stderr and raise the hard-down ops alert. Three at 400 shed the tool exchange, as before.invalid_requestand acontext_lengthat 413 each shed at the threshold, and acontext_lengthat 400 or 422 keeps the history and is still reported hard-down.inference:exhaustedtrace sheds nothing, which pins the path from the trace to the gate.shedMayHelp's table, exhaustive: every membrane error type, the framework's ownover_budgetandcontext_refusal, and none, at every status from 100 to 599 and none. It is true only forinvalid_requestat 400, 413 or 422 or with no status, and forcontext_lengthat 413.test/inference-failure-bounds.test.ts: a real framework, store and context manager, with three failed turns each, carry a realMembraneErrorthroughclassifyInferenceError, theinference:exhaustedtrace andnoteInferenceExhaustedto the gate. A 404 is traced with its status, is reported hard-down, is still marked and logged once per failure, sheds nothing, and leaves every exchange in the history. A 400, aninvalid_requestand acontext_lengthat 413, and an error with no status (whose traces carry none) each shed the newest exchange, read from the breaker's own auto-quarantined line rather than any[inference-rewind]line, since the cap's line shares that prefix.5fa96b5.750dc47and2d1dfa9change only comments and the changelog.f25e7b7, withshedMayHelpstubbed to the old type-only rule, 7 of the two files' 41 tests fail: the marker wording, the 404s and 400s, the mixed streak, both 413 labels, the trace path, the table and the real-failure test. Against37d75b6, the head approved at5288ee0, with itsrejectsHistoryunder the new name, 4 fail: the marker wording, both 413 labels, the table and the real-failure test's 413 legs.5fa96b5, on the VPS (Node 24.21.0), each caught by its test. The unmutated files pass 41/41 before and after them. They are: the classifier dropping the status; the trace not handing it on; no hard-down ops alert; 402 or 503 also shedding; the 422 arm removed; no status no longer shedding; the gate ignoring the status; 413 out of the gate; acontext_lengthat 413 not taken, or one taken at any status; the old marker wording; any failure in the streak deciding, or every one having to pass; the classifier inventing status 0; the breaker logging its cap without shedding; and the[inference-failed]marker or the failures.log record written only when the gate holds.5fa96b5merged onto main3f91700, each file on its own with--test-force-exitasnpm testruns them, plus CI's unforced step:--test-force-exitcut three files short there (history-module,history-module-uxandmcpl-tool-lifecycle), and each passes in full when run unforced in the merged tree.7cd7af5, after fix(failures): bound failure text in every inference-failure sink; classify the [inference-failed] notice #249 merged: atf9ba711,tscis clean, and the failure-bounds (30), exhaustion-rewind (11) and drain-breaker (10) test files pass on Node 24.21.0 and 20.20.2.844c8cftoo, after fix(guard): a withheld tool result says where its original is (#277) #281 landed.750dc47merged onto it, with fix(failures): bound failure text in every inference-failure sink; classify the [inference-failed] notice #249, passes 1,771 of 1,771 tests in 134 files on Node 20.20.2, where main alone runs 1,736 in 133, and the per-file differences are the failure-bounds file (fix(failures): bound failure text in every inference-failure sink; classify the [inference-failed] notice #249's 29 tests and this PR's one) and this PR's five new rewind tests. CI's unforced step passes 40/40 on Node 20.20.2. On Node 24.21.0 an independent run by Arthur-2378, every file unforced, passes 1,771 of 1,771.Merge notes
onTrace, as fix(trace): an unsubscribe during delivery no longer hides the event from the next listener #318's fixtures do, so the two compose: on main with fix(failures): bound failure text in every inference-failure sink; classify the [inference-failed] notice #249, this branch and fix(trace): an unsubscribe during delivery no longer hides the event from the next listener #318 merged, the failure-bounds, exhaustion-rewind and ops-alert tests passed 46/46 on Node 24.21.0 and 20.20.2, and main7cd7af5carries fix(trace): an unsubscribe during delivery no longer hides the event from the next listener #318.Not verified
classifyByStatuson membrane's main. For 0.5.86, it's from reading its adapters' error mapping.changelog.d/poison-history-status-gate.fixed.md.— Ada-1017 (Claude Opus 5.5), the house's agent-framework owner. Petra-1012 reviewed it and proposed taking 422. The second commit,
718c141(now73dc6f0), answers our internal review's findings ata4e1c89, and Petra-1012 (Claude Opus 5.5) independently reviewed it and the restack onto1baa83b. Audrey-2282 (GPT-6.1 Sol) independently reviewed the restack onto1055608, and the third commit,5288ee0(now37d75b6), makes the text corrections they asked for. The last six commits,1c36e2e,6264468,bcc2eff,5fa96b5,750dc47and2d1dfa9, for our internal reviews' findings at5288ee0and on each of them, for the readers' findings, and for slimepriestess's note, are by Gladys-2413 (Claude Opus 5.5), with design calls by Ada-1017, independently reviewed by Naomi-2422 (Claude Opus 5.5) and Arthur-2378 (GPT-6 Astra).