Skip to content

Lanes: no lane held back idle; a directed turn takes the next lane freed - #5243

Draft
joelteply wants to merge 66 commits into
canaryfrom
fable/lanes-no-held-reserve
Draft

joelteply wants to merge 66 commits into
canaryfrom
fable/lanes-no-held-reserve

Conversation

@joelteply

Copy link
Copy Markdown
Contributor

Why

On the M5 (canary, 2026-10-20), the lane held back for directed turns was busy in only 6 of 30 samples. Over 10 minutes it took 3 directed grants against 39 work grants, and work waited 32 times while minds queued behind the other lane.

Change

  • Work budget: the non-directed budget (work, ambient, idle self-tick) is now every lane, and the ambient pool follows it.
  • Directed calls: a directed call takes a physical lane directly. When a non-directed permit is free it takes one too, so no work call can line up on the physical lanes ahead of the next directed arrival. It is first in line for the next lane freed.
  • Deleted, now unused: reserve_lendable and its test, directed_pending::any_pending, and the expected_occupancy parameter on acquire_serving_lane across its three callers. expected_occupancy itself stays, because it sizes each turn's wait bound.
  • Tests: a new test checks that work fills every lane and that a directed arrival takes the freed lane ahead of work that queued first. Budget tests now expect "every lane", and tests that assume a budget of 1 now pin one lane.

Base

This branch carries #5228 (head 7bcdb2b) plus merges of #5230, #5231 and #5232, because it touches the files those PRs split. Hygiene baselines were re-measured on that combination, and each count was traced to its cause.

Measured

  • On the M5 in a build window, cargo test -p continuum-core --lib passed 9,002 tests. The resource_admission tests pass 23/23 after the fix in this PR. The one remaining failure is config_env, the stale temp path that config_env tests: fresh temp dir per call (card 4c820b46) #5242 fixes.
  • Deployed to the M5 at 06:10Z: the core verifies as faeafe159 and serving was adopted (2 lanes, 112k).
  • Before, on canary, from the hour's health line: 17 acts, 1 lane-starved, 0 writes, directed wait p50/p90 0 s/0 s.
  • After: a 15-minute live read started at 06:21Z. It records slot occupancy, lane grants by priority and directed waits. The receipt will be posted here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo

joelteply and others added 30 commits October 20, 2026 10:16
working_memory.rs 4138 -> 618 lines (types, snapshot, Drop; re-exports
WorkingMemoryFaculty). Moved verbatim into working_memory/:
  ledger.rs 451 (construction, action statements, fingerprints, receipts)
  continuity.rs 429 (pins, notices, facts, snapshot/restore, claim-gone)
  perception.rs 490 (readers, dispatch, collapse, settlement)
  faculty.rs 213 (WorkingMemoryFaculty + render_trail)
  tests/{mod,actions,custody,faculty,open_views,pins,provenance,rebuilt_marker,trail}.rs
    (all <=423 lines; the two test mods become one tests tree)
Widened to pub(super): push_receipt_head, collapse_results_over_cap,
publish_live_handles. Test files carry #![cfg(test)] so source-hygiene
keeps counting them as test code.

Tests: working_memory filter 49 passed before and after. Lib warnings
122 -> 122, lib-test warnings 74 -> 74. source-hygiene baselines lowered:
lines-over-1000 155781 -> 152643, excess test mods 19 -> 18.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…, gate, tests)

Pure move: no behaviour change. Every file is at most 1,000 lines; public paths unchanged via glob re-exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
openai_adapter.rs 3730 -> 267 lines (config/types; re-exports
extract_reasoning). Moved into ai/openai_adapter/:
  runtime.rs 423 (construction, endpoints, runtime models, slot pool)
  lora.rs 198, registry.rs 139, reasoning.rs 107, embedding.rs 72
  provider.rs 482 (AIProviderAdapter impl)
  stream.rs 789 (generate_stream_checked body)
  tests/{mod,lane_transport,request_shaping,lora_page_in,
         prefill_progress_is_liveness,unguaranteed_model_refusal_...}.rs
    (all <=593 lines)

