Skip to content

Hybrid state machines

The HybridStateMachine (HSM) defines agent behavior in UNaIVERSE, controlling state transitions, multi-step actions, and policy-based selection.

The HybridStateMachine (HSM) is the behavior brain of every UNaIVERSE Agent. Instead of writing a monolithic event loop, you describe your Agent's behavior as a graph of named states connected by transitions. Each transition fires an action, a method on the Agent, and the Agent moves to a new state when that action succeeds. The HSM handles multi-step actions, queued requests from other agents, timing controls, and policy-based selection automatically, leaving you free to focus on what each state should do rather than how the loop is managed.

Key Concepts

  • States


    Named conditions the Agent can be in, such as "idle", "teaching", or "waiting_for_partner". One state is always active at a time.

  • Actions


    Async methods on the Agent that are called during transitions or as in-state behaviors. Every action must return True (success) or False (retry next tick).

  • Transitions


    Rules that define which action to call and which state to move to if that action succeeds. A state can have multiple outgoing transitions.

  • Policy


    A function that selects which available transition to attempt next. The default policy tries queued requests first, then the first ready transition.

Constructing an HSM

HybridStateMachine takes three constructor arguments:

actionable object · required
The Agent (or any object) whose methods serve as the actions the HSM will call. Pass None only when constructing a temporary HSM for introspection or serialization purposes.
wildcards dict[str, str | float | int] | None
A dictionary of named template variables and their initial values. Wildcards embedded in action argument strings (as {name}) are substituted at runtime. See the Wildcards section below.
policy callable | None
A function that selects which transition to attempt from a list of candidate Action objects. If None, the built-in policy is used: prioritize pending requests, then choose the first ready transition.

Defining an HSM in Code

Build an HSM programmatically by adding states and transitions:

from unaiverse.hsm.hsm import HybridStateMachine

hsm = HybridStateMachine(actionable=my_agent)

# Add states
hsm.add_state("idle")
hsm.add_state("working")

# Add transitions: from_state -> action -> to_state
hsm.add_transit("idle",    "working", action="process", args={"num_steps": 5})
hsm.add_transit("working", "idle",    action="show",    args={})

When the Agent is in "idle", the HSM will call my_agent.process(num_steps=5). If that returns True, the Agent moves to "working". On the next successful tick, my_agent.show() fires and the Agent returns to "idle".

Timing Controls

Transitions accept optional timing parameters to control pacing:

total_time float
Maximum total wall-clock seconds the transition (a multi-step action) is allowed to run before it is considered complete regardless of the action's return value.
timeout float
If the action keeps returning False for longer than this many seconds, the transition is declared complete and the Agent moves to the next state.
delay float
Minimum number of seconds to wait before attempting this transition for the first time.
hsm.add_transit(
    "waiting", "processing",
    action="receive_data",
    args={},
    timeout=30.0,    # Give up waiting after 30 seconds
    delay=1.0        # Wait at least 1 second before trying
)

Loading from JSON

HSMs can be defined entirely in JSON files and loaded at runtime. This is exactly how Worlds configure their Agents: each role maps to a .json behavior file in the world_folder, and the World loads and delivers the right file to each connecting Agent.

hsm = HybridStateMachine(actionable=my_agent)
hsm.load("behaviors/service_provider.json")

You can also pass a file object directly, which is useful when loading behavior files packaged inside a Python library:

with open("behaviors/listening_to_teacher.json") as f:
    hsm.load(f)

Saving

Serialize an HSM back to a JSON file at any time:

hsm.save("my_behavior.json")

The saved file captures all states, transitions, timing parameters, wildcards, and the welcome message, everything needed to reconstruct the exact same behavior later.

Wildcards

Wildcards are named template variables embedded in action arguments that get filled in at runtime. This lets you write a single generic behavior file and customize it for each Agent session without editing the JSON.

# Set multiple wildcards at once
hsm.set_wildcards({"target": "AgentBob", "num_steps": 10})

# Update a single wildcard
hsm.update_wildcard("target", "AgentAlice")

In the JSON file, wildcards appear as {target} or {num_steps} inside action argument strings. When the HSM applies wildcards, every occurrence is replaced with the current value.

json { "transitions": { "idle": [ { "on": { "action": "send", "args": { "target": "{partner}", "action_name": "process" } }, "goto": "waiting" } ] } }

python hsm.load("behavior_template.json") hsm.update_wildcard("partner", "AgentBob")

Visualizing

The HSM can render itself as a Graphviz directed graph, useful for documenting World designs or debugging complex behavior flows:

# Get a graphviz.Digraph object for custom rendering
graph = hsm.to_graphviz()

# Save directly to a PDF file
hsm.save_pdf("my_agent_behavior.pdf")

States are shown as nodes and transitions as labeled edges. Multi-step and teleport transitions are visually distinguished from single-step ones.

Tip

The UNaIVERSE examples repository includes several ready-made behavior JSON files you can use as starting points or drop straight into your own World:

  • service_provider.json, waits for requests, processes them, and sends results back
  • service_requester.json, finds a provider, sends a request, waits for results, then evaluates
  • listening_to_teacher.json, connects to a teacher role, receives streams, and learns from them