Skip to main content
Version: v1.9.2

orbit/agent

The observability agent embeds in every framework process and ships events to a standalone admin server over a single Connect-RPC bidirectional stream. One agent per application node; many agents per server.

Wiring it into an app

The agent owns its configuration type (agent.ExtensionConfig) — the framework carries no admin-specific config. Populate it directly, for example from your own config file, and pass it to agent.NewExtension together with the framework's state directory and your application's version string:

import (
"context"
"log"
"os"

"github.com/jcsvwinston/nucleus/pkg/app"
"github.com/jcsvwinston/orbit/agent"
)

func main() {
cfg, err := app.LoadConfig("nucleus.yml")
if err != nil {
log.Fatal(err)
}
a, err := app.New(cfg,
app.WithExtensions(
agent.NewExtension(agent.ExtensionConfig{
Endpoints: []string{"https://admin.internal:9090"},
Token: os.Getenv("NUCLEUS_ADMIN_TOKEN"),
}, cfg.StateDir, "v1.2.3"), // your app's version string
),
)
if err != nil {
log.Fatal(err)
}
if err := a.Run(context.Background()); err != nil {
log.Fatal(err)
}
}

Endpoints is the switch. Leave it empty and the extension is a no-op: the framework runs exactly as it would without it. Set it and the agent starts in parallel with the framework's Run, and observability events flow from the framework's pkg/observability bus into the stream.

TLS

The scheme of each endpoint picks the transport: http:// is cleartext HTTP/2 (h2c, for development), https:// performs a real TLS handshake and negotiates HTTP/2 through ALPN. By default https:// trusts the system store; for a private CA, or when the admin server requires client certificates (--agent-client-ca), pass a *tls.Config in TLS:

pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caPEM)
cert, _ := tls.LoadX509KeyPair("agent.crt", "agent.key")

agent.NewExtension(agent.ExtensionConfig{
Endpoints: []string{"https://admin.internal:9090"},
TLS: &tls.Config{
RootCAs: pool, // private CA
Certificates: []tls.Certificate{cert}, // client certificate (mutual TLS)
MinVersion: tls.VersionTLS12,
},
}, cfg.StateDir, appVersion)

TLS is not read from a config file; build it in code from the PEM files your deployment ships. The /healthz probe the agent sends before opening a stream uses the same configuration and carries no token.

Node identity

The agent resolves a stable NodeID: a UUIDv4 persisted at ${state_dir}/node_id, falling back to a hostname-derived ephemeral value when the state directory is unavailable.

This is the identity the agent registers under, and the value every fleet view keys on — the Nodes page, per-node stream filters, and the metrics cards. Events shipped over the stream carry the same NodeID, so an event's node_id always matches a registered node. (The agent stamps it over the in-process bus's own node label, which is host-local and does not correlate with the fleet registry.)

Hot-path cost

The agent never blocks the framework's request thread. Every producer-side path — the HTTP middleware, the SQL observer — starts with a single atomic load on observability.Bus.HasSubscribers(kind) and short-circuits when nobody is watching.

What's inside

The agent is layered:

  • Node identity — resolution and persistence, as described above.
  • Event pipeline — conversion and sampling, then a drop-oldest ring buffer that absorbs backpressure while the stream is open (it does not buffer across disconnects).
  • Transport — an endpoint-failover dialer with exponential backoff, and the bidirectional stream lifecycle: registration, recv/send/heartbeat, and replay on reconnect.
  • Metrics — the admin_agent_* Prometheus collectors.
  • RBAC snapshot — the read-only handler behind the fleet UI's Access control screen, wired automatically from the application's authorizer when the extension attaches. No extra configuration.

The top-level Agent exposes New, Run, NodeID, Connected, and Metrics.