Skip to content

Host Facts

Facts are the agent’s inventory of the machine it runs on: what OS it is, what hardware it sits on, which volumes and network interfaces it has, what is installed on it, which services it runs (a docker daemon and its containers, a MySQL or SQL Server instance and its databases), and which NSClient++ is running on it. The core collects them from the loaded modules, keeps them as one document per host, and serves that document on /api/v2/facts, in the web UI and in nscp test.

Nothing is collected until you turn a fact set on. An inventory is data an operator did not necessarily agree to ship, so a fresh install reports an empty document and does no work for it.


Facts or tags?

Tags and facts both describe the host. They answer different questions, and facts do not replace tags.

Tags Facts
Shape flat key=value strings a document: sections, numbers, strings, lists of records
Question which group is this host in what is this host
Collected always, a handful per module only the sets you enable
Used for fleet group selectors (os_family = "linux") inventory, and deciding what to monitor
Sent to a fleet server whole, on every state report the hash on every report, the document when it changes

A fleet selector matches a tag whole, which is why os_family and arch are tags. “Which volumes does this host have” is a list of records, and a comma-joined tag such as drives=c:,d: is exactly the shape facts exist to get away from.


Enabling a fact set

A fact set is enabled in the module that produces it, next to that module’s other settings. The switch cannot exist without the module, so there is no way to enable a set and get nothing back because its module is not loaded.

[/modules]
CheckSystem = enabled
CheckDisk = enabled

; CheckSystem on Windows. On Linux: [/settings/system/unix/facts]
[/settings/system/windows/facts]
os = true
hardware = true
network.interfaces = true
software.installed = true
services.installed = true

; CheckTaskSched, on Windows.
[/settings/task schedule/facts]
tasks.scheduled = true

[/settings/disk/facts]
storage.volumes = true

; CheckHyperV, on a Hyper-V host.
[/settings/hyperv/facts]
hyperv.vms = true

; A service the host runs, in the module that already checks it.
[/settings/docker/facts]
docker = true
docker.containers = true
docker.images = true

[/settings/mysql/facts]
mysql = true
mysql.databases = true

[/settings/mssql/facts]
mssql = true
mssql.databases = true

; The one set the core produces itself.
[/settings/facts]
agent = true

Every switch is a bool defaulting to false, so each one is a separate key that a fleet bundle can turn on without overwriting another bundle’s choices.

Set Produced by Key, in section Cost
agent the core agent, [/settings/facts] none
os CheckSystem os, [/settings/system/windows/facts] or [/settings/system/unix/facts] none: read once at start
hardware CheckSystem hardware, same section none: read once at start
network.interfaces CheckSystem network.interfaces, same section low: read every round, no WMI, nothing forked
software.installed CheckSystem software.installed, same section the highest here: every round, a walk of the registry’s Uninstall hives or one forked package-manager query
services.installed CheckSystem services.installed, same section every round except startup: Windows SCM enumeration or bounded systemctl queries on Linux
tasks.scheduled CheckTaskSched tasks.scheduled, [/settings/task schedule/facts] every round except startup: recursive local Task Scheduler enumeration, including hidden tasks
storage.volumes CheckDisk storage.volumes, [/settings/disk/facts] low: the enumeration check_drivesize drive=* does
hyperv.vms CheckHyperV hyperv.vms, [/settings/hyperv/facts] moderate: every round (never at startup), the seven WMI queries check_hyperv_vms runs, which grow with every VM and checkpoint and can stall while the Hyper-V management provider starts
docker CheckDocker docker, [/settings/docker/facts] low: one GET /info on the daemon socket, every round but startup
docker.containers CheckDocker docker.containers, same section low: the listing check_docker all=true does, every round but startup
docker.images CheckDocker docker.images, same section low: one GET /images/json, every round but startup
mysql CheckMySQL mysql, [/settings/mysql/facts] low: one connection and one query, every round but startup
mysql.databases CheckMySQL mysql.databases, same section low: one query of information_schema.SCHEMATA, on the same connection
mssql CheckMSSQL mssql, [/settings/mssql/facts] low: one connection and one SERVERPROPERTY query, every round but startup
mssql.databases CheckMSSQL mssql.databases, same section low: one query of sys.databases, on the same connection

The full description of each switch is in the module’s settings reference. Turning a set off takes effect on the next settings reload: the module stops returning the set, and the core drops it from the document. There is nothing to clean up.


What each set contains

A field that could not be determined is omitted, never written as an empty string or zero. A consumer reads an absent key as unknown.

agent

Field Example Meaning
version 0.12.6 the NSClient++ version
modules ["CheckDisk", "CheckSystem"] the loaded modules, sorted, each once
enrolled true whether this host is enrolled with a fleet server: yes or no, never which server or with which identity

os and hardware

Set Fields
os family, name, version, arch, virtualization, domain
hardware manufacturer, model, cpu_cores, memory_gb

Both are read once, when the module starts, because none of it can change without a reboot. A manual refresh reads them again.

network.interfaces

One record per network interface. The loopback interface is left out.

Field Example Meaning
id eth0, Intel(R) Ethernet Connection I219-V the interface: the kernel name on Linux, the adapter description on Windows
display_name Ethernet the connection name (Windows only)
mac 00:1a:2b:3c:4d:5e the hardware address, lowercase and colon-separated on every platform
status up the RFC 2863 link state: up, down, dormant, lowerlayerdown, notpresent, testing, unknown
speed_bps 1000000000 the negotiated link speed in bits per second
addresses ["192.168.1.10", "fe80::1"] the IPv4 and IPv6 addresses, without an IPv6 zone suffix

There are no traffic counters. They change every second, which would make every round a change. check_network is where traffic is monitored. The Windows XP build reports no speed_bps.

storage.volumes

One record per volume that check_drivesize drive=* would report.

Field Example Meaning
id /, C:\ the mount point: the value check_drivesize calls drive
device /dev/sda1, \\?\Volume{…}\ the block device on Linux, the volume GUID path on Windows
filesystem ext4, NTFS the filesystem
type fixed the check_drivesize type: fixed, remote, removable, cdrom, ramdisk, unknown
label Data the filesystem label
size_bytes 107374182400 the total size

There is no free space, for the same reason there are no traffic counters. A remote volume (NFS, SMB, a mapped drive) is listed without size_bytes. Asking a dead server for a size would stall the round until it times out.

software.installed

One record per installed program, from the same source check_installed_software reads: the registry’s Uninstall hives on Windows - the 64-bit and 32-bit machine views and every loaded per-user hive, never Win32_Product - and the host’s own package manager (dpkg, rpm or pacman) on unix.

Field Example Meaning
id Google Chrome, bash the name, with the version appended when the host has two installs of it
name Google Chrome what the platform calls it: the name of check_installed_software
version 129.0.6668.101 as the platform recorded it; never parsed or compared as a number
publisher Google LLC the publisher (Windows), the maintainer or vendor (unix)
architecture x86_64, x86, noarch the os set’s vocabulary, plus noarch for a package that has none (rpm’s noarch, dpkg’s all, pacman’s any)
source registry, dpkg which database the record came from
scope machine machine or user, by the hive it was installed into. Windows only: a unix package manager installs for the machine
install_date 2026-09-09 a date, not a time, and only where the platform records one
size_bytes 5904384 the installed size, where it is recorded

What it leaves out is as deliberate as what it carries. Entries Windows hides from Programs and Features (SystemComponent) are not listed: they are the runtimes and bookkeeping keys a component left behind, and they roughly double the record count. One product installed into several user hives is one record, because the host has that software on it once. And nothing here says whether anything is running - that is a check, and it would change the document every round.

This is the one set with a size limit of its own. A Linux desktop has a few thousand packages, and the whole document has to fit [/settings/facts] max size; a set that would not fit is rejected whole, which would leave the host with no inventory at all. So the list stops at 2500 records, and the set then carries an error under errors saying how many were found.

