Skip to content

Conversation

@samueljklee
Copy link
Contributor

Summary

  • Fix _invalidate_all_install_state() to correctly find the activator when the resolver is wrapped in an AppModuleResolver

Problem

The self-healing detection was working, but the actual fix (invalidating install state) was always failing silently with "No activator found" warning. This meant missing dependency issues like "tool-web module has a missing aiohttp dependency" would never self-heal.

Root Cause

At line 248-251 of session_runner.py, the resolver gets wrapped with AppModuleResolver, which stores the original BundleModuleResolver as self._bundle. However, _invalidate_all_install_state() was looking for _activator directly on the AppModuleResolver instead of unwrapping to get the BundleModuleResolver first.

Fix

Unwrap AppModuleResolver to get BundleModuleResolver before accessing _activator. This follows the same pattern already used in session_spawner.py.

Test plan

  • Verify self-healing now works when dependency issues are detected
  • Confirm the fix correctly handles both wrapped (AppModuleResolver) and direct (BundleModuleResolver) cases

🤖 Generated with Amplifier

The _invalidate_all_install_state() function was looking for _activator
directly on the resolver, but when the resolver is an AppModuleResolver,
the _activator lives on the wrapped BundleModuleResolver (stored as _bundle).

This caused self-healing to always fail with 'No activator found' warning,
meaning missing dependency issues would never actually self-heal even though
detection worked correctly.

Fix by unwrapping AppModuleResolver first, following the same pattern
already used in session_spawner.py.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
@samueljklee
Copy link
Contributor Author

PR Review: ✅ Approve

The Problem

The self-healing mechanism was detecting dependency issues correctly, but the actual fix (invalidating install state) was silently failing. The _invalidate_all_install_state() function couldn't find the activator because:

  1. At lines 248-251, the BundleModuleResolver gets wrapped in an AppModuleResolver:

    prepared_bundle.resolver = AppModuleResolver(
        bundle_resolver=prepared_bundle.resolver,  # stored as self._bundle
        settings_resolver=fallback_resolver,
    )
  2. The _activator lives on the BundleModuleResolver, but the code was looking for it directly on the AppModuleResolver wrapper.

The Fix

# Before:
activator = getattr(resolver, "_activator", None)

# After:
bundle_resolver = getattr(resolver, "_bundle", resolver)  # unwrap if wrapped
activator = getattr(bundle_resolver, "_activator", None)

The getattr(resolver, "_bundle", resolver) pattern elegantly handles both cases:

  • If wrapped (AppModuleResolver): returns resolver._bundle (the BundleModuleResolver)
  • If direct (BundleModuleResolver): returns resolver itself (no _bundle attribute)

Assessment

Aspect Verdict
Root cause analysis ✅ Correct
Fix correctness ✅ Correct
Pattern consistency ✅ Follows existing pattern in session_spawner.py
Code change size ✅ Minimal (+4/-1)
Risk ✅ Low - handles both wrapped and direct cases

Ship it. The fix is correct, minimal, and follows established patterns in the codebase.

@colombod
Copy link
Contributor

colombod commented Jan 28, 2026

Is there a better way than accessing protected state in python, this is something easy to break since in foundation this could change even if we constrain to keeping the public surface locked. Maybe we review the proposed refactored change in foundation :microsoft/amplifier-foundation#41

colombod added a commit to colombod/amplifier-bundle-python-dev that referenced this pull request Jan 28, 2026
Add new principle to PYTHON_BEST_PRACTICES.md addressing the anti-pattern
of accessing private attributes via getattr(obj, '_internal', ...).

This documents:
- Why accessing private state is dangerous
- Real example from PR #70 showing the anti-pattern
- Correct approaches (expose public API, redesign)
- Litmus test for detecting the problem

Motivated by microsoft/amplifier-app-cli#70 which demonstrates
the anti-pattern spreading across the codebase.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
colombod added a commit to colombod/amplifier-core that referenced this pull request Jan 28, 2026
Add 'Accessing Private State (Critical Anti-Pattern)' subsection to
DESIGN_PHILOSOPHY.md under Module Development anti-patterns.

Documents:
- Why getattr(obj, '_private', ...) is dangerous
- Code example showing the anti-pattern vs correct approach
- Guidance on when internal access seems needed
- Litmus test for detecting protocol violations

Motivated by microsoft/amplifier-app-cli#70 which shows this
anti-pattern spreading with 'Pattern from X' comments.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
colombod added a commit to colombod/amplifier-core that referenced this pull request Jan 28, 2026
Add 'Violating Encapsulation' subsection to Module Development anti-patterns
in DESIGN_PHILOSOPHY.md.

Documents the general principle that accessing internal state instead of
using public contracts creates fragile code. The fix for missing APIs is
to extend the interface, not bypass it.

Motivated by microsoft/amplifier-app-cli#70.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
colombod added a commit to colombod/amplifier-core that referenced this pull request Jan 28, 2026
Add bullet to 'In Module Development' anti-patterns about bypassing
public interfaces to access internal state.

Motivated by microsoft/amplifier-app-cli#70.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
colombod added a commit to colombod/amplifier-bundle-python-dev that referenced this pull request Jan 28, 2026
Add principle to PYTHON_BEST_PRACTICES.md about not accessing private
attributes from outside a class. Includes table of anti-patterns and
code example.

Motivated by microsoft/amplifier-app-cli#70.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
@samueljklee
Copy link
Contributor Author

Changes here have been brought into #71 . Closing this PR.

colombod added a commit to colombod/amplifier-foundation that referenced this pull request Jan 28, 2026
Add good/bad examples showing why bypassing public interfaces creates
fragile code. The fix for missing APIs is to extend the interface,
not reach into internal state.

Motivated by microsoft/amplifier-app-cli#70.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
colombod added a commit to colombod/amplifier-foundation that referenced this pull request Jan 28, 2026
Add good/bad examples for bypassing vs using public interfaces.

Motivated by microsoft/amplifier-app-cli#70.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
bkrabach pushed a commit to microsoft/amplifier-foundation that referenced this pull request Jan 28, 2026
Add good/bad examples for bypassing vs using public interfaces.

Motivated by microsoft/amplifier-app-cli#70.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants