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:
| Label | Typical provider surface |
|---|---|
| Kenop | Full orchestrated path (this contract, kenop-ai-router) |
| Claude | Anthropic |
| GPT | OpenAI |
| Gemini | |
| Grok | xAI |
| Perplexity | Perplexity (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)
kenop-ai-routeris in play end-to-end: intent / scope classification, deterministic (SQL/RPC) lane first, retrieval over knowledge packets and memory, task routing, provider selection within the Kenop policy, fallbacks, and contracted telemetry.- This is the only mode where full router logic (compute-first, use-case routing, degraded modes) is deployed.
B — Direct provider mode (execution_mode: 'direct', with explicit provider_id / model_family)
- User chose Grok, GPT, Claude, Perplexity, or Gemini (not Kenop).
- There is no point invoking the router here: the user already fixed the destination model. Running
kenop-ai-routerwould be redundant (and wrong — it would second-guess a choice the user already made). Skip router entirely for this turn. - The Kenop router does not drive task routing or SQL-first orchestration for that turn. There is no “pretend Kenop then swap model”: the selected provider’s API is invoked directly (or via a thin adapter), subject to safety and quotas.
- What still runs (Kenop data plane only) — unchanged in spirit:
- Data → knowledge (ingestion pipeline where relevant),
- Compression of knowledge / packets as defined by product (including knowledge compression stages),
- Memory and active knowledge assembly for context loading into the chosen LLM.
- So: memory + knowledge loading attach to the external model; router classification, deterministic lane, and Kenop-specific routing do not.
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
- Inside client data and Kenop mode: router + SQL/retrieval + LLM as per contract below.
- Outside client data or user explicitly wants a general model: show the model clearly; if not Kenop, use path B (memory/knowledge context only, no full router).
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)
classification_confidence >= 0.60-> accept classifier outputdeterministic_confidence >= 0.80-> return deterministic directly- otherwise continue to retrieval + LLM lane
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):
trace_id(propagate to responsetelemetry.trace_idand every span)client_idexecution_mode(kenop|direct) — user-selected; see §0direct_provider(whendirect)use_case(whenkenop/ router participated)scopemodeprovidermodellatency_ms(total; per-span durations in structured logs or trace backend)tokens_in/tokens_out(when LLM ran)successclassification_confidence(whenkenop)deterministic_confidence(whenkenop)fallback_count
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:
answer <- answerquery_type <- use_casesource <- telemetry.providerused_fallback <- mode === "degraded"readings_used <- provenance.row_count || 0