Technical reference

Kenop Router Contract v1

This contract defines the request/response shape between Next.js and kenop-ai-router.

0) Provider modes, transparency, and when the Kenop router runs

0.1 Open disclosure of models (UX contract)

For queries that are general, outside the user’s database, or not answered by deterministic compute over client data, the product openly shows which backend the user is talking to. At minimum, selectable or labeled providers include:

LabelTypical provider surface
KenopFull orchestrated path (this contract, kenop-ai-router)
ClaudeAnthropic
GPTOpenAI
GeminiGoogle
GrokxAI
PerplexityPerplexity (where used for live / cited web)

Copy and UI should never hide which model family answered. Kenop is a distinct mode: it is not “another logo on the same black box” — it is the path where our router and compute-first policy apply.

0.2 User selection is the source of truth

The mode is explicitly user-selected in the product (e.g. picker: Kenop vs Claude vs GPT vs …). The backend must respect that choice: do not override a direct LLM selection with Kenop routing, and do not run kenop-ai-router when the user has already chosen a non-Kenop model for that turn.

0.3 Two classes of execution path

A — Kenop mode (execution_mode: 'kenop' or equivalent in request metadata)

B — Direct provider mode (execution_mode: 'direct', with explicit provider_id / model_family)

Stated plainly: router deployment is tied to Kenop mode. In other modes, only the memory/knowledge loading path feeds the model; the rest is general LLM behavior on top of that context.

0.4 Scope reminder

0.5 Request hint (optional field for implementers)

export type ExecutionMode = 'kenop' | 'direct';

export type DirectProviderHint =
  | 'anthropic'
  | 'openai'
  | 'google'
  | 'grok'
  | 'perplexity';

// Optional on RouterRequest (or parallel “chat completion” API):
// execution_mode?: ExecutionMode;
// direct_provider?: DirectProviderHint; // required when execution_mode === 'direct'

When execution_mode === 'direct', kenop-ai-router route selection may be skipped; a separate entrypoint still enforces memory/knowledge assembly and logging.


1) Request Contract

TypeScript type

export type RouterAction = 'route' | 'get_routing';

export type RouterUseCase =
  | 'compute_kpi'
  | 'compute_anomaly'
  | 'lookup_knowledge'
  | 'extract_document'
  | 'market_news_fetch'
  | 'deep_research'
  | 'strategy_reasoning';

export type RouterScope = 'inside_data' | 'outside_data' | 'hybrid';

export type RouterRequest = {
  action?: RouterAction; // default: route
  client_id?: string;
  conversation_id?: string;
  query?: string;
  user_prompt?: string; // preferred prompt field
  task_hint?: RouterUseCase;
  max_tokens?: number;
  input_type?: 'text' | 'image' | 'pdf' | 'excel' | 'video';
  file_mime_type?: string;
  image_base64?: string;
  image_mime_type?: string;
  video_base64?: string;
  metadata?: Record<string, unknown>;
};

JSON Schema (request)

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "RouterRequest",
  "type": "object",
  "properties": {
    "action": { "type": "string", "enum": ["route", "get_routing"] },
    "client_id": { "type": "string" },
    "conversation_id": { "type": "string" },
    "query": { "type": "string" },
    "user_prompt": { "type": "string" },
    "task_hint": {
      "type": "string",
      "enum": [
        "compute_kpi",
        "compute_anomaly",
        "lookup_knowledge",
        "extract_document",
        "market_news_fetch",
        "deep_research",
        "strategy_reasoning"
      ]
    },
    "max_tokens": { "type": "number", "minimum": 1, "maximum": 8192 },
    "input_type": {
      "type": "string",
      "enum": ["text", "image", "pdf", "excel", "video"]
    },
    "file_mime_type": { "type": "string" },
    "image_base64": { "type": "string" },
    "image_mime_type": { "type": "string" },
    "video_base64": { "type": "string" },
    "metadata": { "type": "object", "additionalProperties": true }
  },
  "additionalProperties": true
}

2) Core Routing Decision Contract

TypeScript type

export type RoutingDecision = {
  use_case: RouterUseCase;
  scope: RouterScope;
  classification_confidence: number; // 0..1
  deterministic_confidence: number; // 0..1
  need_llm: boolean;
  mode: 'deterministic' | 'llm' | 'degraded';
};

3) Success Response Contract

TypeScript type

export type RouterProvider =
  | 'internal'
  | 'anthropic'
  | 'google'
  | 'openai'
  | 'grok'
  | 'perplexity'
  | 'fallback';

export type DeterministicProvenance = {
  source_table?: string;
  rpc_name?: string;
  time_window?: string;
  row_count?: number;
  note?: string;
};

export type RouterTelemetry = {
  trace_id: string;
  latency_ms: number;
  tried?: string[];
  provider?: RouterProvider;
  model?: string;
};

export type RouterSuccessResponse = {
  ok: true;
  answer: string;
  use_case: RouterUseCase;
  scope: RouterScope;
  mode: 'deterministic' | 'llm' | 'degraded';
  confidence: {
    classification: number;
    deterministic: number;
  };
  data?: unknown;
  provenance?: DeterministicProvenance;
  telemetry: RouterTelemetry;
};

JSON Schema (success response)

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "RouterSuccessResponse",
  "type": "object",
  "required": ["ok", "answer", "use_case", "scope", "mode", "confidence", "telemetry"],
  "properties": {
    "ok": { "const": true },
    "answer": { "type": "string" },
    "use_case": {
      "type": "string",
      "enum": [
        "compute_kpi",
        "compute_anomaly",
        "lookup_knowledge",
        "extract_document",
        "market_news_fetch",
        "deep_research",
        "strategy_reasoning"
      ]
    },
    "scope": { "type": "string", "enum": ["inside_data", "outside_data", "hybrid"] },
    "mode": { "type": "string", "enum": ["deterministic", "llm", "degraded"] },
    "confidence": {
      "type": "object",
      "required": ["classification", "deterministic"],
      "properties": {
        "classification": { "type": "number", "minimum": 0, "maximum": 1 },
        "deterministic": { "type": "number", "minimum": 0, "maximum": 1 }
      }
    },
    "data": {},
    "provenance": {
      "type": "object",
      "properties": {
        "source_table": { "type": "string" },
        "rpc_name": { "type": "string" },
        "time_window": { "type": "string" },
        "row_count": { "type": "number" },
        "note": { "type": "string" }
      },
      "additionalProperties": false
    },
    "telemetry": {
      "type": "object",
      "required": ["trace_id", "latency_ms"],
      "properties": {
        "trace_id": { "type": "string" },
        "latency_ms": { "type": "number", "minimum": 0 },
        "tried": {
          "type": "array",
          "items": { "type": "string" }
        },
        "provider": {
          "type": "string",
          "enum": ["internal", "anthropic", "google", "openai", "grok", "perplexity", "fallback"]
        },
        "model": { "type": "string" }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": true
}

4) Error Response Contract

TypeScript type

export type RouterErrorResponse = {
  ok: false;
  error: {
    code:
      | 'BAD_REQUEST'
      | 'UNAUTHORIZED'
      | 'NO_CLIENT'
      | 'ROUTER_UNAVAILABLE'
      | 'PROVIDER_FAILURE'
      | 'INTERNAL_ERROR';
    message: string;
    retryable: boolean;
  };
  telemetry?: {
    trace_id?: string;
    latency_ms?: number;
    tried?: string[];
  };
};

5) Confidence Gate Defaults (v1)

6) Telemetry Minimum (ai_call_log + tracing)

Tracing is mandatory for operating Kenop (latency breakdown, mode correctness, cost). Full span model, Kenop vs direct paths, and dashboards: docs/tracing-v1.md.

Minimum fields to log each request (one row per trace_id at minimum):

Per-span breakdown (router classify, SQL, retrieval, memory assemble, LLM) is specified in docs/tracing-v1.md.

7) Backward Compatibility Adapter (optional)

For old UI payloads, map: