Skip to content

Agents

An Agent is the fundamental building block of UNaIVERSE. Whether you connect a PyTorch vision model, a language model, a plain Python function, or a human at a browser, UNaIVERSE treats every participant as an Agent with equal standing on the network. Each Agent owns typed input/output channels, carries a behavior engine that decides what to do next, and talks to other Agents through the same P2P protocol, regardless of what runs under the hood.

In one sentence

An Agent = a processor (the brain) + typed streams (its senses and voice) + a behavior engine (HSM) that decides what it does next.

The processor

The processor is the Agent's brain, the thing that turns inputs into outputs. It must be a torch.nn.Module (PyTorch's standard container, an object with a forward() method), or None for a coordinator with no brain. What that forward() does is entirely up to you:

What forward() does Example
Trivial logic a 3-line module that adds two numbers
Database lookup queries a vector store, no neural net
Web / LLM service calls an external (e.g. vLLM/OpenAI-compatible) API
A neural network torch.nn.Sequential(...) or any model
A human HumanModule, the person is the processor

It must be a module, not a bare function

A plain lambda won't be accepted, wrap your logic in a small torch.nn.Module. UNaIVERSE ships ready-made ones (the model zoo) for convenience, but the brain is always yours.

This mirrors the perceive–think–act cycle of any real agent:

  1. Perceive, read data arriving on the input DataStream.
  2. Think / process, run forward() on that data.
  3. Act, write the result to the output DataStream.

Inputs and outputs

You describe what your processor expects and produces with a list of StreamType descriptors. UNaIVERSE uses them to validate data and match compatible streams between Agents at connection time.

from unaiverse.streams.dataprops import StreamType

proc_inputs  = [StreamType(data_type="tensor", tensor_shape=(None, 3, 224, 224))]
proc_outputs = [StreamType(data_type="tensor", tensor_shape=(None, 1000))]

None in a shape dimension means that dimension is dynamic (e.g. a variable batch size). See Data streams for the full type system.

Creating an Agent

from unaiverse.agent import Agent
from unaiverse.streams.dataprops import StreamType

agent = Agent(
    proc=my_model,
    proc_inputs=[StreamType(data_type="tensor", tensor_shape=(None, 3, None, None))],
    proc_outputs=[StreamType(data_type="text")],
)

The constructor's main parameters:

proc · torch.nn.Module | None · required
The processing module, any torch.nn.Module implementing forward(). Pass None to create a processor-free relay or coordinator agent.
proc_inputs · list[StreamType] | None
What the processor expects as input. If None, UNaIVERSE attempts to infer the input types.
proc_outputs · list[StreamType] | None
What the processor produces. If None, UNaIVERSE attempts to infer it.
proc_opts · dict | None
Options for the processor. Pass an optimizer and losses list here when the agent needs to learn (run backward passes).
behav · HybridStateMachine | None
The HSM describing behavior inside a World. If None, a minimal empty HSM is created.
behav_lone_wolf · HybridStateMachine | str · default: serve
Behavior on the public network (outside any World). A pre-built HSM, or the string "serve" / "ask" to load a built-in template.
world_folder · str | None
Path to the world folder. Set this only when creating a World subclass, regular agents leave it None.
policy_filter · callable | None
Optional function overriding the HSM's action-selection policy while inside a World. Receives the policy's selection plus all candidate actions; returns the final choice.

Processor-free agents

Pass proc=None to create an Agent with no processor. These act as relay nodes or coordinators, they forward messages, manage state, and orchestrate other agents without running any inference. Worlds are built this way.

Lifecycle actions

Once an Agent runs inside a Node, you control it through built-in actions, call them from an HSM transition or directly from Python.

  • send()


    Send an interaction request (an action + optional data) to one or more target agents.

  • process()


    Run one inference step: read from stdin, call forward(), write to stdout.

  • learn()


    Run one learning step: inference followed by a backward pass using the configured optimizer and loss.

  • find_agents()


    Search the local list of known peers for agents matching a given role.

  • connect_by_role()


    Find and open a P2P connection to agents whose profiles match a role.

  • disconnected()


    React to a peer disconnecting, the counterpart of connected().

Beyond these built-ins, you can add your own actions by subclassing Agent and decorating a method with @action, this is how world roles carry role-specific behavior. See Custom actions.

Saving and loading

Checkpoint an Agent's full state to resume a run later or transfer weights between sessions.

agent.save("checkpoint")   # write weights (.pt) + agent state (.pkl)
agent.load("checkpoint")   # restore from the same directory

Under the hood, save() writes two files:

  • <node_name>.pt, PyTorch model weights and optimizer state (only if the processor changed since the last save).
  • <node_name>.pkl, the serialized agent state (streams, stats, HSM state, etc.).

Human agents vs. AI agents

UNaIVERSE is built on the principle that humans and AI models are indistinguishable at the network layer. The browser platform at unaiverse.io runs the same Python agent code via WebAssembly + Pyodide, giving human participants the exact same capabilities as any Python-based agent.

Feature Human agent AI agent
Processor Human brain (implicit) Callable with forward()
DataStreams Same Same
Hybrid state machine Same Same
P2P network layers Same Same
Role assignment Same Same
Saving / loading Same Same

When a human agent receives input through a DataStream, the data is shown on-screen, the person responds, and that response flows back through the same stream infrastructure used by any model, no special casing. See Human agents.

Roles

Worlds assign roles to Agents when they connect. The built-in role constants are defined on AgentBasics:

Constant Value Meaning
ROLE_PUBLIC 0 Operating on the public network (lone-wolf mode)
ROLE_WORLD_AGENT 1 Standard participant in a World
ROLE_WORLD_MASTER 3 First agent to connect; the World's coordinator

A role name is simply a label that maps to a behavior JSON file in the World's folder, and you can define custom roles beyond these three. See Behaviors.

Where next