Skip to content

symfonic.agent.observability

observability

Observability hooks for the agent framework.

Extends the core ObservabilityHook with memory-specific metrics: hydration latency, consolidation operations, routing results, and token budget savings.

FrameworkObservabilityHook

Bases: Protocol

Protocol for agent framework observability events.

Extends core ObservabilityHook with HMS-specific lifecycle events. All methods are async and accept keyword arguments for forward compatibility.

on_consolidation_complete async

on_consolidation_complete(
    tenant_id: str, operations_count: int, latency_ms: float
) -> None

Called after memory consolidation completes.

Source code in src/symfonic/agent/observability.py
async def on_consolidation_complete(
    self,
    tenant_id: str,
    operations_count: int,
    latency_ms: float,
) -> None:
    """Called after memory consolidation completes."""
    ...

on_hydration_complete async

on_hydration_complete(
    tenant_id: str,
    query: str,
    entries_count: int,
    latency_ms: float,
) -> None

Called after memory hydration completes.

Source code in src/symfonic/agent/observability.py
async def on_hydration_complete(
    self,
    tenant_id: str,
    query: str,
    entries_count: int,
    latency_ms: float,
) -> None:
    """Called after memory hydration completes."""
    ...

on_routing_complete async

on_routing_complete(
    tenant_id: str,
    query: str,
    tools_selected: list[str],
    latency_ms: float,
) -> None

Called after lazy tool routing completes.

Source code in src/symfonic/agent/observability.py
async def on_routing_complete(
    self,
    tenant_id: str,
    query: str,
    tools_selected: list[str],
    latency_ms: float,
) -> None:
    """Called after lazy tool routing completes."""
    ...

on_token_budget_computed async

on_token_budget_computed(
    tenant_id: str,
    total_tool_tokens: int,
    filtered_tool_tokens: int,
    savings_pct: float,
) -> None

Called when token budget savings are computed after tool filtering.

Source code in src/symfonic/agent/observability.py
async def on_token_budget_computed(
    self,
    tenant_id: str,
    total_tool_tokens: int,
    filtered_tool_tokens: int,
    savings_pct: float,
) -> None:
    """Called when token budget savings are computed after tool filtering."""
    ...

LoggingFrameworkHook

Logs all framework observability events at INFO level.

Log formats follow structured patterns for easy parsing

HMS hydration: [tenant=X] HMS hydration: N entries in Yms Consolidation: [tenant=X] Consolidation: N operations in Yms Routing: [tenant=X] Tool routing: N tools selected in Yms Token budget: [tenant=X] Tool routing: A -> B tokens (C% reduction)

NoOpFrameworkHook

Default no-op implementation. Zero overhead when no observability needed.

dispatch_framework_hook async

dispatch_framework_hook(
    hooks: list[FrameworkObservabilityHook] | list,
    method_name: str,
    *args,
    **kwargs,
) -> None

Fan method_name out to every registered framework hook.

Each hook's method is awaited inside its own try/except so a single misbehaving hook can never break the memory pipeline. This helper is the canonical emit-site dispatcher used by SymfonicAgent for the four FrameworkObservabilityHook lifecycle events.

Roadmap Item 10 / PR-5b: before this helper, the protocol existed but had no engine-side emit calls.

Source code in src/symfonic/agent/observability.py
async def dispatch_framework_hook(
    hooks: list[FrameworkObservabilityHook] | list,
    method_name: str,
    *args,
    **kwargs,
) -> None:
    """Fan ``method_name`` out to every registered framework hook.

    Each hook's method is awaited inside its own try/except so a single
    misbehaving hook can never break the memory pipeline. This helper
    is the canonical emit-site dispatcher used by ``SymfonicAgent`` for
    the four ``FrameworkObservabilityHook`` lifecycle events.

    Roadmap Item 10 / PR-5b: before this helper, the protocol existed
    but had no engine-side emit calls.
    """
    if not hooks:
        return
    for hook in hooks:
        method = getattr(hook, method_name, None)
        if method is None:
            continue
        try:
            await method(*args, **kwargs)
        except Exception:
            logger.exception(
                "FrameworkObservabilityHook %r raised in %s; suppressing",
                hook,
                method_name,
            )