Repository navigation
docs(telemetry): document the hook/plugin span-production rule and enforce it (Epic #929) #1464
Description
Parent epic: #929
Phase: cross-cutting (do before #1142 starts)
Depends on: nothing
Blocks: #1466
Problem
Mellea has a hard rule about how spans are produced — library code fires a hook,
and a plugin in mellea/telemetry/tracing_plugins.py turns that hook into a
span. Library code never opens spans itself. That rule is nowhere written down,
and nothing enforces it, so it keeps being broken by people acting in good faith.
The history matters, because it explains why "just read the docs" is not
currently an answer:
- Until refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 (2026-06-10,
refactor(telemetry)!), calling the span helpers
directly from library code was the pattern. All five backends contained a
barespan = start_generate_span(...), with the supporting machinery in
mellea/telemetry/backend_instrumentation.py. - refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 moved span production onto hooks and plugins and deleted that module.
This was the right change. - refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 rewrote around 97 lines of
docs/docs/observability/tracing.md— and
that page still contains no occurrence of the word "plugin". It documents
which spans exist and what attributes they carry, never how a span comes to be
produced. - There is no import contract, so reintroducing the pre-refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 pattern is
invisible to CI. - The only in-code example of importing
telemetry.tracingis
mellea/stdlib/session.py, which is a deliberate, documented exception
(OTelTokenattach/detach is task-affine). Anyone using it as a template
copies the exception rather than the pattern. - Six weeks after refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181,
docs/dev/adapter_observability.mdwas written and
describes the pre-refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 approach, namingstart_backend_span/
start_action_spanas the model to mirror. - feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) + from_catalog() (Epic #929 Phase 2) #1141 followed that document, and produced spans inline inside
mellea/backends/. They are being removed in PR feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) + from_catalog() (Epic #929 Phase 2) #1454.
Two live consequences:
-
docs/dev/adapter_observability.mdstill instructs the next implementer to do
the same thing. -
feat(backends): EmbeddedBinding implements apply_activation (Granite Switch path); remove render_controls + set_request_adapter (Epic #929 Phase 2) #1142's telemetry acceptance criteria mandate the same defect. feat(backends): EmbeddedBinding implements apply_activation (Granite Switch path); remove render_controls + set_request_adapter (Epic #929 Phase 2) #1142 is now
unblocked, specified spans "under theintrinsic.callparent", and names tests
(test_prepare_span_binding_type_is_embedded,
test_activate_span_has_controls_key). Whoever picks it up will reproduce
feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) + from_catalog() (Epic #929 Phase 2) #1454's problem by following the issue as written.Partly addressed, 2026-08-11. feat(backends): EmbeddedBinding implements apply_activation (Granite Switch path); remove render_controls + set_request_adapter (Epic #929 Phase 2) #1142 has since been rescoped (
apply_activation,
no lifecycle verbs) and its span criteria now carry an explicit "emitted from a
tracing plugin, not from library code" qualifier and ablocked on #1464 + #1466
marker, so the mechanism half of this consequence is defused. The invented
intrinsic.*span names were also corrected across feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) + from_catalog() (Epic #929 Phase 2) #1141, feat(backends): EmbeddedBinding implements apply_activation (Granite Switch path); remove render_controls + set_request_adapter (Epic #929 Phase 2) #1142 and feat(telemetry): add complete adapter-function lifecycle observability #1466 the same
day. What remains for this issue is the durable half: the page that taught the wrong
pattern, and CI enforcement so the next occurrence fails a check rather than needing
a reviewer to spot it.
There is also a structural gap that makes the rule impossible to follow for
adapter functions specifically. ADAPTER_FUNCTION_INVOCATION_COMPLETE and
ADAPTER_FUNCTION_PHASE_COMPLETE are the only HookType family with no
pre/start sibling, so there is no event at which a plugin could open those spans.
Documenting the rule without noting this would set an unmeetable expectation.
Scope
docs/docs/observability/tracing.md— explain how spans are produced, not just
which spans exist: hooks fire, plugins emit, and why (telemetry stays optional
and testable without an exporter).- Record the
session.pyexception as an exception, at a place a reader will find
before copying it. docs/dev/adapter_observability.md— correct the direct-start_*_span
guidance.- Enforcement in CI, so the next violation fails a check rather than relying on
review attention. Suggested boundary: nothing undermellea/backends/imports
mellea/telemetry/tracing, withmellea/stdlib/session.pyas the sole
allowlisted exception. - Amend feat(backends): EmbeddedBinding implements apply_activation (Granite Switch path); remove render_controls + set_request_adapter (Epic #929 Phase 2) #1142's telemetry acceptance criteria so they no longer require the
pre-refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 pattern. - Note the pre/start hook gap for the
ADAPTER_FUNCTION_*family where the rule
is documented, so the exception is visible rather than surprising.
Out of scope
Adding the missing start hooks, and emitting adapter-function spans — both belong
to #1466, which this issue blocks.
Acceptance criteria
-
docs/docs/observability/tracing.mdstates the hook-fires/plugin-emits rule
and contains at least one worked reference totracing_plugins.py - The
mellea/stdlib/session.pyexception is documented as such -
docs/dev/adapter_observability.mdno longer instructs implementers to call
start_*_spanfrom library code - A CI check fails on a new
telemetry.tracingimport under
mellea/backends/, withsession.pyallowlisted - The check is verified to fail by temporarily reintroducing such an import
(evidence in the PR description; the temporary change is not committed) - feat(backends): EmbeddedBinding implements apply_activation (Granite Switch path); remove render_controls + set_request_adapter (Epic #929 Phase 2) #1142's telemetry acceptance criteria updated
-
npx markdownlint-cli2passes on every page touched
References
- refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 — the refactor that introduced the rule without documenting it
- feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) + from_catalog() (Epic #929 Phase 2) #1141 / PR feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) + from_catalog() (Epic #929 Phase 2) #1454 — where it went wrong in practice
- refactor(backends): AdapterMixin verb rename/narrow + resolve_model_options + IntrinsicMetricsPlugin (Epic #929 Phase 2) #1140 — the design issue whose span schema named no emitter
mellea/telemetry/tracing_plugins.py— the four existing span plugins
Activity
feat(backends): implement LocalFileBinding verbs and from_catalog()
planetf1 commentedon Aug 11, 2026
The proposed CI boundary is currently green — and the allowlist may be unnecessary
Checked while finishing PR #1454, so this issue can be picked up knowing whether enforcement lands on existing violations.
Every importer of
mellea/telemetry/tracingacrossmellea/, identical onupstream/mainand on PR #1454's branch:mellea/stdlib/session.pyTokenattach/detach is task-affine)mellea/telemetry/tracing_plugins.pySo there are zero violations today and the check can be added green, rather than needing a cleanup pass first. PR #1454 removed the only ones (
_core.pyandadapter.py).A wrinkle in the suggested rule. This issue proposes: "nothing under
mellea/backends/importsmellea/telemetry/tracing, withmellea/stdlib/session.pyas the sole allowlisted exception." Butsession.pyis not undermellea/backends/— so scoped that narrowly, it needs no allowlist entry at all, and neither doestracing_plugins.py. The allowlist would be dead configuration.That leaves a real choice worth making deliberately:
mellea/backends/only) — no allowlist needed, trivially green, but does not catch the same defect appearing inmellea/stdlib/ormellea/formatters/.mellea/exceptmellea/telemetry/) — catches the general case, and then needs exactly one allowlist entry:mellea/stdlib/session.py.tracing_plugins.pyis excluded by living undermellea/telemetry/.I'd lean repo-wide with the single exception, since the failure mode this issue exists to prevent is "library code opens a span", which is not specific to backends — but flagging it as a decision rather than assuming, because the narrow version is what the issue text currently specifies.
ajbozarth commentedon Aug 11, 2026
The intent is repo-wide, but iirc there are imports for logging telemetry that are also an exception in addition to session, so any language would nee to be specific to metrics and traces telemetry.
docs(telemetry): document the session.py tracing exception at its imp…
test(telemetry): enforce the backends-never-import-tracing invariant
docs: explain how spans are produced in tracing.md
planetf1 commentedon Aug 12, 2026
Checked — it's
mellea/stdlib/tools/_bash_audit.py, importing.context/.metrics.No allowlist needed: the issue's already scoped to
mellea.telemetry.tracing, so this one isn't caught anyway. Confirms the narrow scoping was right.refactor: split generative-computing#1464 telemetry work into its own PR
planetf1 commentedon Aug 17, 2026
Reopened after the 2026-08-17 epic audit: PR #1545 closed without merging, and
mainstill lacks both the hook→plugin rule indocs/docs/observability/tracing.mdand the import-boundary test. The acceptance criteria remain unmet; #1466 stays formally blocked by this issue.planetf1 commentedon Aug 17, 2026
Correction to my 2026-08-17 reopen comment: #1545 was intentionally closed after review, not abandoned accidentally. Its closing rationale says the relevant source guidance is already in place and that a standalone import guard needs a broader tracing-and-metrics policy. This scope is not being moved to another epic issue; re-closing #1464 and removing its stale blocker edge from #1466.
docs: delete docs/dev/, migrate current content, close generative-com…
feat(backends): implement LocalFileBinding verbs and from_catalog()
feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) +…