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.
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 bridges brief 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.