The registration flow transforms a device configuration entry into a fully profiled, connected, and task-ready constellation agent through a coordinated 5-phase process:
- Loads user configuration from YAML
- Establishes WebSocket connection to device agent server
- Performs AIP registration protocol exchange
- Collects client telemetry data
- Activates the agent as task-ready
See Agent Registration Overview for architecture context and DeviceRegistry for data management details.
Multi-source AgentProfile construction and registration flow.
graph TB
Start([Start]) --> P1[Phase 1: User Configuration]
P1 --> P2[Phase 2: WebSocket Connection]
P2 --> P3[Phase 3: Service Registration]
P3 --> P4[Phase 4: Telemetry Collection]
P4 --> P5[Phase 5: Agent Activation]
P5 --> End([Agent Ready])
P2 -->|Connection Failed| Retry{Retry < Max?}
Retry -->|Yes| P2
Retry -->|No| Failed([Failed])
P3 -->|Registration Rejected| Failed
style P1 fill:#e1f5ff
style P2 fill:#fff4e1
style P3 fill:#ffe1e1
style P4 fill:#e8f5e9
style P5 fill:#f3e5f5
style End fill:#c8e6c9
style Failed fill:#ffcdd2
| Phase | Duration | Can Fail? | Retry? | Result |
|---|---|---|---|---|
| 1. User Configuration | < 1s | Yes | No | AgentProfile created |
| 2. WebSocket Connection | 1-5s | Yes | Yes (up to max_retries) | Active WebSocket |
| 3. Service Registration | 1-2s | Yes | No | Client type recorded |
| 4. Telemetry Collection | 1-3s | No (graceful degradation) | No | System info merged |
| 5. Agent Activation | < 1s | No | No | Status = IDLE |
Load device configuration from YAML file and create initial AgentProfile with user-specified data.
config/galaxy/devices.yaml:
devices:
- device_id: "windowsagent"
server_url: "ws://localhost:5005/ws"
os: "windows"
capabilities:
- "web_browsing"
- "office_applications"
- "file_management"
metadata:
location: "office_desktop"
performance: "high"
description: "Primary Windows workstation"
operation_engineer_email: "admin@example.com"
max_retries: 5
auto_connect: truesequenceDiagram
participant YAML as devices.yaml
participant Manager as DeviceManager
participant Registry as DeviceRegistry
YAML->>Manager: Load configuration
Manager->>Manager: Parse YAML
loop For each device in config
Manager->>Registry: register_device(device_id, server_url, ...)
Registry->>Registry: Create AgentProfile
Registry->>Registry: Set status = DISCONNECTED
Registry-->>Manager: AgentProfile created
end
Manager->>Manager: Check auto_connect flag
alt auto_connect == true
Manager->>Manager: Schedule connect_device()
end
from galaxy.client.device_manager import ConstellationDeviceManager
# Initialize manager
manager = ConstellationDeviceManager(
task_name="production_constellation",
heartbeat_interval=30.0,
reconnect_delay=5.0
)
# Phase 1: Register device from configuration
success = await manager.register_device(
device_id="windowsagent",
server_url="ws://localhost:5005/ws",
os="windows",
capabilities=["web_browsing", "office_applications", "file_management"],
metadata={
"location": "office_desktop",
"performance": "high",
"description": "Primary Windows workstation"
},
auto_connect=True # Proceed to Phase 2 automatically
)AgentProfile (Version 1):
AgentProfile(
device_id="windowsagent",
server_url="ws://localhost:5005/ws",
os="windows",
capabilities=["web_browsing", "office_applications", "file_management"],
metadata={
"location": "office_desktop",
"performance": "high",
"description": "Primary Windows workstation"
},
status=DeviceStatus.DISCONNECTED,
last_heartbeat=None,
connection_attempts=0,
max_retries=5,
current_task_id=None
)Phase 1 Complete: Device registered in local registry with user-specified configuration. Status:
DISCONNECTED
Establish a persistent WebSocket connection to the device agent's UFO server. This connection is managed by the WebSocketConnectionManager component.
See Client Components for component architecture details.
sequenceDiagram
participant Manager as DeviceManager
participant Registry as DeviceRegistry
participant WSManager as WebSocketConnectionManager
participant Server as UFO Server
participant MsgProc as MessageProcessor
participant HB as HeartbeatManager
Manager->>Registry: update_device_status(CONNECTING)
Manager->>Registry: increment_connection_attempts()
Manager->>WSManager: connect_to_device(device_info, message_processor)
WSManager->>Server: WebSocket handshake (ws://...)
alt Connection Successful
Server-->>WSManager: Connection accepted
WSManager->>MsgProc: start_message_handler(device_id, websocket)
MsgProc->>MsgProc: Start listening for messages
WSManager-->>Manager: Connection established
Manager->>Registry: update_device_status(CONNECTED)
Manager->>Registry: update_heartbeat()
Manager->>HB: start_heartbeat(device_id)
HB->>HB: Start periodic heartbeat checks
else Connection Failed
Server-->>WSManager: Connection refused / timeout
WSManager-->>Manager: ConnectionError
Manager->>Registry: update_device_status(FAILED)
Manager->>Manager: schedule_reconnection()
end
| Parameter | Value | Description |
|---|---|---|
| URL | ws://host:port/ws |
WebSocket endpoint from configuration |
| Timeout | 30 seconds | Connection timeout |
| Protocols | WebSocket standard | No special sub-protocols |
| Headers | None | Standard WebSocket headers |
async def connect_device(self, device_id: str, is_reconnection: bool = False) -> bool:
"""Connect to a registered device with retry logic."""
device_info = self.device_registry.get_device(device_id)
# Update status
self.device_registry.update_device_status(device_id, DeviceStatus.CONNECTING)
# Increment attempts (only for initial connection, not reconnections)
if not is_reconnection:
self.device_registry.increment_connection_attempts(device_id)
try:
# Establish WebSocket connection
await self.connection_manager.connect_to_device(
device_info,
message_processor=self.message_processor
)
# Success: Update status
self.device_registry.update_device_status(device_id, DeviceStatus.CONNECTED)
self.device_registry.update_heartbeat(device_id)
# Start heartbeat monitoring
self.heartbeat_manager.start_heartbeat(device_id)
return True
except (websockets.WebSocketException, OSError, asyncio.TimeoutError) as e:
self.logger.error(f"Connection failed: {e}")
self.device_registry.update_device_status(device_id, DeviceStatus.FAILED)
# Schedule reconnection if under retry limit
if device_info.connection_attempts < device_info.max_retries:
self._schedule_reconnection(device_id)
return Falsegraph TB
Disconnect[Connection Lost] --> CheckRetries{Attempts < Max?}
CheckRetries -->|Yes| Wait[Wait reconnect_delay seconds]
Wait --> Attempt[Attempt Reconnection]
Attempt --> Success{Success?}
Success -->|Yes| Connected[CONNECTED]
Success -->|No| Increment[Increment Retry Counter]
Increment --> CheckRetries
CheckRetries -->|No| Failed[FAILED - Give Up]
Connected --> End([Ready for Phase 3])
Failed --> End2([Registration Failed])
style Connected fill:#c8e6c9
style Failed fill:#ffcdd2
Reconnection Parameters:
| Parameter | Default | Description |
|---|---|---|
max_retries |
5 | Maximum reconnection attempts |
reconnect_delay |
5.0 seconds | Delay between attempts |
retry_counter |
Per-device | Tracked in AgentProfile.connection_attempts |
Warning: If a device fails to connect after
max_retriesattempts, it entersFAILEDstatus and requires manual intervention (e.g., restarting the device agent server).
- WebSocket connection established and active
- Message handler listening for incoming messages
- Heartbeat monitoring started
- Status:
CONNECTED
Phase 2 Complete: WebSocket connection established. Message handler and heartbeat monitoring active.
Perform AIP registration protocol exchange to:
- Identify client type (DEVICE vs CONSTELLATION)
- Advertise platform information
- Validate registration with server
See AIP Protocol Documentation for detailed protocol specifications.
sequenceDiagram
participant Manager as DeviceManager
participant WSManager as WebSocketConnectionManager
participant Transport as WebSocketTransport
participant RegProtocol as RegistrationProtocol
participant Server as UFO Server Handler
Note over Manager,Server: Device Agent Client Registration
Manager->>RegProtocol: register_as_device(device_id, metadata, platform)
RegProtocol->>RegProtocol: Prepare ClientMessage
Note over RegProtocol: type: REGISTER<br/>client_type: DEVICE<br/>metadata: system_info, etc.
RegProtocol->>Transport: send_message(ClientMessage)
Transport->>Server: WebSocket: REGISTER message
Server->>Server: Parse ClientMessage
Server->>Server: Validate registration
Server->>Server: Extract metadata, system_info
Server->>Server: Store in ClientConnectionManager
Server->>Transport: ServerMessage (status: OK)
Transport->>RegProtocol: receive_message(ServerMessage)
alt Registration Successful
RegProtocol-->>Manager: True (registration successful)
Manager->>Registry: update_device_status(CONNECTED)
else Registration Failed
RegProtocol-->>Manager: False (registration failed)
Manager->>Registry: update_device_status(FAILED)
end
Client → Server (REGISTER message):
ClientMessage(
type=ClientMessageType.REGISTER,
client_id="windowsagent",
client_type=ClientType.DEVICE,
status=TaskStatus.OK,
timestamp="2025-11-06T10:30:00.000Z",
metadata={
"platform": "windows",
"registration_time": "2025-11-06T10:30:00.000Z",
"system_info": {
"platform": "windows",
"os_version": "10.0.22631",
"cpu_count": 16,
"memory_total_gb": 32.0,
"hostname": "DESKTOP-DEV01",
"ip_address": "192.168.1.100",
"supported_features": ["gui", "cli", "browser", "file_system", "office"],
"platform_type": "computer",
"schema_version": "1.0"
}
}
)Server → Client (Confirmation):
ServerMessage(
type=ServerMessageType.HEARTBEAT,
status=TaskStatus.OK,
timestamp="2025-11-06T10:30:01.000Z",
response_id="reg_confirmation_12345"
)# In UFOWebSocketHandler.connect()
async def connect(self, websocket: WebSocket) -> str:
"""Server-side registration handling."""
await websocket.accept()
# Initialize AIP protocols
self.transport = WebSocketTransport(websocket)
self.registration_protocol = RegistrationProtocol(self.transport)
# Parse registration message
reg_info = await self._parse_registration_message()
# Validate client type
client_type = reg_info.client_type # DEVICE or CONSTELLATION
platform = reg_info.metadata.get("platform", "windows")
# Register client
client_id = reg_info.client_id
self.client_manager.add_client(
client_id,
platform,
websocket,
client_type,
reg_info.metadata # Contains system_info
)
# Send confirmation
await self._send_registration_confirmation()
return client_idFor constellation clients (not device agents), the registration differs:
# Constellation client registration
ClientMessage(
type=ClientMessageType.REGISTER,
client_id="constellation_orchestrator",
client_type=ClientType.CONSTELLATION,
target_id="windowsagent", # Target device for this constellation
status=TaskStatus.OK,
timestamp="2025-11-06T10:30:00.000Z",
metadata={
"type": "constellation_client",
"targeted_device_id": "windowsagent"
}
)Note: Device clients register as
ClientType.DEVICE, while constellation orchestrators register asClientType.CONSTELLATIONwith atarget_idpointing to the device they want to control.
- Client registered in server's
ClientConnectionManager - Client type (DEVICE/CONSTELLATION) recorded
- Platform information stored
- Registration confirmation received
Phase 3 Complete: AIP registration protocol completed. Client type and platform recorded on server.
Collect real-time system information from the device client and merge it into the AgentProfile. The system information is collected by the device's DeviceInfoProvider during registration and sent to the server as part of the registration metadata.
See Device Info Provider for details on telemetry collection.
sequenceDiagram
participant Manager as DeviceManager
participant WSManager as WebSocketConnectionManager
participant Server as UFO Server
participant DIP as DeviceInfoProvider
participant Registry as DeviceRegistry
Note over Manager,Registry: Request Device Info
Manager->>WSManager: request_device_info(device_id)
WSManager->>Server: Send DEVICE_INFO_REQUEST
Note over Server,DIP: Server has already received system_info during registration
Server->>Server: Retrieve stored system_info from ClientConnectionManager
Server-->>WSManager: Return system_info
WSManager-->>Manager: system_info dict
Note over Manager,Registry: Merge System Info into AgentProfile
Manager->>Registry: update_device_system_info(device_id, system_info)
Registry->>Registry: Update OS if not set
Registry->>Registry: Merge supported_features into capabilities
Registry->>Registry: Add system_info to metadata
Registry->>Registry: Add custom_metadata if present
Registry-->>Manager: Update complete
The device client collects system info during registration (before Phase 4):
# In WebSocket client's register_client() method
from ufo.client.device_info_provider import DeviceInfoProvider
# Collect device info
system_info = DeviceInfoProvider.collect_system_info(
client_id=self.ufo_client.client_id,
custom_metadata=None
)
# Prepare metadata for registration
metadata = {
"system_info": system_info.to_dict(),
"registration_time": datetime.now(timezone.utc).isoformat()
}
# Register with AIP (includes system_info in metadata)
await self.registration_protocol.register_as_device(
device_id=self.ufo_client.client_id,
metadata=metadata,
platform=self.ufo_client.platform
){
"platform": "windows",
"os_version": "10.0.22631",
"cpu_count": 16,
"memory_total_gb": 32.0,
"hostname": "DESKTOP-DEV01",
"ip_address": "192.168.1.100",
"supported_features": [
"gui",
"cli",
"browser",
"file_system",
"office",
"windows_apps"
],
"platform_type": "computer",
"schema_version": "1.0"
}See Device Info Provider for telemetry collection details.
def update_device_system_info(
self, device_id: str, system_info: Dict[str, Any]
) -> bool:
"""Update AgentProfile with system information."""
device_info = self.get_device(device_id)
if not device_info:
return False
# 1. Update OS information
if "platform" in system_info:
device_info.os = system_info["platform"]
# 2. Merge capabilities with supported features (avoid duplicates)
if "supported_features" in system_info:
features = system_info["supported_features"]
existing_caps = set(device_info.capabilities)
new_caps = existing_caps.union(set(features))
device_info.capabilities = list(new_caps)
# 3. Update metadata with system information
device_info.metadata.update({
"system_info": {
"platform": system_info.get("platform"),
"os_version": system_info.get("os_version"),
"cpu_count": system_info.get("cpu_count"),
"memory_total_gb": system_info.get("memory_total_gb"),
"hostname": system_info.get("hostname"),
"ip_address": system_info.get("ip_address"),
"platform_type": system_info.get("platform_type"),
"schema_version": system_info.get("schema_version")
}
})
# 4. Add custom metadata if present
if "custom_metadata" in system_info:
device_info.metadata["custom_metadata"] = system_info["custom_metadata"]
# 5. Add tags if present
if "tags" in system_info:
device_info.metadata["tags"] = system_info["tags"]
return TrueBefore Telemetry (AgentProfile v2):
AgentProfile(
device_id="windowsagent",
os="windows", # From user config
capabilities=["web_browsing", "office_applications", "file_management"],
metadata={
"location": "office_desktop",
"performance": "high"
}
)After Telemetry (AgentProfile v3 - Complete):
AgentProfile(
device_id="windowsagent",
os="windows", # Confirmed by telemetry
capabilities=[
"web_browsing", "office_applications", "file_management", # User config
"gui", "cli", "browser", "file_system", "office", "windows_apps" # Auto-detected
],
metadata={
# User config
"location": "office_desktop",
"performance": "high",
# Telemetry
"system_info": {
"platform": "windows",
"os_version": "10.0.22631",
"cpu_count": 16,
"memory_total_gb": 32.0,
"hostname": "DESKTOP-DEV01",
"ip_address": "192.168.1.100",
"platform_type": "computer",
"schema_version": "1.0"
}
}
)Phase 4 Complete: System information collected and merged into AgentProfile. Capabilities expanded with auto-detected features.
Finalize agent registration and set it to IDLE status, ready to accept task assignments.
sequenceDiagram
participant Manager as DeviceManager
participant Registry as DeviceRegistry
participant HB as HeartbeatManager
Manager->>Registry: set_device_idle(device_id)
Registry->>Registry: Update status = IDLE
Registry->>Registry: Clear current_task_id = None
Registry-->>Manager: Status updated
Manager->>HB: Verify heartbeat active
HB-->>Manager: Heartbeat OK
Manager->>Manager: Log successful registration
Note over Manager: ✅ Device ready for tasks
# Set device to IDLE (ready to accept tasks)
self.device_registry.set_device_idle(device_id)
self.logger.info(f"✅ Successfully connected to device {device_id}")AgentProfile(
# Identity
device_id="windowsagent",
server_url="ws://localhost:5005/ws",
# Platform & Capabilities
os="windows",
capabilities=[
"web_browsing", "office_applications", "file_management",
"gui", "cli", "browser", "file_system", "office", "windows_apps"
],
metadata={
"location": "office_desktop",
"performance": "high",
"platform": "windows",
"registration_time": "2025-11-06T10:30:00Z",
"system_info": {
"platform": "windows",
"os_version": "10.0.22631",
"cpu_count": 16,
"memory_total_gb": 32.0,
"hostname": "DESKTOP-DEV01",
"ip_address": "192.168.1.100",
"platform_type": "computer",
"schema_version": "1.0"
}
},
# Status
status=DeviceStatus.IDLE, # ✅ Ready for tasks!
last_heartbeat=datetime(2025, 11, 6, 10, 30, 45),
# Connection
connection_attempts=0, # Reset after successful connection
max_retries=5,
# Task
current_task_id=None
)Phase 5 Complete: Agent fully registered, profiled, and activated. Status:
IDLE- Ready to accept task assignments.
import asyncio
from galaxy.client.device_manager import ConstellationDeviceManager
async def register_windows_workstation():
"""Complete registration flow example."""
# Initialize manager
manager = ConstellationDeviceManager(
task_name="office_constellation",
heartbeat_interval=30.0,
reconnect_delay=5.0
)
print("📝 Phase 1: User Configuration")
# Register device from user config
success = await manager.register_device(
device_id="windowsagent",
server_url="ws://localhost:5005/ws",
os="windows",
capabilities=["web_browsing", "office_applications", "file_management"],
metadata={
"location": "office_desktop",
"performance": "high",
"description": "Primary Windows workstation",
"operation_engineer_email": "admin@example.com"
},
max_retries=5,
auto_connect=True # Will proceed to Phase 2-5 automatically
)
if success:
print("✅ Registration successful!")
# Get complete profile
profile = manager.get_device_info("windowsagent")
print(f"\n📊 AgentProfile:")
print(f" Device ID: {profile.device_id}")
print(f" Status: {profile.status.value}")
print(f" OS: {profile.os}")
print(f" Capabilities: {profile.capabilities}")
print(f" System Info:")
system_info = profile.metadata.get("system_info", {})
print(f" - CPU Cores: {system_info.get('cpu_count')}")
print(f" - Memory: {system_info.get('memory_total_gb')} GB")
print(f" - Hostname: {system_info.get('hostname')}")
print(f" - IP: {system_info.get('ip_address')}")
# Device is now ready for tasks
print(f"\n🚀 Device is ready to receive tasks!")
else:
print("❌ Registration failed")
# Run the example
asyncio.run(register_windows_workstation())Output:
📝 Phase 1: User Configuration
🌐 Phase 2: WebSocket Connection
Connecting to ws://localhost:5005/ws...
Connection established
📡 Phase 3: Service Registration
Sending REGISTER message...
Registration confirmed
📊 Phase 4: Telemetry Collection
Collecting system information...
System info merged
✅ Phase 5: Agent Activation
Device set to IDLE
✅ Registration successful!
📊 AgentProfile:
Device ID: windowsagent
Status: idle
OS: windows
Capabilities: ['web_browsing', 'office_applications', 'file_management', 'gui', 'cli', 'browser', 'file_system', 'office', 'windows_apps']
System Info:
- CPU Cores: 16
- Memory: 32.0 GB
- Hostname: DESKTOP-DEV01
- IP: 192.168.1.100
🚀 Device is ready to receive tasks!
try:
success = await manager.register_device(...)
except websockets.WebSocketException as e:
logger.error(f"WebSocket error: {e}")
# Will automatically retry if under max_retries
except OSError as e:
logger.error(f"Network error: {e}")
# Check network connectivity
except asyncio.TimeoutError:
logger.error("Connection timeout")
# Server may be down or unreachable# Server-side validation
if not self.client_manager.is_device_connected(claimed_device_id):
error_msg = f"Target device '{claimed_device_id}' is not connected"
await self._send_error_response(error_msg)
await self.transport.close()
raise ValueError(error_msg)# Graceful degradation - always returns valid DeviceSystemInfo
try:
return DeviceSystemInfo(...)
except Exception as e:
logger.error(f"Error collecting system info: {e}")
# Return minimal info instead of failing
return DeviceSystemInfo(
device_id=client_id,
platform="unknown",
os_version="unknown",
cpu_count=0,
memory_total_gb=0.0,
hostname="unknown",
ip_address="unknown",
supported_features=[],
platform_type="unknown"
)| Topic | Document | Description |
|---|---|---|
| Overview | Agent Registration Overview | Registration architecture |
| AgentProfile | AgentProfile | Profile structure details |
| Device Registry | Device Registry | Registry component |
| Galaxy Devices Config | Galaxy Devices Configuration | YAML config reference |
| Device Info | Device Info Provider | Telemetry collection |
| AIP Protocol | AIP Overview | Protocol fundamentals |
1. Use auto_connect for Production
await manager.register_device(..., auto_connect=True)
# Automatically completes all 5 phases2. Configure Appropriate max_retries
# Critical devices: higher retries
max_retries=10 # For production servers
# Test devices: lower retries
max_retries=3 # For development environments3. Monitor Registration Status
profile = manager.get_device_info(device_id)
if profile.status == DeviceStatus.FAILED:
logger.error(f"Device {device_id} failed to register")
# Take corrective action4. Provide Rich Metadata
metadata={
"location": "datacenter_us_west",
"performance": "high",
"tags": ["production", "critical"],
"operation_engineer_email": "ops@example.com"
}- Configure Devices: Read Galaxy Devices Configuration
- Understand DeviceRegistry: Check Device Registry
- Learn Task Assignment: See Task Execution Documentation
- Study AIP Messages: Read AIP Messages
- ConstellationDeviceManager:
galaxy/client/device_manager.py - DeviceRegistry:
galaxy/client/components/device_registry.py - RegistrationProtocol:
aip/protocol/registration.py - UFOWebSocketHandler:
ufo/server/ws/handler.py - DeviceInfoProvider:
ufo/client/device_info_provider.py