unaiverse.hsm.hsm
What this module does 🔴
Implements the HybridStateMachine, the core behaviour engine that manages states, transitions, teleports, policies, wildcards, and serialization for agent behaviour.
hsm
¶
█████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
A Collectionless AI Project (https://collectionless.ai)
Registration/Login: https://unaiverse.io
Code Repositories: https://github.com/collectionlessai/
Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
HybridStateMachine
¶
HybridStateMachine(actionable: object, wildcards: dict[str, str | float | int] | None = None, policy: Callable[[list[Action]], tuple[int, Interaction | None]] | None = None)
A hybrid state machine that orchestrates agent behaviour through states and transitions.
HybridStateMachine (HSM) combines classical finite-state-machine semantics with
support for multi-step, async actions and external interaction requests. Each state
can carry an inner action (a method on the actionable object that runs while the
machine is in that state) and is connected to other states via transitions, each
guarded by an Action. At every execution tick, the machine selects a feasible
transition action according to a configurable policy, executes it, and advances to
the next state when the action succeeds.
State machines can be defined programmatically (using add_state and add_transit)
or loaded from a JSON file (using load). They can also be saved (save),
composed (include), and visualised (to_graphviz, save_pdf).
Key concepts:
- Wildcards: placeholder strings (e.g.
"<role>") embedded in action arguments and state messages that are replaced at runtime with concrete values (seeset_wildcards,update_wildcard,apply_wildcards). - Policy: a callable that, given the list of currently feasible actions, returns
the index of the action to execute and an optional
Interaction(seeset_policy). The default policy is first-requested or first-ready. - Policy filter: an optional second callable that can veto or override the policy's
choice (see
set_policy_filter). - Teleports: transitions that are hidden in the visual graph but still run normally
(added with
add_teleportoradd_global_teleport).
Attributes:
| Name | Type | Description |
|---|---|---|
initial_state |
str | None
|
Name of the first state the machine enters. |
prev_state |
str | None
|
Name of the state the machine was in before the most recent transition. |
limbo_state |
str | None
|
Name of the state the machine was in when a multi-step action started.
While a multi-step action is running, |
state |
str | None
|
Name of the current state, or |
role |
str | None
|
Role string assigned to the agent running this machine (e.g. |
enabled |
bool
|
Whether the machine will execute actions when |
states |
dict[str, State]
|
Mapping from state name to |
transitions |
dict[str, dict[str, list[Action]]]
|
Nested mapping |
actionable |
object
|
The object whose methods serve as the actions of this machine. |
wildcards |
dict[str, str | float | int] | None
|
Mapping from wildcard placeholder to its current replacement value. |
policy |
Callable that selects which action to execute. |
|
policy_filter |
Optional callable that overrides the policy's decision. |
|
welcome_msg |
Message printed once when the machine reaches its initial state,
with wildcards already substituted. |
|
show_blocking_states |
Whether colored markers are appended to blocking-state log
messages (see |
|
show_action_completion |
Whether tick symbols are appended to action log messages
(see |
|
show_action_request_info |
Whether requester/UUID info is appended to action log
messages (see |
Examples:
Build a minimal two-state machine and wire it to an actionable object:
>>> class MyAgent:
... async def do_work(self): ...
... async def finish(self): ...
>>>
>>> agent = MyAgent()
>>> hsm = HybridStateMachine(actionable=agent)
>>> hsm.add_transit("idle", "working", action="do_work")
>>> hsm.add_transit("working", "done", action="finish")
>>> print(hsm.get_state_name())
idle
Load a machine from a JSON file and attach it to an actionable:
Initialize a HybridStateMachine with an actionable object, optional wildcards, and an optional policy.
All internal state is reset: no states, no transitions, no current action. The
Custom.DEFAULT_WILDCARDS are merged in via add_wildcards immediately after
construction so that built-in placeholders (such as the role wildcard) are always
available. Wildcards are then applied to any existing messages and the welcome
message. set_actionable is called last, propagating actionable to all
action and state objects (currently none, since the machine is empty at this point).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
actionable
|
object
|
The object whose methods serve as actions. Every |
required |
wildcards
|
dict[str, str | float | int] | None
|
Initial wildcard dictionary. Merged with |
None
|
policy
|
Callable[[list[Action]], tuple[int, Interaction | None]] | None
|
Callable with signature
|
None
|
Examples:
Provide a custom policy that always picks the last action in the list:
>>> def last_action_policy(actions):
... return len(actions) - 1, None
>>> hsm = HybridStateMachine(actionable=my_agent, policy=last_action_policy)
Source code in unaiverse/hsm/hsm.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |
wildcards
instance-attribute
¶
policy
instance-attribute
¶
show_ticks_in_action_messages
¶
Enable or disable tick symbols appended to action log messages upon completion.
When enabled, a status character from Custom.ACTION_TICKS_PER_STATUS is
printed to the user-facing log after each action completes, giving a quick
visual indicator of success or failure. This setting corresponds to the
show_action_ticks_after_messages option stored in the serialised JSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
do_it
|
bool
|
|
True
|
Source code in unaiverse/hsm/hsm.py
show_marks_in_blocking_state_messages
¶
Enable or disable colored markers appended to blocking-state log messages.
When enabled, a colored marker is added to the log message of every blocking
state to make it visually distinct from non-blocking state messages. This
corresponds to the highlight_blocking_states_in_messages option in the
serialised JSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
do_it
|
bool
|
|
True
|
Source code in unaiverse/hsm/hsm.py
show_request_info_in_action_messages
¶
Enable or disable requester and UUID info appended to action log messages.
When enabled, the identity of the requester and the UUID of the interaction
are included in action-related log output, which is useful for tracing which
external entity triggered a given action. This corresponds to the
show_action_request_after_messages option in the serialised JSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
do_it
|
bool
|
|
True
|
Source code in unaiverse/hsm/hsm.py
set_welcome_message
¶
Set a message that is printed exactly once when the machine first reaches its initial state.
The raw message is stored in welcome_msg after HTML-unescaping (via
html.unescape), and a copy is kept in welcome_msg_with_wildcards as the
source for future wildcard substitution. When act executes and detects that the
current state equals initial_state, it logs welcome_msg and then sets it to
None so it is never shown again. Calling this method with None clears any
previously set welcome message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
str | None
|
The welcome message string, which may contain HTML entities and wildcard
placeholders. Pass |
required |
Source code in unaiverse/hsm/hsm.py
to_dict
¶
Serialize the state machine's current configuration into a nested dictionary.
The resulting dictionary mirrors the JSON schema used by save and load.
It contains four top-level keys:
"machine": role, initial state, welcome message, and display option flags."states": one entry per state, produced byState.to_dict()."transitions": ordinary (non-teleport) transitions, grouped by source state."teleports": teleport transitions, grouped by source state. If a teleport action originates from every state in the machine (except the destination), it is collapsed under the special"all"key so that the JSON stays compact and round-trips cleanly throughload.
The welcome message, if present, is ASCII-safe-encoded with XML character references so that non-ASCII characters survive the JSON round-trip.
Returns:
| Type | Description |
|---|---|
dict
|
A dictionary representation of the machine, suitable for serialisation with |
dict
|
|
Examples:
Source code in unaiverse/hsm/hsm.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | |
set_actionable
¶
Set the object on which the state machine's actions are performed.
Updates self.actionable and propagates the new reference to every Action
already registered in the machine, including inner state actions. This allows the
same machine topology to be reattached to a different object without rebuilding
the whole machine from scratch. If obj is None the call is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
object
|
The object instance to set as the new |
required |
Source code in unaiverse/hsm/hsm.py
set_policy
¶
set_policy(policy_fcn: Callable[[int, Interaction | None, list[Action], dict], tuple[int, Interaction | None]] | None) -> None
Set the policy callable used to select which action to execute in the current state.
The policy is invoked by act_transitions with the list of feasible Action
objects available from the current state. It must return the index of the chosen
action (into that list) and an Interaction that drives the execution, or
-1 and None to indicate that no action should be executed this tick. If
policy_fcn is None, the machine falls back to the built-in
first-requested-or-first-ready policy on the next call to act_transitions.
The policy can be overridden at runtime by a policy filter; see
set_policy_filter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
policy_fcn
|
Callable[[int, Interaction | None, list[Action], dict], tuple[int, Interaction | None]] | None
|
Callable with signature
|
required |
Source code in unaiverse/hsm/hsm.py
set_policy_filter
¶
set_policy_filter(filter_fcn: Callable[[int, Interaction | None, list[Action], dict], tuple[int, Interaction | None]] | None, filter_fcn_opts: dict) -> None
Set a filter callable that can veto or redirect the primary policy's decision.
After the main policy selects an action index and an Interaction, the filter
(if set) is called with those results, the full list of feasible actions, and a
mutable options dict. It may return a different index and interaction, or
(-1, None) to suppress execution for this tick. The filter is called inside a
try/except in act_transitions; any exception is logged and the original policy
decision is kept. Passing None for filter_fcn disables filtering.
The provided filter_fcn_opts dict is cleared immediately on assignment, then
stored as self.policy_filter_opts so that the caller shares the same object
and can update options in place between ticks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filter_fcn
|
Callable[[int, Interaction | None, list[Action], dict], tuple[int, Interaction | None]] | None
|
Callable with signature
|
required |
filter_fcn_opts
|
dict
|
A mutable dictionary of caller-defined options forwarded to the filter on every invocation. It is cleared upon registration. |
required |
Source code in unaiverse/hsm/hsm.py
set_wildcards
¶
Replace the entire wildcard dictionary and propagate it to all actions and states.
The new dictionary completely replaces self.wildcards; existing entries not
present in wildcards are lost. The wildcard reference is then propagated to
every Action and State object already registered in the machine via their
respective set_wildcards methods. If apply is True, apply_wildcards
is called immediately afterwards to substitute the new values into all messages
and action arguments.
Use add_wildcards to merge new entries without discarding existing ones, and
update_wildcard to change a single entry by key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wildcards
|
dict[str, str | float | int] | None
|
The new wildcard mapping. |
required |
apply
|
bool
|
If |
False
|
Source code in unaiverse/hsm/hsm.py
apply_wildcards
¶
Apply the current wildcard substitutions to all actions, states, and the welcome message.
Iterates over every registered Action and State and calls their
apply_wildcards methods so that any placeholder found in their messages or
arguments is replaced with the corresponding value from self.wildcards. If a
welcome message is set, welcome_msg_with_wildcards (the original template) is
first restored, then each wildcard replacement is applied in turn, and the result
is stored in welcome_msg.
This method does not alter self.wildcards itself. It is called automatically
by set_wildcards and add_wildcards when their apply parameter is
True, and by set_role after updating the role wildcard.
Source code in unaiverse/hsm/hsm.py
set_role
¶
Set the role string for this machine and propagate it through all wildcards and messages.
Stores role in self.role, then updates the Custom.ROLE_WILDCARD entry
in the wildcard dictionary with the new value, and finally calls apply_wildcards
so that every action argument and state message that references the role placeholder
is refreshed. This is the canonical way to assign or change the agent role at
runtime; direct assignment to self.role bypasses wildcard propagation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
role
|
str
|
The role string to assign (e.g. |
required |
Examples:
Source code in unaiverse/hsm/hsm.py
get_wildcards
¶
Return the wildcard dictionary currently used by the state machine.
The returned object is the live internal dictionary shared with all registered
actions and states. Callers should not mutate it directly; use update_wildcard,
add_wildcards, or set_wildcards instead.
Returns:
| Type | Description |
|---|---|
dict
|
The |
dict
|
values (strings, floats, or ints). |
Source code in unaiverse/hsm/hsm.py
add_wildcards
¶
Merge new wildcard entries into the existing wildcard dictionary.
Uses dict.update on self.wildcards, so keys present in both are
overwritten by the new values. Existing keys not present in wildcards are
preserved. If apply is True, apply_wildcards is called after the
merge, substituting updated values into all action arguments, state messages, and
the welcome message. Unlike set_wildcards, this method never discards existing
entries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wildcards
|
dict[str, str | float | int | list[str]]
|
Dictionary of wildcard entries to add or update. Values may be strings, floats, ints, or lists of strings. |
required |
apply
|
bool
|
If |
False
|
Source code in unaiverse/hsm/hsm.py
update_wildcard
¶
Update the value of a single existing wildcard entry.
If wildcard_key is not already present in self.wildcards, the error is
logged but no exception is raised (the call is silently ignored). When the key
exists, its value is updated in place; because all actions and states share the
same dictionary reference, they see the change immediately even without calling
apply_wildcards. If apply is True, apply_wildcards is called
afterwards to refresh all messages and action arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wildcard_key
|
str
|
The placeholder string that must already exist in
|
required |
wildcard_value
|
str | float | int
|
The new replacement value for the wildcard. |
required |
apply
|
bool
|
If |
False
|
Note
No exception is raised for an unknown wildcard_key; the condition is
logged as an error and the method returns without modifying state.
Source code in unaiverse/hsm/hsm.py
get_action_step_idx
¶
Return the current step index of the action being executed.
Reads the step index from the Interaction object stored in the internal
feasible-actions-status dict. For single-step actions the index is 0 while
the action is running and -1 when idle. For multi-step actions it advances
with each call to act_transitions.
Returns:
| Type | Description |
|---|---|
int
|
An integer representing the current step index (>= 0), or |
int
|
action is currently being executed. |
Source code in unaiverse/hsm/hsm.py
is_busy_acting
¶
Return whether the state machine is currently mid-action.
Delegates to get_action_step_idx and returns True when the step index is
= 0, meaning an action has been started but has not yet completed. During this time
self.stateisNoneandself.limbo_stateholds the originating state.
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in unaiverse/hsm/hsm.py
add_state
¶
add_state(state: str, action: str = None, args: dict | None = None, state_id: int | None = None, waiting_time: float | None = None, blocking: bool | None = None, msg: str | None = None, msg_action: str | None = None) -> None
Add a new state to the machine, or update an existing state's configuration.
If state is not yet registered, a new State object is created and appended
to the internal ordered list, and a new Action is created for the inner state
action (if action is given). If state already exists, the existing entry is
updated in place: parameters passed as None fall back to the current values
stored on the existing State object.
When the first state is added and no current state is set yet, set_state is
called automatically to make this the initial and current state.
The wildcard dictionary shared by the machine is propagated to both the new
State and its inner Action.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
str
|
Name of the state to add or update. |
required |
action
|
str
|
Name of the method on |
None
|
args
|
dict | None
|
Keyword arguments forwarded to |
None
|
state_id
|
int | None
|
Explicit integer ID for the state. If |
None
|
waiting_time
|
float | None
|
Minimum time (in seconds) the machine must remain in this state
before transitions are considered. |
None
|
blocking
|
bool | None
|
If |
None
|
msg
|
str | None
|
Human-readable message associated with the state, shown in log output.
|
None
|
msg_action
|
str | None
|
Human-readable message for the inner state action. Only used when
a new |
None
|
Examples:
>>> hsm.add_state("processing", action="run_model",
... args={"timeout": 30}, blocking=False,
... msg="Processing user input...")
Source code in unaiverse/hsm/hsm.py
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 | |
get_state_name
¶
Return the name of the current state of the machine.
During a multi-step action, self.state is None and the originating state
is stored in self.limbo_state. When consider_limbo is True and
self.state is None, this method returns limbo_state instead, giving
callers a non-None state name even while an action is in progress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
consider_limbo
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str | None
|
The name of the current (or limbo) state as a string, or |
str | None
|
state has been set and |
Source code in unaiverse/hsm/hsm.py
get_state
¶
get_state() -> State | None
Return the State object corresponding to the current state.
When the machine is mid-transition (a multi-step action is running), self.state
is None and this method therefore returns None as well. Use
get_state_name(consider_limbo=True) to obtain the originating state name in that
situation.
Returns:
| Type | Description |
|---|---|
State | None
|
The |
State | None
|
(e.g., while a multi-step action is in progress or before any state has been set). |
Source code in unaiverse/hsm/hsm.py
get_all_states
¶
get_all_states() -> list[State]
Return the ordered list of all State objects registered in the machine.
The list is ordered by the integer ID assigned to each state at creation time
(either explicitly via the state_id parameter of add_state, or
sequentially). This is the same order in which states appear in the serialised
JSON. See also get_states for a plain list of state name strings.
Returns:
| Type | Description |
|---|---|
list[State]
|
A list of |
list[State]
|
machine's internal list; do not modify it directly. |
Source code in unaiverse/hsm/hsm.py
get_all_actions
¶
get_all_actions() -> list[Action]
Return the ordered list of every Action object registered in the machine.
This list includes both transition actions (those that guard edges between states)
and inner state actions (those attached to a state via the action parameter of
add_state). Actions are ordered by their integer ID, which is assigned at
creation time. See get_action to retrieve the action currently being executed.
Returns:
| Type | Description |
|---|---|
list[Action]
|
A list of |
list[Action]
|
machine's internal list; do not modify it directly. |
Source code in unaiverse/hsm/hsm.py
get_action
¶
get_action() -> Action | None
Return the Action object that is currently being executed.
An action is considered "current" from the moment it is selected by the policy
until it completes (status 0). For single-step actions this window is a single
call to act_transitions; for multi-step actions it spans multiple calls. When
no action is running, this method returns None. See is_busy_acting for a
boolean convenience check.
Returns:
| Type | Description |
|---|---|
Action | None
|
The active |
Action | None
|
progress. |
Source code in unaiverse/hsm/hsm.py
get_action_name
¶
Return the name of the action currently being executed.
This is a convenience wrapper around get_action that returns only the name
string instead of the full Action object. The name corresponds to the method
name on actionable that is being called.
Returns:
| Type | Description |
|---|---|
str | None
|
The name string of the active action, or |
str | None
|
in progress. |
Source code in unaiverse/hsm/hsm.py
get_last_completed_action_name
¶
Return the name of the most recently completed transition action.
The internal reference is updated every time act_transitions receives status
0 (action fully done) from the action callable. It persists across state
changes and is not reset by reset_state. It is None only before any
action has ever completed.
Returns:
| Type | Description |
|---|---|
str | None
|
The name string of the last successfully completed action, or |
str | None
|
action has completed yet in the lifetime of this machine. |
Source code in unaiverse/hsm/hsm.py
reset_state
¶
Reset the state machine to its initial state, clearing all transient execution state.
The following fields are cleared or reset:
stateis set back toinitial_state.limbo_stateandprev_stateare set toNone.- The currently executing action (if any) is abandoned without completing it.
- All step counters and interaction queues on every registered transition action
and inner state action are cleared via
clear_interactionsandsystem_interaction.reset_state().
Note
__last_completed_action is not cleared, so get_last_completed_action_name
still returns the name of the last action that completed before the reset.
Source code in unaiverse/hsm/hsm.py
get_states
¶
Return a list of all state names defined in the machine, in creation order.
The returned list contains plain strings (state names), not State objects.
Use get_all_states to obtain the full State objects, or states to
access the underlying name-to-State mapping directly.
Returns:
| Type | Description |
|---|---|
list
|
A list of state name strings ordered by state creation sequence. |
Source code in unaiverse/hsm/hsm.py
set_state
¶
Forcibly set the current state of the machine, bypassing normal transition logic.
limbo_state is cleared, prev_state is updated to the outgoing state, and
state is assigned the new value. If an action was in progress, its associated
Interaction is reset and the action reference is discarded. If no initial state
has been set yet, initial_state is also assigned to state.
This method is intended for programmatic overrides (e.g., during machine setup or
after a reset_state call). Normal runtime transitions are handled by
act_transitions, which calls this method internally after a successful action.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
str
|
The name of the state to activate. Must already be registered in the
machine (either in |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
Source code in unaiverse/hsm/hsm.py
are_debug_messages_active
¶
Return whether debug messages are currently active for this machine.
When debug mode is active, the machine appends tick symbols to action messages,
colored markers to blocking-state messages, requester info to action log lines, and
auto-generated labels to states and actions that have none. See
set_debug_messages_active for details.
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in unaiverse/hsm/hsm.py
set_debug_messages_active
¶
Enable or disable debug mode for this machine.
When yes is True, the following are enabled in one call:
- Tick symbols appended to action log messages (
show_ticks_in_action_messages). - Colored markers appended to blocking-state log messages
(
show_marks_in_blocking_state_messages). - Requester and UUID info appended to action log messages
(
show_request_info_in_action_messages). - Auto-generated human-readable labels for states and actions that have none
(
generate_auto_messages), withforce=Trueso that existing messages are prefixed rather than discarded.
When yes is False, all four features are disabled and the original messages
stored at enable-time are restored. Wildcards are then re-applied to the restored
messages. This method is idempotent: calling it twice in the same direction is safe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
yes
|
bool
|
|
required |
Source code in unaiverse/hsm/hsm.py
generate_auto_messages
¶
Auto-generate human-readable messages for states and actions that currently have none.
For each state (and its inner action) or transition action that has no message set,
a label is derived from the state/action name by capitalising it and replacing
underscores with spaces, prefixed with a pin or rocket emoji. When force is
True, existing messages are not skipped but are instead prepended with the
auto-generated label and kept in square brackets (e.g. "-> my_state [Original text]").
The original messages are saved internally before any modification so that
set_debug_messages_active(False) can restore them exactly. Calling this method
twice for states (or for actions) has no effect on the second call because the
saved originals list is populated only once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
states
|
bool
|
If |
True
|
actions
|
bool
|
If |
True
|
force
|
bool
|
If |
False
|
Note
This method is called automatically by set_debug_messages_active(True)
with force=True. Calling it manually before enabling debug mode is
supported but may interact with the saved-original-message restoration
performed when debug mode is later disabled.
Source code in unaiverse/hsm/hsm.py
add_global_teleport
¶
add_global_teleport(to_state: str, action: str, args: dict | None = None, ready: bool = True, msg: str | None = None, high_priority: bool = False, total_time: float | str = 0.0, timeout: float | str = 0.0, delay: float | str = 0.0) -> None
Add a teleport transition from every state in the machine to a single destination state.
This is a shorthand for add_teleport with from_state set to the special
Custom.ALL_STATES_NAME sentinel (typically "all"). The underlying
add_transit call expands the wildcard source by iterating over every state
currently registered and adding an individual teleport transition for each one
(except the destination itself, which is skipped automatically).
Teleports are visually hidden in to_graphviz output but execute normally
during act_transitions. This makes them suitable for global "escape" or
"interrupt" transitions (for example, moving every state back to an error handler
on a particular action).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_state
|
str
|
The name of the destination state. If this state is not yet
registered, |
required |
action
|
str
|
The name of the method on |
required |
args
|
dict | None
|
Keyword arguments forwarded to |
None
|
ready
|
bool
|
Whether the action is immediately selectable by the policy. Defaults to
|
True
|
msg
|
str | None
|
Human-readable label for the action shown in log output. Defaults to
|
None
|
high_priority
|
bool
|
If |
False
|
total_time
|
float | str
|
Maximum wall-clock duration (seconds) for this transition from the
moment it starts. |
0.0
|
timeout
|
float | str
|
Maximum cumulative wait time (seconds) before the action is abandoned.
|
0.0
|
delay
|
float | str
|
Minimum time (seconds) that must have elapsed in the source state before
this transition is considered. |
0.0
|
Examples:
>>> # Jump to "error" from any state when "handle_error" fires.
>>> hsm.add_global_teleport(to_state="error", action="handle_error")
Source code in unaiverse/hsm/hsm.py
add_teleport
¶
add_teleport(from_state: str, to_state: str, action: str, args: dict | None = None, ready: bool = True, act_id: int | None = None, msg: str | None = None, avoid_changing_ready: bool = False, high_priority: bool = False, total_time: float | str = 0.0, timeout: float | str = 0.0, delay: float | str = 0.0) -> None
Add a hidden (teleport) transition between two states.
A teleport is functionally identical to a normal transition but is marked with the
teleport flag so that it is rendered with a transparent edge (or omitted from
the visual graph in to_graphviz). It runs normally during act_transitions
and is picked up by the policy like any other feasible action. This is useful for
background "escape" edges that would clutter the visual layout.
This is a thin wrapper around add_transit that forces teleport=True. For a
shorthand that adds the teleport from every state, use add_global_teleport.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_state
|
str
|
The name of the source state. Pass |
required |
to_state
|
str
|
The name of the destination state. If it does not exist yet, it is
created automatically by |
required |
action
|
str
|
The name of the method on |
required |
args
|
dict | None
|
Keyword arguments forwarded to |
None
|
ready
|
bool
|
Whether the action is immediately selectable. Defaults to |
True
|
act_id
|
int | None
|
Explicit integer ID for the action. |
None
|
msg
|
str | None
|
Human-readable label for the action. Defaults to |
None
|
avoid_changing_ready
|
bool
|
If |
False
|
high_priority
|
bool
|
If |
False
|
total_time
|
float | str
|
Maximum wall-clock duration (seconds) for this transition.
|
0.0
|
timeout
|
float | str
|
Maximum cumulative wait time (seconds) before giving up. |
0.0
|
delay
|
float | str
|
Minimum time (seconds) that must elapse in |
0.0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If an identical action (same name and args) already exists on the
|
FileNotFoundError
|
If |
Source code in unaiverse/hsm/hsm.py
add_transit
¶
add_transit(from_state: str, to_state: str, action: str, args: dict | None = None, ready: bool = True, act_id: int | None = None, msg: str | None = None, avoid_changing_ready: bool = False, teleport: bool = False, high_priority: bool = False, total_time: float | str = 0.0, timeout: float | str = 0.0, delay: float | str = 0.0) -> None
Add a transition between two states with an associated action.
This is the primary method for defining the machine's topology. It handles three special cases transparently:
- Wildcard source (
from_state == Custom.ALL_STATES_NAME): the transition is expanded and added individually from every state currently registered in the machine (except the destination). - Sub-machine file (
to_stateends with".json"): the referenced JSON file is loaded as a separateHybridStateMachine, state-name clashes are resolved by appending".1",".2", etc., and the sub-machine is then merged into the current one viainclude. The edge fromfrom_stateis wired to the sub-machine's initial state. - Ordinary transition: the source and destination states are registered (if
not already present) and a new
Actionis appended to the edge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_state
|
str
|
Name of the source state. Use |
required |
to_state
|
str
|
Name of the destination state. If it ends with |
required |
action
|
str
|
Name of the method on |
required |
args
|
dict | None
|
Keyword arguments forwarded to |
None
|
ready
|
bool
|
Whether the action is immediately selectable by the policy. Defaults to
|
True
|
act_id
|
int | None
|
Explicit integer ID for the new action. |
None
|
msg
|
str | None
|
Human-readable label for the action shown in log output. Defaults to
|
None
|
avoid_changing_ready
|
bool
|
If |
False
|
teleport
|
bool
|
If |
False
|
high_priority
|
bool
|
If |
False
|
total_time
|
float | str
|
Maximum wall-clock duration in seconds for this transition from the
moment it starts. |
0.0
|
timeout
|
float | str
|
Maximum cumulative time in seconds the policy will keep retrying this
transition before giving up. |
0.0
|
delay
|
float | str
|
Minimum time in seconds that must elapse in |
0.0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If an identical action (same name and args) already exists on the
|
FileNotFoundError
|
If |
Examples:
>>> hsm.add_transit("idle", "working", action="start_task",
... args={"retries": 3}, msg="Starting task...")
>>> # Embed a sub-machine from a JSON file:
>>> hsm.add_transit("working", "sub_behaviours.json", action="enter_sub")
Source code in unaiverse/hsm/hsm.py
1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 | |
include
¶
include(hsm: HybridStateMachine, make_a_copy: bool = False) -> None
Merge all states and transitions from another machine into this one.
This is the primary composition primitive for building complex machines from smaller, reusable components. The merge proceeds in three steps:
- Wildcards from
hsmare merged into this machine viaadd_wildcards. - Every state in
hsm(including its inner state action and configuration) is added to this machine viaadd_state, using a fresh sequential ID. - Every transition in
hsmis added viaadd_transit, withavoid_changing_ready=Trueso that the existingreadyflags are preserved verbatim.
When make_a_copy is True, the runtime execution state of hsm
(state, prev_state, initial_state, limbo_state, the welcome
message, and the display flags) is also copied onto this machine, effectively
turning this machine into a live duplicate of hsm.
This method is called internally by add_transit when to_state ends with
".json" to embed a sub-machine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hsm
|
HybridStateMachine
|
The |
required |
make_a_copy
|
bool
|
If |
False
|
Examples:
>>> base = HybridStateMachine(actionable=agent)
>>> base.add_transit("a", "b", action="do_ab")
>>> ext = HybridStateMachine(actionable=agent)
>>> ext.add_transit("b", "c", action="do_bc")
>>> base.include(ext)
>>> print(base.get_states())
['a', 'b', 'c']
Source code in unaiverse/hsm/hsm.py
1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 | |
must_wait
¶
Checks if the current state is in a waiting period before any transitions can occur.
Returns:
| Type | Description |
|---|---|
bool
|
A boolean indicating if the state machine must wait. |
Source code in unaiverse/hsm/hsm.py
is_enabled
¶
A simple getter to check if the state machine is currently enabled to run.
Returns:
| Type | Description |
|---|---|
bool
|
True if the state machine is enabled, False otherwise. |
enable
¶
Enables or disables the state machine. When disabled, the act_states and act_transitions methods will
not perform any actions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
yes_or_not
|
bool
|
A boolean to enable ( |
required |
Source code in unaiverse/hsm/hsm.py
act_states
async
¶
Executes the inner action of the current state, if one exists. This method is for actions that occur upon entering a state but do not cause an immediate transition. It only runs if the state machine is enabled (async).
Source code in unaiverse/hsm/hsm.py
act_ghost_transition
async
¶
Source code in unaiverse/hsm/hsm.py
get_time_spent_in_current_state
¶
act_transitions
async
¶
This is the core execution loop for transitions. It finds all feasible actions from the current state and, using a policy, selects and executes one. It handles single-step and multistep actions, managing state changes, timeouts, and failed executions. It returns an integer status code indicating the outcome (e.g., transition done, try again, move to next action) (async).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
only_the_ones_with_interactions
|
bool
|
A boolean to consider only actions that have pending interactions. |
False
|
Returns:
| Type | Description |
|---|---|
int
|
An integer status code: |
int
|
next action, or |
Source code in unaiverse/hsm/hsm.py
1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 | |
act
async
¶
A high-level method that combines act_states and act_transitions to run the state machine. It repeatedly
processes states and transitions until a blocking state is reached or all feasible actions have been tried,
thus ensuring a complete processing cycle in one call (async).
Returns:
| Type | Description |
|---|---|
bool
|
True if, during this whole 'act', the state changed at least once. |
Source code in unaiverse/hsm/hsm.py
get_state_changed
¶
Returns an internal flag that indicates if a state transition has occurred in the last execution cycle. This can be used by an external loop to know when to re-evaluate the state machine's context.
Returns:
| Type | Description |
|---|---|
bool
|
True if the state has changed, False otherwise. |
Source code in unaiverse/hsm/hsm.py
transition_exists
¶
Source code in unaiverse/hsm/hsm.py
request_action
¶
request_action(interaction: Interaction | None = None, **kwargs) -> bool
Allows an external entity to request a specific action. The request is validated by a signature checker (if one exists) and then queued on the corresponding action. This method enables dynamic, external triggers for state machine transitions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interaction
|
Interaction | None
|
The interaction object. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the request was accepted and queued, False otherwise. |
Source code in unaiverse/hsm/hsm.py
wait_for_all_actions_that_start_with
¶
Sets the ready flag to False for all actions whose name begins with a given prefix. This method is used
to programmatically disable a group of actions, effectively pausing them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
The string prefix to match against action names. |
required |
Source code in unaiverse/hsm/hsm.py
wait_for_all_actions_that_include_an_arg
¶
Sets the ready flag to False for all actions that include a specific argument name in their signature.
This provides another way to programmatically disable actions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arg_name
|
str
|
The name of the argument to look for. |
required |
Source code in unaiverse/hsm/hsm.py
save
¶
Saves the state machine's current configuration to a JSON file. It can optionally check if the configuration has changed before saving to avoid redundant file writes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
The path to the file to save to. |
required |
only_if_changed
|
object | None
|
An optional object to compare against for changes. If a change is not detected, the file is not written. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the file was written, False otherwise. |
Source code in unaiverse/hsm/hsm.py
load
¶
load(filename_or_hsm_as_string: str | TextIOWrapper | IO[str]) -> HybridStateMachine
Loads a state machine's configuration from a JSON file or a JSON string. It reconstructs the states, actions, and transitions from the serialized data. This method is critical for persistence and for loading pre-defined state machine models.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename_or_hsm_as_string
|
str | TextIOWrapper | IO[str]
|
The path to the JSON file or a JSON string representation of the state machine. |
required |
Returns:
| Type | Description |
|---|---|
HybridStateMachine
|
The loaded |
Source code in unaiverse/hsm/hsm.py
1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 | |
load_backward_compat
¶
load_backward_compat(hsm_data: dict) -> HybridStateMachine
Loads a state machine's configuration from a JSON file or a JSON string. It reconstructs the states, actions, and transitions from the serialized data. This method is critical for persistence and for loading pre-defined state machine models.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hsm_data
|
dict
|
The dict with an HSM loaded from a JSON file. |
required |
Returns:
| Type | Description |
|---|---|
HybridStateMachine
|
The loaded |
Source code in unaiverse/hsm/hsm.py
2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 | |
to_graphviz
¶
Generates a Graphviz Digraph object representing the state machine's structure. This method visualizes
states as nodes and transitions as edges. It includes details such as node shapes (diamond for initial state,
oval for others), styles (filled for blocking states), and labels for both states and transitions. The labels
for actions include their names and arguments, formatted to wrap lines for readability.
Returns:
| Type | Description |
|---|---|
Digraph
|
A |
Source code in unaiverse/hsm/hsm.py
2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 | |
save_pdf
¶
Saves the state machine's Graphviz representation as a PDF file. It calls to_graphviz() to create the
graph and then uses the Graphviz library's render method to generate the PDF.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
The path and name of the PDF file to save. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the file was successfully saved, False otherwise. |
Source code in unaiverse/hsm/hsm.py
print_actions
¶
Prints a list of all transitions and their associated actions from a given state. If no state is provided, it defaults to the current state. This method is useful for quickly inspecting the available transitions from a specific point in the state machine's flow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
str | None
|
The name of the state from which to print actions. Defaults to the current state. |
None
|