Orchestrata Ultimate / Specs / 028-llm-client
028-llm-client: LLM Client Facade
The LLM Client Facade is the single async entry point through which every Volk makes LLM calls in Orchestrata. It routes each request through the ProviderManager using per-volk provider/model assignments, injects Knowledge Graph context into prompts, validates that the LLM used the injected entities, and retries transient errors with exponential backoff.
At a glance
| Item | Value |
|---|---|
| Spec directory | specs/028-llm-client/ |
| Status line in the spec | Implemented |
| Requirement IDs defined | 56 |
With a @traces citation in src/ |
34 (60.7%) |
With a @traces citation in tests/ |
1 (1.8%) |
| Tracked files | 6 |
| RTM page | 028-llm-client |
Reading this spec
The LLM Client Facade is the single async entry point through which every Volk makes LLM calls. It routes each request through the Provider Manager of spec 011 using per-Volk provider and model assignments, injects knowledge graph context into prompts, validates that the LLM used the injected entities, and retries transient errors. A lightweight facade keeps provider selection and retry logic out of individual Volk implementations.
The scope is sixteen functional requirements: the single entry point, reasoning tier access, provider configuration and registration, transient error retry, permanent error propagation, knowledge graph context injection, compliance validation, environment variable overrides, the exception hierarchy, Volk resolution, lazy adapter initialization, adapter replacement, compliance metadata, logging and provider lifecycle. Requirement IDs use the REQ-028- prefix.
The key decisions are these. call_llm() is the one entry point and every other function delegates to it. A VolkConfig maps each Volk alias to a provider, a model and a reasoning model, and environment variable overrides take precedence, so Volks never hardcode provider or model choices. The named providers are claude_cli, anthropic, deepseek, openrouter and ollama. Transient errors (HTTP 429, 500, 502, 503, timeouts, connection refusals) are retried with exponential backoff and jitter up to MAX_RETRIES; permanent errors (400, 401, 403, 404, malformed responses) propagate. Graph context is a structured prefix on the first user message, and non-compliant responses are retried up to MAX_VALIDATION_RETRIES before being passed through with a warning. Reasoning-tier tasks (U:Ar and U:SD) use a distinct high-capability model tier.
In the code, the package is orchestrata_ultimate/llm: orchestrata_ultimate/llm/client holds most citations, with orchestrata_ultimate/llm/exceptions, orchestrata_ultimate/llm/config and orchestrata_ultimate/core/infra_config. The per-requirement view is on the RTM page.
The spec's status line reads Implemented. It tracks 6 files and defines 56 requirement IDs. The full text follows below.
Measured coverage is 34 of 56 requirements cited by at least one @traces in src/ (60.7%), spread over 6 modules; the module citing the most of them is orchestrata_ultimate/llm/client (26 requirements). In tests/, 1 requirement is cited by a test-side @traces.
Coverage gaps: 22 requirements have no citing symbol. The largest untraced groups are REQ-028-* (22). 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 34).
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-028-001 |
no | call_llm is an async function with signature call_llm(volk_id: str, messages: list[dict], *, model: str | None, provider: str | None, temperature: float | None, max_tokens: int | None, graph_context: dict | None, reasoning: bool) -> str |
none |
REQ-028-002 |
no | call_llm resolves the effective provider via get_volk_provider(volk_id) unless provider override is supplied |
none |
REQ-028-003 |
no | call_llm resolves the effective model via get_volk_model(volk_id, reasoning=reasoning) unless model override is supplied |
1 |
REQ-028-004 |
no | call_llm delegates the resolved request to ProviderManager.complete(provider, model, messages) |
none |
REQ-028-005 |
no | call_llm injects graph context into the first user message before dispatching when graph_context is non-empty |
1 |
REQ-028-006 |
no | call_llm validates graph context compliance on the response and retries up to MAX_VALIDATION_RETRIES on failure |
10 |
REQ-028-007 |
no | call_llm returns the plain assistant response text as a str |
none |
REQ-028-008 |
no | call_llm raises LLMClientError for permanent provider errors (HTTP 400, 401, 403, 404) |
none |
REQ-028-009 |
no | call_llm raises LLMProviderUnavailableError after exhausting all retry attempts on transient errors |
none |
REQ-028-010 |
no | call_llm logs each attempt at DEBUG level and each retry at WARNING level with attempt index and backoff delay |
none |
REQ-028-011 |
no | call_reasoner is an async function with the same signature as call_llm minus the reasoning parameter |
1 |
REQ-028-012 |
no | call_reasoner delegates to call_llm with reasoning=True |
1 |
REQ-028-013 |
no | Volks in REASONING_VOLKS calling call_reasoner receive the reasoning_model from their VolkConfig entry |
1 |
REQ-028-014 |
no | Volks not in REASONING_VOLKS calling call_reasoner receive their standard model (no error raised) |
1 |
REQ-028-015 |
no | REASONING_VOLKS is defined as the frozen set {"U:Ar", "U:SD"} |
1 |
REQ-028-016 |
no | VolkConfig is a dataclass with fields provider: str, model: str, reasoning_model: str |
none |
REQ-028-017 |
no | Default assignments are defined for U:Cx → claude/claude-sonnet-4-5 |
none |
REQ-028-018 |
no | Default assignments are defined for U:Ar → claude/claude-opus-4-5 (standard) and claude/claude-opus-4-5 (reasoning) |
none |
REQ-028-019 |
no | Default assignments are defined for J:Junior → claude/claude-sonnet-4-5 |
none |
REQ-028-020 |
no | Default assignments are defined for U:Ox, U:PS, U:Cd, U:Ux, U:PM, U:Qa, U:SD covering all system Volks |
none |
REQ-028-021 |
no | A sentinel default key in VolkConfig map applies to any unrecognised volk_id |
8 |
REQ-028-022 |
no | ORCHESTRATA_LLM_PROVIDER env var globally overrides the provider field for all Volks |
1 |
REQ-028-023 |
no | ORCHESTRATA_LLM_MODEL env var globally overrides the model field for all Volks |
1 |
REQ-028-024 |
no | ORCHESTRATA_LLM_REASONING_MODEL env var globally overrides the reasoning_model field for all Volks |
none |
REQ-028-025 |
no | Per-volk env var overrides (pattern ORCHESTRATA_LLM_MODEL_<VOLK_SANITISED>) take precedence over global overrides |
none |
REQ-028-026 |
no | get_volk_provider(volk_id: str) -> str returns the effective provider after applying env overrides |
1 |
REQ-028-027 |
no | get_volk_model(volk_id: str, reasoning: bool = False) -> str returns reasoning_model when reasoning=True, else model |
none |
REQ-028-028 |
no | Both functions fall back to the default config entry when volk_id is not in the map |
4 |
REQ-028-029 |
no | HTTP status codes 429, 500, 502, 503 are classified as transient errors | 1 |
REQ-028-030 |
no | asyncio.TimeoutError and concurrent.futures.TimeoutError are classified as transient errors |
1 |
REQ-028-031 |
no | ConnectionRefusedError, ConnectionResetError, and aiohttp.ClientConnectorError are classified as transient errors |
1 |
REQ-028-032 |
no | HTTP status codes 400, 401, 403, 404 are classified as permanent errors | 1 |
REQ-028-033 |
no | A helper is_transient(exc) -> bool function encapsulates all transient error detection logic |
1 |
REQ-028-034 |
no | Maximum retry attempts is MAX_RETRIES = 5 (6 total attempts including the first) |
2 |
REQ-028-035 |
no | Backoff delay formula is min(base_delay * 2 ** attempt + random.uniform(0, jitter), max_delay) |
2 |
REQ-028-036 |
no | Default base_delay = 1.0 second, jitter = 0.5 seconds, max_delay = 60.0 seconds |
2 |
REQ-028-037 |
no | On permanent error, the exception is re-raised immediately without any retry | 2 |
REQ-028-038 |
no | After MAX_RETRIES transient failures, LLMProviderUnavailableError is raised with the final underlying exception attached as __cause__ |
2 |
REQ-028-039 |
no | When graph_context is a non-empty dict, its content is serialised to a human-readable block |
none |
REQ-028-040 |
no | The serialised block is prepended to the content of the first message where role == "user" |
1 |
REQ-028-041 |
no | The injected block is delimited by ### Knowledge Graph Context ### header and ### End Graph Context ### footer |
none |
REQ-028-042 |
no | The original user message content follows the injected block verbatim without modification | 1 |
REQ-028-043 |
no | When graph_context is None or an empty dict, no prefix is added and the messages list is unchanged |
1 |
REQ-028-044 |
no | After receiving a response, the facade extracts entity names from graph_context |
none |
REQ-028-045 |
no | Compliance passes if at least one entity name appears (case-insensitive substring match) in the response text | none |
REQ-028-046 |
no | On compliance failure, a nudge message "Please reference the provided graph context entities in your response." is appended to messages and the call retried |
none |
REQ-028-047 |
no | Maximum validation retries is MAX_VALIDATION_RETRIES = 3 |
none |
REQ-028-048 |
no | After MAX_VALIDATION_RETRIES compliance failures, the last response is returned with compliance_warning=True in call metadata; no exception is raised |
none |
REQ-028-049 |
no | Compliance validation is entirely skipped when graph_context is None or empty |
2 |
REQ-028-050 |
no | ProviderManager is accessed via a module-level get_provider_manager() function returning a singleton |
1 |
REQ-028-051 |
no | Default adapter registration covers claude_cli, anthropic, deepseek, openrouter, ollama |
1 |
REQ-028-052 |
no | Each adapter is lazily initialised on first use (constructor not called at import time) | 1 |
REQ-028-053 |
no | Registering a provider name that already exists silently replaces the previous adapter | 1 |
REQ-028-054 |
no | LLMClientError(Exception) is the base exception for all LLM client failures |
1 |
REQ-028-055 |
no | LLMProviderUnavailableError(LLMClientError) signals exhausted retries or no available provider |
1 |
REQ-028-056 |
no | Both exception classes are importable from the orchestrata_llm package top-level __init__.py |
1 |
Code that traces this spec
Checklists
checklists/constitutional.md
LLM Client Facade Constitutional Checklist
Security: API Key Handling
- [ ] CONST-028-001: API keys and tokens for all provider adapters must be read exclusively from environment variables; never hardcoded or logged
- [ ] CONST-028-002: Provider credentials must never appear in exception messages, log output, or stack traces
- [ ] CONST-028-003: The
ProviderManagersingleton must not expose raw credentials through any public property or method
Privacy: Prompt and Response Confidentiality
- [ ] CONST-028-004: Full prompt content (including injected graph context) must not be logged at any level below CRITICAL without explicit operator opt-in
- [ ] CONST-028-005: LLM response text must not be logged in full; only response length and compliance status may be logged by default
- [ ] CONST-028-006: Graph context entity names used in compliance checks must not be written to persistent audit logs that outlive the session
Reliability: Retry Safety
- [ ] CONST-028-007: Retry logic must be idempotent — retrying a failed request must not produce duplicate side effects in downstream systems
- [ ] CONST-028-008: Backoff delays must include random jitter to prevent thundering-herd on provider recovery
- [ ] CONST-028-009: The retry cap (
MAX_RETRIES = 5) must be enforced as a hard limit; no code path may silently exceed it - [ ] CONST-028-010: Permanent errors (4xx excluding 429) must never be retried regardless of caller configuration
Reliability: Provider Isolation
- [ ] CONST-028-011: A failure in one provider adapter must not affect calls routed to other providers
- [ ] CONST-028-012: Adapter initialisation failures must raise
LLMProviderUnavailableErrorimmediately rather than leaving the manager in a partially initialised state
Correctness: Graph Context Integrity
- [ ] CONST-028-013: Graph context injection must never truncate or modify the original user message content
- [ ] CONST-028-014: The compliance validation nudge message must be appended, not substituted, so the original instruction remains in context
- [ ] CONST-028-015: Compliance validation must not block the caller indefinitely;
MAX_VALIDATION_RETRIESis an absolute ceiling
Performance
- [ ] CONST-028-016: The
call_llmhot path (no graph context, no retries needed) must add less than 2ms overhead versus calling the provider adapter directly - [ ] CONST-028-017:
VolkConfigresolution (including env var lookup) must complete in under 1ms per call - [ ] CONST-028-018: Adapter lazy-init must not introduce latency on subsequent calls after the first initialisation
Ethical and Responsible AI
- [ ] CONST-028-019: The compliance validation mechanism must not be used to coerce the LLM into producing outputs that contradict its safety guidelines
- [ ] CONST-028-020:
call_reasonerelevation to reasoning-tier models must only apply to volks with legitimate architectural or strategic roles; arbitrary caller escalation must not be permitted through config alone
checklists/requirements.md
The requirement list above comes from this file; 0 of 56 checkbox items are ticked.
checklists/testing.md
LLM Client Facade Testing Checklist
call_llm Interface Tests
- [ ] TEST-028-001: Verify
call_llmsignature accepts all documented parameters with correct types; call with minimal args (volk_id + messages) succeeds against a live provider - [ ] TEST-028-002: Verify provider is resolved from
VolkConfigwhen noprovideroverride is supplied - [ ] TEST-028-003: Verify model is resolved from
VolkConfigwhen nomodeloverride is supplied - [ ] TEST-028-004: Verify
call_llmdispatches toProviderManager.completewith the resolved provider/model and messages - [ ] TEST-028-005: Verify graph context injection occurs when
graph_contextis non-empty; assert injected block appears in messages sent to provider - [ ] TEST-028-006: Verify compliance validation is attempted after receiving a response when
graph_contextis provided - [ ] TEST-028-007: Verify return type is
strand matches the provider response text - [ ] TEST-028-008: Verify
LLMClientErroris raised on HTTP 400/401/403/404 without any retry - [ ] TEST-028-009: Verify
LLMProviderUnavailableErroris raised afterMAX_RETRIEStransient failures - [ ] TEST-028-010: Verify WARNING log entry is emitted for each retry attempt including attempt index and delay
call_reasoner Interface Tests
- [ ] TEST-028-011: Verify
call_reasonerdelegates tocall_llmwithreasoning=True - [ ] TEST-028-012: Verify U:Ar resolves to
reasoning_model(claude-opus) whencall_reasoneris used - [ ] TEST-028-013: Verify a volk not in
REASONING_VOLKScallingcall_reasonerreceives its standard model without error - [ ] TEST-028-014: Verify
REASONING_VOLKScontains exactly{"U:Ar", "U:SD"}and no other aliases - [ ] TEST-028-015: Verify
call_reasonerreturns the same response string ascall_llmfor identical inputs
VolkConfig Tests
- [ ] TEST-028-016: Verify
VolkConfigdataclass hasprovider,model, andreasoning_modelfields - [ ] TEST-028-017: Verify default config entry for
U:Cxmaps toclaudeprovider andclaude-sonnet-4-5model - [ ] TEST-028-018: Verify default config entry for
U:Armaps toclaudeprovider andclaude-opus-4-5for both standard and reasoning model - [ ] TEST-028-019: Verify default config entry for
J:Juniormaps toclaudeprovider andclaude-sonnet-4-5model - [ ] TEST-028-020: Verify all system Volk aliases (
U:Ox,U:PS,U:Cd,U:Ux,U:PM,U:Qa,U:SD) have entries in the config map - [ ] TEST-028-021: Verify
defaultsentinel entry is present and used for unrecognised volk_id values - [ ] TEST-028-022: Verify
ORCHESTRATA_LLM_PROVIDERenv var overrides provider for all Volks; unset env var restores default - [ ] TEST-028-023: Verify
ORCHESTRATA_LLM_MODELenv var overrides standard model for all Volks - [ ] TEST-028-024: Verify
ORCHESTRATA_LLM_REASONING_MODELenv var overrides reasoning model for all Volks - [ ] TEST-028-025: Verify per-volk env var (e.g.
ORCHESTRATA_LLM_MODEL_U_AR) takes precedence over the globalORCHESTRATA_LLM_MODEL
get_volk_provider / get_volk_model Tests
- [ ] TEST-028-026: Verify
get_volk_provider("U:Cx")returns"claude"under default config - [ ] TEST-028-027: Verify
get_volk_model("U:Ar", reasoning=True)returns"claude-opus-4-5"under default config - [ ] TEST-028-028: Verify
get_volk_model("unknown-volk")returns the default model without raising
Retry Logic: Transient Error Detection Tests
- [ ] TEST-028-029: Verify HTTP 429 response triggers retry; confirm exactly 5 retries before
LLMProviderUnavailableError - [ ] TEST-028-030: Verify HTTP 500/502/503 responses are each classified as transient and retried
- [ ] TEST-028-031: Verify
asyncio.TimeoutErroris classified as transient and retried - [ ] TEST-028-032: Verify
ConnectionRefusedErroris classified as transient and retried - [ ] TEST-028-033: Verify
is_transientreturnsFalsefor HTTP 400, 401, 403
Retry Logic: Backoff and Limits Tests
- [ ] TEST-028-034: Verify
MAX_RETRIES = 5; assert no more than 5 retry attempts occur in any code path - [ ] TEST-028-035: Verify backoff delay for attempt N is within
[base * 2^N, base * 2^N + jitter]range - [ ] TEST-028-036: Verify backoff delay never exceeds
max_delay = 60.0seconds - [ ] TEST-028-037: Verify permanent error (HTTP 400) causes immediate raise with zero retries
- [ ] TEST-028-038: Verify
LLMProviderUnavailableError.__cause__is the final underlying exception after exhausted retries
Graph Context Injection Tests
- [ ] TEST-028-039: Verify non-empty
graph_contextdict produces a prefixed user message containing the### Knowledge Graph Context ###header - [ ] TEST-028-040: Verify the injected prefix appears before the original user message content in the first user-role message
- [ ] TEST-028-041: Verify
### End Graph Context ###footer appears at the end of the injected block - [ ] TEST-028-042: Verify original user message content is preserved verbatim after the injected block
- [ ] TEST-028-043: Verify
Noneor empty dictgraph_contextleaves the messages list completely unchanged
Graph Context Compliance Validation Tests
- [ ] TEST-028-044: Verify compliance check extracts entity names from
graph_contextcorrectly - [ ] TEST-028-045: Verify a response containing an injected entity name passes compliance (no retry)
- [ ] TEST-028-046: Verify a response containing no injected entity names fails compliance and triggers retry with nudge appended
- [ ] TEST-028-047: Verify
MAX_VALIDATION_RETRIES = 3; assert at most 3 compliance retries occur - [ ] TEST-028-048: Verify that after 3 compliance failures the last response is returned with
compliance_warning=True; no exception raised - [ ] TEST-028-049: Verify compliance validation is entirely skipped when
graph_contextis absent
Provider Registration Tests
- [ ] TEST-028-050: Verify
get_provider_manager()returns the same singleton instance on repeated calls - [ ] TEST-028-051: Verify all five default adapters (
claude_cli,anthropic,deepseek,openrouter,ollama) are registered after module import - [ ] TEST-028-052: Verify adapter constructor is not called at import time (lazy init); confirm init occurs on first
completecall - [ ] TEST-028-053: Verify registering a duplicate provider name replaces the previous adapter without error
Exception Tests
- [ ] TEST-028-054: Verify
LLMClientErroris a subclass ofExceptionand can be caught as such - [ ] TEST-028-055: Verify
LLMProviderUnavailableErroris a subclass ofLLMClientError - [ ] TEST-028-056: Verify both exceptions are importable from the
orchestrata_llmtop-level package
Other tracked files
specs/028-llm-client/README.mdspecs/028-llm-client/compliance-audit.md
Full spec text
Verbatim from specs/028-llm-client/spec.md; headings are pushed down two levels.
SPEC-028: LLM Client Facade
Metadata
- Spec ID: 028-llm-client
- Title: LLM Client Facade
- Version: 1.0.0
- Author: Mohamed
- Status: Implemented
- Created: 2026-04-04
- Last Updated: 2026-04-04
- Priority: P1
- Components: call_llm, call_reasoner, VolkConfig, ProviderManager, LLMClientError, LLMProviderUnavailableError
- Dependencies: Python 3.14t, orchestrata-core, orchestrata-knowledge, SPEC-011 (Provider Manager)
Overview
What
The LLM Client Facade is the single async entry point through which every Volk makes LLM calls in Orchestrata. It routes each request through the ProviderManager using per-volk provider/model assignments, injects Knowledge Graph context into prompts, validates that the LLM used the injected entities, and retries transient errors with exponential backoff.
Why
- Volks must not hardcode provider or model choices — assignments must be centralised and overridable via environment variables
- Graph context injection is mandatory: the system must confirm the LLM used injected KG entities, not ignore them
- Transient provider errors (rate limits, 5xx, timeouts, connection resets) must be handled uniformly with backoff rather than propagating to every caller
- Reasoning-tier tasks (U:Ar, U:SD) require a distinct high-capability model tier separate from standard sonnet-class models
- A lightweight facade keeps all provider-selection and retry logic out of individual Volk implementations
How
call_llm()is the single entry point; all other functions delegate to itVolkConfigmaps each Volk alias to a (provider, model, reasoning_model) triple; env-var overrides take precedenceProviderManager(SPEC-011) holds lazy-initialised adapter singletons for each provider backend- Retry loop applies exponential backoff with jitter for up to
MAX_RETRIESattempts on transient errors - Graph context injected as a structured prefix on the first user message; compliance validated post-response
- Non-compliant responses retried up to
MAX_VALIDATION_RETRIEStimes before passing the result through with a warning
Terminology
| Term | Definition |
|---|---|
| Volk | A named agent role in Orchestrata (e.g. U:Ar, U:Cx, J:Junior) |
| VolkConfig | Data structure that maps a Volk alias to its LLM provider, standard model, and reasoning model |
| Provider | An LLM backend adapter: claude_cli, anthropic, deepseek, openrouter, or ollama |
| ProviderManager | SPEC-011 singleton that owns adapter lifecycle and routes requests to the correct provider |
| Transient error | A recoverable failure: HTTP 429, 500, 502, 503, Timeout, or connection-refused |
| Permanent error | A non-recoverable failure: HTTP 400, 401, 403, 404, or malformed response |
| Exponential backoff | Retry delay strategy: delay = min(base_delay * 2^attempt * jitter, max_delay); caps at a configured maximum |
| Graph context | A structured block of KG entities (classes, functions, relationships) prepended to the first user message |
| Compliance validation | Post-response check that the LLM response references at least one of the injected KG entities |
| Reasoning tier | High-capability model assignment (e.g. claude-opus) reserved for volks in REASONING_VOLKS |
| REASONING_VOLKS | Set of Volk aliases that receive reasoning-tier models: {U:Ar, U:SD} |
| MAX_RETRIES | Maximum number of retry attempts for transient errors (default: 5) |
| MAX_VALIDATION_RETRIES | Maximum retries when LLM response fails graph context compliance check (default: 3) |
Data Models
VolkConfig
{
"type": "object",
"properties": {
"volk_alias": {
"type": "string",
"description": "Unique identifier for the Volk (e.g., 'U:Ar', 'U:Cx', 'J:Junior')",
"pattern": "^[A-Z]:[A-Za-z]+$"
},
"provider": {
"type": "string",
"description": "LLM provider backend identifier",
"enum": ["claude_cli", "anthropic", "deepseek", "openrouter", "ollama"],
"default": "anthropic"
},
"model": {
"type": "string",
"description": "Standard model for this Volk",
"examples": ["claude-3-5-sonnet-latest", "deepseek-chat", "ollama/llama3.1"]
},
"reasoning_model": {
"type": "string",
"description": "High-capability model for reasoning tasks",
"examples": ["claude-3-5-opus-latest", "deepseek-reasoner"]
}
},
"required": ["volk_alias", "provider", "model", "reasoning_model"],
"additionalProperties": false
}
LLMRequest
{
"type": "object",
"properties": {
"volk_alias": {
"type": "string",
"description": "Volk making the request",
"required": true
},
"messages": {
"type": "array",
"description": "List of messages in the conversation",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"enum": ["system", "user", "assistant"],
"required": true
},
"content": {
"type": "string",
"description": "Message content",
"required": true
}
},
"required": ["role", "content"]
},
"minItems": 1
},
"knowledge_graph_context": {
"type": "array",
"description": "Entities from knowledge graph to inject",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"type": {"type": "string"},
"description": {"type": "string"},
"relationships": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["name", "type", "description"]
}
},
"provider_override": {
"type": "string",
"description": "Override default provider for this call"
},
"model_override": {
"type": "string",
"description": "Override default model for this call"
},
"reasoning_model_override": {
"type": "string",
"description": "Override default reasoning model for this call"
},
"max_retries_override": {
"type": "integer",
"description": "Override MAX_RETRIES for this call",
"minimum": 0,
"maximum": 10
},
"metadata": {
"type": "object",
"description": "Additional metadata for the request",
"additionalProperties": true
}
},
"required": ["volk_alias", "messages"]
}
LLMResponse
{
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "The assistant's response text",
"required": true
},
"usage": {
"type": "object",
"properties": {
"prompt_tokens": {"type": "integer"},
"completion_tokens": {"type": "integer"},
"total_tokens": {"type": "integer"}
},
"required": ["prompt_tokens", "completion_tokens", "total_tokens"]
},
"model_used": {
"type": "string",
"description": "The actual model that generated the response"
},
"provider": {
"type": "string",
"description": "The provider that handled the request"
},
"compliance_verified": {
"type": "boolean",
"description": "Whether KG context compliance was verified",
"default": false
},
"kg_entities_referenced": {
"type": "array",
"items": {"type": "string"},
"description": "KG entity names found in the response"
},
"retries_attempted": {
"type": "integer",
"description": "Number of retry attempts made",
"minimum": 0
},
"metadata": {
"type": "object",
"description": "Additional response metadata",
"additionalProperties": true
}
},
"required": ["content", "usage", "model_used", "provider"]
}
LLMClientError
{
"type": "object",
"properties": {
"error_type": {
"type": "string",
"enum": ["TRANSIENT", "PERMANENT", "PROVIDER_UNAVAILABLE", "VALIDATION_FAILED"],
"required": true
},
"message": {
"type": "string",
"description": "Human-readable error message",
"required": true
},
"provider": {
"type": "string",
"description": "Provider that raised the error"
},
"model": {
"type": "string",
"description": "Model being used when error occurred"
},
"http_status": {
"type": "integer",
"description": "HTTP status code if applicable"
},
"retry_count": {
"type": "integer",
"description": "Number of retries attempted before failure"
},
"original_error": {
"type": "object",
"description": "Original exception from provider SDK"
}
},
"required": ["error_type", "message"]
}
Functional Requirements
FR-001: Single Entry Point
The LLM Client Facade MUST provide a single async function call_llm() that accepts all LLM requests and routes them through the appropriate provider, model, and retry logic without exposing implementation details to callers.
FR-002: Reasoning Tier Access
The facade MUST provide call_reasoner() function that delegates to call_llm() with automatic selection of the reasoning-tier model based on the Volk's configuration.
FR-003: Provider Configuration
The system MUST support per-Volk provider and model assignments via VolkConfig, with environment variable overrides taking precedence over default assignments.
FR-004: Provider Registration
The ProviderManager MUST maintain a registry of LLM provider adapters, lazily initializing them on first use and supporting registration of new providers at runtime.
FR-005: Transient Error Retry
The facade MUST implement exponential backoff retry for transient errors (HTTP 429, 500-503, timeouts, connection refused) with up to MAX_RETRIES attempts before raising an exception.
FR-006: Permanent Error Propagation
Permanent errors (HTTP 400, 401, 403, 404) MUST be raised immediately without retry attempts.
FR-007: Knowledge Graph Context Injection
When knowledge_graph_context is provided, the facade MUST serialize entities as a structured prefix and prepend them to the first user message with clear delimiters.
FR-008: Compliance Validation
The facade MUST validate that LLM responses reference at least one injected KG entity, retrying up to MAX_VALIDATION_RETRIES times with compliance nudges when validation fails.
FR-009: Environment Variable Overrides
The system MUST support global overrides via ORCHESTRATA_LLM_PROVIDER, ORCHESTRATA_LLM_MODEL, and ORCHESTRATA_LLM_REASONING_MODEL environment variables, with per-Volk overrides taking precedence.
Deliberate divergence (2026-07): The live configuration model was redesigned so that
spike-profiles.yamlis the single source of truth for provider/model assignments (seesrc/orchestrata_ultimate/llm/config.py). TheORCHESTRATA_LLM_*environment variable overrides described above are not evaluated by the current resolver; profile selection is viaSPIKE_PROFILE(default profile when unset). This spec section is retained as the original requirement; the operational behavior follows the profile-based model.
FR-010: Exception Hierarchy
The system MUST define LLMClientError as the base exception and LLMProviderUnavailableError as a specialized subclass for provider unavailability.
FR-011: Volk Resolution
The system MUST provide resolve_provider() and resolve_model() functions that return effective (post-override) values for a given Volk.
FR-012: Lazy Adapter Initialization
Provider adapters MUST be lazily initialized on first use, avoiding startup costs for unused providers.
FR-013: Adapter Replacement
Registering a provider with an existing name MUST replace the previous registration atomically.
FR-014: Compliance Metadata
Non-compliant responses MUST be returned with compliance_verified: false and kg_entities_referenced list in the response metadata.
FR-015: Logging
Each retry attempt MUST be logged at WARNING level with attempt number and calculated delay.
FR-016: Provider Lifecycle
Unregistering all providers MUST cause LLMProviderUnavailableError on the next call requiring a provider.
FR-017: No Direct SDK Imports
No Volk package MAY import provider SDKs directly; all LLM access MUST route through call_llm() or call_reasoner().
Error Conditions
| ID | Condition | HTTP Status | Handling |
|---|---|---|---|
| ERR-028-001 | Rate limit exceeded (429) | 429 | Retry with exponential backoff up to MAX_RETRIES |
| ERR-028-002 | Provider internal server error (500) | 500 | Retry with exponential backoff up to MAX_RETRIES |
| ERR-028-003 | Bad gateway (502) | 502 | Retry with exponential backoff up to MAX_RETRIES |
| ERR-028-004 | Service unavailable (503) | 503 | Retry with exponential backoff up to MAX_RETRIES |
| ERR-028-005 | Request timeout | - | Retry with exponential backoff up to MAX_RETRIES |
| ERR-028-006 | Connection refused | - | Retry with exponential backoff up to MAX_RETRIES |
| ERR-028-007 | Bad request (400) | 400 | Raise LLMClientError immediately (permanent) |
| ERR-028-008 | Unauthorized (401) | 401 | Raise LLMClientError immediately (permanent) |
| ERR-028-009 | Forbidden (403) | 403 | Raise LLMClientError immediately (permanent) |
| ERR-028-010 | Not found (404) | 404 | Raise LLMClientError immediately (permanent) |
| ERR-028-011 | Maximum retries exceeded | - | Raise LLMClientError with TRANSIENT error type |
| ERR-028-012 | Provider not registered | - | Raise LLMProviderUnavailableError |
| ERR-028-013 | Model not available | - | Raise LLMClientError with provider-specific message |
| ERR-028-014 | Invalid Volk alias | - | Raise LLMClientError before provider selection |
| ERR-028-015 | Missing messages list | - | Raise LLMClientError with validation error |
| ERR-028-016 | Malformed response from provider | - | Retry up to MAX_RETRIES, then raise LLMClientError |
| ERR-028-017 | All providers unregistered | - | Raise LLMProviderUnavailableError |
| ERR-028-018 | Compliance validation failed | - | Retry with compliance nudge up to MAX_VALIDATION_RETRIES |
| ERR-028-019 | KG context injection failed | - | Proceed without context, log WARNING |
| ERR-028-020 | Provider adapter initialization failed | - | Retry up to MAX_RETRIES, then raise LLMProviderUnavailableError |
API Contracts
call_llm()
Signature:
async def call_llm(
volk_alias: str,
messages: List[Dict[str, str]],
knowledge_graph_context: Optional[List[Dict]] = None,
provider_override: Optional[str] = None,
model_override: Optional[str] = None,
reasoning_model_override: Optional[str] = None,
max_retries_override: Optional[int] = None,
metadata: Optional[Dict] = None
) -> LLMResponse
Parameters:
volk_alias(str, required): The Volk making the requestmessages(List[Dict], required): Conversation messages with role and contentknowledge_graph_context(Optional[List[Dict]], optional): KG entities to injectprovider_override(Optional[str], optional): Override default providermodel_override(Optional[str], optional): Override default modelreasoning_model_override(Optional[str], optional): Override reasoning modelmax_retries_override(Optional[int], optional): Override retry count (0-10)
Returns:
LLMResponse: Object containing content, usage statistics, provider/model info, and compliance data
Raises:
LLMClientError: For permanent or exhausted transient errorsLLMProviderUnavailableError: When no providers are available
Example:
response = await call_llm(
volk_alias="U:Cx",
messages=[
{"role": "user", "content": "Explain the architecture"}
],
knowledge_graph_context=[
{
"name": "ProviderManager",
"type": "class",
"description": "Manages LLM provider adapters",
"relationships": ["implements SPEC-011"]
}
]
)
print(response.content)
call_reasoner()
Signature:
async def call_reasoner(
volk_alias: str,
messages: List[Dict[str, str]],
knowledge_graph_context: Optional[List[Dict]] = None,
**overrides
) -> LLMResponse
Parameters:
- Same as
call_llm()with automatic reasoning model selection
Returns:
LLMResponse: Response from reasoning-tier model
Example:
# U:Ar will use reasoning_model from VolkConfig
response = await call_reasoner(
volk_alias="U:Ar",
messages=[{"role": "user", "content": "Design a system architecture"}]
)
resolve_provider()
Signature:
def resolve_provider(volk_alias: str) -> str
Parameters:
volk_alias(str, required): The Volk to resolve
Returns:
- (str): Effective provider name after env var overrides
resolve_model()
Signature:
def resolve_model(volk_alias: str, use_reasoning: bool = False) -> str
Parameters:
volk_alias(str, required): The Volk to resolveuse_reasoning(bool, optional): If True, return reasoning model
Returns:
- (str): Effective model name after env var overrides
Non-Functional Requirements
Performance
- Provider adapter initialization MUST complete within 100ms on first use
- LLM calls MUST complete within configured timeout (default 30s) or raise timeout error
- Retry attempts MUST add minimum overhead (<10ms between attempts)
- Knowledge graph context serialization MUST complete within 50ms for typical entity counts
Scalability
- ProviderManager MUST support registration of up to 50 provider adapters
- Each adapter MUST maintain independent connection pools
- System MUST handle 1000+ concurrent LLM requests without degradation
- Memory usage MUST scale linearly with number of active providers only
Reliability
- System MUST achieve 99.9% availability for LLM calls under normal conditions
- Transient error recovery MUST succeed for 95% of rate-limited requests within retry budget
- Compliance validation MUST detect non-compliant responses with >99% accuracy
- Zero data loss for in-flight requests during provider adapter replacement
Observability
- All LLM calls MUST be logged at INFO level with timing, model, and provider
- All retry attempts MUST be logged at WARNING level with attempt count and delay
- All errors MUST be logged at ERROR level with full context
- Metrics MUST track: request count, success rate, average latency, retry count distribution
Maintainability
- Provider adapter implementations MUST be self-contained in <200 LOC
- Adding a new provider MUST require <50 LOC of adapter code
- All provider-specific code MUST be isolated in adapter layer
- No breaking changes to
call_llm()signature without major version bump
Technical Design
Architecture Overview
The LLM Client Facade follows a layered architecture:
┌─────────────────────────────────────┐
│ Volk Implementations │
│ (U:Ar, U:Cx, J:Junior, etc.) │
└──────────────┬──────────────────────┘
│ call_llm() / call_reasoner()
┌──────────────▼──────────────────────┐
│ LLM Client Facade │
│ • Context injection │
│ • Compliance validation │
│ • Retry logic │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ ProviderManager (SPEC-011) │
│ • Provider registry │
│ • Adapter lifecycle │
│ • Load balancing │
└──────────────┬──────────────────────┘
│
┌──────────┼──────────┐
│ │ │
┌───▼───┐ ┌──▼──┐ ┌──▼──┐
│Claude │ │Deep │ │Open │
│Adapter│ │Seek │ │Router│
└───────┘ └─────┘ └─────┘
Provider Adapters
Each provider implements a standard interface:
class LLMProviderAdapter(ABC):
@abstractmethod
async def initialize(self) -> None: ...
@abstractmethod
async def generate(
self,
model: str,
messages: List[Dict],
**kwargs
) -> Dict: ...
@abstractmethod
async def cleanup(self) -> None: ...
Supported providers:
- claude_cli: Direct Claude CLI integration
- anthropic: Official Anthropic SDK
- deepseek: DeepSeek API
- openrouter: OpenRouter aggregation
- ollama: Local Ollama instance
Retry Strategy
Exponential backoff formula:
delay = min(base_delay * (2 ^ attempt) * jitter, max_delay)
Where:
base_delay: 1 secondjitter: Random value between 0.5 and 1.5max_delay: 60 secondsattempt: 0-based retry attempt number
Knowledge Graph Context Format
<knowledge_graph_context>
<entities>
<entity name="ProviderManager" type="class">
<description>Manages LLM provider adapters</description>
<relationships>implements SPEC-011</relationships>
</entity>
</entities>
</knowledge_graph_context>
Compliance Validation Algorithm
- Extract all entity names from
knowledge_graph_context - Tokenize response text (case-insensitive)
- Check for substring matches of entity names in response
- If no matches found after
MAX_VALIDATION_RETRIES, mark as non-compliant
Examples
Example 1: Basic LLM Call
from llm_client_facade import call_llm
response = await call_llm(
volk_alias="U:Cx",
messages=[
{"role": "user", "content": "What is the purpose of ProviderManager?"}
]
)
print(f"Response: {response.content}")
print(f"Model: {response.model_used}")
print(f"Provider: {response.provider}")
Example 2: Reasoning Tier Call
from llm_client_facade import call_reasoner
# U:Ar automatically uses reasoning-tier model
response = await call_reasoner(
volk_alias="U:Ar",
messages=[
{"role": "user", "content": "Design a microservices architecture"}
]
)
Example 3: With Knowledge Graph Context
context = [
{
"name": "call_llm",
"type": "function",
"description": "Main entry point for LLM requests",
"relationships": ["used by all Volks", "injects KG context"]
},
{
"name": "ProviderManager",
"type": "class",
"description": "Routes requests to provider adapters",
"relationships": ["implements SPEC-011", "contains registry"]
}
]
response = await call_llm(
volk_alias="J:Junior",
messages=[
{"role": "user", "content": "How does call_llm integrate with ProviderManager?"}
],
knowledge_graph_context=context
)
print(f"Compliance verified: {response.compliance_verified}")
print(f"Entities referenced: {response.kg_entities_referenced}")
Example 4: Error Handling
try:
response = await call_llm(
volk_alias="U:Cx",
messages=[{"role": "user", "content": "Hello"}]
)
except LLMClientError as e:
if e.error_type == "TRANSIENT":
print(f"Temporary error, retries exhausted: {e.retry_count}")
elif e.error_type == "PERMANENT":
print(f"Permanent error: {e.message}")
elif e.error_type == "PROVIDER_UNAVAILABLE":
print("No providers available")
Example 5: Environment Variable Overrides
# Override provider globally
export ORCHESTRATA_LLM_PROVIDER=deepseek
# Override model for specific Volk
export ORCHESTRATA_LLM_MODEL_U:AR=claude-3-5-opus-latest
# Override reasoning model globally
export ORCHESTRATA_LLM_REASONING_MODEL=claude-3-5-opus-latest
# Resolves to: provider=deepseek, model=claude-3-5-opus-latest (override)
provider = resolve_provider("U:Cx")
model = resolve_model("U:Cx", use_reasoning=True)
Example 6: Provider Registration
from provider_manager import ProviderManager
from my_custom_provider import MyCustomAdapter
pm = ProviderManager.get_instance()
# Register new provider
pm.register_provider("my_custom", MyCustomAdapter())
# Now available to all Volks
response = await call_llm(
volk_alias="U:Cx",
messages=[{"role": "user", "content": "Test"}],
provider_override="my_custom"
)
Security
Input Validation
- All
volk_aliasvalues MUST be validated against known aliases - Message content MUST be sanitized to prevent prompt injection
knowledge_graph_contextMUST be schema-validated before injection- Model and provider names MUST be validated against registered values
Authentication
- Provider adapters MUST handle authentication using environment variables
- No credentials MAY be logged or exposed in error messages
- API keys MUST be rotated via environment variable updates
- Provider authentication failures MUST raise permanent errors (no retry)
Authorization
- Provider access MUST respect per-Volk assignments
- Environment variable overrides MUST be validated against authorized users
- Provider adapter registration MUST require system-level permissions
- Model access MUST be restricted to authorized Volks (e.g., only U:Ar and U:SD access reasoning tier)
Audit Logging
All LLM calls MUST log:
- Timestamp with microsecond precision
- Volk alias (masked in production logs)
- Provider and model used
- Token usage statistics
- Success/failure status
- Retry count for failures
Example log format:
2026-04-09T10:15:23.456789 INFO [call_llm] volk=U:Cx provider=anthropic model=claude-3-5-sonnet-latest tokens=1250/300 success=true retries=0
Data Privacy
- User messages MUST NOT be logged in full (truncate to 100 chars)
- Knowledge graph context MUST be redacted in logs
- Provider responses MUST NOT be cached unless explicitly requested
- Token usage data MAY be aggregated for billing but not exposed to other Volks
Secure Configuration
- All environment variables MUST be loaded once at startup
- Provider credentials MUST be validated before adapter initialization
- Invalid configuration MUST fail fast with clear error messages
- Configuration MUST be immutable after initialization
Governance
Version Compatibility
call_llm()signature changes require major version bumps- New optional parameters MAY be added with minor version bumps
- Provider adapter interfaces MUST maintain backward compatibility
- Environment variable names MUST remain stable across versions
Deprecation Policy
- Deprecated features MUST be marked with
DeprecationWarning - Minimum 2 minor versions notice before removal
- Migration guides MUST be provided for breaking changes
- Old provider adapters MUST work for at least 1 major version
Provider Support
- Official provider adapters: anthropic, claude_cli, openrouter
- Community provider adapters: deepseek, ollama
- Provider adapters MUST follow versioning of their upstream SDKs
- Breaking changes in provider SDKs MUST trigger adapter updates within 1 week
Change Management
- Provider adapter registration/unregistration MUST be atomic
- Configuration changes MUST take effect within 1 second
- Provider failures MUST trigger alerts but not crash the system
- System MUST degrade gracefully when non-critical providers fail
Review Process
- All provider adapters MUST undergo security review
- New providers MUST be reviewed by 2+ architects
- Compliance validation logic changes MUST be peer-reviewed
- Error handling changes MUST be tested against all provider types
Visual Architecture
Class Diagram
Unit Tests
test_call_llm_basic: Verify basic LLM routingtest_call_reasoner: Verify reasoning tier model selectiontest_retry_logic: Verify exponential backoff for transient errorstest_permanent_error: Verify immediate propagation of permanent errorstest_kg_context_injection: Verify context serialization and injectiontest_compliance_validation: Verify entity reference checkingtest_env_var_overrides: Verify override precedencetest_provider_resolution: Verify provider/model resolutiontest_exception_hierarchy: Verify error types and inheritancetest_adapter_lifecycle: Verify lazy initialization and registration
Integration Tests
test_all_providers: Integration test with all 5 default providerstest_concurrent_requests: Verify thread-safety under loadtest_provider_failover: Verify graceful degradationtest_large_context: Verify performance with large KG contextstest_rate_limit_handling: Verify real-world rate limit scenarios
Test Coverage
- Minimum 90% code coverage for llm_client_facade module
- Minimum 80% code coverage for provider adapters
- All error paths MUST be tested
- All retry scenarios MUST be tested
- Compliance validation MUST achieve >99% accuracy in tests
Quality Gates
- All unit tests MUST pass before merging
- Integration tests MUST pass in CI/CD pipeline
- Performance benchmarks MUST not regress by >5%
- Security scan MUST show no critical or high vulnerabilities
- Documentation coverage MUST be 100% for public APIs
Integration Points
SPEC-011: Provider Manager
- Provides provider registry
- Manages adapter lifecycle
- Handles adapter initialization
- Routes requests to appropriate adapters
Integration:
from provider_manager import ProviderManager
pm = ProviderManager.get_instance()
adapter = pm.get_adapter(provider_name)
orchestrata-knowledge: Knowledge Graph
- Provides entity retrieval
- Supplies context injection data
- Validates entity relationships
- Enables codebase-aware reasoning
Integration:
from orchestrata_knowledge import KnowledgeGraph
kg = KnowledgeGraph()
entities = kg.get_relevant_entities(query)
orchestrata-core: Base Framework
- Provides async runtime
- Supplies logging infrastructure
- Manages configuration loading
- Handles environment variables
Integration:
from orchestrata_core import get_config, setup_logging
config = get_config()
logger = setup_logging()
Volk Implementations
- U:Ar (Architect) - Uses reasoning tier
- U:Cx (Consultant) - Uses standard tier
- J:Junior - Uses standard tier with learning focus
- All Volks integrate via
call_llm()andcall_reasoner()
Integration:
# In any Volk implementation
from llm_client_facade import call_llm, call_reasoner
response = await call_llm("U:Cx", messages)
Monitoring Systems
- Metrics collection via orchestrata-core
- Alerting for provider failures
- Dashboard integration for token usage
- Performance tracking
Integration:
from orchestrata_core.metrics import Counter, Histogram
llm_requests = Counter("llm_requests_total")
response_time = Histogram("llm_response_time_seconds")
User Stories
US-028-001: Single Entry Point for LLM Calls
As a Volk implementation,
I want a single call_llm() async function,
So that I never need to know which provider or model I am using and all retry/context logic is handled for me.
Acceptance Criteria:
- [x]
call_llm()acceptsvolk_alias,messages, and optionalknowledge_graph_context,provider_override,model_override,reasoning_model_override,max_retries_overrideoverrides - [x] Return type is a plain
str(the assistant response text) - [x] All provider selection, retry, and context injection logic is encapsulated inside
call_llm() - [x] No Volk package imports any provider SDK directly
US-028-002: Reasoning Tier via call_reasoner
As a Volk that performs architectural or strategic reasoning (U:Ar, U:SD),
I want a call_reasoner() wrapper that automatically selects the reasoning-tier model,
So that I receive the highest-capability model without manually specifying it in every call site.
Acceptance Criteria:
- [x]
call_reasoner()delegates entirely tocall_llm()withuse_reasoning=True - [x] When
use_reasoning=True, the model resolved fromVolkConfigis thereasoning_modelfield, not the standardmodel - [x] Volks not in
REASONING_VOLKScallingcall_reasoner()receive the standard model (no error) - [x] U:Ar is confirmed to resolve to
reasoning_modelwhen usingcall_reasoner()
US-028-003: Per-Volk Provider and Model Assignment
As an operator deploying Orchestrata, I want each Volk to have a default provider/model assignment that I can override via environment variables, So that I can switch models for specific Volks in production without changing code.
Acceptance Criteria:
- [x]
VolkConfigcontains default assignments for all defined Volk aliases - [x]
ORCHESTRATA_LLM_PROVIDERenv var overrides the provider for all Volks globally - [x]
ORCHESTRATA_LLM_MODELenv var overrides the standard model for all Volks globally - [x]
ORCHESTRATA_LLM_REASONING_MODELenv var overrides the reasoning model for all Volks globally - [x] Per-volk env var overrides (e.g.
ORCHESTRATA_LLM_MODEL_U:AR) take precedence over global overrides - [x]
resolve_provider()andresolve_model()return the effective (post-override) values
US-028-004: Retry with Exponential Backoff on Transient Errors
As a system operator, I want transient provider errors to be retried automatically with exponential backoff, So that short-lived rate limits or provider blips do not surface as errors to the calling Volk.
Acceptance Criteria:
- [x] HTTP 429, 500, 502, 503, Timeout, and connection-refused are classified as transient
- [x] Up to
MAX_RETRIESattempts are made before raisingLLMClientError - [x] Backoff delay follows
delay = min(base_delay * 2^attempt * jitter, max_delay)formula - [x] HTTP 400, 401, 403 are classified as permanent and raised immediately without retry
- [x] Each retry attempt is logged at WARNING level with attempt number and delay
US-028-005: Knowledge Graph Context Injection
As a Volk performing code-aware reasoning, I want relevant KG entities automatically prepended to my prompt, So that the LLM has grounded, accurate knowledge of the codebase without me manually constructing context blocks.
Acceptance Criteria:
- [x] When
knowledge_graph_contextis provided, it is serialised and prepended to the first user message - [x] The injected block is clearly delimited (e.g.
<knowledge_graph_context>...</knowledge_graph_context>) - [x] The original user message content is preserved verbatim after the injected block
- [x] If no entities are present in
knowledge_graph_context, no prefix is added
US-028-006: Graph Context Compliance Validation
As a system architect requiring grounded LLM responses, I want the facade to verify that the LLM actually referenced the injected KG entities in its response, So that Volks acting on LLM output can trust it is grounded in the current codebase state.
Acceptance Criteria:
- [x] After receiving a response, the facade checks that at least one injected entity name appears in the response text
- [x] If the check fails, the call is retried (up to
MAX_VALIDATION_RETRIES) with a compliance nudge added to the prompt - [x] If all validation retries fail, the response is returned with a
compliance_verified: falseflag set in metadata - [x] Compliance check is skipped when
knowledge_graph_contextis null or not provided
US-028-007: Provider Registration and Adapter Lifecycle
As a developer adding a new LLM provider, I want to register a provider adapter once and have it available system-wide via the ProviderManager, So that adding a new backend requires no changes to call sites.
Acceptance Criteria:
- [x] Adapters for
claude_cli,anthropic,deepseek,openrouter, andollamaare registered by default - [x] Each adapter is lazily initialised on first use (no startup cost for unused providers)
- [x]
ProviderManageris a module-level singleton; repeat calls toget_instance()return the same instance - [x] Registering a provider with a duplicate name replaces the previous registration
- [x] Unregistering all providers causes
LLMProviderUnavailableErroron the next call
Acceptance Criteria Summary
- [x]
call_llm()routes requests throughProviderManagerusingVolkConfigassignments - [x]
call_reasoner()selects reasoning-tier models forREASONING_VOLKS - [x] All five default provider adapters are registered and lazily initialised
- [x] Transient errors retried up to 5 times with exponential backoff
- [x] Graph context injected as structured prefix on user message
- [x] Compliance validation retries up to 3 before passing through with warning
- [x]
LLMClientErrorandLLMProviderUnavailableErrorform a clean exception hierarchy - [x] All env var overrides (
ORCHESTRATA_LLM_PROVIDER,ORCHESTRATA_LLM_MODEL,ORCHESTRATA_LLM_REASONING_MODEL) applied at config load time - [x] 719 LOC across 4 source files; zero provider SDK imports outside the adapter layer
Success Criteria
Implementation Success
- [ ]
call_llm()andcall_reasoner()functions are implemented and accessible from a public module - [ ]
VolkConfigdata structure maps all Volk aliases to provider/model/reasoning_model triples - [ ]
ProviderManagersingleton maintains lazy-initialized adapter registry - [ ] Exception hierarchy with
LLMClientErrorandLLMProviderUnavailableErroris functional - [ ] Environment variable overrides (
ORCHESTRATA_LLM_*) are applied at runtime
Functional Success
- [ ] All LLM calls route through
call_llm()without direct SDK imports in Volk packages - [ ] Reasoning tier model selection works for U:Ar and U:SD via
call_reasoner() - [ ] Transient errors (429, 500-503, timeouts) are retried with exponential backoff
- [ ] Permanent errors (400, 401, 403, 404) are raised immediately without retry
- [ ] Knowledge graph context is injected as structured prefix when provided
- [ ] Compliance validation checks for entity references and retries on failure
Performance Success
- [ ] Provider adapter initialization completes within 100ms on first use
- [ ] LLM calls respect configured timeout (default 30s)
- [ ] Retry logic adds minimal overhead (<10ms between attempts)
- [ ] System handles 1000+ concurrent requests without degradation
Observability Success
- [ ] All LLM calls logged at INFO level with timing, model, and provider
- [ ] Retry attempts logged at WARNING level with attempt count and delay
- [ ] Errors logged at ERROR level with full context
- [ ] Metrics track request count, success rate, average latency, retry distribution
Testing Success
- [ ] Unit tests achieve 90%+ code coverage for llm_client_facade module
- [ ] Integration tests pass for all 5 default providers
- [ ] All error paths and retry scenarios are tested
- [ ] Compliance validation tested with >99% accuracy
Observability
Logging Requirements
- Component Logging
- Log all major operations with appropriate log levels
- Include correlation IDs for request tracing
- Log performance metrics for critical operations
- Log errors with full context for debugging
Metrics Requirements
- Performance Metrics
- Operation duration histograms
- Request/error rate counters
- Resource utilization gauges
- Business metrics relevant to domain
Tracing Requirements
- Distributed Tracing
- Trace propagation across component boundaries
- Span attributes for domain-specific context
- Trace sampling based on operational needs
Alerting Requirements
- Critical Alerts
- Error rate spikes
- Performance degradation
- Resource exhaustion
- Business metric anomalies
Dashboard Requirements
- Operational Dashboard
- Real-time system health
- Performance trends
- Error analysis
- Resource monitoring
Revision History
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | 2026-04-04 | Mohamed | Initial specification |
(End of file - total 1007 lines)