Skip to content

Built-in actions

Every UNaIVERSE agent comes with a set of built-in actions for free. You reference them by name from a behavior transition (or call them in your own custom actions). This page is the canonical reference: every built-in, with an example, and how it makes agents communicate. Whenever the docs mention a built-in action, they link here.

The action contract (applies to all of them)

An action is an async method that returns True (it succeeded, the state machine advances) or False (retry on the next tick). Many actions take an interaction argument the framework injects; you usually leave it at its default. To write your own, see Creating actions.

How a request actually works

This is the most important idea on the page, and the one that makes UNaIVERSE different from a remote procedure call. When agent A sends an action to agent B, it does not "call a method on B". It makes a request that B's behavior may choose to honor.

Concretely, three things have to line up:

  1. A sends. A.send(action_name="X", target=B, streams=[...]) creates an interaction and ships it to B.
  2. B must be willing. B runs X only if its behavior, in the state it is currently in, has a transition that accepts X. That transition is usually a request-driven self-loop, written with ready=False (it does not fire on its own; an incoming request fires it). If B is in the wrong state, or has no such transition, the request simply waits until B is ready, or times out.
  3. The result flows back. When B runs X, it reads the streams A bound (into its stdin), does the work, and writes the result to its stdout, which is wired back to A. A then learns the request completed (and can run a callback).
sequenceDiagram
    participant A as Agent A (requester)
    participant B as Agent B (target)
    A->>B: send(action_name="X", streams=[...])
    Note over B: in a state whose behavior<br/>accepts "X" (a ready=False transition)?
    B->>B: run X (read A's data, act, write result)
    B-->>A: completion (+ result on the bound stream)
    Note over A: wait_completion / a guard / a callback

The thing to internalize: the effect of a request depends on the target's state and behavior, not only on the request. Sending an agent a learn request does nothing unless that agent is in a state ready to learn. An agent only ever does what its own behavior allows, even when others ask.

Two kinds of send, two kinds of effect

  • With an action_name: the target runs that action (if its behavior accepts it).
  • Without an action_name (a pure data push via data_samples): the target just stores the data; no transition fires on its side.

Communication scenarios

The four shapes nearly every world is made of. Switch the tabs to see each, with both sides of the exchange.

A client asks a worker to process some data, and waits for the answer.

The worker sits ready and accepts process requests (the ready=False self-loop makes it respond rather than act on its own):

# worker behavior
behav.add_state("ready", blocking=True)
behav.add_transit("ready", "ready", action="process", args={}, ready=False)

The client sends one request and blocks until it completes:

# client behavior
behav.add_transit("ask", "got_answer", action="send",
                  args={"action_name": "process", "target": "<worker>",
                        "streams": {"stdin": ["<client>:question"]},
                        "wait_completion": True})

The worker reads the question from stdin, runs its model, writes the answer to stdout; the answer travels back and the client moves on. Had the worker been in another state with no process transition, the request would have waited until it returned to ready.

Before a back-and-forth, agents form a pair, so neither hard-codes the other.

# A: find a partner by role, then offer to engage
behav.add_transit("searching", "engaged", action="connect_by_role", args={"role": "student"})
behav.add_transit("engaged", "ready", action="send_engage", args={})

# B: accept an engagement from an agent playing the "teacher" role
behav.add_transit("waiting", "teacher_engaged", action="engage", args={"acceptable_role": "teacher"})

Now A and B are a stable pair: A can send learn / process and B's teacher_engaged state has the transitions to honor them. disengage ends it.

A single send addresses a whole group; each follower reacts on its own, and the leader waits for all.

followers = list(self.world_agents)          # the peers you lead
await self.send(action_name="learn", target=followers,
                streams={"stdin": ["<lesson>"], "stdtar": ["<lesson>"]}, num_steps=50)
# the leader then waits on a guard: all_sent_completed(action_name="learn")

The effect on each follower is independent. The leader does not loop over followers; it sends once and waits with a completion guard, with a timeout so one slow follower cannot stall the rest.

One agent reaches many at once without direct links (a price feed, a sensor reading, a best student's labels). That is pub/sub.

await self.subscribe(stream_owners=["<publisher>"], stream_props=["readings"])
# or a coordinator subscribes others on their behalf:
await self.send_subscribe(agent="<follower>", stream_hashes=["<publisher>:readings"])
graph LR
    PUB[Publisher] -->|writes a stream| T[(stream)]
    T --> S1[Subscriber 1]
    T --> S2[Subscriber 2]
    T --> S3[Subscriber N]

Whatever the publisher writes, every subscriber receives, with no per-recipient send. The publisher need not know who is listening.

The action catalogue

Each tab is a family of actions. Every action has an example, shown either as a behavior transition (add_transit(... action="x" ...)) or a direct call (await self.x(...)).

Find and link to peers by role, not by hard-coded names.

graph LR
    A[Agent] -->|connect_by_role| F[matching peers]
    A -->|find_agents| K[known peers, no connect]
    P[peer leaves] -.disconnected.-> A

connect_by_role(role, filter_fcn=None) finds peers whose role matches and connects to them. Example: add_transit("searching", "connected", action="connect_by_role", args={"role": "broadcaster"}). filter_fcn names a method that skips unwanted candidates.

find_agents(role, engage=False, handshake_completed=False) searches the already-known peers, without connecting. Example: await self.find_agents("student") to gather a cohort. engage=True also offers engagement.

connect_to(peer_id) opens a connection to one peer by id. Example: await self.connect_to(self._broadcaster_peer_id).

connected(agent=None, handshake_completed=False) is a guard that succeeds once the peer is connected. Example: add_transit("waiting_handshake", "ready", action="connected", args={"handshake_completed": True}).

disconnected(agent=None, handshake_completed=False) succeeds once a peer is gone. Example (teleport home): add_transit("ready", "init", action="disconnected", args={"delay": 5.0}).

disconnect(agent) drops one peer. Example: await self.disconnect(peer_id).

disconnect_by_role(role, disengage_too=False) drops every peer with a role. Example: await self.disconnect_by_role("student", disengage_too=True).

Form a working pair before exchanging tasks.

sequenceDiagram
    participant A as Agent A
    participant B as Agent B
    A->>B: send_engage()
    B->>B: engage(acceptable_role) then paired
    Note over A,B: exchange learn / process / data
    A->>B: send_disengage()
    B->>B: disengage() then free

send_engage() offers engagement to found agents. Example: add_transit("connected", "offered", action="send_engage").

engage(acceptable_role=None, sender_role=None, interaction=None) accepts an incoming engagement. Example: add_transit("waiting", "teacher_engaged", action="engage", args={"acceptable_role": "teacher"}).

set_engaged_partner(agent, clear_found=True) declares who you are working with. Example: await self.set_engaged_partner(best_student).

send_disengage(send_disconnection_too=False) tells your partner it is over. Example: await self.send_disengage().

disengage(disconnect_too=False, interaction=None) handles an incoming disengage. Example: add_transit("teacher_engaged", "init", action="disengage").

disengage_all() clears all engagements locally. Example: add_state("ready", action="disengage_all").

Ask others to act (send), or run your own processor (process / learn).

graph LR
    IN[stdin] --> P["forward()"] --> OUT[stdout]
    TAR[stdtar] -.learn: backward pass.-> P

send(action_name=None, target=None, streams=None, data_samples=None, num_steps=-1, timeout=-1., callback=None, copy_sys=False, wait_completion=False, volatile=False, ...) dispatches an interaction to one or more targets. Named-action example: await self.send(action_name="broadcast_message", target=[bc], streams=[my_hash], copy_sys=True). Data-push example: await self.send(target=others, data_samples={"proc_output_0": text}, num_steps=1). Full walkthrough: How interactions travel.

process(interaction=None) runs one inference step: read stdin, call forward(), write stdout. Example (respond to requests): add_transit("ready", "ready", action="process", args={}, ready=False), this is what runs on you when another agent sends a process request.

learn(interaction=None) runs one learning step: process, then a backward pass against stdtar. Example: add_transit("teacher_engaged", "finished_learning", action="learn"), train on what a teacher bound.

nop(message=None) does nothing (a placeholder to move states). Example: add_transit("message_sent", "ready", action="nop").

show(interaction=None) logs a completed interaction. Example: await self.show(interaction) while debugging.

Pub/sub streaming, and snapshotting data into a replayable stream.

subscribe(stream_owners=None, stream_props=None, unsubscribe=False, interaction=None) receives whatever is published to matching streams. Example: await self.subscribe(stream_owners=["<best>"], stream_props=["labels"]).

send_subscribe(agent=None, stream_hashes=None, unsubscribe=False) asks a remote agent to subscribe. Example: await self.send_subscribe(agent="<follower>", stream_hashes=["<best>:labels"]).

record(interaction=None, record_uuid=...) snapshots incoming samples into a new owned stream. Example: add_transit("init", "recorded", action="record", args={"streams": ["<world>:cats"], "num_steps": 998}).

A "playlist" of streams an agent steps through, used by teaching worlds.

graph LR
    SP[set_pref_streams] --> L1[lesson 1] --> L2[lesson 2] --> L3[lesson 3]
    L3 -.check_pref_stream: last? .-> EXAM[exam]

set_pref_streams(net_hashes, repeat=1) sets the playlist. Example: action="set_pref_streams", args={"net_hashes": ["<agent>:l1", "<agent>:l2"], "repeat": 4}.

first_pref_stream() · next_pref_stream() move to the first / next stream. Example: the loop advances with action="next_pref_stream" after each lesson.

check_pref_stream(what="last") branches on playlist position. Example: add_transit("change_lecture", "exam_time", action="check_pref_stream", args={"what": "last"}).

Score an agent's output, used by a master to grade.

graph LR
    EV[evaluate gives an error metric] --> CMP{compare_eval}
    CMP -->|metric within threshold| GOOD[pass]
    CMP -->|otherwise| BAD[fail]

evaluate(stream_hash, how, steps=100, re_offset=False, agents_to_evaluate=None) computes an error metric over a stream. Example: action="evaluate", args={"stream_hash": "<agent>:exam", "how": "mse", "steps": 1000}. re_offset=True re-aligns when sample tags are unreliable.

compare_eval(cmp, thres, good_if_true=True) turns the last evaluate into a pass/fail branch. Example: add_transit("eval_time", "good", action="compare_eval", args={"cmp": "<=", "thres": 0.2}).

Guards that report whether a phase is done, so a requester can wait for one or many targets.

graph LR
    SEND[send to N targets] --> W{guard}
    W -->|not yet| W
    W -->|all done| NEXT[advance]

all_sent_completed(action_name=None) is true once every interaction you sent has completed. Example: add_transit("lesson_in_progress", "exam", action="all_sent_completed", args={"action_name": "learn"}).

all_asked_finished() is true once every agent you asked has reported done. Example: action="all_asked_finished" after a mixed fan-out.

all_engagements_completed() is true once every found agent has been engaged. Example: action="all_engagements_completed" before teaching the cohort.

agents_are_waiting() is true while found agents are still registering. Example: add_transit("connected", "connected", action="agents_are_waiting").

received_some_asked_data(processing_fcn=None, data_type=None) is true when asked-for data arrives, and runs processing_fcn on each item. Example: action="received_some_asked_data", args={"processing_fcn": "collect_predictions"}.

How an agent reports up to the world; the world decides what to record.

graph LR
    AG[Agent / evaluator] -->|suggest_badges_to_world| W[World]
    AG -->|suggest_role_to_world| W

suggest_badges_to_world(agent=None, score=-1.0, badge_type="completed", badge_description=None) suggests a badge. Example: action="suggest_badges_to_world", args={"badge_description": "Completed the Animal School"}. badge_type is completed / attended / intermediate / pro; score is in [0, 1].

suggest_role_to_world(agent, role) suggests a role change. Example: action="suggest_role_to_world", args={"agent": "<valid_cmp>", "role": "teacher"}, promote a student who passed.

Want to add your own?

These built-ins cover discovery, communication, learning, and scoring. When you need something specific, a domain rule, a device command, a custom aggregation, you write a custom action. The world-builder chapter Creating actions shows how, with examples well beyond AI (a robot arm, a sensor, a rule).