feat(provider): reuse-or-separate credential flow for multi-instance providers - #281
Merged
Brian Krabach (bkrabach) merged 1 commit intoAug 27, 2026
Merged
Conversation
…providers
When adding a second instance of the same provider type, the wizard's
env-var-collision check unconditionally forced a distinct credential
name, with no way to intentionally share a credential across instances
(e.g. two Anthropic instances -- one pinned to Opus, one to Sonnet --
that both authenticate with the same account/key).
This offers a reuse-or-separate choice instead, defaulting to reuse:
- Reuse (default): binds the new instance to the SAME ${ENV_VAR} as the
existing instance. The secret field is never prompted for or
overwritten -- only the placeholder is persisted, so both instances
resolve the same stored credential at runtime.
- Separate: derives/validates a distinct env var (unchanged from
today's collision flow) and may be left blank to persist the
placeholder UNSET for runtime injection (shell / CI / DTU
passthrough), or populated through the existing secure-storage flow
(KeyManager.save_key), unchanged.
- Editing an existing instance recovers its binding from the stored
placeholder, including "shared" when another configured instance
still references the same env var -- the reuse invariant survives
edits, not just the initial add.
- Non-interactive mode carries the same semantics: an explicit binding
(shared or separate) is exempt from the existing collision fail-loud
guard, since it is intentional rather than an accidental default-name
collision.
- Runtime hardening: a provider instance's required secret placeholder
that resolves to unset now fails loudly before session mount, instead
of silently expanding to "" and letting the provider module fall back
to its own ambient credential (which would route a "separate"
instance through a DIFFERENT account's key). Optional/keyless secret
fields are unaffected.
No settings schema, amplifier-core, or provider-module contract
changes -- this is app-CLI wizard/runtime policy only.
Tests cover all four spec paths (shared / separate / unset-for-runtime-
injection / edit-preservation) plus the non-interactive parity and
runtime fallback-hardening cases. Full suite: 1469 passed, 1 skipped,
13 deselected, 1 xfailed.
This design was independently validated across 10 reference
implementations produced in an internal first-turn evaluation of this
exact issue; this change follows the most consistent design among them
(a named CredentialDecision/binding-mode contract) and additionally
folds in the runtime unset-fallback hardening one of those
implementations added after adversarial review.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Collaborator
Author
|
Admin merge — self-authored PR, merged at explicit user direction. This PR cannot go through the normal self-approval flow (GitHub prevents authors from approving their own PRs). Per the established maintainer admin-merge pattern (precedent: gemini#39, foundation#317–#320, all merged this way this week), this is being merged directly at the user's explicit direction, with the rationale documented here for the record. Basis for merge:
CI: all checks green (pytest across ubuntu/macos/windows × py3.11/3.12, integration suites, license/cla). Merging via |
Brian Krabach (bkrabach)
pushed a commit
that referenced
this pull request
Aug 28, 2026
Inline /amplifier-config skill (root provider selection vs spawned-session routing vs bundle composition vs settings-scope precedence): inspects effective config with provenance, proposes smallest safe change with blast-radius/rollback, applies only authorized edits. Verified against this week's changes (#284 wizard None-default rendering, provider-openai#69 model-gated ConfigFields, #281 multi-instance credential flow) -- none hardcoded in SKILL.md; it correctly defers version-sensitive facts to live inspection + app-cli:cli-expert. Wiring confirmed via auto-discovery in _ensure_default_skills_dirs() (same mechanism as goalify/goal-batch/ten-lane-highway) and new tests (test_packaged_amplifier_config_discovery_and_invocation, test_amplifier_config_skill_contract). Full suite: 1493 passed (main's 1491 + 2 new tests), zero regressions. Admin-merge at team direction: fork PR (no push access to author's branch to rebase), ruleset requires 1 approving review with none yet recorded, CI 9/9 green, content review complete -- see PR comment for full due-diligence writeup. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Today,
provider add(and themanage/init dashboard "add" paths) treat a second instance of the same provider type as a hard credential collision: it always forces a distinct env var name, with no way to say "this new instance should authenticate with the same key as the existing one." There's no way to add, e.g., a second Anthropic instance pinned to a different model but sharing the sameANTHROPIC_API_KEY— the wizard renames it into a new var every time, splitting one account's credential across two names for no reason.The strict collision check shipped in #225 (
_prompt_env_var_collision/_resolve_env_var_overridesinamplifier_app_cli/commands/provider.py, with supporting logic inprovider_config_utils.py).What this does
When adding another instance of a provider type whose default credential env var is already claimed, the wizard now offers a choice instead of forcing a rename:
<EXISTING_VAR>(default) — binds the new instance to the same${ENV_VAR}. The secret field is never prompted for or overwritten; only the placeholder is persisted, so both instances resolve the same stored credential at runtime.KeyManager.save_key), or leave it blank to persist the placeholder UNSET for runtime injection (shell / CI / DTU passthrough).Editing an existing instance recovers its binding from the stored placeholder (including "shared" when another configured instance still references the same var), so the reuse invariant survives edits, not just the initial add. Non-interactive mode carries the same semantics — an explicit binding (shared or separate) is exempt from the existing collision fail-loud guard, since it's intentional.
Runtime hardening (new, beyond the wizard): a provider instance's required secret placeholder that resolves to unset now fails loudly before session mount, instead of silently expanding to
""and letting the provider module fall back to its own ambient env var — which would otherwise route a "separate" instance through a different account's key. Optional/keyless secret fields (e.g. a local Chat Completions server) are unaffected.No settings schema,
amplifier-core, or provider-module contract changes — this is app-CLI wizard/runtime policy only.Testing
New/updated coverage in
tests/test_provider_instance_credentials.py(TestReuseVsSeparateCredentialBinding) and a newtests/test_runtime_credential_validation.py, covering all four spec paths plus non-interactive parity and the runtime hardening:binding_modes={"shared": ...}so the edit never re-prompts for/overwrites the common secret.resolve_bundle_config()); optional/keyless fields, shared bindings with a set var, and missing provider metadata are all confirmed not to raise.Full suite:
1469 passed, 1 skipped, 13 deselected, 1 xfailed. Ruff/pyright clean (no new lint findings beyond pre-existing baseline warnings, confirmed by diffing againstmainbefore these changes).This design was validated across 10 independent reference implementations produced in an internal first-turn evaluation of this exact issue, and follows the most consistent design among them (a named
CredentialDecision/binding-mode contract), additionally folding in the runtime unset-fallback hardening one of those implementations added after adversarial review.Conflict note
Draft PR #234 (full-screen interactive TUI runtime) also touches
amplifier_app_cli/commands/provider.pyandamplifier_app_cli/provider_config_utils.py. Since #234 is a draft, I haven't coordinated a rebase — expect a merge conflict when #234 is updated to targetmainafter this lands, in both files' provider-add/edit code paths.🤖 Generated with Amplifier