Skip to content

Subscription-Compatible Runs #45

Description

@CraftsMan-Labs

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

  1. 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

  1. 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.

  1. 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:
  1. resolve profile
  2. resolve AuthContext
  3. build provider request with correct headers
  4. execute
  5. emit route metadata into output + telemetry

  1. 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
    }
    }
    }
    }

  1. 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.

  1. 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",
    )

  1. 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

  1. 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

  1. Migration Plan
  1. Existing users with api_key continue unchanged.
  2. Introduce implicit auto-profile generation for old constructors.
  3. Add deprecation notice only after profile mode stabilizes (not immediate).
  4. Add docs with side-by-side “old vs profile-based” examples.
  1. 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

  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions