Parent epic: #929
Phase: 2
Depends on: #1141
Blocks: #1466 (generate / parse spans only)
Problem
Adapter-function generation does not run inside adapter_scope. LocalFileBinding
can now prepare / activate / deactivate / release a PEFT adapter, and
adapter_scope() demonstrably flips the real model's active adapter set — but the
model call in _generate_from_intrinsic still happens outside that scope, and the
standard generation path deactivates adapters first
(_generate_with_adapter_lock). So the lifecycle verbs and the generation they
exist to serve are not yet connected.
This was #1141's acceptance criterion 66 — "E2E adapter call (prepare →
activate → generate → deactivate → release) passes" — and it is not excluded by
#1141's out-of-scope list. It was deferred during implementation of PR #1454, and
the deferral is disclosed only in a test docstring
(test/backends/test_adapters/test_local_file_e2e.py) which refers to a follow-up
issue that was never created. This issue is that follow-up.
It is also not what the epic informally calls "4.1". 4.1 is #1144 (shim
removal plus the intrinsics_and_adapters.md rewrite), whose scope does not
mention generation, activation, scoping or locking. Without a separate issue this
work falls through the gap between the two.
Known constraint: the activation lock is not reentrant
_adapter_activation_lock returns _generation_lock, which is not reentrant.
Once generation runs inside adapter_scope and activate() re-acquires that
lock, it deadlocks. This was raised in PR #1454 review. Separately, the lock is
weaker than its docstring implies today — it does not serialise everything the
docstring claims — so the fix should reconcile behaviour and documentation rather
than only unblocking the deadlock.
Any solution needs to keep the current guarantee that a caller cannot observe
another caller's adapter active, which is what the existing
deactivate-before-generate behaviour buys.
Scope
- Route the model call in
_generate_from_intrinsic through adapter_scope, so
the prepared and activated adapter is the one used for generation.
- Resolve the activation/generation lock reentrancy, and align the docstring with
actual behaviour.
- Extend the existing e2e coverage so the full lifecycle is exercised with
generation inside the scope, and remove the deferral note from
test_local_file_e2e.py.
Out of scope
Acceptance criteria
References
Parent epic: #929
Phase: 2
Depends on: #1141
Blocks: #1466 (
generate/parsespans only)Problem
Adapter-function generation does not run inside
adapter_scope.LocalFileBindingcan now
prepare/activate/deactivate/releasea PEFT adapter, andadapter_scope()demonstrably flips the real model's active adapter set — but themodel call in
_generate_from_intrinsicstill happens outside that scope, and thestandard generation path deactivates adapters first
(
_generate_with_adapter_lock). So the lifecycle verbs and the generation theyexist to serve are not yet connected.
This was #1141's acceptance criterion 66 — "E2E adapter call (prepare →
activate → generate → deactivate → release) passes" — and it is not excluded by
#1141's out-of-scope list. It was deferred during implementation of PR #1454, and
the deferral is disclosed only in a test docstring
(
test/backends/test_adapters/test_local_file_e2e.py) which refers to a follow-upissue that was never created. This issue is that follow-up.
It is also not what the epic informally calls "4.1". 4.1 is #1144 (shim
removal plus the
intrinsics_and_adapters.mdrewrite), whose scope does notmention generation, activation, scoping or locking. Without a separate issue this
work falls through the gap between the two.
Known constraint: the activation lock is not reentrant
_adapter_activation_lockreturns_generation_lock, which is not reentrant.Once generation runs inside
adapter_scopeandactivate()re-acquires thatlock, it deadlocks. This was raised in PR #1454 review. Separately, the lock is
weaker than its docstring implies today — it does not serialise everything the
docstring claims — so the fix should reconcile behaviour and documentation rather
than only unblocking the deadlock.
Any solution needs to keep the current guarantee that a caller cannot observe
another caller's adapter active, which is what the existing
deactivate-before-generate behaviour buys.
Scope
_generate_from_intrinsicthroughadapter_scope, sothe prepared and activated adapter is the one used for generation.
actual behaviour.
generation inside the scope, and remove the deferral note from
test_local_file_e2e.py.Out of scope
EmbeddedBinding(feat(backends): EmbeddedBinding implements apply_activation (Granite Switch path); remove render_controls + set_request_adapter (Epic #929 Phase 2) #1142) andServerMediatedBinding.generate/parse— that is blocked on this issue andtracked in feat(telemetry): emit adapter-function spans from a tracing plugin (Epic #929 Phase 2) #1466.
Acceptance criteria
_generate_from_intrinsicperforms its model call insideadapter_scopesmoke-tested
activate()insideadapter_scopewith generation nestedcompletes, with a regression test that would deadlock under the current lock
_adapter_activation_lock/_generation_lockdocstrings match observedbehaviour
caller's adapter
test_local_file_e2e.pycovers prepare → activate → generate → deactivate →release, and its deferral note is gone
uv run pytest test/ -m "not qualitative"passes; the e2e test passes onGPU hardware
References
deadlock
adapter_scopeand the verb set