You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue previously asked EmbeddedBinding to implement the four weights verbs
(prepare/activate/deactivate/release). It no longer does. The conclusion in #1486, agreed by @psschwei and @jakelorocco, is that an embedded adapter has no weights
lifecycle to manage — it is already in the model, and turning it on is a field on the
outgoing request. So EmbeddedBinding gets one method, apply_activation, not four
verbs.
The two dead activation methods that the old scope was built around
(render_controls, set_request_adapter) are removed here — see "Removals" below.
The contract/reformatting half of #1486 is a separate axis and is tracked in #1516;
the two are independent and can proceed in parallel.
⚠️Update 2026-08-11: #1464's doc/CI half has landed; span emission still awaits #1466.
The rule — library code fires a hook, a plugin in mellea/telemetry/tracing_plugins.py emits the span — is now written down in docs/docs/observability/tracing.md,
and test/telemetry/test_tracing_import_boundary.py enforces in CI that
nothing under mellea/backends/ imports mellea.telemetry.tracing
directly. docs/dev/adapter_observability.md — the stale note that taught #1141 the wrong pattern, leading to the PR #1454 revert — has been deleted
entirely (#1483); its still-current content moved into code docstrings, the
published docs, and the mellea-telemetry internal reference. Every
reference to that file below is stale as a result — see the inline notes.
There is still a structural blocker: ADAPTER_FUNCTION_INVOCATION_COMPLETE and ADAPTER_FUNCTION_PHASE_COMPLETE have no pre/start sibling, so no plugin can
open these spans until #1466 adds the start hooks.
Practical effect: the implementation half of this issue remains
unblocked and can proceed. The span naming below is now settled (see the
2026-08-11 note under "OTel spans") — what's still missing is the mechanism,
which is #1466's job. Do not implement the span criteria as written until #1466 adds the missing start hooks.
⚠️Correction, 2026-08-18: the doc/CI citations two paragraphs up are dead.
PR #1545 — which would have added the #how-spans-are-produced section to docs/docs/observability/tracing.md and the new test/telemetry/test_tracing_import_boundary.py CI guard — was closed
unmerged on 2026-08-17. Neither exists on main: tracing.md has no such
section and doesn't mention adapters, and the test file isn't in the repo.
The epic's own tracker (#929) already records #1464 as intentionally closed
without that doc/CI addition, since the underlying source guidance was judged
sufficient — this issue's 2026-08-11 note was simply never updated to match.
The docs/dev/adapter_observability.md deletion (#1483) is real and
unaffected by this correction. Practical effect is unchanged: the
implementation half of this issue is still unblocked; there is just no
written doc/CI enforcement of the hook/plugin rule to point to.
Problem
Activating an embedded adapter has no home in the codebase, so it was written inline into a
backend. mellea/backends/openai.py:699 carries the whole of it:
ifisinstance(adapter, EmbeddedIntrinsicAdapter):
chat_template_kwargs=extra_body.pop("chat_template_kwargs", {}) or {}
chat_template_kwargs["adapter_name"] =action.intrinsic_nameextra_body["chat_template_kwargs"] =chat_template_kwargs# The rewriter config may set `model` to the adapter name, but# for embedded adapters the actual model is self._model_id.api_params.pop("model", None)
Any new backend supporting embedded adapters has to copy that block — including the
non-obvious api_params.pop("model", None) — or embedded adapters silently fail to
activate. EmbeddedBinding is meanwhile a stub raising NotImplementedError.
Reality B (Granite Switch's embedded adapters; today's EmbeddedIntrinsicAdapter, used by
the OpenAI backend per PR #881) needs that block to become a method on the binding.
Agreed design
One method (Reality B — adapters are part of the base-model weights, no separate file)
apply_activation(request, identity) — edits the outgoing request so the model uses the
named adapter. For Granite Switch that is the chat_template_kwargs["adapter_name"] write
plus the model removal currently inlined at openai.py:699.
There is no prepare (nothing to download), no release (nothing to unload), and no activate/deactivate pair (nothing is toggled — each request either carries the
activation field or does not). Activation varies by deployment, which is why it lives on
the binding; request reformatting varies by capability and comes from the adapter's io_contract instead (#1516).
Currently only the OpenAI backend supports Reality B (per PR #881). HF backend will gain
support via #1018, which is the acceptance test for this shape: the same capability on a
different deployment should need only a binding, with no reformatting written twice.
Removals
render_controls and set_request_adapter were the previous attempt at a home for
activation. Neither does anything, and what actually activates an embedded adapter today is
the inline block above, bypassing both. apply_activation replaces them. Remove:
AdapterMixin.render_controls (mellea/backends/adapters/adapter.py:432) and AdapterMixin.set_request_adapter (adapter.py:454) — both raise NotImplementedError
The one override, an explicit no-op that only logs (mellea/backends/openai.py:278)
Their tests — test/backends/test_adapters/test_adapter_mixin.py (in the verb list and
in test_openai_backend_overrides_only_render_controls, test_set_request_adapter_has_no_implementation) and test/backends/test_adapters/test_embedded_adapter.py::test_render_controls_is_noop
load_peft_adapter and unload_peft_adapter are unaffected and stay as they are.
OTel spans
Mechanism pending #1466 — see the note at the top. The span shape below
is what we want; how it is produced is what changes. Note the span set shrinks
with the scope — there is no prepare/deactivate to instrument.
Child spans under the adapter_function parent (set up in 2.2):
adapter_function.activate — attributes: mellea.adapter_function.name, mellea.adapter_function.binding_type="embedded", mellea.adapter_function.source
(base model identifier), and the name of the activation field written into the request
Naming, corrected 2026-08-11. This section previously said intrinsic.call / intrinsic.activate with intrinsic.* attributes. That spelling has never existed in
the codebase — it originated in #1141 and propagated. The merged convention, from #1140 / PR #1422, is adapter_function for the parent, adapter_function.<phase>
for children, mellea.adapter_function.* for attributes — matching the existing mellea.adapter_function.invocations/.phase_duration/.parse_failures metrics in mellea/telemetry/metrics.py. Metric labels are bare (name, phase, revision)
while span attributes carry the mellea.* prefix — same values, different surface,
each following its own signal type's convention; don't harmonize the two. Do not
reintroduce the intrinsic.* names. #1466 owns the mechanism and should document the
emitted spans in docs/docs/observability/tracing.md alongside the other span
families, now that #1483 has deleted docs/dev/adapter_observability.md.
Docs updates (ship with this PR)
docs/docs/advanced/intrinsics.md — add Embedded construction example: Adapter(weights=EmbeddedBinding.from_base_model(backend)); note which backends support which bindings (backend × reality matrix)
AGENTS.md §14 — update with normalised post-parse shape reference table for both bindings
Out of scope
HF backend embedded adapters (#1018), other realities (2.2, 3.1), the output-contract
resolution change (#1516).
Acceptance criteria
apply_activation implemented for EmbeddedBinding
The inline isinstance(adapter, EmbeddedIntrinsicAdapter) block at openai.py:699 is gone; the OpenAI backend activates through the binding
The api_params.pop("model", None) behaviour is preserved by the binding, with a test that fails if it is dropped
render_controls and set_request_adapter removed, along with the openai.py override and their tests (no adapter_observability.md entries to remove — the file no longer exists)
No prepare/activate/deactivate/release methods added to EmbeddedBinding
OpenAI backend continues to support embedded adapters via the new binding
Existing Granite Switch tests pass through the new binding
None for end users. Replaces internal stubs. OpenAI-backend Granite Switch behaviour
preserved. render_controls and set_request_adapter are removed, but neither has a
working implementation or any caller, so nothing can depend on them.
Parent epic: #929
Design proposal: PR #1080 §8.2, §9.2, §10
Design discussion: #1486 (rescoped this issue — see the note below)
Phase: 2 (Wave 4 — start after 2.1 merges, in parallel with 2.2)
Depends on: 0.1, 2.1
Blocks: 4.1
Problem
Activating an embedded adapter has no home in the codebase, so it was written inline into a
backend.
mellea/backends/openai.py:699carries the whole of it:Any new backend supporting embedded adapters has to copy that block — including the
non-obvious
api_params.pop("model", None)— or embedded adapters silently fail toactivate.
EmbeddedBindingis meanwhile a stub raisingNotImplementedError.Reality B (Granite Switch's embedded adapters; today's
EmbeddedIntrinsicAdapter, used bythe OpenAI backend per PR #881) needs that block to become a method on the binding.
Agreed design
One method (Reality B — adapters are part of the base-model weights, no separate file)
apply_activation(request, identity)— edits the outgoing request so the model uses thenamed adapter. For Granite Switch that is the
chat_template_kwargs["adapter_name"]writeplus the
modelremoval currently inlined atopenai.py:699.There is no
prepare(nothing to download), norelease(nothing to unload), and noactivate/deactivatepair (nothing is toggled — each request either carries theactivation field or does not). Activation varies by deployment, which is why it lives on
the binding; request reformatting varies by capability and comes from the adapter's
io_contractinstead (#1516).Currently only the OpenAI backend supports Reality B (per PR #881). HF backend will gain
support via #1018, which is the acceptance test for this shape: the same capability on a
different deployment should need only a binding, with no reformatting written twice.
Removals
render_controlsandset_request_adapterwere the previous attempt at a home foractivation. Neither does anything, and what actually activates an embedded adapter today is
the inline block above, bypassing both.
apply_activationreplaces them. Remove:AdapterMixin.render_controls(mellea/backends/adapters/adapter.py:432) andAdapterMixin.set_request_adapter(adapter.py:454) — both raiseNotImplementedErrormellea/backends/openai.py:278)test/backends/test_adapters/test_adapter_mixin.py(in the verb list andin
test_openai_backend_overrides_only_render_controls,test_set_request_adapter_has_no_implementation) andtest/backends/test_adapters/test_embedded_adapter.py::test_render_controls_is_noopTheir entries in— moot; that filedocs/dev/adapter_observability.mdwas deleted entirely by docs: delete docs/dev/, migrate current content, close #1464's telemetry-doc gap #1483, so there's nothing left to strike from it
load_peft_adapterandunload_peft_adapterare unaffected and stay as they are.OTel spans
Mechanism pending #1466 — see the note at the top. The span shape below
is what we want; how it is produced is what changes. Note the span set shrinks
with the scope — there is no
prepare/deactivateto instrument.Child spans under the
adapter_functionparent (set up in 2.2):adapter_function.activate— attributes:mellea.adapter_function.name,mellea.adapter_function.binding_type="embedded",mellea.adapter_function.source(base model identifier), and the name of the activation field written into the request
Docs updates (ship with this PR)
docs/docs/advanced/intrinsics.md— add Embedded construction example:Adapter(weights=EmbeddedBinding.from_base_model(backend)); note which backends support which bindings (backend × reality matrix)AGENTS.md §14— update with normalised post-parse shape reference table for both bindingsOut of scope
HF backend embedded adapters (#1018), other realities (2.2, 3.1), the output-contract
resolution change (#1516).
Acceptance criteria
apply_activationimplemented forEmbeddedBindingisinstance(adapter, EmbeddedIntrinsicAdapter)block atopenai.py:699is gone; the OpenAI backend activates through the bindingapi_params.pop("model", None)behaviour is preserved by the binding, with a test that fails if it is droppedrender_controlsandset_request_adapterremoved, along with theopenai.pyoverride and their tests (noadapter_observability.mdentries to remove — the file no longer exists)prepare/activate/deactivate/releasemethods added toEmbeddedBindingadapter_function.activatespan emitted withmellea.adapter_function.binding_type="embedded"— emitted from a tracing plugin, not from library code; blocked on feat(telemetry): emit adapter-function spans from a tracing plugin (Epic #929 Phase 2) #1466 (naming settled — see the 2026-08-11 note above)AdapterFunctionMetricsPlugincounters (mellea.adapter_function.invocations,.parse_failures,.phase_duration) increment correctly for Embedded callsdocs/docs/observability/tracing.md— feat(telemetry): emit adapter-function spans from a tracing plugin (Epic #929 Phase 2) #1466's responsibility, once it implements emissiondocs/docs/advanced/intrinsics.mdupdated: Embedded construction example + backend × reality matrixAGENTS.md §14updated with normalised post-parse shape reference tableruff format,ruff check,mypycleanTest plan
Unit tests (
test/backends/test_adapters/test_embedded_binding.py) with mocked OpenAI backend:test_apply_activation_sets_adapter_name— writeschat_template_kwargs["adapter_name"]test_apply_activation_removes_model_param— theapi_params.pop("model")behaviour; fails loudly if regressedtest_apply_activation_preserves_existing_chat_template_kwargs— does not clobber caller-supplied keystest_no_weights_verbs_on_embedded_binding— assertsprepare/activate/deactivate/releaseare absenttest_multi_call_isolation— activation from call N does not leak into call N+1test_activate_span_binding_type_is_embeddedtest_metrics_invocation_counter_increments_for_embeddedRemoval gate:
grepforrender_controlsandset_request_adapteracrossmellea/,test/,docs/— no hitsIntegration test (
test/backends/test_adapters/test_embedded_integration.py,@pytest.mark.integration,@pytest.mark.openai):OpenAIBackend × EmbeddedBinding × lora/aloraagainst Granite Switch — activation through the binding; existing Granite Switch tests passDocs validation:
npx markdownlint-cli2 "docs/docs/advanced/intrinsics.md" "docs/docs/observability/tracing.md" "AGENTS.md"Breaking changes
None for end users. Replaces internal stubs. OpenAI-backend Granite Switch behaviour
preserved.
render_controlsandset_request_adapterare removed, but neither has aworking implementation or any caller, so nothing can depend on them.
References
EmbeddedBindingagainstLocalHFBackend