"use client"; import { type Edge, type EdgeProps, getSmoothStepPath, Handle, MarkerType, type Node, type NodeProps, Position, } from "@xyflow/react"; import { FlowCanvas } from "./flow-canvas"; type AgentNodeData = { title: string; items: string[]; }; type HubNodeData = { title: string; badge: string; rows: string[]; }; type TargetNodeData = { accent: string; title: string; body: string; rows: { text: string; color?: string; mono?: boolean }[]; badge?: string; }; type AgentNode = Node; type HubNode = Node; type TargetNode = Node; type FlowNode = AgentNode | HubNode | TargetNode; const AGENT_W = 252; const AGENT_H = 96; const HUB_W = 306; const HUB_H = 190; const TARGET_W = 268; const TARGET_H = 148; const CENTER_X = 470; const ROW_AGENT_Y = 0; const ROW_HUB_Y = 196; const ROW_TARGET_Y = 488; const AGENT_X = CENTER_X - AGENT_W / 2; const HUB_X = CENTER_X - HUB_W / 2; const TARGET_GAP_X = 38; const TARGETS_TOTAL = TARGET_W * 2 + TARGET_GAP_X; const TARGETS_START_X = CENTER_X - TARGETS_TOTAL / 2; const CONTEXT_X = TARGETS_START_X; const WAREHOUSE_X = TARGETS_START_X + TARGET_W + TARGET_GAP_X; const EDGE_STROKE = "#94a3b8"; const CYCLE_STROKE = "#0e7490"; const EMERALD = "#059669"; const TEAL = "#0e7490"; const nodes: FlowNode[] = [ { id: "agent", type: "agent", position: { x: AGENT_X, y: ROW_AGENT_Y }, data: { title: "Your agent", items: ["Claude Code", "Cursor", "Codex"], }, draggable: false, selectable: false, }, { id: "hub", type: "hub", position: { x: HUB_X, y: ROW_HUB_Y }, data: { title: "ktx", badge: "MCP + CLI", rows: [ "Search wiki + semantic layer", "Return approved metrics", "Compile metrics → SQL", ], }, draggable: false, selectable: false, }, { id: "context", type: "target", position: { x: CONTEXT_X, y: ROW_TARGET_Y }, data: { accent: TEAL, title: "Context layer", body: "Approved definitions agents search before they answer.", rows: [ { text: "wiki/*.md", color: EMERALD, mono: true }, { text: "semantic-layer/*.yaml", color: TEAL, mono: true }, ], }, draggable: false, selectable: false, }, { id: "warehouse", type: "target", position: { x: WAREHOUSE_X, y: ROW_TARGET_Y }, data: { accent: "#334155", title: "Database", badge: "read-only", body: "Runs the compiled SQL. ktx never writes to it.", rows: [], }, draggable: false, selectable: false, }, ]; const labelBg = { labelBgPadding: [6, 3] as [number, number], labelBgBorderRadius: 4, labelStyle: { fontSize: 13, fontWeight: 600, fill: "var(--color-fd-muted-foreground)", }, labelBgStyle: { fill: "var(--color-fd-background)", stroke: "var(--color-fd-border)", strokeWidth: 1, }, }; const requestMarker = { type: MarkerType.ArrowClosed, color: EDGE_STROKE, width: 16, height: 16, }; const flowEdges: Edge[] = [ { id: "e-ask", source: "agent", sourceHandle: "ask", target: "hub", targetHandle: "ask", type: "straight", label: "ask", ...labelBg, style: { stroke: EDGE_STROKE, strokeWidth: 1.5 }, markerEnd: requestMarker, }, { id: "e-answer", source: "hub", sourceHandle: "answer", target: "agent", targetHandle: "answer", type: "straight", label: "answer", ...labelBg, style: { stroke: EDGE_STROKE, strokeWidth: 1.5 }, markerEnd: requestMarker, }, { id: "e-search", source: "hub", sourceHandle: "to-context", target: "context", targetHandle: "in", type: "smoothstep", label: "search + read", ...labelBg, style: { stroke: CYCLE_STROKE, strokeWidth: 1.5 }, markerStart: { type: MarkerType.ArrowClosed, color: CYCLE_STROKE, width: 14, height: 14 }, markerEnd: { type: MarkerType.ArrowClosed, color: CYCLE_STROKE, width: 14, height: 14 }, }, { id: "e-readonly", source: "hub", sourceHandle: "to-warehouse", target: "warehouse", targetHandle: "in", type: "smoothstep", label: "read-only", ...labelBg, style: { stroke: CYCLE_STROKE, strokeWidth: 1.5 }, markerStart: { type: MarkerType.ArrowClosed, color: CYCLE_STROKE, width: 14, height: 14 }, markerEnd: { type: MarkerType.ArrowClosed, color: CYCLE_STROKE, width: 14, height: 14 }, }, ]; function AgentNodeView({ data }: NodeProps) { return (

{data.title}

{data.items.map((item) => ( {item} ))}
); } function HubNodeView({ data }: NodeProps) { return (
k {data.title} {data.badge}
{data.rows.map((row) => (
{row}
))}
); } function TargetNodeView({ data }: NodeProps) { return (

{data.title}

{data.badge ? ( {data.badge} ) : null}
{data.rows.length > 0 ? (
{data.rows.map((row) => ( {row.text} ))}
) : null}

{data.body}

); } /* ------------------------------- Particles ------------------------------- */ const PARTICLE_SPEED_PX_PER_SEC = 150; const PARTICLE_MIN_DURATION_SEC = 5; type Leg = { sx: number; sy: number; sPos: Position; tx: number; ty: number; tPos: Position; }; const AGENT_ASK_X = AGENT_X + AGENT_W * 0.35; const AGENT_ANSWER_X = AGENT_X + AGENT_W * 0.65; const AGENT_BOTTOM_Y = ROW_AGENT_Y + AGENT_H; const HUB_ASK_X = HUB_X + HUB_W * 0.375; const HUB_ANSWER_X = HUB_X + HUB_W * 0.625; const HUB_TO_CONTEXT_X = HUB_X + HUB_W * 0.44; const HUB_TO_WAREHOUSE_X = HUB_X + HUB_W * 0.56; const HUB_BOTTOM_Y = ROW_HUB_Y + HUB_H; const CONTEXT_TOP_X = CONTEXT_X + TARGET_W / 2; const WAREHOUSE_TOP_X = WAREHOUSE_X + TARGET_W / 2; function buildCyclePath(spokeX: number, targetX: number): { d: string; length: number; } { const legs: Leg[] = [ // agent → hub (ask, down) { sx: AGENT_ASK_X, sy: AGENT_BOTTOM_Y, sPos: Position.Bottom, tx: HUB_ASK_X, ty: ROW_HUB_Y, tPos: Position.Top }, // through the hub to its spoke handle (down, drawn behind the hub) { sx: HUB_ASK_X, sy: ROW_HUB_Y, sPos: Position.Bottom, tx: spokeX, ty: HUB_BOTTOM_Y, tPos: Position.Top }, // hub → target (down) { sx: spokeX, sy: HUB_BOTTOM_Y, sPos: Position.Bottom, tx: targetX, ty: ROW_TARGET_Y, tPos: Position.Top }, // target → hub (up) { sx: targetX, sy: ROW_TARGET_Y, sPos: Position.Top, tx: spokeX, ty: HUB_BOTTOM_Y, tPos: Position.Bottom }, // through the hub to its answer handle (up, drawn behind the hub) { sx: spokeX, sy: HUB_BOTTOM_Y, sPos: Position.Top, tx: HUB_ANSWER_X, ty: ROW_HUB_Y, tPos: Position.Bottom }, // hub → agent (answer, up) { sx: HUB_ANSWER_X, sy: ROW_HUB_Y, sPos: Position.Top, tx: AGENT_ANSWER_X, ty: AGENT_BOTTOM_Y, tPos: Position.Bottom }, ]; const segments = legs.map((leg) => { const [segment] = getSmoothStepPath({ sourceX: leg.sx, sourceY: leg.sy, sourcePosition: leg.sPos, targetX: leg.tx, targetY: leg.ty, targetPosition: leg.tPos, }); return segment; }); let d = segments[0]; for (let i = 1; i < segments.length; i += 1) { d += ` ${segments[i].replace(/^M/, "L")}`; } const length = legs.reduce( (sum, leg) => sum + Math.abs(leg.tx - leg.sx) + Math.abs(leg.ty - leg.sy), 0, ); return { d, length }; } type ParticleEdgeData = { d: string; duration: number; beginOffset: number; color: string; }; type ParticleEdge = Edge; function ParticleEdgeView({ id, data }: EdgeProps) { if (!data) return null; const pathId = `runtime-particle-path-${id}`; return ( <> ); } function makeCycleEdge( id: string, source: string, spokeX: number, targetX: number, beginFraction: number, ): ParticleEdge { const { d, length } = buildCyclePath(spokeX, targetX); const duration = Math.max( PARTICLE_MIN_DURATION_SEC, length / PARTICLE_SPEED_PX_PER_SEC, ); return { id, source, target: source, type: "particle", data: { d, duration, beginOffset: duration * beginFraction, color: CYCLE_STROKE }, }; } const particleEdges: ParticleEdge[] = [ makeCycleEdge("p-context", "context", HUB_TO_CONTEXT_X, CONTEXT_TOP_X, 0), makeCycleEdge("p-warehouse", "warehouse", HUB_TO_WAREHOUSE_X, WAREHOUSE_TOP_X, 0.5), ]; const nodeTypes = { agent: AgentNodeView, hub: HubNodeView, target: TargetNodeView, }; const edgeTypes = { particle: ParticleEdgeView, }; const edges = [...flowEdges, ...particleEdges]; export function ProductRuntime() { return (

How serving works

At runtime, agents reach ktx through MCP. ktx searches the context layer, returns approved metrics, and compiles them into read-only SQL the warehouse runs.

Serving flow

From an agent request to a governed answer

The agent asks in plain language. ktx is the only thing that touches the context layer and the warehouse, and every database connection is read-only.

); }