The first amd64 relaunch (stopped by Captain Bob after 1h41m) showed Hera's
busy wait taking an equal CPU share and writing 43% of all heartbeat rows.
100 WAIT charges her 100 beats of work heat per iteration while executing
almost nothing, so workers get the CPU. cfg-0 check: 115 s vs ~210 s,
fail=0, 2/2 dead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each shown dead three ways (FABRIC-3.5 §XXII): the pump loop by its
condition, by construction (birth cap == switch table capacity, every birth
registers) and at run time (probe silent through boot, G interleave, an
8-worker DoE trial and the relay); SWITCH-MARK-WORK by callers, capsules
(source and baked), experiments/tools/logs; the depth counter and back_to
field by having no readers.
Kept: Hera's own pump drain (last timer-paced delivery, Hera only) and the
always-0 readiness CSV columns (a DoE schema call). Acceptance on amd64/
aarch64/riscv64: G interleave unchanged, cfg-0 trial fail=0 2/2 dead,
relay 3, idle growth 0; dict_hash set changed by the removed word
(word_count 531) and identical across ISAs. FABRIC-3.8 stages A-I done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Workload capsules the DoE births are definitions only; each defines WL-RUN
(its old birth-time work plus its entry word twice) and WL-DONE-ARGS. The
driver births N workers, SENDs each 'WL-RUN WL-DONE-ARGS SEND', waits in an
ordinary loop for N MU-DONE messages, KILLs them (parked idle), and emits
the marker. The serialized VM-EXEC round-robin is gone.
Kernel: no switch checkpoint after a payload's last word; work heat is the
assessed per-beat charge, and a VM gaining work is raised to the busy set's
floor (Captain Bob's ruling) -- fixes a live stall where a busy-waiting Hera
never yielded, without serializing batch starts.
Evidence: cfg-4 rows spread evenly over Hera and all 8 worker slots (44k
switches); full cfg-4 trials on amd64/aarch64/riscv64 end fail=0 with 8/8
KILL dead; Stage G interleave byte-identical on all three. Acceptance:
dict_hash unchanged, relay 3, idle growth 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SEND ( paddr plen name-c name-u -- ok? ): general kernel-Hermes send to a
named VM, from = calling VM, one block max (refused, not truncated), every
VM. New SK_HERMES_MSG_TYPE_SEND (30).
Ruling C-4 (beta), Captain Bob: the switch decision compares work heat --
the heat each VM pulled in by its own per-beat charges -- not absolute
heat, so Hera's structural endowment no longer makes her hottest. Before
it, the two-worker test ran serial (AAAA...BBBB); after it, byte-identical
on amd64/aarch64/riscv64:
AAABBBBBBBBAAAAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAABB
(40 A, 40 B, 16 runs; ordinary words only, nothing yields). Acceptance:
relay 3, idle growth 0; dict_hash set changed by the new word
(word_count 533) and identical across ISAs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
capsule_birth_baby() registers each VM at its LIVE completion point, beside
the common-channel subscription. register() is idempotent. The Artemis/
Hestia registrations in kernel_main.c and WIREBIND's own call are removed
(Hera, never born through capsule_birth_baby, stays with set_current).
Probe (restored away): Tripod registered once each (3 slots), a
WORKER-BIRTH VM becomes a participant with no hand registration (4) and
KILL frees it (3), WIREBIND registers exactly console+user, zero
duplicates, on amd64/aarch64/riscv64. Acceptance: dict_hash unchanged,
relay 3, idle growth 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sk_vm_parked_idle(): saved context + exactly its own IDLE frame.
capsule_vm_kill_refusal() is the single kill rule, shared by
capsule_vm_kill() and the KILL word: refuse SWITCHED_OUT mid-task, parked
with mail pending, or the VM running; allow parked-idle. capsule_vm_kill()
now unregisters the switch slot (G6).
Finding fixed: vm_registry_alloc() never initialized pending_reap (kmalloc
does not zero); C-b's reap_pending walks every participant, so garbage
reaped a healthy VM (seen live before any KILL). Now zeroed at alloc.
Probe (reverted): baseline KILL refused, slots 3->4->4 (FAIL, proves G6);
with Stage D dprobe parked idle, KILL dead, slots 3->4->3, PASS on amd64/
aarch64/riscv64. Acceptance: dict_hash unchanged, relay 3, idle growth 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Who runs next is now sk_vm_switch_decide(): among participants with work
(pending kernel-Hermes mail, parked mid-task, or Hera with console input),
the coolest by VM-physics heat; ties with the incumbent keep the incumbent,
ties among candidates go to the longest waiting (unique wait stamps). No
scheduler state, no timer: heartbeat_tick() no longer touches switching,
readiness/threshold/has_work/take_pending removed. Decided at owned word
checkpoints (colon words and, new, between top-level words) after a beat
or new mail, at the trampoline between messages, and at Hera's idle point
in the console poll loops. The idle pump skips participants (G3 closed for
them): the WIREBIND relay is delivered by a real switch to rajames~user.
Probe (restored away): relay via switch Hera->rajames~user->Hera on all
three ISAs, parked saved_sp inside own stack, walk-order disagreements 0
over tens of millions of decisions, 0 bytes idle log growth. Acceptance:
dict_hash unchanged, relay 3, no faults. Rulings: C-3, C-5 as proposed;
C-4 taken as (alpha), consequence recorded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vm_physics_charge(): per-beat slope into a per-VM carry, whole units pulled
from the rest of the live fleet in proportion to heat (conservative
vm_physics_transfer), rounding shortfall kept in the carry, no fleet_window
sample. Called from vm_tick() only for the switch-current VM. Closes G9:
a VM running its own mail on its own stack now heats up while it works.
Probe (reverted): baseline Hestia heat 0->0->0 over 71 beats (FAIL, proves
G9); with charge 0->11->23 at beats 15->51->86, identical on amd64/aarch64/
riscv64, conserved, PASS. Acceptance: dict_hash unchanged, WIREBIND relay 3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Captain Bob: "give frames IDs and call by ID" -- frames stack per VM, each
with an ID. New vm_frame.h/.c: VMFrameEntry { VMFrameID id; VMUuid vm_id;
kind (INTERPRET/DRAIN/IDLE) }, a growable VMFrameStack embedded as VM.frames
(reserved at birth, freed in vm_cleanup, grown by allocate-copy-free).
vm_interpret() -> vm_interpret_kind() pushes a frame onto the running stack's
owner, holds only its ID, fails closed if it cannot grow, and pops by ID
(mismatch reported as VM FRAME CORRUPTION).
The Stage A/B loose fields are gone and everything is derived from the stack:
- ownership gate: one frame, and it is vm's own
- drain guard: a DRAIN frame for vm already on the running stack
- parked-idle: an IDLE frame the trampoline pushes around its yield
- log attribution: the top frame's vm_id, resolved by ID through the registry
The single saved-and-restored attribution global is removed.
Evidence: the probe baseline on the pre-frame code read HSSAS (the global
named Hestia while Hera resumed). With frames it reads HSHAS on all three
ISAs, with Hestia's top frame IDLE and Hera's frames back to 0; the probe is
reverted. Formal acceptance on all three ISAs: dict_hash identical and
unchanged, zero UNKNOWN WORD, faults, frame corruption or refused interprets,
and the WIREBIND relay 1 2 + . -> 3 works.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sk_vm_switch_entry() stops being the Stage-2 placeholder bounce and becomes
every switch participant's own idle loop: it drains one of its own
kernel-Hermes messages per pass, on its own native stack (driven by the drain's
return value, so a non-consuming drain cannot spin), and when nothing is
drained it marks the VM parked_idle and yields back. It is entirely OS-driven,
with no VM-side yield word. sk_hermes_drain_checkpoint() gains a per-VM
hermes_draining guard so a payload's own in-word checkpoint cannot drain a
second message inside the first. Still inert in normal operation: nothing
marks work until Stage C.
Finding fixed here: amd64 sk_vm_switch_prime built the first-entry frame at
top-56, entering with rsp 16-aligned instead of the SysV-required 8 mod 16, so
the first aligned SSE spill in a callee took #GP (logs/20260923-142526, kept).
It had been latent since SXXVIII Stage 2 because the placeholder never called
anything. The frame is now at top-64 with a zeroed pad slot. aarch64 and
riscv64 were already correct.
Evidence: a temporary kernel_main probe (reverted, not in this commit) queued
two messages to Hestia, the first calling a colon word, and switched into her.
It passed on all three ISAs: 0 pending, parked_idle, depth 0, context saved on
her own stack, SB-SEQ=12 (drained one at a time, not nested), ledger and
stadium_conserved restored. Formal acceptance without the probe on all three
ISAs: dict_hash identical and unchanged, zero UNKNOWN WORD or faults, and the
live WIREBIND mint -> USE -> 1 2 + . -> 3 relay.
FABRIC-3.8.md also records Captain Bob's rulings that no VM-side word ever gives
up control and that multitasking is a compudynamic hybrid, and opens SS3.12
(VM frames for per-VM execution state).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the global g_vm_interpret_depth test at both execute_colon_word()
checkpoints (kernel-Hermes drain, switch-signal take) with
sk_vm_owns_outermost_frame(vm): the VM physically owns the stack
(sk_vm_switch_current_vm() == vm), is at its own outermost vm_interpret()
frame (new per-VM VM.interpret_depth), and is not compiling. Once VMs
interleave on their own native stacks, a global depth count across all VMs
no longer means "this VM owns this stack"; this is the invariant it was
approximating. The global and the old function stay until Stage I.
Acceptance, all three ISAs, foreground with the framebuffer visible, coupled
disk images reset before each: zero UNKNOWN WORD, zero self-test failures,
dict_hash identical across ISAs and unchanged from the pre-Stage-A boot, and
the live WIREBIND mint -> attach -> USE -> 1 2 + . -> 3 relay with
stadium_conserved(self)=true. Logs: 20260923-140120 (amd64), -140654
(aarch64), -141303 (riscv64).
FABRIC-3.8.md also records Captain Bob's ruling that preemption comes from
the compudynamics, not the timer: principle 5 is corrected and the design
question is opened as SS3.11, to be ruled before Stage C.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-ISA amd64 campaign (12345 3 S" amd64" DOE) was stopped ~49 min in,
zero trials complete: its harness drives workers by synchronous VM-EXEC on
Hera's stack, so the concurrency factor counted resident VMs while execution
stayed serialized. Tracing further showed the v2.1.0 reshuffle stops short of
real concurrency: kernel-Hermes never publishes eligibility (no mark_work in
sk_hermes_send_one), the switch trampoline is still the Stage-2 placeholder,
delivery to passive VMs still goes through the idle pump, only Tripod/WIREBIND
VMs are switch participants, and the stack-ownership gate is a single global.
FABRIC-3.8.md records the gap analysis and a staged A-I design, each stage its
own commit and 3-arch acceptance. FABRIC-3.md SXXXV.16 points to it. Logs and
extracted CSVs from today's boots and the stopped run are kept as audit
artifacts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>