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 viacortexbuild 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¶
- Per-agent budget — orchestrator queries
estimate_cost()before invoking and aborts/escalates if over per-task budget. - Per-task budget — sum of all invocations on a task; pauses task on hit.
- Per-project monthly budget — pauses project; notifies user.
- Cache — Redis-backed;
cache_keydeduplicates 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)¶
- Create
cortexbuild_core/providers/{name}.pyimplementingProviderAdapter. - Register in
cortexbuild_core/providers/__init__.py: REGISTERED_ADAPTERS. - Add ModelTier mapping table to docstring.
- Add capability declarations.
- Add credential schema to
cortexbuild_core/providers/credentials.py. - Add SDK/CLI to
CortexBuild-vm/installers/. - Add UI key-input form in
CortexBuild-web/components/ProviderKeyForm.tsx. - Add smoke test fixture.
- Add nightly health check.
- Document in
docs/providers/{name}.md.
What we don't promise across providers¶
- Identical tool-call schemas — adapter normalises to our
ToolDefbut 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 cost —
estimate_cost()reflects current pricing; user sees this before invocation