Stage G: SEND, and two workers interleave; decision compares work heat (FABRIC-3.8 §3.8, §3.13.7)

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>
This commit is contained in:
Robert Allan James
2026-09-23 21:01:44 -04:00
co-authored by Claude Opus 5.5
parent c7185facf0
commit 81def7b4a5
18 changed files with 46044 additions and 7 deletions
+125 -2
View File
@@ -666,6 +666,55 @@ without §3.7's idle point** (Stage F):
the idle point she is eligible when input is pending (the REPL's own input poll), exactly as a
worker is eligible when its kernel-Hermes queue is non-empty. **Ruling C-5**, below.
#### 3.13.7 Finding at Stage G: under C-4 (α), a sender can't dispatch a batch (2026-09-23)
`SEND` is built (§3.8). The naive interleave test, typed at `[zuse@Hera] ok>` with ordinary
words only (`logs/20260923-201602/amd64/`):
```
S" workload-1-lite.4th" S" wa" WORKER-BIRTH . → 1
S" workload-1-lite.4th" S" wb" WORKER-BIRTH . → 1
S" : LA 40 0 DO 65 EMIT 300 0 DO LOOP LOOP ; LA" S" wa" SEND . S" : LB 40 0 DO 66 EMIT 300 0 DO LOOP LOOP ; LB" S" wb" SEND .
→ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB1 ok
```
`SEND` works: each payload is delivered by a switch and runs on the worker's own stack. But the
run is **serial**, and the mechanism is exactly C-4 (α):
- The first `SEND` makes `wa` eligible at heat 0.
- Hera (heat ≈ `Q48_ONE`) is displaced at her next owned checkpoint, before she even prints that
`SEND`'s `1`.
- She is eligible only as "parked mid-task" and is the hottest VM, so she gets the CPU back only
once `wa` has no work left. Then she sends to `wb`.
The rebuilt DoE driver (Stage H) would serialize the same way: it is Hera sending to N workers in
a loop. So would the console under load (already recorded under C-b).
**Needs Captain Bob's ruling (C-4 reopened):**
- **(β) compare work heat, not absolute heat.** Work heat is each VM's cumulative heat pulled in
by its own per-beat charges (Stage C-a). It is conserved-transfer bookkeeping already happening,
so no new physics, but it is a different quantity than the one "coolest" has meant so far.
Hera's structural endowment doesn't count.
- Predicted: Hera keeps the CPU while she has work, because on a tie the incumbent stays and the
workers' heat is still 0. She sends both messages, and the workers then alternate by work
heat.
- Hera with pending console input is cooler than busy workers, so the console gets turns under
load.
- **(δ) mail arrival stops triggering a decision for the running sender.** Decisions happen only
at beats, the trampoline and idle points.
- Batches sent within one beat all land before anyone runs.
- Where the beat boundary falls decides whether a given batch lands intact. It's deterministic,
not guaranteed.
- It doesn't address the console starvation.
- **(ε) keep (α)** and prove interleaving with a chain instead: worker A's payload `SEND`s B's.
That satisfies Stage G's evidence, but leaves the DoE driver and the console serialized.
**RULED 2026-09-23, Captain Bob: (β) work heat.** The decision compares
`vm_physics_work_heat_of()`, not `vm_physics_heat_of()`. Absolute heat still moves, is still
conserved, and still appears in the DoE heat columns; it just no longer decides turns. The
prediction held on all three ISAs: see the Stage G entry in §6.
#### 3.13.6 Stage C, split
This splits the single C row in §4 in two:
@@ -715,7 +764,7 @@ QMP `send-key` computing a value, per `FABRIC-3.6.md` task 1.5).
phase is the concurrent one). If the latter: new `-def` capsule variants, or strip the tail
call from the existing `workload-N.4th` files (check their other users first, per `FABRIC-3.5.md`
§XXII.2's three reachability routes)?
3. **The send word's name** (§3.8): `SEND`, `HERMES-SEND`, `KH-SEND`, something else.
3. **The send word's name** (§3.8) — **RULED 2026-09-23: `SEND`.**
4. **The multiuser factor.** The DoE as built varies concurrency × workload mode and runs once
per ISA. `FABRIC-3.md` §XXXIII.2 planned identity origin as a fixed arm, never built. Should
identities (`UNATTENDED-BIRTH` capsule identities, or WIREBIND thumbdrives) become a crossed
@@ -733,7 +782,8 @@ QMP `send-key` computing a value, per `FABRIC-3.6.md` task 1.5).
8. **C-3 (§3.13.3):** ties. The incumbent stays on a tie with itself. Among several candidates:
whether "waiting longest," by a single-writer arrival stamp, is acceptable under *"no scheduler
allowed,"* or whether Bob wants something else. Slot, UUID and registry order are rejected.
9. **C-4 (§3.13.2):** Hera's reservoir mass means the console gets no turn under worker load.
9. **C-4 (§3.13.2) — RULED 2026-09-23: (β) work heat** (§3.13.7; first taken as (α) at C-b,
reopened at Stage G). Hera's reservoir mass means the console gets no turn under worker load.
Options are (α) accept it, (β) compare charged heat instead of absolute heat, or (γ) something
else. No recommendation.
10. **C-5 (§3.13.5):** fold the idle point (Stage F's §3.7) into C-b, since the relay can't be
@@ -1103,3 +1153,76 @@ explicit instruction.
**What this makes possible (not exercised yet):** every `WORKER-BIRTH`ed DoE worker is now
a participant. Mail sent to a worker is delivered by a switch onto its own stack, and it
competes by heat. Stage G's `SEND` word and interleave test are next.
- [x] **Stage G — `SEND`, and two workers interleave (§3.8, §3.13.7).** 2026-09-23. Captain
Bob: *"SEND, go ahead with stage G"*, then ruling C-4 (β), work heat, when the first run
came out serial.
Changes:
- `repl.c`: new `SEND ( paddr plen name-c name-u -- ok? )`, registered for every VM
through the standard word set (`sk_repl_register_words`).
- It resolves the target by registry name, copies the payload (at most 1023 chars plus
the NUL; oversized payloads are **refused, not truncated**), and calls
`sk_hermes_send_one()`. That stamps the message and sets decision-due (C-b).
- `from` is the calling VM, derived in C, never taken from the stack.
- It returns 1 if admitted, 0 if refused.
- No C-side restriction; ACL is `ACL.4th`'s job.
- `kernel_hermes.h`: new `SK_HERMES_MSG_TYPE_SEND` (30), clear of every older type space.
- **C-4 (β):**
- `capsule_vm_physics.c/.h`: a per-VM `work_heat_q48` accumulates what each of the VM's
own per-beat charges actually moved in. Read with `vm_physics_work_heat_of()`.
- `sk_vm_switch_decide()` compares work heat for candidates and for the incumbent.
**First run, before (β):** `logs/20260923-201602/amd64/`, the §3.13.7 finding. It came
out `AAAA…A1 BBBB…B1`, serial.
**Evidence, as named in §4.** Only ordinary words, typed at `[zuse@Hera] ok>`, no probe:
```
S" workload-1-lite.4th" S" wa" WORKER-BIRTH .
S" workload-1-lite.4th" S" wb" WORKER-BIRTH .
S" : LA 40 0 DO 65 EMIT 300 0 DO LOOP LOOP ; LA" S" wa" SEND . S" : LB 40 0 DO 66 EMIT 300 0 DO LOOP LOOP ; LB" S" wb" SEND .
```
Result, **byte-identical on all three ISAs** (`logs/20260923-202403/amd64/`,
`-204054/aarch64/`, `-205150/riscv64/`):
```
[zuse@Hera] 1 1 ok
[zuse@Hera] ok> AAABBBBBBBBAAAAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAABB
```
- **A = 40, B = 40, in 16 alternating runs.**
- Hera kept the CPU while she had work (work heat tied at ~0, so the incumbent stays), and
both sends landed.
- From her idle point, `wa` and `wb` took turns. Each switch came from the per-beat charge
making the runner strictly hotter by work heat.
- Nothing in either payload asks to yield.
- The sequence is deterministic across ISAs, as the execution-paced beat implies.
**Acceptance,** in the same three boots, after the interleave test with both workers still
alive:
- All reach `[zuse@Hera] ok>` with zero `UNKNOWN WORD`, failures and faults.
- Mint → WIREBIND → `USE` → `1 2 + .` → `3`, with `stadium_conserved(self)=true`.
- Idle log growth: 0 bytes.
- **The `dict_hash` set changes, as expected, because `SEND` is a new word in every VM's
dictionary, and is identical across all three ISAs.**
| | New | Stages A–E |
|---|---|---|
| `word_count` | 533 | 532 |
| M7.1a | `0x330b28ce0f4e1c75` | `0x3933d76939ef66c6` |
| Hera | `0x0bf15066a73f6e32` | `0x27ba08011349842b` |
| Hestia | `0x9116e19b34d2cb0b` | `0xdd1aa879b3a6c5df` |
| Artemis | `0x165a9a66eafce131` | `0xe9d0408b1119d51f` |
- Clean build on all three plus hosted, no new warnings.
**Harness note (not a kernel change):** the first aarch64 attempt
(`logs/20260923-203226/aarch64/`, kept) typed the next line while Hera was still birthing
`wa`. Serial input received while Hera isn't polling is dropped, so the line arrived
truncated (`S" workload-1-li…`) and gave `UNKNOWN WORD`. My test script now waits for each
prompt before typing. Serial input is lost while the console isn't reading, which is the
pre-existing behaviour.
**Consequences of (β) to know:**
- Work heat only grows. A long-lived VM that has done a lot of work loses ties to newer
ones until they catch up.
- Hera's work heat grows only when other VMs hold absolute heat for her charges to pull
from. She runs cheaply while they hold ~0.
- Both are properties of the ruled quantity, recorded here, not changed.
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-23T23:59:50Z -->
<!-- Generated by mkcapsule --manifest 2026-09-24T00:51:15Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
+12
View File
@@ -111,6 +111,18 @@ void vm_physics_touch(VMUuid vm_id);
*/
void vm_physics_charge(VMUuid vm_id);
/**
* vm_physics_work_heat_of - Cumulative heat a VM has pulled in by working
*
* FABRIC-3.8.md §3.13.7, ruling C-4 (β), Captain Bob 2026-09-23: the sum of
* every transfer vm_physics_charge() actually moved INTO this VM -- heat
* earned by its own work, independent of any structural endowment (Hera's
* initial Q48_ONE). This is the quantity the switch decision compares
* ("coolest VM with work" = least work heat). Only grows; 0 for an unknown
* or dead VM.
*/
uint64_t vm_physics_work_heat_of(VMUuid vm_id);
/**
* vm_physics_tick - Heartbeat-gated inference pass
*
@@ -138,7 +138,9 @@ int sk_vm_switch_signal_take_decision_due(void);
* Among registered participants other than `self` that have work -- a
* pending kernel-Hermes message, or parked on their own stack mid-task
* (top frame not VM_FRAME_IDLE), or (Hera only) pending console input --
* returns the one with the lowest VM-physics heat; among equally cool
* returns the one with the lowest work heat (vm_physics_work_heat_of(): heat
* earned by its own per-beat charges -- ruling C-4 (β), FABRIC-3.8.md
* §3.13.7; Hera's structural endowment doesn't count); among equally cool
* candidates, the one waiting longest (unique wait stamps). With
* self_competes set (the caller still has work), returns a candidate only
* if it is strictly cooler than `self`. Returns NULL when nobody should
+8
View File
@@ -752,6 +752,14 @@ int sk_hermes_reassemble(SkHermesMessage **chunks, int n_chunks,
* Phase 4 entry. */
#define SK_HERMES_MSG_TYPE_ELEVATE_REQUEST 8
/* SK_HERMES_MSG_TYPE_SEND (FABRIC-3.8.md §3.8, Stage G, 2026-09-23) --
* a general message from the FORTH SEND word (repl.c): payload is FORTH
* text the recipient interprets, like CONSOLE_CMD, but from any VM to any
* VM. A fresh kernel-Hermes-space value, clear of messaging.4th's old type
* space and the negotiation types (20-24) listed below -- no FORTH-side
* type ever existed for it to reuse. */
#define SK_HERMES_MSG_TYPE_SEND 30
/* The five negotiation types above (20-24) were chosen clear of
* messaging.4th's own live/reserved type space (PAUSE-EVENT=2,
* RESUME-EVENT=3, KILL-EVENT=4, CONSOLE-CMD-EVENT=7, ELEVATE-REQUEST=8,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -67,6 +67,16 @@ typedef struct {
* FABRIC-3.8.md §3.13.2: kept, never truncated
* away, so a one-beat charge (slope < 1 unit)
* still moves heat once enough beats accrue */
uint64_t work_heat_q48; /* FABRIC-3.8.md §3.13.7, ruling C-4 (β), Captain
* Bob 2026-09-23: cumulative heat this VM has
* pulled in through its OWN per-beat charges --
* heat earned by working. The switch decision
* compares this, not execution_heat_q48, so
* Hera's structural endowment (all of Q48_ONE
* at init) never counts against her. Only ever
* grows; a record of transfers that already
* happened, not heat of its own (fleet K is
* unaffected). */
} VMPhysics;
typedef struct vm_physics_node {
@@ -208,6 +218,7 @@ void vm_physics_init(VMUuid vm_id)
node->physics.touched = 0;
node->physics.is_live = 1;
node->physics.charge_carry = 0;
node->physics.work_heat_q48 = 0;
return;
}
@@ -220,6 +231,7 @@ void vm_physics_init(VMUuid vm_id)
node->physics.touched = 0;
node->physics.is_live = 1;
node->physics.charge_carry = 0;
node->physics.work_heat_q48 = 0;
node->next = vm_physics_head;
vm_physics_head = node;
}
@@ -407,6 +419,16 @@ void vm_physics_charge(VMUuid vm_id)
* vanishing. Conservation is unaffected either way -- only what
* vm_physics_transfer() actually moved ever changed any heat. */
target->physics.charge_carry += (moved_total - moved) << 16;
/* §3.13.7 (β): what this VM's own work actually pulled in. */
target->physics.work_heat_q48 += moved;
}
uint64_t vm_physics_work_heat_of(VMUuid vm_id)
{
vm_physics_node_t *n = vm_physics_find(vm_id);
if (!n || !n->physics.is_live) return 0;
return n->physics.work_heat_q48;
}
/**
@@ -34,7 +34,7 @@
#include "starkernel/kmalloc.h"
#include "starkernel/console.h"
#include "starkernel/capsule_birth.h" /* registry, capsule_vm_force_reap() */
#include "starkernel/capsule_vm_physics.h" /* vm_physics_heat_of() -- the decision's heat */
#include "starkernel/capsule_vm_physics.h" /* vm_physics_work_heat_of() -- the decision's heat (C-4 β) */
#include "starkernel/vm/kernel_hermes.h" /* sk_hermes_pending_peek() -- eligibility is the queue */
#include "starkernel/vm/vm_frame.h" /* top frame kind -- in progress vs parked idle */
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h" /* sk_get_mama_vm() */
@@ -222,7 +222,10 @@ static int candidate_standing(VM *v, VM *mama, uint64_t parked_stamp,
}
}
if (!eligible) return 0;
*heat_out = vm_physics_heat_of(v->stadium_vm_id);
/* §3.13.7, ruling C-4 (β): work heat -- heat earned by this VM's own
* work -- not absolute heat, so Hera's structural endowment never
* makes her the "hottest" VM by construction. */
*heat_out = vm_physics_work_heat_of(v->stadium_vm_id);
*stamp_out = stamp;
return 1;
}
@@ -260,7 +263,7 @@ VM *sk_vm_switch_decide(VM *self, int self_competes) {
/* A running VM that still has work keeps the CPU unless the best
* candidate is strictly cooler: a tie with the incumbent is "no reason
* to move," not a coin toss (ruling C-3). */
if (best && self_competes && !(best_heat < vm_physics_heat_of(self->stadium_vm_id))) {
if (best && self_competes && !(best_heat < vm_physics_work_heat_of(self->stadium_vm_id))) {
return (VM *)0;
}
return best;
+63
View File
@@ -489,10 +489,73 @@ static void sk_word_kh_elevate_send(VM *vm) {
vm_push(vm, rc == 0 ? 1 : 0);
}
/* FABRIC-3.8.md §3.8 (Stage G, 2026-09-23; name ruled by Captain Bob:
* "SEND"). SEND ( paddr plen name-c name-u -- ok? ): the general
* FORTH-callable kernel-Hermes send. Delivers a copy of the payload text
* to the VM named name-c/name-u, where it is interpreted -- on that VM's own
* native stack, by a switch, once the compudynamic decision chooses it.
* `from` is the calling VM, derived here, never taken from the stack (the
* anti-spoof rule KH-ELEVATE-SEND established). Bounded to one block of
* text (the payload is sent NUL-terminated, so at most
* SK_HERMES_CHUNK_MAX_PAYLOAD - 1 characters); an oversized payload is
* refused, not truncated -- truncated code would run as different code.
* A plain, unconditional primitive: restricting who may SEND is ACL.4th's
* job, never a C check. Returns 1 if admitted, 0 if refused (no such VM,
* dead, oversized, bad address, or kernel-Hermes refused: queue full or the
* sender's reservoir can't fund it). */
static char g_send_buf[SK_HERMES_CHUNK_MAX_PAYLOAD];
static void sk_word_send(VM *vm) {
cell_t name_u, name_c, plen, paddr;
char name[VM_NAME_MAX];
VMRegistryEntry target;
const char *src;
int i, rc;
if (vm->dsp < 3) {
console_println("SEND: expects paddr plen S\" name\" SEND");
vm->error = 1;
return;
}
name_u = vm_pop(vm);
name_c = vm_pop(vm);
plen = vm_pop(vm);
paddr = vm_pop(vm);
if (name_u <= 0 || (uint32_t)name_u >= VM_NAME_MAX ||
plen < 0 || plen > SK_HERMES_CHUNK_MAX_PAYLOAD - 1) {
vm_push(vm, 0);
return;
}
src = (const char *)vm_ptr(vm, (vaddr_t)name_c);
if (!src) { vm_push(vm, 0); return; }
for (i = 0; i < (int)name_u; i++) name[i] = src[i];
name[name_u] = '\0';
if (capsule_vm_find_by_name_nocase(name, &target) != 0 || !target.vm_ptr ||
target.state == VM_STATE_DEAD || target.state == VM_STATE_STILLBORN) {
vm_push(vm, 0);
return;
}
if (plen > 0) {
src = (const char *)vm_ptr(vm, (vaddr_t)paddr);
if (!src) { vm_push(vm, 0); return; }
for (i = 0; i < (int)plen; i++) g_send_buf[i] = src[i];
}
g_send_buf[plen] = '\0';
rc = sk_hermes_send_one(vm->stadium_vm_id, target.vm_id,
SK_HERMES_MSG_TYPE_SEND, 0,
g_send_buf, (uint32_t)(plen + 1));
vm_push(vm, rc == 0 ? 1 : 0);
}
void sk_repl_register_words(VM *vm) {
register_word(vm, "BLK-ATTACH-ACK", sk_word_blk_attach_ack);
register_word(vm, "KH-BLK-ATTACH-SEND", sk_word_kh_blk_attach_send);
register_word(vm, "KH-ELEVATE-SEND", sk_word_kh_elevate_send);
register_word(vm, "SEND", sk_word_send); /* FABRIC-3.8.md §3.8 */
}
/*===========================================================================