Skip to content

discovery detailed analysis

Pola, Sudhir edited this page May 15, 2026 · 3 revisions

DMT Device Discovery & Observability — Solution Proposal

Objective

This document proposes a unified solution for three related features in the Device Management Toolkit (DMT). The common goal is to give operators greater visibility into their AMT-capable device fleet — covering what devices are present on the network, what their firmware and platform state is, and whether key services such as Intel LMS are running. The proposal defines the required data model changes, API impact across Console, MPS, RPS, and rpc-go, and a design for the discovery agent that collects and reports device state.

Scope

Issue Summary
sample-web-ui#1265SKU Type & Config Mode in Devices UI Display SKU type (vPro / ISM / Non-vPro) and Configuration mode (ACM / CCM / Pre-Prov) in the Devices list of the OpenAMT UI. Use the existing deviceInfo JSON column in MPS; add the same column to Console if not already present.
console#858Device Discovery Add a discovery capability so customers gain visibility into AMT-capable devices across their network. A lightweight agent (likely extending rpc-go) collects AMT/ME details (FW version, SKU, control mode, TLS mode, network config, UPID, cert hashes) and platform details (OS, LMS, CPU, hostname, IP, ethernet adapters, monitor presence) and reports them back to Console. Console ingests, stores, and visualises the data via a dashboard with filtering, sorting, capability breakdowns, and export support.
rpc-go#1246Detect LMS During Orchestration Detect whether Intel LMS (Local Manageability Service) is installed on a device during the v3 orchestration/registration process. rpc-go reports LMS presence in the registration payload; Console captures and persists it in a new is_lms_available field in the device information table. UI enhancements based on LMS presence are out of scope for this story.

Solution Proposal

Proposed Data Model Changes

Fields Required by the Three Features — Gap Analysis

The table below maps each data field required by the three issues against what is currently stored.

Required Field Required By Currently in MPS/Console? Notes
CSME FW Version Discovery, SKU/Mode UI deviceInfo.fwVersion Already stored in deviceInfo JSON
SKU type label (vPro / ISM / Non-vPro) Discovery, SKU/Mode UI ⚠️ Partial — raw deviceInfo.fwSku string SKU is stored as a raw number; decoded label (features field) exists but is not a structured enum
Control Mode (ACM / CCM / Pre-Prov) Discovery, SKU/Mode UI deviceInfo.currentMode Stored as "0"/"1"/"2"; UI label decoding needed
Provisioned State Discovery ✅ Derivable from currentMode 0 = pre-provisioned, 1/2 = provisioned
TLS Mode Discovery ❌ Not stored Not in deviceInfo or any column
DHCP vs Static Discovery ⚠️ Partial — deviceInfo.ipAddress only No DHCP flag, gateway, or subnet stored
802.1x / wireless config Discovery ❌ Not stored Available via Console AMT API (/amt/networkSettings) but not persisted
AMT certificate hashes Discovery ❌ Not stored Available during activation payload but not persisted in deviceInfo
UPID Discovery ❌ Not stored Not collected anywhere today
AMT enabled in BIOS Discovery ❌ Not stored Collectable via rpc-go but not stored
ME Interface Version Discovery ❌ Not stored Not in deviceInfo
LMS version / installed Discovery, LMS Orchestration ❌ Not stored Not collected today
OS details (distro, version) Discovery ❌ Not stored Not collected today
CPU details Discovery ❌ Not stored Available via Console CIM API (/amt/hardwareInfo) but not persisted
OS IP address Discovery ⚠️ Partial — deviceInfo.ipAddress is AMT IP OS IP may differ from AMT IP
Number of ethernet adapters Discovery ❌ Not stored Not collected today
Monitor connected Discovery ❌ Not stored Not collected today
OS hostname Discovery hostname column Already a top-level column
DNS suffix (AMT) Discovery dnsSuffix column Already a top-level column
Last reported/discovered time Discovery (NFR) deviceInfo.lastUpdated Exists in deviceInfo JSON
Export support Discovery (NFR) ⚠️ Partial Console has an export usecase but not wired for discovery data

Summary

  • MPS is the system of record for CIRA connection state and the deviceInfo JSON blob. It does not store credentials.
  • Console mirrors deviceInfo, adds credentials/TLS fields, and is the entry point for all AMT management actions. It has live APIs to query most of the missing fields (network, hardware, features) but does not persist them back to the database.
  • The deviceInfo JSON column is the natural extension point for adding new discovery fields without a disruptive schema change. All new fields — both AMT/ME and OS/platform — are added as flat top-level keys, consistent with the existing flat AMT keys. Fields that are queried live today (network settings, hardware info, features) should be persisted into this blob by the discovery agent on each report cycle.
  • Fields not yet collected anywhere (UPID, LMS version, OS distro, monitor detection, 802.1x state) require new collection logic. rpc amtinfo already collects AMT data and automatically falls back to OS-only data when AMT is unavailable — extending its sync payload to include OS/platform fields is incremental work within rpc-go.