services.installed

One record per local Windows service or systemd service on Linux, including stopped and disabled services. Linux enumerates both installed unit files and loaded units, so never-started services, templates and transient instances are included. Systemd aliases, including template aliases such as [email protected] pointing to [email protected], resolve to one record with the canonical name and the canonical unit’s startup mode.

Field Example Meaning
id Spooler, sshd the service name used by check_service; Linux strips .service
name sshd the same native service name
display_name OpenSSH server daemon Windows display name or systemd description, when available
start_type auto, disabled the platform’s native startup mode, as used by check_service

Windows startup modes include auto, delayed, auto_trigger, delayed_trigger, demand, disabled, boot and system. Linux retains systemd’s UnitFileState, such as enabled, disabled, static or masked; these are not interchangeable with Windows startup types. A template such as [email protected] has id worker@ and a startup mode, without a description. An installed unit with LoadState=error or bad-setting still appears with the metadata systemd can return; its configuration problem does not block the other services. Units reported as not-found, including dangling aliases, are skipped. Template aliases resolve to the canonical fragment even on systemd versions that do not report the alias unit-file state. Load state is not published as a fact. Unknown fields are omitted. Drivers and per-user systemd managers are outside this set. Linux hosts without a working systemd manager report an error. The service inventory is not yet supported on macOS; enabling it reports an explicit unsupported-platform error without affecting launchd checks.

There is no runtime state, PID, resource usage, service account or executable command line. Each systemctl query has a 30-second deadline and bounded output; service properties are fetched in batches rather than one process per service.

tasks.scheduled

One record per local Windows scheduled task, including disabled tasks and hidden tasks in every folder. This is independent of check_tasksched’s default filter, which excludes disabled tasks, and its default enumeration, which hides tasks marked hidden.

When CheckTaskSched is loaded with a custom module alias, enable tasks.scheduled under [/settings/<alias>/facts]. The default [/settings/task schedule/facts] section applies to an unaliased module.

Field Example Meaning
id \Maintenance\Backup the full registered task path, the uri of check_tasksched
name Backup the check’s title; two folders can contain this same name
folder \Maintenance the containing folder; \ for the root
enabled false whether the task is enabled
hidden true whether Task Scheduler marks the task hidden

On legacy Windows with only the Task Scheduler 1.0 API, the id is \ followed by the enumerated task name (including .job), the folder is \, and hidden is omitted because that API does not expose it. No action, arguments, account, XML definition, runtime state, last result or next-run timestamp is published. Enumeration uses the agent’s account and requires permission to enumerate every Task Scheduler folder and read each returned task. Use an account with that access (normally the agent’s LocalSystem service account); a non-elevated account may fail on protected folders. An access-denied error aborts the collection, reports the failing operation under errors.tasks, and retains the last successful inventory. Inaccessible folders are not silently skipped.

Both new sets are opt-in and capped at 2500 records, with a truncation error when more are found. They are claimed but not collected during startup, to avoid holding up service start on SCM/systemd/Task Scheduler calls. Collect immediately with facts refresh or POST /api/v2/facts/commands/refresh, or wait for the first scheduled round. A failed collection keeps the last successful set and reports the failure under errors. Disabling a set on reload removes it from the document.

hyperv.vms

One record per virtual machine on a Hyper-V host, from the same root\virtualization\v2 classes check_hyperv_vms reads.

Field Example Meaning
id web-01 the VM name: the value check_hyperv_vms calls vm, with the VM’s GUID appended when the host has two VMs of that name
name web-01 the VM name as Hyper-V shows it
vm_id 1e4f6e3b-0f7c-4a51-9c2e-6f8a0b1c2d3e the VM’s GUID
generation 2 the VM generation
version 9.0 the configuration version
vcpus 4 the configured virtual processors
memory_startup_bytes 2147483648 the configured startup memory
dynamic_memory true whether dynamic memory is on
memory_minimum_bytes 536870912 the dynamic memory floor (dynamic memory only)
memory_maximum_bytes 8589934592 the dynamic memory ceiling (dynamic memory only)
checkpoints 2 how many checkpoints (snapshots) the VM has
replication_mode primary the Hyper-V Replica role: none, primary, replica, test_replica, extended_replica

