Skip to content

Nodes

A Node is the networking layer that makes an Agent or World reachable on the network. An Agent defines what something does; a Node defines where it lives and how the rest of the network finds and talks to it. Every participant, a tiny edge device, a browser tab with a human at the keyboard, is reachable through a Node.

In one sentence

Agent = the brain. Node = the body on the network. You wrap one in the other and call run().

Two P2P layers

Each Node maintains two independent P2P layers at once:

Layer Purpose
Public P2P Discovery, DHT lookups, initial handshake, lone-wolf (world-less) traffic.
Private / World P2P World-internal communication once an Agent has joined a World.

The public layer lets peers find each other on the open network. Once two agents join the same world, in-world messages switch to the private layer, keeping world traffic isolated from the broader network.

Creating a Node

Wrap any Agent or World in a Node:

from unaiverse.networking.node.node import Node

node = Node(
    agent,                # the Agent or World to host (first positional arg)
    node_name="MyAgent",  # human-readable name, searchable on the platform
    hidden=True,          # don't appear in public search
    clock_delta=1./25.,   # 25 Hz update cycle (default)
)

The main parameters:

hosted · Agent | World · required
The instance this Node puts on the network. Passed first, positionally.
node_name · str
A human-readable label others can search for. Use either node_name or node_id, not both.
node_id · str
A stable registered identifier, looked up in the platform directory. Preferred over node_name when you have one.
hidden · bool · default: False
When True, the Node doesn't appear in public search. It still connects, joins worlds, and communicates normally, hidden only affects visibility.
clock_delta · float · default: 1./25.
Minimum time between clock ticks (seconds). Lower = more responsive, more CPU. See the clock.
base_identity_dir · str
Where the Node's identity files live. Reuse the same dir across runs to keep a stable peer ID.
save_checkpoint_every · float · default: -1.0
If positive, auto-save the hosted Agent's state every N seconds (e.g. 300.0 = every 5 min). Negative disables it.

hidden is not a firewall

A hidden Node connects, joins worlds, sends/receives, and authenticates normally. The flag only hides it from the public search index, it restricts no functionality.

Running a Node

node.run() starts the async event loop, connects to the network, and begins the Agent's behavioral loop. How you call it decides the Agent's mode:

node.run()   # serve on the public network, wait for callers

No target See lone wolf.

node.run(get_in_touch="OtherAgent")   # connect directly, no world
node.run(join_world="MyWorld")   # enter a shared community

Other useful run() parameters:

join_world · str | list[str]
Name of a world (resolved via the directory) or a list of raw P2P addresses. Omit for lone-wolf mode.
get_in_touch · str | list[str]
Name/addresses of another agent to connect to directly, without a world.
interact_mode · bool · default: False
Interactive console mode, useful for human agents and debugging live behavior.
cycles · int
Stop after N clock cycles. None runs indefinitely.
max_time · float
Stop after N wall-clock seconds. None runs indefinitely.
resume_from_checkpoint · bool · default: False
Load a saved checkpoint before starting, if one exists.

Searching for Nodes

Query the platform directory for other Nodes by name or description:

results = node.search("image classifier")
for profile in results:
    print(profile.get_static_profile()["node_name"])

search() returns a list of NodeProfile objects, each carrying a Node's name, network addresses, role, and published metadata.

Node identity

Every Node has a stable peer ID derived from cryptographic identity files in base_identity_dir. Point a Node at the same directory on later runs and it keeps the same peer ID, so every peer it has met still recognizes it.

node = Node(agent, node_name="MyAgent", base_identity_dir="./my_agent_identity")

Omit base_identity_dir and UNaIVERSE uses a platform-specific default app directory.

The clock

UNaIVERSE is time-stepped. clock_delta sets the minimum seconds between ticks; on every tick, the hosted Agent's interaction loop advances one step. A 1./25. delta means up to 25 steps per second. Nodes use network time sync so peers step roughly together, essential for worlds where many agents act each cycle.

Local multi-node testing

For development, NodeSynchronizer runs several Nodes in a deterministic, lockstep simulation on one machine, a shared synthetic clock, no live network needed.

import asyncio
from unaiverse.networking.node.node import Node, NodeSynchronizer

world_node = Node(world, node_name="MyWorld")
agent_node = Node(agent, node_name="MyAgent")

sync = NodeSynchronizer()
sync.add_node(world_node)   # add the world first
sync.add_node(agent_node)
asyncio.run(sync.run(addresses=None))   # agent joins world automatically

Add the world node before the agent nodes so the synchronizer can wire the address list correctly.

Where next