# Hive-wide OTEL stats export. Set ONCE here at host level; the # meta-flake renderer (`hive-c0re/src/meta.rs::otel_config`) reads the # HYPERHIVE_OTEL_* env exported off hive-c0re's unit (see # ./hive-c0re) and injects the matching `hyperhive.otel.*` build-time # config into EVERY agent (mirroring the CA-cert injection), so each # agent's harness exports its own Claude Code stats directly to the # collector. There is no per-agent opt-in — this is the single switch # for the whole hive. { lib, config, ... }: { options.services.hyperhive.otel = { enable = lib.mkEnableOption '' hive-wide export of every agent's Claude Code stats (token usage, cost, tool calls) to an OTLP endpoint via Claude Code's built-in OpenTelemetry. One switch for all agents; each harness exports directly to the collector, so it keeps working even when hive-c0re is down ''; endpoint = lib.mkOption { type = lib.types.str; default = ""; example = "https://collector.example.com/otel"; description = '' OTLP collector endpoint, set as `OTEL_EXPORTER_OTLP_ENDPOINT` for every agent. Required when `enable` is true. ''; }; protocol = lib.mkOption { type = lib.types.enum [ "http/protobuf" "http/json" "grpc" ]; default = "http/protobuf"; description = '' OTLP wire protocol, set as `OTEL_EXPORTER_OTLP_PROTOCOL`. ''; }; headersCredential = lib.mkOption { # `str`, not `path`: a `path`-typed relative literal is hash-copied # into the world-readable nix store at eval time, defeating the # point. Keep it a string + require an absolute runtime path so the # secret is only ever read from disk by systemd at start. type = lib.types.nullOr lib.types.str; default = null; example = "/run/secrets/otel-headers"; description = '' Absolute path to an operator-provided secret file whose contents become `OTEL_EXPORTER_OTLP_HEADERS` (e.g. `Authorization=Bearer `). hive-c0re forwards this host file into each agent container's credential store via systemd-nspawn `--load-credential=otel-headers:`; the inner harness unit inherits it by name (`LoadCredential`), so the token is never copied into the nix store, the generated config, a bind mount, or argv. Must be absolute. Leave null if the endpoint needs no auth header. A configured-but-missing file is skipped with a log warning (OTEL still exports, without the auth header). ''; }; extraResourceAttributes = lib.mkOption { type = lib.types.str; default = ""; example = "deployment.environment=prod"; description = '' Extra comma-separated entries appended to `OTEL_RESOURCE_ATTRIBUTES` after the built-in `service.name` / `agent` / `hive` / `swarm` labels. ''; }; debug = lib.mkOption { type = lib.types.bool; default = false; description = '' Emit OTEL SDK diagnostic messages to every agent's stderr by setting `CLAUDE_CODE_OTEL_DIAG_STDERR=1`. Useful when troubleshooting collector connectivity or endpoint config; leave off in normal operation to avoid noise in agent logs. Only meaningful when `enable` is true. ''; }; metricIntervalMs = lib.mkOption { type = lib.types.nullOr lib.types.ints.positive; default = null; example = 10000; description = '' Metric export interval in milliseconds, set as `OTEL_METRIC_EXPORT_INTERVAL` for every agent. Claude Code's default is 60000 (60s). Leave `null` to use that default. Each agent runs claude as a short-lived per-turn process; claude force-flushes metrics on shutdown, so this is not required for metrics to be exported, but a lower value gives more frequent intermediate flushes within long turns. Cosmetic, not a correctness knob. ''; }; }; config = lib.mkIf config.services.hyperhive.c0re.enable { assertions = lib.optionals config.services.hyperhive.otel.enable [ { assertion = config.services.hyperhive.otel.endpoint != ""; message = "services.hyperhive.otel.enable is true but services.hyperhive.otel.endpoint is empty."; } ]; }; }