There is no power state, heartbeat, uptime, load or assigned memory: those change from one round to the next and are what check_hyperv_vms monitors. On a host without the Hyper-V role the set is enabled but cannot be collected, and says so under errors every round. The same goes for a stopped management service (vmms), and for an account that may not see the virtual machines: Hyper-V hides them from it without an error, so an empty list would claim the host has none. The set reports that under errors and the last list collected is kept.

The set is not read on the startup round. That round runs on the thread that starts the service, and the first query into the virtualization namespace starts the Hyper-V management provider when it is not already running, which can take long enough to hold up every producer after it. So at startup the set says so under errors, and it is collected on the first scheduled round (every [/settings/facts] interval), on a settings reload, or right away with facts refresh / POST /api/v2/facts/commands/refresh.

docker, docker.containers and docker.images

The docker daemon behind [/settings/docker] endpoint - the one check_docker talks to - what it holds, and what it has pulled. Three switches, one set: docker is the daemon record, and each list is enabled on its own, so a host can report its containers without the image list that goes with them.

The daemon record:

Field Example Meaning
version 27.3.1 the daemon version: the version keyword of check_docker_info
os Ubuntu 24.04.1 LTS the operating system the daemon reports
os_type linux linux or windows: the os set’s family vocabulary
architecture x86_64, arm64 the os set’s vocabulary, whatever the daemon’s own spelling
kernel_version 6.8.0-45-generic
storage_driver overlay2
cgroup_driver systemd
cgroup_version 2
cpus 8 the CPUs the daemon sees
memory_bytes 33547567104 the memory the daemon sees
swarm inactive the node’s swarm state: inactive, active, pending, error, locked

There are no container or image counts. They change every round, and check_docker_info reports them.

docker.containers: one record per container the daemon knows, stopped ones included, because an inventory lists what exists rather than what runs.

Field Example Meaning
id web the container’s names, comma separated: the value check_docker calls names
container_id aaa111… the daemon’s id: the id keyword of check_docker
image nginx:1.25 the image it was created from
image_id sha256:…
created 2026-09-19T14:03:11Z
ports ["0.0.0.0:8080->80/tcp", ":::8080->80/tcp", "443/tcp"] published and exposed ports, spelled as check_docker spells ports and sorted; a port published on both address families is one entry per family, since which addresses it is bound on is part of the record
compose_project shop the compose project the container belongs to, where compose started it
compose_service web the compose service, likewise

There is no state, status, health or address. Each changes under a running agent and belongs to check_docker. Nor are the container’s other labels carried: their keys (com.docker.compose.project) are not fact keys, and most of them are configuration. The two compose labels are read out by name because they say what a container is.

docker.images: one record per image the daemon holds.

Field Example Meaning
id sha256:… the image id: the one name of an image that does not move when it is retagged, so a consumer diffing by id sees a pull as a pull and not as a removal plus an addition
tags ["nginx:1.25", "nginx:latest"] every tag; a dangling image has none, never a <none>:<none>
created 2026-09-01T10:00:00Z when the image was built
size_bytes 187000000

Both lists are re-read every round, because containers are started and removed and images pulled while the agent runs. Everything is fetched before anything is stored: a round that read the daemon but could not list the containers reports an error against docker and keeps the set the core has, rather than replacing it with one that is missing a list.

Each list stops at 2500 records, for the reason software.installed does: the whole document has to fit [/settings/facts] max size, and a set that would not is rejected whole, daemon record included. A build host with more images than that reports the first 2500 and says under errors how many there were.

The set is not read on the startup round. That round runs on the thread that starts the service, and a daemon that is hung, or a socket that is there but not answering, would hold the service start for the full timeout. So at startup the set says so under errors, and it is collected on the first scheduled round (every [/settings/facts] interval), on a settings reload, or right away with facts refresh / POST /api/v2/facts/commands/refresh.

mysql and mysql.databases

The server [/settings/mysql] points at - the one check_mysql connects to by default - and the databases it holds. A facts round has no request to take credentials from, so it connects with the ones configured there (user and password, or the defaults file), and it needs nothing the health check does not already need.

The server record:

Field Example Meaning
flavor mariadb mysql, mariadb or percona: the flavor keyword of check_mysql
version 10.11.14-MariaDB-ubu2404 @@version, as recorded; never parsed or compared as a number
version_comment Ubuntu 24.04 @@version_comment
hostname db01 what the server calls the machine it runs on (@@hostname)
port 3306 the port the server listens on (@@port)
server_id 1 the replication identity (@@server_id)
character_set utf8mb4 the server default (@@character_set_server)
collation utf8mb4_general_ci the server default (@@collation_server)
os debian-linux-gnu, Win64 what the server was built for (@@version_compile_os)
architecture x86_64, arm64 the os set’s vocabulary, whatever the server’s own spelling

The record describes the server, not how the agent reaches it: hostname and port are what the server says about itself, never the configured target, and nothing from [/settings/mysql] appears in it. There is no uptime and no connection count. They change every round, and check_mysql reports them.

mysql.databases: one record per database (schema) the configured user may see, sorted by name. The system schemas (information_schema, mysql, performance_schema, sys) are listed like any other: they are databases the server has.

Field Example Meaning
id shop the schema name
character_set utf8mb4 the schema’s default character set
collation utf8mb4_unicode_ci the schema’s default collation

There is no size. Summing a schema’s tables is a query that changes its answer every round, and it belongs to check_mysql_query. The list stops at 2500 records, as software.installed does, and says under errors how many there were.

Like docker, the set is not read on the startup round: a server that is down, or a host that drops the packets, would hold the service start for the full connect timeout. At startup the set says so under errors, and it is collected on the first scheduled round, on a settings reload, or right away with a manual refresh.

mssql and mssql.databases

The SQL Server instance [/settings/mssql] points at - the one check_mssql connects to by default - and the databases it holds. It connects the way the checks do when a check passes no credentials of its own: Windows integrated authentication, or the configured user and password. Windows only, like the module.

The server record is read with SERVERPROPERTY() alone, which every version answers and which returns NULL for a property it does not have, so an old instance reports fewer fields rather than an error:

Field Example Meaning
server_name DB01\PROD the instance name: the server_name keyword of check_mssql
machine_name DB01 the computer, or the cluster network name
instance_name PROD the named instance; absent for the default instance
version 16.0.4135.4 the product version, as recorded; never parsed or compared as a number
product_level RTM, SP3
product_update_level CU15 the cumulative update, where the version records one
edition Standard Edition (64-bit)
engine_edition standard personal, standard, enterprise, express, azure_sql_database, azure_sql_managed_instance, … or the number for one this build does not name
collation SQL_Latin1_General_CP1_CI_AS the server collation
authentication mixed windows or mixed
clustered false whether the instance is a failover cluster instance
always_on true whether Always On availability groups are enabled (2012 and later)

There is no uptime. It changes every round, and check_mssql reports it.

mssql.databases: one record per database the login may see, system databases included, by the name check_mssql_databases uses.

Field Example Meaning
id shop the database name
recovery_model FULL SIMPLE, FULL or BULK_LOGGED, spelled as the check spells it
collation Latin1_General_100_CI_AS
compatibility_level 160
create_date 2026-03-02 a date, rendered by the server
read_only false

There is no state and no data or log size. Both move every round and belong to check_mssql_databases. The list stops at 2500 records, as software.installed does, and says under errors how many there were.

Like docker and mysql, the set is not read on the startup round: a stopped instance would hold the service start for the full login timeout. At startup the set says so under errors, and it is collected on the first scheduled round, on a settings reload, or right away with a manual refresh.

Record ids match check instance names

A list record’s id is the same string that the corresponding check uses to name the instance. storage.volumes[].id is the drive of check_drivesize. network.interfaces[].id is the name of check_network. services.installed[].id is the name of check_service. tasks.scheduled[].id is the full uri of check_tasksched (the legacy API uses a root path plus its task name). software.installed[].id is the name of check_installed_software - with the version appended in the one case where the host has two installs sharing a name, because an id has to be unique in its list. hyperv.vms[].id is the vm of check_hyperv_vms, with the GUID appended for the same reason when two VMs share a name. docker.containers[].id is the names of check_docker, mysql.databases[].id is the schema name a check_mysql_query would name in its FROM, and mssql.databases[].id is the name of check_mssql_databases. The ids are stable across rounds, so a consumer can diff two documents record by record.


When facts are collected

When What happens
Start one round, before the first fleet report
Every [/settings/facts] interval (default 1h) a round; a set whose values have not changed does not move the document’s revision
A settings reload a round, because a set may have been switched on or off
On demand facts refresh in nscp test, or POST /api/v2/facts/commands/refresh

Each set a module produces carries a gathered time: when its values were read off the machine, which for os and hardware is when the module started. The core builds agent on the round itself, so the round’s time is its age. A set that is enabled but could not be collected keeps its previous value, and the reason is reported under errors, so a transient failure never blanks the inventory.


Reading facts

nscp> facts
Revision: 4  Checked: 2026-09-24T13:19:40Z
Enabled: agent, network, os, storage
Gathered:
  network: 2026-09-24T13:19:40Z
  os: 2026-09-24T13:19:40Z
  storage: 2026-09-24T13:19:40Z

  agent:
    enrolled: false
    modules:
      - CheckDisk
      - CheckSystem
    version: 0.12.6
  network:
    interfaces:
      - eth0
          addresses:
            - 192.0.2.2
          mac: 02:fc:00:00:00:01
          status: up
  os:
    arch: x86_64
    family: linux
    name: Ubuntu 24.04.4 LTS
    version: 6.18.44
    virtualization: virtual
  storage:
    volumes:
      - /
          device: /dev/vda
          filesystem: ext4
          size_bytes: 270553174016
          type: fixed

facts <path> shows one subtree (facts storage.volumes), and facts refresh collects now. Over REST the same document is GET /api/v2/facts (see Facts), and the web UI shows it on the Facts page.


Facts and the fleet server

An agent enrolled with a fleet server sends its facts there too, but only when the server does not already have them. The document is up to a megabyte and changes rarely, so the agent sends its hash and lets the server say whether it needs the rest:

  • Every desired-state poll and every state report carries facts_hash, the SHA-256 of the document: a facts_hash= query parameter on the poll, a member of the state report. A host with nothing enabled sends the hash of the empty document, {}.
  • The server answers with the hash it holds, in an X-Facts-Hash response header (none, in any case, when it holds nothing). It is a header so that it works on the 304 a host that is in sync gets on nearly every poll. The agent reads it only on a 2xx or 304: an error page from the server or a proxy says nothing about what the server holds. The server sends it on its answer to an upload too, saying what it now holds, so the agent learns at once whether the document stuck.
  • The document is uploaded only on a miss: when the server’s answer differs from the agent’s hash, the agent sends it on its own call, POST /agent/v1/facts. A matching answer costs nothing more than the hash, a host with nothing enabled never uploads, and a server whose poll answers carry no X-Facts-Hash is one that does not do facts and is never sent the document - including one that sent it before and was downgraded since. (A state report answered without the header changes nothing: the poll is the call it is for.) A header the agent cannot read counts as no header, and is logged once.
  • A round is published whole. A round stores its sets one at a time; until it has finished, the hash on the poll and the document an upload sends are the previous round’s. A round therefore costs at most one upload, never one per set.
{
  "collected_at": "2026-09-25T10:00:00Z",
  "facts": { "os": { "family": "linux", "...": "..." } },
  "facts_hash": "<sha256 hex of the facts value>"
}

