How `openlogi-agent-core` Orchestrates Agent Functionality: Inside the Orchestrator

The Orchestrator in openlogi-agent-core acts as the central coordinator that owns user configuration, device inventory, and shared runtime structures, synchronizing state across OS hooks, HID++ watchers, and GUI components through atomic rebuilds and Arc-based shared memory.

The OpenLogi agent relies on a central Orchestrator to translate static configuration and live device enumeration into coherent runtime behavior. Located in crates/openlogi-agent-core/src/orchestrator.rs, this component serves as the single source of truth for how the system orchestrates agent functionality across background workers and foreground GUI interactions.

The Three Phases of Orchestration

The Orchestrator manages agent lifecycle through three distinct phases, each handling specific responsibilities for state consistency.

Initialization Phase

During startup, the Orchestrator::new function (lines 96-133) creates a fresh SharedRuntime populated with empty Arcs and seeds them from the loaded Config. This phase establishes the foundational runtime structures that background threads will consume.

The initialization sequence:

  • Instantiates RwLocks and Arc containers for hook maps, capture plans, and DPI cycles
  • Seeds observable state with the launch-at-login flag
  • Triggers the first rebuild() to publish initial hook maps and DPI state

State Synchronization Phase

The orchestrator continuously refreshes device inventory through refresh_inventory, publish_device_runtime, rebuild, and publish_inventory (lines 158-230). This phase reconciles the physical device state with the agent's internal representation.

When the inventory watcher detects changes, the orchestrator:

  • Updates the InventoryState enum (Pending, Ready, or Unavailable)
  • Recomputes per-device facts into AgentDevice snapshots
  • Determines if volatile settings must be reapplied after system wake events
  • Triggers full rebuilds only when device routes or selection changes

Runtime Mutation Phase

User-driven changes flow through dedicated mutation methods: set_current_app, reload_config, set_camera_active, and set_manual_light_power (lines 270-340). Each mutation repopulates relevant Arcs and notifies the observable IPC cell to maintain GUI consistency.

Core Data Structures

Four primary structures enable the orchestrator to coordinate thread-safe access across the agent.

AgentDevice

The AgentDevice struct (lines 43-64) represents a minimal per-device snapshot containing the config key, HID++ route, capabilities, and online flag. Background workers use these snapshots to build capture plans and DPI cycles without blocking on the main orchestrator lock.

SharedRuntime

SharedRuntime (lines 66-107) holds all mutable runtime state as cheap-to-clone Arcs, including hook_maps, capture_plans, dpi_cycle, and channel pools. Background threads read these structures without contention while the orchestrator atomically rewrites them during rebuilds.

ObservableState

Defined in crates/openlogi-agent-core/src/observable.rs, this IPC-visible cell exposes public fields like inventory(), status(), and camera_active that the GUI polls. The orchestrator updates this state whenever public-facing fields change, ensuring the GUI reflects the current device reality.

InventoryState

The InventoryState enum (lines 174-186) tracks the enumeration lifecycle, storing the latest DeviceInventory snapshots and signaling whether the inventory is Pending, Ready, or Unavailable for downstream consumers.

How the Orchestrator Coordinates Runtime Components

Device Discovery and Inventory Management

The refresh_inventory method processes updates from the inventory watcher each tick. It updates inventory and hid_open_failures counters, then calls the static helper build_devices to transform raw HID++ inventory into AgentDevice instances.

If device routes changed or the selected device switched, the orchestrator updates self.devices, selects a new current device, and triggers a full rebuild(). If only online flags changed, it republishes minimal updates to avoid unnecessary computation.

Publishing Runtime Views

The publish_device_runtime method computes capture plans via capture_plans_for, generating one plan per online device reflecting current config and foreground app context. It then rebuilds per-device DPI cycles through rebuild_dpi_cycles and updates global keyboard bindings and host-switch links.

Hook Map Generation

The hook_maps_for method generates a HookMaps struct that merges button bindings and OS-hook gestures for a specific device key and foreground application. This result is written atomically into shared.hook_maps, ensuring the OS-hook never observes a half-updated state that could cause missed inputs or race conditions.

Handling Dynamic Mutations

Foreground app changes invoke set_current_app, which swaps app-scoped hook maps and republishes capture plans immediately.

