Captain Bob ruled directly: all four subsystem docs are superseded, not individually assessed for partial staleness case-by-case. FABRIC.md (design history) and FABRIC-2.md (current/living) are the sole design-of-record for Tripod/Hermes/Artemis/Console work now. Added a superseded-header banner to the top of all four .claude/*.md files, pointing to FABRIC.md/FABRIC-2.md. Corrected .claude/CLAUDE.md's own pointer paragraph, which previously claimed these four were individually "authoritative" for their subsystems - that's now wrong. Closes FABRIC-2.md's three open documentation questions (CONSOLE.md's fate, HERMES.md's stale block map, 5.3's larger shrink-the-docs ask) at once: the header approach makes reconciling a superseded document's internal accuracy moot, and accomplishes what "shrink to a pointer" was already trying to do. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
345 lines
12 KiB
Markdown
345 lines
12 KiB
Markdown
# HERMES.md — Hermes VM Architecture
|
||
# StarshipOS / StarForth — Captain Bob (Robert Allan James)
|
||
# This document constrains Claude Code behavior. Read it completely before touching Hermes.
|
||
|
||
---
|
||
|
||
> **SUPERSEDED (Captain Bob, 2026-08-15).** This document is no longer authoritative.
|
||
> `FABRIC.md`/`FABRIC-2.md` (repo root — read `FABRIC-2.md` first, it is the current/living
|
||
> one) are the sole design-of-record for Hermes work now. Kept here as historical record
|
||
> only — note its message-layout table (8 cells) is confirmed stale against the real shipped
|
||
> capsule (9 cells, `capsules/hermes/init.4th`), see `FABRIC-2.md`. Do not read this for
|
||
> current design authority. See `.claude/CLAUDE.md`'s own pointer.
|
||
|
||
---
|
||
|
||
## Language Constraint — Non-Negotiable
|
||
|
||
All Hermes implementation is in StarForth dialect ONLY.
|
||
|
||
This includes:
|
||
- Data structures
|
||
- Constants
|
||
- Variables
|
||
- Control flow
|
||
- Everything
|
||
|
||
C99 is forbidden unless explicitly blocked in StarForth AND explicit written permission
|
||
is given by Captain Bob for that specific construct. "I could not figure out how to do
|
||
this in StarForth" is not permission. Ask first. Wait for the answer.
|
||
|
||
If you are about to write a C struct, a #define, or a C variable — stop.
|
||
Implement it in StarForth or ask.
|
||
|
||
---
|
||
|
||
## What Hermes Is
|
||
|
||
Hermes is the messenger. He moves messages between VMs and manages channels.
|
||
That is his complete contract.
|
||
|
||
Hermes does **not** store persistent data — that is Artemis.
|
||
Hermes does **not** make lifecycle decisions — that is Hera.
|
||
Hermes does **not** route by load or heat — dispatch is capability-based, always.
|
||
|
||
Capsules are baked into the OS blob by mkcapsule.c at build time.
|
||
Hermes has no capsule cache, no fetch logic, no repository concern.
|
||
That is a future chapter.
|
||
|
||
---
|
||
|
||
## The Event Loop
|
||
|
||
Hermes runs a compudynamic event loop. Every message is a compudynamic object:
|
||
|
||
- Born hot (full heat at creation)
|
||
- Cools each heartbeat tick
|
||
- ACK received → clean death, K redistributed to fleet
|
||
- NACK received → requeue with reduced heat, or reaped if heat floor reached
|
||
- TTL expires (heat → 0) → Hermes reaps the message, K rebalanced
|
||
|
||
There is no special timeout logic. The physics handles it.
|
||
A message that nobody answers simply cools to death.
|
||
|
||
---
|
||
|
||
## Message Structure
|
||
|
||
Every message carries:
|
||
|
||
```
|
||
type — work type tag (determines recipient)
|
||
sender — originating VM identity
|
||
recipient — destination VM identity
|
||
payload — message body
|
||
heat — current thermal value (compudynamic)
|
||
sequence — monotonic sequence number
|
||
```
|
||
|
||
Point-to-point only. Sender addresses recipient directly.
|
||
Hermes delivers and tracks thermal state. He does not inspect payload.
|
||
|
||
---
|
||
|
||
## ACK/NACK Protocol
|
||
|
||
- ACK → message dies cleanly, K redistributed
|
||
- NACK → message requeued with reduced heat
|
||
- No response → message cools naturally to death via TTL
|
||
|
||
The binary receipt of a response IS the ACK where applicable.
|
||
No separate acknowledgement frame needed in those cases.
|
||
|
||
---
|
||
|
||
## PubSub — Channels
|
||
|
||
Hermes implements a publish/subscribe model. Topics are called **channels**.
|
||
|
||
### The Common Channel
|
||
|
||
There is one permanent channel: `COMMON`.
|
||
|
||
- Always exists, always hot
|
||
- Any VM can publish to it
|
||
- Used for negotiation and channel establishment only
|
||
- Not for business transactions — those happen on ephemeral channels
|
||
|
||
### Ephemeral Channels
|
||
|
||
Ephemeral channels are compudynamic objects — born hot, reaped when cold or closed.
|
||
|
||
**Channel structure:**
|
||
|
||
```
|
||
channel_id — owner VM identity + monotonic sequence number
|
||
owner — VM that created the channel (always the responder)
|
||
members[] — participant list
|
||
heat — compudynamic, born hot at creation
|
||
state — NEGOTIATING | OPEN | CLOSING
|
||
```
|
||
|
||
### The Negotiation Protocol
|
||
|
||
```
|
||
1. X publishes request on COMMON — born hot, TTL ticking
|
||
2. Y sees request, responds yes/no on COMMON
|
||
— No response → message cools to death → implicit NACK to X
|
||
3. Y accepts → Y creates ephemeral channel
|
||
→ Y publishes channel_id to X over COMMON
|
||
4. X and Y conduct business on ephemeral channel
|
||
5. Business concluded → owner Y reaps channel → K rebalanced
|
||
```
|
||
|
||
Y owns channel creation and destruction. Always the responder, never the requester.
|
||
A channel dying of cold (abandoned transaction) is not an error — the physics handles it.
|
||
|
||
### Multi-Party Channels
|
||
|
||
Channels are bilateral by default. The owner may invite additional VMs:
|
||
|
||
```
|
||
1. Owner publishes invite to candidate VM on COMMON
|
||
2. Candidate accepts or declines on COMMON — same TTL/cool protocol
|
||
3. Accept → candidate added to members[]
|
||
4. Decline or timeout → candidate never joins, channel continues bilateral
|
||
```
|
||
|
||
Multi-party is designed for but not implemented yet.
|
||
Do not implement invite logic until explicitly instructed.
|
||
The members[] structure must exist from the start to avoid future redesign.
|
||
|
||
### Channel Identity
|
||
|
||
`channel_id` = owner VM identity + monotonic sequence number.
|
||
Unforgeable by non-owners. Hermes mints it. No VM constructs its own channel_id.
|
||
|
||
### Compudynamic Invariant for Channels
|
||
|
||
Channels participate in K≡1.0.
|
||
An open channel with active messages holds heat.
|
||
Channel reap must redistribute K correctly to the fleet.
|
||
A channel cannot be reaped while messages on it are still in flight —
|
||
drain first, then reap.
|
||
|
||
---
|
||
|
||
## What Is Not Hermes
|
||
|
||
- **Capsule cache** — future chapter, not now
|
||
- **Remote fetch** — future chapter, not now
|
||
- **UDP server / client** — future chapter, not now
|
||
- **Instance authentication** — future chapter, not now
|
||
- **Persistent block storage** — that is Artemis
|
||
- **VM lifecycle decisions** — that is Hera
|
||
- **Heat-based routing** — wrong model, never implement
|
||
|
||
---
|
||
|
||
## Compudynamic Invariant
|
||
|
||
Every object Hermes manages — messages and channels — participates in K≡1.0.
|
||
Fleet K includes the thermal contribution of in-flight messages and open channels.
|
||
Hermes reaping a message or channel must rebalance K correctly.
|
||
|
||
Do not write code that breaks K≡1.0 and add a comment explaining why it's okay.
|
||
|
||
---
|
||
|
||
## Current Scope
|
||
|
||
1. Compudynamic event loop — message emit, drain, TTL, ACK/NACK, reap
|
||
2. COMMON channel — permanent, always hot, negotiation only
|
||
3. Ephemeral channels — compudynamic lifecycle, owner=responder, bilateral default
|
||
4. Channel negotiation protocol — request on COMMON, Y creates, Y reaps
|
||
5. members[] structure — present from the start, multi-party invite not yet implemented
|
||
|
||
Capsule management is a future chapter.
|
||
Remote repository is a future chapter.
|
||
Multi-party channel invite is a future chapter.
|
||
|
||
---
|
||
|
||
## v1 Known Scope Gaps
|
||
|
||
Named, bounded limitations. Logged here to prevent future confusion and make
|
||
the upgrade path explicit. None of these are defects in v1.
|
||
|
||
### Synchronous delivery — RESOLVED (G1)
|
||
|
||
`MSG-SEND` now queues only; `HERMES-TICK` drives delivery via `MSG-DELIVER-ALL`
|
||
as its first step. Delivered messages are marked `MSG-DELIVERED` (type=255) so the
|
||
scanner skips them on the next tick. `MSG-REAP` clears them when heat reaches zero.
|
||
|
||
### G2/G4: ACK/NACK server implemented; NACK-requeue deferred
|
||
|
||
`MSG-ACK-LAST` and `MSG-NACK-LAST` live in block 4121. `MSG-DELIVER` stores the
|
||
current message pointer in `MSG-LAST-MSG` before calling `VM-EXEC`; the target
|
||
VM's handler calls `HERMES-ACK` / `HERMES-NACK` (from `common:msg.4th`) which
|
||
use `VM-EXEC` back into Hermes to invoke the server words — no cross-VM stack
|
||
passing required. `MSG-DELIVER-ALL` guards the `MSG-DELIVERED` stamp so an ACK
|
||
inside the handler does not corrupt the freed slot.
|
||
|
||
v1 NACK = immediate reap (same as ACK). NACK-requeue (reduced heat, retry) is
|
||
deferred: it requires preserving the original message type through delivery, which
|
||
needs either an extra cell (MSG-CELLS=9) or a side table. Neither is warranted
|
||
until a real NACK-retry use case appears.
|
||
|
||
### MSG-REAP ordering bug — FIXED
|
||
|
||
`MSG-REAP` (block 4109) previously called `MSG-FREE-NODE` before clearing
|
||
the type field. `MSG-FREE-NODE` writes old_free_head into cell[0]; the
|
||
subsequent `MSG-TYPE!` then overwrote cell[0] with 0, severing the free list.
|
||
|
||
Fixed: type is cleared first (`0 MSG-SCAN @ MSG-TYPE!`), then the node is
|
||
prepended to the free list (`MSG-SCAN @ MSG-FREE-NODE`). Matches the correct
|
||
order already used by `MSG-ACK-LAST`/`MSG-NACK-LAST` in block 4121.
|
||
|
||
### Payload size coupled to block size
|
||
|
||
`MSG-PADDR`/`MSG-PLEN` carry FORTH string pointers. Payloads from `S"` literals
|
||
in block source are bounded by block size (1024 bytes). No logical-block spanning
|
||
is implemented. No real Hermes script has hit this ceiling yet — revisit when
|
||
something forces the issue.
|
||
|
||
### G8: HERMES-K implemented; K-FLEET integration deferred
|
||
|
||
`HERMES-K ( -- q48 )` = `MSG-TOTAL-HEAT + CH-TOTAL-HEAT` is implemented in
|
||
Hermes's VM (block 4116) and gives the total thermal mass of all in-flight
|
||
messages and active channels. Wiring it into `K-FLEET` in Hera requires a
|
||
cross-VM return value — something the current synchronous VM-EXEC model cannot
|
||
deliver. Full K≡1.0 accounting (K-FLEET includes Hermes thermal mass) is
|
||
deferred until the async inter-VM model supports return values (G1 path).
|
||
|
||
### `IDX>NAME` and `CH-ACCEPT` owner hardcoded for 3-VM Tripod
|
||
|
||
`IDX>NAME` maps 0→Hera, 1→Hermes, 2→Artemis only. `CH-ACCEPT` writes
|
||
`1 OVER CH-OWNER!` unconditionally. Correct for v1 Tripod; generalization
|
||
needed if Tripod grows beyond three VMs.
|
||
|
||
### G10: EVENT-WAIT reads raw arena slot 0
|
||
|
||
`EVENT-WAIT` returns `MSG-ARENA MSG-TYPE@` — the type field of slot 0, whatever
|
||
it happens to hold. With the async model this is unreliable: slot 0 may be
|
||
pending, delivered (type=255), or free (type=0) depending on allocation order.
|
||
`EVENT-EMIT` and `EVENT-DRAIN` have been corrected (G10): EMIT delegates to
|
||
`HERA-NOTIFY-*`; DRAIN is a no-op (MSG-REAP owns cleanup). `EVENT-WAIT` is
|
||
retained as a diagnostic peek but must not be used for correctness decisions.
|
||
|
||
### Terminology: heat-gated reclamation, not mark-and-sweep
|
||
|
||
Channel and message reaping is **heat-gated reclamation**: objects are freed only
|
||
when compudynamic heat decays to zero. This serializes reaping against active
|
||
traffic on that object only — it is not stop-the-world for the VM as a whole.
|
||
Do not apply GC vocabulary (mark-and-sweep, generational, root-set tracing) to
|
||
this mechanism; those terms carry expectations the design never intended.
|
||
|
||
---
|
||
|
||
## v1 Block Map — LOCKED
|
||
|
||
Blocks 4110–4113 are Artemis. NEVER touch them.
|
||
|
||
```
|
||
4100 Constants: event codes, channel states, node sizes, arena sizes, MSG-DELIVERED
|
||
4101 Arena CREATE: MSG-ARENA CH-ARENA MBR-ARENA; free-list vars; MSG-SEQ CH-ACTIVE
|
||
4102 MSG-INIT-FREE + CH-INIT-FREE
|
||
4103 MBR-INIT-FREE + MSG-ALLOC + MSG-FREE-NODE
|
||
4104 CH-ALLOC + CH-FREE-NODE + MBR-ALLOC + MBR-FREE-NODE
|
||
4105 Message field accessors: MSG-TYPE@/! MSG-FROM@/! MSG-TO@/! MSG-PADDR@/! MSG-PLEN@/! MSG-HEAT@/! MSG-SEQ@/!
|
||
4122 Message field accessors cont.: MSG-CH@/!
|
||
4106 Channel+member accessors: CH-ID@/! CH-OWNER@/! CH-STATE@/! CH-HEAT@/! CH-MBRS@/! CH-NEXT@/! MBR-NEXT@ MBR-VM@
|
||
4107 VARIABLE MSG-LAST-MSG + IDX>NAME + MSG-DELIVER
|
||
4123 MSG-SEND
|
||
4108 VARIABLE MSG-SCAN + MSG-COOL-ONE + MSG-COOL-ALL
|
||
4124 MSG-TOTAL-HEAT
|
||
4128 MSG-DELIVER-ALL
|
||
4109 MSG-REAP
|
||
---- 4110–4113: ARTEMIS — DO NOT TOUCH ----
|
||
4114 VARIABLE CH-SCAN + CH-COOL-ALL + CH-TOTAL-HEAT
|
||
4115 CH-REAP-SAFE
|
||
4116 VARIABLE COMMON-CH + COMMON-INIT + HERMES-TICK
|
||
4125 HERMES-K + WELCOME (LOG-INFO at load)
|
||
4117 EVENT-EMIT + EVENT-WAIT + EVENT-DRAIN (backward compat)
|
||
4118 HERA-NOTIFY-SPAWN + HERA-NOTIFY-KILL
|
||
4119 CH-MINT-ID + CH-REQUEST
|
||
4126 CH-ACCEPT + CH-CONFIRM + CH-CLOSE
|
||
4120 CD-INIT (loads lib.4th + common:msg.4th; LOG-INFO" Hermes: ready")
|
||
4121 MSG-ACK-LAST + MSG-NACK-LAST
|
||
4127 MSG-USED + CH-USED + HERMES-STATUS
|
||
```
|
||
|
||
### Node layouts (cells)
|
||
|
||
**Message node — 8 cells:**
|
||
- 0: type (in-use) / next-free ptr (free)
|
||
- 1: sender VM index
|
||
- 2: recipient VM index
|
||
- 3: payload addr (FORTH string addr)
|
||
- 4: payload len
|
||
- 5: heat (Q48.16)
|
||
- 6: seq
|
||
- 7: channel ptr (0 = no channel)
|
||
|
||
**Channel node — 6 cells:**
|
||
- 0: channel-id (in-use) / next-free ptr (free)
|
||
- 1: owner VM index
|
||
- 2: state (0=NEGOTIATING 1=OPEN 2=CLOSING)
|
||
- 3: heat (Q48.16)
|
||
- 4: members-head (→ member list)
|
||
- 5: next-active (active channel list link)
|
||
|
||
**Member node — 2 cells:**
|
||
- 0: next-ptr
|
||
- 1: vm-id
|
||
|
||
### Physics
|
||
|
||
Cooling constant: Q-DECAY (65208) from compudynamics.4th — same as VM-DECAY-ONE.
|
||
Messages and VMs cool at identical rates. Fleet is thermodynamically consistent.
|
||
|
||
---
|
||
|
||
*This document is authoritative. If it conflicts with something in the codebase,
|
||
the codebase is wrong.*
|