Files
LithosAnanake/docs/v4.0.0/plans/2026-10-08-step6e-messages-on-the-wire.md
T
rajamesandClaude Opus 5.5 88554666d4 docs(v4.0.0): step 6e -- the plan; a mark on each wire, a sleep woken by a message coming, and the room probe
MESH.md 7d.3 amended while the plan was written: first and next in place
of find by type; sleep is woken by a message coming, not by one being
there; operation 10, room.  7d.4: room for a text's answer is kept on
the wire back.  7d.9: a ping across hops, thought about and not ruled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:58:13 -04:00

22 KiB

Step 6e: Messages on the Wire — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: A node that is stuck holds up no other node, by having the fabric move whole messages between neighbours and hold them on the wires.

Architecture: The fabric keeps a queue of whole messages each way on every wire. A node asks the fabric, through a few addresses after its ports, to look at, take, move, drop or put a whole message; each is one engine step, all or nothing. A node holds nothing in transit: the nucleus's ring of messages waiting, the words that turned it, the list of NACKs owed and (GATE) are removed. Only two things block: sleep, and sleep for room on a wire, used by printing alone.

Tech Stack: C99 (v4/src, v4/include/v4, v4/system/boot.c, v4/tools/hosted.c, tests), the v4 nucleus dialect (v4/capsule/core.v4, quit.v4), FORTH (capsules/v4/hera.4th), Python 3 (v4/tools/depthsweep.py), make -C v4, make -f kernel/Makefile.

Spec: docs/v4.0.0/MESH.md section 7d (7d.3 the interface, 7d.4 the nucleus, 7d.6 the limits, 7d.7 the acceptance). Sections 7c.6 and 7c.7 say what the first attempt got wrong; read them before Task 1.

