symfonic.platform.telemetry¶
telemetry ¶
The seam that costs a kernel turn.
ConversationMetricsCollector.on_llm_end does two jobs: it records the call
for the usage dashboard, and it fans the same usage into the process-wide
TokenBudgetTracker so a per-tenant ceiling can fire on the next call.
Nothing on the kernel path called it, so a generated app installed a budget
tracker, mounted the 429 dependency, and never incremented a single row. A
tenant over their real spend was not refused.
The gap was never the collector -- platform.observability already builds
one and the host already holds it. What was missing was a place for a turn to
hand it a call.
Why post-model. The kernel dispatches it once per model round, so a
turn that calls three tools reports three calls rather than one. pre-model
would be too early to know the usage, and finalize runs once and would
collapse a multi-round turn into a single billing event.
What the stage never does is fail a turn. Telemetry that can break a run is worse than telemetry that is missing: the collector's own fan-out already swallows tracker failures for that reason, and this stage takes the same posture one layer out.
TelemetryCapability ¶
Reports each model round to the collector the host built.
Source code in src/symfonic/platform/telemetry.py
contribute ¶
Declare the post-model stage and the handler that reports the call.
request is read for its grants and found to need none. Counting a
call is not an effect on the turn: nothing downstream reads what this
stage writes, and a grant would be authority never exercised.
Source code in src/symfonic/platform/telemetry.py
attribution_is_bound ¶
Whether these calls can be charged to a tenant at all.
False means the agent was composed without a scope, so every call is
recorded against the run and no per-tenant ceiling can ever fire. That is a
legitimate choice for a single-tenant deployment and a silent billing hole
for any other -- the same reason budget_is_shared exists next door.
Source code in src/symfonic/platform/telemetry.py
telemetry ¶
Bind the host's collector to the turn, so a call is counted and charged.
Agent(provider, capabilities=[telemetry(resources.services, scope)])
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
services
|
Any
|
the observability bundle from |
required |
scope
|
Any
|
the scope this agent was composed for. Without it the calls are
recorded and charged to nobody; see :func: |
None
|