Enable workflow/skill runs through official provider access modes beyond raw API keys, while preserving:
- existing API-key behavior
- approval/retry/media capabilities
- full telemetry correlation across runs
- Scope and Constraints
- In scope
- API key mode (existing)
- Official delegated auth mode (OAuth/Bearer where provider officially supports)
- Official managed gateway mode (enterprise proxy/gateway with supported contracts)
- Out of scope
- ChatGPT web/session scraping
- Browser automation login reuse
- any non-documented consumer subscription workaround
- Current Baseline (what changes)
Today config is key/base-url centric (OPENAI_API_KEY, etc.).
Plan is to add a provider profile + auth abstraction layer and keep existing flows as default.
- Target Architecture
3.1 New core types (Rust source of truth)
Add in shared config domain (likely simple-agent-type + core wiring):
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccessMode {
ApiKey,
OAuthDelegated,
GatewayManaged,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderProfile {
pub profile_id: String,
pub provider: String, // "openai", "azure_openai", ...
pub api_base: Option,
pub model_overrides: Option<std::collections::BTreeMap<String, String>>,
pub access_mode: AccessMode,
pub auth_ref: String, // references credential source, not secret itself
pub capabilities: Option,
}
#[derive(Debug, Clone)]
pub struct AuthContext {
pub access_mode: AccessMode,
pub auth_header_value: String, // "Bearer ...", "Api-Key ..."
pub expires_at_unix: Option,
pub tenant_id: Option,
pub scope: Option,
}
3.2 Credential resolver interface
#[async_trait::async_trait]
pub trait CredentialResolver: Send + Sync {
async fn resolve(&self, auth_ref: &str) -> Result<AuthContext, CredentialError>;
}
Resolvers:
- EnvApiKeyResolver (existing-compatible)
- OAuthTokenResolver (official delegated flow)
- GatewayTokenResolver (enterprise managed token/key)
3.3 Provider routing
Execution call receives provider_profile and resolves credentials at runtime:
- resolve profile
- resolve AuthContext
- build provider request with correct headers
- execute
- emit route metadata into output + telemetry
- Data/Config Contracts
4.1 Profile config file (example)
provider_profiles:
- profile_id: openai_api_default
provider: openai
api_base: https://api.openai.com/v1
access_mode: api_key
auth_ref: env:OPENAI_API_KEY
- profile_id: openai_enterprise_gateway
provider: openai
api_base: https://llm-gateway.company.com/v1
access_mode: gateway_managed
auth_ref: vault:llm_gateway_token
4.2 Workflow/skill execution options
{
provider_profile: openai_enterprise_gateway,
workflow_options: {
trace: {
tenant: {
workspace_id: ws_123,
conversation_id: conv_123
}
}
}
}
- Runtime and Package-by-Package Changes
Rust (crates/simple-agent-type, simple-agents-core, providers, workflow)
- Add profile/auth contracts and validation.
- Add resolver manager and profile registry.
- Add provider request builder that consumes AuthContext.
- Keep old api_key constructor paths working (auto-create implicit profile).
Provider layer adjustments
- OpenAI adapter: support both key and bearer token header composition.
- Azure/OpenAI-compatible profile support via provider/base-url policies.
- Gateway mode: standard auth header + endpoint behavior only (no provider-specific hacks).
Workflow runtime (simple-agents-workflow)
- Accept optional provider_profile in run options.
- Carry profile info in run metadata (non-secret).
- Ensure pause/resume + failure-resume re-resolve credentials safely.
Bindings parity
- Python/Node/Go all gain:
- profile registration/loading
- run by profile id
- route metadata in results
- Preserve old signatures; add optional args only.
- API Surface (all bindings parity)
- register_provider_profile(profile)
- list_provider_profiles()
- run_workflow_yaml(..., provider_profile="...")
- run_skill(..., provider_profile="...")
- continue_run(run_id=..., provider_profile="...") (optional override; default stored profile)
Python example:
client.register_provider_profile({
"profile_id": "openai_enterprise_gateway",
"provider": "openai",
"api_base": "https://llm-gateway.company.com/v1",
"access_mode": "gateway_managed",
"auth_ref": "env:LLM_GATEWAY_TOKEN"
})
out = client.run_skill(
skill_id="deep-research-brief",
input={"topic": "semiconductor supply risk"},
provider_profile="openai_enterprise_gateway",
)
- Telemetry and Compliance Requirements
Mandatory metadata on spans/events/output:
- provider_profile
- access_mode
- provider_route
- auth_source (non-secret label like env, vault, oauth)
- plus existing trace_id, run_id, workflow_id, conversation_id
Never emit:
- API keys
- bearer tokens
- raw credential refs with secret values
Example span attributes:
provider.profile_id=openai_enterprise_gateway
provider.access_mode=gateway_managed
provider.route=openai
auth.source=vault
- Security & Reliability Controls
- Token expiry validation before request dispatch.
- Optional proactive refresh window (e.g., refresh if <60s remaining).
- Typed errors:
- CredentialsExpired
- CredentialsMissing
- UnsupportedAccessModeForProvider
- ProfileNotFound
- Circuit-breaker/fallback policy:
- optional fallback profile chain
- always explicit in metadata when fallback is used
- Migration Plan
- Existing users with api_key continue unchanged.
- Introduce implicit auto-profile generation for old constructors.
- Add deprecation notice only after profile mode stabilizes (not immediate).
- Add docs with side-by-side “old vs profile-based” examples.
- Test Strategy
- Unit
- profile validation
- resolver behavior (env/vault/oauth)
- header construction per access mode
- Integration
- run workflow with profile-based auth
- approval pause/resume with profile
- node failure resume with profile
- Binding contract tests
- identical run metadata shape across Rust/Python/Node/Go
- Telemetry tests
- correlation fields present
- no secret leakage assertions
- Definition of Done
- Profile-based runs work in Rust/Python/Node/Go.
- Existing key-based users are not broken.
- Official-compliant access modes only.
- Approval/retry/media/telemetry flows function unchanged.
- Security tests confirm secret redaction in logs/events/traces.
Enable workflow/skill runs through official provider access modes beyond raw API keys, while preserving:
Today config is key/base-url centric (OPENAI_API_KEY, etc.).
Plan is to add a provider profile + auth abstraction layer and keep existing flows as default.
3.1 New core types (Rust source of truth)
Add in shared config domain (likely simple-agent-type + core wiring):
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccessMode {
ApiKey,
OAuthDelegated,
GatewayManaged,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderProfile {
pub profile_id: String,
pub provider: String, // "openai", "azure_openai", ...
pub api_base: Option,
pub model_overrides: Option<std::collections::BTreeMap<String, String>>,
pub access_mode: AccessMode,
pub auth_ref: String, // references credential source, not secret itself
pub capabilities: Option,
}
#[derive(Debug, Clone)]
pub struct AuthContext {
pub access_mode: AccessMode,
pub auth_header_value: String, // "Bearer ...", "Api-Key ..."
pub expires_at_unix: Option,
pub tenant_id: Option,
pub scope: Option,
}
3.2 Credential resolver interface
#[async_trait::async_trait]
pub trait CredentialResolver: Send + Sync {
async fn resolve(&self, auth_ref: &str) -> Result<AuthContext, CredentialError>;
}
Resolvers:
3.3 Provider routing
Execution call receives provider_profile and resolves credentials at runtime:
4.1 Profile config file (example)
provider_profiles:
provider: openai
api_base: https://api.openai.com/v1
access_mode: api_key
auth_ref: env:OPENAI_API_KEY
provider: openai
api_base: https://llm-gateway.company.com/v1
access_mode: gateway_managed
auth_ref: vault:llm_gateway_token
4.2 Workflow/skill execution options
{
provider_profile: openai_enterprise_gateway,
workflow_options: {
trace: {
tenant: {
workspace_id: ws_123,
conversation_id: conv_123
}
}
}
}
Rust (crates/simple-agent-type, simple-agents-core, providers, workflow)
Provider layer adjustments
Workflow runtime (simple-agents-workflow)
Bindings parity
Python example:
client.register_provider_profile({
"profile_id": "openai_enterprise_gateway",
"provider": "openai",
"api_base": "https://llm-gateway.company.com/v1",
"access_mode": "gateway_managed",
"auth_ref": "env:LLM_GATEWAY_TOKEN"
})
out = client.run_skill(
skill_id="deep-research-brief",
input={"topic": "semiconductor supply risk"},
provider_profile="openai_enterprise_gateway",
)
Mandatory metadata on spans/events/output:
Never emit:
Example span attributes:
provider.profile_id=openai_enterprise_gateway
provider.access_mode=gateway_managed
provider.route=openai
auth.source=vault