clients/ — Infrastructure Clients & External Service Adapters
Thin clients wrapping external infrastructure (PostgreSQL, Valkey, Vault, LLMs, embeddings, federation). Each client owns its connection lifecycle and credential sourcing. Vault is the foundation — all other clients get secrets from it. Clients follow fail-fast semantics: infrastructure failures propagate, never return silent defaults.
Files
__init__.py — Re-exports the public API: HybridEmbeddingsProvider, LLMProvider, PostgresClient, SQLiteClient, ValkeyClient + their factory functions. Note: LatticeClient and FilesManager are NOT exported here — import them directly
vault_client.py — HashiCorp Vault client (AppRole auth). Foundational — every other client depends on this for secrets. Key functions: get_database_url(), get_api_key(), get_auth_secret(), get_service_config(field), preload_secrets(). Exports VaultHealthCheck TypedDict. preload_secrets() is fail-fast — raises RuntimeError if any secret group fails to load
postgres_client.py — PostgresClient with connection pooling and automatic RLS user isolation via SET app.current_user_id. Dict-style results via raw SQL (execute_query, execute_single, execute_update, execute_insert, execute_returning, execute_scalar, execute_transaction). get_pool_stats() returns Dict[str, PoolStats] TypedDict. Note: All execute_* methods are monkey-patched by utils/perf.py at startup when the mira.perf logger is at INFO or DEBUG — see install_db_instrumentation()
valkey_client.py — ValkeyClient for caching, sessions, rate limiting. Dual sync/async clients plus a binary client for raw bytes (numpy arrays). Factory: get_valkey() / get_valkey_client()
sqlite_client.py — SQLiteClient for per-user tool data. Manual user_id filtering (no RLS). Factory: get_sqlite_client(db_path, user_id). Methods: execute_query, execute_insert, execute_update, execute_delete, create_table
llm_provider.py — LLMProvider — universal LLM entry point. Anthropic SDK primary, emergency failover to generic OpenAI-compatible providers. Streaming events, parallel tool execution with circuit breaker, prompt caching, billing integration. NOT a singleton — instantiated per-use. ALWAYS use LLMProvider.generate_response() for ALL LLM calls — never instantiate GenericOpenAIClient directly. internal_llm= param is the single source of truth for all LLM-tuning params — resolves endpoint, model, API key, max_tokens, and effort from InternalLLMConfig (DB-backed). Callers just pass internal_llm='purpose'. Caller-provided params override DB values (explicit > implicit). Mutually exclusive with endpoint_url/model_override/api_key_override (which remain for one-off overrides). Thinking/effort control: auto-resolved from DB config's effort field, or overridden via effort (named level) or thinking_tokens (exact budget) params. Resolved into a ThinkingConfig frozen dataclass at entry, translated per-backend by _anthropic_thinking_params() (adaptive for 4.6, budget for older) and _generic_thinking_params() (effort passthrough). Two public entry points: generate_response() (convenience wrapper adding internal_llm DB resolution + non-streaming consumption) and stream_events() (self-sufficient streaming entry point with explicit keyword params for all LLM-tuning: effort, thinking_tokens, thinking, temperature, max_tokens, container_id, endpoint_url, model_override, model_preference, api_key_override, system_override, allow_negative). stream_events() resolves effort/thinking_tokens → ThinkingConfig internally — direct callers (orchestrator) get the same resolution as generate_response() callers. No **kwargs anywhere in the call chain — all 5 methods (generate_response, stream_events, _generate_non_streaming, _stream_response, _execute_with_tools) use explicit keyword-only params so Python raises TypeError on unknown arguments at the call site. EFFORT_BUDGET_MAP maps named levels to token counts. build_batch_params(purpose, system_prompt, messages, *, cache_ttl=None) — standard way to construct Anthropic Batch API param dicts. All LLM-tuning params (model, max_tokens, effort) resolved from InternalLLMConfig — callers just pass the purpose key. Two states: effort set → thinking/effort params; effort NULL → vanilla API call (no thinking, no temperature). Wraps system prompt in content blocks with cache_control, optional cache_ttl="1h" for paid 1-hour caching. All 4 batch sites (extraction, relationship, consolidation, entity_gc) use this. Exports ThinkingConfig dataclass, EFFORT_BUDGET_MAP dict, build_batch_params function, ToolCall TypedDict, ToolExecution NamedTuple, ToolExecutionResult NamedTuple. Default model from config.api.model
hybrid_embeddings_provider.py — HybridEmbeddingsProvider — local asymmetric embeddings via MongoDB/mdbr-leaf-ir-asym (768-dim). Separate encode_realtime() (query) and encode_deep() (document) methods. Valkey-cached. Singleton via get_hybrid_embeddings_provider()
lattice_client.py — LatticeClient — thin HTTP client for Lattice federation service. Only used by pager_tool. Methods: send_message() -> SendMessageResponse, get_identity() -> LatticeIdentity. Singleton via get_lattice_client()
files_manager.py — FilesManager — Anthropic Files API operations (upload/delete) with segment-scoped cleanup. NOT a singleton — requires an initialized Anthropic client
Patterns to Follow
Credential Sourcing
- VaultClient itself — configured via env vars (
VAULT_ADDR, VAULT_ROLE_ID, VAULT_SECRET_ID). The only place env vars are the primary source
- All clients — source secrets from Vault:
get_database_url(), get_api_key(), get_service_config(field). No env var fallbacks — Vault failure propagates
Singleton Factories
Most clients use a module-level singleton pattern with a get_*() factory function. Use these factories instead of constructing clients directly:
get_valkey() / get_valkey_client() — ValkeyClient
get_hybrid_embeddings_provider() — HybridEmbeddingsProvider
get_sqlite_client(db_path, user_id) — SQLiteClient (cached per user_id:path)
get_lattice_client() — LatticeClient
- Exceptions:
LLMProvider (instantiated per-use), FilesManager (requires Anthropic client), PostgresClient (shared pool but not singleton instances)
Connection Pooling
- PostgresClient: Class-level
ConnectionPool dict keyed by database name. Thread-safe via RLock. Max 20 connections, with automatic connection recycling (1 hour lifetime) and idle timeout (5 minutes)
- ValkeyClient: Module-level global
ConnectionPool, max 50 connections. Dual sync + async + binary clients
- SQLiteClient: No pooling (appropriate for SQLite — fresh connection per request)
- LLMProvider: Anthropic SDK manages its own pooling internally
Billing: Auto-Resolved pricing_key
The billing hooks in generate_response() auto-resolve pricing_key by matching (model, endpoint_url) against config caches via billing.pricing.resolve_pricing_key(). Callsites do NOT need to pass pricing_key. Unknown model+endpoint in user context raises BillingConfigurationError (fails closed). No user context (system/startup calls) → billing skipped.
Fail-Fast Infrastructure
All clients propagate infrastructure failures. Never catch and return None/[]/defaults. Specific patterns:
ValkeyClient.__init__() pings on construction — fails immediately if unreachable
ValkeyClient._load_config() sources URL from Vault — fails if Vault is unavailable
VaultClient raises typed exceptions: FileNotFoundError, PermissionError, RuntimeError, KeyError
PostgresClient lets pool creation and connection exhaustion errors propagate
- Only exception:
FilesManager.delete_file() logs warnings on 404 (intentional — cleanup is best-effort)
RLS User Context
PostgresClient.get_connection() automatically sets app.current_user_id via SQL SET on checkout and RESET on release. This is the core mechanism for PostgreSQL Row Level Security. No manual user filtering needed in queries.
Deferred Imports
Clients use inline imports to break circular dependencies (e.g., PostgresClient.__init__() imports vault_client.get_database_url inline). Follow this pattern when adding new cross-references.
Vault Preloading
Call vault_client.preload_secrets() at startup to bulk-load all secrets into memory cache. This avoids individual lookups and token expiration issues during long-running service operation. Raises RuntimeError if any secret group (API keys, database, auth) fails to load — this is intentional fail-fast behavior.
LLMProvider User Context Propagation
LLMProvider._execute_with_tools() explicitly propagates contextvars to ThreadPoolExecutor worker threads. If you add new threaded execution paths, use contextvars.copy_context() to preserve user context for RLS enforcement.
1---2name: clients-infrastructure-clients-and-external-service-adapters3description: Thin clients wrapping external infrastructure (PostgreSQL, Valkey, Vault, LLMs, embeddings, federation). Each client owns its connection lifecycle and credential sourcing.4---5# clients/ — Infrastructure Clients & External Service Adapters67Thin clients wrapping external infrastructure (PostgreSQL, Valkey, Vault, LLMs, embeddings, federation). Each client owns its connection lifecycle and credential sourcing. Vault is the foundation — all other clients get secrets from it. Clients follow fail-fast semantics: infrastructure failures propagate, never return silent defaults.89## Files1011- **`__init__.py`** — Re-exports the public API: `HybridEmbeddingsProvider`, `LLMProvider`, `PostgresClient`, `SQLiteClient`, `ValkeyClient` + their factory functions. Note: `LatticeClient` and `FilesManager` are NOT exported here — import them directly12- **`vault_client.py`** — HashiCorp Vault client (AppRole auth). Foundational — every other client depends on this for secrets. Key functions: `get_database_url()`, `get_api_key()`, `get_auth_secret()`, `get_service_config(field)`, `preload_secrets()`. Exports `VaultHealthCheck` TypedDict. `preload_secrets()` is fail-fast — raises `RuntimeError` if any secret group fails to load13- **`postgres_client.py`** — `PostgresClient` with connection pooling and automatic RLS user isolation via `SET app.current_user_id`. Dict-style results via raw SQL (`execute_query`, `execute_single`, `execute_update`, `execute_insert`, `execute_returning`, `execute_scalar`, `execute_transaction`). `get_pool_stats()` returns `Dict[str, PoolStats]` TypedDict. **Note:** All execute_* methods are monkey-patched by `utils/perf.py` at startup when the `mira.perf` logger is at INFO or DEBUG — see `install_db_instrumentation()`14- **`valkey_client.py`** — `ValkeyClient` for caching, sessions, rate limiting. Dual sync/async clients plus a binary client for raw bytes (numpy arrays). Factory: `get_valkey()` / `get_valkey_client()`15- **`sqlite_client.py`** — `SQLiteClient` for per-user tool data. Manual user_id filtering (no RLS). Factory: `get_sqlite_client(db_path, user_id)`. Methods: `execute_query`, `execute_insert`, `execute_update`, `execute_delete`, `create_table`16- **`llm_provider.py`** — `LLMProvider` — universal LLM entry point. Anthropic SDK primary, emergency failover to generic OpenAI-compatible providers. Streaming events, parallel tool execution with circuit breaker, prompt caching, billing integration. NOT a singleton — instantiated per-use. ALWAYS use `LLMProvider.generate_response()` for ALL LLM calls — never instantiate GenericOpenAIClient directly. **`internal_llm=` param** is the single source of truth for all LLM-tuning params — resolves endpoint, model, API key, max_tokens, and effort from `InternalLLMConfig` (DB-backed). Callers just pass `internal_llm='purpose'`. Caller-provided params override DB values (explicit > implicit). Mutually exclusive with `endpoint_url`/`model_override`/`api_key_override` (which remain for one-off overrides). Thinking/effort control: auto-resolved from DB config's `effort` field, or overridden via `effort` (named level) or `thinking_tokens` (exact budget) params. Resolved into a `ThinkingConfig` frozen dataclass at entry, translated per-backend by `_anthropic_thinking_params()` (adaptive for 4.6, budget for older) and `_generic_thinking_params()` (effort passthrough). **Two public entry points**: `generate_response()` (convenience wrapper adding `internal_llm` DB resolution + non-streaming consumption) and `stream_events()` (self-sufficient streaming entry point with explicit keyword params for all LLM-tuning: `effort`, `thinking_tokens`, `thinking`, `temperature`, `max_tokens`, `container_id`, `endpoint_url`, `model_override`, `model_preference`, `api_key_override`, `system_override`, `allow_negative`). `stream_events()` resolves `effort`/`thinking_tokens` → `ThinkingConfig` internally — direct callers (orchestrator) get the same resolution as `generate_response()` callers. **No `**kwargs` anywhere in the call chain** — all 5 methods (`generate_response`, `stream_events`, `_generate_non_streaming`, `_stream_response`, `_execute_with_tools`) use explicit keyword-only params so Python raises `TypeError` on unknown arguments at the call site. `EFFORT_BUDGET_MAP` maps named levels to token counts. **`build_batch_params(purpose, system_prompt, messages, *, cache_ttl=None)`** — standard way to construct Anthropic Batch API param dicts. All LLM-tuning params (model, max_tokens, effort) resolved from `InternalLLMConfig` — callers just pass the purpose key. Two states: effort set → thinking/effort params; effort NULL → vanilla API call (no thinking, no temperature). Wraps system prompt in content blocks with `cache_control`, optional `cache_ttl="1h"` for paid 1-hour caching. All 4 batch sites (extraction, relationship, consolidation, entity_gc) use this. Exports `ThinkingConfig` dataclass, `EFFORT_BUDGET_MAP` dict, `build_batch_params` function, `ToolCall` TypedDict, `ToolExecution` NamedTuple, `ToolExecutionResult` NamedTuple. Default model from `config.api.model`17- **`hybrid_embeddings_provider.py`** — `HybridEmbeddingsProvider` — local asymmetric embeddings via `MongoDB/mdbr-leaf-ir-asym` (768-dim). Separate `encode_realtime()` (query) and `encode_deep()` (document) methods. Valkey-cached. Singleton via `get_hybrid_embeddings_provider()`18- **`lattice_client.py`** — `LatticeClient` — thin HTTP client for Lattice federation service. Only used by pager_tool. Methods: `send_message() -> SendMessageResponse`, `get_identity() -> LatticeIdentity`. Singleton via `get_lattice_client()`19- **`files_manager.py`** — `FilesManager` — Anthropic Files API operations (upload/delete) with segment-scoped cleanup. NOT a singleton — requires an initialized Anthropic client2021## Patterns to Follow2223### Credential Sourcing241. **VaultClient itself** — configured via env vars (`VAULT_ADDR`, `VAULT_ROLE_ID`, `VAULT_SECRET_ID`). The only place env vars are the primary source252. **All clients** — source secrets from Vault: `get_database_url()`, `get_api_key()`, `get_service_config(field)`. No env var fallbacks — Vault failure propagates2627### Singleton Factories28Most clients use a module-level singleton pattern with a `get_*()` factory function. Use these factories instead of constructing clients directly:29- `get_valkey()` / `get_valkey_client()` — ValkeyClient30- `get_hybrid_embeddings_provider()` — HybridEmbeddingsProvider31- `get_sqlite_client(db_path, user_id)` — SQLiteClient (cached per user_id:path)32- `get_lattice_client()` — LatticeClient33- **Exceptions**: `LLMProvider` (instantiated per-use), `FilesManager` (requires Anthropic client), `PostgresClient` (shared pool but not singleton instances)3435### Connection Pooling36- **PostgresClient**: Class-level `ConnectionPool` dict keyed by database name. Thread-safe via `RLock`. Max 20 connections, with automatic connection recycling (1 hour lifetime) and idle timeout (5 minutes)37- **ValkeyClient**: Module-level global `ConnectionPool`, max 50 connections. Dual sync + async + binary clients38- **SQLiteClient**: No pooling (appropriate for SQLite — fresh connection per request)39- **LLMProvider**: Anthropic SDK manages its own pooling internally4041### Billing: Auto-Resolved pricing_key42The billing hooks in `generate_response()` auto-resolve `pricing_key` by matching `(model, endpoint_url)` against config caches via `billing.pricing.resolve_pricing_key()`. Callsites do NOT need to pass `pricing_key`. Unknown model+endpoint in user context raises `BillingConfigurationError` (fails closed). No user context (system/startup calls) → billing skipped.4344### Fail-Fast Infrastructure45All clients propagate infrastructure failures. Never catch and return `None`/`[]`/defaults. Specific patterns:46- `ValkeyClient.__init__()` pings on construction — fails immediately if unreachable47- `ValkeyClient._load_config()` sources URL from Vault — fails if Vault is unavailable48- `VaultClient` raises typed exceptions: `FileNotFoundError`, `PermissionError`, `RuntimeError`, `KeyError`49- `PostgresClient` lets pool creation and connection exhaustion errors propagate50- **Only exception**: `FilesManager.delete_file()` logs warnings on 404 (intentional — cleanup is best-effort)5152### RLS User Context53`PostgresClient.get_connection()` automatically sets `app.current_user_id` via SQL `SET` on checkout and `RESET` on release. This is the core mechanism for PostgreSQL Row Level Security. No manual user filtering needed in queries.5455### Deferred Imports56Clients use inline imports to break circular dependencies (e.g., `PostgresClient.__init__()` imports `vault_client.get_database_url` inline). Follow this pattern when adding new cross-references.5758### Vault Preloading59Call `vault_client.preload_secrets()` at startup to bulk-load all secrets into memory cache. This avoids individual lookups and token expiration issues during long-running service operation. Raises `RuntimeError` if any secret group (API keys, database, auth) fails to load — this is intentional fail-fast behavior.6061### LLMProvider User Context Propagation62`LLMProvider._execute_with_tools()` explicitly propagates contextvars to `ThreadPoolExecutor` worker threads. If you add new threaded execution paths, use `contextvars.copy_context()` to preserve user context for RLS enforcement.