Global Constraints

  • Branch StarForth-v4.0.0, main checkout. No new branch, no stash, no worktree. End commit messages with Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>.
  • Every commit is chained on its checks with &&: make -C v4 hosted-check must pass in the same command that commits. A commit was once pushed with it failing.
  • No bad code is released: a defect found on the way is fixed, test first, and reported. No stubs or stand-ins. Do not change v3.
  • A statement about what v3 or v4 does today is checked in the code before it is relied on. A difference from the F18 is noted in a line and not held for ruling.
  • Both stacks stay at 32 (V4_DATA_RING=30, V4_RET_RING=31). A text may leave 28 values on the stack.
  • V4_WIRE_CELLS is 1,024, a build parameter set where V4_PORTS is.
  • A message is seven words and its text, four characters to a word (MESH.md section 6); on a wire it takes 7 + (length + 3) / 4 cells. A length below 0 or above 1,024 is "not a message".
  • Operation numbers and results are those of MESH.md 7d.3: 1 look, 2 take, 3 move, 4 drop, 5 put, 6 first, 7 next, 8 sleep, 9 sleep for room, 10 room; results 0 done, 1 no room, 2 nothing on that port, 3 no such message, 4 not a message.
  • Message types: 1 text, 2 output, 3 done, 4 NACK, 5 GONE. Errors: 18 "No one on that port", 19 "Message refused", 20 "Node gone", 21 "Interrupted".
  • Every nucleus path that begins, takes or passes on a message is tested at every depth of both stacks, and with a node removed at every step of the exchange, before the task that adds it is called done. A hang, a line not answered, or a node that does not come back is a failure; a clean error is not.
  • After each task the hosted product is compared with the commit before on the same typed session (the session of depthsweep.py at shallow depth, plus WORDS, a 400-number loop, an error inside a definition after output, QUIT, ABORT"): any difference is explained by MESH.md 7d.6 or it is a defect.
  • Before any QEMU run read .claude/CLAUDE.md "Running / Acceptance" and the memory note acceptance-test-rules.md. One QEMU at a time, clean before qemu, all three ISAs, logs kept and committed. Never delete a log.

Review Focus

  1. A put or take whose addresses run off the node's memory, or lie in its ports. Expected: result 4, nothing copied, nothing on the wire changed. Test in Task 1.
  2. A node that leaves a message for itself on a wire and sleeps. Expected: it sleeps (the engine clock does not move) until another message comes; it does not go round. Tests in Task 1 (engine) and Task 3 (AWAIT, and printing that waits).
  3. A message for another node behind one the node has left for itself. Expected: it is passed on; the one left stays where it was. Test in Task 3.
  4. A text that prints with the wire back holding exactly the room kept for its answer. Expected: printing waits; how the text ended is never refused at the node that did it. Test in Task 3.
  5. A node removed, or a wire cut, with messages waiting both ways. Expected: they are let go; no other node is left blocked or with a mark at a message that is gone. Tests in Task 1 and Task 4.

Task 1: The wire in the engine

Files: Create v4/include/v4/wire.h, v4/src/wire.c, v4/tests/test_wire.c. Modify v4/include/v4/node.h, v4/src/node.c, v4/include/v4/fabric.h, v4/src/fabric.c, v4/tests/test_fabric.c.

Interfaces — Produces:

/* wire.h -- a queue of whole messages, one way along a wire */
#ifndef V4_WIRE_CELLS
#define V4_WIRE_CELLS 1024
#endif
typedef struct {
    v4_cell  cell[V4_WIRE_CELLS];   /* the messages, one after another, going round */
    unsigned head;                  /* where the front message begins */
    unsigned used;                  /* how many cells they take */
    unsigned mark;                  /* how many cells from the front the reader's mark is; == used: at no message */
    unsigned arrived;               /* not 0: a message has come since the reader last asked which wires have one */
} v4_wire_queue;

enum { V4_WIRE_DONE = 0, V4_WIRE_NO_ROOM = 1, V4_WIRE_NO_ONE = 2, V4_WIRE_NO_MESSAGE = 3, V4_WIRE_NOT_A_MESSAGE = 4 };

void     v4_wire_reset(v4_wire_queue *q);
unsigned v4_wire_cells(v4_cell length);                 /* cells a message of that length takes; 0: not a message */
int      v4_wire_room(const v4_wire_queue *q, unsigned cells);
int      v4_wire_put(v4_wire_queue *q, const v4_cell *header7, const v4_cell *text);   /* a V4_WIRE_ result */
void     v4_wire_first(v4_wire_queue *q);
int      v4_wire_next(v4_wire_queue *q);                /* V4_WIRE_DONE, or V4_WIRE_NO_MESSAGE at the end */
int      v4_wire_look(const v4_wire_queue *q, v4_cell *header7);
int      v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap);
int      v4_wire_drop(v4_wire_queue *q);
int      v4_wire_move(v4_wire_queue *from, v4_wire_queue *to);   /* the marked message, its fourth word one fewer */
  • node.h: indexes V4_PORT_WIRE (V4_PORTS + 4), V4_PORT_A, V4_PORT_B, V4_PORT_DO, V4_PORT_HOW, V4_PORT_HAVE (V4_PORTS + 9). Node fields unsigned wire; v4_cell wire_a, wire_b; int doing; v4_cell do_op; v4_cell how; unsigned have;. A store to V4_PORT_DO records the operation and blocks the node (doing = 1) until whoever connects its ports has done it.

  • int v4_node_doing(const v4_node *n); and void v4_node_done(v4_node *n, v4_cell how); for that connector; void v4_node_have(v4_node *n, unsigned mask);.

  • fabric.h: void v4_fabric_queues(v4_fabric *f, v4_wire_queue *pool, unsigned count); — the owner gives the fabric the queues; wiring a port takes two, unwiring gives them back, and v4_fabric_wire/v4_fabric_wire_device fail when none are left. int v4_fabric_device_put(v4_fabric *f, unsigned node, unsigned port, const v4_cell *header7, const v4_cell *text); and int v4_fabric_device_take(v4_fabric *f, unsigned node, unsigned port, v4_cell *header7, v4_cell *text, unsigned text_cap); for what is on the other end of a device's wire.

  • The word-at-a-time ports, v4_device's take/give, and the looking words stay as they are in this task.

  • Step 1: Write v4/tests/test_wire.c, failing. The queue alone, no node:

    • an empty queue: room for 1,024, look/take/drop answer 3;
    • put of a message of 0, 1, 4, 5 and 1,024 characters: each takes v4_wire_cells; taken back, the seven words and every word of text are what was put, in order, across the end of the array (fill and empty it three times with lengths that do not divide 1,024);
    • a put that does not fit answers 1 and changes nothing (compare head, used, and every cell);
    • a length of -1 and of 1,025 answers 4 and changes nothing;
    • first/next walk three messages and then answer 3; take or drop of the middle one leaves the first and third in order and the mark at the third;
    • move takes the marked message from one queue to the back of another with its fourth word one fewer, and answers 1, changing neither queue, when the other has no room;
    • take with text_cap too small answers 4 and takes nothing;
    • arrived is set by a put and by a move into the queue.
  • Step 2: Run make -C v4 run-64-test_wire.c. Expected: it does not compile, naming v4/wire.h.

  • Step 3: Write wire.h and wire.c. No allocation; every function leaves the queue as it found it unless it answers V4_WIRE_DONE. v4_wire_cells(length) is length < 0 || length > 1024 ? 0 : 7 + (length + 3) / 4.

  • Step 4: Run make -C v4 run-64-test_wire.c run-32-test_wire.c and both under sanitize. Expected: 0 failures.

  • Step 5: Write the fabric tests, failing, in test_fabric.c (small assembled programmes, as there):

    • a node puts a message on its wire 0 and a neighbour takes it from its wire 3: the words are what was put; "how it went" is 0 on both; each took one engine step blocked;
    • a put with nothing on the port is 2; to a wire with no room is 1; neither changes the wire;
    • addresses off the end of memory, or inside the ports, for look, take and put: 4, and nothing copied (Review Focus 1);
    • "which wires have a message" has the bit of each wire with one, and fetching it clears arrived;
    • sleep: a node with nothing arrived is blocked and the engine clock does not move for 500 steps; a put to one of its wires wakes it; a node that looks, leaves the message there, fetches "which wires" and sleeps again stays asleep until a second message comes (Review Focus 2);
    • sleep for room: woken when the reader takes enough; woken by a message coming instead, and "how it went" says which;
    • room answers at once and changes nothing;
    • move from one of a node's wires to another; with the other full, 1 and nothing moved;
    • a node removed with messages waiting both ways on two wires: the neighbours' queues are empty, their marks at no message, and a neighbour asleep for room on that wire is woken with 2 (Review Focus 5);
    • a device's wire: v4_fabric_device_put and _take each way;
    • wiring a third wire when the pool has queues for two fails and changes nothing.
  • Step 6: Run make -C v4 run-64-test_fabric.c. Expected: it does not compile, naming V4_PORT_WIRE.

  • Step 7: Implement the node indexes and fields, and in v4_fabric_step, after the nodes have executed, a loop that does each doing node's operation whole and calls v4_node_done, but for 8 and 9, which stay until their condition holds. Before the nodes execute, each awake node's "which wires" mask is set.

  • Step 8: Run make -C v4 -k test and sanitize. Expected: 0 failures; the nucleus does not use any of it yet, and hosted-check is unchanged.

  • Step 9: Commit feat(v4.0.0): the wire in the engine -- whole messages, put and taken in one step, chained on hosted-check, and push.

Task 2: The products' host and the test hosts speak whole messages

Files: Modify v4/system/boot.c, v4/tools/hosted.c, kernel/src/v4/sk_v4.c, v4/tests/test_host_quit.c, v4/tests/test_host_mesh.c, v4/tests/test_host_unit.c, and any other v4/tests/test_host_*.c whose loop serves a node's console (find them with grep -ln 'ask_port == CONSOLE_PORT\|console_take' v4/tests v4/system v4/tools kernel/src/v4).

Interfaces — Consumes: Task 1. Produces: in boot.c, for the lone node, the same operations done by the host: port 1 is the console's wire and port 0 the kernel's; v4_boot owns two v4_wire_queue for each. The test hosts' consoles use v4_fabric_device_put/_take.

This task changes no behaviour by itself: it is done together with Task 3 and committed with it, because the nucleus and its hosts change over at once. It is a separate task so that the hosts are written, and read, before the nucleus is.

  • Step 1: In each host, replace the word-at-a-time console (a v4_message filled word by word from asking on the console port, and words given one at a time to a read) with whole messages: a typed line is put on the node's console wire; each step, every message on the wire from the node is taken and handled (output shown; done ends the line).
  • Step 2: Kernel requests stay words: asking on port 0 is served as now.
  • Step 3: In boot.c the lone node's operations are done by a function boot_do(b), called each pass when v4_node_doing(n); sleep ends when a line has been put on its wire; sleep for room cannot fail to find room once the host has taken the console's messages, which it does every pass.
  • Step 4: Do not run the suite yet: it passes only with Task 3.

Task 3: The nucleus keeps no messages but the one it is doing

Files: Modify v4/capsule/core.v4, v4/capsule/quit.v4, v4/tests/host_map.h, v4/tools/mkimage.c if it lists the cells, capsules/v4/hera.4th only if a word it uses changes its stack effect, v4/tools/depthsweep.py. Tests: v4/tests/test_host_mesh.c, v4/tests/test_host_unit.c, v4/tests/test_host_quit.c; create v4/tests/test_host_depth.c.

