Skip to content

Three seams #873 measured and did not take: the mid-stream rate test asks the wrong machine, a late first token teaches only a losing arm, and a refusal that cannot name an unknown machine #891

Open

Description

@santoshkumarradha

Three things #873 measured and deliberately did not take, because each crosses a file that lane was scoped away from or wants a ruling rather than a patch. They are written down here so they do not live only in a report.

#873 (dev@834bdce26) made the chooser refuse a machine whose expected time to a usable answer is longer than the role's own declared deadline, and made that refusal reach provider.ignore rather than provider.order. All three below are about the half of the same question that lands AFTER the request has gone out, or about the one policy decision that is not ours to make.


1. The mid-stream rate test asks the wrong machine, so a slow machine is never surprising to itself

What happened

2026-09-11 10:02, dev@c9f24f2b6, tag:task node 7. After a bash cd … tool result the next step was served by one machine at roughly 6–8 tokens a second, and ran 219,014 ms — three and a half minutes during which the person watched a cd row and read it as stuck. Two machines on the same model were writing at 90–120 tok/s. From the same call log, that machine's own rate over the previous night:

01:04–01:11   45 rows at 40–105 tok/s (median ~62)
09:33:33      9.8 tok/s
09:37:16      5.9 tok/s   (197 s stream)
09:52:49      6.1 tok/s   (93 s)
09:59:46      7.9 tok/s   (543 s, 4,307 tokens)
10:06:18      6.8 tok/s   (219 s)

The stream was producing tokens throughout, so #853's "a producing stream is never cut" correctly held. What did not happen is a move to one of the machines that were forty times faster.

Why

hazard.keepingUp (internal/lane/control/hazard.go) judges the live per-token gap against h.plan.Gap — the belief about the machine that is answering:

func (h *hazard) keepingUp() bool {
	return !h.plan.Gap.Known() || h.weight < 1 || h.drift/math.Sqrt(h.weight) <= h.z
}

Six tokens a second is not surprising for a machine believed to write at six tokens a second. rateCollapsed is therefore false, the ceiling runs on the ordinary clock, and nothing is done. The test can only ever catch a machine getting worse than ITSELF; it cannot see a machine that is simply far worse than what the request could have had.