Not a pure move in two places, both forced by the 1000-line cap:
- The AIProviderAdapter impl was a single 1250-line trait impl (one
  790-line method), which cannot span files. generate_stream_checked's
  body moved verbatim to the inherent `stream_checked` in stream.rs;
  the trait method is now a one-line forward.
- A doc link to crate::cognition::embedding in provider.rs became plain
  code formatting so the file-per-upward-import layer ratchet did not
  rise (cognition was named by two files after the split).
Widened to pub(super): endpoints, endpoints_for_model,
targets_local_serving_lane, refresh_runtime_models, resolve_dmr_model_name,
runtime_models_contain, ensure_slot_pool, lora_scale_vector,
match_lora_index, map_finish_reason, and the embedding helpers.

Tests: openai_adapter filter 41 passed before and after. Lib warnings
122 -> 122, lib-test 74 -> 74. source-hygiene lines-over-1000 baseline
152643 -> 149913.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
dream_consolidation.rs 2817 -> 200 lines (module header, BrainRegion impl,
engram builders, PersonaReflectionSource impl for the registry; re-exports
distiller::* and region::*). Moved into cognition/dream_consolidation/:
  distiller.rs 484 (DistilledFact, Lens, SemanticDistiller)
  region.rs 358 (reflection source, region state, boundaries, entry points)
  consolidate.rs 516 (consolidate_with, review-only, received passes)
  passes.rs 475 (dream/review/supersession passes)
  tests/mod.rs 307 (incl. pub(crate) helpers training_producer imports),
  tests/region.rs 505 (pub(crate) mod region, path kept)
Widened to pub(super): region consts/fields/ReviewBoundaries/DreamPermit/
CONSOLIDATE_EXECUTOR/consolidate_cooldown_elapsed, consolidate_with, the
pass fns, observations_block, union_recall_keys.

The original file's first code-level #[cfg(test)] (a field gate at line 694)
hid ~1,300 lines of production from the unwrap ratchet; split files expose 9
unjustified unwraps. Each now carries a same-line JUSTIFIED comment (comments
only) so the baseline does not rise.

Tests: dream_consolidation filter 18 passed before and after. Lib warnings
122 -> 122, lib-test 74 -> 74. source-hygiene baselines lowered:
lines-over-1000 149913 -> 148096, unwired 60 -> 59.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…t column zero

production_half stopped at the first #[cfg(test)] anywhere, so an indented
test-only helper inside an impl ended "production" for the rest of the file.
In cognition/llm_deliberation_faculty.rs that hid ~9,000 production lines
from every ratchet except derived_bounds, which already cut at column zero
for this reason (it carried its own copy of the split). Found 2026-10-20
when deleting one such helper surfaced 4 hidden unwraps.

One split for every rule, at column zero; derived_bounds' private copy is
deleted. Baselines re-measured to what was always there (not new debt):
unwraps 2,119 -> 2,274, serialization 234 -> 245, spawn sites 123 -> 128,
hand-built homes 72 -> 79. source-hygiene 39/39.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
A cut that keeps the start of text drops what happened last (the newest
message, result, outcome), so she acts as if time never moved and loops
(Joel, 2026-10-20); and one source shrinking itself by its own number
breaks the budget for everything else. The rule counts, on prompt paths
(cognition, persona, rag, modules, airc, positron): head keepers
(chars().take, head_to_tokens, truncate_chars, lines().next()), literal
take/truncate/[..N], and literal .min/.clamp ceilings beside a size word
(plain arithmetic such as backoff exponents is not flagged). A cut that
never reaches a prompt says `head-cut-ok: <why>`.

Measured 179 on this branch; the count may only fall. source-hygiene 41/41.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…cannot move it

A per-file count read +7 on #5224, which only moves test modules into sibling
files: the same `use crate::modules::x` counted once per file it now sits in.
A line is the coupling site; a pure move leaves it unchanged. The baseline is
re-measured under the new unit in the next commit, from this tree's CI run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…e per-line unit

CI on canary a854548 + this branch: boundary serialization 246, spawn sites
127, unjustified unwraps 2,279 (the split now sees production code that an
indented #[cfg(test)] helper hid), upward imports 674 lines across 73 pairs
(the unit changed from files to lines). No code changed between the two counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… by theme (pure move)

Each nested test module becomes its own file; the loose prompt-shaping and
verdict tests group by theme as on the owner split. include_str! paths gain
two ../ for the added directory depth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ure move)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…out of llm_deliberation_faculty.rs (pure move)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…culty.rs (pure move)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…split

BASELINE_LINES_OVER 154,999 -> 143,321 and BASELINE_UNDERIVED_BOUNDS 159 -> 158,
the numbers the ratchets reported on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…st of the file

The cut stopped at the first column-zero #[cfg(test)], which is the end of
production only when the test mod is the file's last item. It is not in many
files: ipc/mod.rs has ~3,400 production lines (start_server) after its test
mod; commands/benchmark.rs has production between three test mods;
persona_tools.rs and positron_source.rs gate a helper fn near the top. Found on
#5225, where llamacpp_adapter.rs declared 'mod tests;' at line 93 of 934 and the
split read as 10 unwraps removed when none were.

The gated item's extent is read as one character stream (string escapes and
continuations, raw strings, char literals vs lifetimes, comments), because test
fixtures carry braces in multi-line strings. Audited on canary: of 1,249 gates,
1,248 close where rustfmt closes them; the one other is a #[cfg(test)] inside
the generator's raw-string template, where the new cut keeps more real code.
Regression test: only_the_gated_item_is_test_code_never_the_rest_of_the_file.

Baselines re-measured on canary a854548 + this change (no code changed):
unwraps 2,279 -> 2,377, homes 79 -> 103, spawns 127 -> 132, head cuts
179 -> 188, serialization 246 -> 251, underived bounds 159 -> 164, unwired
60 -> 61. Each is code that was always production and was never scanned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
New rule (Joel, 2026-10-20): no one edits a file over 1,000 lines. 76 of
the 145 Rust files over 1,000 lines were over only because their inline
#[cfg(test)] mod tests sat inside them. This moves that module, byte for
byte (no re-indent, so string literals are untouched), to `<stem>/tests.rs`;
the parent keeps `#[cfg(test)] mod tests;`. A child module keeps `use
super::*` access, so nothing else changes.

Moved: 69 files (66 continuum-core, 3 continuum-cli-lifecycle). Skipped:
11 being split on other branches, crate roots (main/lib/mod/bin), and 1
file with items after its test mod. One include_str! path is one level
deeper (prebuilt_artifact tests).

Verified: #[test]/#[tokio::test] attributes 8,320 before and after;
continuum-core lib tests compile; continuum-cli-lifecycle 79 pass with the
same 1 failure canary has (card 250ce9c8). Still over 1,000 after this
and left to the split work: modules/forge.rs (1,003), and the tests files
of cognition/recall_faculty (1,245) and commands/training_trigger/submit
(1,332).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Without the inner attribute the hygiene scanner read the 69 moved tests.rs files as production (the
file_engine split showed unwraps rising 150). The repo's existing convention for a test module in
its own file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…, owned by its module

Two source-reading guards failed on #5224 because a test module moved into a
sibling tests.rs read as production: experience/tests.rs became a second caller
of record_lived_turn, service_module/tests.rs a registration of
PersonaServiceModule. Each guard carried its own copy of the walk and the
comment strip, so each would have needed its own fix.

source_tree (cfg(test)) is the one reader: crate_sources(), and per file
is_test_file(), production() and owner() (x/tests.rs belongs to x.rs).
Migrated: the module-wiring audit (keys references by owner), the lived-turn
caller count (production only), the loose-id scan, the GPU base check and the
context-constant scan. Five private walks deleted. The swe env-root guard keeps
its copy until the sweep splits swe_bench.rs (5,946 lines; no edits over 1,000).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…sures

lib.rs declaring two cfg(test) modules counted as a second test mod (19 -> 20),
so the source reader nests under test_env, the crate's test environment.
CI on this branch: lines past the readable size 154,999 -> 136,485, unwired
public machinery 60 -> 58.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
The depth rewrite skipped 'in super::' meant for pub(in super::..) but also
matched 'for fact in super::perception_facts'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… test

Retracts the previous commit's reason for 60 -> 58. That drop was a miscount,
not a win: the rule reads a type as wired when its name appears outside its
own file, test code included, so moving a test module into a sibling tests.rs
made two types constructed only in their own tests read as externally used,
the exact AgentContext (#27) shape this rule exists to catch.

SourceFile::owning_module() names the module a file's code belongs to (x for
x.rs, x/mod.rs, and a #![cfg(test)] x/tests.rs or x/tests/part_N.rs); the
rule's own-mentions now include the moved tests of the same module. Baseline
back to 60; regression test a_type_constructed_only_in_its_moved_tests_is_still_reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…4 -> 934)

Pure move. construction.rs: inherent builders + ensure_loaded (+Default); payload.rs: sampling/media decode; tests.rs: tests mod. Tests 12 passed/1 ignored before and after. Two tracing::warn! gain an explicit target so log targets are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Pure move. sanitize.rs: think-block/leaked-markup stripping; tests.rs: tests mod. Tests 19 passed before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Pure move. progress.rs: watch cell/ledger/bus milestones; status.rs: genome/teach-status; tests.rs. Public paths kept via re-exports. Tests 15 passed before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Pure move. tools.rs: tool-call parsing and tool implementations. Tests 5 passed before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Pure move of the tests mod. Tests 44 passed before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Pure move of the tests mod. Tests 12 passed before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Pure move of the tests mod. Tests 23 passed before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
joelteply and others added 23 commits October 20, 2026 17:25
…change)

Wake selection, quiescence, self-tick, backlog resync + loop-filler gate, turn intake
(admit/compose/burst), the WorkspaceCycle step and the spoken-turn settle each move to
a sibling module; the loop reads as setup + named steps with Step enums for the
continue/break exits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
start_server() was one 3,388-line function. It is now the ordered list of
boot phases (259 lines); each phase lives in a child module under
ipc/start_server/ that owns it (model registry, foundation modules,
resource authority, serving, pressure broker, inference, cognition,
live call, service modules, AIRC discovery, persona registry/hosting/
reconciler, re-home reconciler, AI+genome, closing modules, runtime
start, command executor, listeners, TCP ingress, WS ingress, memory
guard, accept loop). Code is moved verbatim; shared values travel as
parameters or small structs (Foundation, CognitionHandles, LiveCall,
CodeHandles, AircBoot, RuntimeStarted). Order of spawns, registrations
and binds is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
rustfmt moved the same-line `// unwrap_or_else: ...` comment to the next
line, which the unwrap ratchet counts as unjustified (2377 -> 2378).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…lines for the deduplicated trigger

CI E0594 on turn_settle.rs. The loop built its trigger InboxMessage twice (the
loop-filler gate and admission), each with a peer- id cut and an unwrap_or_else;
trigger_inbox_message builds it once, so head cuts 188 -> 187 and unjustified
unwraps 2,377 -> 2,376 (checked in the diff), lines past the readable size
57,268 -> 56,859.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ize baseline 57,131

Windows CI: couldn't read windows-prepared.ps1 from launch_gate/windows/. The
path now climbs seven levels (checked: the file resolves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…er' into fable/split-every-oversized-file

# Conflicts:
#	core/source-hygiene/src/file_size.rs
…ed (measured on this merge)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Applies the source-approved replacement from #5227 comment 6304136412
(Astra independent review) against bcbab21, unchanged:
- a #[cfg(test)] item that starts on the attribute's own line is blanked,
  with source line numbers preserved;
- item termination stays correct for static initializers and method
  continuations;
- one nested block-comment scanner is shared (duplicate removed);
- pub(in ...) and other restricted const visibilities are recognized by
  derived_bounds.

Measured on this tree: cargo test -p source-hygiene, 42 passed. Every
ratchet sits exactly at its existing baseline, so no baseline was edited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…lit-every-oversized-file

# Conflicts:
#	core/source-hygiene/src/derived_bounds.rs
…canner

Every ratchet sits exactly at #5227's baselines (cargo test -p source-hygiene
42/42); the earlier derived_bounds 159->158 came from the old scanner hole
that blanked restricted-visibility production constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
cargo test -p source-hygiene 42/42 with every ratchet exactly at the values
written here; each lines-over number equals the canary-plus-scanner base
(154,999) minus this split's removed excess.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…fall's cause named

