Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/changes/unreleased/937-chooser-demands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
kind: changed
title: the chooser demands the set it admitted rather than suggesting it
pr: 937
surface: [engine, chat, docs]
invalidates:
- "The belief's ranking went out as `provider.order` with `allow_fallbacks: true`, which is ADVICE: the router weighed it against its own load and price and was free to serve the request from a machine the list never named. Measured over ten days of the call log, the machine asked for first served 29% of the time and a machine a strict preference named served 93%. The admitted set now goes out as `provider.only` with `allow_fallbacks: false` (internal/lane's `demandOf`), and the router may not go outside it."
- "`provider.only` meant a PIN and nothing else — a person's strict row, or a rescue's one arm — and every reader of it keyed on one name. It is now also the belief's admitted set, which is two names or more; the one-wide shape still means a pin and is still what `demandedLane` answers strict for. A belief that admitted fewer than two machines makes NO demand, because a demand of one would be this build pinning somebody to a machine they never asked for."
- "A demand rode alone: the sort word, the ledger's order and the price ceiling all came off and the object returned. The ranking now travels INSIDE the demand (`order` says in what sequence, `only` says which machines), a borrowable lane pin joins the set instead of leading an order the same object forbids, and the affinity cache pin yields to a machine outside the admitted set exactly as it already yielded to a refused one."
- "`keepingServable` narrowed a demand only while two names were left, so a demand down to its last machine was left as it stood — which asked the router to serve the request from the machine that had just refused it, while `allow_fallbacks: false` forbade it the healthy machine it would otherwise have found. `dropRefusedHere` now asks HOW WIDE the demand is, through `demandedLane` — the same door #925's veto-law exemption reads and the same reading `requestSet` confines the plan to: a request that demanded ONE machine (a person's pin, a rescue's arm) is left exactly as it stands, and the belief's wider demand is struck like the ranking and dropped entirely when every name has gone, with `allow_fallbacks` restored. `keepingServable` is deleted."
- "A PIN AND A DEMAND WERE THE SAME FACT, because `Only` only ever held a pin: `control.Plan.Pinned` was `len(choice.Only) > 0`, and everything a pin changes — the hazard answering a stall with `switch to auto? (y)` instead of a rescue, the primary arm refusing to walk off a transient fault — hung off that count. With the chooser demanding its own admitted set, counting would have made every ordinary routed call pinned. `lane.Choice.Pinned` now carries the fact, written only where a PERSON's strict row is read (`drawLaneChoice`) and read only where the plan is built (`planFor`). A demand is ours to relax; a pin is theirs to be asked about."
- "`calllog.Record.Lane` MEANT \"the machine the router was free to ignore\", and the census family built on it — `served != asked` — meant \"the router went somewhere we did not choose\". It is one name, documented as the first entry of `provider.order`; a demand names a SET, and the router must stay inside it while choosing which member serves. So the field now records the HEAD OF THE ADMITTED SET and `served != asked` means the router picked a different machine from the set we chose. The number falls — 96%/90% on dev to 42%/26% on the same rig (L3, two homes, twenty messages, reflex tier) while the whole wait p50 went 679/536 ms to 589/312 ms and the switches 41/16 to 23/14 — and anyone reading the old column will read an improvement as a regression. The row needs the demanded set beside the name before that family can be read again: issue #969, `internal/calllog`\u0027s `Record` and `internal/provider/calllog.go`."
- "The chat manual said auto RANKS the machines it wants. It now says aforge names the set it will accept and closes it, what that costs when the set runs out, and that a set of one is never made this way because a set of one is a pin. The `switch to auto?` page said that question belongs to a pinned lane; it now says so explicitly, because auto asks for machines by name too and is never the subject of that question."
---

#934 is the seam this needed and it is on `dev`: the plan, the watch and the
body have to be looking at one set before the chooser can demand one.

Owed and stated in the pull request rather than hidden: the `PhaseNews.Lane`
fill (the machine the next body will ask for is the plan's move, in a file this
change does not own). The measured before/after — the L3 rig at this head
against `dev`, and `make replay` over the ten-day call log — is in the pull
request body.
64 changes: 64 additions & 0 deletions internal/lane/choose.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,13 +652,77 @@ func (c *chooser) Choose(req Request) Choice {
// of the order is a machine the router is still free to choose. This is the
// only half of the veto the router can actually be made to honour.
choice.Ignore = refusedInto(choice.Ignore, refused, choice.Order)
// AND WHAT SURVIVED ALL OF IT IS DEMANDED RATHER THAN SUGGESTED (demandOf).
choice.Only = demandOf(scored, choice.Ignore)
// AND NOTHING ABOUT TIME. Where a rescue would go and when it would go
// there are [PlanFor]'s, built for every call out of the same frontier this
// carries — see the note on [Choice].
choice.Why = whyOf(scored[0], perceived[scored[0].ID])
return choice
}

// demandOf is the set this request may go to at all: every machine the role
// admitted and this choice did not veto, in the order it ranked them.
//
// ── WHY A DEMAND AND NOT A RANKING ──────────────────────────────────────────
//
// `provider.order` is ADVICE. With `allow_fallbacks` true the router reads the
// list, weighs it against its own load and price, and is free to serve the
// request from a machine the list never named — which it does, often. Measured
// over ten days of the call log (L10's replay, 2026-09-11): the machine this
// build asked for FIRST served 29 % of the time, while the machine a strict
// preference NAMED served 93 %. Every gate above this line — the capability
// gate, the Pareto prune, the price ceiling, the quality floor, and above all
// [beyondThePatience], which takes out the machines whose expected time to a
// usable answer is longer than this role will wait — spent its evidence on a
// set the router was then free to ignore.
//
// So the admitted set is stated as `provider.only`, which the router may not go
// outside, and the ranking travels inside it as before. What the frontier
// refused is refused, and the cost is accepted and named: a pool whose every
// admitted machine is busy REFUSES and the call walks (internal/lane/control's
// [Next] relaxes the shape, then hands the model to the session) rather than
// being served by whoever happened to be free.
//
// ── WHY TWO ─────────────────────────────────────────────────────────────────
//
// THE THRESHOLD IS THE ROUTER'S OWN RULE AND NOT A NUMBER THIS FILE CHOSE. A
// demand of ONE machine is a pin: `only: [x]` with fallbacks off means the
// request goes to x or it does not go, which is somebody's INSTRUCTION and not a
// belief's opinion — internal/provider builds exactly that object for a person's
// strict pin and for a rescue's arm, and the rest of the build reads a one-wide
// demand as a pin (its `demandedLane`, the single legal same-machine repeat).
// A belief that has admitted one machine has nothing to demand, because
// demanding it would be this build pinning somebody to a machine they never
// asked for; it ranks the one it has and leaves the router its fallbacks.
//
// THE VETOES ARE SUBTRACTED AND NOT SENT BESIDE IT, because `only` and `ignore`
// naming one machine is an empty serving set written by us, in one object, about
// a machine we just asked for — the same rule [refusedInto] keeps one line up
// and internal/provider keeps where it drops the machines that refused a call.
// A machine can be in the frontier and still be vetoed: [ignoredOf] names the
// candidates this process is SURE are far slower than the best, and the ranking
// above only has room for [orderNames] of them.
func demandOf(scored []Scored, ignore []string) []string {
if len(scored) < 2 {
return nil
}
vetoed := make(map[string]bool, len(ignore))
for _, lane := range ignore {
vetoed[lane] = true
}
demand := make([]string, 0, len(scored))
for _, candidate := range scored {
if candidate.ID.Lane != "" && !vetoed[candidate.ID.Lane] {
demand = append(demand, candidate.ID.Lane)
}
}
if len(demand) < 2 {
return nil
}
return demand
}

// orderNames is how many lanes the router is told to try in order. Three is the
// design's figure: past the third the fallbacks are doing the choosing anyway,
// and a longer list is a claim about lanes this process has barely seen.
Expand Down
21 changes: 18 additions & 3 deletions internal/lane/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,11 @@ type Scored struct {

// Choice is what one request should ask the router for.
//
// Order is a preference and Only is a demand: a pin sends Only, and everything
// else sends Order with fallbacks left on, because a slow answer beats no
// answer. Ignore names the lanes we are SURE about rather than the ones we are
// Order is a preference and Only is a demand. Both are sent: the machines that
// survived the gate and the prune are DEMANDED (`provider.only` with fallbacks
// off) and ranked inside that set by Order, because the machines outside it are
// ones this process has measured and rejected rather than ones it never heard
// of. Ignore names the lanes we are SURE about rather than the ones we are
// merely unlucky with, and it expires with the belief rather than on a timer.
//
// A zero Choice is "no opinion", which is a real answer and the right one when
Expand All @@ -208,6 +210,19 @@ type Choice struct {
Order []string
Only []string
Ignore []string
// PINNED IS A PERSON'S OWN WORD AND IT IS NOT THE SAME FACT AS A DEMAND.
// A demand is OURS — the set this process admitted, which it may relax the
// moment the set stops working — and a pin is THEIRS: one machine, named
// by hand, which nothing may quietly route around. The two looked alike for
// exactly as long as `Only` held nothing but a pin, and the day the chooser
// began demanding its own set, counting `Only` would have called every
// ordinary call pinned: the rescue road turns into a question
// ([control.Plan.Pinned] takes the `Ask` road), and the person is offered
// `switch to auto?` about a machine they never asked for. So the fact is
// carried rather than inferred, written in the one place that knows whether
// a person spoke (`internal/provider`'s lanepin.go and drawLaneChoice), and
// read in the one place the plan is built.
Pinned bool
// IT SAYS NOTHING ABOUT TIME, and the absence is the law. Routing and
// waiting are two questions and they must never share one nil: this answers
// WHICH LANE, and [control.Plan] — built for every token-generating call,
Expand Down
7 changes: 7 additions & 0 deletions internal/lane/control/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,13 @@ type Plan struct {
Alts []Alternative
// Pinned says a person named this lane themselves. It changes the ACT and
// never the arithmetic: where an unpinned call hedges, a pinned one asks.
//
// IT IS NOT "THE REQUEST NAMED MACHINES". Nearly every request names
// machines — the chooser demands the set it admitted — and a set this
// process admitted is this process's to leave the moment it stops working.
// Only a person's own word turns a rescue into a question, which is why
// what fills this is carried from the one place that knows one was spoken
// (`internal/lane`'s Choice.Pinned) and never counted off a list of names.
Pinned bool
// Purse is the spend rail, nil when nothing bounds it.
Purse Purse
Expand Down
39 changes: 39 additions & 0 deletions internal/manual/chat/lanes.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ needs — and then ranks what is left by the only thing you actually feel: how l
you will be sitting there, plus what it costs, with the money converted into
seconds by how much your waiting is worth.

**The machines that survive all that are asked for by name, and the provider may
not go outside them.** It is not a ranking the provider is free to put aside; it
is a closed set with the ranking applied inside it. The section below says what
that costs when every machine in the set is busy at once.

Nothing is waiting on this when nobody is waiting on you. A background errand is
ranked on price, because a second saved for a machine is a second nobody spends.

Expand All @@ -72,6 +77,34 @@ model that company serves. So the first request to a brand-new model is still
routed, still has a clock on it, and asks for a fresh sheet in the background
while it goes. You never wait for that fetch.

## The machines your message may go to are asked for by name — and the provider may not go outside them

When aforge has measured enough to have an opinion, it does not merely *rank* the
machines it wants. It **names the set it will accept, and closes it**: the
provider may not serve your message from a machine outside that set.

The list used to be advice. The provider read it, weighed it against its own
queues and prices, and was free to hand your message to somebody else — and often
did. Over ten days of this build's own call record, the machine aforge asked for
first served 29 requests in every 100; the machine a closed set names served 93.
Everything aforge works out before sending — which machines can do the job at all,
which are quick enough for how long this kind of work waits, what each one costs —
was being spent on a list the provider could put aside.

**What it costs, and it is a real cost.** A closed set can run out. If every
machine in it is busy at once, your message is refused rather than handed to
whoever happened to be free. What follows is a move, not an ending: the machine
that refused comes off the set and the request goes straight to the next one in
it, with no wait (`trying another machine · 2 of 3`); and when the last one has
gone, the set comes off the request entirely, so the provider has its whole
roster back for the one send that needs it.

**A set of one is never made this way.** One machine named with nothing to fall
back on is a pin — it is exactly what pinning a lane yourself sends — so aforge
closes a set only when it has at least two machines it is happy with. With one,
it ranks what it has and leaves the provider its usual freedom. Pinning a lane
still means what it always did, and nothing here narrows a set you asked for.

## The machines one message may go to are decided once — why a retry walks the same set, and why something learned mid-answer waits for your next message

Before the first byte of a request leaves, aforge decides which machines that
Expand Down Expand Up @@ -238,6 +271,12 @@ endpoint that was already ranked second — no new decision made at the worst
possible moment. The question is asked **once** per answer, and it disappears
the moment your answer starts arriving, because by then it is moot.

**You are only ever asked about a machine you pinned yourself.** On auto, aforge
also asks for machines by name — the closed set above — but that set is its own,
so a slow machine in it is simply left: the answer is started somewhere else and
you are told what is happening (`trying another machine`) rather than asked to
decide anything. The question is what your own instruction earns.

**`y` is the only key the question takes.** There is nothing to press to say no, because
there is nothing to decline — the wait is happening either way, and refusing would only
leave you in it. So the question is not a card you have to clear: it takes itself down the
Expand Down
Loading
Loading