(#873 closed the half of this that happens before the send: a machine believed that slow is no longer chosen for a role somebody watches. This is the half that matters once a healthy-looking machine degrades mid-stream, and the series above is five consecutive steps of exactly that.)

The change

control.Plan carries one more field: what the best admissible machine in this request's own frontier is believed to give. lane.PlanFor already holds it — Choice.Frontier carries every candidate's Rate — so nothing new is measured and nothing is fetched.

keepingUp then compares the observed gap's likelihood under the serving machine's belief against its likelihood under that alternative's — a likelihood ratio, not a ratio of medians, so a wide belief is not made to look decisive and a narrow one is not ignored. It stays one predicate, for the reason its own doc comment gives: the two readings of it (which clock the ceiling runs on, and which word the act carries) must always agree.

Replication — deterministic, no model

internal/lane/lanestub: a pool of three machines on one model, one writing at 2 tok/s and two at 90, and a request in a role the table declares Visible. Drive it so the slow machine answers (Profile.Rate: 2, a few hundred tokens). Assert on the controller's Act.

Today: no act before the role's ceiling; the stream runs to completion on the slow machine.

Acceptance — end to end, naming the door

The real door is internal/provider's stream watch driving control.Controller. A lanestub scenario asserting (a) an Act whose Reason is control.RateReason lands while the slow stream is still producing, and (b) the call log's rows for that request name a second, distinct machine. A hazard unit test beside it is fine; instead of it is not.


2. A late first token teaches the ledger only on the LOSING arm of a race

What happened

#853 landed the rule that "a late first token from a named machine teaches the ledger" — the fix for four streams that took 260, 370, 375 and 428 seconds to a first token and taught nothing, so the sheet went on saying that machine answers in eight. The condition was correctly relaxed in streamWatch.sighting (internal/provider/armwatch.go):

if w.served == "" || w.first.IsZero() || w.recovered {
	return lanes.Sighting{}, false
}

But sighting has exactly one caller in the tree — internal/provider/hedge.go, inside a loop that skips the arm that produced the result:

for _, arm := range arms {
	if arm.index == result.index {   // the arm that answered is skipped
		continue
	}
	...
	if sighting, ok := arm.watch.sighting(model, tokens); ok {
		lanes.Default().Ledger().Note(sighting)
	}
}

The skip is justified — "the answer that was used is noted once, by the ordinary path" — and the ordinary path is client.go's epilogue, which sits after every error return in the stream decode loop. A single-arm call is always result.index == 0.

Consequence

A stream with a late first token that was then cut or refused teaches nothing at all: the epilogue is never reached, and settle skips the arm. That is the same shape #853 was written to end, reached by a different door. Two further holes in the same place: knobs.trace.connectionRecovered suppresses the whole measurement after any local connectivity blip, and hedgeRace.abandon (a turn the caller cancelled) writes no sighting at all.

Replication — deterministic, no model

internal/lane/lanestub with Profile.TTFT set past LagTTFT and StallAfter/StallFor arranged so the stream is cut after the first token arrives. One arm, no race. Read lanes.Default().Ledger().Belief(...) before and after.

Today: the belief is unchanged — the machine's published first-token figure survives an answer that proved it wrong.

Acceptance — end to end, naming the door

Through Client.CompleteWithMessages against that stub, not through sighting directly: the belief's first-token median must move toward the observed late one. The likely shape of the fix is that the result arm's sighting is noted by the same door as the losing arms — one place that files every arm's measurement, including the one that errored — rather than by an epilogue that error returns skip.


3. Ruling wanted: may a role's refusal become provider.only rather than order + ignore?

The measurement

2026-09-11 09:33, tag:reflex, mistralai/mistral-nemo — the keypress-lookup tier. The call log's lane field is the machine the preference asked for and served is the machine that answered:

09:33:10   "lane": "DeepInfra"   "served": "DekaLLM"   ms 10746

We asked for one machine and the router used another, because provider.order is a RANKING the router may ignore once allow_fallbacks is true (#850's own sentence). Only provider.ignore takes a machine off the table.

What is closed and what is not

#873 closed the latch: the affinity pin latches onto whichever machine ANSWERED and leads the next order, so that one deviation had become the head of every request for the rest of the session — six of seventeen calls, whole-wait p50 1.8 s → 5.5 s. A refused machine no longer keeps the pin, and once the unknown machine has answered once it acquires a belief, becomes a candidate, and can be refused. Six calls became one.

The first deviation is still possible, and cannot be closed by this mechanism at all. ignoredOf, flooredInto and beyondThePatience all iterate the beliefs this process holds; DekaLLM had no belief for that model and no sheet prior either, so it was never a candidate, so it could never appear in ignore. Meanwhile internal/provider/velocity.go writes AllowFallbacks: &yes on every emitted block. The veto list is built from what we know, while the router picks from what exists.

The question

For a request whose admitted set is non-empty, should the body carry provider.only of the admitted machines instead of order plus ignore?

  • The field exists (providerPrefs.Only) and keepTheSetServable already guards the empty-set case, so this is small.
  • The cost is real and is why it is a ruling and not a patch. A demand turns a busy pool into a refusal where a preference would have been served by somebody — precisely the trade Never repeat: a body per attempt, a ledger keyed on who served, the sheet's doubts demoted #850 declined when it wrote "a slow answer beats no answer". It would change what happens on every 429-heavy afternoon, not only on the machines this is aimed at.

A middle reading also exists and may be the right one: send only for roles whose declared patience is short and nobody reads the stream (where a slow answer really is no better than none, because the whole of the call is dead time), and keep order + ignore for everything else. That is derivable from the role table's existing columns, so it would need no new configuration — but it is still a change to what a refusal MEANS, and that wants a person to say yes.


Raised out of #873's report. Happy to take any of the three; 1 and 2 are defects with the fix already scoped, 3 needs the ruling first.

🤖 Generated with Claude Code


Drafted with CodeAF · reviewed and owned by the author

Activity

  1. santoshkumarradha commented on Sep 11, 2026

    @santoshkumarradha
    MemberAuthor

    §3 — the ruling was taken, the change was built, and it is blocked on a seam

    The owner ruled on 2026-09-11 that the chooser demands, it does not advise:
    provider.only = the admitted frontier, allow_fallbacks true only when that
    set has fewer than two machines, ignore kept for the vetoes.

    That was built in speed/l9-named-refusal-is-a-move (lane.demandOf filling
    Choice.Only, applyLaneChoice sending it with the ranking kept inside the
    demand, the role's vetoes beside it, a borrowable pin joining it) and then
    taken back out of #925, because two things have to move first and neither
    belongs in a recovery PR.

    1. A demand narrowed to its last machine re-sends to the machine that just refused

    Client.dropRefusedHere narrows a demand only while something is left in it —
    keepingServable returns nil below two names, deliberately, because a demand
    nobody can serve is a round trip spent to be told what we already knew. So a
    demanded pool of two with one refusing leaves only: [the refuser] on the very
    next body, and allow_fallbacks: false now forbids the router the healthy
    machine it would otherwise have fallen back to.

    TestARefusedMachineIsNotAskedTwiceWhileAnotherIsAdmissible fails exactly this
    way with the demand on — the wire reads:

    send 0  lane=DeepInfra  only=[]           ignore=[]
    send 1  lane=Fireworks  only=[]           ignore=[DeepInfra]
    send 2  lane=Fireworks  only=[Fireworks]  ignore=[DeepInfra]
    

    and the call ends API error (429): … (via Fireworks) with GMICloud idle.

    2. The plan that bounds the call cannot see the demand

    Client.withLaneChoice stamps ONE choice on the context for the streamed path
    only (client.go:1346). A non-streamed call draws a fresh choice inside every
    encode, by design and with a doc comment saying so (laneChoiceFor: "a
    non-streamed call has no watch to agree with, so it decides here"). With order
    that is harmless — a ranking the router may ignore. With only it is not:
    requestSet(knobs) is empty, so control.Plan.Lane/Alts is an open set,
    and control.Next answers every refusal with an anonymous machine move while the
    bytes on the wire carry a demand the generator has never heard of.

    The walk the ruling describes — "relax the demand to the next admissible set,
    then model" — is control.Next returning MoveShape once every demanded machine
    is in the move log, and it can only do that if it is walking the same set the
    body names.

    What §3 needs first

    One choice per call, streamed or not. withLaneChoice on every completion
    path, and laneChoiceFor's per-encode draw retired with it. Once the plan and
    the wire agree about the set, the demand is safe: dropRefusedHere narrows it,
    control.Next walks it, the last refusal yields MoveShape, the membership rung
    widens the set, and then the model — never eight retries.

    Two things §3 unblocks and that are owed until then:

    • the waiting line can say what is being tried next (trying novita) instead
      of only an ordinal, because with a demand the next machine is a name this
      process knows — PhaseNews.Lane already exists and notePhase does not fill it;
    • the census gains "served ≠ asked" as a family that can actually go to ~0, which
      is unmeasurable while order is advisory.

    The law the ruling asks for (no request with a frontier of ≥2 leaves without only) and the lanestub acceptance are ready to land with it.

    🤖 Generated with Claude Code
    https://claude.ai/code/session_01FSshDkszBQ9RpR4dPsC5fQ

  2. santoshkumarradha commented on Sep 11, 2026

    @santoshkumarradha
    MemberAuthor

    Adjacent work landed 2026-09-11. The three seams in this issue are still open.

    Leave this issue open until §1 teaching, §2 late-first-token, and §3 last-machine demand are actually taken.

  3. santoshkumarradha commented on Sep 11, 2026

    @santoshkumarradha
    MemberAuthor

    §3 is taken by #937 (speed/l12b-chooser-demands), and the ruling it asked for
    has been given: the chooser demands the set it admitted.

    What landed against this section:

    • lane.demandOf puts the admitted frontier — the candidates that survived the
      capability gate, the Pareto prune, the price ceiling, the quality floor and
      beyondThePatience, minus this choice's vetoes — into Choice.Only, and the wire
      sends it as provider.only with allow_fallbacks: false. The ranking travels
      inside the demand; the sort word and the price ceiling come off beside it.
    • It is never a set of one. A one-wide only with fallbacks off is exactly what
      this build already sends for a person's strict pin, and demandedLane reads a
      one-wide demand as a pin — so a belief demanding one machine would be this build
      pinning somebody to a machine they never asked for. Below two admitted machines the
      request is ranked and the router keeps its fallbacks.
    • The cost §3 named is paid where it said it would be. dropRefusedHere strikes a
      machine that refused THIS call out of the demand, narrowing it a machine at a time,
      and when the last name goes the demand comes off the body and allow_fallbacks is
      restored — so the one body with nowhere left to go is carried exactly as a request
      with no belief behind it has always been carried. keepingServable is deleted.
    • The middle reading is not taken. Roles are not split: every routed call with two
      or more admitted machines demands. The narrowing above is what makes that safe, and
      it is what the wire scenarios in internal/provider/demand_test.go assert.

    One thing §3 did not see, and it is worth writing down here because it is the shape of
    the mistake: provider.only used to carry two facts at once. control.Plan.Pinned
    was len(choice.Only) > 0, so the day the chooser started demanding its own set, every
    ordinary routed call read as pinned — the hazard's Ask road instead of a hedge
    (switch to auto? (y) about a machine nobody chose, and no rescue sent), and a primary
    arm that would not walk off a 429. A demand is ours to relax; a pin is a person's word.
    lane.Choice.Pinned now carries the second, written only where their own row is read.

    §1 (teaching the ledger mid-stream) and §2 (the late first token on the winning arm)
    are untouched by #937 and stay open.

  4. added
    bugSomething the code does that it should not
    area:providerRouting, lanes, refusals, hedging, what a call costs
    sev:seriousWrong or missing behaviour a person meets in ordinary use
    on Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:providerRouting, lanes, refusals, hedging, what a call costsbugSomething the code does that it should notsev:seriousWrong or missing behaviour a person meets in ordinary use

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions