Skip to content

feat: First-class multi-instance provider support#41

Merged
bkrabach merged 3 commits intomainfrom
feat/multi-instance-providers
Mar 8, 2026
Merged

feat: First-class multi-instance provider support#41
bkrabach merged 3 commits intomainfrom
feat/multi-instance-providers

Conversation

@bkrabach
Copy link
Collaborator

@bkrabach bkrabach commented Mar 8, 2026

Summary

Adds first-class multi-instance provider support to the kernel, enabling multiple instances of the same provider module with different configs.

Changes

  • Loader cache fix — caches resolved entry points but creates fresh mount closures per instance (prevents second instance from getting first's config)
  • Session init instance_id support — extracts instance_id from mount plan, remaps provider mount names after self-mount
  • Multi-instance validation — rejects duplicate modules without instance_id

Mount Plan Schema

providers:
  - module: provider-anthropic
    instance_id: anthropic-sonnet
    config: { default_model: claude-sonnet-4-6, priority: 1 }
  - module: provider-anthropic
    instance_id: anthropic-opus
    config: { default_model: claude-opus-4-6, priority: 2 }

No Rust changes. No proto changes. No provider module changes required.

Stats

  • 3 commits, 12 new tests (488 total, 1 skipped)

⚠️ Release Mandate

After merge: version bump + tag + push required per release mandate.

bkrabach added 3 commits March 7, 2026 20:28
…stance

Cache the resolved entry point (raw mount function) in _loaded_modules,
not the config-bound closure. Each load() call now creates a fresh closure
wrapping the cached raw function with the current config.

This fixes the bug where two provider entries sharing the same module_id
but with different configs would silently reuse the first instance's closure,
discarding the second config entirely.

Changes:
- _load_entry_point(): remove config param, return raw mount_fn (no closure)
- _load_filesystem(): remove config param, return raw mount_fn (no closure)
- _load_direct(): cache raw fn, create fresh closure with current config
- load() cache hit: create fresh closure from cached raw fn with current config
- load() resolver path: cache raw fn, create fresh closure with current config

Tests added (tests/test_loader_cache.py):
- test_loader_returns_different_closures_for_same_module_different_config
- test_loader_caches_entry_point_resolution
- test_loader_backward_compat_single_instance
- Extract instance_id from provider config in both session init paths
- After a provider self-mounts under its default name, remap the mount
  entry to use instance_id as the key when instance_id is specified
- Default name is derived by stripping 'provider-' prefix from module_id
- Changes applied to both session.py (PyAmplifierSession) and
  _session_init.py (Rust bridge path)
- Add tests/test_multi_instance.py with 5 tests covering:
  - single instance, no instance_id → no remapping (backwards compat)
  - instance_id remapping removes default key
  - two providers same module, different instance_ids, both accessible
  - session.py path instance_id remapping
  - session.py path no instance_id, no remap
@bkrabach bkrabach merged commit 400e1bc into main Mar 8, 2026
5 checks passed
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.

1 participant