Self-hosting
CLI can run with no CLI-hosted component in the loop. There are three levels, from least to most isolated:
| Option | What leaves your process | What leaves your network | Needs |
|---|---|---|---|
Hosted service with a CustomBackend | context, instructions, and evidence (probabilities, samples) | the same | An API key |
| Self-hosted service in your VPC | requests to your own deployment | nothing | Enterprise deployment |
Offline engine (cli_sdk.stats) | nothing | nothing | pip install cci-sdk |
Running the service inside your VPC
The self-hosted deployment runs the same components as the hosted
service — API gateway, evaluation service, calibration service, monitoring
service, and the calibration store that holds profiles, examples, audits,
and monitor state — inside your own network. It exposes the same /v1
API, so SDK code does not change; point the client at it:
export CLI_BASE_URL=https://cli.internal.example.com/v1
export CLI_API_KEY=... # issued by your deploymentfrom cli_sdk import CLIClient, VLLMBackend
backend = VLLMBackend(
model="meta-llama/Llama-3.3-70B-Instruct",
base_url="http://vllm.internal:8000", # reachable from the CLI service
engine_version="0.11.0",
quantization="fp8",
)
with CLIClient(backend=backend) as client:
print(client.list_backends())- API keys. The SDK sends a key whenever one is configured. It only
allows a missing key when
base_urlis onlocalhost,127.0.0.1,0.0.0.0or::1(for a deployment running next to your code, or a port-forward during development). Any other hostname needs a key. - Model backends. Hosted backends (
VLLMBackend,SGLangBackend, and the closed-API classes) are called by the service, so theirbase_urland credentials must be reachable from where the service runs. A self-hosted service next to self-hosted vLLM or SGLang keeps every model call inside the VPC. - Proxies and TLS. Pass your own
httpx.Clientfor corporate proxies or a private certificate authority:CLIClient(http_client=httpx.Client(verify="/etc/ssl/internal-ca.pem")). - Engine version. Guarantee cards report
stats_version. Keep the deployment and any offlinecci-sdkinstalls on the same version so audit trails can be compared if a workload moves between them.
Self-hosted deployment artifacts and installation instructions come with the Enterprise plan.
Zero-retention mode
In zero-retention mode, the calibration store keeps only calibration statistics — quantile tables, thresholds, e-process state, audit results — and no example content: no contexts, labels, or model outputs are stored after a request or an upload is processed. Zero-retention is a property of the workspace or deployment, not a request parameter, so no SDK code changes.
| Works | Unavailable |
|---|---|
evaluate with every primitive and guarantee | Anything that re-reads stored examples: recomputing a threshold after a method change, reproducing a guarantee card from the stored calibration set |
add_examples (examples are folded into statistics, then discarded) | Inspecting or exporting stored examples |
audit against fresh examples you send | Audits that need to re-score previously uploaded examples |
| Drift monitors |
Because the stored examples are gone, keep your own copy of every
calibration set you upload if you may need to recalibrate, and run audits
locally when examples cannot be sent at all (audit_coverage, or
cli calibration audit-local).
Air-gapped offline engine
The statistics engine is a pip-installable package with no network
dependency. cli_sdk.stats, cli_sdk.LocalMonitor, and the audit and
label-efficiency helpers in cli_sdk.calibration never import the HTTP
client or open a connection. They need only NumPy.
Install from a wheelhouse built on a connected machine:
# on a connected machine
pip download cci-sdk -d wheelhouse/
# inside the air-gapped network
pip install --no-index --find-links wheelhouse/ cci-sdkThen calibrate, predict, audit, and monitor with no service at all:
import json
import numpy as np
from cli_sdk import LocalMonitor
from cli_sdk.calibration import audit_coverage
from cli_sdk.stats.conformal import aps
cal_probs, cal_labels = np.load("cal_probs.npy"), np.load("cal_labels.npy")
q_hat = aps.calibrate(cal_probs, cal_labels, alpha=0.10, seed=0)
with open("support-routing-v3.json", "w") as handle: # your own versioned "profile"
json.dump({"method": "APS", "alpha": 0.10, "q_hat": q_hat, "n": len(cal_labels)}, handle)
sets = aps.predict(np.load("test_probs.npy"), q_hat)
audit = audit_coverage([y in s for y, s in zip(np.load("test_labels.npy"), sets)], target=0.90)
monitor = LocalMonitor(type="coverage", target=0.88, false_alarm_rate=0.05)The offline calibration guide walks through a full run, including Venn-Abers intervals, group-conditional thresholds, and the local drift monitor.
Offline, you own the pieces the hosted calibration store normally
enforces: minimum- checks (minimum_calibration_size), versioning of
thresholds, audits, and backend fingerprints. Calibrate on the exact
artifact you deploy — same weights, quantization, prompt template, and
decoding settings — because any change to the scoring pipeline breaks
exchangeability.