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
Provider discovery should be driven by provider profiles wherever possible instead of requiring a hard-coded Rust ProviderPlugin and ProviderDiscoverySpec per provider. PR #1313 (docker-agent) shows the current failure mode: adding one partner/provider requires a new YAML profile plus provider-specific Rust discovery, registry registration, alias handling, command detection, and tests. Medium term, partners such as Docker should be able to publish provider profiles from their own repositories and have OpenShell discover credentials, binaries, and command intent generically.
Technical Context
The current v1 discovery path is mostly a thin wrapper around environment-variable lookup. Most provider plugins define a ProviderDiscoverySpec { id, credential_env_vars } and call discover_with_spec, while v2 provider profiles already model the same credential env vars and policy binaries. That means the profile model has enough information for static env-var discovery today, but the CLI and TUI still use the hard-coded registry for provider type lists, credential env vars, --from-existing, and auto-provider command inference.
PR #1313 adds Docker Agent by extending both systems: a v2 profile in providers/docker-agent.yaml, plus v1-style Rust provider discovery and hard-coded command detection for docker agent. The useful behavior is generic: discover credential env vars from the profile, optionally detect local binaries, and infer provider type from command metadata.
Uses provider discovery for provider create --from-existing, provider update --from-existing, sandbox auto-provider creation, and command inference.
TUI provider creation
crates/openshell-tui/src/app.rs
Lists provider types and performs autodetection from the hard-coded registry.
Gateway policy composition
crates/openshell-server/src/grpc/policy.rs
Already composes policy from built-in or custom provider profiles once provider records exist.
Partner/provider profile data
providers/*.yaml
Defines credentials, endpoints, binaries, and categories for profile-backed provider v2 behavior.
Technical Investigation
Architecture Overview
Provider v1 discovery is local and registry-driven. ProviderRegistry::new() registers hard-coded plugins; callers ask the registry to discover credentials for a provider type. discover_with_spec then scans a static env-var list and returns a DiscoveredProvider with credential keys matching env-var names.
Provider v2 profiles are data-driven. Built-in YAML profiles are loaded through profiles.rs, custom profiles can be imported into the gateway store, and policy composition uses provider type/profile IDs to construct provider policy layers. The profile DTO already exposes credential_env_vars() by flattening credentials[*].env_vars, and profile binaries are preserved through proto conversion.
The missing bridge is discovery. CLI/TUI code does not ask profiles how to discover a provider. It asks ProviderRegistry, so every new discoverable provider still needs Rust registration even if the profile already contains the credential env vars and policy binaries.
Code References
Location
Description
crates/openshell-providers/src/lib.rs:46
ProviderDiscoverySpec only stores an id and env-var names. This duplicates profile credential env vars.
crates/openshell-providers/src/lib.rs:81
ProviderRegistry::new() hard-registers every supported discovery plugin. Partners cannot extend this without a core PR.
crates/openshell-providers/src/lib.rs:111
ProviderRegistry::discover_existing() fails with UnsupportedProvider for types that are not registered as plugins.
crates/openshell-providers/src/lib.rs:136
known_types() returns plugin keys, not built-in or imported provider profiles.
crates/openshell-providers/src/lib.rs:143
normalize_provider_type() is a hard-coded alias map. Custom provider profile IDs are not normalized here.
crates/openshell-providers/src/lib.rs:162
detect_provider_from_command() only inspects the first command basename, so shapes like docker agent need provider-specific Rust special-casing.
crates/openshell-providers/src/discovery.rs:6
discover_with_spec() is generic env-var scanning over ProviderDiscoverySpec. This can be profile-backed.
crates/openshell-providers/src/profiles.rs:283
ProviderTypeProfile::credential_env_vars() already derives the env-var scan list from profile credentials.
crates/openshell-cli/src/run.rs:3419
Sandbox command inference calls the hard-coded detect_provider_from_command() helper.
crates/openshell-cli/src/run.rs:3549
auto_create_provider() uses ProviderRegistry::discover_existing() and cannot discover custom profile-backed providers.
crates/openshell-cli/src/run.rs:4058
provider_create() can resolve custom profiles, but --from-existing still calls the hard-coded registry.
crates/openshell-cli/src/run.rs:4862
provider update --from-existing also uses hard-coded registry discovery.
crates/openshell-tui/src/app.rs:1769
TUI provider creation lists ProviderRegistry::known_types(), not provider profiles.
crates/openshell-tui/src/app.rs:1810
TUI credential prompts come from registry env vars instead of profile credentials.
crates/openshell-tui/src/app.rs:1849
TUI autodetect calls registry discovery.
crates/openshell-server/src/grpc/policy.rs:544
Gateway policy composition already resolves built-in or custom profiles from provider type/profile ID.
Current Behavior
A simple provider currently requires multiple core-code touch points:
Add profile YAML if it should participate in providers v2 policy composition.
Add a Rust provider module with a ProviderDiscoverySpec and ProviderPlugin implementation for env scanning.
Register the plugin in ProviderRegistry::new().
Update normalize_provider_type() for aliases.
Update detect_provider_from_command() if command inference is not a one-token executable basename.
Update CLI/TUI tests for the new hard-coded provider.
This is what PR #1313 does for Docker Agent. The actual desired discovery behavior is profile-shaped: DOCKER_ACCESS_TOKEN is an optional credential env var, and Docker-related binaries/commands indicate the provider may be useful even without a static token.
What Would Need to Change
Provider discovery should gain a generic profile-backed path:
Add a discover_from_profile(profile, context) helper that reads profile.credentials[*].env_vars and stores discovered values under the actual env-var key so existing injection behavior remains compatible.
Extend DiscoveryContext with binary/command existence checks if binary presence should be a discovery signal. PR feat(providers): add Docker Agent provider #1313's path_exists() is directionally useful, but the API should be shaped around profile discovery semantics rather than one provider.
Decide whether profile.binaries should double as host discovery paths. These paths are currently policy enforcement paths inside the sandbox; using them for host discovery is convenient but semantically leaky. A small explicit discovery block in the profile may be cleaner for command matching and host-side aliases.
Add generic command inference from profile metadata so profiles can express command shapes such as docker agent, not only executable basenames.
Change CLI provider create --from-existing, provider update --from-existing, and sandbox auto-provider creation to resolve the provider profile first and then run generic profile discovery. Keep plugin discovery as a fallback for advanced legacy cases.
Change TUI provider creation to list available profiles and derive credential rows/autodetect behavior from profile metadata instead of ProviderRegistry::known_types().
Keep provider-specific plugins only for discovery that cannot be expressed in profile data, such as reading provider-specific config files or future local OAuth cache importers.
Alternative Approaches Considered
Keep adding provider plugins: Lowest immediate implementation cost per provider, but it keeps every partner profile blocked on a core OpenShell PR and duplicates v2 profile metadata.
Use existing binaries directly for discovery: Minimal schema change, but conflates sandbox policy binaries with host discovery paths and does not model multi-token command shapes like docker agent cleanly.
Add explicit profile discovery metadata: More schema work, but it separates policy enforcement from local discovery and lets profiles express env vars, binary/path checks, command prefixes, and tokenless optional-credential discovery in a portable way.
Hybrid approach: Use profile credentials for generic env-var discovery immediately, keep plugin fallback for non-generic discovery, and add explicit discovery metadata for command/binary inference. This is the recommended direction.
Patterns to Follow
The providers v2 profile DTO pattern in profiles.rs should remain the source of truth for profile-defined fields and proto conversion.
Existing credential injection stores provider credentials by env-var key, so generic discovery should preserve keys like GITHUB_TOKEN, ANTHROPIC_API_KEY, or DOCKER_ACCESS_TOKEN rather than storing logical credential names.
Gateway policy composition already supports custom profile IDs; CLI/TUI changes should align with that existing server behavior instead of introducing a second notion of custom provider type.
Legacy provider aliases should continue to work for backwards compatibility, but aliases should not be the only way to discover or create providers.
Proposed Approach
Move common discovery to profile-backed helpers and treat hard-coded plugins as legacy or advanced-provider fallbacks. Profiles should be sufficient for the common partner use case: declare credentials, optional credential behavior, binaries/endpoints for policy, and optional command discovery metadata. CLI and TUI should resolve provider profiles from built-ins and the gateway store, then use the generic profile discovery path for --from-existing, provider auto-create, and credential prompts.
This would let a partner ship a provider profile without adding a Rust plugin for basic env-var discovery and command/binary matching. PRs like #1313 could shrink to profile data plus tests, or move entirely to partner-owned profile repositories once external profile distribution exists.
Scope Assessment
Complexity: Medium
Confidence: High for env-var discovery; medium for command/binary discovery schema because it needs a product decision
Estimated files to change: 6-10 core files plus tests
Issue type:feat
Decisions
Add an explicit profile discovery section for credential discovery instead of dual-purposing policy binaries.
Use credential-name references in discovery.credentials rather than duplicating env vars. credentials[*].env_vars remains the source of truth for which local env vars are scanned.
Scope providers v2 discovery to explicit credential discovery only. Do not add command inference or auto-attach semantics for providers v2.
Preserve current --from-existing behavior: fail when no credential or config material is discovered, even when profile credentials are optional. Empty providers should be created through explicit non-discovery flows.
CLI/TUI may fetch profiles from the gateway when needed. For CLI, this is acceptable for provider create --type <profile> --from-existing and related explicit provider operations.
When providers v2 is enabled, profile-backed discovery should be the discovery path. Do not fall back to v1 provider-plugin discovery for v2 profiles.
Do not introduce aliases for partner/custom profiles. Use exact profile IDs.
Keep provider attachment explicit in providers v2. Profile-backed discovery must not infer or attach providers from sandbox commands.
TUI support can land after the CLI/gateway implementation if needed.
Risks & Open Questions
Confirm whether the final proto field names should mirror the YAML shape exactly or use more explicit generated names. The intended YAML shape is credential-name based:
discovery.credentials references credentials[*].name; OpenShell then scans the referenced credential's env_vars. Do not duplicate env vars directly in discovery.
Should any legacy v1 command inference remain available only for legacy provider paths, or should enabling providers v2 disable it entirely for provider attachment?
How should custom provider profile fetch failures be surfaced in explicit CLI flows: unsupported profile, gateway unavailable, or profile missing?
Test Considerations
Unit test discover_from_profile() for env-var discovery, duplicate env vars, required vs optional credentials, empty env values, and tokenless optional-credential discovery.
Unit test binary/command discovery once the discovery metadata shape is chosen, including docker agent style command prefixes.
CLI integration coverage for provider create --from-existing using an imported custom profile with profile-defined env vars.
CLI integration coverage for provider update --from-existing using profile-derived discovery.
Sandbox auto-provider coverage proving command inference can create/attach a profile-backed provider without a hard-coded registry plugin.
TUI tests or focused unit coverage proving provider type selection and credential prompts are derived from profiles.
Regression tests for existing built-in providers and aliases such as gh, glab, and claude.
Created by spike investigation. Use build-from-issue to plan and implement.
Problem Statement
Provider discovery should be driven by provider profiles wherever possible instead of requiring a hard-coded Rust
ProviderPluginandProviderDiscoverySpecper provider. PR #1313 (docker-agent) shows the current failure mode: adding one partner/provider requires a new YAML profile plus provider-specific Rust discovery, registry registration, alias handling, command detection, and tests. Medium term, partners such as Docker should be able to publish provider profiles from their own repositories and have OpenShell discover credentials, binaries, and command intent generically.Technical Context
The current v1 discovery path is mostly a thin wrapper around environment-variable lookup. Most provider plugins define a
ProviderDiscoverySpec { id, credential_env_vars }and calldiscover_with_spec, while v2 provider profiles already model the same credential env vars and policy binaries. That means the profile model has enough information for static env-var discovery today, but the CLI and TUI still use the hard-coded registry for provider type lists, credential env vars,--from-existing, and auto-provider command inference.PR #1313 adds Docker Agent by extending both systems: a v2 profile in
providers/docker-agent.yaml, plus v1-style Rust provider discovery and hard-coded command detection fordocker agent. The useful behavior is generic: discover credential env vars from the profile, optionally detect local binaries, and infer provider type from command metadata.Affected Components
crates/openshell-providers/src/lib.rs,crates/openshell-providers/src/discovery.rs,crates/openshell-providers/src/profiles.rscrates/openshell-cli/src/run.rsprovider create --from-existing,provider update --from-existing, sandbox auto-provider creation, and command inference.crates/openshell-tui/src/app.rscrates/openshell-server/src/grpc/policy.rsproviders/*.yamlTechnical Investigation
Architecture Overview
Provider v1 discovery is local and registry-driven.
ProviderRegistry::new()registers hard-coded plugins; callers ask the registry to discover credentials for a provider type.discover_with_specthen scans a static env-var list and returns aDiscoveredProviderwith credential keys matching env-var names.Provider v2 profiles are data-driven. Built-in YAML profiles are loaded through
profiles.rs, custom profiles can be imported into the gateway store, and policy composition uses provider type/profile IDs to construct provider policy layers. The profile DTO already exposescredential_env_vars()by flatteningcredentials[*].env_vars, and profile binaries are preserved through proto conversion.The missing bridge is discovery. CLI/TUI code does not ask profiles how to discover a provider. It asks
ProviderRegistry, so every new discoverable provider still needs Rust registration even if the profile already contains the credential env vars and policy binaries.Code References
crates/openshell-providers/src/lib.rs:46ProviderDiscoverySpeconly stores an id and env-var names. This duplicates profile credential env vars.crates/openshell-providers/src/lib.rs:81ProviderRegistry::new()hard-registers every supported discovery plugin. Partners cannot extend this without a core PR.crates/openshell-providers/src/lib.rs:111ProviderRegistry::discover_existing()fails withUnsupportedProviderfor types that are not registered as plugins.crates/openshell-providers/src/lib.rs:136known_types()returns plugin keys, not built-in or imported provider profiles.crates/openshell-providers/src/lib.rs:143normalize_provider_type()is a hard-coded alias map. Custom provider profile IDs are not normalized here.crates/openshell-providers/src/lib.rs:162detect_provider_from_command()only inspects the first command basename, so shapes likedocker agentneed provider-specific Rust special-casing.crates/openshell-providers/src/discovery.rs:6discover_with_spec()is generic env-var scanning overProviderDiscoverySpec. This can be profile-backed.crates/openshell-providers/src/profiles.rs:283ProviderTypeProfile::credential_env_vars()already derives the env-var scan list from profile credentials.crates/openshell-cli/src/run.rs:3419detect_provider_from_command()helper.crates/openshell-cli/src/run.rs:3549auto_create_provider()usesProviderRegistry::discover_existing()and cannot discover custom profile-backed providers.crates/openshell-cli/src/run.rs:4058provider_create()can resolve custom profiles, but--from-existingstill calls the hard-coded registry.crates/openshell-cli/src/run.rs:4862provider update --from-existingalso uses hard-coded registry discovery.crates/openshell-tui/src/app.rs:1769ProviderRegistry::known_types(), not provider profiles.crates/openshell-tui/src/app.rs:1810crates/openshell-tui/src/app.rs:1849crates/openshell-server/src/grpc/policy.rs:544Current Behavior
A simple provider currently requires multiple core-code touch points:
ProviderDiscoverySpecandProviderPluginimplementation for env scanning.ProviderRegistry::new().normalize_provider_type()for aliases.detect_provider_from_command()if command inference is not a one-token executable basename.This is what PR #1313 does for Docker Agent. The actual desired discovery behavior is profile-shaped:
DOCKER_ACCESS_TOKENis an optional credential env var, and Docker-related binaries/commands indicate the provider may be useful even without a static token.What Would Need to Change
Provider discovery should gain a generic profile-backed path:
discover_from_profile(profile, context)helper that readsprofile.credentials[*].env_varsand stores discovered values under the actual env-var key so existing injection behavior remains compatible.DiscoveryContextwith binary/command existence checks if binary presence should be a discovery signal. PR feat(providers): add Docker Agent provider #1313'spath_exists()is directionally useful, but the API should be shaped around profile discovery semantics rather than one provider.profile.binariesshould double as host discovery paths. These paths are currently policy enforcement paths inside the sandbox; using them for host discovery is convenient but semantically leaky. A small explicitdiscoveryblock in the profile may be cleaner for command matching and host-side aliases.docker agent, not only executable basenames.provider create --from-existing,provider update --from-existing, and sandbox auto-provider creation to resolve the provider profile first and then run generic profile discovery. Keep plugin discovery as a fallback for advanced legacy cases.ProviderRegistry::known_types().Alternative Approaches Considered
binariesdirectly for discovery: Minimal schema change, but conflates sandbox policy binaries with host discovery paths and does not model multi-token command shapes likedocker agentcleanly.Patterns to Follow
profiles.rsshould remain the source of truth for profile-defined fields and proto conversion.GITHUB_TOKEN,ANTHROPIC_API_KEY, orDOCKER_ACCESS_TOKENrather than storing logical credential names.Proposed Approach
Move common discovery to profile-backed helpers and treat hard-coded plugins as legacy or advanced-provider fallbacks. Profiles should be sufficient for the common partner use case: declare credentials, optional credential behavior, binaries/endpoints for policy, and optional command discovery metadata. CLI and TUI should resolve provider profiles from built-ins and the gateway store, then use the generic profile discovery path for
--from-existing, provider auto-create, and credential prompts.This would let a partner ship a provider profile without adding a Rust plugin for basic env-var discovery and command/binary matching. PRs like #1313 could shrink to profile data plus tests, or move entirely to partner-owned profile repositories once external profile distribution exists.
Scope Assessment
featDecisions
discoverysection for credential discovery instead of dual-purposing policybinaries.discovery.credentialsrather than duplicating env vars.credentials[*].env_varsremains the source of truth for which local env vars are scanned.--from-existingbehavior: fail when no credential or config material is discovered, even when profile credentials are optional. Empty providers should be created through explicit non-discovery flows.provider create --type <profile> --from-existingand related explicit provider operations.Risks & Open Questions
Confirm whether the final proto field names should mirror the YAML shape exactly or use more explicit generated names. The intended YAML shape is credential-name based:
discovery.credentialsreferencescredentials[*].name; OpenShell then scans the referenced credential'senv_vars. Do not duplicate env vars directly indiscovery.Should any legacy v1 command inference remain available only for legacy provider paths, or should enabling providers v2 disable it entirely for provider attachment?
How should custom provider profile fetch failures be surfaced in explicit CLI flows: unsupported profile, gateway unavailable, or profile missing?
Test Considerations
discover_from_profile()for env-var discovery, duplicate env vars, required vs optional credentials, empty env values, and tokenless optional-credential discovery.docker agentstyle command prefixes.provider create --from-existingusing an imported custom profile with profile-defined env vars.provider update --from-existingusing profile-derived discovery.gh,glab, andclaude.Created by spike investigation. Use
build-from-issueto plan and implement.