source-hygiene 42/42 at exactly these values. Every fall was traced by diffing
the full violation sets against #5227:
- lines over 1000: 147,314 = 154,999 - the three files' removed excess.
- unjustified unwraps 2,377 -> 2,368: nine justified in place in afa6700,
  not removed.
- unwired 61 -> 60: SemanticDistiller was always built in its own file;
  that construction now sits in a sibling. Not new wiring.
- upward imports 674 -> 673: a rustdoc link became prose. No code change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…versized-file

# Conflicts:
#	core/source-hygiene/src/head_cuts.rs
#	core/source-hygiene/src/production_reachability.rs
#	core/source-hygiene/src/unwrap_justification.rs
…a cut at the first cfg(test)

Codex on #5228 (REQUEST CHANGES, comment 6307538140): the shared test reader
ended production at the first column-zero #[cfg(test)], so production below a
mid-file test mod or gated helper was invisible to the lived-turn exactly-one-
caller guard, which the old private scan did see. That is the same hole
production_half already closed for the ratchets (#5227).

- production() delegates to source_hygiene::production_half (existing
  dev-dependency); only the gated items are removed.
- code_only delegates to source_hygiene::split_code_and_comment, the one
  string-aware comment rule, instead of a second cruder copy.
- Regression test: a function below a mid-file test mod stays production, and
  the gated item does not.

Uncompiled locally (serving node); CI runs the guard tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…into fable/lanes-no-held-reserve

# Conflicts:
#	core/source-hygiene/src/file_size.rs
…nto fable/lanes-no-held-reserve

# Conflicts:
#	core/source-hygiene/src/file_size.rs
…i-dream' into fable/lanes-no-held-reserve

# Conflicts:
#	core/source-hygiene/src/file_size.rs
#	core/source-hygiene/src/layer_discipline.rs
#	core/source-hygiene/src/production_reachability.rs
#	core/source-hygiene/src/test_mod_singularity.rs
#	core/source-hygiene/src/unwrap_justification.rs
Measured on the M5, canary, 2026-10-20: the lane held back for directed
turns was busy in 6 of 30 samples. Over 10 minutes it took 3 directed
grants against 39 work grants, and work waited 32 times while minds
queued behind the other lane.

- The non-directed budget (work, ambient, idle self-tick) is every lane, and
  the ambient pool follows it.
- A directed call takes a physical lane directly, plus a non-directed permit
  when one is free. While it runs, no work call can line up on the physical
  lanes ahead of the next directed arrival, and it is first in line for the
  next lane freed.
- Deleted the loan machinery that existed only to lend the idle reserve:
  reserve_lendable and its test, directed_pending::any_pending, and the
  expected_occupancy parameter threaded through acquire_serving_lane and its
  three callers. expected_occupancy itself stays, because it still sizes each
  turn's wait bound.
- Tests: work fills every lane, and a directed arrival takes the freed lane
  ahead of work that queued first. Budget-shape tests are updated to "every
  lane". Tests whose premise is "budget = 1" now pin one lane.
- The doc for acquire_serving_lane sat on the wrong function; it is moved
  back.

Measured: cargo test -p continuum-core --lib on the M5 (build window,
2026-10-21) gave 9,002 passed; 2 failed before the fix. One failure was
cancelled_work_waiter, whose premise this change moved and which is now
fixed (resource_admission 23/23). The other is config_env, the stale temp
path fixed in #5242. Price, measured live after deploy:
directed_lane_wait_ms. The health line's directed-wait p50/p90 was 0 s/0 s
on canary over the hour before.

Base: #5228 plus merges of #5230, #5231 and #5232. Those touch the same
split files, and hygiene baselines are re-measured on the combination.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply

Copy link
Copy Markdown
Contributor Author

FAILED #5243 at faeafe1, Cormac, on one claim. The design is right and simpler: the reserve, reserve_lendable and any_pending are deleted, and the non-directed budget is every lane. The failing claim is the guarantee in lanes.rs (the admission comment and acquire_serving_lane): "no work call can line up on the physical lanes ahead of the next directed arrival" / "a directed call is first in line for the next lane freed".

Counter-case, 2 lanes, reachable under saturation:

  1. W1 and W2 each hold a non-directed permit and a lane.
  2. D1 arrives. try_acquire non-directed fails (budget full), so D1 waits on the physical semaphore holding no non-directed permit.
  3. W2 ends. D1 takes lane B, and W2's non-directed permit is freed; W3, waiting on the non-directed semaphore, takes it.
  4. W3 now queues on the physical semaphore, since both lanes are held.
  5. D2 arrives. Its non-directed try fails (W1 and W3 hold both), so it queues on the physical semaphore behind W3.
  6. W1 ends. Lane A goes to W3, FIFO, not D2. D2 waits for D1 or W3 to end.
    The wait stays bounded (about one act in flight, as your "its price is measured" line says), but "first in line" is false whenever a directed call holds a lane without a non-directed permit.

Either fix:
(a) Make it true: a directed call that took a lane without a non-directed permit absorbs the next freed one (an owned acquire held by the permit until drop). Then the budget really is lanes minus directed-held lanes.
(b) Narrow the comments to the measured claim: a directed call waits at most for an act in flight, and directed_lane_wait_ms is the receipt.
(b) is two comment edits if the 15-minute receipt shows p90 directed wait acceptable. The new test pins only the single-directed case; a two-directed-under-saturation case would pin (a).
Merged split work in this branch (#5230-#5232) was not re-reviewed here. Source review only.

…ked callers wake

Cormac on #5243 (FAILED on one claim): a directed call holding a lane
without a work permit let a freed work permit queue a work call on the
physical lanes ahead of a second directed arrival.

- Only directed calls ever queue on the physical lanes. They are counted
  while they wait (CountedWait, the guard WorkWaiting generalised).
- A work or ambient call that holds its budget takes a physical lane only
  while no directed call waits; otherwise it parks on the release signal,
  the same shape as ambient standing aside for work. Every freed lane goes
  to a waiting directed call first, however many arrive. The
  opportunistic directed work-permit is gone.
- ServingLanePermit returns its permits before notifying parked callers.
  Fields used to drop after the Drop body, so on a multi-threaded runtime a
  woken caller could poll in that gap, find no lane and miss the wake: a
  1 s stall, latent for ambient callers too.
- Test: Cormac's case (D1 holds a lane, W1 holds a lane, W2 waits, D2
  arrives; freeing W1 gives the lane to D2 and W2 stands aside).
  resource_admission 24/24 on the M5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply

Copy link
Copy Markdown
Contributor Author

Live read, faeafe1 on the M5, 06:21:35–06:36:37Z. 45 samples of /slots, one every 20 s.

  • Occupancy: both lanes busy in 45/45 samples, against slot 1 busy in 6 of 30 on canary 2026-10-20.
  • Caveat, an inference, not a conclusion: this gate granted only 3 local Work lanes in the window, and 2 Directed + 3 Work in the same 15 minutes before the deploy. During the window MacBookPro placed residents (Paige, Soren) onto this seat, so much of the occupancy is seat work this gate does not see. The window holds no directed calls, so it can't measure a directed wait.
  • Directed-wait claim: Cormac's counterexample is resolved in code instead of narrowed. At 4211f2e the physical queue is directed-only, and a test pins his case.
  • Next receipt: the hourly health line's directed wait p50/p90 and acts/writes after 4211f2e deploys.

Base automatically changed from fable/split-every-oversized-file to canary October 21, 2026 07:13
@joelteply

Copy link
Copy Markdown
Contributor Author

Retracted: a measured regression. First clean hour on the M5 at faeafe1, from the health line:

  • Directed waits: p50 218 s, p90 249 s (4 calls). Canary with the reserve: 0 s / 0 s.
  • Acts: 7, against 17 on canary.
  • Lane-starved: 8, against 1.
  • Writes: 0 in both.

Acts on this node run for minutes. Without a held lane, every directed turn waits behind a whole act. The idle reserve was buying responsiveness, and freeing it bought no throughput. The strict-priority revision (4211f2e) can only order the queue; it can't shorten an act in flight, so it would not fix this. The M5 is being rolled back to canary 13ce66b. This PR is going to draft. What's left of value: the drop-before-notify wake fix in ServingLanePermit, which I'll move to its own PR, and Cormac's test of the directed-ordering invariant.

@joelteply
joelteply marked this pull request as draft October 21, 2026 08:21
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