mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Merge pull request #64536 from victor-kyriazakos/feat/gateway-health-diagnostics
feat(monitoring): gateway health & diagnostics OTLP export
This commit is contained in:
commit
7de33cc57e
37 changed files with 4316 additions and 26 deletions
|
|
@ -2230,7 +2230,7 @@ DEFAULT_CONFIG = {
|
|||
"privacy": {
|
||||
"redact_pii": False, # When True, hash user IDs and strip phone numbers from LLM context
|
||||
},
|
||||
|
||||
|
||||
# Text-to-speech configuration
|
||||
# Each provider supports an optional `max_text_length:` override for the
|
||||
# per-request input-character cap. Omit it to use the provider's documented
|
||||
|
|
@ -3211,6 +3211,44 @@ DEFAULT_CONFIG = {
|
|||
"force_ipv4": False,
|
||||
},
|
||||
|
||||
# Gateway monitoring — Service Health Monitoring plus redacted Operational
|
||||
# Diagnostics for the gateway daemon, exported over OTLP to an
|
||||
# operator-configured endpoint (OTEL Collector, DataDog, ...). Content-free
|
||||
# by construction: no prompts, messages, tool args/results, session
|
||||
# history, usage analytics, audit logs, or trajectories. Off by default;
|
||||
# nothing is collected or sent until an operator enables it and sets an
|
||||
# endpoint.
|
||||
"monitoring": {
|
||||
# Stable install identifier attached to exported health signals so an
|
||||
# operator can tell instances apart in their collector. Empty string
|
||||
# means "mint a fresh UUID on first use"; clear it to rotate. Carries
|
||||
# no account identity.
|
||||
"install_id": "",
|
||||
# Gateway health & diagnostics export.
|
||||
"gateway_health_export": {
|
||||
"enabled": False,
|
||||
"metrics_enabled": True,
|
||||
"diagnostic_events_enabled": True,
|
||||
"warning_error_events_enabled": True,
|
||||
"export_interval_seconds": 60,
|
||||
"logs_export_interval_seconds": 5,
|
||||
"resource_attributes": {
|
||||
"service.name": "hermes-gateway",
|
||||
"deployment.environment.name": "production",
|
||||
},
|
||||
},
|
||||
# OTLP destination. headers_env maps header names to ENVIRONMENT
|
||||
# VARIABLE NAMES (never secret values); values are read from the
|
||||
# environment at export time.
|
||||
"export": {
|
||||
"otlp": {
|
||||
"enabled": False,
|
||||
"endpoint": "",
|
||||
"headers_env": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
# Gateway settings — control how messaging platforms (Telegram, Discord,
|
||||
# Slack, etc.) deliver agent-produced files as native attachments.
|
||||
"gateway": {
|
||||
|
|
|
|||
|
|
@ -465,6 +465,7 @@ from hermes_cli.subcommands.memory import build_memory_parser
|
|||
from hermes_cli.subcommands.acp import build_acp_parser
|
||||
from hermes_cli.subcommands.tools import build_tools_parser
|
||||
from hermes_cli.subcommands.insights import build_insights_parser
|
||||
from hermes_cli.subcommands.monitoring import build_monitoring_parser
|
||||
from hermes_cli.subcommands.skills import build_skills_parser
|
||||
from hermes_cli.subcommands.pairing import build_pairing_parser
|
||||
from hermes_cli.subcommands.plugins import build_plugins_parser
|
||||
|
|
@ -15429,7 +15430,7 @@ _BUILTIN_SUBCOMMANDS = frozenset(
|
|||
"dump", "egress", "fallback", "gateway", "hooks", "import", "import-agent", "insights",
|
||||
"gui", "desktop", "kanban", "login", "logout", "logs", "lsp", "mcp", "memory", "migrate", "moa",
|
||||
"journey", "memory-graph", "learning",
|
||||
"model", "pairing", "pets", "plugins", "portal", "profile",
|
||||
"model", "monitoring", "pairing", "pets", "plugins", "portal", "profile",
|
||||
"project", "proxy",
|
||||
"prompt-size",
|
||||
"send", "sessions", "setup",
|
||||
|
|
@ -15911,6 +15912,51 @@ def cmd_insights(args):
|
|||
print(f"Error generating insights: {e}")
|
||||
|
||||
|
||||
def cmd_monitoring(args):
|
||||
"""Gateway monitoring status: health & diagnostics export posture."""
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
action = getattr(args, "monitoring_action", None) or "status"
|
||||
config = load_config()
|
||||
mon_raw = config.get("monitoring")
|
||||
mon: dict = mon_raw if isinstance(mon_raw, dict) else {}
|
||||
|
||||
if action == "status":
|
||||
from agent.monitoring import otlp_exporter
|
||||
|
||||
gh_raw = mon.get("gateway_health_export")
|
||||
gh: dict = gh_raw if isinstance(gh_raw, dict) else {}
|
||||
export_raw = mon.get("export")
|
||||
export_cfg: dict = export_raw if isinstance(export_raw, dict) else {}
|
||||
otlp_raw = export_cfg.get("otlp")
|
||||
otlp: dict = otlp_raw if isinstance(otlp_raw, dict) else {}
|
||||
|
||||
print("Gateway monitoring")
|
||||
print(f" Health export: {'enabled' if gh.get('enabled') else 'disabled'} "
|
||||
f"(monitoring.gateway_health_export.enabled)")
|
||||
if gh.get("enabled"):
|
||||
print(f" Metrics: {'on' if gh.get('metrics_enabled', True) else 'off'} "
|
||||
f"(interval {gh.get('export_interval_seconds', 60)}s)")
|
||||
print(f" Diagnostic events: {'on' if gh.get('diagnostic_events_enabled', True) else 'off'}")
|
||||
print(f" Warning/error logs: {'on' if gh.get('warning_error_events_enabled', True) else 'off'} "
|
||||
f"(interval {gh.get('logs_export_interval_seconds', 5)}s)")
|
||||
print(" Content safety: always on "
|
||||
"(rendered messages are never exported; not configurable)")
|
||||
endpoint = otlp.get("endpoint") or ""
|
||||
if otlp.get("enabled") and endpoint:
|
||||
print(f" OTLP endpoint: {endpoint}")
|
||||
else:
|
||||
print(" OTLP endpoint: not configured (monitoring.export.otlp)")
|
||||
print(f" OTel SDK: {'installed' if otlp_exporter.is_available() else 'not installed'} "
|
||||
f"(optional extra: hermes-agent[otlp])")
|
||||
print("\n Scope: gateway service health + redacted diagnostics only.")
|
||||
print(" No prompts, messages, tool args/results, usage analytics, or traces.")
|
||||
return
|
||||
|
||||
print(f"Unknown monitoring action: {action}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def cmd_skills(args):
|
||||
# Route 'config' action to skills_config module
|
||||
if getattr(args, "skills_action", None) == "config":
|
||||
|
|
@ -18216,6 +18262,7 @@ def main():
|
|||
# insights command (parser built in hermes_cli/subcommands/insights.py)
|
||||
# =========================================================================
|
||||
build_insights_parser(subparsers, cmd_insights=cmd_insights)
|
||||
build_monitoring_parser(subparsers, cmd_monitoring=cmd_monitoring)
|
||||
|
||||
# =========================================================================
|
||||
# claw command (parser built in hermes_cli/subcommands/claw.py)
|
||||
|
|
|
|||
36
hermes_cli/subcommands/monitoring.py
Normal file
36
hermes_cli/subcommands/monitoring.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""``hermes monitoring`` subcommand parser.
|
||||
|
||||
Gateway monitoring control and inspection. ``status`` shows whether the
|
||||
gateway health & diagnostics export is enabled, where it points, and the
|
||||
redaction posture.
|
||||
|
||||
The handler is injected to avoid importing ``main`` (mirrors the insights
|
||||
subcommand).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def build_monitoring_parser(subparsers, *, cmd_monitoring: Callable) -> None:
|
||||
"""Attach the ``monitoring`` subcommand (with actions) to ``subparsers``."""
|
||||
p = subparsers.add_parser(
|
||||
"monitoring",
|
||||
help="Inspect gateway monitoring (health & diagnostics export)",
|
||||
description=(
|
||||
"Gateway monitoring: service health metrics plus redacted "
|
||||
"diagnostics, exported over OTLP to an operator-configured "
|
||||
"endpoint. Content-free by construction — no prompts, messages, "
|
||||
"tool args/results, or usage analytics. Configure under "
|
||||
"monitoring.* in config.yaml."
|
||||
),
|
||||
)
|
||||
sub = p.add_subparsers(dest="monitoring_action")
|
||||
|
||||
sub.add_parser(
|
||||
"status",
|
||||
help="Show monitoring settings, export state, and redaction posture",
|
||||
)
|
||||
|
||||
p.set_defaults(func=cmd_monitoring)
|
||||
Loading…
Add table
Add a link
Reference in a new issue