facts_hash is the digest of the facts value exactly as it appears in the body: compact JSON with every object’s keys sorted, so the server can check it without re-encoding anything.

Because the switches are ordinary INI, a fleet bundle turns inventory on for a whole group of hosts the same way it configures anything else; see Collect an inventory.

Uploads are paced so that a server in trouble is never sent the document on every poll:

  • A rejected upload (any error status but 413) is retried after a minute, then two, doubling up to once an hour. The wait belongs to that document: an inventory that changed in the meantime was never tried and goes at once.
  • A server in trouble - any error answer to the poll, the state report or the upload, a proxy’s error page included - is sent no document in that poll cycle. A Retry-After on a report or an upload holds every upload until it has passed. On a poll, the agent waits out a 429’s Retry-After before it calls again at all; any other failed poll - a 503 maintenance page included - is logged as a failure and backed off as usual, with its Retry-After as the shortest wait. A 429 or 503 on the upload itself skips the next poll cycle too. A rejection is forgotten as soon as the document gets through.
  • A document the server acknowledged and then reports missing - having reported holding it first - is sent again at once the first time, and the agent log says so at info level. One it has not confirmed yet is not sent again for a minute, so a store that is slow to land a write never costs a second copy of the document. Further re-sends of it wait a minute, then two, doubling up to once an hour, which is logged as an error when it gets there. Once the server has kept it for a whole wait, that resets, so a loss weeks later is repaired at once again.
  • A connection that fails outright costs nothing when it is the poll: the next poll that gets through tries again. When the polls get through and the upload alone is cut - a proxy that resets a large request, a timeout too short for a large document on a slow link - it is paced like a rejection.
  • A document the server refuses as too large (413) is not offered again until it changes, or for a day - its cap may be raised in the meantime.
  • A document the server does not end up holding as sent is not offered again until it changes, the hash the server reports changes for a reason other than an upload of the agent’s own (a fix on its side), or a day has passed, with an error in the agent log. The server has to hash the facts value exactly as it received it. When it answers the upload with a new, different hash, that is known at once. When its answers carry no hash, or only repeat what it held before the upload (an asynchronous store whose write has not landed yet), it takes three acknowledgements of the document with no confirmation between them, with the re-sends paced as above - one stale answer from a lagging server never gets there.

The size cap is enforced in one place, where the document is kept, so every reader - the web UI, REST and the fleet upload - sees the same document: the core refuses any set that would take the document past [/settings/facts] max size, and keeps the previous value of that set. max size is re-read on every settings reload. Lowering it under the document the core already holds drops the largest sets until the rest fits, logging each one; the next round offers them again and keeps whichever fit. Raising it takes effect on the next reload, with nothing else to change.


The document rules

The core checks every set against these rules before it accepts it. A set that breaks a rule is rejected whole, and the previous value of that set is kept.

  • Keys are snake_case ASCII, at most 64 characters. A key that carries a unit ends with it (size_bytes, speed_bps).
  • Values are strings, numbers or booleans. There is no null: an unknown value is left out.
  • Lists are lists of records, each with an id unique in its list and stable across rounds, or plain lists of strings (addresses, modules).
  • Size: depth at most 6, at most 5000 records in a list, and the whole document within [/settings/facts] max size (1 MiB by default).

What facts never contain

Facts describe the machine, not the configuration. They never contain settings, credentials, command lines or environment variables. That holds inside a set too: software.installed carries what is installed and which version of it, never the install path or the uninstall command line check_installed_software can also show; docker.containers carries the image a container runs, never its command line, its environment or its labels at large; and mysql and mssql say what the server reports about itself, never the target, login, driver or connection string the agent connected with. The loaded module list in agent is the one configuration-adjacent value, and it is opt-in like everything else.