Guest Agent¶
The guest agent (adarevm) is a Python process that runs inside the
virtual machine. It receives commands from the host over a WebSocket
connection, executes them, and streams results and events back.
Architecture¶
The core of the agent is AdareVMServer
(adarevm.core.server.AdareVMServer), a WebSocket server built on the
websockets library. The server class is assembled from four tool mixins
via multiple inheritance:
class AdareVMServer(
GUIToolsMixin, # screenshot, click, keyboard, scroll, drag, goto, idle
TestToolsMixin, # upload testfunctions, set variables, run test
SystemToolsMixin, # shell execution, system info, dependency install
FileToolsMixin, # chunked file pull, filesystem snapshot, timestamp
): ...
Each mixin provides a group of related tool methods that are registered in a
tool dictionary at init time. When a TOOL_CALL message arrives, the server
looks up the tool name in this dictionary and dispatches to the corresponding
method.
Tool methods run as background asyncio tasks so that long-running operations (such as dependency installation or test execution) do not block the WebSocket message loop. The server tracks running tasks per client and cancels them automatically when a client disconnects.
Communication¶
The agent communicates with the host using a custom WebSocket protocol
defined in adarelib.websocket.protocol. The host runs an
AdareVMClient (adare.backend.experiment.websocket_client) that
connects to the agent’s WebSocket server (default port 18765).
Host (adare) Guest VM (adarevm)
+-------------------+ +-------------------+
| AdareVMClient | WebSocket | AdareVMServer |
| | --------------> | |
| send TOOL_CALL | | dispatch to tool |
| | <-------------- | |
| receive | TOOL_RESULT | return result |
| TOOL_RESULT | or EVENT | or stream events |
+-------------------+ +-------------------+
The connection uses max_size=None (no message size limit) and disables
automatic WebSocket ping/pong on the client side. The host-side client
maintains a dictionary of pending calls keyed by message ID, using
asyncio.Future objects to correlate results with requests.
Message Types¶
All messages are JSON-serialised dataclasses defined in
adarelib.websocket.protocol.
Message Type |
Direction |
Purpose |
|---|---|---|
|
Host to Guest |
Invoke a tool on the agent. Contains |
|
Guest to Host |
Response to a tool call. Contains |
|
Guest to Host |
Real-time streaming event. Contains |
|
Guest to Host |
Status update with a |
|
Either |
Connection establishment with |
|
Either |
Connection keep-alive. |
Event types include: TEST_START, TEST_COMPLETE, TEST_FAILED,
GUI_CLICK, GUI_FIND, GUI_KEYPRESS, GUI_IDLE, GUI_DRAG,
COMMAND_START, COMMAND_COMPLETE, LOG, ERROR, and
PROGRESS.
Tool Categories¶
GUI Tools (GUIToolsMixin)¶
Automate mouse and keyboard input inside the VM using PyAutoGUI. GUI imports
are lazy-loaded to avoid initialisation failures on Wayland or when running
in host-GUI mode (ADARE_GUI_MODE=host).
screenshot– capture the full screen or a region; returns base64 PNG.click,right_click,double_click– mouse clicks at coordinates.drag– click-and-drag between two points.keyboard– type text or press key combinations.scroll– mouse wheel scrolling.goto– move the mouse cursor to coordinates.idle– wait for a specified duration.screenshot_window– capture a specific window.
Test Tools (TestToolsMixin)¶
Manage test functions inside the VM.
upload_testfunctions– receive a base64-encoded archive of test function Python files, extract them into a temporary directory, and make them available for execution.install_dependencies– install Python dependencies required by test functions.set_variables– update the variable dictionary available to tests.run_test– execute a single test function by name with parameters. Uses an instance-level cache (_testfunction_cache) to avoid re-discovering test functions on every call.
System Tools (SystemToolsMixin)¶
Shell execution and system management.
execute_shell– run a shell command and return stdout, stderr, and return code.collect_system_info– gather OS version, hostname, installed packages, and other system metadata.get_status– report agent health and available tools.set_screenshot_method– switch between screenshot backends.chain_commands– execute a sequence of shell commands in order.
File Tools (FileToolsMixin)¶
File transfer and filesystem operations.
pull_file_chunk– read a chunk of a guest file and return it as base64. Used for host-initiated file downloads when shared directories are not available.get_filesystem_snapshot– enumerate files and metadata in a directory tree. Used for forensic diff analysis.get_timestamp– return the current guest system time.
Agent Lifecycle¶
Wheel installation – during experiment setup, the host installs
adarevmandadarelibwheels inside the guest via pip. Version detection avoids redundant reinstalls (seeagent_installer.py). The wheels are placed in the/adare/vmshared directory by the file transfer strategy (see File Sharing).Startup – the host launches
adarevminside the guest (either via QGA command execution or a scheduled task on Windows). Theadarevm.main.run()entry point loadsconfig.jsonfrom the run directory, configures logging, resolves tool and data paths, and starts theAdareVMServeron0.0.0.0:18765.Connection – the host’s
AdareVMClientconnects to the agent’s WebSocket server through the forwarded port. The agent sends a welcome event listing available tools.Before the agent is installed, a setup command opens port 18765 in the guest’s firewall on both platforms:
New-NetFirewallRuleon Windows, andfirewall-cmd --add-port=18765/tcpon Linux guests that run firewalld (Fedora and relatives). The Linux step is a no-op wherefirewall-cmddoes not exist, which covers the whole Debian/Ubuntu family – those guests ship ufw inactive, so nothing blocks the port there.The Linux rule is deliberately not
--permanent: it changes only firewalld’s in-memory state, so nothing is written under/etc/firewalldand the guest’s on-disk configuration stays untouched for forensic diffing.Note
A blocked port does not look like a blocked port. firewalld’s default zone permits loopback while dropping inbound traffic on the external interface, so the agent installs, starts, binds and answers its own
/dev/tcp/localhost/18765probe – every in-guest check passes, and only the host’s forwarded connection is dropped. The run then fails at Connecting to Agent: All 6 connection attempts failed, which reads as a broken agent rather than a firewall. If you see that on a guest with a default-on firewall, check the firewall before the agent.Operation – the host sends
TOOL_CALLmessages as dictated by the playbook. Each tool runs as a background task; the agent streamsEVENTmessages for progress and returns aTOOL_RESULTwhen done.Shutdown – when the experiment finishes, the host disconnects. The agent cancels any remaining tasks for that client and continues listening (it may serve additional connections in dev mode). The VM is eventually stopped by the host lifecycle manager.