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:
- Perceive, read data arriving on the input DataStream.
- Think / process, run
forward()on that data. - 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.Moduleimplementingforward(). PassNoneto 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
optimizerandlosseslist 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
Worldsubclass, regular agents leave itNone. 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, callforward(), write tostdout. -
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¶
- Build your first agent, hands-on, step by step.
- Lone wolves, standalone agents on the public network.
- Hybrid state machines, how an agent decides what to do.
-
AgentAPI reference, every parameter and method.