Antheos Protocol
This specification is stable. No implementation of it is published at present.
ANTHEOS_PROTOCOL_SPEC.md (Markdown, 126,945 bytes)
Version 1.11 — Level 1 Specification - Transport
© Are Bjørby are.bjorby@langsyn.org November 2026
Contents
- Introduction
- Design Principles
- Terms and Definitions
- Protocol Encoding
- Identifier Types
- Word Encoding
- Message Frame Structure
- Level 1 Symbols
- Bus Operations
- Service Operations
- Session Operations
- Bus Transmission Rules
- Standard Exception Codes
- Conformance Requirements
- Security Considerations
- Licensing
1. Introduction
Antheos (Ant) is an anonymous, lightweight, decentralized communication protocol with a wrap-and-tunnel philosophy and a bus abstraction that utilizes existing hardware and software. It is anonymous in a narrow sense: an instance needs no identity to communicate. Its BID is random and lasts one bus connection (Section 5.4), and Level 1 does not expose its OID:DID:IID, whose exposure is left to the device's designer (Section 5). It is designed to operate on any transport — from a UART serial line to a TCP socket, from a LoRa radio to a human carrying a USB drive.
This document defines the Level 1 Specification - Transport: the complete, self-contained procedural protocol covering bus operations, service discovery, and session management. Every verb does exactly one thing. It runs on all devices from MCU to server cluster.
Level 1 is the foundation for higher-level protocols.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals, as shown here.
1.1 Design Context: Object-Oriented Hardware — the O³ Vision
(This section is non-normative. It records the design intent behind the protocol; a conformant implementation depends only on the normative sections that follow.)
Antheos exists to let independent devices exchange services without a central operating system. The motivating observation is that a computer is already a collection of devices sharing a chassis — processor, memory, storage, network, sensors — and that as each device acquires its own compute, the operating system's historical role narrows. A traditional operating system answers a scarcity question: one expensive processor multiplexed across many tasks by a privileged arbiter. When every device is itself a processor, the machine is better understood as a small network of peers, and what those peers need is not a kernel but a shared communication law. Antheos is that law. This is why Design Principle 1 (Section 2) admits only devices and buses — no controller, no central authority.
The same protocol spans scales because the model is uniform at every scale: on every bus, whether it joins logical devices inside one chassis, physical devices on a shared wire or services across a network, instances are addressed and discovered identically. This cross-scale uniformity — one object model holding from an on-die component out to a remote service — is what this design names O³ (Object-Oriented Omni-scale), pronounced "object cubed": object-oriented in the messaging sense (peers communicate; no shared mutable state) and omni-scale because the single law holds at every distance. Only the transport changes with distance — shared memory within a machine, a socket across a network, a radio between devices — while the law above it does not. The identity model (Section 5) anticipates this: a device's DID and IID are hardwired — carried by the device, not assigned by the network — which is the shape fully object-oriented hardware takes natively.
On conventional hardware, where the processor and memory are still physically shared, this model is realized by a thin layer beneath Antheos that presents the shared resources as logical devices speaking the protocol. Such a layer need not interpose on every operation: a peer negotiates access to a resource once over Antheos (the control path), then uses the granted resource directly at hardware speed (the data path). The protocol is the control plane; raw access is the data plane. This separation is the same one found in capability-export kernels and in production paravirtualized device interfaces.
Reduced to essentials, the authority such a layer must hold is small: the power to grant access to a shared resource, and the power to reclaim it from a peer that does not yield. Everything else — isolation enforcement, stable identity, the bus itself — is either delegated to the participating devices or carried by the fabric they share. Grant and reclaim are physical-layer concerns; they do not reintroduce a central authority over meaning, which is what Principle 1 forbids. As hardware becomes natively object-oriented, even these responsibilities migrate from software into the interconnect — and the operating system, in the form Antheos assumes away, is gone.
2. Design Principles
- Simplicity: Only devices and buses exist. No routers, no controllers, no central authority.
- Decentralization: All instances are peers, and communication is peer-to-peer. A registered OID is optional (Section 5.1).
- Universality: A message's head is CP437 text, printable ASCII and seven delimiters with CP437 glyphs of their own (Section 4), so a display that renders CP437 shows a head as written.
- Scalability: The same protocol frames flow from server clusters to 8-bit microcontrollers. Constrained devices handle what they can and drop what they cannot.
- Wrap and Tunnel: Antheos wraps existing hardware and software in a shim that speaks the protocol for it, and tunnels through existing network protocols rather than replacing them. A bus can be any transport that can carry bytes: the bus abstraction is agnostic to the physical or virtual medium.
- Bus-agnostic: The protocol knows no bus. No message names a bus or a kind of bus. Which buses an instance is connected to, the BID it holds on each, what can be reached through them, and each bus's limits and timing are the instance's own knowledge, kept by the device.
- Query, Don't Advertise: Instances discover capabilities by querying, not by advertising. No upfront capability handshakes. No schema exchanges.
3. Terms and Definitions
| Term | Definition |
|---|---|
| Text | Printable ASCII characters, 0x20 to 0x7E (§6.2.4) |
| Blob | Binary data (arbitrary bytes) |
| Instance | Device, software, or HID endpoint communicating on a network |
| Network | Instances connected to one or more buses |
| Bus | Device, software, system, or person facilitating instance communication |
| Bus connection | An instance's attachment to one bus, from establishing its BID there (Section 9.1) until it disconnects |
| Gateway | An instance on more than one bus through which a BID on another bus is reached (Section 9.6) |
| Routing table | An instance's own record of the BID through which another BID is reached (Section 9.3.3) |
| Message | A head of words followed by an optional binary tail |
| Head | The CP437 portion of a message, containing words |
| Word | A typed, delimited unit within the head |
| Tail | Optional binary data following the head |
| Flag | A qualifier within a word (radix or unit) |
| Body | The content portion of a word |
| Session | A separate group of related messages between instances |
| Opener | The instance that opened a session or, after a Resume, the one that resumed it (Section 11.1.2) |
| Trace identifier | A plain ID word a Call MAY carry after its MID, which its sender chooses (Section 11.3) |
| Service | A capability an instance offers to accomplish a specific task, found by Query and taken up by Accept (Section 10) |
| Origin | Source, creator, or owner of instances |
| Id | A text or number used to identify objects |
| Registry | An entity maintaining a list of identifiers |
4. Protocol Encoding
Antheos uses CP437 (Code Page 437), the character encoding developed by IBM for the original PC in 1981. CP437 is a single-byte encoding defining 256 characters. The first 128 (0x00–0x7F) align with ASCII. The upper 128 (0x80–0xFF) include accented letters, Greek symbols, and box-drawing characters.
A message's head is human-readable on any device that displays CP437's glyphs, those of the control range included, and its tail is binary. The protocol reserves 7 bytes from the CP437 control character range (0x00–0x1F) as structural delimiters.
4.1 Reserved Control Characters
All reserved bytes were selected to avoid collisions with ASCII flow control (XON 0x11, XOFF 0x13), terminal escapes (ESC 0x1B), whitespace (TAB 0x09, LF 0x0A, CR 0x0D), and null terminators (0x00). A head never holds these bytes, so it passes through software flow control and C string handling. A tail may hold any byte (Section 7.3), and a bus carries every byte unchanged (Section 12), so a link that carries Antheos MUST be 8-bit clean: without software flow control, and in raw mode on a terminal line. In its default mode, a POSIX terminal acts on 0x03 (INTR), 0x04 (EOF), 0x12 (REPRINT) and 0x1A (SUSP), and sounds 0x07 as its bell.
| Hex | Abbr | CP437 Glyph | Role |
|---|---|---|---|
| 0x02 | SOM | ☻ | Start of Message |
| 0x03 | EOM | ♥ | End of Message |
| 0x04 | SOR | ♦ | Start of Radix qualifier |
| 0x07 | SOU | • | Start of Unit qualifier |
| 0x10 | EOW | ► | End of Word |
| 0x12 | SOW | ↕ | Start of Word |
| 0x1A | SOB | → | Start of Body |
These 7 bytes are forbidden in word bodies. In the head their presence always indicates protocol structure, enabling stream parsing without buffering entire messages. The tail is length-delimited (Section 7.3), so a tail block, a STRING's included, may hold any byte.
5. Identifier Types
Antheos uses a layered identity model. Each identifier type has a defined scope, format, and lifetime. The layers build from organizational identity (OID) down to per-message sequencing (MID).
Level 1 carries the BID, SID and MID. It carries no OID, DID or IID, and an instance needs none of them to conform: they are defined here for a device's designer, who controls their exposure (Section 5.3).
| Id | Name | Scope | Format | Lifetime |
|---|---|---|---|---|
| OID | Origin | Origin | ASCII | Origin-defined, or the registry's |
| DID | Device | Origin | ASCII | Origin-defined (hardwired type identifier) |
| IID | Instance | Origin | ASCII | Origin-defined (hardwired serial) |
| BID | Bus | Bus | Base-32 | Ephemeral (established at bus connection) |
| SID | Session | Instance | Base-32 | Session duration (fresh per session) |
| MID | Message | Session | Decimal | Message duration (sequential counter) |
5.1 OID (Origin Identifier)
The OID identifies the origin — the organization, individual, or entity that created and owns instances. An OID is a plaintext ASCII string. Registering one is optional.
5.2 DID (Device Identifier)
The DID identifies the device type within an origin's namespace. It is a hardwired, human-readable ASCII string (e.g., "Thermostat", "SensorV2"). Defined by the origin, not the network.
5.3 IID (Instance Identifier)
The IID identifies a specific instance of a device type. It is a hardwired unique serial (e.g., "SN00482").
An instance's connection to a bus is OID to BID: the BID is generated at random on every local bus, so the instance stays anonymous there (Section 5.4), and Level 1 exposes nothing of its OID:DID:IID. A hash of OID:DID:IID:BID gives a globally unique id, and registering the OID in an Antheos namespace guarantees that uniqueness within the namespace. Whether a device has such an id, which hash makes it and who sees it are left to the device's designer: Level 1 neither computes it nor carries it.
5.4 BID (Bus Identifier)
The BID is a base-32 address, generated at random and negotiated at bus connection through the Establish/Conflict protocol (see Section 9.1). BIDs are ephemeral and local to a single bus. They are kept as short as possible, growing only on collision. A device connected to multiple buses holds a separate BID on each. Carried in an ID word with radix flag U (duotrigesimal).
5.5 SID (Session Identifier)
The SID identifies a session between instances. It is a base-32 hash of a counter and device entropy (Section 11.2). Every new session receives a fresh SID: an instance never hashes the same device entropy and counter together twice, and never issues a SID a session holds. Carried in an ID word with radix flag U (duotrigesimal).
5.6 MID (Message Identifier)
The MID is a decimal counter that increments sequentially within a session, starting at 1, up to at most 4294967295 (2³²−1). MID=0 is the wrap signal (see Section 11.1.2). Carried in an ID word with radix flag D (decimal).
5.7 Identifier Encoding Summary
The identifiers Level 1 carries are carried in ID words (@), in one of three forms. An ID word in any other form, with any other radix flag included, is an invalid word (§6.1). Unit flags are never used with ID words.
- Plain, with no radix flag: a Call's trace identifier (Section 11.3). Its body is one or more characters of printable ASCII other than space and colon (0x21 to 0x7E, except
:). - Base-32, radix flag U: a BID or SID. Its body is one or more characters of the identifier alphabet,
ABCDEFGHJKLMNPQRSTUVWXYZ23456789, which has no0,1,IorOand no lower case. Two identifiers are the same only when their characters are. As base-32, the characters stand for 0 to 31 in the order listed,Afor 0 and9for 31 (Section 11.2). These are not the digits of radix U in a numeric word (§6.3). - Decimal, radix flag D: a MID, from 0 to 4294967295 (Section 5.6), in decimal digits with no leading zero, so
01is invalid.
| Identifier | Radix | Wire Example |
|---|---|---|
| BID | U (base-32) | ↕@♦U→4T9X2► |
| SID | U (base-32) | ↕@♦U→A7K2M► |
| MID | D (decimal) | ↕@♦D→1► |
| Trace | (none) | ↕@→trace-7f3a► |
BID/SID Disambiguation: Both BID and SID use base-32 encoding with radix flag U. The verb scope determines which identifier type is expected:
- Bus-scope verbs (E, C, B, P, p, R, D, d, S, W, X): base-32 IDs are BIDs, except in an Exception whose reason is of Session scope
- Service-scope verbs (Q, O, A): base-32 IDs are BIDs, a directed Query's its target's, an Offer's its sender's, and an Accept's its sender's and then the offering instance's (Section 10)
- Session-scope verbs (K, T, N, L, U, F): the first base-32 ID is the SID, the second the sender's BID, and a third, if present, the BID of the instance the message is meant for (Section 11); a plain ID after a Call's MID is its trace identifier (Section 11.3)
- Exception whose reason is of Session scope (X, Section 13): the first base-32 ID is the SID, the second the sender's BID, and the third the BID of the instance it answers (Section 9.8)
- RELAY_FAILED (X, Section 9.3.3): its one base-32 ID is the BID its sender could not reach
- Any other Exception (X, Section 9.8): the first base-32 ID is the sender's BID, and a second, if present, the BID of the instance it answers. A BID_OVERFLOW carries none
A receiver determines identifier type by the verb in the SYMBOL word, and for an Exception by its reason, not by inspecting the identifier value itself.
6. Word Encoding
A word is the fundamental data unit within an Antheos message head. Each word is delimited by SOW (0x12) and EOW (0x10), with typed content between them.
6.1 Word Structure
The general word structure is:
[SOW] [WT] ([SOR][RF]) ([SOU][UF]) [SOB] Body [EOW]WT (Word Type) is always the first byte after SOW. SOR/RF (Radix Flag) and SOU/UF (Unit Flag) are conditionally present depending on the word type. SOB marks the start of the body content. EOW terminates the word. Appendix B gives this structure, with each word type's flags and body, as a grammar.
Flag requirements vary by word type:
| Category | Radix (SOR/RF) | Unit (SOU/UF) | Word Types |
|---|---|---|---|
| Radix + Unit required | Required | Required | INTEGER (#), REAL ($), SCIENTIFIC (%), BLOB (*), COMPLEX (|), STRING (") |
| Radix optional, Unit forbidden | Optional | Forbidden | ID (@) |
| Both forbidden | Forbidden | Forbidden | SYMBOL (!), PATH (/), TEXT (~), LOGICAL (?), DATETIME (&), DURATION (:), BOOL (=), NULL (_) |
A body names exactly one value, and each value has one spelling for its word type, radix and unit; the same value in another radix is another word (e.g., #♦D•B→10 and #♦H•B→A). A body has at least one character, except a TEXT word's, which may be empty (the empty text, §6.2.4), and a NULL word's, which is always empty (§6.2.11).
A body that breaks the rules of its word type (§6.2) makes the word invalid, and a receiver treats it as a malformed word (§7.4).
6.2 Word Types
| CP437 | Hex | Type | Description | Flags |
|---|---|---|---|---|
| ! | 0x21 | SYMBOL | Protocol verbs and markers | None |
| @ | 0x40 | ID | Identifiers (BID, SID, MID, trace) | Radix optional |
| / | 0x2F | PATH | Routing sequences | None |
| ~ | 0x7E | TEXT | Plain text in printable ASCII, §6.2.4 | None |
| # | 0x23 | INTEGER | Signed integer value, §6.2.5 | Radix + Unit |
| $ | 0x24 | REAL | Binary floating-point value, held exactly, §6.2.6 | Radix + Unit |
| % | 0x25 | SCIENTIFIC | A number carrying its significant figures, §6.2.7 | Radix + Unit |
| ? | 0x3F | LOGICAL | Logical expression, §6.2.1 | None |
| & | 0x26 | DATETIME | ISO 8601 date and time in UTC, §6.2.2 | None |
| * | 0x2A | BLOB | Tail block size in bytes, §6.2.8 | Radix + Unit |
| = | 0x3D | BOOL | Truth value, §6.2.9 | None |
| : | 0x3A | DURATION | ISO 8601 length of time, §6.2.3 | None |
| | | 0x7C | COMPLEX | Complex number, §6.2.10 | Radix + Unit |
| _ | 0x5F | NULL | Explicit absent value, §6.2.11 | None |
| " | 0x22 | STRING | Unicode string in UTF-8, carried in the tail, §6.2.12 | Radix + Unit |
SYMBOL, ID and PATH bodies are defined where they are used: SYMBOL in Section 8, ID in Section 5 and PATH in Section 9.3. Every other type's body is defined below.
6.2.1 LOGICAL
A logical expression over variables and constants. The whole expression is a truth value, so A + 1 is invalid.
Characters: A–Z, a–z, 0–9, &, |, ^, ~, -, >, =, (, ), <, +, *, / and a space, one on each side of an operator between two operands (e.g., A & B).
Variables and constants: a variable is a single letter, and A and a are different variables. A LOGICAL word carries the expression, not its value: what each variable names is set by the message that carries the word, as a Scaleback names a flag by its letter (§9.2.2). Each operand is a truth value or a number, by the operator it belongs to: those of ~, &, |, ^ and -> are truth values, those of -, +, *, /, < and > are numbers, and < and > give a truth value. A variable is one of the two throughout an expression. A constant is written in decimal digits with no leading zero: a number where a number is expected (e.g., (A + 2) < B), and 0 for false or 1 for true where a truth value is.
Numbers: exact. A number is the exact value its constant or variable names, and +, -, * and / are exact rational arithmetic, so nothing rounds or overflows (e.g., (7 / 2) > 3 and ((1 / 3) * 3) = 1 are both true). A division by zero makes the whole expression false.
Words a variable names: when a variable names a word, a number variable may name an INTEGER, a REAL or a SCIENTIFIC, and stands for the exact value it holds, a SCIENTIFIC's being its magnitude alone: figures do not count, so A = B is true when A names 6.02E23 and B names 6.020E23 (§6.2.7). A truth variable may name a BOOL. No other word type may be named, a COMPLEX included.
Operators:
&AND: true if both operands are true;|OR: true if at least one operand is true;^XOR: true if exactly one operand is true;~NOT: inverts its operand, written before it with no space (e.g.,~A);->IMPLIES: false only if the left operand is true and the right is false;=EQUIVALENCE: true if its two operands, both truth values or both numbers, have the same value;-: subtraction between two operands (e.g.,A - B), and arithmetic negation before one, with no space (e.g.,-A);<: less than;>: greater than;+: addition;*: multiplication;/: division;(and): grouping. Operators have no precedence: an operand that is itself two operands joined by an operator is written in parentheses, and parentheses appear nowhere else (e.g.,(A & B) | CandA & (B | C)differ, andA & B | Cis not valid).
Operands of ~ and negation: ~ and negation - apply to a variable, a constant, or two operands joined by an operator in parentheses, and to nothing else: ~A, ~1, -2 < A and ~(A & B) are valid, and ~~A, --A and ~(~A) are not. The whole expression is never in parentheses, so (A & B) is not valid.
Examples: A & B, ~A | B, (A & B) -> (C ^ D) (if A and B are true, C and D differ), ~H & ~q (a Scaleback's: neither hex nor quadword).
6.2.2 DATETIME
An ISO 8601 date and time in UTC: YYYY-MM-DDTHH:mm:SSZ.
Characters: 0–9, -, ., T, : and Z.
Fields:
YYYY: the year,0000–9999, in the proleptic Gregorian calendar:0000is the year before0001, and a leap year, and a date before 1582 is the calendar's, not a historical record (e.g.,0000-02-29T00:00:00Zand1582-10-10T00:00:00Zare valid);MM: the month,01–12;DD: the day of the month,01to its last in the Gregorian calendar; a day the month does not have is invalid (e.g.,2024-02-29is valid and2025-02-30is not);T: separates the date from the time;HH: the hour,00–23, in 24-hour time;mm: the minute,00–59;SS: the second,00–59;60is a leap second, valid only at23:59on the last day of a month, where leap seconds fall, which is all a receiver checks, with no table of the leap seconds UTC has had, and written only for one UTC had (e.g.,2016-12-31T23:59:60Z);Z: the time is UTC ("Zulu").
Variants: the seconds may carry a decimal fraction of 1 to 9 digits, with no trailing zero (e.g., 2025-04-03T12:34:56.789Z, not 2025-04-03T12:34:56.7890Z or 2025-04-03T12:34:56.0Z).
Examples: 2025-04-03T12:34:56Z, 2023-11-15T09:00:00Z.
6.2.3 DURATION
A length of time, positive, negative or zero, to the nanosecond: an ISO 8601 duration in days, hours, minutes and seconds, PnDTnHnMnS, with no bound on the days. A day is 24 hours, an hour 60 minutes and a minute 60 seconds, so a duration has a fixed length; years and months are not used, since theirs vary, and a week is written as 7 days.
Characters: 0–9, -, ., P, D, T, H, M and S.
Terms: P, then these, in order, each written only when its number is not zero, every number in decimal with no leading zero:
nD, the days, any number of them;T, written only when one of the next three is, before it;nH, the hours, below 24;nM, the minutes, below 60;nS, the seconds, below 60, with a decimal fraction of 1 to 9 digits and no trailing zero when they are not whole, and a lone0before the point under one second (e.g.,PT1.5SandPT0.25S, notPT1.50SorPT.25S).
Each term holds only what the next larger cannot, so each duration has one spelling (e.g., PT1H30M, not PT90M, and P1DT12H, not PT36H), and only the seconds carry a fraction (e.g., PT1H30M, not PT1.5H). The zero duration is PT0S, the one spelling that writes a zero term, and it has no sign (not -PT0S). A negative duration is written with - before P (e.g., -PT2H), the sign ISO 8601-2 adds: ISO 8601-1's durations have none.
Examples: PT1H30M, P1DT12H, P7D, PT0.5S, -PT2H, PT0S.
6.2.4 TEXT
Characters: ASCII excluding control characters, which is 0x20 (space) to 0x7E (~). Text past ASCII is carried by STRING (§6.2.12).
Values: any run of these characters, the empty one included: a TEXT word with an empty body is the empty text.
6.2.5 INTEGER
The value has no leading zero, and zero has no sign (e.g., 10 and 0, not 010, 00 or -0).
Characters: the digits of the word's radix (§6.3), and - before a negative value.
Unit: the value's width, B 8, W 16, D 32 or Q 64 bits; a value of n bits is from -2^(n-1) to 2^(n-1)-1 (e.g., B from -128 to 127), and one outside its range is invalid. M, G and T are not allowed.
6.2.6 REAL
Any real value its format holds (see Unit), positive or negative, large or small, integers and fractions included.
The value is written in positional notation, with no exponent: E-notation is SCIENTIFIC's (§6.2.7). The integer part has no leading zero, a lone 0 before the point aside (e.g., 10 and 0.5, not 010 or .5), the fractional part has no trailing zero (e.g., 1.5 and 1, not 1.50 or 1.0), and -0 is the negative zero the IEEE format holds.
Characters:
- the digits of the word's radix (§6.3), for the integer and fractional parts;
., the decimal point, between the integer and fractional parts, with a digit on each side of it (e.g.,3.14,0.5); a value may have none (e.g.,5,1000);-, before a negative value (e.g.,-0.001).
Unit: the IEEE 754 binary format the value is held in: W binary16, D binary32, Q binary64. B is not allowed, since there is no 8-bit format, and neither are M, G and T. A value the format does not hold exactly is invalid, and is not rounded, so a REAL names exactly the value it is held as (e.g., $♦D•W→0.5 is valid and $♦D•W→0.1 is not; binary16's nearest to 0.1 is 0.0999755859375). A value beyond the format's finite range is therefore invalid, as is a nonzero value smaller than the least the format holds, and a subnormal value the format holds is valid.
6.2.7 SCIENTIFIC
A number carrying its significant figures, in scientific notation, a x 10^b, written in E-notation, 1.23E2 for 1.23 x 10^2. The figures are part of the value: 6.02E23 and 6.020E23 are the same magnitude but different values. The coefficient is not zero, since significant figures are counted from its first nonzero digit and a zero has none (e.g., 0.0E0 is invalid).
The coefficient is written with one nonzero digit, followed, when it has more than one figure, by the point and the rest (e.g., 6E23 for one figure, 6.0E23 for two, and 1.23E2, not 12.3E1 or 0.123E3), so every digit it carries is a significant figure and each value has one spelling. The exponent has no leading zero, and a zero exponent has no sign (e.g., 1.5E0, not 1.5E00 or 1.5E-0).
Characters:
- the digits of the word's radix (§6.3), for the coefficient and the exponent;
., the decimal point, with at least one digit after it, in a coefficient of two figures or more (e.g.,1.23);E, before the exponent (e.g.,1.23E2);-, before a negative value or exponent (e.g.,-1.6E-19).
Radix: decimal (D) only, since every SCIENTIFIC value carries an exponent.
Unit: the IEEE 754 decimal format the value is held in, which keeps its figures: D decimal32, Q decimal64. B and W are not allowed, since there is no 8- or 16-bit decimal format, and neither are M, G and T. The format holds a value as its figures, read as an integer, times ten to the exponent of its last figure, q (e.g., 1.23E2 is 123 with q 0, and 6.0E23 is 60 with q 22): decimal32 holds 7 figures with q from -101 to 90, and decimal64 16 figures with q from -398 to 369. A value with more significant figures than its format holds, or whose q is outside that range, is invalid; it is not rounded, since its figures are part of the value.
Examples: 6.02E23, 1.6E-19, 6E23.
6.2.8 BLOB
The size in bytes of the tail block the word declares (§7.3): a count from 0 to the unsigned maximum of its unit, with no leading zero.
Characters: the digits of the word's radix (§6.3).
Unit: the count's width, B 8, W 16, D 32 or Q 64 bits; a count of n bits is from 0 to 2^n-1, and one outside its range is invalid. M, G and T are not allowed.
An invalid BLOB word breaks the frame, since the length of the tail cannot be known from it (Section 7.4): *♦D•B→300 (beyond its unit), *♦D•M→1 (a unit no BLOB takes) and *♦H•B→0FF (a leading zero) each break it.
6.2.9 BOOL
A truth value.
Characters: 0 and 1.
Values: the body is one character, 0 for false or 1 for true.
6.2.10 COMPLEX
A complex number: a real part and an imaginary part, each any real value its format holds (see Unit), as a REAL's is (§6.2.6).
The body is the real part, then the imaginary part's sign, - if it is negative and + if not, then the imaginary part without its sign, then j (e.g., 3+4j and 1.5-0.25j). Each part is written as a REAL's body is, in positional notation with no exponent, so -0 is the negative zero in either (e.g., -0-0j). Both parts and the sign between them are always written, so each value has one spelling (e.g., 0+4j, not 4j, and 3+0j, not 3).
Characters:
- the digits of the word's radix (§6.3), for both parts;
., the decimal point, in either part, as in a REAL (e.g.,0.5+1.5j);-, before a negative real part, and between the parts before a negative imaginary part (e.g.,-1-2j);+, between the parts before an imaginary part that is not negative;j, after the imaginary part, in lower case, so that it is never read as a base-32 digit,Jbeing one (§6.3).
Unit: the IEEE 754 binary format each part is held in, as a REAL's (§6.2.6): W binary16, D binary32, Q binary64, and B, M, G and T are not allowed. Both parts are held in the one format, and a part the format does not hold exactly is invalid, and is not rounded (e.g., |♦D•W→0.5+1j is valid and |♦D•W→0.1+1j is not).
Examples: 3+4j, 0.5-1.5j, 0+1j, -0-0j.
6.2.11 NULL
An explicit absent value: a NULL word states that a value is absent, and is not the empty text, a zero or false (§6.2.4, §6.2.5, §6.2.9).
Characters: none.
Values: one, whose body is empty: [SOW] _ [SOB] [EOW] is the NULL word.
6.2.12 STRING
A string of any Unicode characters, in UTF-8 (RFC 3629), where a TEXT word holds ASCII alone (§6.2.4). The string is not the word's body but its block in the tail, as a BLOB's data is (§7.3), and the body is the string's size in bytes, written as a BLOB word's body is, a count from 0 to the unsigned maximum of its unit with no leading zero (§6.2.8); so no UTF-8 enters the head, and an instance that never reads a string still knows the tail's length from its size, as it does from a BLOB's.
Characters: in the body, the digits of the word's radix (§6.3); in the block, the code points U+0000 to U+10FFFF except the surrogates U+D800 to U+DFFF, each in its one UTF-8 encoding, of one to four bytes.
Values: any run of those code points, the empty one included: "♦D•B→0, with an empty block, is the empty string. A string is not normalized: the same letter in other code points is another string (e.g., é as U+00E9, the bytes 0xC3 0xA9, and as e then U+0301, the bytes 0x65 0xCC 0x81).
A block that is not well-formed UTF-8, as one holding an overlong form, a surrogate, a code point past U+10FFFF, a byte that begins no sequence or a sequence the block's end cuts off, makes the word invalid, and its message malformed (Section 7.4). Its size, and so the tail, is known, so a receiver reads past the block. A STRING word whose flags or body are invalid breaks the frame, as an invalid BLOB word does (§6.2.8).
Examples: "♦D•B→5 with the block hello, "♦D•B→6 with héllo, "♦D•B→0 with an empty block.
6.3 Radix Flags
Specifies the base encoding of a numeric body. Follows the SOR (0x04) delimiter.
| Flag | Name | Base | Digits |
|---|---|---|---|
| I | Binary | Base 2 | 0–1 |
| O | Octal | Base 8 | 0–7 |
| D | Decimal | Base 10 | 0–9 |
| H | Hexadecimal | Base 16 | 0–9, A–F |
| U | Duotrigesimal | Base 32 | 0–9, A–V |
Letters are upper case. These are the digits of a numeric word's body (§6.2); the base-32 identifiers of Section 5 are identifiers, not numeric values, and have an alphabet of their own (Section 5.7).
6.4 Unit Flags
Specifies the data width of a numeric value. Follows the SOU (0x07) delimiter. Required for INTEGER, REAL, SCIENTIFIC, BLOB, COMPLEX and STRING word types. Forbidden for all other types including ID. Each word type takes only the units its rules name (§6.2), and none takes M, G or T, which are reserved.
| Flag | Name | Size |
|---|---|---|
| B | Byte | 8 bits |
| W | Word | 16 bits |
| D | Doubleword | 32 bits |
| Q | Quadword | 64 bits |
| M | Megabyte | 2²⁰ bytes |
| G | Gigabyte | 2³⁰ bytes |
| T | Terabyte | 2⁴⁰ bytes |
6.5 Word Encoding Examples
Each example shows the pseudo form (using abbreviations) and the wire form (using CP437 glyphs). In wire form: ↕=SOW, ♦=SOR, •=SOU, →=SOB, ►=EOW.
SYMBOL word (verb "Establish"):
Pseudo: [SOW] ! [SOB] E [EOW]
Wire: ↕!→E►No flags. Body is a single character.
ID word (ASCII, no radix — trace identifier):
Pseudo: [SOW] @ [SOB] example [EOW]
Wire: ↕@→example►Plain ASCII identifier. No radix or unit flags.
ID word (base-32 radix — BID):
Pseudo: [SOW] @ [SOR]U [SOB] 4T9X2 [EOW]
Wire: ↕@♦U→4T9X2►Radix flag U (duotrigesimal). No unit flag.
ID word (decimal radix — MID):
Pseudo: [SOW] @ [SOR]D [SOB] 1 [EOW]
Wire: ↕@♦D→1►Radix flag D (decimal). No unit flag.
TEXT word:
Pseudo: [SOW] ~ [SOB] temperature [EOW]
Wire: ↕~→temperature►No flags. Body is printable ASCII per §6.2.4.
PATH word (dot-separated BID sequence):
Pseudo: [SOW] / [SOB] AA.BB.CC.DD [EOW]
Wire: ↕/→AA.BB.CC.DD►No flags. Body is a dot-separated sequence of BIDs forming a route.
INTEGER word (decimal, byte-width, value 32):
Pseudo: [SOW] # [SOR]D [SOU]B [SOB] 32 [EOW]
Wire: ↕#♦D•B→32►Radix D (decimal), Unit B (byte). Both required.
INTEGER word (hex, doubleword, value FF00):
Pseudo: [SOW] # [SOR]H [SOU]D [SOB] FF00 [EOW]
Wire: ↕#♦H•D→FF00►Radix H (hex), Unit D (doubleword).
INTEGER word (decimal, word-width, negative):
Pseudo: [SOW] # [SOR]D [SOU]W [SOB] -273 [EOW]
Wire: ↕#♦D•W→-273►Signed, within -32768 to 32767 for unit W, per §6.2.5.
REAL word (decimal, binary16, value 0.5):
Pseudo: [SOW] $ [SOR]D [SOU]W [SOB] 0.5 [EOW]
Wire: ↕$♦D•W→0.5►Unit W (binary16), which holds 0.5 exactly, per §6.2.6.
SCIENTIFIC word (decimal, decimal32):
Pseudo: [SOW] % [SOR]D [SOU]D [SOB] 6.02E23 [EOW]
Wire: ↕%♦D•D→6.02E23►Three significant figures, unit D (decimal32), per §6.2.7.
LOGICAL word (exclusion expression):
Pseudo: [SOW] ? [SOB] ~H & ~q [EOW]
Wire: ↕?→~H & ~q►No flags. Body is a logical expression per §6.2.1 — in a Scaleback, neither hex nor quadword.
BLOB word (hex, doubleword, declaring 6656 byte tail block):
Pseudo: [SOW] * [SOR]H [SOU]D [SOB] 1A00 [EOW]
Wire: ↕*♦H•D→1A00►Radix H, Unit D. Declares a tail block of 0x1A00 (6656) bytes.
DATETIME word:
Pseudo: [SOW] & [SOB] 2026-02-08T12:00:00Z [EOW]
Wire: ↕&→2026-02-08T12:00:00Z►No flags. Body is an ISO 8601 date and time in UTC per §6.2.2.
DURATION word:
Pseudo: [SOW] : [SOB] P1DT12H [EOW]
Wire: ↕:→P1DT12H►No flags. Body is an ISO 8601 duration per §6.2.3: one day and twelve hours, not PT36H.
BOOL word:
Pseudo: [SOW] = [SOB] 1 [EOW]
Wire: ↕=→1►No flags. True, per §6.2.9.
COMPLEX word (decimal, binary32):
Pseudo: [SOW] | [SOR]D [SOU]D [SOB] 3+4j [EOW]
Wire: ↕|♦D•D→3+4j►Both parts held in binary32, per §6.2.10.
NULL word:
Pseudo: [SOW] _ [SOB] [EOW]
Wire: ↕_→►No flags. The body is always empty, per §6.2.11.
STRING word (decimal, byte-width, declaring a 6-byte tail block):
Pseudo: [SOW] " [SOR]D [SOU]B [SOB] 6 [EOW]
Wire: ↕"♦D•B→6►The block in the tail holds héllo in UTF-8, six bytes, per §6.2.12.
7. Message Frame Structure
7.1 Frame Layout
[SOM] Word₁ Word₂ ... Wordₙ [EOM] Tail₁ Tail₂ ...A message begins with SOM (0x02) and the head ends with EOM (0x03). The head contains one or more words, each delimited by SOW/EOW. The optional tail follows EOM and consists of binary data blocks. Appendix B gives a message's syntax as a grammar.
7.2 Head
The head is the structured, CP437-encoded portion of the message. It contains words that specify the operation, addressing, parameters, and any tail size declarations. Words are parsed sequentially as they arrive — a receiver does not need to buffer the entire head before processing.
7.3 Tail
The tail is optional binary data following EOM. It is partitioned into blocks whose sizes are declared by BLOB (*) and STRING (") words in the head, indexed by their position among those words. The first such word declares the size of the first tail block, the second the second block, and so on. A BLOB block is arbitrary bytes; a STRING block is a string in UTF-8 (§6.2.12). A receiver that has parsed the head knows the exact byte count of the tail before the first tail byte arrives.
BLOB Word Semantics: The BLOB body is a number representing the byte count of the corresponding tail block, per §6.2.8. The radix flag specifies the number's encoding (decimal, hex, etc.) and the unit flag specifies the numeric range (B = 8-bit, W = 16-bit, D = 32-bit, Q = 64-bit). A STRING body is the same count for its block. For example, *♦D•B→10 declares a 10-byte block; *♦H•D→1A00 declares a 6656-byte block.
Example: A message with two BLOB words in the head:
[SOM] ...words... [SOW]*[SOR]H[SOU]D[SOB]100[EOW] [SOW]*[SOR]H[SOU]D[SOB]200[EOW] [EOM] <256 bytes> <512 bytes>Both BLOB words use unit D (doubleword / 32-bit size field) and radix H (hexadecimal). The first declares 0x100 = 256 bytes. The second declares 0x200 = 512 bytes. Total tail length is 768 bytes.
BLOB and STRING words are positional within a message head. Each declares exactly one tail block of its own, indexed by its ordinal position among the head's BLOB and STRING words. A tail shorter than the sum of their declared sizes makes the message malformed. A receiver detects that only when the bytes stop before the tail is complete, and then drops the message (Section 7.4).
7.4 Stream Parsing
Antheos is designed for stream parsing. A receiver processes one byte at a time:
- Wait for SOM (0x02). All bytes before SOM are discarded.
- Parse words as SOW/EOW pairs arrive. Each word is self-describing: word type, optional radix flag, optional unit flag, and body content.
- On EOM (0x03), the head is complete. Sum the sizes declared by any BLOB and STRING words to determine the total tail length.
- Read tail bytes until the declared total is reached. The next SOM starts a new message.
A receiver that cannot process a message drops it, and how it finds the next one depends on whether the frame is whole. A word whose flags or body its type's rules do not admit (§6.1, §6.2), other than a BLOB or STRING word, makes the message malformed but leaves the frame whole: the receiver reads on to EOM, skips the tail the message's BLOB and STRING words declare (Section 7.3), and the next byte is outside the message. A word of a type the receiver does not implement leaves the frame whole in the same way, and its message is dropped: every instance recognizes the fifteen word types of §6.2, though it need not implement them all (Section 14.1). The frame is broken when a byte that must be a delimiter or a flag is not one, when the byte after SOW is none of the fifteen word types, when a word holds a reserved byte (Section 4.1), or when a BLOB or STRING word has flags or a body its rules do not admit (§6.2.8, §6.2.12), since the tail it declares cannot then be trusted, and the receiver then abandons the frame and scans forward for the next SOM byte. A message whose BLOB and STRING words declare more than 2⁶⁴−1 bytes of tail in all breaks the frame as well. A receiver MAY break the frame when the tail a message declares is longer than it will read, or MAY read past that tail with the frame whole (Section 15). It MAY answer a message beyond its limits, such as one too long for its buffer, with a Scaleback (Section 9.2), and one holding a word of a type it does not implement with !X UNSUPPORTED_TYPE if it implements Exception (Section 13). It MAY answer a malformed message, such as one with an invalid word (§6.1), with !X MALFORMED_FRAME if it implements Exception (Section 13), and never answers one with a Scaleback.
Because SOM is a reserved byte (Section 4.1) that cannot appear inside a head word, a SOM encountered before the current frame's EOM is unambiguously the start of a new message. A stream parser abandons the incomplete frame and resynchronizes at this SOM — consistent with step 1, only the bytes before the SOM are discarded; the SOM itself begins the new frame — rather than consuming it and waiting for a later SOM (which would also discard the frame the in-head SOM begins). This does not apply within the tail: tail bytes are length-delimited binary (Section 7.3), so a 0x02 byte there is data, not a delimiter, and is consumed as part of the declared block.
7.5 Messages Without Tail
A message with no BLOB or STRING words in the head has no tail. The byte immediately following EOM is either the SOM of the next message or non-message bus traffic. This is the common case for control messages (Establish, Conflict, Ping, etc.).
7.6 Word Order
Words within a message head MUST appear in the following order:
- SYMBOL word — exactly one, always the first word.
- ID words — zero or more: those the verb's section defines, in the order it defines them (e.g., a session message's SID, sender BID, target BID and MID, Section 11).
- Payload words — zero or more, in the order the verb's section defines where it defines one (e.g., a Scaleback's, Section 9.2.2, and a path-addressed message's index and PATH last, Section 9.3), and otherwise in application-defined order.
7.7 Text Payloads
Some payload words carry text of the sender's own: the text of a Broadcast (Section 9.4) or a Relay (Section 9.3.2), the capability of a Query, an Offer, an Accept or a SERVICE_UNKNOWN Exception (Section 10), and the request, response or event of a Call (Section 11.3) or a Notify (Section 11.5). Such a payload is a TEXT word when TEXT can hold it, which is when it is printable ASCII (§6.2.4), and otherwise a STRING word, whose block in the tail is the text in UTF-8 (§6.2.12). A receiver reads the two alike: the payload is the text, whichever word carries it, and a STRING holding text TEXT could hold is read as that text. A sender writes as TEXT all the text TEXT can hold; the examples in Sections 9 to 11 are printable ASCII and are TEXT words.
The other TEXT words carry protocol tokens, which are printable ASCII and are always TEXT: the nonce of an Establish (Section 9.1.1), the reason of an Exception (Section 13) and the state of a Status (Section 11.4).
Bytes that are not UTF-8 are not text, and are carried by a BLOB (§6.2.8).
Pseudo: [SOM] [SOW]![SOB]N[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]D[SOB]3[EOW] [SOW]"[SOR]D[SOU]D[SOB]12[EOW] [EOM] <12 bytes: température>
Wire: ☻↕!→N►↕@♦U→A7K2M►↕@♦U→4T9X2►↕@♦U→7M3K9►↕@♦D→3►↕"♦D•D→12►♥températureSession Notify from 4T9X2 to 7M3K9: SID=A7K2M, MID=3, event="température", which TEXT cannot hold. The twelve bytes after EOM are the event in UTF-8, the STRING's block in the tail.
8. Level 1 Symbols
Level 1 defines three scopes of operation: bus, service, and session. Symbols are single ASCII characters following the SYMBOL word type (!). Every symbol is unique across all scopes. No disambiguation is required.
8.1 Bus Scope
Bus scope operations are those between the instances on a bus: addressing, message relay, keepalive, capability negotiation, and error reporting.
| Symbol | Verb | Description |
|---|---|---|
| E | Establish | Claim a BID on this bus |
| C | Conflict | Report a BID collision |
| B | Broadcast | Send to all instances on this bus |
| P | Ping | Keepalive / presence check |
| p | Ping response | Report presence, with the responder's BID |
| R | Relay | Forward a message along a path |
| D | Discover | Find how a BID is reached |
| d | Discover response | Report the BID a target is reachable via |
| S | Scaleback | Declare receiver capability limits (reactive) |
| W | Acknowledge | Confirm receipt of a Scaleback |
| X | Exception | Report a structured error |
A Ping or Discover is answered with its own symbol in lower case, so a response is never read as a request. A bus delivers a response to every instance on it, so a response answers every instance that asked, each matching it to its request by what it names: a Ping or Discover response by the BID it names, and an Offer by its capability (Section 10.2). No request names its sender.
8.2 Service Scope
Service scope operations handle runtime capability discovery. An instance that wants a capability queries for it. Instances that can provide it offer. The querying instance accepts. No upfront advertisement, no schema exchange.
| Symbol | Verb | Description |
|---|---|---|
| Q | Query | Request a service or capability |
| O | Offer | Offer to provide the requested service |
| A | Accept | Accept an offer, establishing the service agreement |
8.3 Session Scope
Session scope operations manage active communication sessions between instances. Sessions are optional — not every interaction requires one.
| Symbol | Verb | Description |
|---|---|---|
| K | Call | Send a request within a session |
| T | Status | Request or report session state |
| N | Notify | Push an event notification within a session |
| L | Locate | Find the instance holding a session (SID) |
| U | Resume | Reconnect to an interrupted session |
| F | Finish | Close a session |
9. Bus Operations
9.1 BID Establishment
When an instance connects to a bus, it MUST obtain a unique BID through the Establish/Conflict protocol.
9.1.1 Establishment Procedure
- Generate a random BID candidate of the current length, in base-32 (Section 5.7). The current length is the initial length when the attempt begins.
- Transmit an Establish message with the candidate BID and a fresh nonce: 8 random bytes, written as 16 lower-case hex characters in a TEXT word.
- Wait for the timeout period.
- If a Conflict is received for this BID, or an Establish for it whose nonce is not the one this instance sent, increment the BID length and return to step 1, unless the length now exceeds the maximum: then go to step 5. An Establish read back with its own nonce is this instance's own, which a bus may deliver to its transmitter (Section 12).
- If the BID length exceeds the maximum, abort the connection attempt. An instance that implements Exception MAY first transmit one with reason "BID_OVERFLOW", which names no BID (Section 9.8).
- If no Conflict is received within the timeout, the BID is claimed. The instance begins listening on the bus, and keeps its nonce for as long as it holds the BID, so that it knows its own Establish when the bus delivers it late (Section 9.1.2).
The initial length, the maximum and the timeout are the instance's own, for the bus it connects to. The protocol fixes none of them.
Establishment detects a collision only while it runs. If two instances propose one BID and both Establishes are lost, each claims it, and nothing in this specification detects the duplicate afterwards.
9.1.2 Conflict Detection
While listening on a bus, an instance that receives an Establish message for a BID it already holds MUST respond with a Conflict message containing that BID, unless the Establish carries its own nonce: that one is its own, read back (Section 9.1.1).
9.1.3 State Machine
| State | Entry Condition | Action | Transitions |
|---|---|---|---|
| IDLE | Initial / after disconnect | None | PROPOSING (on connect) |
| PROPOSING | Generated candidate BID | Send !E, start timeout | ESTABLISHED (timeout expires, no conflict) / PROPOSING (Conflict or rival Establish received, length < max) / FAILED (length >= max) |
| ESTABLISHED | No conflict within timeout | Listen on bus | IDLE (on disconnect) |
| FAILED | Max BID length exceeded | MAY send !X "BID_OVERFLOW" | IDLE |
9.1.4 Wire Examples
Establish BID:
Pseudo: [SOM] [SOW]![SOB]E[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]~[SOB]9f3a61c08e2b4d57[EOW] [EOM]
Wire: ☻↕!→E►↕@♦U→4T9X2►↕~→9f3a61c08e2b4d57►♥Instance proposes BID "4T9X2" on the bus, with the nonce 9f3a61c08e2b4d57.
Conflict detected:
Pseudo: [SOM] [SOW]![SOB]C[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→C►↕@♦U→4T9X2►♥Existing instance reports BID "4T9X2" is already in use.
Establishment failed:
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]~[SOB]BID_OVERFLOW[EOW] [EOM]
Wire: ☻↕!→X►↕~→BID_OVERFLOW►♥Instance could not establish a BID within the maximum length.
9.2 Reactive Scaleback
Scaleback is the mechanism by which a constrained receiver communicates its limits to a sender. There is no upfront capability handshake. A receiver that cannot process a message because it exceeds the receiver's limits has two options: send a Scaleback response stating its limits, or silently drop the message. A malformed message is never answered with a Scaleback (Section 7.4).
9.2.1 Drop (Silent)
A receiver that cannot process a message and cannot (or chooses not to) send a Scaleback simply discards the rest of the message, as Section 7.4 describes. This is the universal fallback that requires zero transmit capability. The sender receives no indication that the message was dropped.
9.2.2 Scaleback Message
A Scaleback message carries its sender's BID, then the BID of the instance its limits apply to, and communicates those limits using two mechanisms:
- Negative flags (LOGICAL word): the radix and unit flags the receiver cannot handle, as one or more negated flag variables joined by
&(§6.2.1):~H & ~qreads "neither hex nor quadword". A radix is its flag letter in upper case (I,O,D,HorU, §6.3) and a unit its flag letter in lower case (b,w,dorq, §6.4), so~Dexcludes the decimal radix and~dthe doubleword unit. Three or more are grouped as §6.2.1 requires, as in(~H & ~q) & ~w. Any other expression, or a variable that names no such flag, makes the Scaleback malformed (Section 7.4). The exclusions govern the flags of INTEGER, REAL, SCIENTIFIC, BLOB, COMPLEX and STRING words; an ID word keeps its form whatever a Scaleback says (Section 5.7). - Positive sizes (INTEGER words): Maximum head size in bytes, followed by maximum tail size in bytes, each 0 or more; a Scaleback with a negative size is malformed (Section 7.4). The head size counts every byte of the head, from SOM through EOM, and the tail size every byte of the tail, all its blocks (Section 7.3). One INTEGER word is the head size alone. A head size of 0 leaves the head unconstrained, since no head is empty, and is how an unconstrained head is written before a tail size. A tail size of 0 means the receiver cannot handle binary tails.
A Scaleback carries no payload word but these: at most one LOGICAL, then at most two INTEGERs. Any other makes it malformed (Section 7.4).
A device with no limits sends no Scaleback, except to lift limits it has stated (Section 9.2.4). Only constraints are communicated: a dimension a Scaleback omits is unconstrained.
9.2.3 Scaleback Wire Examples
Full Scaleback (type exclusions + size limits):
Pseudo: [SOM] [SOW]![SOB]S[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]?[SOB]~H & ~q[EOW] [SOW]#[SOR]D[SOU]W[SOB]48[EOW] [SOW]#[SOR]D[SOU]W[SOB]0[EOW] [EOM]
Wire: ☻↕!→S►↕@♦U→7M3K9►↕@♦U→4T9X2►↕?→~H & ~q►↕#♦D•W→48►↕#♦D•W→0►♥From 7M3K9 to 4T9X2: cannot handle hex or quadword. Max head 48 bytes. No tail.
Size-only Scaleback (no type exclusions):
Pseudo: [SOM] [SOW]![SOB]S[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]#[SOR]D[SOU]W[SOB]64[EOW] [SOW]#[SOR]D[SOU]W[SOB]0[EOW] [EOM]
Wire: ☻↕!→S►↕@♦U→7M3K9►↕@♦U→4T9X2►↕#♦D•W→64►↕#♦D•W→0►♥From 7M3K9 to 4T9X2: no type exclusions. Max head 64 bytes. No tail.
Flags-only Scaleback (no size limits):
Pseudo: [SOM] [SOW]![SOB]S[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]?[SOB]~q & ~w[EOW] [EOM]
Wire: ☻↕!→S►↕@♦U→7M3K9►↕@♦U→4T9X2►↕?→~q & ~w►♥From 7M3K9 to 4T9X2: cannot handle the quadword or word (16-bit) unit. No size constraints.
Tail-only Scaleback (head unconstrained):
Pseudo: [SOM] [SOW]![SOB]S[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]#[SOR]D[SOU]W[SOB]0[EOW] [SOW]#[SOR]D[SOU]W[SOB]512[EOW] [EOM]
Wire: ☻↕!→S►↕@♦U→7M3K9►↕@♦U→4T9X2►↕#♦D•W→0►↕#♦D•W→512►♥From 7M3K9 to 4T9X2: no type exclusions. Head unconstrained. Max tail 512 bytes.
Lifting Scaleback (no limits):
Pseudo: [SOM] [SOW]![SOB]S[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→S►↕@♦U→7M3K9►↕@♦U→4T9X2►♥From 7M3K9 to 4T9X2: every limit stated before is lifted.
On receiving a Scaleback, the sender may resend the message within the stated limits, attempt a different encoding, or accept that this receiver cannot handle the message. The sender has no obligation to retry.
9.2.4 Scaleback Semantics
- A Scaleback governs the messages its target addresses to its sender: those naming the sender's BID as their target, and a Relay whose next hop is the sender (Section 9.3.2). Its limits hold for the duration of the current bus connection. A message to the whole bus is written as its sender writes it, and an instance that cannot take it drops it (Section 9.2.1).
- A Scaleback states every limit its sender has toward its target and replaces the one before. A receiver may send a new Scaleback at any time to change its limits (e.g., after freeing buffer space), and states in it every limit still in force: one it omits is lifted.
- A Scaleback with no LOGICAL word and no INTEGER words lifts every limit.
- Scaleback is unreliable by design. There is no guaranteed delivery. A sender that receives a Scaleback may respond with an Acknowledge (
!W) carrying its own BID, then the Scaleback sender's, confirming the limits were received: an Acknowledge from BID X to BID Y confirms receipt of the most recent Scaleback from Y to X, and with it the whole of the limits in force. A receiver that does not receive an Acknowledge MAY retransmit the Scaleback. A sender that does not respect stated limits after acknowledging them will have its messages silently dropped.
9.3 Path Addressing
Antheos uses a path-and-index mechanism for multi-hop routing. A PATH word contains a dot-separated sequence of BIDs representing the complete route. An INTEGER index word identifies the current position within that path. The index is zero-based: position 0 is the origin, position N is the Nth hop.
When paths are used for addressing, the message ends with the index followed by the path. This ordering is intentional: a stream-parsing instance reads the index first, then scans the path counting dots to find its target position. Single-pass, no backtracking, no buffering the entire path.
... [payload words] [#index] [/path] [EOM]Beyond the bus it is sent on, Level 1 carries the text of a broadcast that carries a PATH (Section 9.3.1) and of a Relay, and the RELAY_FAILED Exception (Section 9.3.3), and nothing else: a Query, Offer and Accept (Section 10) and every session message (Section 11) are addressed on one bus. A service on another bus is reached through an instance on both, which offers it on the one and uses it on the other.
9.3.1 Broadcast Path (Expanding)
A broadcast that carries a PATH travels beyond its bus, and the path expands as it goes. An instance on more than one bus forwards it onto its other buses, appending to the path the BID it holds on the bus it forwards onto. It forwards no broadcast whose path already holds one of its BIDs. No index is needed during broadcast — the message goes to all instances.
Each entry of a path is therefore the BID its instance holds on the bus it shares with the next entry, and the last entry the BID its instance holds on the bus it shares with the one before. A BID is unique only on its own bus (Section 5.4), so a path can hold one string for two instances on different buses, and either can take the other's entry for its own: it stops a broadcast that was not its to stop, or takes a relay not meant for it. Longer BIDs make this rarer.
A receiver that replies first appends to the path the BID it holds on the bus it received the broadcast on, so that the path holds the complete return route. It then sets the index to its own position, the last entry, and sends a Relay along that path (Section 9.3.2).
Nothing else bounds a broadcast in a mesh of buses. It can arrive at an instance once by each route that passes no instance twice, a receiver may reply along each, and nothing de-duplicates it (Section 15).
Trace: Broadcast from A (BID=AA) through B, C to D:
At A (origin):
Pseudo: [SOM] [SOW]![SOB]B[EOW] [SOW]~[SOB]Hello[EOW] [SOW]/[SOB]AA[EOW] [EOM]
Wire: ☻↕!→B►↕~→Hello►↕/→AA►♥Origin broadcasts. Path contains only the origin BID.
At B (appends BB):
Pseudo: [SOM] [SOW]![SOB]B[EOW] [SOW]~[SOB]Hello[EOW] [SOW]/[SOB]AA.BB[EOW] [EOM]
Wire: ☻↕!→B►↕~→Hello►↕/→AA.BB►♥B forwards broadcast, appending its BID to the path.
At C (appends CC):
Pseudo: [SOM] [SOW]![SOB]B[EOW] [SOW]~[SOB]Hello[EOW] [SOW]/[SOB]AA.BB.CC[EOW] [EOM]
Wire: ☻↕!→B►↕~→Hello►↕/→AA.BB.CC►♥C forwards broadcast, appending its BID.
At D (appends DD):
Pseudo: [SOM] [SOW]![SOB]B[EOW] [SOW]~[SOB]Hello[EOW] [SOW]/[SOB]AA.BB.CC.DD[EOW] [EOM]
Wire: ☻↕!→B►↕~→Hello►↕/→AA.BB.CC.DD►♥D receives the broadcast with the path AA.BB.CC and, to reply, appends the BID it holds on that bus. The path is now complete and fixed.
9.3.2 Relay Path (Index Decrement)
A relay carries the complete fixed path (established during broadcast or known in advance) and a zero-based index indicating the current sender's position. Its originator holds the path's last entry and sets the index to that position, as a broadcast's replier does (Section 9.3.1), and a recovery origin retransmits it from its own position (Section 9.3.3). Each forwarding instance decrements the index and forwards to the BID at position index–1 in the path. The procedure routes every message that carries an index and a PATH: a Relay, and the RELAY_FAILED Exception of Section 9.3.3.
The path never changes as a relay is forwarded, except where a recovery origin writes a gateway into it (Section 9.3.3). Only the index is modified. This means a forwarding instance performs no string manipulation — it reads the index, subtracts one, counts dots in the path to find the next-hop BID, and forwards.
Relay procedure at each hop:
- Read the index from the INTEGER word, and compute my position p = index − 1.
- If p < 0, or the index is past the path's last position, discard the message and answer nothing.
- Find path[p] by counting dots (zero-based) in the PATH body. If path[p] is none of the BIDs I hold, on any bus, the message is not addressed to me: discard it (Section 12).
- If p = 0: I am the destination. Deliver the message.
- Otherwise: write p as the new index and forward to path[p − 1], on the bus where I reach that BID. Which bus that is is my own knowledge (Section 2): the bus the path's broadcast arrived on, or a route a Discover gave me (Section 9.3.3).
Trace: D (index=3) replies to A via the established path AA.BB.CC.DD:
D sends (index=3, D's position):
Pseudo: [SOM] [SOW]![SOB]R[EOW] [SOW]~[SOB]Reply[EOW] [SOW]#[SOR]D[SOU]B[SOB]3[EOW] [SOW]/[SOB]AA.BB.CC.DD[EOW] [EOM]
Wire: ☻↕!→R►↕~→Reply►↕#♦D•B→3►↕/→AA.BB.CC.DD►♥D sends relay. Index=3, next hop is path[2]=CC.
At C (decrements to 2):
Pseudo: [SOM] [SOW]![SOB]R[EOW] [SOW]~[SOB]Reply[EOW] [SOW]#[SOR]D[SOU]B[SOB]2[EOW] [SOW]/[SOB]AA.BB.CC.DD[EOW] [EOM]
Wire: ☻↕!→R►↕~→Reply►↕#♦D•B→2►↕/→AA.BB.CC.DD►♥C forwards. Index=2, next hop is path[1]=BB.
At B (decrements to 1):
Pseudo: [SOM] [SOW]![SOB]R[EOW] [SOW]~[SOB]Reply[EOW] [SOW]#[SOR]D[SOU]B[SOB]1[EOW] [SOW]/[SOB]AA.BB.CC.DD[EOW] [EOM]
Wire: ☻↕!→R►↕~→Reply►↕#♦D•B→1►↕/→AA.BB.CC.DD►♥B forwards. Index=1, next hop is path[0]=AA.
At A (index=1, path[0]=AA = me): message delivered.
A receives the message. Index-1=0, path[0]=AA matches own BID. Delivered.
9.3.3 Path Recovery
Failure Detection: A forwarding instance that cannot deliver to the next-hop BID (instance disconnected, bus lost, timeout) MUST NOT forward the message. It becomes the recovery origin for that message.
Recovery: The recovery origin issues a Discover (!D) for the unreachable BID, broadcasting it on all buses it is connected to. If a Discover response is received within timeout, the recovery origin records the route it gives and retransmits the message from its own position in the path. A response from the BID's holder leaves the path as it is. A response from a gateway, which names the BID the unreachable one is reached via (Section 9.6), writes that BID into the path just before the recovery origin's entry, so that the recovery origin's position, and the index it sends, is one greater: every instance that handles the message holds a position in its path.
Wire example (C cannot reach BB):
Pseudo: [SOM] [SOW]![SOB]D[EOW] [SOW]@[SOR]U[SOB]BB[EOW] [EOM]
Wire: ☻↕!→D►↕@♦U→BB►♥C broadcasts Discover for BID BB.
Wire example (the gateway 7M3K9 answers !d @U BB @U 7M3K9, and C retransmits D's Relay through it):
Pseudo: [SOM] [SOW]![SOB]R[EOW] [SOW]~[SOB]Reply[EOW] [SOW]#[SOR]D[SOU]B[SOB]3[EOW] [SOW]/[SOB]AA.BB.7M3K9.CC.DD[EOW] [EOM]
Wire: ☻↕!→R►↕~→Reply►↕#♦D•B→3►↕/→AA.BB.7M3K9.CC.DD►♥C writes 7M3K9 in before its own entry and sends from its new position, 3. 7M3K9 (p = 2) forwards to BB, and BB (p = 1) to AA.
Discover Fails: If no Discover response is received within timeout, the recovery origin sends !X RELAY_FAILED back to the Relay's originator. Its path is the Relay's path from the originator's entry to the recovery origin's, reversed, so that the originator is at position 0 and the recovery origin last; its index is the recovery origin's position in that path, and it is routed as Section 9.3.2 describes. The Exception carries the unreachable BID as its one ID word, then its reason, then the index and the path: !X @U <unreachable BID> ~RELAY_FAILED #<index> /<path>. It names no sender, the path's entry at its index being the recovery origin's.
Wire example (D relays to A along AA.BB.CC.DD, and C cannot reach BB):
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]@[SOR]U[SOB]BB[EOW] [SOW]~[SOB]RELAY_FAILED[EOW] [SOW]#[SOR]D[SOU]B[SOB]1[EOW] [SOW]/[SOB]DD.CC[EOW] [EOM]
Wire: ☻↕!→X►↕@♦U→BB►↕~→RELAY_FAILED►↕#♦D•B→1►↕/→DD.CC►♥C sends RELAY_FAILED for BB along DD.CC, the Relay's path from D to C reversed. Index=1, C's position. D, at position 0, delivers it.
Return Path Also Broken: If the recovery origin cannot relay the Exception back, it MUST silently discard the message. The originator will detect failure through its own timeout.
9.4 Broadcast
A broadcast message is sent to all instances on the current bus. A broadcast with no PATH, as below, stays on that bus. One with a PATH travels on beyond it (Section 9.3.1), and a reply returns along the path as a Relay (Section 9.3.2).
Pseudo: [SOM] [SOW]![SOB]B[EOW] [SOW]~[SOB]Hello all[EOW] [EOM]
Wire: ☻↕!→B►↕~→Hello all►♥Broadcast to all instances on this bus.
9.5 Ping
Ping is a keepalive and presence check. It may target a specific BID or be broadcast. The response is a Ping response (!p) carrying the responder's BID. A Ping naming a BID is answered by the holder of that BID alone, and every other instance stays silent. A broadcast Ping is answered by every instance that reads it, its sender included when the bus delivers the Ping back to it (Section 12).
Ping a specific instance:
Pseudo: [SOM] [SOW]![SOB]P[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→P►↕@♦U→4T9X2►♥Ping instance at BID "4T9X2".
Response:
Pseudo: [SOM] [SOW]![SOB]p[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→p►↕@♦U→4T9X2►♥Pong: instance 4T9X2 confirms presence.
Broadcast ping:
Pseudo: [SOM] [SOW]![SOB]P[EOW] [EOM]
Wire: ☻↕!→P►♥Broadcast ping. All instances respond with their BID.
Broadcast responses:
Wire: ☻↕!→p►↕@♦U→4T9X2►♥
Wire: ☻↕!→p►↕@♦U→7M3K9►♥Multiple instances respond, each with own BID.
9.6 Discover
Discover (!D) finds how a BID is reached: through its holder, or through an instance that can reach it. This is used for path recovery when a relay cannot reach the next hop.
Request:
Pseudo: [SOM] [SOW]![SOB]D[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→D►↕@♦U→4T9X2►♥Discover: where is BID 4T9X2?
Response (from the holder of BID 4T9X2):
Pseudo: [SOM] [SOW]![SOB]d[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→d►↕@♦U→4T9X2►↕@♦U→4T9X2►♥Discovered: BID 4T9X2 answers for itself.
Response (from a gateway to BID 4T9X2):
Pseudo: [SOM] [SOW]![SOB]d[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [EOM]
Wire: ☻↕!→d►↕@♦U→4T9X2►↕@♦U→7M3K9►♥Discovered: BID 4T9X2 is reachable via BID 7M3K9.
A Discover response (!d) carries the target BID, then the BID it is reachable via. An instance that receives a Discover for a BID it holds responds with that BID as both. An instance connected to multiple buses that knows the target BID is reachable on another bus MAY respond with the gateway BID for that route as the second. Every other instance stays silent. If no response is received, the BID is considered unreachable.
9.7 Acknowledge
Acknowledge (!W) confirms receipt of a Scaleback, and of nothing else. It carries its sender's BID, then the BID of the instance whose Scaleback it confirms: an Acknowledge from X to Y confirms the most recent Scaleback from Y to X (Section 9.2.4).
Pseudo: [SOM] [SOW]![SOB]W[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [EOM]
Wire: ☻↕!→W►↕@♦U→4T9X2►↕@♦U→7M3K9►♥Acknowledge: 4T9X2 confirms receipt of the most recent Scaleback from BID "7M3K9".
Acknowledge is never mandatory. A Scaleback's sender that receives none MAY retransmit the Scaleback (Section 9.2.4).
9.8 Exception
Exceptions are structured error reports. The SYMBOL !X is followed by the BIDs that address the Exception, then a TEXT word containing the error reason. An Exception names its sender's BID, then, when the message it answers names its sender, that instance's BID, as every message naming two BIDs names its sender first. One answering a message that names no sender, as a Broadcast, Query, Ping or Discover does not, names its sender's BID alone. An instance drops an Exception that names another instance as its target.
Three reasons are addressed otherwise. BID_OVERFLOW names no BID: its sender holds none, and its last candidate is another instance's (Section 9.1.1). RELAY_FAILED is addressed by its path, and carries the BID its sender could not reach, with an index and a path after its reason (Section 9.3.3). A reason of Session scope (Section 13) carries the session's IDs, as set out below. A SERVICE_UNKNOWN carries the capability after its reason (Sections 10.1 and 10.3.1).
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]~[SOB]MALFORMED_FRAME[EOW] [EOM]
Wire: ☻↕!→X►↕@♦U→4T9X2►↕@♦U→7M3K9►↕~→MALFORMED_FRAME►♥Instance "4T9X2" reports a malformed message from "7M3K9".
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]~[SOB]UNSUPPORTED_TYPE[EOW] [EOM]
Wire: ☻↕!→X►↕@♦U→4T9X2►↕~→UNSUPPORTED_TYPE►♥Instance "4T9X2" reports a word type it does not implement, in a message that named no sender.
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]~[SOB]BID_OVERFLOW[EOW] [EOM]
Wire: ☻↕!→X►↕~→BID_OVERFLOW►♥Exception: BID establishment exceeded maximum length.
Session reasons: an Exception whose reason is of Session scope answers a session message, and is addressed as one is (Section 11): the SID, the sender's BID, then the BID of the instance it answers, which it always names. Its reason is its only TEXT word and it carries no MID, and one in any other shape is malformed (Section 7.4). An instance drops one that names its own BID as sender, or another instance as its target. Section 11.7 shows one.
10. Service Operations
Service discovery follows a strict Query/Offer/Accept pattern. An instance that needs a capability queries for it. Instances that can provide it respond with offers. The querying instance selects and accepts an offer.
Sessions are not obligatory. A service interaction may consist of a single Query/Offer/Accept exchange followed by direct message exchange, or it may establish a long-lived session. The service scope defines the discovery mechanism; the session scope (Section 11) defines optional persistent communication. Both are addressed on one bus (Section 9.3).
10.1 Query
An instance broadcasts a query describing the service it needs, or directs it to one instance by naming that instance's BID before the capability. A directed Query is answered by the instance it names alone. The query carries the desired capability as a text payload (Section 7.7). The capability MUST be non-empty: an empty query describes no service, and a match-all interpretation would constitute the capability enumeration the demand-driven model prohibits (Sections 10.2, 15). A sender MUST NOT send an empty capability, and a receiver MUST NOT answer a Query with one.
Pseudo: [SOM] [SOW]![SOB]Q[EOW] [SOW]~[SOB]temperature[EOW] [EOM]
Wire: ☻↕!→Q►↕~→temperature►♥Query: I need a temperature service.
Directed Query:
Pseudo: [SOM] [SOW]![SOB]Q[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]~[SOB]temperature[EOW] [EOM]
Wire: ☻↕!→Q►↕@♦U→4T9X2►↕~→temperature►♥Query to 4T9X2 alone: do you offer a temperature service?
The target of a directed Query that does not offer the capability MAY answer with !X SERVICE_UNKNOWN, naming its own BID before the reason and carrying the capability after it, so that the refusal is matched to its Query as an Offer is (Section 8.1). A broadcast Query is never answered with it.
SERVICE_UNKNOWN:
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]~[SOB]SERVICE_UNKNOWN[EOW] [SOW]~[SOB]temperature[EOW] [EOM]
Wire: ☻↕!→X►↕@♦U→4T9X2►↕~→SERVICE_UNKNOWN►↕~→temperature►♥4T9X2 does not offer a temperature service.
10.2 Offer
Instances that can provide the requested service respond with an offer. The offer includes the responding instance's BID and the capability the Query asked for, exactly as the Query wrote it, and answers every instance whose Query carried that text (Section 8.1). The capability names the interface — there is no separate interface definition language.
An Offer MUST only be sent in response to a Query. Unsolicited Offers (broadcasting capabilities at startup or at any time without a preceding Query) are prohibited. This prevents capability enumeration by passive observers and enforces the demand-driven discovery model described in Section 8.2.
Pseudo: [SOM] [SOW]![SOB]O[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]~[SOB]temperature[EOW] [EOM]
Wire: ☻↕!→O►↕@♦U→4T9X2►↕~→temperature►♥Offer: instance at BID "4T9X2" can provide temperature.
10.3 Accept
The querying instance accepts an offer by addressing the offering instance's BID. This establishes the service agreement and may optionally initiate a session.
10.3.1 Accept Wire Format
An Accept names its sender's BID, then the offering instance's, then the capability it takes, as its Query wrote it (Section 10.1). Naming its sender lets the offering instance identify who accepted its offer without waiting for a session to be established, and naming the capability tells it which of its offers was taken.
Pseudo: [SOM] [SOW]![SOB]A[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]~[SOB]temperature[EOW] [EOM]
Wire: ☻↕!→A►↕@♦U→7M3K9►↕@♦U→4T9X2►↕~→temperature►♥Accept: instance "7M3K9" accepts the offer of temperature from BID "4T9X2".
The first @U word is the sender's BID (the accepting instance) and the second the target's (the offering instance), as every message naming two BIDs names its sender first.
An Offer has no lifetime. An offering instance that no longer provides the capability an Accept takes MAY answer it with !X SERVICE_UNKNOWN, naming its own BID and then the accepting instance's, since the Accept names its sender, and carrying the capability after the reason.
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]~[SOB]SERVICE_UNKNOWN[EOW] [SOW]~[SOB]temperature[EOW] [EOM]
Wire: ☻↕!→X►↕@♦U→4T9X2►↕@♦U→7M3K9►↕~→SERVICE_UNKNOWN►↕~→temperature►♥4T9X2 refuses 7M3K9's Accept: it no longer offers a temperature service.
10.4 Offer Collection
A Query may elicit multiple Offers from different instances. The querying instance decides how to handle them:
- First-offer-wins: Accept the first Offer received, ignore subsequent Offers. Minimizes latency.
- Collect-then-choose: Wait for a timeout period, collect all Offers, then Accept the preferred one. Enables comparison.
Offers not followed by an Accept are implicitly declined. No explicit rejection message is required. An offering instance MUST NOT assume its Offer will be accepted and MUST NOT allocate resources until Accept is received.
11. Session Operations
Sessions provide persistent, stateful communication between instances. They are optional — bus-scope operations and one-shot service interactions do not require sessions. When an interaction needs message sequencing, state tracking, or long-lived communication, a session is established.
Addressing: every session message carries the SID, then the BID of its sender, then optionally the BID of the instance it is meant for, its target. Call, Status and Notify carry a MID after them (Section 11.1.2), a Call MAY carry a trace identifier after its MID (Section 11.3), and Locate, Resume and Finish carry neither. A session message in any other shape is malformed (Section 7.4). A bus may deliver a frame to its sender as well as to every other instance, so an instance can read its own session messages back: it drops one that names its own BID as sender. An instance drops a session message that names another instance as its target. An Exception answering a session message is addressed the same way, and always names its target (Section 9.8).
11.1 Session Lifecycle
11.1.1 Creation
A session is typically initiated after a service Accept, but may be opened directly between instances that already know each other. The opener generates a fresh SID (Section 11.2) and opens the session with a Call carrying it. An instance that receives a Call at MID 1 carrying a SID it does not hold takes it as the opening of a session, whether or not an Accept came first; this is the direct open. An instance MAY decline a session by dropping its first Call. One that cannot hold another session instead refuses that Call with !X SESSION_LIMIT (Section 9.8), and no session opens.
Any other session message carrying a SID the receiver does not hold opens nothing. A Call at any other MID, a Status or a Resume draws !X SESSION_NOT_FOUND (Section 9.8), so that an opener whose peer has lost the session, as by a reboot, learns it and opens again under a fresh SID (Section 11.2). A Locate, a Notify or a Finish is dropped: a Locate is a search, which an instance not holding the session leaves to the one that does (Section 11.6).
A Call at MID 1 carrying a SID the receiver already holds, other than the first Call after a wrap (Section 11.1.2), would open a second session under that SID, unless it comes from the instance that opened that session. From that instance it is a retransmission (Section 11.1.2): the receiver answers it again while MID 1 is the last request it answered, and drops it as stale after, keeping that answer and nothing more. From any other instance, the receiver refuses it with !X SID_IN_USE (Section 9.8), and that instance opens again under a fresh SID (Section 11.2). It sends no Finish for the refused SID, which names the receiver's other session.
11.1.2 Active Communication
Within a session, each end sequences the messages it sends by MID, a decimal counter of its own that starts at 1 and increments by 1 with each Call, Status or Notify it sends. Only the opener sends requests, a Call (!K) or a Status (!T) querying the session's state, and it sends one at a time: the other end's next Call answers a Call, and its next Status answers a Status. Either end may push an unsolicited event with Notify (!N).
MID Wrap: After sending a message with MID 4294967295, an end transmits a wrap message: a Call (!K) addressed as its other messages in the session are (Section 11), with MID 0 and no payload, which answers nothing and expects no response. The receiver resets its expectation of that end's MIDs to 1. Normal sequencing resumes immediately after the wrap message.
Retransmission: a retransmitted Call or Status carries its original MID. An end keeps the last answer it sent in each session. When the other end sends that answer's request again, with its MID, the end sends the answer again, unchanged, and does not act on the request twice. Of the MIDs the other end has sent since its last wrap message, if any, the end accepts each one it has not received and that is not stale: one above the next it expects, the messages between having been lost, as a Notify may be, and one below it, a lost message sent again, as a Call is when a Notify sent after it arrived first. How many MIDs below the highest it has received an end remembers is its own, and it takes an older one as received. One below the MID of the last request it answered is stale and is dropped, as is any MID it has already received, other than that of the request it answers again. A request that arrives while an earlier one is unanswered is taken in turn, after it. For example, 7M3K9's Call at MID 5 is answered by 4T9X2's Call at MID 9, which is lost: 7M3K9 sends its Call at MID 5 again, and 4T9X2 sends its Call at MID 9 again, as it first sent it, without acting on the request a second time.
Another instance: a session's opener is the instance that opened it or, after a Resume, the instance that resumed it (Section 11.7). The session's other end takes no Call, Status or Notify in it from any other instance. It refuses a Call from another instance with !X SID_IN_USE (Section 9.8) at any MID, as at MID 1 (Section 11.1.1), and drops a wrap message, a Status or a Notify from it, so none of them disturbs the session or its opener. A sender's BID is self-asserted, so this stops an accident or casual interference and not a forger (Section 15).
11.1.3 Interruption and Recovery
If a session is interrupted (bus disconnect, device reset), the Locate verb finds where the remote instance now resides, and Resume reconnects to the session. An ACTIVE session with no message from the other end within the instance's idle time, which is implementation-defined, becomes SUSPENDED, as on a disconnect, and is then resumed or times out as any other (Section 11.1.5).
11.1.4 Termination
Finish closes a session, and its SID is discarded (Section 11.2).
11.1.5 State Machine
| State | Entry Condition | Transitions |
|---|---|---|
| IDLE | No session | ACTIVE (on the opening Call, Section 11.1.1) |
| ACTIVE | Session created, SID assigned | SUSPENDED (on disconnect or idle time) / IDLE (on Finish) |
| SUSPENDED | Bus lost or timeout | ACTIVE (on Resume, or a message from the other end) / IDLE (on Finish or timeout expiry) |
Session Timeout: The duration before a SUSPENDED session transitions to IDLE is implementation-defined. An instance that receives a Resume (!U) for a session that has timed out responds with !X SESSION_EXPIRED while it still remembers the SID. How long it remembers one is implementation-defined, and once it has forgotten the SID, responding with !X SESSION_NOT_FOUND is conformant. Either end MAY signal an impending timeout with an unsolicited Status (!T) reporting EXPIRING (Section 11.4) before transitioning.
11.2 SID Generation
SIDs are variable-length base-32 hashes derived from a monotonic counter and device entropy. Every session receives a fresh SID, and no SID is issued while a session holds it. SID length starts short and grows on collision, keeping typical messages compact while scaling to high session counts.
SID Length: An implementation defines initial length, increment on collision, and maximum length, which is at most 26: the 16 bytes the procedure below hashes make 26 base-32 characters. SID generation starts at the initial length and grows on collision until a unique SID is found or maximum length is reached.
Device Entropy: device entropy is 8 bytes the instance holds, and the counter runs from 0 to 4294967295 (2³²−1). An instance never hashes the same device entropy and counter together twice. It either keeps both across reboots, storing the counter, or a lease of a block of counters, before the sessions it covers are opened, or draws fresh device entropy from a cryptographically secure source at every start, the counter then free to start at 0. Either way, it draws new device entropy when the counter wraps, before making another SID. It MAY draw fresh device entropy more often, as often as for every SID.
Generation Procedure:
- Hash the counter, written in decimal with no leading zero, followed by the 8 bytes of device entropy, with FNV-1a 64-bit (offset basis 14695981039346656037, prime 1099511628211). Store the hash as 8 bytes, least significant first, then hash those 8 bytes, as stored, with FNV-1a 64-bit from its offset basis, and store that hash after them, least significant first, for 16 bytes in all. (Truncation keeps the characters from the low-order bytes, which FNV-1a's last rounds vary most. It can still collide badly at very short lengths, so an implementation's initial length SHOULD stay clear of that regime — e.g. an initial length of 6 base-32 characters gives a 32⁶ ≈ 1.07×10⁹ space.)
- Encode the 16 bytes as base-32 (Section 5.7), five bits to a character, most significant bit first, the last character padded with zero bits, and keep the first characters, as many as the current SID length (starting at initial length).
- Check whether the truncated SID names a session the instance holds.
- If collision: increase length by increment, re-truncate, and repeat from step 3.
- If length exceeds maximum: increment sid_counter, reset length to initial, and repeat from step 1.
- If no collision: SID is assigned, increment sid_counter.
Rotation:
SIDs are generated fresh for every session. When a session finishes, its SID is discarded. The monotonic counter ensures forward progress, and the device entropy rule keeps a hash input from repeating, across reboots as within one.
11.2.1 Non-conformant Escape Hatch
The §11.2 generation procedure is the conformance bar — implementations claiming spec conformance MUST run the collision-check-and-grow algorithm against the active-session set. An implementation MAY additionally expose a non-conformant SID-generation path (e.g. an "unchecked" acquire that skips the active-set check) for memory-constrained environments where maintaining the active-set is infeasible.
Such a path is a structurally-named escape hatch — not a default. Implementations exposing one MUST:
- Name it explicitly to signal non-conformance at every call site (e.g.
acquire_unchecked()rather thanacquire()). - Document at the call site and in API documentation that calling it FORFEITS the spec §11.2 uniqueness guarantee: two concurrent sessions can receive identical SIDs at the minimum length under the base-32 truncation birthday bound.
- Not present the unchecked path as the default-named API of the SID generator.
These requirements bind an implementation's interface and documentation, not its messages, and no peer can test them on the wire.
A conformant peer encountering a counterparty that produced a duplicate SID refuses the second-arriving session with !X SID_IN_USE (Section 11.1.1). A duplicate SID from the session's own counterparty, the instance that opened it, is read as a retransmission instead. The conformance bar binds the implementation that generates the SID; the receiving peer is entitled to assume §11.2 was followed.
11.3 Call
Call sends a request within an active session and expects a response; the wrap message of Section 11.1.2 is the one Call that expects none. The response is itself a Call, the other end's next message, numbered from that end's own count (Section 11.1.2). Receipt of the response confirms delivery of the request. If no response arrives within timeout, the sender MAY retransmit it, with its original MID (Section 11.1.2), or treat the session as interrupted.
Trace identifier: a Call MAY carry a trace identifier, which its sender chooses, as a plain ID word after its MID (Section 5.7), so it is printable ASCII with no space or colon. A Call's payload is the application's, whole: no header is read in it, and a payload beginning T: is kept as it is.
Request:
Pseudo: [SOM] [SOW]![SOB]K[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]D[SOB]1[EOW] [SOW]~[SOB]get_reading[EOW] [EOM]
Wire: ☻↕!→K►↕@♦U→A7K2M►↕@♦U→7M3K9►↕@♦U→4T9X2►↕@♦D→1►↕~→get_reading►♥Session Call from 7M3K9 to 4T9X2: SID=A7K2M, MID=1, request="get_reading".
Response:
Pseudo: [SOM] [SOW]![SOB]K[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]D[SOB]1[EOW] [SOW]~[SOB]23.5[EOW] [EOM]
Wire: ☻↕!→K►↕@♦U→A7K2M►↕@♦U→4T9X2►↕@♦U→7M3K9►↕@♦D→1►↕~→23.5►♥Session Call response from 4T9X2 to 7M3K9: SID=A7K2M, MID=1, response="23.5".
Request with a trace identifier:
Pseudo: [SOM] [SOW]![SOB]K[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]D[SOB]1[EOW] [SOW]@[SOB]trace-7f3a[EOW] [SOW]~[SOB]get_reading[EOW] [EOM]
Wire: ☻↕!→K►↕@♦U→A7K2M►↕@♦U→7M3K9►↕@♦U→4T9X2►↕@♦D→1►↕@→trace-7f3a►↕~→get_reading►♥Session Call from 7M3K9 to 4T9X2: SID=A7K2M, MID=1, trace identifier "trace-7f3a", request="get_reading".
11.4 Status
Status requests or reports the current state of a session. A Status carrying a state is a report, and one carrying none is a request, which only the opener sends (Section 11.1.2). Either end MAY send a report unsolicited, as one reporting EXPIRING (Section 11.1.5). An end with a Status request outstanding takes the next report from the other end as its answer, an unsolicited one included, which states the session's state as truly; a report with none outstanding is unsolicited. A report answering a request confirms both the request's delivery and the session's current state.
A Status report carries one of three states. ACTIVE: the session is in use. SUSPENDED: it is interrupted and can be resumed (Section 11.1.3). EXPIRING: it is about to time out (Section 11.1.5). A Status reporting any other state is malformed (Section 7.4). IDLE is never reported, since an IDLE session does not exist.
Request:
Pseudo: [SOM] [SOW]![SOB]T[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]D[SOB]2[EOW] [EOM]
Wire: ☻↕!→T►↕@♦U→A7K2M►↕@♦U→7M3K9►↕@♦U→4T9X2►↕@♦D→2►♥Session Status request from 7M3K9 to 4T9X2: SID=A7K2M, MID=2.
Response:
Pseudo: [SOM] [SOW]![SOB]T[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]D[SOB]2[EOW] [SOW]~[SOB]ACTIVE[EOW] [EOM]
Wire: ☻↕!→T►↕@♦U→A7K2M►↕@♦U→4T9X2►↕@♦U→7M3K9►↕@♦D→2►↕~→ACTIVE►♥Session Status response from 4T9X2 to 7M3K9: SID=A7K2M, MID=2, state="ACTIVE".
11.5 Notify
Notify pushes an unsolicited event within a session. Unlike Call, it does not expect a response and delivery is not confirmed. Notify is fire-and-forget; use Call if acknowledgment is required.
Pseudo: [SOM] [SOW]![SOB]N[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]D[SOB]3[EOW] [SOW]~[SOB]threshold_exceeded[EOW] [EOM]
Wire: ☻↕!→N►↕@♦U→A7K2M►↕@♦U→4T9X2►↕@♦U→7M3K9►↕@♦D→3►↕~→threshold_exceeded►♥Session Notify from 4T9X2 to 7M3K9: SID=A7K2M, MID=3, event="threshold_exceeded".
11.6 Locate
Locate finds the instance holding a session (SID), by its BID. This is used after a bus change or reconnect. A Locate is a search and names no target. The holder's response names the requester as its target, and that is how a response is told from a request. An instance answers a Locate only for a session it holds SUSPENDED: one whose other end is still active with it is not the session sought. A holder drops a Locate that arrives while its session is still ACTIVE, and answers one that arrives after its idle time has suspended the session (Section 11.1.3). A requester that is not answered MAY send its Locate again; how often, and when it gives up, are its own.
Request:
Pseudo: [SOM] [SOW]![SOB]L[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [EOM]
Wire: ☻↕!→L►↕@♦U→A7K2M►↕@♦U→7M3K9►♥Locate from 7M3K9: where is session A7K2M?
Response (session found):
Pseudo: [SOM] [SOW]![SOB]L[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [EOM]
Wire: ☻↕!→L►↕@♦U→A7K2M►↕@♦U→4T9X2►↕@♦U→7M3K9►♥Located, to 7M3K9: session A7K2M is at BID 4T9X2.
11.7 Resume
Resume reconnects to an interrupted session. The remote instance responds with Status confirming the session state, its next message in the session, numbered from its own count (Section 11.1.2), or with an Exception if the session is no longer available. An instance that holds the session a Resume names, but will not resume it, responds with !X RESUME_DENIED, as does one holding that session ACTIVE. Only a session's opener sends a Resume: the opener answers a Resume of its own session with !X RESUME_DENIED, and an end that has moved waits for the opener to Locate it (Section 11.6) and resume the session.
A Resume answered with a Status moves the session to the Resume's sender, whose BID is its opener's from then on (Section 11.1.2): an instance that has reconnected holds a new BID (Section 5.4). A Resume answered with an Exception moves nothing.
Request:
Pseudo: [SOM] [SOW]![SOB]U[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→U►↕@♦U→A7K2M►↕@♦U→7M3K9►↕@♦U→4T9X2►♥Resume session A7K2M, from 7M3K9 to 4T9X2.
Response (success):
Pseudo: [SOM] [SOW]![SOB]T[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]D[SOB]47[EOW] [SOW]~[SOB]ACTIVE[EOW] [EOM]
Wire: ☻↕!→T►↕@♦U→A7K2M►↕@♦U→4T9X2►↕@♦U→7M3K9►↕@♦D→47►↕~→ACTIVE►♥Session resumed, from 4T9X2 to 7M3K9: SID=A7K2M, MID=47, state=ACTIVE. The Status is 4T9X2's next message, and each end goes on from its own count.
Response (failure):
Pseudo: [SOM] [SOW]![SOB]X[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]~[SOB]SESSION_EXPIRED[EOW] [EOM]
Wire: ☻↕!→X►↕@♦U→A7K2M►↕@♦U→4T9X2►↕@♦U→7M3K9►↕~→SESSION_EXPIRED►♥Resume refused, from 4T9X2 to 7M3K9: session A7K2M has timed out and been reclaimed.
11.8 Finish
Finish closes a session. The sender immediately transitions the session to IDLE and discards the SID. Finish is fire-and-forget — no response is expected. If the remote instance does not receive the Finish, its session timeout will eventually reclaim the session.
Pseudo: [SOM] [SOW]![SOB]F[EOW] [SOW]@[SOR]U[SOB]A7K2M[EOW] [SOW]@[SOR]U[SOB]7M3K9[EOW] [SOW]@[SOR]U[SOB]4T9X2[EOW] [EOM]
Wire: ☻↕!→F►↕@♦U→A7K2M►↕@♦U→7M3K9►↕@♦U→4T9X2►♥Finish session A7K2M, from 7M3K9 to 4T9X2. SID discarded.
12. Bus Transmission Rules
- A bus delivers each transmission to every instance connected to it, and may deliver it to its transmitter as well. It holds nothing.
- An instance keeps no record of the requests it sends, so one that reads its own broadcast request back answers it as any other instance would: a broadcast Ping with a Ping response (Section 9.5), and a broadcast Query with an Offer if it offers the capability (Section 10.1). Its answer is true, and every instance hears it.
- Buses carry CP437 byte streams transparently. A bus does not interpret, modify, or filter message content.
- Devices respect PATH words for routing. A message carrying an index and a PATH is addressed to the instance at path[index − 1], which delivers or forwards it (Section 9.3.2). Any other instance that receives it discards it.
- No session or message interpretation occurs at the bus level. The bus is a passive transport medium.
- Bytes between messages (outside SOM/EOM framing) are ignored. This allows Antheos to coexist with other traffic on shared buses.
13. Standard Exception Codes
The following table lists every exception reason this specification defines. Implementations may define additional reason strings for application-specific errors. A reason an implementation defines is of Bus scope, and is addressed as Section 9.8 sets out. An error within a session is reported with a reason this table defines. Exception reasons are carried in a TEXT word following the !X symbol and the IDs that address the Exception (Section 9.8).
| Reason | Scope | Meaning |
|---|---|---|
| BID_OVERFLOW | Bus | BID establishment exceeded maximum length |
| RELAY_FAILED | Bus | Message could not be forwarded to the next hop |
| MALFORMED_FRAME | Bus | Message is malformed (Section 7.4) |
| UNSUPPORTED_TYPE | Bus | Word type not supported by this instance |
| SERVICE_UNKNOWN | Service | This instance does not offer the requested service |
| SESSION_NOT_FOUND | Session | SID does not match any known session |
| SESSION_EXPIRED | Session | Session has timed out and been reclaimed |
| RESUME_DENIED | Session | Session cannot be resumed |
| SID_IN_USE | Session | SID already names a session the receiver holds |
| SESSION_LIMIT | Session | Receiver cannot hold another session |
14. Conformance Requirements
This section defines the requirements for a conformant Antheos implementation: the minimum every instance implements, and the features beyond it that an instance claims whole.
14.1 Minimum Conformant Implementation
An instance MUST:
- Parse SOM and EOM to identify message boundaries.
- Parse SOW and EOW to identify word boundaries.
- Recognize each of the fifteen word types (§6.2), and drop a message holding a word of a type it does not implement with its frame whole (Section 7.4).
- Implement the SYMBOL word type (!), the ID word type (@), which Establish and Conflict carry, and the TEXT word type (~), which carries an Establish's nonce.
- Implement the Establish/Conflict protocol (bus scope: E, C).
- Implement the BLOB (*) and STRING (") word types at least far enough to read the sizes they declare (Section 7.3).
- Drop messages it cannot process, as Section 7.4 describes.
14.2 Optional Features
An instance MAY implement any of the features below. A feature is the symbols it lists, as this specification describes them, and an instance that claims a feature implements all of it and every feature it requires. A claim of conformance names the features the instance implements, and an instance that implements part of a feature does not claim it.
| Feature | Symbols | Requires |
|---|---|---|
| Ping | P, p (Section 9.5) | — |
| Discover | D, d (Section 9.6) | — |
| Broadcast | B (Section 9.4) | — |
| Scaleback | S, W (Sections 9.2 and 9.7) | INTEGER |
| Exception | X (Section 9.8) | — |
| Relay | R, with path addressing (Section 9.3) | Discover, Exception, PATH, INTEGER |
| Service | Q, O, A (Section 10) | — |
| Sessions | K, T, N, L, U, F (Section 11) | Exception |
Each word type beyond SYMBOL, ID and TEXT is a feature of its own, as is BLOB or STRING read beyond the sizes it declares.
15. Security Considerations
Antheos Level 1 provides no encryption, authentication, or integrity protection. The protocol is designed as a transparent transport layer: it carries what it is given.
- No encryption: All message content is transmitted in cleartext CP437. Any entity with access to the bus can read all traffic.
- No authentication: BIDs are self-asserted through Establish/Conflict. Any instance can claim any available BID.
- No integrity: Messages can be modified in transit on shared buses. There is no checksum or signature mechanism at Level 1.
- Sender spoofing: A sender's BID is self-asserted in every message that carries one, so any instance can send any message as any BID, at any time, whether or not another instance holds it.
- Forged Conflicts: An instance that answers every Establish with a Conflict, or with an Establish of its own for the same BID and another nonce, keeps every other instance from establishing a BID: each grows its candidate until it passes its maximum length (Section 9.1).
- Forged control messages: A forged Finish closes a session at the end that receives it (Section 11.8), a forged Exception refuses or ends what it answers (Section 9.8), a forged Scaleback with a tail size of 0 tells a sender that its target takes no tail (Section 9.2), and a forged Acknowledge tells a Scaleback's sender that its limits were received, so that it does not retransmit them (Section 9.7).
- Route hijacking: A forged Discover response can name any BID as the way to a target, and a recovery origin that receives one retransmits there (Sections 9.3.3 and 9.6).
- Offer hijacking: Nothing shows that the instance an Offer names provides the capability. An instance that answers a Query first, with an Offer of its own, takes the Accept of a querier that accepts the first Offer, and any session opened after it (Sections 10.2 and 10.4).
- Session takeover: Anyone who has seen a SID can send any session message for it. A Resume of a session held SUSPENDED moves the session to its sender (Section 11.7), and any other message needs only the opener's BID, which a sender asserts for itself (Section 11.1.2).
- One SID, two sessions: A SID is unique only among the sessions of the instance that generated it (Section 11.2). Two instances can each hold a suspended session under one SID string; both answer a Locate for it, and the requester resumes the one it reaches first (Section 11.6).
- Broadcast amplification: In a mesh of buses, every instance on more than one bus forwards a broadcast that carries a PATH onto its other buses, and the rule that it forwards none whose path holds one of its BIDs is the only bound on how often a broadcast is carried (Section 9.3.1). A broadcast can arrive once by each route that passes no instance twice, a receiver may reply along each, and nothing de-duplicates it, so the copies and the replies grow with the number of routes.
- Tail exhaustion: A BLOB or STRING word may declare a block of up to 2⁶⁴−1 bytes, and a stream receiver that keeps the frame whole reads the whole declared tail before it can read another message. Declared sizes can also sum past what a receiver's count holds, and a tail declared beyond 2⁶⁴−1 bytes in all breaks the frame. A size made larger by corruption in transit does the same by accident: a receiver that keeps the frame whole reads the messages after it as tail, until the declared tail is complete. A receiver MAY break the frame on a tail longer than it will read (Section 7.4); one that does scans for SOM inside the tail, and so reads a message framed there, which a receiver keeping the frame whole skips as data.
- Capability enumeration: A passive observer on a shared bus can catalogue available services by monitoring Query/Offer traffic. Unsolicited Offer broadcasts would make this trivial without any traffic generation; the prohibition in Section 10.2 limits enumeration to active observation of demand-driven exchanges.
An application that needs encryption or authentication provides them itself, in the content of its messages, and can find a service that offers them through Query/Offer/Accept. The Antheos frame structure (typed words, delimited boundaries) provides clear attachment points for security wrappers around message content.
SIDs are generated fresh per session, so that a SID does not link one session to another. A BID does: it is carried in clear in every session message, and links everything an instance does for as long as its bus connection lasts. The SID hash, FNV-1a (Section 11.2), is not a cryptographic hash, and this specification does not claim that a SID cannot be predicted. Knowledge of a SID allows interaction with the session.
A session's opener is the instance that sent its opening Call or, after a Resume, the one that resumed it (Section 11.1.2). Its BID is as self-asserted as any sender's, so the receiving peer can attribute the session to that BID, and to nothing more certain.
16. Licensing
MIT License
Copyright (c) 2025-2026 Are Bjørby are.bjorby@langsyn.org
Permission is hereby granted, free of charge, to any person obtaining a copy of this specification and associated documentation files (the "Specification"), to deal in the Specification without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Specification, and to permit persons to whom the Specification is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Specification.
THE SPECIFICATION IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SPECIFICATION OR THE USE OR OTHER DEALINGS IN THE SPECIFICATION.
Appendix B: Grammar
This appendix gives the syntax of a message, of each word and of each body in ABNF (RFC 5234), with the %s of RFC 7405 marking a string matched with its case. It is normative: where the text of this specification and this grammar disagree on syntax, the grammar governs. ALPHA, DIGIT, OCTET and SP are the core rules of RFC 5234 (Appendix B.1), and UTF8-char is the rule of RFC 3629 (Section 4).
A message the grammar does not match is malformed, or its frame is broken, as Section 7.4 sets out. A symbol is one this specification defines. The grammar does not state what the text does: the range a unit gives a value (§6.2.5, §6.2.8, §6.2.12), a MID's greatest value (Section 5.6), that a REAL or a COMPLEX part is held exactly (§6.2.6, §6.2.10), a SCIENTIFIC value's figures and exponent (§6.2.7), the days of each month and where a leap second falls (§6.2.2), the sort of a LOGICAL operand (§6.2.1), the length of the tail (Section 7.3), and which ID and payload words each symbol takes (Sections 8 to 11).
B.1 Message
message = SOM head EOM tail
head = symbol-word *id-word *payload-word
payload-word = path-word / text-word / integer-word / real-word
/ scientific-word / logical-word / datetime-word
/ blob-word / bool-word / duration-word / complex-word
/ null-word / string-word
tail = *OCTET ; the blocks the BLOB and STRING words declare,
; in their order and of their sizes (Section 7.3)
string-block = *UTF8-char ; a STRING word's block (§6.2.12)
SOM = %x02
EOM = %x03
SOR = %x04
SOU = %x07
EOW = %x10
SOW = %x12
SOB = %x1AB.2 Words
word = symbol-word / id-word / payload-word
symbol-word = SOW %x21 SOB symbol EOW
symbol = %s"E" / %s"C" / %s"B" / %s"P" / %s"p" / %s"R" / %s"D"
/ %s"d" / %s"S" / %s"W" / %s"X"
/ %s"Q" / %s"O" / %s"A"
/ %s"K" / %s"T" / %s"N" / %s"L" / %s"U" / %s"F"
id-word = SOW %x40 ( SOB plain-id
/ SOR %s"U" SOB base32-id
/ SOR %s"D" SOB decimal-id ) EOW
path-word = SOW %x2F SOB path EOW
text-word = SOW %x7E SOB text EOW
logical-word = SOW %x3F SOB logical EOW
datetime-word = SOW %x26 SOB datetime EOW
duration-word = SOW %x3A SOB duration EOW
bool-word = SOW %x3D SOB ( "0" / "1" ) EOW
null-word = SOW %x5F SOB EOW
integer-word = SOW %x23 SOR ( %s"I" SOU count-unit SOB integer-2
/ %s"O" SOU count-unit SOB integer-8
/ %s"D" SOU count-unit SOB integer-10
/ %s"H" SOU count-unit SOB integer-16
/ %s"U" SOU count-unit SOB integer-32 ) EOW
real-word = SOW %x24 SOR ( %s"I" SOU real-unit SOB real-2
/ %s"O" SOU real-unit SOB real-8
/ %s"D" SOU real-unit SOB real-10
/ %s"H" SOU real-unit SOB real-16
/ %s"U" SOU real-unit SOB real-32 ) EOW
complex-word = SOW %x7C SOR ( %s"I" SOU real-unit SOB complex-2
/ %s"O" SOU real-unit SOB complex-8
/ %s"D" SOU real-unit SOB complex-10
/ %s"H" SOU real-unit SOB complex-16
/ %s"U" SOU real-unit SOB complex-32 ) EOW
blob-word = SOW %x2A SOR count-flags EOW
string-word = SOW %x22 SOR count-flags EOW
count-flags = %s"I" SOU count-unit SOB count-2
/ %s"O" SOU count-unit SOB count-8
/ %s"D" SOU count-unit SOB count-10
/ %s"H" SOU count-unit SOB count-16
/ %s"U" SOU count-unit SOB count-32
scientific-word = SOW %x25 SOR %s"D" SOU decimal-unit SOB scientific EOW
count-unit = %s"B" / %s"W" / %s"D" / %s"Q"
real-unit = %s"W" / %s"D" / %s"Q"
decimal-unit = %s"D" / %s"Q"B.3 Identifiers, Paths and Text
plain-id = 1*( %x21-39 / %x3B-7E ) ; printable ASCII but space and ":"
base32-id = 1*id-char
id-char = %x41-48 / %x4A-4E / %x50-5A / %x32-39
; A to Z but I and O, and 2 to 9
decimal-id = "0" / nz-10 *dig-10
path = base32-id *( "." base32-id )
text = *%x20-7EB.4 Numbers
A numeric body is written in the digits of its word's radix (§6.3), so each rule whose name ends in a base is given for each of the five.
dig-2 = %x30-31
dig-8 = %x30-37
dig-10 = %x30-39
dig-16 = %x30-39 / %x41-46
dig-32 = %x30-39 / %x41-56
nz-2 = %x31
nz-8 = %x31-37
nz-10 = %x31-39
nz-16 = %x31-39 / %x41-46
nz-32 = %x31-39 / %x41-56
count-2 = "0" / nz-2 *dig-2
count-8 = "0" / nz-8 *dig-8
count-10 = "0" / nz-10 *dig-10
count-16 = "0" / nz-16 *dig-16
count-32 = "0" / nz-32 *dig-32
integer-2 = "0" / [ "-" ] nz-2 *dig-2
integer-8 = "0" / [ "-" ] nz-8 *dig-8
integer-10 = "0" / [ "-" ] nz-10 *dig-10
integer-16 = "0" / [ "-" ] nz-16 *dig-16
integer-32 = "0" / [ "-" ] nz-32 *dig-32
magnitude-2 = count-2 [ "." *dig-2 nz-2 ]
magnitude-8 = count-8 [ "." *dig-8 nz-8 ]
magnitude-10 = count-10 [ "." *dig-10 nz-10 ]
magnitude-16 = count-16 [ "." *dig-16 nz-16 ]
magnitude-32 = count-32 [ "." *dig-32 nz-32 ]
real-2 = [ "-" ] magnitude-2
real-8 = [ "-" ] magnitude-8
real-10 = [ "-" ] magnitude-10
real-16 = [ "-" ] magnitude-16
real-32 = [ "-" ] magnitude-32
complex-2 = real-2 ( "+" / "-" ) magnitude-2 %s"j"
complex-8 = real-8 ( "+" / "-" ) magnitude-8 %s"j"
complex-10 = real-10 ( "+" / "-" ) magnitude-10 %s"j"
complex-16 = real-16 ( "+" / "-" ) magnitude-16 %s"j"
complex-32 = real-32 ( "+" / "-" ) magnitude-32 %s"j"
scientific = [ "-" ] nz-10 [ "." 1*dig-10 ] %s"E" exponent
exponent = "0" / [ "-" ] nz-10 *dig-10B.5 Logical Expressions
An expression has no precedence: an operand of two operands is in parentheses, and the whole expression never is (§6.2.1).
logical = binary / single
binary = operand SP operator SP operand
operand = single / "(" binary ")"
single = variable / constant / unary
unary = ( "~" / "-" ) ( variable / constant / "(" binary ")" )
operator = "&" / "|" / "^" / "->" / "=" / "-" / "<" / ">"
/ "+" / "*" / "/"
variable = ALPHA
constant = "0" / nz-10 *dig-10B.6 Dates and Durations
datetime = year "-" month "-" day %s"T"
hour ":" minute ":" second [ "." fraction ] %s"Z"
year = 4dig-10
month = "0" nz-10 / "1" %x30-32
day = "0" nz-10 / %x31-32 dig-10 / "3" %x30-31
; within its month (§6.2.2)
hour = %x30-31 dig-10 / "2" %x30-33
minute = %x30-35 dig-10
second = %x30-35 dig-10 / "60" ; 60 where §6.2.2 allows it
fraction = *8dig-10 nz-10 ; 1 to 9 digits, none trailing zero
duration = %s"PT0S"
/ [ "-" ] %s"P" ( days [ %s"T" clock ] / %s"T" clock )
days = nz-10 *dig-10 %s"D"
clock = hours [ minutes ] [ seconds ] / minutes [ seconds ] / seconds
hours = ( nz-10 / "1" dig-10 / "2" %x30-33 ) %s"H"
minutes = ( nz-10 / %x31-35 dig-10 ) %s"M"
seconds = ( ( nz-10 / %x31-35 dig-10 ) [ "." fraction ] / "0." fraction ) %s"S"B.7 Examples
Each word below is shown in wire form, with the rule it matches or the reason it matches none.
| Word | Reading |
|---|---|
↕!→E► |
symbol-word |
↕@♦U→4T9X2► |
id-word, a base32-id |
↕@♦D→0► |
id-word, a decimal-id |
↕/→AA.BB.CC► |
path-word |
↕#♦H•B→-7F► |
integer-word, an integer-16 |
↕$♦D•W→0.5► |
real-word, a real-10 |
↕%♦D•D→6.02E23► |
scientific-word |
↕|♦D•D→-0-0j► |
complex-word, a complex-10 |
↕?→~(A & B)► |
logical-word |
↕:→-PT2H► |
duration-word |
↕_→► |
null-word |
↕@♦D→01► |
none: a decimal-id has no leading zero |
↕#♦D•B→-0► |
none: an integer-10's zero has no sign |
↕%♦H•D→1E2► |
none: a scientific-word's radix is D |
↕?→(A & B)► |
none: a logical is never in parentheses whole |
↕:→PT90M► |
none: minutes are below 60 |
↕&→2025-04-03T12:34:56.0Z► |
none: a fraction has no trailing zero |
Appendix C: Revision History
This appendix is non-normative.
Every change to this document is a new version. A version whose row says that no requirement changed interoperates with the one before it; every other version is a wire break against the one before it: peers of the two do not interoperate. This document's date is that of its latest version.
| Version | Date | Changes |
|---|---|---|
| 1.11 | November 2026 | Section 2 says what Wrap and Tunnel means: existing hardware and software is wrapped in a shim, and Antheos is spoken through existing network protocols. No requirement changed. |
| 1.10 | November 2026 | Section 1.1 says that O³ is pronounced "object cubed". No requirement changed. |
| 1.9 | November 2026 | The rulings on an outside audit of Version 1.7: UNKNOWN_BID leaves Section 13; a holder answers a Locate that arrives once its session is suspended, and a requester may send its Locate again (Section 11.6); a session's controlling end is its opener, defined in Section 3, who alone sends requests and a Resume (Sections 11.1, 11.4, 11.7 and 15); and an end accepts every MID it has not received that is not stale, one below the next it expects included (Section 11.1.2). |
| 1.8 | November 2026 | The Establish procedure grows its candidate after a collision and gives up once the length passes the maximum, as Sections 5.4 and 9.1.3 state, and an instance keeps its nonce while it holds its BID, as Section 9.1.2 needs (Section 9.1.1). The security considerations name a forged Acknowledge, an Offer from an instance that does not provide the capability, and a tail size made larger by corruption (Section 15). Section 1.1 no longer says that instances are authenticated, and Appendix B no longer cites Appendix A. No requirement changed. |
| 1.7 | November 2026 | The title is Level 1 Specification - Transport, and the footer names its version. No requirement changed. |
| 1.6 | November 2026 | Verify leaves this document, with its symbols, and Sections 9.8 and 9.9 become 9.7 and 9.8: Level 1 does not expose an instance's OID:DID:IID, which is left to the device's designer (Section 5.3). An instance needs none of them to conform, an OID need not be registered (Section 5.1), and a SID is a hash of its counter and device entropy alone (Section 11.2). A session's other end refuses another instance's Call at any MID and drops its other messages (Section 11.1.2), a Resume moves a session to its sender (Section 11.7), and SESSION_LIMIT refuses a session an instance cannot hold (Sections 11.1.1 and 13). |
| 1.5 | November 2026 | Section 14 leaves this document, and Sections 15 to 17 become 14 to 16. The Introduction holds the one thing this specification says of what is built on it, that it is a foundation; the others leave, from the security considerations, Appendix B and this history. No requirement changed. |
| 1.4 | October 2026 | Appendix A leaves this document, with its symbol, its kind of plain ID, its exception reason and its row of features. The appendices that remain keep their letters. |
| 1.3 | October 2026 | The rulings of the publish-readiness review, among them: an Establish carries a nonce (Section 9.1.1), an Exception names its sender and the instance it answers (Section 9.8), an Accept names its sender first and a Query may name its target (Section 10), RELAY_FAILED carries the BID it could not reach and returns along the Relay's path (Section 9.3.3), a Call carries its trace identifier as an ID word (Section 11.3), five exception reasons are removed (Section 13), and Appendix B gives the grammar, which governs syntax. |
| 1.2 | October 2026 | The rulings of the Version 1.1 review, among them: every session message names its sender (Section 11), each end numbers its own messages from 1 (Section 11.1.2), responses have symbols of their own (Section 8.1), a Scaleback and its Acknowledge carry two BIDs (Sections 9.2 and 9.7), an ID word has one of three forms (Section 5.7), and a malformed message is read to its end (Section 7.4). |
| 1.1 | October 2026 | Fifteen word types, and one spelling for each value (Section 6). TEXT moves to ~ and holds printable ASCII, STRING takes " and carries UTF-8 in the tail, DURATION moves to :, DATETIME is UTC only, BOOL, COMPLEX and NULL are new, and LOGICAL has no precedence. |
| 1.0 | February 2026 | The first version. |
— End of the Antheos Protocol Specification, Version 1.11 —