core: single-source provider metadata (#19) + resolve config once per call (#15)#33
Merged
Conversation
… call (#15) #19 — single source of truth for built-in provider metadata - registry.py now owns OPENAI_COMPAT_PROVIDERS (prefix -> URL + env vars) and NATIVE_PROVIDERS as the authoritative tables; PROVIDER_ENV_VARS is derived from them so it can never drift. - Add an import-time _assert_metadata_consistent() guard (RegistryError) that fails loudly if adapters.OPENAI_COMPAT_URLS ever drifts from the registry, converting the former silent per-call KeyError into an immediate, diagnosable startup error. (adapters/ left untouched per cluster ownership.) #15 — config resolved once / injectable, not re-read per call - call_model gains an optional config param (backward compatible default None); when omitted it falls back to a now-memoized load_config so even repeated standalone calls avoid redundant disk reads + YAML parses. - load_config memoizes by (path, mtime): self-invalidates on file change, preserves always-fresh-across-edits behavior; clear_config_cache() for tests. - council.py/cli.py/transport.py/adapters left untouched: memoization removes the per-call disk read without threading config through council. Tests: single-source/no-drift + import-time guard (url drift, missing url, url mismatch); injected-config means no load_config; standalone reads disk at most once over a 13-call burst; full Council.ask hits disk at most once per run. 79 passed (68 baseline + 11 new); ruff check + format clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Core call-path cluster (Wave 2). Two tech-debt items in the provider call path, implemented serially. No changes to council.py, cli.py, transport.py, or adapters/ (owned by parallel clusters).
closes #19
closes #15
#19 — single source of truth for provider metadata (KeyError escape)
Provider metadata was split: env-var names in
registry.PROVIDER_ENV_VARS, URLs inadapters.openai_compat.OPENAI_COMPAT_URLS. The two could silently drift; a half-declared OpenAI-compatible provider surfaced as a runtimeKeyErrorinadapters._openai_compat_adapterthat escaped theProviderErrorhandler into the broadexcept Exception.registry.pyis now the single source of truth:OPENAI_COMPAT_PROVIDERS(prefix -> URL + env vars, via a frozenOpenAICompatProvider) andNATIVE_PROVIDERSare the authoritative tables.PROVIDER_ENV_VARSis derived from them, so it can never desync.RegistryError+_assert_metadata_consistent()runs at import time and fails loudly ifadapters.OPENAI_COMPAT_URLSever drifts from the registry (extra/missing prefix, URL mismatch, or a URL with no env var). The former silent per-callKeyErrorbecomes an immediate, diagnosable startup error.adapters/was intentionally left untouched (parallel cluster owns it); the import-time guard is the in-bounds mechanism that makes drift impossible to ship.#15 — call_model re-read config from disk on every call (caching blocker)
call_modelcalledload_config()per invocation (~13 disk reads + YAML parses in a 4x3 debate + synthesis).call_modelgains an optionalconfigparam (defaultNone, fully backward compatible). When injected it's used directly; when omitted it falls back to a now-memoizedload_config.load_configmemoizes by(path, mtime): self-invalidates when the file changes (preserves always-fresh-across-edits behavior), serves repeated same-file calls from memory.clear_config_cache()added for tests/long-lived processes.council.py, so council.py/cli.py/transport.py/adapters stayed untouched. The publiccall_modelsignature is unchanged for existing callers (new param is keyword-only, optional).Tests (79 passed; 68 baseline + 11 new)
PROVIDER_ENV_VARSderived from the single source; every compat provider has env vars; registry/adapter URL tables in sync; import-time guard detects url-drift, missing-url, and url-mismatch.load_configis never called; standalone calls read disk at most once over a 13-call burst; cache invalidates on mtime change; a fullCouncil.askrun hits disk at most once.ruff check .andruff format --check .clean.🤖 Generated with Claude Code