Skip to content

CortexBuild — Provider Adapter Spec (v0.1)

Why this matters

The single biggest external risk to CortexBuild is LLM provider API churn. The Provider Adapter is the only place that touches provider-specific APIs. Every other CortexBuild component talks to the adapter interface.

Interface

# cortexbuild_core/providers/base.py
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal
from enum import Enum
from typing import Protocol, AsyncIterator, Sequence


class ModelTier(str, Enum):
    REASONING_HEAVY = "reasoning_heavy"  # Opus, GPT-5, Sonnet 4.5
    BALANCED = "balanced"                # Sonnet, GPT-5-mini
    FAST_CHEAP = "fast_cheap"            # Haiku, GPT-5-nano


class Capability(str, Enum):
    CODE_EXECUTION = "code_execution"
    WEB_BROWSE = "web_browse"
    FILE_IO = "file_io"
    VISION = "vision"
    LONG_CONTEXT_200K = "long_context_200k"
    LONG_CONTEXT_1M = "long_context_1m"
    PROMPT_CACHING = "prompt_caching"
    TOOL_USE = "tool_use"
    STREAMING = "streaming"


@dataclass(frozen=True)
class ToolDef:
    name: str
    description: str
    input_schema: dict  # JSON Schema


@dataclass(frozen=True)
class InvocationResult:
    content: str
    tool_calls: list[dict]
    prompt_tokens: int
    completion_tokens: int
    cost_usd: Decimal
    latency_ms: int
    model_used: str
    cache_hit: bool = False


@dataclass(frozen=True)
class StreamChunk:
    delta_text: str
    delta_tool_call: dict | None
    is_final: bool


@dataclass(frozen=True)
class HealthStatus:
    healthy: bool
    error: str | None
    latency_ms: int
    checked_at_iso: str


@dataclass(frozen=True)
class RateLimitStatus:
    requests_remaining: int | None
    tokens_remaining: int | None
    reset_at_iso: str | None


class ProviderAdapter(Protocol):
    """Provider-agnostic LLM interface.

    Implementations live in `cortexbuild_core/providers/{name}.py`.
    """

    name: str

    async def invoke(
        self,
        prompt: str,
        *,
        system: str | None = None,
        tools: Sequence[ToolDef] | None = None,
        model_hint: ModelTier = ModelTier.BALANCED,
        max_tokens: int = 4096,
        timeout_seconds: int = 300,
        cache_key: str | None = None,
    ) -> InvocationResult: ...

    async def stream(
        self,
        prompt: str,
        *,
        system: str | None = None,
        tools: Sequence[ToolDef] | None = None,
        model_hint: ModelTier = ModelTier.BALANCED,
        max_tokens: int = 4096,
        timeout_seconds: int = 300,
    ) -> AsyncIterator[StreamChunk]: ...

    def estimate_cost(
        self,
        prompt: str,
        model_hint: ModelTier = ModelTier.BALANCED,
        expected_output_tokens: int = 1024,
    ) -> Decimal: ...

    def capabilities(self) -> set[Capability]: ...

    async def rate_limit_status(self) -> RateLimitStatus: ...

    async def health_check(self) -> HealthStatus: ...

Provider matrix

Provider Day-1 impl Capabilities ModelTier mapping (May 2026)
Anthropic Claude (native) ✅ v0 code-exec via tool use; long-context 200k & 1M; prompt caching; tool use; streaming reasoning_heavy=opus-4.7, balanced=sonnet-4.6, fast_cheap=haiku-4.5
Google Gemini (native) v1 tool use; long-context; streaming configured per .cortexbuild/llm-config.yaml (e.g. gemini-1.5-pro)
OpenAI-compatible endpoint v1 code-exec; tool use; streaming covers OpenAI/Codex models, gateways, and local servers (Ollama, LM Studio, vLLM) configured via base_url
AWS Bedrock / GCP Vertex / Azure OpenAI via gateway varies by underlying model reached through an OpenAI-compatible gateway only — no native adapter

Host tools, not providers. Copilot, Claude Code, Codex CLI, and Cursor are the AI coding tools CortexBuild renders its harness into — render targets / host surfaces, not LLM providers. They run on whatever model their vendor configures; the provider router above governs only the LLM providers, not these host tools.

Operating modes: rendered vs. orchestration

CortexBuild operates in two distinct modes:

Mode How it works Who controls the model
Rendered-into-host Rules and agent personas are written into Copilot, Claude Code, Codex CLI, or Cursor via cortexbuild render. The host tool executes them. The host tool's vendor. Copilot uses GitHub-provisioned models; Claude Code always uses Claude; Cursor uses its configured default. CortexBuild's provider router is not involved.
Own orchestration engine CORTEXBUILD_LLM=1 + endpoint config → CortexBuild's orchestrator calls the configured LLM provider directly, using the registry/router. CortexBuild's router — tier and capability-driven; per-agent model assignment applies.

Edge case — rendered mode: If you have GitHub Copilot logged in and use CortexBuild purely through the rendered agents (.github/copilot-instructions.md), Copilot's own models handle all completions. No BYO LLM key is required. CortexBuild's locality gate, BYOK config, and tier assignments have no effect in this mode.

Edge case — orchestration mode with local model: Set CORTEXBUILD_LLM=1, configure .cortexbuild/llm-config.yaml with base_url: http://localhost:11434 (Ollama on loopback), and the locality gate routes all completions through the loopback tier — zero egress, no consent prompt required.

Per-agent default routing

Defined in cortexbuild_core/orchestrator/agent_routing.py:

DEFAULT_AGENT_ROUTING: dict[str, ModelTier] = {
    "planner": ModelTier.REASONING_HEAVY,        # deep planning
    "critic": ModelTier.REASONING_HEAVY,         # adversarial reasoning
    "builder": ModelTier.BALANCED,               # most volume; cost matters
    "tester_validator": ModelTier.BALANCED,      # test coverage + regression analysis; matches registry.yaml
    "security_compliance": ModelTier.REASONING_HEAVY,  # security depth
    "optimizer": ModelTier.BALANCED,
    "pr_reviewer": ModelTier.REASONING_HEAVY,    # final gate
    "explore": ModelTier.FAST_CHEAP,             # read-only summarisation
    "recurrence_guard": ModelTier.BALANCED,
    "enterprise_orchestrator": ModelTier.BALANCED,
}

Users can override per project, per agent.

Cross-model verification (Critic gate)

For tasks with a risk score of medium or higher, the Critic agent MUST run on an independent model — different from the one that authored the plan. A single model reviewing its own reasoning shares its blind spots.

  • The Critic emits independent_reviewer: <model-or-agent-id> in its output for auditability.
  • This is enforced by rule-critic-gate (rendered into every host surface via cortexbuild render).
  • The orchestrator records the reviewer identity; completion is rejected if the field is absent on medium+ risk tasks.

Example: Planner runs on claude-opus-4.6 (reasoning_heavy); Critic runs on gpt-5.2 (reasoning_heavy, different vendor) — surfaces assumptions the author cannot see.

Cost controls

  1. Per-agent budget — orchestrator queries estimate_cost() before invoking and aborts/escalates if over per-task budget.
  2. Per-task budget — sum of all invocations on a task; pauses task on hit.
  3. Per-project monthly budget — pauses project; notifies user.
  4. Cache — Redis-backed; cache_key deduplicates identical prompts within 24h. Default ON for read-only agents (Tester, Optimizer in advisory mode).

Health & smoke tests

Every adapter MUST implement health_check(). CI runs daily:

# .github/workflows/provider-smoke.yml runs nightly
async def smoke_test_all():
    for adapter_cls in REGISTERED_ADAPTERS:
        a = adapter_cls(credentials=test_creds_from_secret(adapter_cls.name))
        status = await a.health_check()
        if not status.healthy:
            alert(f"Provider {a.name} unhealthy: {status.error}")

Failures page the on-call engineer.

Pin strategy

  • Each adapter pins the upstream SDK / CLI version in requirements.txt.
  • A weekly automated PR bumps versions, runs full integration suite, opens PR if green. Manual review required.
  • VM image rebuilt nightly with pinned versions of provider CLIs (gh, claude, codex).

Failure handling

Failure Adapter response Orchestrator response
Timeout Raise ProviderTimeoutError Retry once with same provider + smaller model tier; if fails, try next preferred provider
Rate limit (429) Raise ProviderRateLimitError with retry_after Queue invocation; retry after delay; route to alternate provider if user enabled
Auth failure (401) Raise ProviderAuthError Mark project paused; notify user; do not retry
Bad request (400) Raise ProviderRequestError with full diagnostic Surface to user; do not retry
Server error (5xx) Raise ProviderServerError Retry with exponential backoff (3 attempts)
Network error Raise ProviderNetworkError Retry once; check CortexBuild's own connectivity
Capability missing Raise CapabilityNotSupportedError Re-route to provider that supports it; if none, error to user

Adding a new provider (the checklist)

  1. Create cortexbuild_core/providers/{name}.py implementing ProviderAdapter.
  2. Register in cortexbuild_core/providers/__init__.py: REGISTERED_ADAPTERS.
  3. Add ModelTier mapping table to docstring.
  4. Add capability declarations.
  5. Add credential schema to cortexbuild_core/providers/credentials.py.
  6. Add SDK/CLI to CortexBuild-vm/installers/.
  7. Add UI key-input form in CortexBuild-web/components/ProviderKeyForm.tsx.
  8. Add smoke test fixture.
  9. Add nightly health check.
  10. Document in docs/providers/{name}.md.

What we don't promise across providers

  • Identical tool-call schemas — adapter normalises to our ToolDef but underlying model may interpret differently
  • Identical streaming token granularity — some send sentence-level, some word-level
  • Identical vision capabilities — some support images, some don't
  • Identical max context window — exposed via capabilities()
  • Identical costestimate_cost() reflects current pricing; user sees this before invocation