Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion DOCUMENTATION_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ context diagram, and this index linking everything together. The Product Design
the canonical authority spec on top of those.

- **Repo:** `/Users/ernestprovo/dev/conclave/`
- **Version:** 0.1.0 (package) · **License:** MIT
- **Version:** 0.3.0 · **License:** MIT
- **Last updated:** 2026-06-08

---
Expand Down Expand Up @@ -60,6 +60,9 @@ Package root: `src/conclave/` (installed as the `conclave` package; console scri
| Adapter tests | [`tests/test_adapters.py`](tests/test_adapters.py) | Per-adapter `build_request` + `parse_response` for openai-compat/anthropic/gemini: system-hoist, max_tokens, role mapping, usage parsing, empty/malformed/error-status raises. |
| Provider highway tests | [`tests/test_providers.py`](tests/test_providers.py) | `resolve_adapter` (built-in prefixes, per-provider URLs, custom endpoints, unknown-prefix raise), end-to-end `call_model`, and `redact()` (bearer/`sk-`/env-var-value/`x-api-key` scrubbing; pre-redacted provider errors). |
| Registry/config tests | [`tests/test_registry_config.py`](tests/test_registry_config.py) | Name resolution, key-presence logic, config merge. |
| CLI tests | [`tests/test_cli.py`](tests/test_cli.py) | Typer `CliRunner`: exit-code contract (0 success / 1 zero-usable-answers / 2 usage error), `--json` payload + exit code, human renderers per mode, `providers` table never prints secrets, aclose lifecycle. |
| Transport tests | [`tests/test_transport.py`](tests/test_transport.py) | `post_json` via httpx `MockTransport`: success/error-status/non-JSON fallback, timeout & connect/HTTP errors → `TransportError` (key never leaks), client reuse/pooling, aclose idempotency. |
| Logging tests | [`tests/test_logging.py`](tests/test_logging.py) | `CONCLAVE_LOG_LEVEL` resolution (default `WARNING`, case-insensitive, unknown → `WARNING`), factory contract, one-shot configuration. |
| Fixtures | [`tests/conftest.py`](tests/conftest.py) | Shared fixtures; mocks the httpx transport so the suite needs no network and no API keys. |

Run: `pytest` (config in `pyproject.toml`, `asyncio_mode = "auto"`).
Expand All @@ -70,6 +73,9 @@ Run: `pytest` (config in `pyproject.toml`, `asyncio_mode = "auto"`).
|------|------|---------|
| Packaging | [`pyproject.toml`](pyproject.toml) | hatchling build, deps (httpx, pydantic, rich, typer, pyyaml — no LLM SDK), dev extras, console script, pytest config. License: MIT. |
| License | [`LICENSE`](LICENSE) | MIT License. Copyright (c) 2026 Ernest Provo. Matches the `pyproject.toml` license field. |
| Security policy | [`SECURITY.md`](SECURITY.md) | BYO-keys vulnerability reporting policy: how to report, scope, and the key-handling guarantees consumers can rely on. |
| Contributing guide | [`CONTRIBUTING.md`](CONTRIBUTING.md) | Dev setup, the BYO-keys contract for contributors, and the PR checklist (tests, ruff lint/format, coverage). |
| Code of conduct | [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) | Contributor Covenant v2.1. |

---

Expand All @@ -83,6 +89,7 @@ Run: `pytest` (config in `pyproject.toml`, `asyncio_mode = "auto"`).

| Date | Change |
|------|--------|
| 2026-06-08 | v0.3.0 version bump; CI foundation (Actions matrix, ruff, coverage floor, gitleaks, branch protection); redact() custom-endpoint key-leak fix (#14); status_error consolidation + conditional temperature (#16/#22); provider-metadata single-source + import-time drift guard + config memoization (#19/#15); CLI exit-code contract + httpx client lifecycle (#17/#20); transport/cli/logging test backfill (#18); public release + community files. |
| 2026-06-08 | PDD §11 repositioned vs. new direct peers (`llm-council-core`, `the-llm-council`); §12 Q1/Q3/Q4/Q5 resolved. Index Tests table updated for the PR #2 split (`test_adapters.py`, `test_providers.py`). |
| 2026-06-07 | v0.3 provider-highway refactor (LiteLLM removed → owned httpx transport + adapter registry); 3-core docs + PDD authored. |

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "conclave"
version = "0.1.0"
version = "0.3.0"
description = "Bring-your-own-keys multi-model council CLI + library. Fan a prompt to N foundation models concurrently and merge their answers."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion src/conclave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
)
from .transport import aclose

__version__ = "0.1.0"
__version__ = "0.3.0"

__all__ = [
"Council",
Expand Down
4 changes: 2 additions & 2 deletions src/conclave/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ class ConclaveConfig(BaseModel):
endpoints: dict[str, CustomEndpoint] = Field(default_factory=dict)

def resolve_model_id(self, name: str) -> str:
"""Map a friendly name to a LiteLLM model id.
"""Map a friendly name to a provider-prefixed model id.

If ``name`` is unknown it is passed through verbatim, so a user can name
a council member by a raw LiteLLM id (e.g. ``"openai/gpt-4o"``).
a council member by a raw provider-prefixed id (e.g. ``"openai/gpt-4o"``).
"""
return self.models.get(name, name)

Expand Down
4 changes: 2 additions & 2 deletions src/conclave/council.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""The Council: concurrent multi-model fan-out plus synthesis.

``Council`` is the primary importable entry point. It resolves friendly names to
LiteLLM model ids, skips any member whose API key is absent, fans the prompt out
provider-prefixed model ids, skips any member whose API key is absent, fans the prompt out
concurrently, collects partial results even when some members fail, and (in
synthesize mode) asks a synthesizer model to merge the answers into one.

Expand Down Expand Up @@ -42,7 +42,7 @@ class Council:
"""A council of foundation models with an optional synthesizer.

Args:
models: Friendly names (or raw LiteLLM ids) of council members.
models: Friendly names (or raw provider-prefixed model ids) of council members.
synthesizer: Friendly name of the synthesizer model. If ``None``, the
config default is used.
config: Pre-loaded config; if ``None``, loaded from disk + defaults.
Expand Down
6 changes: 3 additions & 3 deletions src/conclave/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ModelAnswer(BaseModel):

Attributes:
name: Friendly council member name (e.g. ``"grok"``).
model_id: Resolved LiteLLM model id (e.g. ``"xai/grok-4.3"``).
model_id: Resolved provider-prefixed model id (e.g. ``"xai/grok-4.3"``).
answer: The raw text answer, or ``None`` if the call failed.
latency_s: Wall-clock seconds for the call.
usage: Token usage if reported by the provider.
Expand Down Expand Up @@ -73,7 +73,7 @@ class AdversarialResult(BaseModel):
judge could not run.
verdict_error: Error message if the judge step failed, else ``None``.
judge: Friendly name of the judge (synthesizer) model.
judge_model_id: Resolved LiteLLM id of the judge.
judge_model_id: Resolved provider-prefixed id of the judge.
"""

proposer: str
Expand Down Expand Up @@ -101,7 +101,7 @@ class CouncilResult(BaseModel):
``debate`` this mirrors the final round so existing consumers that
read ``answers``/``synthesis`` keep working unchanged.
synthesizer: Friendly name of the synthesizer model, if synthesis ran.
synthesizer_model_id: Resolved LiteLLM id of the synthesizer.
synthesizer_model_id: Resolved provider-prefixed id of the synthesizer.
synthesis: The merged consolidated answer, or ``None`` if not produced.
For ``debate`` this holds the final synthesized answer; for
``adversarial`` it mirrors the judge's verdict.
Expand Down
10 changes: 5 additions & 5 deletions src/conclave/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This module owns, in ONE place, everything that defines a built-in provider:

* its LiteLLM provider prefix,
* its provider prefix,
* the env var(s) that satisfy its key,
* (for OpenAI-compatible providers) its full ``/chat/completions`` URL,
* the friendly-name -> default model id mapping.
Expand Down Expand Up @@ -65,7 +65,7 @@ class OpenAICompatProvider:
env_vars: tuple[str, ...]


# Friendly name -> default LiteLLM model id. Overridable via ~/.conclave/config.yml.
# Friendly name -> default provider-prefixed model id. Overridable via ~/.conclave/config.yml.
DEFAULT_MODELS: dict[str, str] = {
"grok": "xai/grok-4.3",
"gemini": "gemini/gemini-2.5-pro",
Expand Down Expand Up @@ -100,7 +100,7 @@ class OpenAICompatProvider:
"anthropic": ("ANTHROPIC_API_KEY",),
}

# Derived: LiteLLM provider prefix -> the env var(s) that satisfy it. Built from
# Derived: provider prefix -> the env var(s) that satisfy it. Built from
# the single-source tables above so it can never drift from them. The first
# present var in the list is the active key. Order matters for fallbacks.
PROVIDER_ENV_VARS: dict[str, list[str]] = {
Expand Down Expand Up @@ -165,14 +165,14 @@ def _assert_metadata_consistent() -> None:


def provider_prefix(model_id: str) -> str:
"""Extract the LiteLLM provider prefix from a model id.
"""Extract the provider prefix from a model id.

Args:
model_id: e.g. ``"xai/grok-4.3"``.

Returns:
The provider prefix (``"xai"``). If the id has no ``/`` we treat the
whole string as the prefix, which mirrors LiteLLM's bare-name handling.
whole string as the prefix (the bare-name convention for unprefixed ids).
"""
return model_id.split("/", 1)[0] if "/" in model_id else model_id

Expand Down
Loading