feat(observability): observer-grade telemetry hooks + NeMo-Relay plugin
Adds backend-neutral observer hooks for plugins: session, turn, API request, tool, approval, and subagent lifecycle events with stable correlation IDs (session_id, task_id, turn_id, api_request_id, tool_call_id, parent/child subagent ids). Extends VALID_HOOKS with api_request_error and subagent_start. Hot path is zero-cost when no plugin subscribes: has_hook()/presence checks gate all payload construction, request payloads are returned by reference when no middleware rewrites, and the sanitized response payload no longer embeds raw response objects. Bundles the optional NeMo-Relay observability plugin (plugins/observability/nemo_relay) as an in-repo consumer of the new hooks, peer to the existing langfuse plugin. Fails open when the optional nemo-relay package is not installed. Authored-by: Bryan Bednarski <bbednarski@nvidia.com> Salvaged from #29722 onto current main.
This commit is contained in:
@@ -969,7 +969,12 @@ def _run_cleanup():
|
||||
# session boundary — NOT per-turn inside run_conversation().
|
||||
try:
|
||||
from hermes_cli.plugins import invoke_hook as _invoke_hook
|
||||
_invoke_hook("on_session_finalize", session_id=_active_agent_ref.session_id if _active_agent_ref else None, platform="cli")
|
||||
_invoke_hook(
|
||||
"on_session_finalize",
|
||||
session_id=_active_agent_ref.session_id if _active_agent_ref else None,
|
||||
platform="cli",
|
||||
reason="shutdown",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
@@ -989,6 +994,42 @@ def _run_cleanup():
|
||||
pass
|
||||
|
||||
|
||||
def _emit_interrupted_session_end(cli, *, reason: str = "keyboard_interrupt") -> None:
|
||||
"""Best-effort on_session_end hook for interrupted non-interactive runs."""
|
||||
agent = getattr(cli, "agent", None)
|
||||
if agent is None:
|
||||
return
|
||||
|
||||
try:
|
||||
agent.interrupt(reason.replace("_", " "))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
session_id = getattr(agent, "session_id", None) or getattr(cli, "session_id", None)
|
||||
if session_id:
|
||||
try:
|
||||
cli.session_id = session_id
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
from hermes_cli.plugins import invoke_hook as _invoke_hook
|
||||
_invoke_hook(
|
||||
"on_session_end",
|
||||
session_id=session_id,
|
||||
task_id=getattr(agent, "_current_task_id", "") or "",
|
||||
turn_id=getattr(agent, "_current_turn_id", "") or "",
|
||||
api_request_id=getattr(agent, "_current_api_request_id", "") or "",
|
||||
completed=False,
|
||||
interrupted=True,
|
||||
model=getattr(agent, "model", None),
|
||||
platform=getattr(agent, "platform", None) or "cli",
|
||||
reason=reason,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _reset_terminal_input_modes_on_exit() -> None:
|
||||
"""Best-effort: disable focus reporting + mouse tracking on TUI exit so they
|
||||
don't leak into the next shell session sharing the tab.
|
||||
@@ -6530,6 +6571,7 @@ class HermesCLI:
|
||||
event_type,
|
||||
session_id=self.agent.session_id if self.agent else None,
|
||||
platform=getattr(self, "platform", None) or "cli",
|
||||
reason="new_session" if event_type == "on_session_reset" else "session_boundary",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -15284,6 +15326,7 @@ class HermesCLI:
|
||||
interrupted=True,
|
||||
model=getattr(self.agent, 'model', None),
|
||||
platform=getattr(self.agent, 'platform', None) or "cli",
|
||||
reason="shutdown",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -15643,6 +15686,7 @@ def main(
|
||||
raise KeyboardInterrupt()
|
||||
try:
|
||||
import signal as _signal
|
||||
_signal.signal(_signal.SIGINT, _signal_handler_q)
|
||||
_signal.signal(_signal.SIGTERM, _signal_handler_q)
|
||||
if hasattr(_signal, "SIGHUP"):
|
||||
_signal.signal(_signal.SIGHUP, _signal_handler_q)
|
||||
@@ -15764,10 +15808,15 @@ def main(
|
||||
# status lines). The response is printed once below.
|
||||
cli.agent.stream_delta_callback = None
|
||||
cli.agent.tool_gen_callback = None
|
||||
result = cli.agent.run_conversation(
|
||||
user_message=effective_query,
|
||||
conversation_history=cli.conversation_history,
|
||||
)
|
||||
try:
|
||||
result = cli.agent.run_conversation(
|
||||
user_message=effective_query,
|
||||
conversation_history=cli.conversation_history,
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
_emit_interrupted_session_end(cli, reason="keyboard_interrupt")
|
||||
print(f"\nsession_id: {cli.session_id}", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
# Sync session_id if mid-run compression created a
|
||||
# continuation session. The exit line below reports
|
||||
# session_id to stderr for automation wrappers; without
|
||||
|
||||
Reference in New Issue
Block a user