The three features require extending the existing devices table with additional persisted data. All new fields are stored in the single deviceInfo JSON column as flat top-level keys — consistent with the existing flat AMT keys and fully backward-compatible. A separate discoveryInfo column is not introduced — deviceInfo is already schemaless, so splitting into a second column would add DTOs, API plumbing, and duplicate SQL + Mongo backend implementations with no querying or migration benefit.

Storage Decision Summary

Concern Storage Location Rationale
AMT/ME firmware state (SKU, mode, TLS, UPID) deviceInfo JSON — flat keys, both MPS and Console Consistent with existing flat AMT keys; fully backward-compatible; RPS and rpc amtinfo --sync already populate this blob
OS and platform state (LMS, OS details, network adapters) deviceInfo JSON — flat keys, Console only Same schemaless column; flat keys are consistent and require no struct nesting in the Go DTOs
LMS presence at registration (lmsInstalled) deviceInfo.lmsInstalled — Console only, partial JSON merge at registration No new column needed; deviceInfo already accepts partial JSON merges
Credentials and TLS connection config Existing Console-only columns — no change Already correctly separated
Live connection state (connectionStatus, lastConnected) Existing MPS columns — no change Updated at CIRA runtime; not a discovery concern

Entity Structure

The diagram below shows the current entity structure and the proposed additions. Each field is annotated with its presence — M = MPS, C = Console, M+C = both — and new fields are marked with <<new>>.

erDiagram
    DEVICES {
        uuid guid PK "M+C"
        string hostname "M+C"
        string dnsSuffix "M+C"
        boolean connectionStatus "M+C"
        string mpsusername "M+C"
        string mpsInstance "M+C"
        string tenantId "M+C"
        string friendlyName "M+C"
        string tags "M+C"
        timestamp lastConnected "M+C"
        timestamp lastSeen "M+C"
        timestamp lastDisconnected "M+C"
        json deviceInfo "M+C"
        string username "C"
        string password "C"
        string mpspassword "C"
        string mebxpassword "C"
        boolean useTLS "C"
        boolean allowSelfSigned "C"
        string certHash "C"
    }

    DEVICE_INFO_AMT {
        string fwVersion "M+C"
        string fwBuild "M+C"
        string fwSku "M+C"
        string currentMode "M+C"
        string features "M+C"
        string ipAddress "M+C"
        timestamp lastUpdated "M+C"
        string tlsMode "M+C <<new>>"
        json upid "M+C <<new>>"
        boolean amtEnabledInBIOS "M+C <<new>>"
        string meInterfaceVersion "M+C <<new>>"
        boolean dhcpEnabled "M+C <<new>>"
        string[] certHashes "M+C <<new>>"
        string lmsVersion "C <<new>>"
        boolean lmsInstalled "C <<new>>"
        string osName "C <<new>>"
        string osVersion "C <<new>>"
        string osDistro "C <<new>>"
        string cpuModel "C <<new>>"
        string osIpAddress "C <<new>>"
        int ethernetAdapterCount "C <<new>>"
        boolean monitorConnected "C <<new>>"
        boolean ieee8021xEnabled "C <<new>>"
        timestamp lastDiscovered "C <<new>>"
    }

    DEVICES ||--|| DEVICE_INFO_AMT : "deviceInfo (JSON)"
Loading

New Flat Extensions to deviceInfo JSON

These fields are added as flat top-level keys within the existing deviceInfo JSON blob, consistent with all existing keys (fwVersion, fwBuild, etc.) and fully backward-compatible. The write path is unchanged (RPS activation + rpc amtinfo --sync). rpc amtinfo already collects AMT data and falls back to OS-only data when AMT is unavailable, so OS/platform fields can be populated even without AMT connectivity.

Field Type Example Feature Collection Method Presence
tlsMode string "TLS 1.2" Discovery AMT_TLSProtocolEndpointCollection via WSMAN (/amt/tls/:guid) M + C
upid json {"oemPlatformIdType":"Not Set (0)","oemId":"","csmeId":"4A45A39C5ED94620…82510000"} Discovery UPID.MarshalJSON() in rpc-go pkg/upid — already in InfoResult; keys: oemPlatformIdType, oemId, csmeId M + C
amtEnabledInBIOS boolean true Discovery AMT_BootSettingData via WSMAN or rpc-go amtinfo M + C
meInterfaceVersion string "16.1.25.2124" Discovery CIM_SoftwareIdentity (ME version) — via GET /amt/version/:guid in Console M + C
dhcpEnabled boolean true Discovery AMT_EthernetPortSettings.DHCPEnabled — already in GET /amt/networkSettings/:guid M + C
certHashes string[] ["a1b2c3…"] Discovery Sent in rpc-go activation payload (MessagePayload.CertificateHashes) — already collected, not persisted M + C

