Skip to main content

Native Add-ons (Agent Feature Sets)

ServiceRadar agents gain optional capabilities through native add-ons — signed, per-architecture binaries that operators select in Edge Ops and push down to chosen agents. Add-ons are the native counterpart to Wasm Plugins: use a Wasm plugin for a sandboxed checker, and a native add-on when a capability needs a real OS process (a sidecar daemon, a scheduled scanner, a host-level collector).

This page is the operator/author overview for the add-on framework defined in issue #3425. The framework spine is in place; some delivery models and UI surfaces are landing in follow-up changes (see the add-native-addon-* OpenSpec changes).

Why native add-ons

The base serviceradar-agent package stays small and ships only the core agent. Every optional capability is a separately built, signed add-on that stays dormant until an operator selects it:

  • Selectable. Operators choose which agents (or cohorts) run which add-ons in the Edge Ops UI — no rebuild, no redeploy of the base agent.
  • Signed. Add-on artifacts are Cosign-signed (Rekor transparency log) and carry an ed25519 upload-signature; the agent verifies before activation.
  • Out-of-process by default. The agent-sidecar model runs add-ons as HashiCorp go-plugin subprocesses (gRPC over a Unix-domain socket with AutoMTLS). The base agent never imports an add-on's code — isolation is CI-enforced — so an add-on crash cannot take down the agent.
  • Polyglot. Add-ons can be written in Go or Rust; both speak the same gRPC contract to the agent's plugin client.

Delivery and supervision models

An add-on declares two axes in its manifest. Together they tell the agent how to obtain and run it:

Delivery — how the artifact reaches the host:

  • compiled-in — capability already in the base agent; the assignment is just a config toggle (reserved for legacy/coupled capabilities such as remote-access).
  • pushed-artifact — a signed per-arch tarball delivered over the existing runtime-push rail, staged and activated by the agent.
  • os-package — a deb/rpm that depends on serviceradar-agent and is dormant until selected.

Supervision — how the agent runs it:

  • config-toggle — flip a flag on an in-agent capability.
  • agent-sidecar — supervised go-plugin subprocess (health checks, restart backoff, circuit breaker).
  • systemd-service / systemd-timer — a long-running unit or a scheduled job that spools results for ingest.
  • ephemeral-helper — a short-lived one-shot process.

The reference sample add-on is pushed-artifact / agent-sidecar.

Package format

Each add-on's manifest package lives under addons/<id>/:

  • addon.yaml — the manifest (identity, delivery/supervision, capabilities, requires, exec, config_schema pointer). Mirrors plugin.yaml.
  • config.schema.json — JSON Schema (draft 2020-12) for operator config; the control plane validates AddonAssignment.params against it before persisting.
  • BUILD.bazel, README.md.

Implementation sources live elsewhere (go/cmd/serviceradar-<id>-addon/ for Go, rust/ for Rust). See addons/sample-addon/README.md for the worked example.

Capability and approval model

Like Wasm plugins, add-ons declare capabilities in the manifest, and an operator approves a package before it can be assigned. On approval the operator may narrow the granted set via approved_capabilities; the control plane sends that narrowed subset (not the full manifest list) to the agent. Confirm the capabilities and the delivery/supervision model during review, especially for add-ons that run as a privileged sidecar or apply OS capabilities.

SDKs and authoring

The Go SDK (go/pkg/addon) wraps the go-plugin server boilerplate — handshake, gRPC serving over the UDS, AutoMTLS, health, config decode from the typed assignment, and result submission. The gRPC contract lives in proto/agent/addon/v1/. A Rust helper/contract for Rust add-ons (e.g. fingerprintd) is landing in a follow-up change; until then the documented contract in proto/agent/addon/v1/ is the source of truth for Rust interop.

Author checklist

Mirror the Wasm plugin author flow:

  1. Scaffold addons/<id>/ — copy addons/sample-addon/ and edit addon.yaml (id, version, delivery, supervision, language, capabilities, requires, exec) and config.schema.json.
  2. Implement the add-on service against the Go SDK (go/pkg/addon) or the Rust contract, using the gRPC service in proto/agent/addon/v1/. Put sources in go/cmd/serviceradar-<id>-addon/ (or under rust/).
  3. Validate the manifest against the add-on manifest schema and validate config.schema.json is a supported JSON-Schema subset.
  4. Enroll in the build — add an entry to build/native_addons/addon_inventory.bzl so the release build cross-compiles, bundles, signs, and indexes your add-on per (os, arch) without bespoke release wiring.
  5. Verify locally — build the binary and run the agent's add-on tests (go test ./go/pkg/agent/addon/...); confirm addon.yaml requires and app_protocol_version match the agent's plugin client.
  6. Publish & approve — the signed bundle and discovery index ship with the release; import/approve the AddonPackage in Edge Ops, narrowing approved_capabilities as needed.
  7. Assign — create an AddonAssignment for the target agent or cohort with validated params; the control plane compiles it into the agent config push and the agent supervises it.

Lifecycle

  1. Build a signed, per-arch bundle + discovery index (release workflow).
  2. Import/approve the AddonPackage (staged → approved) in the admin UI.
  3. Assign to agents/cohort with config validated against config.schema.json.
  4. The control plane pushes the typed add-on section in the versioned agent config.
  5. The agent fetches/verifies/activates and supervises the add-on per its model.
  6. Per-add-on installed/active/unhealthy status is reported back and reconciled against the desired assignment in the UI.