Configuration reloads through reload_config swap the config, republish scroll preferences, update the launch-at-login flag, and retain manual light overrides when still applicable. This method also reapplies native wheel modes, Fn-locks, and light settings across all connected devices.

Camera-linked lighting uses set_camera_active to toggle lights across capable devices while clearing manual overrides, while set_manual_light_power records transient overrides for specific light routes that persist only while the device remains camera-linked.

Volatile Settings Re-application

When detecting system wake events, reapply_volatile_on_next_refresh sets an internal flag. During the subsequent refresh_inventory call, the orchestrator invokes reapply_volatile_settings for every online device, writing DPI, SmartShift, wheel mode, lighting, and Fn-lock values in background threads to restore hardware state lost during sleep.

Working with the Orchestrator

Use the Orchestrator type to bridge configuration, device discovery, and GUI state:

use openlogi_core::config::Config;
use openlogi_agent_core::{Orchestrator, ObservableState};
use std::sync::Arc;

// 1️⃣ Initialise the orchestrator from a loaded config.
let config: Config = /* load Config from toml */;
let observable = Arc::new(ObservableState::new());
let mut orch = Orchestrator::new(config, observable.clone());

// 2️⃣ Periodically refresh the device inventory (called by the inventory watcher).
fn on_inventory_tick(
    inventories: &[DeviceInventory],
    standalone: &[StandaloneDevice],
    hid_open_failures: bool,
) {
    orch.refresh_inventory(inventories, standalone, hid_open_failures);
}

// 3️⃣ React to a foreground‑app change from the GUI.
fn foreground_changed(app: Option<ForegroundApp>) {
    orch.set_current_app(app);
}

// 4️⃣ Reload the configuration after the user edits `config.toml`.
fn reload(new_cfg: Config) {
    orch.reload_config(new_cfg);
}

// 5️⃣ Query the IPC‑exposed state (e.g., for the GUI).
let current_inventory = orch.inventory();       // Vec<DeviceInventory>
let health = orch.inventory_health();           // InventoryHealth enum
let ring = orch.action_ring_session(None);     // Option<ActionRingSessionSpec>

Summary

  • The Orchestrator in openlogi-agent-core serves as the single source of truth for device state, configuration, and runtime mappings.
  • Three operational phases—initialization, state synchronization, and runtime mutation—ensure consistent behavior across system events and user interactions.
  • SharedRuntime uses Arc-based structures to provide lock-free reads for background workers while maintaining atomic updates from the main thread.
  • Device inventory changes trigger selective updates, with full rebuilds occurring only when hardware routes or selected devices change.
  • The observable state pattern enables reactive GUI updates without polling the hardware directly.

Frequently Asked Questions

How does SharedRuntime enable thread-safe coordination?

SharedRuntime stores all mutable state—including hook maps, capture plans, and DPI cycles—as std::sync::Arc pointers that can be cloned cheaply. Background threads like the OS-hook and HID++ gesture watcher hold clones of these Arcs and read them without acquiring locks. When the orchestrator updates state, it creates new Arc allocations and atomically swaps the pointers, ensuring readers never observe partial writes or block on the update path.

What triggers a full rebuild() versus a partial update?

A full rebuild occurs when refresh_inventory detects changes to device routes, the selected device, or the set of connected hardware. In crates/openlogi-agent-core/src/orchestrator.rs lines 158-230, the orchestrator checks reapply_targets and plan_reapply flags to determine if hardware capabilities changed. If only online flags toggle without structural changes, the orchestrator calls publish_inventory to update availability status without recomputing hook maps or capture plans.

How does the orchestrator handle configuration reloads at runtime?

The reload_config method (lines 270-340) accepts a new Config object and performs a coordinated swap. It updates scroll preferences in SharedRuntime, recomputes the selected device based on new configuration rules, and conditionally retains manual light overrides if they remain applicable. The method then triggers a full rebuild() to regenerate hook mappings and capture plans that reflect the new settings, ensuring zero-downtime configuration updates.

What is the purpose of the AgentDevice snapshot?

AgentDevice (lines 43-64) provides a minimal, serializable view of device capabilities and routing information that the orchestrator uses to build execution plans. Unlike the full HID++ device objects, these snapshots contain only the config key, route, capabilities bitmask, and online status. This abstraction allows the Orchestrator to compute capture plans and DPI cycles without holding references to low-level hardware handles that could block or fail during enumeration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →