Documentation

Cross-orbital events

Connect two orbitals with an external event and a qualified listen, without either reaching into the other.

Orbitals never call each other or share state machines. One emits an event; another listens for it. This tutorial connects an order to a notification board.

1. Emit an external event

An event that leaves its orbital is external, and must declare its payload:

lolo
1emits { 2 ORDER_SHIPPED -> external { 3 orderId : string! 4 } 5}
lolo
1 2 3 SHIP -> shipped (set @entity.status shipped) (emit ORDER_SHIPPED { orderId: "A-1042" })

2. Listen across orbitals

Inside its own orbital a trait writes Trait.EVENT. Across orbitals, the source is qualified with the orbital's name:

lolo
1listens { 2 OrderOrbital.OrderLifecycle.ORDER_SHIPPED -> SHOW 3 SHOW { 4 orderId : string! 5 } 6}

A two-part source naming a trait that only exists in another orbital is an error, and the message names the qualified form to use.

3. Both orbitals

The program above as state machines, one per trait: each box is a state, each arrow an event that moves it, and the icons on an arrow are the effects that event runs. A ◇ on an arrow is a guard; hover it to read the check. How to read AVL

OrderLifecycle

on Order · states: 2 · transitions: 2
emitsORDER_SHIPPED
SHIP
RESET
pending
shipped

NoticeBoard

on Notice · states: 1 · transitions: 1
listensORDER_SHIPPED
SHOW
idle

How the traits talk to each other, in order:

NotificationOrbital.NoticeBoardOrderOrbital.OrderLifecycleNotificationOrbital.NoticeBoardOrderOrbital.OrderLifecycleUserSHIPset @entity.status shippedORDER_SHIPPED → SHOWset @entity.messagerender-ui mainUser
std-orb-docex-relay.lolololo
1app std-orb-docex-relay "1.0.0" 2"std-orb-docex-relay — two orbitals: one emits an external event, the other listens with the qualified three-part source." 3orbital OrderOrbital { 4 entity Order [runtime] { 5 id : string! 6 status : "pending" | "shipped" = pending 7 } 8 9 trait OrderLifecycle -> Order [interaction, instance] { 10 initial: pending 11 12 state pending { 13 INIT -> pending 14 (render-ui main { type: stack, direction: horizontal, gap: md, align: center, children: [ 15 { type: badge, label: "Pending", variant: warning }, 16 { type: button, label: "Ship order", action: SHIP, variant: primary } 17 ] }) 18 SHIP -> shipped 19 (set @entity.status shipped) 20 (emit ORDER_SHIPPED { orderId: "A-1042" }) 21 (render-ui main { type: stack, direction: horizontal, gap: md, align: center, children: [ 22 { type: badge, label: "Shipped", variant: success }, 23 { type: button, label: "New order", action: RESET, variant: ghost }, 24 { type: button, label: "See notifications", href: "/notices", variant: link } 25 ] }) 26 } 27 28 state shipped { 29 RESET -> pending 30 (set @entity.status pending) 31 (render-ui main { type: stack, direction: horizontal, gap: md, align: center, children: [ 32 { type: badge, label: "Pending", variant: warning }, 33 { type: button, label: "Ship order", action: SHIP, variant: primary } 34 ] }) 35 } 36 37 emits { 38 ORDER_SHIPPED -> external { 39 orderId : string! 40 } 41 @description "An order left the warehouse." 42 } 43 } 44 45 page "/orders" -> OrderLifecycle 46} 47 48orbital NotificationOrbital { 49 entity Notice [runtime] { 50 id : string! 51 message : string = "" 52 } 53 54 trait NoticeBoard -> Notice [interaction, instance] { 55 initial: idle 56 57 state idle { 58 INIT -> idle 59 (render-ui main { type: stack, direction: vertical, gap: sm, children: [ 60 { type: typography, content: "No notifications yet.", variant: body, color: muted }, 61 { type: button, label: "Back to orders", href: "/orders", variant: link } 62 ] }) 63 SHOW -> idle 64 (set @entity.message (str/concat "Order " ?orderId " shipped")) 65 (render-ui main { type: alert, variant: info, message: @entity.message }) 66 } 67 68 listens { 69 OrderOrbital.OrderLifecycle.ORDER_SHIPPED -> SHOW 70 SHOW { 71 orderId : string! 72 } 73 } 74 } 75 76 page "/notices" -> NoticeBoard 77}

The validator checks the wiring end to end: the event is declared, its payload matches what the listener reads, and an external event with no listener anywhere is reported.

Wiring through config

A behavior that needs to be told which event to raise takes an event-typed knob, addressed with the same forms as listens: EVENT, Trait.EVENT or Orbital.Trait.EVENT. The address must name a trait in the program and one of its declared inputs.

Orb

The language where the rule is the program.

© 2026 Almadar. Ljubljana, Slovenia.
Not reported here