Interfaces — Consumes: Tasks 1 and 2. Produces (nucleus words):

  • (DO) ( op -- how ) stores the operation and fetches how it went. (WIRE!) ( port -- ) by its number.

  • (ROOM?) ( cells port -- flag ) operation 10.

  • (PUT) ( text-addr port -- how ) puts the message whose seven words are in (OUT-HDR) and whose text is at the word address given.

  • (WAY) ( node -- port | -1 ) the number of the port that leads to a node.

  • (REFUSE) — the marked message on the wire in (WIRE) is dropped, counted, and a NACK put toward its sender (an answer's NACK is about the node that answered and goes toward whom the answer was for).

  • (SERVE) ( -- ) one pass over every wire that has a message, with first and next: what is for another node is moved or refused; what is for this node is left. (IDLE), AWAIT and the wait for room each call it and then look for what is theirs.

  • (ROOM) as now, tried before text begins any of this.

  • Removed: (MQ) and its cells, (MQ!), (MQ@), (TAKE-HDR), (TAKE-KEEP), (TAKE), (GATE), (OWE), (PAY), (PAY1), (PAID), (OWED), (A-QUEUE), (A-HDR), (A-BACK), (A-TEXT), (LOW), and the use of (WRITERS)/(READERS). (NEAR) and NEIGHBOUR stay, for (CENTRE).

  • Kept, with the same meaning to their callers: SEND, SEND-ON, AWAIT, GONE, NO-ROUTE, ROUTE, DEFAULT-ROUTE, (REFUSED), (LOST), EMIT, (FLUSH-OUT).

  • Step 1: Rewrite the tests that say what must hold, failing. In test_host_mesh.c (three nodes; keep row-style fresh rows for each scene):

    • everything in the file up to the flood that does not depend on waiting to write stays as it is and must pass unchanged;
    • refused at once (acceptance 2, 3): node 11 stuck in BEGIN 0 UNTIL. Node 10 sends it short texts in a loop: (REFUSED) on node 10 is 0 while they fit on the wire and rises by one for each after; node 10's line ends; nothing waited. tell(12, ...) and tell(10, ...) answer throughout;
    • a stuck node on the way: text for node 12 beyond stuck node 11 in a row is refused at node 10 once the wire to 11 is full, and the console is sent the NACK;
    • a refusal owed to a stuck node: node 11 sends 55 a message by node 12, which has no way, and sticks: node 12's NACK goes on the wire (or is counted if it is full), and node 12 is asleep, not going round, and answers;
    • printing is not lost (acceptance 5): node 12 does : P 3000 0 DO I . LOOP ; P with node 10 made slow to take (a word that loops between lines): every number arrives, in order, and then how the text ended;
    • printing held by a stuck node: node 12's way to the console is by stuck node 11; P fills the wire and node 12 sleeps for room; meanwhile a message from node 10 for a fourth number by node 12 is passed on or refused, not kept; node 11 removed: node 12's text ends "No one on that port" and it goes on;
    • the answer has room kept (Review Focus 4): the wire from node 12 back to node 10 is filled to within 8 cells by messages node 12 passes on; node 12 then does text from node 10 that prints: its printing waits, and how it ended arrives;
    • left for itself (Review Focus 2, 3): node 10 in 12 AWAIT is sent, on the same wire, text for itself from node 11, then a message for the console from node 11, then node 12's answer: the message for the console is passed on, the wait ends with the answer, the text is then done, in that order; and between arrivals node 10's engine clock does not move;
    • order (acceptance 8): the TRIO scene of the first attempt ("DEF");
    • AWAIT's endings: refused (NACK about the node), gone (GONE from the centre, and not from another), interrupted (text from the console), as now;
    • the flood: 800 messages among three nodes comes to rest; every message sent is either done or counted once ((LOST) plus (REFUSED) accounts for it exactly — the check is exact again).
  • Step 2: In test_host_unit.c (Hera and four): the scenes of step 6c for KILL, with these changed: text for a stuck node is refused once its wire is full, not kept; KILL of one stuck node with another stuck ends, and every node not stuck is told; Hera is typed to throughout (acceptance 4).

  • Step 3: Create test_host_depth.c (acceptance 7 on the mesh): on a row of three, for the data stack of node 11 at each depth from 20 to 32 and from 20 to 32 calls deep, each of: a text that prints; SEND to node 12; SEND to a node there is no way to; 12 AWAIT answered; 12 AWAIT with node 12 stuck, ended from the console's node by a GONE; node 11 passing a message on while its own text waits for room. After each: every node answers 1 2 + . and node 11's stack is as deep as it was or was emptied by an error it reported.

  • Step 4: Extend depthsweep.py with: a loop that prints 400 numbers; SEND to a node with a route and nothing on the port.

  • Step 5: Run the three tests. Expected: they do not build or fail throughout, as the nucleus still writes messages a word at a time.

  • Step 6: Write the nucleus as MESH.md 7d.4 says, in this order, running make -C v4 run-64-test_host_quit.c after each: (a) (DO), (WIRE!), (ROOM?), (PUT), (WAY); (b) (FLUSH-OUT) and (FINISH) — output and then how the text ended, put on the wire, with room for the answer kept and the wait for room; (c) (IDLE): sleep, (SERVE), take the first message for this node on any wire and do it; (d) (REFUSE); (e) SEND-ON, (SEND1); (f) AWAIT; (g) delete what is listed as removed, and the cells of host_map.h that go with it. Cells freed are noted in host_map.h as free.

  • Step 7: Stack depth. (SERVE), (REFUSE) and everything they call keep to four cells of the data stack and four entries of the return stack, with what they need between operations held in cells, not on the stack. test_host_quit.c's "28 values may wait" must still say 28.

  • Step 8: Run make -C v4 -k test, sanitize, hosted-check (which runs depthsweep.py). Expected: 0 failures; test_host_depth.c reports no node that did not come back.

  • Step 9: Compare the product with the commit before on the session of Global Constraints (build the commit before into the scratchpad with git archive). Every difference is one MESH.md 7d.6 names.

  • Step 10: Mutation checks. Each must make a named test fail: (a) sleep woken by a message being there, not coming; (b) the 8 cells for the answer not kept; (c) (SERVE) stopping at a message for itself; (d) a refused SEND not raising 19.

  • Step 11: Commit Tasks 2 and 3 together, feat(v4.0.0): messages are whole and wait on the wire -- a node keeps none but the one it is doing, chained on hosted-check, and push.

Task 4: A node removed at every step; what is no longer used; the documents; the boots

Files: Modify v4/tests/test_host_mesh.c, v4/src/fabric.c, v4/include/v4/fabric.h, v4/src/node.c, v4/include/v4/node.h (only what Step 2 finds unused), docs/v4.0.0/MESH.md, v4/README.md, docs/v4.0.0/ENGINE.md if it describes the ports.

  • Step 1: Write the removal test, failing if anything is wrong (acceptance 6). For each exchange — text from the console to node 12 through 11, and its printing and answer back; a SEND from 11 to 12 and its answer; a NACK from 12 to 10 — record how many engine steps it takes with no removal, then for every step from 0 to that many run it again on a fresh row with node 11 (and, again, node 12) removed at that step. After each: every node left comes to rest, answers 1 2 + ., and is not blocked in any operation.
  • Step 2: Remove what nothing uses, found with grep, not assumed: the pending hook of v4_device and anything of the first attempt if any is left; the word-at-a-time give of the console in hosts. The looking words stay in the engine if the engine's own tests use them.
  • Step 3: Run make -C v4 -k test, sanitize, hosted-check, lint with the freshly built mkcapsule.
  • Step 4: Documents. MESH.md: 4.1's table gains the six addresses; 7a and 7b say what of them still holds (the lower-number rule and the NACKs owed do not; the endings of AWAIT and Hera's KILL do); 7b.7's limit is closed with a pointer to 7d.6; step 6e is marked done with tests, logs and the hash. v4/README.md likewise.
  • Step 5: The three boots, amd64, aarch64, riscv64, each clean qemu with STARFORTH_V4=1 and the typed session of step 6d's last boots (29 values on a line; WORDS with 26; AWAIT with 27 and a line), plus a loop printing 400 numbers. Expected: POST 538 of 538, PARITY:OK, one dict_hash on all six, and the session's answers the same on all three.
  • Step 6: Commit feat(v4.0.0): step 6e -- a node removed at any step leaves the rest at rest; written up, with the logs, chained on hosted-check, and push.
  • Step 7: Final review by a fresh reviewer on the most capable model, with MESH.md 7c.6, 7c.7 and 7d, this plan's Review Focus, and the instruction to break it: faults at every depth, removal at every step, and what the products do that the commit before did not.