Orchestrata Ultimate / Specs / 011-provider-manager
011-provider-manager: Provider Manager
The Provider Manager is the unified LLM provider abstraction for Orchestrata. It manages multiple LLM backends (OpenAI, Anthropic, DeepSeek, Ollama, etc.), providing request routing, load balancing, cost tracking, fallback chains, and rate limit management — all governed by PAM++ policies.
At a glance
| Item | Value |
|---|---|
| Spec directory | specs/011-provider-manager/ |
| Status line in the spec | Draft |
| Requirement IDs defined | 60 |
With a @traces citation in src/ |
39 (65.0%) |
With a @traces citation in tests/ |
1 (1.7%) |
| Tracked files | 9 |
| RTM page | 011-provider-manager |
Reading this spec
The Provider Manager is the unified LLM provider abstraction for Orchestrata. It manages several LLM backends (OpenAI, Anthropic, DeepSeek, Ollama and others) and provides request routing, load balancing, cost tracking, fallback chains and rate limit management, all governed by PAM++ policies. It exists because Volks depend on specific providers but need resilience when one fails, cost optimization needs per-model pricing awareness, different tasks need different models, and the verification package has only a partial, non-general model registry.
The scope is seven user stories: provider-agnostic execution, cost-aware routing, fallback resilience and rate limit management (priority 1), and model capability matching, budget enforcement and provider health monitoring (priority 2). Requirement IDs use the REQ-011- prefix.
The key decisions are an IProviderManager interface registered in the DI container as ComponentType.PROVIDER_MANAGER; provider adapters that each implement the IProvider protocol; routing based on model capability, cost, latency and availability; fallback chains with configurable retry and circuit breaker patterns; per-request cost tracking with budget enforcement at Volk and project level; and PAM++ governance on model access and budget limits.
In the code, the largest tracers are orchestrata_ultimate/providers/provider_manager, orchestrata_ultimate/core/protocols/providers, orchestrata_ultimate/providers/router, orchestrata_ultimate/providers/cost_tracker and orchestrata_ultimate/providers/rate_limiter. The per-requirement view is on the RTM page.
The spec's status line reads Draft. It tracks 9 files and defines 60 requirement IDs. The full text follows below.
Measured coverage is 39 of 60 requirements cited by at least one @traces in src/ (65.0%), spread over 14 modules; the module citing the most of them is orchestrata_ultimate/core/protocols/providers (17 requirements). In tests/, 1 requirement is cited by a test-side @traces.
Coverage gaps: 21 requirements have no citing symbol. The largest untraced groups are REQ-011-* (21). Untraced means no @traces names the ID; the behaviour may still exist in code that was never annotated. Test-side citations are thinner than code-side ones (1 against 39).
Requirements
Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.
| ID | Done | Requirement | Traced by |
|---|---|---|---|
REQ-011-001 |
no | IProviderManager defined as ABC in orchestrata-core interfaces module | 1 |
REQ-011-002 |
no | IProviderManager registered with ComponentType.PROVIDER_MANAGER in DI container | none |
REQ-011-003 |
no | IComponent lifecycle implemented (initialize, start, stop, get_health) | 7 |
REQ-011-004 |
no | ProviderRequest data class defined with messages, model, capabilities, parameters | 1 |
REQ-011-005 |
no | ProviderResponse data class defined with content, model_used, provider_used, usage, cost | 1 |
REQ-011-006 |
no | Execute accepts ProviderRequest and returns ProviderResponse | 3 |
REQ-011-007 |
no | Provider selected based on model specification or capability matching | 12 |
REQ-011-008 |
no | Response normalized to common ProviderResponse format | 3 |
REQ-011-009 |
no | ProviderError raised with details when all providers fail | 4 |
REQ-011-010 |
no | Cost recorded for every executed request | 4 |
REQ-011-011 |
no | Routing strategy: cost_optimized implemented | 3 |
REQ-011-012 |
no | Routing strategy: latency_optimized implemented | 3 |
REQ-011-013 |
no | Routing strategy: quality_optimized implemented | 3 |
REQ-011-014 |
no | Routing strategy: round_robin implemented | 3 |
REQ-011-015 |
no | Providers filtered by availability before routing | 6 |
REQ-011-016 |
no | Providers filtered by rate limit headroom before routing | 5 |
REQ-011-017 |
no | Routing decisions logged for auditability | none |
REQ-011-018 |
no | Cost estimated before execution based on input token count | 4 |
REQ-011-019 |
no | Actual cost recorded after execution based on usage | 2 |
REQ-011-020 |
no | Cumulative cost tracked per volk_id | 3 |
REQ-011-021 |
no | Cumulative cost tracked per session_id | 3 |
REQ-011-022 |
no | Cumulative cost tracked per project_id and portfolio_id | 1 |
REQ-011-023 |
no | Cost queries supported with time range filters | 2 |
REQ-011-024 |
no | Dynamic provider registration and deregistration | 9 |
REQ-011-025 |
no | Provider configuration validated on registration | 2 |
REQ-011-026 |
no | YAML configuration loading from configs/production.yaml | none |
REQ-011-027 |
no | Environment variable interpolation for secrets | none |
REQ-011-028 |
no | Model registry with capabilities, context_window, pricing | 11 |
REQ-011-029 |
no | IProvider protocol defined for adapter implementations | 1 |
REQ-011-030 |
no | OpenAI adapter implemented | 1 |
REQ-011-031 |
no | Anthropic adapter implemented | 4 |
REQ-011-032 |
no | DeepSeek adapter implemented | 1 |
REQ-011-033 |
no | Ollama adapter implemented | 1 |
REQ-011-034 |
no | Custom adapter registration supported | none |
REQ-011-035 |
no | Fallback chains configurable per capability class | none |
REQ-011-036 |
no | Fallback triggered on timeout, rate limit, server error | 1 |
REQ-011-037 |
no | Fallback NOT triggered on auth error, invalid request, budget exceeded | none |
REQ-011-038 |
no | Circuit breaker per provider (closed/open/half-open) | 7 |
REQ-011-039 |
no | Circuit opens after configurable consecutive failures (default: 5) | 3 |
REQ-011-040 |
no | Half-open probe after configurable cooldown (default: 60s) | 4 |
REQ-011-041 |
no | Per-provider RPM and TPM tracking | 10 |
REQ-011-042 |
no | Rate limit headers parsed from provider responses | 1 |
REQ-011-043 |
no | Request queuing when rate limit headroom < 10% | 2 |
REQ-011-044 |
no | Alternative provider routing when primary rate-limited | 1 |
REQ-011-045 |
no | Budget limits configurable at Volk, project, portfolio level | 8 |
REQ-011-046 |
no | BudgetExceededError raised when budget exhausted | 2 |
REQ-011-047 |
no | Budget warning events at 80%, 90%, 95% thresholds | none |
REQ-011-048 |
no | PAM++ governance overrides for emergency escalations | none |
REQ-011-049 |
no | Model access policies enforced via Prolog governance | none |
REQ-011-050 |
no | Per-Volk model allowlists and denylists supported | none |
REQ-011-051 |
no | All provider operations logged to audit trail | none |
REQ-011-052 |
no | Audit records stored in Neo4j for compliance queries | none |
REQ-011-053 |
no | Audit queries by time range, volk, provider, cost threshold | none |
REQ-011-054 |
no | Provider selection < 5ms (P95) | none |
REQ-011-055 |
no | Cost estimation < 2ms (P95) | none |
REQ-011-056 |
no | Full request overhead < 20ms (P95) | none |
REQ-011-057 |
no | Concurrent requests: 200+ simultaneous | none |
REQ-011-058 |
no | API keys in environment variables, never in code or logs | none |
REQ-011-059 |
no | API keys excluded from audit records and error messages | none |
REQ-011-060 |
no | Provider credentials rotatable without restart | none |
Code that traces this spec
Checklists
checklists/constitutional.md
Provider Manager Constitutional Checklist
Security and Governance Principles
- [ ] CONST-011-001: All provider operations must enforce PAM++ governance policies
- [ ] CONST-011-002: Model access must be controlled via Prolog-based allowlists and denylists
- [ ] CONST-011-003: API credentials must never appear in logs, audit records, or error messages
- [ ] CONST-011-004: Provider communications must use TLS encryption
- [ ] CONST-011-005: All provider operations must be logged to immutable audit trail
Data Integrity and Consistency
- [ ] CONST-011-006: Cost records must accurately reflect actual provider usage
- [ ] CONST-011-007: Token counting must align with provider tokenizer within 5% tolerance
- [ ] CONST-011-008: Budget enforcement must be atomic (no race conditions on concurrent checks)
- [ ] CONST-011-009: Audit trail must be append-only and tamper-resistant
- [ ] CONST-011-010: Provider health state must be eventually consistent across routing decisions
Privacy and Data Protection
- [ ] CONST-011-011: Request content must not be logged in production audit trail
- [ ] CONST-011-012: Provider API keys must be encrypted in transit and at rest
- [ ] CONST-011-013: Cost records must not contain request/response content
- [ ] CONST-011-014: Provider selection metadata must be anonymized in shared dashboards
- [ ] CONST-011-015: Data retention policies must be enforced for cost and audit records
System Reliability and Resilience
- [ ] CONST-011-016: System must remain available when any single provider fails
- [ ] CONST-011-017: Circuit breaker must prevent cascading failures across providers
- [ ] CONST-011-018: Rate limit management must prevent provider account suspension
- [ ] CONST-011-019: Fallback must not compromise request quality below configurable minimum
- [ ] CONST-011-020: Provider failures must be transparent to Volk execution
Ethical and Responsible AI
- [ ] CONST-011-021: Cost tracking must enable accountability for AI resource usage
- [ ] CONST-011-022: Budget enforcement must prevent uncontrolled AI spending
- [ ] CONST-011-023: Provider selection must not introduce bias in agent outputs
- [ ] CONST-011-024: System must support model restrictions for safety-critical tasks
- [ ] CONST-011-025: Human oversight must be available for budget override decisions
Compliance and Regulatory Requirements
- [ ] CONST-011-026: Audit trail must meet regulatory requirements for financial tracking
- [ ] CONST-011-027: Cost records must support compliance reporting
- [ ] CONST-011-028: Provider usage must comply with provider terms of service
- [ ] CONST-011-029: Data residency requirements must be respected for provider routing
- [ ] CONST-011-030: System must support compliance-driven provider restrictions
Performance and Resource Management
- [ ] CONST-011-031: Routing overhead must not measurably impact LLM response latency
- [ ] CONST-011-032: Cost tracking must not block request execution
- [ ] CONST-011-033: Health monitoring must use minimal resources
- [ ] CONST-011-034: Rate limit tracking must be accurate within 1-second windows
- [ ] CONST-011-035: Resource allocation must be fair across concurrent Volk requests
Interoperability and Standards
- [ ] CONST-011-036: Provider adapters must normalize responses to common format
- [ ] CONST-011-037: Configuration must follow existing configs/production.yaml patterns
- [ ] CONST-011-038: DI registration must follow orchestrata-core conventions
- [ ] CONST-011-039: Integration with verification package must be backward-compatible
- [ ] CONST-011-040: CXP packet metadata must follow established format standards
Transparency and Explainability
- [ ] CONST-011-041: Routing decisions must be explainable (why this provider was chosen)
- [ ] CONST-011-042: Cost breakdowns must be transparent (input/output/total per request)
- [ ] CONST-011-043: Fallback events must include original error and resolution path
- [ ] CONST-011-044: Budget usage must be visible to Volk owners and project managers
- [ ] CONST-011-045: Provider health status must be queryable in real-time
Innovation and Future-Proofing
- [ ] CONST-011-046: Provider adapter interface must support new providers without core changes
- [ ] CONST-011-047: Routing strategies must be pluggable and extensible
- [ ] CONST-011-048: Cost model must adapt to new pricing structures
- [ ] CONST-011-049: Capability taxonomy must be extensible for new model capabilities
- [ ] CONST-011-050: System must support streaming responses when providers offer them
Human-AI Collaboration
- [ ] CONST-011-051: Human users must be able to override provider selection
- [ ] CONST-011-052: Human users must be able to set and modify budget limits
- [ ] CONST-011-053: Cost reports must be human-readable and actionable
- [ ] CONST-011-054: Provider health dashboards must be accessible to operators
- [ ] CONST-011-055: System must support human-in-the-loop for high-cost requests
checklists/requirements.md
The requirement list above comes from this file; 0 of 60 checkbox items are ticked.
checklists/testing.md
Provider Manager Testing Checklist
Core Interface Testing
- [ ] TEST-011-001: Verify IProviderManager initialization with provider configurations
- [ ] TEST-011-002: Validate IComponent lifecycle (initialize, start, stop, get_health)
- [ ] TEST-011-003: Test DI container registration and resolution of IProviderManager
- [ ] TEST-011-004: Verify health check returns per-provider status
- [ ] TEST-011-005: Test graceful shutdown closes all provider connections
Provider Registration Testing
- [ ] TEST-011-006: Register provider with valid configuration
- [ ] TEST-011-007: Register provider with invalid API key fails validation
- [ ] TEST-011-008: Deregister provider removes from routing pool
- [ ] TEST-011-009: YAML configuration loading from configs/production.yaml
- [ ] TEST-011-010: Environment variable interpolation for API keys
Request Execution Testing
- [ ] TEST-011-011: Execute request with explicit model name
- [ ] TEST-011-012: Execute request with capability matching
- [ ] TEST-011-013: Execute request returns normalized ProviderResponse
- [ ] TEST-011-014: Execute request records cost in tracker
- [ ] TEST-011-015: ProviderError raised when all providers fail
Routing Strategy Testing
- [ ] TEST-011-016: Cost-optimized routing selects cheapest provider
- [ ] TEST-011-017: Latency-optimized routing selects fastest provider
- [ ] TEST-011-018: Quality-optimized routing selects highest-quality provider
- [ ] TEST-011-019: Round-robin routing distributes evenly
- [ ] TEST-011-020: Unavailable providers excluded from routing
- [ ] TEST-011-021: Rate-limited providers excluded from routing
Fallback Chain Testing
- [ ] TEST-011-022: Fallback triggered on provider timeout
- [ ] TEST-011-023: Fallback triggered on server error (5xx)
- [ ] TEST-011-024: Fallback triggered on rate limit (429)
- [ ] TEST-011-025: Fallback NOT triggered on auth error (401)
- [ ] TEST-011-026: Fallback NOT triggered on invalid request (400)
- [ ] TEST-011-027: Fallback chain exhaustion raises ProviderError
- [ ] TEST-011-028: Fallback events logged with error details
Circuit Breaker Testing
- [ ] TEST-011-029: Circuit opens after consecutive failures
- [ ] TEST-011-030: Open circuit rejects requests immediately
- [ ] TEST-011-031: Half-open probe after cooldown period
- [ ] TEST-011-032: Circuit closes after consecutive successes
- [ ] TEST-011-033: Circuit state per-provider (independent)
Rate Limit Testing
- [ ] TEST-011-034: RPM tracking accurate across requests
- [ ] TEST-011-035: TPM tracking accurate across requests
- [ ] TEST-011-036: Rate limit headers parsed from responses
- [ ] TEST-011-037: Requests queued when approaching limit
- [ ] TEST-011-038: Alternative provider selected when primary rate-limited
Cost Tracking Testing
- [ ] TEST-011-039: Cost calculated correctly (input + output tokens * price)
- [ ] TEST-011-040: Cost aggregated by volk_id
- [ ] TEST-011-041: Cost aggregated by session_id
- [ ] TEST-011-042: Cost aggregated by project_id
- [ ] TEST-011-043: Cost query with time range filter
- [ ] TEST-011-044: Cost stored in Redis (hot) and Neo4j (cold)
Budget Enforcement Testing
- [ ] TEST-011-045: Request rejected when Volk budget exhausted
- [ ] TEST-011-046: Request rejected when project budget exhausted
- [ ] TEST-011-047: Budget warning at 80% threshold
- [ ] TEST-011-048: Budget warning at 95% threshold
- [ ] TEST-011-049: PAM++ override allows request beyond budget
Governance Testing
- [ ] TEST-011-050: Model allowlist enforced
- [ ] TEST-011-051: Model denylist enforced
- [ ] TEST-011-052: Audit trail records all provider operations
- [ ] TEST-011-053: Audit records queryable by time range and volk
- [ ] TEST-011-054: Governance-driven routing override works
Performance Testing
- [ ] TEST-011-055: Provider selection < 5ms (P95) under load
- [ ] TEST-011-056: Cost estimation < 2ms (P95) under load
- [ ] TEST-011-057: Full overhead < 20ms (P95) under load
- [ ] TEST-011-058: 200+ concurrent requests handled without errors
Security Testing
- [ ] TEST-011-059: API keys not present in logs
- [ ] TEST-011-060: API keys not present in audit records
- [ ] TEST-011-061: API keys not present in error messages
- [ ] TEST-011-062: TLS enforced for all provider calls
Integration Testing
- [ ] TEST-011-063: Kernel selects provider before Volk execution
- [ ] TEST-011-064: Volk executes LLM request via IProviderManager
- [ ] TEST-011-065: Verification package migrated to IProviderManager
- [ ] TEST-011-066: CXP packets include provider metadata
- [ ] TEST-011-067: Session cost totals match individual request sums
- [ ] TEST-011-068: PAM++ governance integration end-to-end
Other tracked files
specs/011-provider-manager/README.mdspecs/011-provider-manager/compliance-audit.mdspecs/011-provider-manager/contracts/api.yamlspecs/011-provider-manager/contracts/events.yamlspecs/011-provider-manager/contracts/graph-schema.cql
Full spec text
Verbatim from specs/011-provider-manager/spec.md; headings are pushed down two levels.
SPEC-011: Provider Manager
Metadata
- Spec ID: 011-provider-manager
- Title: Provider Manager
- Author: Mohamed
- Status: Draft
- Created: 2026-03-09
- Last Updated: 2026-03-09
- Priority: P1
- Components: IProviderManager, ProviderManager, ProviderRegistry, CostTracker, FallbackChain
- Dependencies: Python 3.11+, orchestrata-core, orchestrata-governance, orchestrata-di
Overview
What
The Provider Manager is the unified LLM provider abstraction for Orchestrata. It manages multiple LLM backends (OpenAI, Anthropic, DeepSeek, Ollama, etc.), providing request routing, load balancing, cost tracking, fallback chains, and rate limit management — all governed by PAM++ policies.
Why
- Volks depend on specific LLM providers but need resilience when providers fail
- Cost optimization requires awareness of per-model pricing and usage
- Different tasks require different models (reasoning vs coding vs summarization)
- The verification package has a partial ModelRegistry but it is not general-purpose
- No unified interface exists for Volks to request LLM completions with provider-agnostic semantics
How
- IProviderManager interface registered in DI container as ComponentType.PROVIDER_MANAGER
- Provider adapters implement IProvider protocol for each backend
- Request routing based on model capability, cost, latency, and availability
- Fallback chains with configurable retry and circuit breaker patterns
- Cost tracking per request with budget enforcement at Volk and project level
- PAM++ governance on model access and budget limits
User Scenarios & Stories
US-001: Provider-Agnostic Execution (P1)
As a Volk I want to send LLM requests without knowing which provider serves them So that I focus on my task rather than provider-specific API details
Acceptance Criteria:
- [ ] Volk submits a ProviderRequest with model capability requirements
- [ ] System selects best available provider based on capability match
- [ ] Response is normalized to a common ProviderResponse format
- [ ] Provider failures are transparent to the Volk (automatic fallback)
US-002: Cost-Aware Routing (P1)
As the Orchestrator (U:Ox) I want to route requests to the cheapest provider that meets quality requirements So that operational costs are minimized without sacrificing output quality
Acceptance Criteria:
- [ ] Each provider has configurable per-token input/output pricing
- [ ] Routing considers cost when multiple providers match capability requirements
- [ ] Cost tracking per request, per Volk, per session, per project
- [ ] Budget alerts when spending approaches configurable thresholds
US-003: Fallback Resilience (P1)
As the system I want to automatically retry on alternative providers when one fails So that agent execution is not blocked by transient provider outages
Acceptance Criteria:
- [ ] Fallback chains configurable per model capability class
- [ ] Circuit breaker prevents repeated calls to failing providers
- [ ] Retry with exponential backoff before fallback
- [ ] Fallback events logged for monitoring and cost analysis
US-004: Rate Limit Management (P1)
As the system I want to respect provider rate limits without failing requests So that Orchestrata maintains good standing with API providers
Acceptance Criteria:
- [ ] Per-provider rate limit tracking (RPM, TPM)
- [ ] Request queuing when approaching rate limits
- [ ] Proactive routing away from rate-limited providers
- [ ] Rate limit headers parsed from provider responses
US-005: Model Capability Matching (P2)
As the Kernel I want to specify required capabilities (reasoning, coding, vision, tool_use) rather than specific model names So that the system picks the best model for the job
Acceptance Criteria:
- [ ] Models tagged with capability sets (reasoning, coding, vision, tool_use, long_context)
- [ ] Request can specify required capabilities and preferred capabilities
- [ ] Capability matching filters eligible models before cost/latency ranking
- [ ] New models registered with capability tags dynamically
US-006: Budget Enforcement (P2)
As the Orchestrator (U:Ox) I want to enforce spending limits per Volk and per project So that runaway agents do not exhaust the budget
Acceptance Criteria:
- [ ] Budget limits configurable at Volk, project, and portfolio level
- [ ] Requests rejected with clear error when budget exhausted
- [ ] Budget usage queryable in real-time
- [ ] PAM++ governance rules can override budget limits for escalations
US-007: Provider Health Monitoring (P2)
As the system I want to track provider health metrics (latency, error rate, availability) So that routing decisions are informed by real-time provider status
Acceptance Criteria:
- [ ] Per-provider latency tracking (P50, P95, P99)
- [ ] Error rate tracking with sliding window
- [ ] Automatic provider demotion when error rate exceeds threshold
- [ ] Health dashboard endpoint for monitoring
Functional Requirements
Core Interface
FR-001: IProviderManager Interface (P1)
- System SHALL define IProviderManager as an abstract base class in orchestrata-core interfaces
- System SHALL register IProviderManager with ComponentType.PROVIDER_MANAGER in DI container
- System SHALL implement IComponent lifecycle (initialize, start, stop, get_health)
FR-002: Execute Request (P1)
- System SHALL accept ProviderRequest with: messages, model (optional), capabilities (optional), parameters (dict)
- System SHALL select provider based on model specification or capability matching
- System SHALL return ProviderResponse with: content, model_used, provider_used, usage (tokens), cost, latency_ms
- System SHALL raise ProviderError with details when all providers fail
FR-003: Route Request (P1)
- System SHALL select the optimal provider based on configurable strategy
- System SHALL support routing strategies: cost_optimized, latency_optimized, quality_optimized, round_robin
- System SHALL filter providers by: availability, rate limit headroom, capability match, budget remaining
- System SHALL log routing decisions for auditability
FR-004: Cost Estimation (P1)
- System SHALL estimate request cost before execution based on input token count
- System SHALL return actual cost after execution based on usage
- System SHALL track cumulative cost per: request, volk, session, project, portfolio
Provider Registry
FR-005: Provider Registration (P1)
- System SHALL support dynamic provider registration and deregistration
- System SHALL validate provider configuration on registration (API key, endpoint, models)
- System SHALL support provider configuration from YAML files (configs/production.yaml)
- System SHALL support environment variable interpolation for secrets (${API_KEY})
FR-006: Model Registry (P1)
- System SHALL maintain a registry of available models per provider
- System SHALL store model metadata: name, capabilities, context_window, input_price, output_price
- System SHALL support model aliasing (e.g., "reasoning" maps to "deepseek-reasoner")
- System SHALL refresh model availability on provider health changes
FR-007: Provider Adapters (P1)
- System SHALL define IProvider protocol for provider implementations
- System SHALL ship adapters for: OpenAI, Anthropic, DeepSeek, Ollama
- System SHALL support custom adapter registration for new providers
- System SHALL normalize all provider responses to common ProviderResponse format
Resilience
FR-008: Fallback Chain (P1)
- System SHALL maintain ordered fallback chains per capability class
- System SHALL try next provider in chain on: timeout, rate limit, server error
- System SHALL NOT fallback on: authentication error, invalid request, budget exceeded
- System SHALL log fallback events with original error and fallback target
FR-009: Circuit Breaker (P1)
- System SHALL implement per-provider circuit breaker (closed -> open -> half-open)
- System SHALL open circuit after configurable consecutive failures (default: 5)
- System SHALL attempt half-open probe after configurable cooldown (default: 60s)
- System SHALL close circuit after configurable consecutive successes (default: 3)
FR-010: Rate Limit Management (P1)
- System SHALL track per-provider rate limits (requests/minute, tokens/minute)
- System SHALL parse rate limit headers from provider responses
- System SHALL queue requests when rate limit headroom < 10%
- System SHALL route to alternative providers when primary is rate-limited
Cost Management
FR-011: Cost Tracking (P1)
- System SHALL record cost per request: input_tokens * input_price + output_tokens * output_price
- System SHALL aggregate costs by: volk_id, session_id, project_id, portfolio_id, provider, model
- System SHALL store cost records in Redis (hot) and Neo4j (cold audit)
- System SHALL support cost queries with time range filters
FR-012: Budget Enforcement (P2)
- System SHALL enforce configurable budget limits at Volk, project, and portfolio level
- System SHALL reject requests that would exceed budget with BudgetExceededError
- System SHALL emit budget warning events at configurable thresholds (80%, 90%, 95%)
- System SHALL support PAM++ governance overrides for emergency escalations
Governance
FR-013: PAM++ Integration (P1)
- System SHALL enforce model access policies via Prolog governance
- System SHALL support per-Volk model allowlists and denylists
- System SHALL log all provider operations to audit trail
- System SHALL support governance-driven routing overrides
FR-014: Audit Trail (P1)
- System SHALL record all requests with: timestamp, volk_id, provider, model, tokens, cost, latency, status
- System SHALL store audit records in Neo4j for compliance queries
- System SHALL support audit queries by time range, volk, provider, cost threshold
FR-015: Token Counting Strategy (P1)
- System SHALL count tokens per provider using provider-specific methods:
- OpenAI: tiktoken (cl100k_base for GPT-4 family)
- Anthropic: anthropic SDK
count_tokens()method - DeepSeek: tiktoken cl100k_base as approximation (verified within 5% tolerance)
- Ollama: response-only token counts (no pre-counting; estimate from word count ÷ 0.75)
- System SHALL achieve token count accuracy within 5% of provider-reported actuals
- System SHALL share the ITokenCounter abstraction with SPEC-010 Memory Engine
FR-016: Request Cancellation (P2)
- System SHALL support cancellation of in-flight requests by request_id
- System SHALL propagate cancellation to the provider adapter (best-effort)
- System SHALL record cancelled requests in audit trail with status "cancelled"
- System SHALL not bill cancelled requests that were aborted before provider response
FR-017: Streaming Support (Deferred — P3)
- Streaming is out of scope for v1.0 of the Provider Manager
- System SHALL design IProvider protocol to be forward-compatible with streaming
- Future streaming interface:
async def complete_stream(messages, model, parameters) -> AsyncIterator[StreamChunk] - Adapters may implement streaming internally but MUST materialize the full response for v1.0 callers
Non-Functional Requirements
Performance
NFR-001: Routing Latency
- Provider selection: < 5ms (P95)
- Cost estimation: < 2ms (P95)
- Full request overhead (excluding provider latency): < 20ms (P95)
NFR-002: Throughput
- Concurrent requests: 200+ simultaneous
- Request queuing capacity: 1000+ pending
- Cost query response: < 50ms (P95)
Reliability
NFR-003: Availability
- System available when at least one provider is healthy
- Automatic failover within 2 seconds of provider failure detection
- Zero request loss during provider transitions
NFR-004: Data Integrity
- Cost records eventually consistent (< 5s lag)
- Zero cost record loss for completed requests
- Audit trail immutable after write
Scalability
NFR-005: Provider Scaling
- Support 20+ registered providers
- Support 100+ registered models
- Support 50+ concurrent Volk sessions
Security
NFR-006: Credential Management
- API keys stored in environment variables, never in code or logs
- API keys never included in audit records or error messages
- Provider credentials rotatable without restart
- TLS enforced for all provider API calls
Technical Design
Architecture
+-------------------------------------------------------------+
| Provider Manager System |
+-------------------------------------------------------------+
| +--------------+ +--------------+ +--------------+ |
| | Request | | Cost | | Health | |
| | Router | | Tracker | | Monitor | |
| +--------------+ +--------------+ +--------------+ |
| | | | |
| v v v |
| +-----------------------------------------------------+ |
| | Provider Registry | |
| | +------+ +----------+ +----------+ +------+ | |
| | |OpenAI| | Anthropic| | DeepSeek | |Ollama| | |
| | +------+ +----------+ +----------+ +------+ | |
| +-----------------------------------------------------+ |
| +-----------------------------------------------------+ |
| | Resilience Layer | |
| | Fallback Chain | Circuit Breaker | Rate Limiter | |
| +-----------------------------------------------------+ |
+-------------------------------------------------------------+
Key Interfaces
class IProviderManager(IComponent, ABC):
"""Unified LLM provider abstraction."""
@abstractmethod
async def execute(
self,
request: ProviderRequest,
) -> ProviderResponse:
"""Execute an LLM request. Handles routing, fallback, cost tracking."""
@abstractmethod
async def estimate_cost(
self,
request: ProviderRequest,
) -> CostEstimate:
"""Estimate cost for a request without executing it."""
@abstractmethod
async def get_usage(
self,
*,
volk_id: str | None = None,
session_id: str | None = None,
project_id: str | None = None,
date_from: datetime | None = None,
date_to: datetime | None = None,
) -> UsageReport:
"""Query cumulative usage and cost."""
@abstractmethod
def register_provider(
self,
name: str,
provider: IProvider,
config: ProviderConfig,
) -> None:
"""Register a provider adapter with configuration."""
@abstractmethod
def deregister_provider(self, name: str) -> bool:
"""Remove a provider. Returns True if found and removed."""
@abstractmethod
async def get_remaining_budget(
self,
*,
volk_id: str | None = None,
project_id: str | None = None,
portfolio_id: str | None = None,
) -> BudgetStatus:
"""Query remaining budget for a scope."""
@abstractmethod
async def cancel(self, request_id: str) -> bool:
"""Cancel an in-flight request. Returns True if successfully cancelled."""
@abstractmethod
def get_provider_health(self) -> dict:
"""Return health status of all registered providers with per-provider metrics."""
class IProvider(Protocol):
"""Protocol for provider adapter implementations."""
async def complete(
self,
messages: list[Message],
model: str,
parameters: dict,
) -> RawProviderResponse:
"""Send completion request to provider."""
def get_models(self) -> list[ModelInfo]:
"""Return available models with metadata."""
def supports_capability(self, capability: ModelCapability) -> bool:
"""Check if provider supports a capability."""
class ModelCapability(Enum):
REASONING = "reasoning"
CODING = "coding"
VISION = "vision"
TOOL_USE = "tool_use"
LONG_CONTEXT = "long_context"
EMBEDDING = "embedding"
SUMMARIZATION = "summarization"
class RoutingStrategy(Enum):
COST_OPTIMIZED = "cost_optimized" # Cheapest eligible provider
LATENCY_OPTIMIZED = "latency_optimized" # Lowest P95 latency provider
QUALITY_OPTIMIZED = "quality_optimized" # Highest quality_rank provider
ROUND_ROBIN = "round_robin" # Rotate across eligible providers
class ProviderRequest:
messages: list[Message]
model: str | None = None
capabilities: list[ModelCapability] | None = None
parameters: dict | None = None
routing_strategy: RoutingStrategy = RoutingStrategy.COST_OPTIMIZED
max_tokens: int | None = None
volk_id: str | None = None
session_id: str | None = None
project_id: str | None = None
class ProviderResponse:
content: str
model_used: str
provider_used: str
usage: TokenUsage
cost: CostRecord
latency_ms: float
metadata: dict
class TokenUsage:
input_tokens: int
output_tokens: int
total_tokens: int
class CostRecord:
input_cost: float
output_cost: float
total_cost: float
currency: str = "USD"
class CostEstimate:
estimated_input_tokens: int
estimated_output_tokens: int
estimated_cost: CostRecord
provider: str
model: str
class BudgetStatus:
scope: str # "volk" | "project" | "portfolio"
scope_id: str
budget_limit_usd: float
spent_usd: float
remaining_usd: float
usage_percent: float
warning_threshold: float # e.g., 0.8
is_warning: bool
is_exceeded: bool
class ProviderConfig:
api_key: str
base_url: str | None = None
models: list[str] = []
rate_limit_rpm: int = 60
rate_limit_tpm: int = 100_000
timeout_seconds: float = 30.0
max_retries: int = 3
pricing: dict[str, ModelPricing] = {}
class ModelPricing:
input_per_1m: float # USD per 1M input tokens
output_per_1m: float # USD per 1M output tokens
capabilities: list[ModelCapability]
context_window: int
quality_rank: int = 50 # 1-100, lower = higher quality (used by QUALITY_OPTIMIZED strategy)
Design Notes
- Partial cost on failed requests: When a provider fails mid-response, the system records the attempt in the audit trail with status "failed" and cost estimated from input tokens only (output tokens unknown). The actual billed cost may differ — this is documented as a known cost tracking gap for failed requests.
- Message schema: The
Messagetype in v1.0 usesrole: str, content: strfor simplicity. Tool use messages, image content, and structured content blocks are deferred to v1.1. Adapters that need structured content (Anthropic tool_use, OpenAI function calling) handle the translation internally from the string content. - Config migration: The spec's provider config schema replaces the existing
runtime.providerssection inproduction.yaml. Existing deployments must update their config files. The old schema (flat list of providers with just api_key and models) is not backward-compatible with the new schema (which adds rate limits, pricing, capabilities). A one-time migration script will be provided. - Health monitoring: Passive only — computed from request metrics (latency, error rate) using a sliding window of the last 100 requests per provider. No active health probes. Zero cost.
- get_health vs IComponent:
IProviderManagerinheritsget_health() -> HealthStatusfromIComponentfor the overall component health. The additionalget_provider_health() -> dictmethod returns per-provider detail. No LSP violation. - Phase 2 coexistence (SPEC-009): During migration, the verification package gets
IProviderManagervia DI. TheVerificationProviderAdapterwrapsIProviderManager.execute()to expose the oldLLMModel.verify()/analyze()/generate_proof()API.ModelRegistryis no longer the source of truth —IProviderManageris. The adapter delegates all routing/fallback to the new system.
Integration Points
| Component | Integration |
|---|---|
| orchestrata-core | IProviderManager interface, ComponentType.PROVIDER_MANAGER |
| orchestrata-kernel | Provider selection before Volk execution |
| orchestrata-agents | Volk uses provider manager for LLM calls |
| orchestrata-governance | PAM++ model access policies, budget governance |
| orchestrata-verification | Replaces existing ModelRegistry/FallbackChain |
| orchestrata-session | Cost tracking per session |
| orchestrata-cxp | Provider metadata in CXP packet metadata dict |
| configs/production.yaml | Provider configuration source |
Kernel Provider Selection
The Kernel selects a provider at the existing governance check point in runtime.py, after Volk definition lookup and before execution begins.
Selection flow:
- Volk definition specifies
capabilities(e.g.,[REASONING, CODING]) or explicitmodelname - Kernel calls
provider_manager.execute(request)— routing is internal to Provider Manager - Provider Manager filters eligible providers by: capability match → availability → rate limit headroom → budget remaining
- Applies
RoutingStrategy(default:COST_OPTIMIZED) to rank remaining providers - Selected provider + model recorded in CXP packet
metadata["provider_selection"]
Configuration per Volk:
# configs/production.yaml
volks:
"U:Ar":
preferred_model: "deepseek-reasoner"
required_capabilities: ["reasoning"]
budget_limit_usd: 50.0
routing_strategy: "quality_optimized"
"U:Vz":
preferred_model: null # auto-select
required_capabilities: ["coding"]
budget_limit_usd: 100.0
routing_strategy: "cost_optimized"
"U:Qa":
preferred_model: null
required_capabilities: ["reasoning", "coding"]
budget_limit_usd: 30.0
routing_strategy: "cost_optimized"
SPEC-009 Migration Strategy
The existing ModelRegistry and ModelFallbackChain in orchestrata-verification are verification-specific (they expose verify(), analyze(), generate_proof() methods). SPEC-011 generalizes provider management for all Volks.
Migration path: Adapter pattern (no breaking change)
orchestrata-verificationkeeps itsLLMModelprotocol andModelRegistryunchanged- A new
VerificationProviderAdapterimplementsIProviderand wrapsLLMModel.verify/analyze/generate_proofas provider completions ModelFallbackChainis deprecated in favor of SPEC-011'sFallbackChainwith circuit breaker- Verification package receives
IProviderManagervia DI instead of constructing its ownModelRegistry - Existing
ModelRegistry.register()calls migrate toprovider_manager.register_provider()
Timeline:
- Phase 1: SPEC-011 implemented independently, no changes to SPEC-009
- Phase 2:
VerificationProviderAdapterbridges old API to new - Phase 3: Verification tests migrated to use
IProviderManagerdirectly - Phase 4: Old
ModelRegistry/ModelFallbackChaindeprecated (not removed until v0.4.0)
CXP Integration
Provider metadata uses the existing metadata dict on CXP packets. No schema change to SPEC-004 required.
Convention:
packet.metadata["provider_selection"] = {
"provider": "deepseek",
"model": "deepseek-reasoner",
"routing_strategy": "quality_optimized",
"fallback_chain": ["deepseek", "anthropic", "openai"],
"cost_estimate_usd": 0.0042,
"rate_limit_headroom_pct": 85.0
}
Event Delivery
All provider events are emitted through the Kernel's EventBus (see SPEC-001 EventBus addendum). The Provider Manager does not push to Redis directly — it calls kernel.emit_event().
Emission pattern:
await kernel.emit_event(SystemEvent(
type="provider.request.completed",
source="SYS:PROVIDER",
payload={"request_id": req_id, "provider": provider, "model": model, "cost": cost},
severity="info",
))
Topic mapping:
| Event | Topic | Severity |
|---|---|---|
| Request completed | provider.request.completed |
info |
| Request failed | provider.request.failed |
error |
| Fallback triggered | provider.fallback.triggered |
warning |
| Circuit state changed | provider.circuit.state_changed |
warning |
| Rate limit approaching | provider.ratelimit.approaching |
warning |
| Budget warning | provider.budget.warning |
warning |
| Budget exceeded | provider.budget.exceeded |
critical |
| Provider registered | provider.registered |
info |
| Health changed | provider.health.changed |
warning |
Reactive subscriptions (registered during initialize()):
# Subscribe to volk termination → cancel in-flight requests
await kernel.event_bus.subscribe(
"volk.lifecycle.terminated",
self._on_volk_terminated,
subscriber_id="SYS:PROVIDER",
)
# Subscribe to session close → migrate cost data to Neo4j
await kernel.event_bus.subscribe(
"session.closed",
self._on_session_closed,
subscriber_id="SYS:PROVIDER",
)
This enables reactive behavior without the Provider Manager needing to know about Volk lifecycle internals or session management — it just subscribes to events.
Redis State Persistence
Circuit breaker state:
orchestrata:provider:cb:{provider_name}:state — STRING ("closed" | "open" | "half_open")
orchestrata:provider:cb:{provider_name}:failures — STRING (int, consecutive failure count)
orchestrata:provider:cb:{provider_name}:last_fail — STRING (ISO 8601 timestamp)
orchestrata:provider:cb:{provider_name}:cooldown — STRING (seconds until half-open probe)
TTL: None (persistent across restarts). Reset on successful half_open → closed transition.
Startup recovery: On Provider Manager initialization, all circuit breaker states are read from Redis. Circuits in open state are immediately transitioned to half_open to trigger a probe on the next request. This prevents circuits from being permanently stuck open after a restart.
Rate limit tracking:
orchestrata:provider:rl:{provider_name}:rpm — STRING (int, requests this minute)
orchestrata:provider:rl:{provider_name}:tpm — STRING (int, tokens this minute)
orchestrata:provider:rl:{provider_name}:window — STRING (ISO 8601, current window start)
TTL: 120s (auto-expires, resets on new window).
Cost aggregation (hot cache):
orchestrata:provider:cost:{volk_id}:{date} — HASH {total, input, output, requests}
orchestrata:provider:cost:session:{session_id} — HASH {total, input, output, requests}
orchestrata:provider:cost:project:{project_id} — HASH {total, input, output, requests}
TTL: None (persistent). Migrated to Neo4j on session close via the same on_session_close hook mechanism as SPEC-010.
Budget atomicity: Budget enforcement uses a Redis Lua script that atomically reads current spend, compares against limit, and increments if within bounds:
-- KEYS[1] = cost hash key, ARGV[1] = estimated_cost, ARGV[2] = budget_limit
local current = tonumber(redis.call('HGET', KEYS[1], 'total') or '0')
local estimate = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
if current + estimate > limit then
return 0 -- denied
end
redis.call('HINCRBYFLOAT', KEYS[1], 'total', estimate)
return 1 -- allowed
This eliminates TOCTOU race conditions under concurrent load.
Neo4j Audit Schema
Formal audit schema defined in contracts/graph-schema.cql. Nodes:
(:ProviderAudit)— per-request audit record with request_id, volk_id, provider, model, tokens, cost, latency, status, timestamp(:CostRecord)— aggregated cost record with scope, scope_id, total, input, output, period_start, period_end(:ProviderHealth)— health snapshot with provider, status, error_rate, latency_p95, recorded_at
Relationships:
(:ProviderAudit)-[:PERFORMED_BY]->(:Volk)(:ProviderAudit)-[:DURING]->(:Session)(:ProviderAudit)-[:BILLED_TO]->(:Project)(:CostRecord)-[:FOR_VOLK]->(:Volk)(:CostRecord)-[:FOR_SESSION]->(:Session)(:CostRecord)-[:FOR_PROJECT]->(:Project)
Prolog Governance Predicates
The Provider Manager extends the PAM++ rule base:
%% Model access governance
allowed_model(VolkId, Model) :-
registered(VolkId),
volk_model_allowlist(VolkId, Model).
allowed_model(VolkId, Model) :-
registered(VolkId),
\+ volk_model_denylist(VolkId, _),
model_registered(Model).
%% Budget governance
within_budget(VolkId, EstimatedCost) :-
volk_budget_limit(VolkId, Limit),
volk_current_spend(VolkId, Spent),
Spent + EstimatedCost =< Limit.
%% Emergency escalation override
budget_override(VolkId, _EstimatedCost) :-
escalation_active(VolkId),
has_role(VolkId, escalation_authority).
%% Provider access
can_use_provider(VolkId, Provider) :-
registered(VolkId),
provider_available(Provider),
\+ provider_blocked_for(VolkId, Provider).
Rate Limit Header Parsing
Per-provider rate limit header mapping:
| Provider | RPM Header | TPM Header | Remaining Header |
|---|---|---|---|
| OpenAI | x-ratelimit-limit-requests |
x-ratelimit-limit-tokens |
x-ratelimit-remaining-requests |
| Anthropic | anthropic-ratelimit-requests-limit |
anthropic-ratelimit-tokens-limit |
anthropic-ratelimit-requests-remaining |
| DeepSeek | x-ratelimit-limit-requests |
x-ratelimit-limit-tokens |
x-ratelimit-remaining-requests |
| Ollama | N/A (local) | N/A (local) | N/A |
Configuration:
# configs/production.yaml
runtime:
providers:
openai:
api_key: «redacted»
base_url: "https://api.openai.com/v1"
models: ["gpt-4o", "gpt-4o-mini", "o1", "o3-mini"]
rate_limit_rpm: 500
rate_limit_tpm: 200000
timeout_seconds: 60.0
max_retries: 3
rate_limit_headers:
rpm: "x-ratelimit-limit-requests"
tpm: "x-ratelimit-limit-tokens"
remaining_rpm: "x-ratelimit-remaining-requests"
pricing:
gpt-4o:
input_per_1m: 2.50
output_per_1m: 10.00
capabilities: ["reasoning", "coding", "vision", "tool_use"]
context_window: 128000
gpt-4o-mini:
input_per_1m: 0.15
output_per_1m: 0.60
capabilities: ["coding", "tool_use"]
context_window: 128000
anthropic:
api_key: «redacted»
base_url: "https://api.anthropic.com/v1"
models: ["claude-opus-4-6", "claude-sonnet-4-6"]
rate_limit_rpm: 1000
rate_limit_tpm: 400000
timeout_seconds: 60.0
max_retries: 3
rate_limit_headers:
rpm: "anthropic-ratelimit-requests-limit"
tpm: "anthropic-ratelimit-tokens-limit"
remaining_rpm: "anthropic-ratelimit-requests-remaining"
pricing:
claude-opus-4-6:
input_per_1m: 15.00
output_per_1m: 75.00
capabilities: ["reasoning", "coding", "vision", "tool_use", "long_context"]
context_window: 200000
claude-sonnet-4-6:
input_per_1m: 3.00
output_per_1m: 15.00
capabilities: ["reasoning", "coding", "vision", "tool_use", "long_context"]
context_window: 200000
deepseek:
api_key: «redacted»
base_url: "https://api.deepseek.com/v1"
models: ["deepseek-reasoner", "deepseek-chat"]
rate_limit_rpm: 300
rate_limit_tpm: 100000
timeout_seconds: 120.0
max_retries: 2
pricing:
deepseek-reasoner:
input_per_1m: 0.55
output_per_1m: 2.19
capabilities: ["reasoning"]
context_window: 65536
deepseek-chat:
input_per_1m: 0.27
output_per_1m: 1.10
capabilities: ["coding", "tool_use"]
context_window: 65536
ollama:
api_key: null
base_url: "http://localhost:11434"
models: ["llama3.1:70b", "codellama:34b"]
rate_limit_rpm: 0 # unlimited (local)
rate_limit_tpm: 0 # unlimited (local)
timeout_seconds: 300.0
max_retries: 1
pricing:
"llama3.1:70b":
input_per_1m: 0.0
output_per_1m: 0.0
capabilities: ["reasoning", "coding"]
context_window: 131072
Budget Query API
Volks and the orchestrator can query remaining budget:
# On IProviderManager
async def get_remaining_budget(
self,
*,
volk_id: str | None = None,
project_id: str | None = None,
portfolio_id: str | None = None,
) -> BudgetStatus:
"""Query remaining budget for a scope."""
class BudgetStatus:
scope: str # "volk" | "project" | "portfolio"
scope_id: str
budget_limit_usd: float
spent_usd: float
remaining_usd: float
usage_percent: float
warning_threshold: float # e.g., 0.8
is_warning: bool
is_exceeded: bool
Success Criteria
- Interface Complete: IProviderManager implemented with execute, estimate_cost, get_usage, register_provider
- Adapters: OpenAI, Anthropic, DeepSeek, Ollama adapters working
- Resilience: Fallback chain + circuit breaker handling provider failures transparently
- Cost Tracking: Per-request cost recording with aggregation queries
- Performance: Routing overhead < 20ms, no measurable impact on LLM latency
- Governance: PAM++ enforced on model access and budget limits
- Integration: DI-registered, verification package migrated to use IProviderManager
Dependencies
- Python 3.11+
- httpx (async HTTP client for provider APIs)
- tiktoken (token counting for OpenAI models)
- orchestrata-core (IComponent, ComponentType)
- orchestrata-governance (PAM++ policy engine)
- orchestrata-di (dependency injection container)
- orchestrata-verification (migration target for ModelRegistry)
- configs/production.yaml (provider configuration)
Revision History
| Date | Version | Author | Changes |
|---|---|---|---|
| 2026-03-09 | 1.0 | Mohamed | Initial specification |