Key: M = MPS · C = Console · M + C = both

OS and Platform Fields (flat keys, Console only)

These fields describe the OS environment and platform state rather than AMT configuration. They are stored as flat top-level keys within the existing deviceInfo JSON column (Console only) — no new column or sub-object is introduced.

Key: for Presence M = MPS · C = Console · M + C = both

Field Type Example Feature Collection Method Presence
lmsVersion string "2410.5.0.0" Discovery, LMS Orchestration rpc amtinfo --sync (OS collection) — OS package query or LMS API C
lmsInstalled boolean true Discovery, LMS Orchestration rpc amtinfo --sync (OS collection) — check process/service presence; partial write at registration (#1246), enriched at discovery scan C
osName string "linux", "windows" Discovery rpc amtinfo --sync (OS collection) — runtime.GOOS C
osVersion string "6.8.0-51-generic", "10.0.26100" Discovery rpc amtinfo --sync (OS collection) — OS version API C
osDistro string "Ubuntu 24.04 LTS", "" (Windows) Discovery rpc amtinfo --sync (OS collection) — Linux: /etc/os-release; Windows: WMI C
cpuModel string "Intel(R) Core(TM) Ultra 7 165H" Discovery rpc amtinfo --sync (OS collection) — Linux: /proc/cpuinfo; Windows: WMI Win32_Processor C
osIpAddress string "10.49.76.163" Discovery rpc amtinfo --sync (OS collection) — net.InterfaceAddrs() C
ethernetAdapterCount int 2 Discovery rpc amtinfo --sync (OS collection) — net.Interfaces() C
monitorConnected boolean true Discovery rpc amtinfo --sync (OS collection) — Windows: EnumDisplayMonitors; Linux: EDID/DRM check C
ieee8021xEnabled boolean false Discovery rpc amtinfo --sync (OS collection) — Linux: nmcli/wpa_supplicant config; Windows: WMI Win32_NetworkAdapterConfiguration C
lastDiscovered timestamp "2026-05-14T10:23:00Z" Discovery (NFR) Set by rpc amtinfo --sync on each report cycle C

LMS Availability — Storage Placement Decision

rpc-go#1246 explicitly calls for storing LMS presence in the "device information table". All fields in deviceInfo are flat, so lmsInstalled is simply added as a flat key alongside the existing AMT fields.

Decision: deviceInfo.lmsInstalled (flat)

All LMS state is stored as flat keys in deviceInfo. Concretely:

  • The boolean from rpc-go#1246 maps to deviceInfo.lmsInstalled — no new column or sub-object is needed.
  • The Console registration handler performs a partial JSON merge into deviceInfo, writing only { "lmsInstalled": <value> } when the device registers, leaving all other new fields absent until a discovery scan runs.
  • A subsequent discovery scan enriches the same blob with lmsVersion, osName, and the remaining platform fields.

This stores all LMS state in one place, requires no new column (and therefore no new DTO, no new Repository method, and no duplicate SQL + Mongo implementations), and ensures LMS presence is captured immediately at registration even for devices that never undergo a full discovery scan.

API Impact Analysis

This section maps the existing API surface against the three features, identifying which endpoints need modification and which new endpoints must be introduced.

Registration and Deregistration Flows

rpc-go already supports two mechanisms for managing a device's presence in Console. The three new features extend both paths.

Path A — Local activation (direct to Console as part of next branch):

rpc activate --local --profile <profile.yaml> \
             --auth-endpoint <console>/api/v1/authorize \
             --auth-username <user> --auth-password <pass>
  1. Authenticate with Console: POST /api/v1/authorize → JWT token
  2. Register device in Console before AMT orchestration: POST /api/v1/devices with a DevicePayload struct carrying GUID, hostname, AMT/MEBx/MPS credentials, and TLS configuration flags
  3. Run orchestrator (CCM/ACM activation, optional CIRA configuration)
  4. If CIRA configuration fails: clear MPS password via PATCH /api/v1/devices { "guid": "...", "mpspassword": "" }

Path A — Local deactivation (direct to Console as part of next branch):

rpc deactivate --local --auth-endpoint <console>/api/v1/authorize \
               --auth-username <user> --auth-password <pass>
  1. Resolve device GUID from AMT (before unprovisioning — AMT is unavailable afterwards)
  2. Locally unprovision AMT
  3. Delete device from Console: DELETE /api/v1/devices/{guid}

Path B — Remote activation via RPS (unchanged):

rpc activate -u wss://rps.example.com --profile <profile>

rpc-go sends a MessagePayload to RPS over WebSocket. This path does not call POST /api/v1/devices directly from rpc-go.

The new LMS detection (rpc-go#1246) must be propagated through both paths, and Console's registration handler must persist the flag into deviceInfo.lmsInstalled.

API Change Summary

# Endpoint Service Type Feature
1 POST /api/v1/devices Console only Modify — accept isLMSAvailable; write to deviceInfo.lmsInstalled LMS Orchestration
2 GET /api/v1/devices[/:guid] Console only Modify — return all deviceInfo fields including new flat keys Discovery, SKU/Mode UI
3 GET /api/v1/devices Console only Modify — add filter/sort query params Discovery
4 GET /api/v1/devices/export Console only New Discovery
5 PATCH /api/v1/devices Console + MPS Modify — extend deviceInfo with new flat fields; fix Console wiring — payload currently received but dropped; returns 404 for unknown GUIDs (no server-side upsert — agent handles 404 with a POST fallback) Discovery, SKU/Mode UI, LMS Orchestration
6 rpc amtinfo --sync payload rpc-go Modify — extend syncDeviceInfo with new flat fields; rpc amtinfo already falls back to OS-only when AMT is unavailable; add 404→POST fallback in SyncDeviceInfo: on 404 from PATCH, call POST /api/v1/devices to create a minimal connectionStatus=discovered record, then re-issue the PATCH Discovery
7 rpc-go registration payloads rpc-go Modify — add isLMSAvailable (Path A DevicePayload) + lmsInstalled (Path B MessagePayload) LMS Orchestration

Required API Changes

1. POST /api/v1/devices — Accept isLMSAvailable in registration body (Console — Modify)

Feature: LMS Orchestration (rpc-go#1246)

Change: The existing device registration endpoint must accept a new isLMSAvailable boolean field in the request body and persist it as the flat key deviceInfo.lmsInstalled (a partial JSON merge into deviceInfo; all other new fields remain absent until a discovery scan runs).

This is a Console-side change that receives the value sent by rpc-go Path A (local activation). The DevicePayload struct in rpc-go internal/device/api.go must be extended with the field:

IsLMSAvailable bool `json:"isLMSAvailable"`
JSON field Maps to Type Write event
isLMSAvailable deviceInfo.lmsInstalled boolean Device registration via Path A

Console handler must read isLMSAvailable from the POST body and perform a partial JSON merge into the deviceInfo column: { "lmsInstalled": <value> }. No new column is created; the existing deviceInfo JSON blob is updated in place. No equivalent change is needed in MPS.


2. GET /api/v1/devices and GET /api/v1/devices/:guid — Return all deviceInfo fields (Console — Modify)

Feature: Device Discovery, SKU/Mode UI

Change: Ensure all deviceInfo fields are returned in the response DTO, including all new flat keys. New fields absent from a given device (not yet collected) should be omitted (omitempty).

MPS's equivalent GET /api/v1/devices endpoints return only the AMT fields they know about — OS/platform fields are Console-only.


3. GET /api/v1/devices — Add filtering and sorting query parameters (Console — Modify)

Feature: Device Discovery Change: The discovery dashboard requires server-side filtering and sorting on the new fields. Extend the existing GET /api/v1/devices to support:

Parameter Example Purpose
filter[skuType] vPro Filter by decoded SKU label
filter[currentMode] ACM Filter by control mode
filter[lmsInstalled] true Filter by LMS presence
filter[osName] Windows Filter by OS
sort hostname, fwVersion Sort column
order asc / desc Sort direction

These map to new SQL query clauses against the deviceInfo JSON column (using JSON path operators for the new flat keys).


4. GET /api/v1/devices/export — New export endpoint (Console — New)

Feature: Device Discovery (NFR) Change: Add a new endpoint that returns all discovery data as a downloadable file (CSV or JSON).

Method Path Description
GET /api/v1/devices/export Returns all devices with all deviceInfo fields as CSV/JSON

Query parameters: format=csv|json, same filter parameters as the list endpoint.


5. PATCH /api/v1/devices — Extend deviceInfo with new flat fields + fix Console wiring (Console — Modify)

Feature: Discovery, SKU/Mode UI, LMS Orchestration

Change: The request body's deviceInfo object must accept all new flat keys. The existing PATCH handler returns 404 for unknown GUIDs — this is correct REST behaviour and is not changed. Unmanaged devices are handled client-side in rpc amtinfo --sync (see §6 below).

Scenario Handler behaviour
GUID matches an existing device record Merge the deviceInfo JSON fields into the existing row (existing behaviour, just extended to persist the payload)
GUID is unknown Return 404 — agent handles this by calling POST /api/v1/devices first, then re-issuing the PATCH

Required wiring fix: Console currently receives the deviceInfo payload from rpc amtinfo --sync but drops it without persistingDeviceInfo is commented out in dtoToEntity/entityToDTO and "deviceinfo" is absent from deviceFieldSetters. The PATCH handler must be updated to deserialise the payload and merge it into the deviceInfo column. Until this is fixed, no new fields sent by rpc amtinfo --sync will be stored.

rpc amtinfo already falls back to OS-only data collection when AMT is unavailable on a device, so OS/platform fields can be populated even for non-activated devices.

AMT/ME fields to add to the accepted deviceInfo payload:

Field Type Populated By
tlsMode string rpc amtinfo --sync (reads AMT_TLSProtocolEndpointCollection)
upid json rpc amtinfo --syncUPID.MarshalJSON() emits {oemPlatformIdType, oemId, csmeId}; stored as a nested JSON object within deviceInfo
amtEnabledInBIOS boolean rpc amtinfo --sync (reads AMT_BootSettingData)
meInterfaceVersion string rpc amtinfo --sync (reads CIM_SoftwareIdentity)
dhcpEnabled boolean rpc amtinfo --sync (reads AMT_EthernetPortSettings.DHCPEnabled)
certHashes string[] RPS activation payload (MessagePayload.CertificateHashes)

Console must deserialise these into the DeviceInfo struct and merge them into the deviceInfo JSON column. No schema migration is required — deviceInfo is already a TEXT/JSON column.

OS/platform fields to add to the accepted deviceInfo payload:

Field Type Example Populated By
lmsInstalled boolean true rpc amtinfo --sync + registration (rpc-go#1246)
lmsVersion string "2410.5.0.0" Discovery scan
osName string "linux", "windows" rpc amtinfo --sync (OS fallback)
osVersion string "6.8.0-51-generic", "10.0.26100" rpc amtinfo --sync (OS fallback)
osDistro string "Ubuntu 24.04 LTS", "" (Windows) rpc amtinfo --sync (OS fallback)
cpuModel string "Intel(R) Core(TM) Ultra 7 165H" Discovery scan
osIpAddress string "10.49.76.163" rpc amtinfo --sync (OS fallback)
ethernetAdapterCount int 2 Discovery scan
monitorConnected boolean true Discovery scan
ieee8021xEnabled boolean false Discovery scan
lastDiscovered timestamp "2026-05-14T10:23:00Z" Discovery scan

6. rpc-go — Extend amtinfo --sync payload (rpc-go — Modify)

Feature: Discovery, LMS Orchestration

Change: Extend the syncDeviceInfo struct to include all new flat fields: the AMT/ME fields (tlsMode, upid, amtEnabledInBIOS, meInterfaceVersion, dhcpEnabled, certHashes) and the OS/platform fields. rpc amtinfo already falls back to OS-only data collection when AMT is unavailable on a device — the extended sync payload can therefore carry OS/platform fields even on unactivated devices. The SyncDeviceInfo function collects and includes all fields in the PATCH body as flat keys within the deviceInfo object.

Note: Console currently drops the deviceInfo payload from rpc amtinfo --sync without persisting it (see item #5). The Console PATCH wiring fix is a prerequisite for any extended syncDeviceInfo fields to be stored.


7. rpc-go — Registration payloads — Add LMS flag (rpc-go — Modify)

Feature: LMS Orchestration (rpc-go#1246)

Change: rpc-go detects LMS presence via TCP probe to localhost:16992 (or :16993 for AMT 19+ with local TLS) before sending any registration payload. The LMS flag must be added to both registration paths:

Path A — Local activation (DevicePayload → Console): Add IsLMSAvailable bool json:"isLMSAvailable" to the DevicePayload struct in internal/device/api.go. Console receives it via POST /api/v1/devices and stores it as deviceInfo.lmsInstalled (see item #1).

Path B — Remote activation (MessagePayload → RPS): Extend the MessagePayload struct in internal/rps/message.go to include LmsInstalled bool json:"lmsInstalled". RPS receives this over WebSocket during activation, then POSTs the device record to ${mps_server}/api/v1/devices — in legacy deployments this is MPS; in Console v3 deployments mps_server is reconfigured to point at Console, so the write goes directly to Console. RPS does not need code changes for the routing; only the payload struct needs extending.

Discovery Procedure Analysis

1. Execution Model and Platform Scheduling

Discovery data collection is a one-shot operationrpc amtinfo --sync runs, collects, reports, and exits. Scheduling, periodic cadence, and reboot persistence are delegated to the OS platform scheduler. No long-running daemon or service harness is required.

Platform Recommended Mechanism Notes
Windows Task Scheduler (XML task via schtasks) Runs as SYSTEM; triggers at system startup + repeating interval; survives reboots; no SCM service registration required
Linux systemd timer (.timer + Type=oneshot .service unit pair) Standard on Ubuntu 18.04+; OnBootSec + OnUnitActiveSec for boot + interval; integrates with journald for log capture
Linux (fallback) cron Adequate for basic periodic execution; add @reboot entry for boot-time start

A Windows SCM service or a continuous Type=notify systemd unit would both require an in-process sleep loop and a service harness library (kardianos/service) solely to replicate what the OS scheduler already provides natively. Task Scheduler and systemd timers also produce cleaner failure logs (each run is a distinct journal / Event Log entry) and allow the binary to be updated without a service stop/start cycle.


2. Discovery Agent — Approaches

Three approaches are feasible. All produce or reuse a Go binary consistent with rpc-go.

Approach A — Standalone rpc-discovery binary (separate new repository)

A new dedicated binary (rpc-discovery) published from a new repository, running as a long-lived service and shelling out to the rpc binary for AMT data collection.

rpc-discovery (new binary, new repo)
  ├── service harness (kardianos/service)
  ├── in-process scheduler (sleep loop)
  ├── AMT collector   → subprocess: rpc amtinfo --all --json
  └── Console reporter → PATCH <console>/api/v1/devices
Pros Cons
Independent versioning from rpc-go New repository duplicates rpc-go's existing CI, cross-compile matrix, GitHub Releases pipeline, SECURITY.md, CONTRIBUTING.md, and governance setup
Clean separation of concerns Two binaries must be co-deployed and kept in version-sync on every device
JSON output of rpc amtinfo --json becomes a soft API between two binaries — silent breakage risk on rpc-go output changes
Subprocess invocation adds process-spawn overhead per scan cycle
The OS scheduler already handles scheduling — the service harness adds complexity for no gain

Not recommended. rpc-go already cross-compiles for all target platforms, has GitHub Actions CI, and ships via GitHub Releases. A new repository duplicates all of that plus governance overhead. Extending rpc amtinfo --sync directly (Approach C) eliminates the subprocess model and the second artifact entirely.

Approach B — Long-running daemon (rpc agent subcommand)

Add an agent subcommand to the existing rpc binary. When invoked as rpc agent start, the process registers as a Windows SCM service or systemd unit and runs continuously, waking on a configured interval.

rpc agent start  →  installs and starts the persistent service
rpc agent stop   →  stops the service
rpc agent status →  reports current state
Pros Cons
Single binary to distribute — no new artifact A persistent process requires an in-process sleep loop and service signal handling (SCM SERVICE_CONTROL_STOP, etc.) for the sole purpose of scheduling a periodic action that the OS scheduler already provides natively
Self-contained — no OS scheduler configuration required Updating the binary requires stopping and restarting the service
Harder to inspect and control via standard IT tooling compared to a Task Scheduler task or systemd timer

Note: the CLI-vs-DLL concern raised in earlier drafts does not apply. The rpc CLI binary and the c-shared library are already separate build artifacts from separate source entry points; an agent subcommand in the CLI binary has no impact on the DLL surface.

Not selected for v1. The OS scheduler provides all required capabilities (boot-time start, periodic interval, SYSTEM account, restart on failure, log capture) with zero in-process logic. A persistent daemon can be reconsidered if requirements emerge that the OS scheduler cannot satisfy (e.g. real-time streaming, sub-minute intervals, or complex retry queuing).

Approach C — Extend rpc amtinfo --sync + OS Scheduler (Recommended)

Extend the existing rpc amtinfo --sync command in rpc-go to also collect OS/platform fields (LMS probe, OS details, CPU, adapters, monitor detection). The binary runs once and exits; the OS scheduler provides the periodic cadence.

rpc amtinfo --sync --url <console>/api/v1/devices
  ├── AMT collection  (existing: WSMAN via MEI/LME or network)
  ├── OS collection   (new: LMS probe, OS name/version, CPU, adapters, monitor)
  ├── PATCH /api/v1/devices  (update existing device — returns 404 if unknown)
  └── POST  /api/v1/devices  (on 404 of PATH: create minimal "discovered" record, then re-PATCH)
Pros Cons
No new binary, no new repository, no new CI pipeline rpc amtinfo --sync gains OS-collection responsibilities beyond its current AMT-only scope
Leverages existing rpc-go cross-compile matrix (Windows / Linux / macOS, amd64 / arm64) and release pipeline OS-level collection (LMS probe, monitor detection) requires platform-specific Go code in rpc-go
Single binary already deployed on managed devices today Non-vPro devices have no GUID — requires a fallback identification strategy (see Open Design Questions)
OS scheduler handles retry, reboot persistence, and interval with zero in-process logic
No service harness dependency — kardianos/service not required
Binary can be updated by replacing the file; no service stop/start cycle needed
CLI and c-shared builds are already separate artifacts — no build conflict

rpc amtinfo already falls back to OS-only data collection when AMT is unavailable, so the same binary works on non-vPro and pre-activation devices.

Recommendation

Approach C — extend rpc amtinfo --sync + OS scheduler. No new binary. No service harness. rpc-go already provides CI, cross-compile, governance, and a release pipeline. The OS scheduler provides native scheduling, reboot persistence, and failure logging. See Open Design Questions for unresolved questions that must be answered before implementation.


3. Discovery Procedures Setup

No new Console endpoints are needed for agent distribution. Operators obtain the rpc binary from the existing rpc-go GitHub Releases channel and register the OS scheduler task directly, embedding the Console URL and a scoped JWT as command arguments or environment variables.

Bootstrap flow:

sequenceDiagram
    actor Operator
    participant GHRelease as GitHub Releases (rpc-go)
    participant Device as Target Device
    participant Console
    participant Scheduler as OS Scheduler

    Operator->>GHRelease: Download rpc binary for platform
    GHRelease-->>Operator: rpc binary (application/octet-stream)

    Note over Operator: Generate scoped JWT via POST /api/v1/authorize
    Operator->>Console: POST /api/v1/authorize
    Console-->>Operator: Bearer JWT

    Operator->>Device: Deploy rpc binary

    Note over Device: Register OS scheduler task with Console URL + JWT embedded

    loop Every scheduled interval (default: 1 hour)
        Scheduler->>Device: rpc amtinfo --sync --url <console>/api/v1/devices --token <JWT>
        Device-->>Console: PATCH /api/v1/devices (Bearer JWT)
        alt Device exists
        Console-->>Device: 200 OK
        else Device unknown (404)
        Device->>Console: POST /api/v1/devices (minimal discovered record)
        Console-->>Device: 201 Created
        Device->>Console: PATCH /api/v1/devices (deviceInfo payload)
        Console-->>Device: 200 OK
        end
        Device-->>Scheduler: exit 0
    end
Loading

Windows — Task Scheduler (runs as SYSTEM with highest privileges):

Save the following as rpc-amtinfo-sync.xml, then import it with:

schtasks /create /xml rpc-amtinfo-sync.xml /tn "DMT\rpc amtinfo sync"
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <Triggers>
    <BootTrigger>
      <Delay>PT5M</Delay>
      <Enabled>true</Enabled>
    </BootTrigger>
    <TimeTrigger>
      <Repetition>
        <Interval>PT1H</Interval>
        <StopAtDurationEnd>false</StopAtDurationEnd>
      </Repetition>
      <StartBoundary>2026-01-01T00:00:00</StartBoundary>
      <Enabled>true</Enabled>
    </TimeTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>S-1-5-18</UserId>  <!-- SYSTEM account -->
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <ExecutionTimeLimit>PT10M</ExecutionTimeLimit>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
  </Settings>
  <Actions>
    <Exec>
      <Command>C:\Program Files\rpc\rpc.exe</Command>
      <Arguments>amtinfo --sync --url https://console.example.com/api/v1/devices --token &lt;scoped-JWT&gt;</Arguments>
    </Exec>
  </Actions>
</Task>

Why SYSTEM? The MEI/HECI driver on Windows restricts device access to elevated processes. Running as SYSTEM (or a member of the local Administrators group) is required to open the Intel MEI device handle that rpc uses to query the AMT UUID and ME firmware version.

Linux — systemd timer (runs as root):

# /etc/systemd/system/rpc-amtinfo-sync.service
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/rpc amtinfo --sync --url https://console.example.com/api/v1/devices --token <scoped-JWT>

# /etc/systemd/system/rpc-amtinfo-sync.timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
[Install]
WantedBy=timers.target

Why root? /dev/mei0 is owned by root:mei with mode 0660. The rpc binary must either run as root or the service user must be a member of the mei group. Running as root is the most portable choice across distributions; operators who prefer least-privilege can set User=rpc-agent and add that user to the mei group instead.

The reporting interval is set in the scheduler task itself, keeping it adjustable via standard IT tooling (GPO, Ansible, MDM) with no Console API involvement.

Security considerations:

  • The JWT should be scoped to the discovery:write permission only — it cannot manage devices or read credentials.
  • Console should support token revocation per device or per batch to allow decommissioning agents without rotating the global credential.
  • For air-gapped or no-internet environments, the binary and a pre-populated scheduler task can be distributed via standard software deployment tooling (SCCM, Ansible, GPO, MDM).

Open Design Questions

All questions resolved. Decisions are recorded in the ADR § 3.

Non-vPro Device Identification

Resolved: use the SMBIOS System UUID as fallback when HECI is unavailable.

How rpc-go currently reads the UUID: rpc amtinfo calls pthi.Command.GetUUID(), which sends a GET_UUID_REQUEST over the HECI interface (/dev/mei0 on Linux, Intel MEI kernel driver on Windows) directly to the ME firmware. The ME firmware returns the 16-byte SMBIOS System UUID. This path is gated on heciAvailable — if the MEI driver is not loaded or the device has no AMT/ME, the UUID field is left empty in InfoResult and SyncDeviceInfo sends an empty GUID.

For non-vPro or pre-AMT devices (HECI unavailable), a SMBIOS fallback is needed. The SMBIOS System UUID is the same value the ME returns over HECI — both ultimately read from the BIOS DMI table — so the keys will match if the device is later activated.

Go package for the SMBIOS fallback: github.com/digitalocean/go-smbios reads the SMBIOS tables in pure Go on both platforms with no subprocess or admin rights:

Platform Underlying mechanism
Linux Reads /sys/firmware/dmi/tables/ (kernel-exported, no root required on Linux 4.0+)
Windows Calls GetSystemFirmwareTable("RSMB", ...) Win32 API (available to unprivileged processes)

Note: github.com/digitalocean/go-smbios is not currently in rpc-go's go.mod. Adding it is a new dependency for the non-vPro fallback path.

Proposed fallback logic in GetAMTInfo:

// Existing path — HECI/PTHI:
if s.heciAvailable {
    uuid, err := s.amtCommand.GetUUID()
    // ...
    result.UUID = uuid
}

// New fallback — SMBIOS (non-vPro / HECI unavailable):
if result.UUID == "" {
    uuid, err := readSMBIOSSystemUUID() // new helper, uses go-smbios
    if err != nil {
        log.Warn("SMBIOS UUID not available, falling back to hostname: ", err)
        result.UUID = "" // hostname fallback handled in SyncDeviceInfo
    } else {
        result.UUID = uuid
    }
}

The SMBIOS Type 1 structure at byte offset 0x08 holds the 16-byte UUID, formatted as a standard RFC 4122 string.

Why this is the right key:

  • Same value as AMT GUID. Both the PTHI path and the SMBIOS path return the same OEM-assigned UUID from the BIOS DMI table. If HECI is later enabled or the device is activated, the record matches automatically.
  • Globally unique. Assigned by the OEM; stable across reboots and OS reinstalls.
  • No composite-key complexity. Console's upsert key remains a single UUID field regardless of how it was read.

If the SMBIOS table is not accessible (e.g. some hypervisors with no DMI passthrough), SyncDeviceInfo falls back to hostname as a last resort and logs a warning — uniqueness is not guaranteed in that case.

No Console API change is required. The existing GUID field in the PATCH /api/v1/devices payload is populated with the SMBIOS UUID for non-AMT devices; Console does not need to distinguish the source.

Run-Once vs Scheduled Execution Semantics

Resolved: rpc-go ships sample OS scheduler config files at a 1-hour default interval.

rpc-go releases include a sample .task.xml (Windows Task Scheduler) and .timer + .service unit pair (Linux) defaulting to a 1-hour repeat. Operators override the interval in their own tooling (GPO, Ansible, MDM) without touching the agent config file.

Console Unreachability

Resolved: exit non-zero and let the OS scheduler retry on the next cycle.

A one-cycle data gap is acceptable for v1. No local spool file — avoids local state management and stale-data edge cases. rpc amtinfo --sync logs the failure and exits with a non-zero code; the OS scheduler provides native retry semantics.

Offline / No-Console Mode

Resolved: not supported in v1. consoleURL is required.

The binary exits with a non-zero code if consoleURL is missing or unreachable. A future --output flag can add local JSON output without Console when needed — that is a v2 concern.

Clone this wiki locally