The KAI-C Orchestrator

KAI-C, the OpenNVR middleware that registers AI adapters, enforces sovereignty, and writes the audit trail every inference lands in.

On this page

KAI-C (Kavach AI Connector) is the middleware between the NVR backend and the AI adapters. The backend never calls an adapter directly. Every inference goes through KAI-C, which is what makes the audit trail complete rather than best-effort.

It lives in the open-nvr repository at kai-c/, and ships inside the opennvr-core container — there is no separate service to install. It listens on port 8100:

Frontend  →  Backend (8000)  →  KAI-C (8100)  →  AI adapter (9100)

Why a middleware at all

A thin proxy would be pointless. KAI-C earns its place by owning four things the core deliberately does not:

  • The adapter registry — on registration and every 60 seconds after, KAI-C polls each adapter’s /capabilities, caching its tasks, model fingerprint and health.
  • Sovereignty enforcement — under the default local_only policy it refuses any adapter declaring non-empty permissions.network_egress. Under federated it refuses wildcard egress. This is re-checked on every poll, so an adapter that becomes cloud-backed at runtime is de-registered rather than grandfathered in.
  • The audit log — an append-only JSONL store. Every registration, inference, refusal and fingerprint mismatch is recorded and queryable.
  • Correlation IDs — every request gets an X-Correlation-Id, minted if absent, threaded to the adapter, echoed back, and stamped on every audit event. One id joins the logs across KAI-C, the adapter and whatever consumed the result.

Drift handling

Registering an adapter is a trust decision, so KAI-C watches for that trust changing underneath you. What happens depends on what drifted:

What changed What KAI-C does
model.fingerprint Records adapter.fingerprint_mismatch, keeps serving — the operator decides.
model.version Records adapter.capability_drift, keeps serving.
permissions.* gains a permission Blocking. The adapter flips to pending and stops serving until an operator re-approves it.
permissions.network_egress violates the policy De-registers, and records inference.refused_sovereignty.
endpoints.* Audited, no action.
scheduling.* Applied silently.

A model quietly swapped behind an endpoint you already approved is the attack this exists to make visible.

The v1 API

Endpoint Purpose
POST /api/v1/adapters/register Register an adapter URL — polls /capabilities, runs the sovereignty check, stores it.
DELETE /api/v1/adapters/{name} Deregister.
GET /api/v1/adapters Adapter summaries for the UI.
GET /api/v1/ai/capabilities Aggregated capabilities across every adapter, in one call.
POST /api/v1/adapters/refresh Force a /capabilities + /health refresh.
GET /api/v1/audit Query the audit log — filter by adapter, event type, camera, or time.
POST /api/v1/infer/{adapter} Contract-compliant inference with correlation-ID threading and audit emission.

Audit events use a fixed vocabulary: adapter.registered, adapter.deregistered, adapter.fingerprint_mismatch, adapter.capability_drift, adapter.unavailable, inference.completed, inference.failed, inference.refused_sovereignty. The log is JSONL on disk at $KAI_C_AUDIT_LOG.

Running it outside the container

For development — debugging bounding-box mapping, say — kai-c/ runs standalone. It is a uv project:

cd kai-c/
uv sync
python start.py          # serves on http://localhost:8100

start_no_reload.py is the same thing without the reloader, which is what you want under load testing. See kai-c/README.md for the current behaviour and kai-c/DESIGN.md for where the architecture is going — a control plane with capability tokens, and no proxy in the inference data path.