Skip to content

Elastic serving window that breathes with demand + KV-quant/flash-attn speed (#234 #232) + fixes (#201/#212/#230) - #2053

Open
joelteply wants to merge 19 commits into
canaryfrom
feat/elastic-serving-window-232-234
Open

Elastic serving window that breathes with demand + KV-quant/flash-attn speed (#234 #232) + fixes (#201/#212/#230)#2053
joelteply wants to merge 19 commits into
canaryfrom
feat/elastic-serving-window-232-234

Conversation

@joelteply

Copy link
Copy Markdown
Contributor

Serving-speed substrate, built + validated in isolation this session. 11 commits.

Elastic context window (#234) — the whole loop, closed

The served window is no longer a launch-baked constant. A turn completes → its
assembled-prompt size feeds the demand → the p95 baseline updates → the next plan tick
sizes the served window to real work. Lean chat turns keep it small (more personas warm,
snappier); heavy coding turns grow it toward the model/budget ceiling.

  • plan_serving_with_demand (elastic ceiling threaded through boot and the live-loop
    plan_serving_stable), OOM-safe by construction.
  • WorkingSetDemand — measured p95 producer + demand_for(measured_prompt) (never truncate
    the current turn), fully unit-tested.
  • Daemon holds the aggregator + reads demand_ceil() on every plan; a process-wide sink lets
    the persona turn path feed input_tokens with no cross-subsystem handle.

KV-quant + flash attention (#232) — the headroom/latency multipliers, opt-in

  • SERVING_KV_CACHE_TYPE=q8_0--cache-type-k/v and the fit math scales kv_per_token
    (conservative divisors), so the elastic window GROWS into the freed memory.
  • SERVING_FLASH_ATTN=1--flash-attn, faster prefill+decode, lower memory.
  • Both OFF by default → byte-identical unless enabled; operator opt-in on a KV-quant/flash
    capable backend, never a blind assumption.

Fixes + tooling

Validation

Every commit compiles clean; new logic is unit-tested (serving_plan, working_set_demand,
chat boot-race, kv_divisor). The elastic window + KV-quant/flash want a live burst to
confirm the window grows on a hard turn + measure the speedup — safe to merge first (inert
by default / bounded by budget), then deploy-and-watch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo

joelteply and others added 11 commits July 27, 2026 14:08
…uting (#230/#229)

Runs a GGUF MoE through the core/llama FFI with LiveExpertObserver attached (the existing
cb_eval → ffn_moe_topk seam), generates N tokens to drive real routing, then dumps the
model-intrinsic affinity: hot/cold expert distribution + co-occurrence + prefetch candidates.
That affinity is the INPUT to expert prefetch (#227), grid placement (#180), compaction, and
distillation (#233). Uses the in-process FFI (not the live llama-server lane) because affinity
is model-intrinsic — valid data, zero risk to live serving.

First run (Qwen3-Coder-30B-A3B, 96 tokens, Metal): 43,640 activations, 6116/6144 expert-slots
fired (~99.5%), hottest expert only 0.20% (~12x uniform), 6092 colder share 95.8%. FINDING:
for an 8/128 (6.25%-active) MoE, activation over a generation is BROAD, not tiny-hot — so the
paging win is the tier ladder + affinity placement, not a small resident set. Prefetch
predictor returned 0 candidates over 96 tokens (needs more data). K3 (1.8% active) should be
far more concentrated — same harness will quantify it when weights land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…nic (#201)

`ChatModule::executor()` did `.cloned().expect(...)` — a hard panic if a
`chat/poll`, `chat/send`, or `persist_posted` landed before `start_server`
called `install_executor_on_all` (a boot race). Panicking there SIGABRTs the
whole core and takes every other module down with it, for a per-request
contract violation that only concerns that one request.

Convert `executor()` to `Result<Arc<CommandExecutor>, String>` returning the
SAME loud, contract-naming message, and `?`-propagate it in the 3 callers
(all already `Result<_, String>`). Faithful to [[no-fallbacks-ever]] — still
loud, still names `install_executor_on_all`, no silent default — while
satisfying #26 (faculties degrade, never panic): a command that races boot
fails loudly to its caller instead of crashing the process.

Regression test: a pre-install `poll()` returns the loud error naming the
contract instead of panicking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…rink-cliff (#212)

The test body was already updated (74acbb3 / #206) to pin the ABSENCE of the
tight-window "discovery-pair only" shrink cliff — it asserts the full native
surface (edit_file/bash/grep) is never window-amputated on an 8192 window. But
the test NAME (`..._is_a_category_index_plus_discovery_pair`) and its header
comment still described the DELETED behavior ("the per-turn tool PAYLOAD is the
two-tool DISCOVERY PAIR"), contradicting the code below them.

Rename to `tool_surface_is_a_category_index_plus_the_unamputated_native_surface`
and rewrite the header to state what the test actually pins: the system prompt
carries a category index, the full native surface rides beside it un-amputated,
and a regression means either the ~150-schema dump or the amputation cliff came
back. Doc/name only — body and assertions unchanged, still green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ing, stop baking it at launch (#234)

The served window was capped by a STATIC constant: window_for(lanes).min(BOOTSTRAP_WORKING_SET).
So a hard coding task was clamped to 16k even when the model supports 128k and the budget
allows it — exactly the "set in stone at launch" anti-pattern, when the whole system is a live,
continuously-re-decided negotiation.

Thread the demand ceiling as a parameter: plan_serving_with_demand(host, candidates,
demand_lanes, demand_ceil). The window sizes UP to what the budget allows (window_for), then
caps DOWN to what the TASK needs. A hard task passes a high ceiling → the window grows; a simple
turn passes a low one → it shrinks so more lanes fit (the multi-persona concurrency win). This
is the local half of "resources ebb and flow with demand"; the grid_overflow_lanes producer is
the scale-out half — same demand→grant loop.

plan_serving(...) stays as a thin cold-start wrapper passing BOOTSTRAP_WORKING_SET as the prior,
so every existing caller is unchanged and behavior is identical until a demand producer threads
live demand — the rail is laid without touching the live-GPU-gated fit math.

OOM-safe by construction: window_for already bounds the window to the budget, so a higher ceiling
only raises the cap TOWARD that bound, never past it. Growing for a hard task can't crash a lane.

Test: cold=16k (prior), high-ceiling=64k (grew for the hard task), low-ceiling=8k (shrank for the
simple one). 22 serving_plan tests green, no regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…astic window (#234)

The elastic served-window seam (plan_serving_with_demand) needed a demand PRODUCER
that is measured, not guessed, and never launch-baked. WorkingSetDemand is it: a
rolling observer of each turn's assembled-prompt token count that produces the live
demand ceiling threaded into the plan.

Two signals combine so it's both efficient AND never truncates:
- demand_ceil() = max(floor, p95(recent prompts) + gen_headroom) — the sustained
  baseline that keeps the WARM lane sized to the persona's usual work, and ebbs
  back to the floor as lean turns roll through the window (a past hard session
  doesn't pin the window forever). p95, not max, so a lone spike can't
  over-provision a KV that swaps the box.
- demand_for(measured_prompt) = max(baseline, measured_prompt + headroom) — the
  MEASURED current turn is never clamped. This is how "if it needs it larger for a
  moment, don't limit it" holds WITHOUT guessing: we already assembled the prompt,
  so we request exactly its size. plan_serving_with_demand still bounds it by the
  budget above, so an impossible prompt degrades honestly, never OOMs.

Pure + fully unit-tested (cold→floor, sustained→grow, ebb-back, spike-excluded,
current-turn-never-truncated). No serving loop, no GPU — the honest measurement
under the elastic lease. Next slices wire it: observe TurnMetrics.input_tokens per
turn, feed demand_for() into the live re-plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ed end to end (#234)

Threads the elastic demand ceiling through the LIVE serving decision path, not just
the boot path:
- plan_serving_stable now takes demand_ceil and forwards it to plan_serving_with_demand
  (both internal calls), so the hysteresis/ongoing-loop path is elastic too.
- ServingDaemonModule holds a WorkingSetDemand aggregator (p95 of recent turns'
  assembled-prompt sizes, rolling window, floored at BOOTSTRAP_WORKING_SET), reads its
  demand_ceil() on every plan (compute_plan + publish_plan), and exposes
  observe_working_set(prompt_tokens) for the cognition turn path to feed.

Inert-by-default: with no observations the aggregator returns the cold prior, so the
served window is identical to before — behavior only changes once the emit is wired.
A poisoned lock degrades to the prior, never panics. The whole plan API (boot +
ongoing loop) is now demand-capable; the last hop is emitting each turn's input_tokens
into observe_working_set so the window breathes with real demand.

74 serving tests green, no regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…served window (#234)

The final hop. The persona turn path now feeds each completed turn's assembled-prompt
size into the serving demand, so the window breathes with real work instead of a baked
constant:

- serving_daemon exposes a process-wide sink (SERVING_WORKING_SET, same
  install_serving_state shape) holding the daemon's WorkingSetDemand (now Arc<Mutex>),
  registered at initialize(). observe_serving_working_set(tokens) is a free function the
  cognition path calls with NO daemon handle — no cross-subsystem plumbing.
- service_loop, where each turn's TurnMetrics is known, calls
  observe_serving_working_set(m.input_tokens) at turn completion.

The whole loop is now live: turn completes -> observe -> p95 baseline updates -> next
plan tick sizes the served window to real demand (plan_serving_with_demand) -> a lean
chat turn keeps it small so more personas stay warm, a heavy coding turn grows it toward
the model/budget ceiling. Safe/inert until a daemon boots (the sink registers at init),
so it can't break anything; it activates on the next deploy.

Full continuum-core lib compiles clean. This completes the #234 elastic serving substrate
started with plan_serving_with_demand + WorkingSetDemand: producer, seam, plan threading,
daemon-awareness, and now the emit — all built and validated in isolation, ready for a
live burst to watch the window grow on a hard turn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…he elastic window (#232)

Adds SERVING_KV_CACHE_TYPE (config, default f16/off): when set to q8_0 (or q4_0) the
llama-server lane runs --cache-type-k/v <type>, cutting resident KV ~in half at
near-lossless quality. That frees memory the elastic window (#234) can spend on a bigger
context or more warm lanes — faster for multiple personas AND more room for hard coding,
the same "faster + best code" pair.

OFF by default and safe-by-construction: absent / f16 → byte-identical f16 launch (no
behavior change), so this can't destabilize a backend whose build lacks Metal KV-quant
kernels — enabling it is an explicit operator opt-in, never a blind assumption
([[verify-real-device-numbers-not-a-clamp-premise]]).

Follow-up (noted in code): to have the PLAN grow the window on the freed memory rather
than leave it as extra headroom, footprint_for must scale kv_per_token by the quant
factor. This slice is the safe enablement; that fit-math coupling is the next step, and
wants a live burst on a KV-quant-capable backend to validate quality + the speedup.

continuum-core lib compiles clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…d KV memory (#232)

Completes the KV-quant feature. The launcher flag (prior commit) makes a lane run q8_0
KV; this makes the PLAN know it: footprint_for scales kv_per_token by the quant divisor,
so the served window is sized against the KV the lane WILL actually hold, and the elastic
window (#234) grows into the freed memory instead of leaving it idle.

- kv_divisor_for (pure, env-free, unit-tested): f16/unset/unknown → 1 (no change), q8_0 → 2,
  q4_0/q4_1 → 3. CONSERVATIVE by design — under the ideal ~3.5x for q4 — so the plan can
  never over-grow the window past the real KV and OOM (over-reserve = smaller window = safe).
- Applied only in the config-aware footprint_for; footprint_from_parts stays pure so its
  tests are env-independent. Same SERVING_KV_CACHE_TYPE key as the launcher — one config,
  two consumers (flag + fit rate), documented to stay in sync.

Default (f16 / unset) → divisor 1 → byte-identical: this can't change serving on a box that
doesn't opt in. Test pins the mapping + the safe-default. Wants a live burst on a
KV-quant-capable backend to confirm quality + the actual window growth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…memory (#232)

The fused attention kernel is faster on BOTH prefill and decode and lowers peak memory —
directly attacking the prefill-bound turn latency (#139) and freeing room the elastic
window (#234) can spend. SERVING_FLASH_ATTN=1|on|true adds --flash-attn to the lane;
absent → llama.cpp default (no flag), byte-identical.

OFF by default: Metal/backend flash-attn support + quality vary by build, so it's an
operator opt-in, never a blind assumption ([[verify-real-device-numbers-not-a-clamp-premise]]).
Composes with the KV-quant flag: enable both for the field-proven GLM-style speedup, then
validate on a live burst. continuum-core lib compiles clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…et-size + Jaccard (#230)

Evolves the MoE glass-box harness from the pooled first cut to the measurement that
actually decides the paging architecture (BigMama's three methodological guardrails):
diverse multi-domain corpus, PER-DOMAIN concentration (top-K% activation share vs the
uniform null), cross-domain hot-set Jaccard, shared-base-vs-own-only activation MASS
split, and the working-set-size curve (experts resident for 50/80/90/95% of a domain's
mass). Prefill-dominant sampling on realistic input, not a degenerating greedy loop.

Ran live on Qwen3-Coder-30B-A3B (Metal): pooled top-10% = 18.6% (mild — the smear), but
per-domain = 25–38% with near-disjoint hot sets (code↔prose Jaccard 0.05) and a tiny
38-expert universal core — i.e. paging is domain-working-set SWAPPING, not frequency
tiering. This is the #180 evidence; the harness is the reusable probe for any MoE (incl.
K3 at weight-drop).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
joelteply and others added 8 commits July 27, 2026 14:22
… capacity LeaseRequest (grid-overflow bridge)

The clean half of the grid-overflow seam. serving_plan reasons about MODEL
RESIDENCY (can a peer hold model M's weights + per-lane KV at the served
window, and how many lanes). capacity/ reasons about CONCURRENCY SPIKES
(does a peer have a free lane RIGHT NOW — LeaseRequest{want_concurrency,
spike_bytes}). They're orthogonal and compose — residency is the eligibility
gate, concurrency is the right-now admission. Neither absorbs the other.

grid_lease_request(served_window, demand_lanes) is the one-directional map
from the serving side into the capacity side: demand_lanes → want_concurrency
(floored at 1), and the prefill compute spike at the live served window →
spike_bytes (prefill_compute_reserve(window, 1) — the transient the peer must
have free to accept the hop, distinct from the resident weights+KV the
residency gate already proved). No transport, no placement policy here — just
the honest projection so the grid-overflow router can ask a residency-eligible
peer for a concurrency lease.

Test grid_lease_request_maps_demand_and_the_prefill_spike pins both mappings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…or grid overflow (governor consumer slice 1)

Grid-overflow routing (spill a persona's generation to a peer) has TWO orthogonal
gates that COMPOSE, never absorb each other (settled with BigMama 2026-07-27):

  (1) RESIDENCY = eligibility — does the peer already hold model M resident? If not,
      accepting the hop forces a cold full-weights load (seconds to minutes), which
      defeats overflowing for speed. So the fast path is eligible only for peers that
      already hold M. THIS module.
  (2) CONCURRENCY = right-now admission — does the peer have a free lane? capacity/grid
      (LeaseRequest, LocalFirstFitPolicy). Unchanged.

The one crossing point between the two abstractions is ModelFootprint::grid_lease_request
(serving demand -> LeaseRequest) — one bridge, not two half-bridges. Residency deliberately
does NOT live on PeerCapacity (that would blur the concurrency abstraction with a residency
fact); it lives here as ModelResidencyView, keyed on Uuid exactly like gossip's capacity
ledger. The governor COMPOSES the two at the placement filter: residency_eligible() returns
a SMALLER snapshot (local untouched — the overflowing node holds M by definition; peers
filtered to those holding M), and the unchanged capacity policy places on the survivors and
applies reachability itself. Two concerns, composed at exactly one point, neither absorbed.

Latest-wins replace (not merge) so a model paged OUT stops being eligible on the next beacon
— a merge would resurrect evicted models and route a hop to a peer that no longer holds it.

3 tests: eligibility filter, latest-wins replace, and the residency->capacity compose
end-to-end (resident+reachable gets lanes; resident+unreachable reclaimed by place();
non-resident absent from placement). Zero blast radius — new file, no existing struct touched.

Next slice: populate the view from a residency beacon (gossip wiring, coordinated with
BigMama — piggyback CapacityOffer vs a separate slower-cadence stream).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ct half of the residency beacon (governor consumer slice 2)

The residency sibling of capacity/gossip's CapacityOffer/GridCapacityLedger, with the
same identity + freshness discipline, a different (slower) cadence, and its own payload:

  - ResidencyBeacon (wire): the model ids a node holds resident + a sender timestamp.
    Rides its OWN grid_residency EphemeralCoalesced envelope — residency changes on model
    page-in/out (minute-scale), NOT the 10s capacity beat, so coupling them would either
    over-publish residency or under-refresh capacity. Peer identity is the WIRE's, never the
    payload's — a peer cannot beacon residency on another's behalf.

  - ResidencyLedger + global_residency_ledger(): folds heard beacons (latest-per-peer wins),
    projects a ModelResidencyView, evicts beacons silent past RESIDENCY_EVICTION_WINDOW_MS,
    excludes the node's own echo (local residency is its own serving truth). view() is the
    exact residency analogue of GridCapacityLedger::snapshot().

Eviction is GENEROUS (6× the capacity window) precisely because the two abstractions stay
orthogonal: residency is sticky, and the COMPOSED capacity snapshot already gates reachability
— so a residency reading never has to prove liveness itself (that would blur residency into
concurrency). A long-silent peer falls back to UNKNOWN residency (not asserted-resident),
keeping the fast overflow path honest.

3 more tests (6 total in the module): heard-beacon-projects + own-echo-excluded (loopback),
stale-evict-then-fresh-restore, serde round-trip (camelCase wire, catches field drift).

Next slice: the publish + inbound-fold wiring — a GridResidencyModule mirroring
GridCapacityModule (build the beacon from the serving plan's resident set, broadcast
grid_residency) + the inbound_attach fold into global_residency_ledger(). Then a node
advertises its residency and residency_eligible() runs on live grid data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ency beacon publish/fold wiring (governor consumer slice 3)

Closes the residency-beacon loop end-to-end, mirroring the capacity gossip path
(GridCapacityModule + inbound_attach fold) exactly:

  - GridResidencyModule (modules/grid_residency.rs): a Background ServiceModule that
    every RESIDENCY_PUBLISH_INTERVAL_MS reads the daemon's live serving plan (lock-free
    watch snapshot — the SAME source, no parallel probe) and broadcasts a ResidencyBeacon
    over airc as an EphemeralCoalesced grid_residency envelope. Today the resident set is
    [base_model_id]; a multi-model plan extends only current_beacon(). Honest silence when
    no plan is computed yet (nothing to advertise). Glass box speaks on change.

  - AircRealtimeSchema::GridResidency (airc/realtime.rs): the new schema variant,
    EphemeralCoalesced like GridCapacity. ts-rs binding regenerated (AircRealtimeSchema.ts).

  - inbound_attach fold: residency_beacon_from_envelope decoder + the else-if that folds a
    heard beacon into global_residency_ledger(), keyed on the WIRE's peer id — the orthogonal
    sibling of the capacity fold. Own echo lands here too (the single-node loopback proof).

  - Registered in ipc/mod.rs right after GridCapacityModule, fed serving_daemon.subscribe().

  - RESIDENCY_PUBLISH_INTERVAL_MS (model_residency.rs) = eviction/12 — the same
    publish:eviction ratio capacity uses, on a slower beat (residency changes minute-scale).

This completes the grid-overflow governor-consumer stack: a node now ADVERTISES which
models it holds, every peer folds those beacons into a ModelResidencyView, and the governor
composes it with the capacity snapshot (residency_eligible -> grid_lease_request -> place ->
aircPeer hop). The live two-node routing smoke (BigMama's node up + serving) validates the
cross-node generation — the milestone this stack was built for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…decision (governor consumer slice 4a)

The DECISION half of the driver, pure + fully unit-tested; only the thin EFFECT half
(the actual Commands.execute("ai/generate", {aircPeer}) hop) needs a live peer and lands
at the two-node smoke.

Overflow lanes are BY DEFINITION the ones ServingPlan.grid_overflow_lanes said couldn't fit
locally, so their placement is REMOTE-ONLY — it must never touch LocalFirstFitPolicy's
local-first >=1 floor (that floor is the local persona's OWN guaranteed lane, orthogonal to
spillover; re-cramming there is the exact thrash the honest overflow signal exists to avoid).
Confirmed with BigMama 2026-07-27.

Two orthogonal gates, composed (never absorbed):
  1. RESIDENCY (ModelResidencyView::residency_eligible) — a peer is a fast overflow target
     only for a model it ALREADY holds (else a cold full-weights load defeats the point).
  2. CONCURRENCY (reachability + lanes_that_fit misfit-parts) — among reachable eligible
     peers, most-free-first, each capped by its OWN budget for the prefill spike.

Unplaced lanes (no eligible+reachable peer could take them) are SURFACED in
OverflowRouting.unplaced for the caller to queue/degrade on — never silently dropped
([[fallbacks-are-illegal-fail-loud]]).

4 tests: remote-only + residency/reachability gating, unplaced-surfaced-not-dropped,
zero-overflow no-op, most-free-first spill spread.

This completes the pure governor-consumer decision path. Remaining (slice 4b, at the live
two-node smoke): read grid_overflow_lanes from the live plan, build the lease via
footprint.grid_lease_request, call route_grid_overflow, and execute the aircPeer hop per
placed (peer, lanes) — the only piece needing a real peer to route to.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…rflow_adapter_for override (governor consumer slice 4b-i)

The composition point where route_grid_overflow's decision becomes a real remote brain.
materialize_adapters gains an `overflow_adapter_for(&profile, slot)` closure (same closure-DI
shape as runtime_lookup / tool_executor_for): return Some(remote adapter) when the governor
routed this persona off-box — her node is over capacity and a reachable peer holds her model —
so her brain runs on that peer via AircRemoteInferenceAdapter; None → build the local adapter
from the factory (the common case). This is the exact re-home seam the DeliberationModelBinding
was designed for (its doc: re-home = "a new adapter / grid failover onto another node").

The remote adapter registers in the global provider registry by model_id just like the local
one, so evaluate_response reaches it transparently — the persona doesn't know or care that her
inference crosses the grid.

host.rs passes `|_,_| None` for now (slice 4b-ii wires the live capacity + residency + airc
closure at the ipc bootstrap, where the serving plan + airc handle live). Test
overflow_effector_supplies_remote_adapter_and_bypasses_the_local_factory pins the contract:
when the override supplies a slot's adapter, the local factory is NOT called for it
(build_count == 1 of 2), both personas host, both warm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…n routes off-box to a residency-eligible peer (governor consumer slice 4b-ii)

The last mile. build_overflow_effector (persona/grid_overflow_effector.rs) composes the whole
tested decision path into the per-persona adapter override the supervisor consumes, wired at
the ipc bootstrap spawn:

  live serving plan (grid_overflow_lanes) → footprint from live_candidates →
  grid_lease_request → global_residency_ledger().view → route_grid_overflow →
  AircLiveTransport(airc, peer) → AircRemoteInferenceAdapter

When the node is over local capacity and a reachable peer already holds a persona's model,
her DeliberationModelBinding.adapter becomes the airc-remote one — her inference crosses the
grid transparently (the re-home the binding was designed for), and she lives in the room as a
peer hosted on another machine. That's "a competent peer so it's not just us there."

DEFENSIVE by construction (safe to ship pre-smoke): returns None → local adapter on ANY
uncertainty (airc not attached, no plan, no overflow, no matching footprint, no eligible
reachable peer). Can only be a safe no-op or a correct off-box route — never a self-route
(own peer excluded via airc.peer_id(), so its own residency-beacon loopback can't pick itself)
and never a panic. The only unit-unprovable part is that the remote hop SUCCEEDS — the live
two-node smoke validates that; a hop that can't warm surfaces as a loud AdapterWarmup slot
failure, never a silent local downgrade ([[fallbacks-are-illegal-fail-loud]]).

Plumbing: overflow_adapter_for threaded through spawn_all → materialize_adapters (4b-i seam);
dedicated Arc clones of the airc-interceptor cell + serving daemon so the boot-spawn async-move
capture doesn't strand the interceptor + reconcile task; live_candidates() → pub(crate).

Completes the grid-overflow governor consumer end-to-end. Live validation + the cross-node
generation run the moment BigMama's node is serving (model_id string-equality is the one thing
to confirm live). All unit paths green (21 supervisor/overflow/residency tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…::spawn — the airc handle cell never filled (BigMama diagnosed the panic)

At ipc/mod.rs the AircInterceptor bootstrap spawned its `Airc::attach_as` task with
bare `tokio::spawn`. But this runs in `start_server` on the IPC thread, and the
`rt_handle.enter()` guard (line ~1111) is SCOPED and has already dropped by here — so
there is NO ambient tokio runtime and `tokio::spawn` panics "there is no reactor running,
must be called from the context of a Tokio 1.x runtime". That panic killed the attach
task, so the `OnceCell<Arc<Airc>>` never filled, and EVERYTHING that reads it silently
no-oped: the AircInterceptor's aircPeer command routing (send side) AND the grid-overflow
effector (build_overflow_effector reads the same cell → always None → local, never routes).

Fix: `rt_handle.spawn` (rt_handle is a start_server param, in scope; the SAME call
1498/2111 already use) — it targets the runtime by handle without needing ambient context.
This is the sender-side unblock for the whole cross-node persona-serving path: without the
handle, a node can never route an ai/generate to a peer.

BigMama diagnosed the panic live 2026-07-27; this is the one-line fix on the continuum side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant