March Docs

NodeSend

NodeSend: an actor message to a process on another node (distributed actors 1/4).

Everything cross-node used to be a CALL (NodeCall, synchronous, typed by a RemoteRef) or a MONITOR frame. This is the missing primitive: a one-way message to a GlobalPid, the way send(pid, msg) is one-way locally.

Wire (msgpack payload behind the NetFrame length prefix; tag byte first, the P5 convention in NetKernel): 0x09 ACTOR_MSG [tag=9, seq:Int, to_local_pid:Int, to_creation:Int, type_tag:Str, payload:Bin] 0x0A DELIVERY_FAILED [tag=10, seq:Int, reason:Str]

seq is the sender's correlation number, echoed by DELIVERY_FAILED so the sender can tell which message a failure is about. to_creation is the receiving node's creation counter as the sender last knew it: a node that restarted reuses local pids, and a message carrying the old creation must be refused rather than delivered to whatever now owns that pid. type_tag names the message type (fully qualified), so a receiver can refuse a type it does not host without decoding the payload.

The receiving side is INJECTED, like NodeRpc's Targets: serve_one decodes the frame, checks the creation, and hands a Delivery to the caller's dispatch function, which knows which local actor hosts which type and which constructor to wrap the payload in (a constructor is minted by the actor that declares it; a library cannot name it). Any failure -- stale creation, a rejected type, a dead pid, an undecodable payload -- goes back to the sender as DELIVERY_FAILED. A remote send is not fire-and-forget.

Payload codec: the sender encodes its message with the type's derived codec (derive Json today, as @[endpoints] does) into bytes; this layer carries bytes. Design record: specs/progress/2026-09-14-remote-send-to-a-global-pid.md.

Type annotations use bare names where a qualified path would not unify (the NodeCall convention).

Types

typeSendErrorSendError = NoConnection | Refused(Int, String)#
typeDeliveryDelivery = {#

Functions

fncastcast(fd : Int, seq : Int, to : Pid, type_tag : String, payload : List(Int)) : Result((), SendError)#
fncast_fromcast_from(fd : Int, seq : Int, from, to : Pid, type_tag : String, payload : List(Int)) : Result((), SendError)#
fndecode_faileddecode_failed(bytes : List(Int)) : Result((Int, String), String)#
fndecode_msgdecode_msg(bytes : List(Int)) : Result(Delivery, String)#
fnencode_failedencode_failed(seq : Int, reason : String) : List(Int)#
fnencode_msgencode_msg(seq : Int, to : Pid, type_tag : String, payload : List(Int)) : List(Int)#
fnhandle_framehandle_frame(frame : List(Int), reply_fd : Int, my_creation : Int, dispatch : Delivery -> Result((), String)) : Result((), String)#
fnon_failureon_failure(frame : List(Int), deliver) : Result((), String)#
fnrecv_failurerecv_failure(fd : Int, buf : List(Int)) : Result((Int, String), String)#
fnserve_oneserve_one(fd : Int, my_creation : Int, dispatch : Delivery -> Result((), String)) : Result((), String)#
fnserve_one_replyserve_one_reply(fd : Int, reply_fd : Int, my_creation : Int, dispatch : Delivery -> Result((), String)) : Result((), String)#
fntag_actor_msgtag_actor_msg() : Int do 9 end#
fntag_delivery_failedtag_delivery_failed() : Int do 10 end#