# How GatewayRegistry Manages Multiple Gateway Records and Active Gateway Switching in openclaw-windows-node

> Learn how GatewayRegistry in openclaw-windows-node manages multiple gateway records and seamlessly switches active gateways. Discover its thread-safe catalog and event notification system.

- Repository: [openclaw/openclaw-windows-node](https://github.com/openclaw/openclaw-windows-node)
- Tags: deep-dive
- Published: 2026-06-05

---

**GatewayRegistry is a thread-safe, pure-data catalog that stores all gateway endpoints in a locked list, tracks the current active gateway via a single ID string, and raises a `Changed` event whenever the registry mutates.**

In the `openclaw/openclaw-windows-node` repository, `GatewayRegistry` serves as the central authority that manages multiple gateway records and handles active gateway switching for the Windows node client. Located in [`src/OpenClaw.Connection/GatewayRegistry.cs`](https://github.com/openclaw/openclaw-windows-node/blob/main/src/OpenClaw.Connection/GatewayRegistry.cs), this class never holds runtime connection state; it only maintains and retrieves gateway information while ensuring atomic updates and disk persistence. Every public operation is synchronized so that background refreshes and UI actions cannot corrupt the gateway list or active selection.

## Core Architecture of GatewayRegistry

The registry is built around two private fields guarded by a single `_lock` object. This design keeps the component thread-safe without exposing synchronization details to callers.

### Thread-Safe Record Storage

All known gateways are stored in a private `List<GatewayRecord> _records` defined at line 16 of [`src/OpenClaw.Connection/GatewayRegistry.cs`](https://github.com/openclaw/openclaw-windows-node/blob/main/src/OpenClaw.Connection/GatewayRegistry.cs). Any read or write to this list occurs inside a `lock (_lock)` block, making every query and mutation atomic. The registry is intentionally a pure-data component; it does not manage sockets, streams, or protocol state.

### Active Gateway Tracking

The registry stores the currently selected gateway in a private string field named `_activeId`. Callers retrieve the active record through `GetActive()`, which looks up `_activeId` under lock and returns the matching `GatewayRecord` (lines 99–102). An `ActiveGatewayId` property exposes the raw ID for callers that only need the identifier. This separation allows the active gateway to be changed instantly in memory before any disk I/O or reconnection occurs.

## Managing Multiple Gateway Records

Because users can pair several devices, the registry provides three mutation methods that all respect the same lock and eventing contract.

### Adding and Updating Records with AddOrUpdate

The `AddOrUpdate(record)` method (lines 71–85) inserts a new `GatewayRecord` or replaces an existing entry when the incoming record has the same `Id`. After the lock is released, the method raises the `Changed` event, delivering a shallow copy of the full record list to any subscriber. This guarantees that UI components and background services always receive a consistent snapshot.

```csharp
var registry = new GatewayRegistry();
registry.AddOrUpdate(new GatewayRecord
{
    Id = "node-01",
    Url = "https://node01.openclaw.local",
    IsLocal = true,
    AuthToken = legacyToken
});

```

### Atomic In-Place Mutation

When a caller needs to transform an existing record—such as clearing an expired token or updating a timestamp—the `Update(id, updater)` method (lines 110–124) runs the supplied delegate inside the lock. Because the updater executes while the registry is locked, concurrent modifications cannot overwrite each other, preventing lost updates.

```csharp
registry.Update("node-01", record =>
{
    record.AuthToken = null;
    record.LastSeen = DateTimeOffset.UtcNow;
});

```

### Removing Gateways

`Remove(id)` (lines 87–96) deletes the specified record and automatically clears `_activeId` if the removed gateway was the active one. This prevents the registry from returning stale active references after a gateway is unpaired.

## Active Gateway Switching

Switching the active gateway is an explicit, two-step process that separates in-memory selection from disk persistence and reconnection.

### Selecting the Active Gateway

`SetActive(id)` writes the new `_activeId` under lock (lines 99–102). No network or disk I/O happens inside this call; it simply updates the pointer. This design keeps the UI responsive and lets the application decide when to persist or reconnect.

### End-to-End Switching Flow

A typical user-driven switch—such as choosing a different gateway from the tray menu—follows this sequence:

1. The menu handler calls `registry.SetActive(selectedId)`.
2. The UI layer or connection manager observes the `registry.Changed` event and calls `registry.GetActive()` to fetch the fresh `GatewayRecord`.
3. The connection manager reconnects using the credentials stored in that record, while the UI updates the displayed gateway name.

Because every step that touches `_records` or `_activeId` is protected by `_lock`, a background refresh cannot race with the user’s click. The connection manager in [`src/OpenClaw.Tray.WinUI/Services/ConnectionManagerWindowsNodeConnector.cs`](https://github.com/openclaw/openclaw-windows-node/blob/main/src/OpenClaw.Tray.WinUI/Services/ConnectionManagerWindowsNodeConnector.cs) consumes the registry exactly this way, resolving credentials from the active record after each switch.

## Persisting Gateway Data to Disk

The registry serializes its entire state through `Save()` and `Load()` (lines 129–166). `Save()` writes a `RegistryData` object—containing both the full record list and the active ID—to [`gateways.json`](https://github.com/openclaw/openclaw-windows-node/blob/main/gateways.json) using an atomic temp-file rename so that a crash during write never leaves a corrupt file. `Load()` deserializes the JSON back into `_records` and `_activeId`, restoring the last known state on application startup.

```csharp
registry.SetActive("node-02");
registry.Save(); // Atomic write to gateways.json

```

## Notifying UI Components via Events

Every mutation method—`AddOrUpdate`, `Remove`, and `Update`—raises the `Changed` event (lines 25–26). Subscribers such as the tray menu, setup wizard, and connection manager receive a shallow copy of the record list and can re-render or reconnect accordingly. This event-driven model decouples the registry from the presentation layer while ensuring every component stays synchronized with the active gateway.

## Migrating Legacy Settings

The registry also handles first-run upgrades through `MigrateFromSettings`. According to the implementation in [`GatewayRegistry.cs`](https://github.com/openclaw/openclaw-windows-node/blob/main/GatewayRegistry.cs), this method copies legacy token fields into a fresh `GatewayRecord`, marks the new record as active, and moves the old identity file into a per-gateway directory. The migration path is exercised in [`tests/OpenClaw.Connection.Tests/GatewayRegistryMigrationTests.cs`](https://github.com/openclaw/openclaw-windows-node/blob/main/tests/OpenClaw.Connection.Tests/GatewayRegistryMigrationTests.cs), validating that legacy data transitions cleanly into the new registry format.

## Summary

- `GatewayRegistry` keeps all gateway records in a `List<GatewayRecord>` protected by a private `_lock`, making every read and write atomic.
- The active gateway is tracked by a single `_activeId` string; callers switch it via `SetActive(id)` and read it via `GetActive()`.
- Mutations such as `AddOrUpdate`, `Update`, and `Remove` persist thread-safe changes and raise the `Changed` event with a snapshot of all records.
- Disk serialization uses an atomic temp-file rename in `Save()`, storing both the record list and active ID to [`gateways.json`](https://github.com/openclaw/openclaw-windows-node/blob/main/gateways.json).
- UI and connection components in [`ConnectionManagerWindowsNodeConnector.cs`](https://github.com/openclaw/openclaw-windows-node/blob/main/ConnectionManagerWindowsNodeConnector.cs) and [`SetupWizardRunner.cs`](https://github.com/openclaw/openclaw-windows-node/blob/main/SetupWizardRunner.cs) rely on the `Changed` event to refresh views and reconnect after an active gateway switch.

## Frequently Asked Questions

### How does GatewayRegistry prevent duplicate gateway entries?

The `AddOrUpdate` method checks for an existing record with the same `Id`. If a match is found, the old record is replaced rather than appended, ensuring the internal list never contains duplicate identities for a single gateway.

### What happens to the active gateway when a record is removed?

`Remove(id)` deletes the record and automatically clears `_activeId` if it referenced the removed gateway. This prevents the registry from pointing to a non-existent endpoint after unpairing.

### Is GatewayRegistry thread-safe for concurrent UI and background operations?

Yes. Every public method acquires the private `_lock` before touching `_records` or `_activeId`. According to the `openclaw-windows-node` source code, this synchronization guarantees that concurrent tray-menu clicks and background refreshes cannot corrupt the list or active selection.

### How is the registry state preserved across application restarts?

`Save()` serializes a `RegistryData` object containing the full record list and active ID to [`gateways.json`](https://github.com/openclaw/openclaw-windows-node/blob/main/gateways.json) using an atomic temp-file rename. On startup, `Load()` deserializes this file back into memory, restoring the exact state from the previous session.