Parent epic: #929
Design proposal: PR #1080 §13, §16 Phase 2
Phase: 2 (Wave 3 — start after 1.A merges, in parallel with 1.B/1.C/1.D)
Depends on: 1.A
Blocks: 2.2, 2.3
Problem
Two related backend-surface concerns that touch the same files:
(a) Mixin verb mismatch. AdapterMixin (verified main mellea/backends/adapters/adapter.py:240) exposes five verbs: base_model_name, add_adapter, load_adapter, unload_adapter, list_adapters. The proposed design uses different verbs: load_peft_adapter, unload_peft_adapter, render_controls, set_request_adapter. This is a rename + introduce, not a pure rename.
(b) Per-call option merging. Each backend calls _simplify_and_merge on every adapter call. Duplicated logic and a known precedence bug source (PR #972 was a fix for this).
Agreed design
(a) AdapterMixin verb rename/narrow
AdapterMixin after this issue:
AdapterMixin:
load_peft_adapter / unload_peft_adapter # LocalFile reality
render_controls # Embedded reality
set_request_adapter # ServerMediated reality
Map existing implementations onto new verbs case-by-case. base_model_name and list_adapters: remove if unused after 1.A; otherwise relocate with documented justification. Bindings (2.2/2.3) call into these from their prepare/activate/etc.
Backends affected: LocalHFBackend, OpenAIBackend (the two adapter-supporting backends). Not affected: OllamaBackend, WatsonxBackend, LiteLLMBackend.
(b) Option resolution
New mellea/backends/_options.py:resolve_model_options — documents and implements precedence: caller-passed model_options= > helper defaults > backend defaults. Both adapter-supporting backends replace _simplify_and_merge with resolve_model_options on the adapter call path.
IntrinsicMetricsPlugin
New plugin at mellea/core/plugins/intrinsic_metrics.py. Three OTel metrics:
mellea.intrinsic.invocations — counter; labels: name, revision, binding_type, adapter_type, outcome (success/schema_error/error)
mellea.intrinsic.phase_duration_ms — histogram; labels: name, phase (prepare/activate/generate/parse/deactivate)
mellea.intrinsic.parse_failures — counter; labels: name, revision. Schema-drift detector: a climbing counter against (name, revision) means a breaking schema change was pushed upstream. Increments on each AdapterSchemaMismatchError.
Auto-wired via the existing TokenMetricsPlugin registration pattern.
Observability doc
New docs/dev/adapter_observability.md — span tree structure, metric labels, parse_failures schema-drift detector pattern, MELLEA_TRACE_CONTENT content-capture gate. Phases 2.2/2.3 add span emission details; this issue writes the structure.
Out of scope
Binding implementations (2.2, 2.3), span instrumentation (lives where verbs are implemented), HF embedded adapter support (#1018).
Acceptance criteria
Test plan
- Existing backend tests pass
resolve_model_options precedence tests: each pair (caller-vs-helper, caller-vs-backend, helper-vs-backend)
- Verb set test: assert
AdapterMixin has exactly the four verbs
IntrinsicMetricsPlugin unit tests:
test_invocations_counter_emits_on_success
test_invocations_counter_emits_schema_error_outcome
test_parse_failures_counter_increments
test_phase_duration_histogram_emits_for_each_phase
Breaking changes
AdapterMixin verb rename — downstream backends extending AdapterMixin must update. Migration table (old → new verb names) in changelog.
_simplify_and_merge removal from adapter call path — internal API only.
References
Parent epic: #929
Design proposal: PR #1080 §13, §16 Phase 2
Phase: 2 (Wave 3 — start after 1.A merges, in parallel with 1.B/1.C/1.D)
Depends on: 1.A
Blocks: 2.2, 2.3
Problem
Two related backend-surface concerns that touch the same files:
(a) Mixin verb mismatch.
AdapterMixin(verified mainmellea/backends/adapters/adapter.py:240) exposes five verbs:base_model_name,add_adapter,load_adapter,unload_adapter,list_adapters. The proposed design uses different verbs:load_peft_adapter,unload_peft_adapter,render_controls,set_request_adapter. This is a rename + introduce, not a pure rename.(b) Per-call option merging. Each backend calls
_simplify_and_mergeon every adapter call. Duplicated logic and a known precedence bug source (PR #972 was a fix for this).Agreed design
(a) AdapterMixin verb rename/narrow
AdapterMixinafter this issue:Map existing implementations onto new verbs case-by-case.
base_model_nameandlist_adapters: remove if unused after 1.A; otherwise relocate with documented justification. Bindings (2.2/2.3) call into these from theirprepare/activate/etc.Backends affected:
LocalHFBackend,OpenAIBackend(the two adapter-supporting backends). Not affected:OllamaBackend,WatsonxBackend,LiteLLMBackend.(b) Option resolution
New
mellea/backends/_options.py:resolve_model_options— documents and implements precedence: caller-passedmodel_options=> helper defaults > backend defaults. Both adapter-supporting backends replace_simplify_and_mergewithresolve_model_optionson the adapter call path.IntrinsicMetricsPlugin
New plugin at
mellea/core/plugins/intrinsic_metrics.py. Three OTel metrics:mellea.intrinsic.invocations— counter; labels:name,revision,binding_type,adapter_type,outcome(success/schema_error/error)mellea.intrinsic.phase_duration_ms— histogram; labels:name,phase(prepare/activate/generate/parse/deactivate)mellea.intrinsic.parse_failures— counter; labels:name,revision. Schema-drift detector: a climbing counter against(name, revision)means a breaking schema change was pushed upstream. Increments on eachAdapterSchemaMismatchError.Auto-wired via the existing
TokenMetricsPluginregistration pattern.Observability doc
New
docs/dev/adapter_observability.md— span tree structure, metric labels,parse_failuresschema-drift detector pattern,MELLEA_TRACE_CONTENTcontent-capture gate. Phases 2.2/2.3 add span emission details; this issue writes the structure.Out of scope
Binding implementations (2.2, 2.3), span instrumentation (lives where verbs are implemented), HF embedded adapter support (#1018).
Acceptance criteria
AdapterMixinexposes exactly the four verbs above; old verbs removed or relocated with documented justificationLocalHFBackendandOpenAIBackendimplement the new verb setresolve_model_optionsexists, documented, testedresolve_model_optionsinstead of_simplify_and_mergeon adapter call pathIntrinsicMetricsPluginregisterable like existing pluginsmellea.intrinsic.parse_failuresincrements onAdapterSchemaMismatchErrordocs/dev/adapter_observability.mdwritten; covers span tree, metric labels,parse_failurespattern,MELLEA_TRACE_CONTENTgate; markdownlint passesAdapterMixinexposes exactly the four verbs (catches accidental re-additions)IntrinsicMetricsPluginusing synthetic OTel exporterruff format,ruff check,mypycleanTest plan
resolve_model_optionsprecedence tests: each pair (caller-vs-helper, caller-vs-backend, helper-vs-backend)AdapterMixinhas exactly the four verbsIntrinsicMetricsPluginunit tests:test_invocations_counter_emits_on_successtest_invocations_counter_emits_schema_error_outcometest_parse_failures_counter_incrementstest_phase_duration_histogram_emits_for_each_phaseBreaking changes
AdapterMixinverb rename — downstream backends extendingAdapterMixinmust update. Migration table (old → new verb names) in changelog._simplify_and_mergeremoval from adapter call path — internal API only.References
render_controlsrender_controlspost-2.1