Skip to content

symfonic.agent.fastapi.shared_metrics

shared_metrics

Owner-exact /metrics reads for the selected native chat root.

The collector keys a record by (tenant, conversation_id) and nothing else: it does not know the sub-tenant, namespace, or principal that ran the turn. So the collector cannot answer "whose is this" and this router never asks it to. The conversation authority does: a record is served only for a conversation whose ownership check (authorize, the refusal transcript answers with, minus the message read) passes for this principal, and the list is built from that principal's own sessions. /metrics/summary is a tenant aggregate with no owner, so it is not served here.

The record itself is still per-process memory. A conversation the owner holds but this process never metered has no record, and answers 404 or is left out of the list rather than being reported as zero.

create_owned_metrics_router

create_owned_metrics_router(conversation: Any, collector: Any) -> APIRouter

Serve a conversation's record only to the principal that owns it.

Source code in src/symfonic/agent/fastapi/shared_metrics.py
def create_owned_metrics_router(conversation: Any, collector: Any) -> APIRouter:
    """Serve a conversation's record only to the principal that owns it."""
    require_metrics_collector(collector)
    router = APIRouter(tags=["metrics"])

    def record_of(tenant: str, session_id: str) -> Any:
        record = collector.get_conversation(session_id, tenant_id=tenant)
        if record is None or (record.tenant_id, record.conversation_id) != (tenant, session_id):
            return None
        return record

    @router.get("/metrics/conversations")
    async def list_conversations(
        request: Request,
        limit: int = 50,
        scope: Any = Depends(get_tenant_scope),  # noqa: B008
    ) -> list[dict[str, Any]]:
        try:
            principal = principal_of(request, scope)
            owned = await conversation.list_sessions(principal)
        except Exception as exc:
            raise raise_mapped(exc, CRUD_POLICY) from exc
        tenant = str(principal.scope.tenant_id)
        records = [record_of(tenant, str(row["session_id"])) for row in owned]
        present = sorted(
            (record for record in records if record is not None),
            key=lambda record: record.last_seen,
            reverse=True,
        )
        return [_row(record) for record in present[: max(limit, 0)]]

    @router.get("/metrics/conversations/{conversation_id}")
    async def get_conversation(
        conversation_id: str,
        request: Request,
        scope: Any = Depends(get_tenant_scope),  # noqa: B008
    ) -> dict[str, Any]:
        try:
            principal = principal_of(request, scope)
            # The refusal the transcript answers with, without reading it:
            # not owned by exactly this principal and scope is 404, as there.
            await conversation.authorize(principal, conversation_id)
        except Exception as exc:
            raise raise_mapped(exc, CRUD_POLICY) from exc
        record = record_of(str(principal.scope.tenant_id), conversation_id)
        if record is None:
            # The legacy wire's 404 body, through the mapper, not a literal.
            absent = ConversationRefused(f"Conversation {conversation_id!r} not found")
            raise raise_mapped(absent, CRUD_POLICY)
        return {**_row(record), "call_records": [_call(call) for call in record.call_records]}

    return router

require_metrics_collector

require_metrics_collector(collector: Any) -> None

Refuse at mount anything that cannot read one tenant's record by id.

Source code in src/symfonic/agent/fastapi/shared_metrics.py
def require_metrics_collector(collector: Any) -> None:
    """Refuse at mount anything that cannot read one tenant's record by id."""
    if not callable(getattr(collector, "get_conversation", None)):
        raise TypeError("selected metrics require a conversation metrics collector")