Agent index (llms.txt) — fetch this first, then the page it names.

Inspector

The inspector is a cluster health check tool that SSHs into every node, collects subsystem data in parallel, runs deterministic checks, and optionally sends failures to an AI model for root-cause analysis.

Pipeline

Collect (parallel SSH) → Check (deterministic Go) → Report (table/JSON) → Analyze (optional AI)
  1. +Collect — SSH into every node in parallel, run diagnostic commands, parse results into structured data.
  2. +Check — Run pure Go check functions against the collected data. Each check produces a pass/fail/warn/skip result with a severity level.
  3. +Report — Print results as a table (default) or JSON. Failures sort first, grouped by subsystem.
  4. +Analyze — If --ai is enabled and there are failures or warnings, send them to an LLM via OpenRouter for root-cause analysis.

Quick Start

bash
# Inspect all subsystems on devnet
orama maint inspect --env devnet

# Inspect only RQLite
orama maint inspect --env devnet --subsystem rqlite

# JSON output
orama maint inspect --env devnet --format json

# With AI analysis
orama maint inspect --env devnet --ai

Usage

orama maint inspect [flags]
FlagDefaultDescription
--config(resolver)Read nodes from this file instead of resolving them
--env(required)Environment to inspect (devnet, testnet)
--subsystemallComma-separated subsystems to inspect
--formattableOutput format: table or json
--timeout30sTime allowed for collecting one node (plus 10s for the whole run)
--verbosefalsePrint collection progress
--output(none)Save results to a directory as markdown (e.g., ./results)
--aifalseEnable AI analysis of failures
--modelmoonshotai/kimi-k2.5OpenRouter model for AI analysis
--api-key$OPENROUTER_API_KEYOpenRouter API key

Subsystem Names

rqlite, olric, ipfs, dns, wireguard (alias: wg), system, network, namespace, tor, webrtc, global

Multiple subsystems can be combined: --subsystem rqlite,olric,dns

Subsystems

SubsystemWhat It Checks
rqliteRaft state, leader election, readyz, commit/applied gap, FSM pending, strong reads, debug vars (query errors, leader_not_found, snapshots), cross-node leader agreement, term consistency, applied index convergence, quorum, version match
olricService active, memberlist up, restart count, memory usage, log analysis (members marked failed: critical; suspicions: warning; flapping; errors), cross-node memberlist consistency
ipfsDaemon active, cluster active, swarm peer count, cluster peer count, cluster errors, repo usage %, swarm key present, bootstrap list empty, cross-node version consistency
dnsCoreDNS (orama-namespace-coredns@nameserver) active, ports 53/80/443, memory, restart count, log errors, Corefile exists, SOA/NS/wildcard/base-A resolution, TLS cert expiry. Caddy is orama-namespace-caddy@index on every node, not nameserver-only. The zone is read from the Corefile through sudo (the file is root:orama-coredns 0640), and the SOA/NS/wildcard/base-A resolution checks use dig: on a node without dig (apt install bind9-dnsutils) they are reported as skipped, not failed.
wireguardInterface up, service active, correct 10.0.0.x IP, listen port 51820, peer count vs expected, MTU 1420, config exists + permissions 600, peer handshakes (fresh/stale/never), peer traffic, catch-all route detection, cross-node peer count + MTU consistency
systemCore services (orama-node, orama-namespace-{olric,ipfs,ipfs-cluster,caddy,wireguard}@index; leftover unit names still accepted during rolling upgrade), nameserver orama-namespace-coredns@nameserver (leftover coredns accepted), failed systemd units, memory/disk/inode usage, load average, OOM kills, swap, UFW active, process user (orama), panic count, expected ports
networkInternet reachability, default route, WireGuard route, TCP connection count, TIME_WAIT count, TCP retransmission rate, WireGuard mesh ping (all peers)
namespacePer-namespace: RQLite up + raft state + readyz, Olric memberlist, Gateway HTTP health. Cross-namespace: all-healthy check, RQLite quorum per namespace. A namespace whose registry status (namespace_clusters.status, with its age from the registry's own clock, read in the same session) is provisioning or deprovisioning for less than 15 minutes is not judged (the registry takes over an abandoned provisioning after 11 minutes and a teardown after 12): its checks are skipped (ns.<name>.settled, ns.<name>.all_healthy), because its services come up and go away node by node. One in transition for 15 minutes or more is judged like any other, and ns.<name>.transition_stuck (high) fails. ready, degraded, failed, a transitional namespace whose stamp cannot be read, a namespace the registry does not list, and an unreadable registry are all judged
torTor client on every node: orama-namespace-tor@index active (fail if not), SOCKS5 :9050 bound, bootstrap % of the running process from its own journal invocation (unknown when vacuumed — low-severity warning), and a warning if Anyone network leftovers remain (an active orama-namespace-anyone-client@index, /etc/anon, /var/lib/anon or its apt source)
webrtcPer-namespace SFU/TURN services active, cross-node SFU coverage (3 nodes) and TURN redundancy (2 nodes). Only applies to namespaces with WebRTC provisioned
global(every chain and global-node check, ids chain.* and global.*, is in this subsystem) Chain unit and RPC on a node that has orama-global-chain.service: height lag against the median responsive height (more than 20 blocks, and not while catching up), no peers while the validator set has more than one member, a block older than 60 seconds, jailed, tombstoned, and missed-block ratio against the chain's own min_signed_per_window (warning at half the downtime miss fraction, failure at the fraction). Slashing and staking are one page of 200 from 127.0.0.1:31003. Public Kubo, provider, and relay or directory-authority units when they are installed: unit down, Kubo repo over StorageMax, provider hot-key balance 0, provider proof misses, provider disk over the declared maximum, relay or authority reporting it is not in the relay set. The hot-key, proof, disk, deal-slot, and relay-set fields come from monitor.json in the unit home; the provider writes its file every step and the Tor relay (orama-global-tor-relay.service, home /var/lib/orama-global/tor-relay) or directory authority (orama-global-tor-dirauth.service, home /var/lib/orama-global/tor-dirauth) writes in_consensus every five minutes, so the relay-set check fires when the node's consensus does not list it. Each monitor.json is read over SSH as root with dd (no link followed, no wait on a FIFO), because the file sits in a directory its service account owns. This SSH path does not verify the CometBFT node key; orama node report does.

Collection Failures

How a node is collected. The sessions of one node share one SSH connection (ControlMaster, sockets in a private /tmp/orama-inspect-* directory removed at the end), and its collectors run side by side, at most four at a time (WireGuard first, because the network probe pings its peers). Setting up an SSH session is most of what a collector costs: on a CPU-starved VPS it took 1-4s against 0.4s over an open connection, so the 14 sessions of one node ran for 49s one after another, longer than --timeout, and every subsystem of the node was reported not collected. Measured on that node, the same collection takes 9s. A collector that panics is recorded as that subsystem's failure, and Ctrl-C or SIGTERM cancels the run and closes the connections before the command exits with an error. Service states are read with one systemctl is-active call, and the Olric journal once.

Olric log checks. olric.log_failed_members (critical) fails when memberlist marked a member failed (Marking <member> as failed) in the last hour: that is its verdict that the member is gone. olric.log_suspicions (warning) counts suspicions (Suspect <member> has failed, no acks received, Refuting a suspect message): the first step of the protocol, refuted when the member answers; a member that keeps being suspected answers late (a starved or partitioned node), which is worth a look and not a failed cluster.

The inspector never reads a failed collection as zero values. Before collecting, it opens one SSH session (true) to each node. If that session fails (a connection reset from an overloaded node, a timeout, a refused key), the node is unreachable: nothing is collected from it and it produces one critical result, node.reachable (subsystem node), could not collect from <node>: <error>; every check on this node was skipped. No wireguard, network, system or dns check runs against it, so there is no "wg0 down", "no default route" or "2/3 nameservers active" from a node the inspector simply could not read.

Each subsystem is collected over its own SSH session. When one of them returns no output, or output cut short of the fields the collector reads, only that subsystem is affected: its data is left unset, its checks on that node are skipped, and one critical <subsystem>.collected result (for example dns.collected) reports why. A namespace collection failure is reported as both namespace.collected and webrtc.collected. The other subsystems on the node are checked normally, and cross-node checks count only the nodes whose data was collected. A command that ran and reported a service inactive is still a normal failure of that service's check.

Every collection failure is also listed under "Collection Errors" in the results summary and handed to the AI analysis.

Severity Levels

LevelWhen Used
CRITICALService completely down. Raft quorum lost, RQLite unresponsive, no leader.
HIGHService degraded. Olric down, gateway not responding, IPFS swarm key missing.
MEDIUMNon-ideal but functional. Stale handshakes, elevated memory, log suspects.
LOWInformational. Non-standard MTU, port mismatch, version skew.

Check Statuses

StatusMeaning
passCheck passed.
failCheck failed — action needed.
warnDegraded — monitor or investigate.
skipCheck could not run (insufficient data).

Output Formats

Table (default)

Inspecting 14 devnet nodes...

## RQLITE
----------------------------------------------------------------------
  OK [CRITICAL] RQLite responding (ubuntu@10.0.0.1)
    responsive=true version=v8.36.16
  FAIL [CRITICAL] Cluster has exactly one leader
    leaders=0 (NO LEADER)
  ...

======================================================================
Summary: 800 passed, 12 failed, 31 warnings, 0 skipped (4.2s)

Failures sort first, then warnings, then passes. Within each group, higher severity checks appear first.

JSON (--format json)

json
{
  "summary": {
    "passed": 800,
    "failed": 12,
    "warned": 31,
    "skipped": 0,
    "total": 843,
    "duration_seconds": 4.2
  },
  "checks": [
    {
      "id": "rqlite.responsive",
      "name": "RQLite responding",
      "subsystem": "rqlite",
      "severity": 3,
      "status": "pass",
      "message": "responsive=true version=v8.36.16",
      "node": "ubuntu@10.0.0.1"
    }
  ]
}

AI Analysis

When --ai is enabled, failures and warnings are sent to an LLM via OpenRouter for root-cause analysis.

bash
# Use default model (kimi-k2.5)
orama maint inspect --env devnet --ai

# Use a different model
orama maint inspect --env devnet --ai --model openai/gpt-4o

# Pass API key directly
orama maint inspect --env devnet --ai --api-key sk-or-...

The API key can be set via:

  1. +--api-key flag
  2. +OPENROUTER_API_KEY environment variable
  3. +.env file in the current directory

The AI receives the full check results plus cluster metadata and returns a structured analysis with likely root causes and suggested fixes.

Exit Codes

CodeMeaning
0All checks passed (or only warnings).
1At least one check failed.

Configuration

By default the inspector resolves nodes the same way every other command does: it asks the network API for the nodes your wallet owns, falling back to nodes.conf when the API is unreachable. It used to read scripts/nodes.conf relative to the working directory, so an installed binary only worked from inside the source tree.

--config overrides that with a pipe-delimited file. nodes.conf is gitignored — copy core/scripts/nodes.conf.example to core/scripts/nodes.conf, or put it at ~/.orama/nodes.conf where it does not depend on the working directory.

Format

# environment|user@host|role
devnet|ubuntu@1.2.3.4|node
devnet|ubuntu@5.6.7.8|nameserver-ns1
FieldDescription
environmentCluster name (devnet, testnet)
user@hostSSH credentials
rolenode or nameserver-ns1, nameserver-ns2, etc.

SSH keys are resolved from the RootWallet agent over its Unix socket, not by shelling out to rw. See Troubleshooting.

Blank lines and lines starting with # are ignored.

Node Roles

  • +node — Regular cluster node. Runs RQLite, Olric, IPFS, WireGuard, namespaces.
  • +nameserver-* — DNS nameserver. Runs CoreDNS + Caddy in addition to base services. System checks verify nameserver-specific services.

Examples

bash
# Full cluster inspection
orama maint inspect --env devnet

# Check only networking
orama maint inspect --env devnet --subsystem wireguard,network

# Quick RQLite health check
orama maint inspect --env devnet --subsystem rqlite

# Verbose mode (shows collection progress)
orama maint inspect --env devnet --verbose

# JSON for scripting / piping
orama maint inspect --env devnet --format json | jq '.checks[] | select(.status == "fail")'

# AI-assisted debugging
orama maint inspect --env devnet --ai --model anthropic/claude-sonnet-4

# Custom config file
orama maint inspect --config /path/to/nodes.conf --env testnet