ADR-0021: Domain-Specific Models Architecture

Date: 2026-01-23 Status: Proposed Author: Rovo (Architect Agent) Reviewers: Codex (Gatekeeper), George (User) Context: Phase T - Multi-Model Strategy for Data Source Isolation

1. Context

Business Need

Users need specialized AI assistants for different data sources:

  • Jorvis-Meteo — Weather data only (Open-Meteo API)
  • Jorvis-Wikipedia — Knowledge queries only (WikiData)
  • Jorvis-Sports — Sports data only (TheSportsDB)
  • Jorvis-Movies — Movie data only (TMDb)
  • Jorvis-Viorica — Google Sheets only (specific spreadsheet)
  • Jorvis-Wizly — SQL analytics (Wizly database)
  • Jorvis-AdventureWorks — SQL analytics (AdventureWorks demo DB)
  • Jorvis-Chinook — SQL analytics (Chinook music DB)
  • Jorvis-Help — Documentation assistant (no data access)

Current Architecture

Open WebUI → model="jorvis-cloud" → Jorvis API → Graph Orchestrator
                                    All tools available
                                    All DBs accessible

Problem

  1. No model isolation — user can accidentally query wrong data source
  2. Tool overload — LLM sees all tools, increases hallucination risk
  3. No specialized system prompts — generic responses
  4. Single model in UI — confusing for users with multiple use cases

Existing Assets (Ready to Use)

ComponentLocationStatus
MCP Weather Toolsrc/modules/mcp/tools/weather.tool.ts✅ Exists
MCP WikiData Toolsrc/modules/mcp/tools/wikidata.tool.ts✅ Exists
MCP SportsDB Toolsrc/modules/mcp/tools/sportsdb.tool.ts✅ Exists
MCP TMDb Toolsrc/modules/mcp/tools/tmdb.tool.ts✅ Exists
MCP Google Sheets Toolsrc/modules/mcp/tools/google-sheets.tool.ts✅ Exists
MCP Excel Toolsrc/modules/mcp/tools/excel.tool.ts✅ Exists
Graph ToolRoutersrc/ai/graph/nodes/tool-router.node.ts✅ Exists
Graph ToolExecutionsrc/ai/graph/nodes/tool-execution.node.ts✅ Exists
DefaultToolRegistrysrc/ai/graph/tools/default-tool-registry.ts✅ Exists
OpenAI-Compat Servicesrc/internal/openai-compat.service.ts✅ Exists

Clarification: MCP tools are part of the MCP server stack and are not automatically wired into the Jorvis API tool execution path. Domain-specific models require explicit bridging (see Task‑T‑3) or direct registration in the runtime tool registry used by the API.

Gap Analysis

ComponentCurrentRequired
Model Registry❌ NoneJSON config with model definitions
listModels()Returns hardcoded 2 modelsReturns registry models
Tool Filtering❌ NoneallowedTools whitelist per model
Forced Tool❌ NoneforcedTool for single-purpose models
SQL Gate❌ NoneBlock SQL for tool-only models
GraphTools Bridge❌ NoneMCP tools → Graph tools adapter

2. Decision

Architecture: Model-as-DataSource

Each Open WebUI model maps to a specific data source with enforced isolation:

┌─────────────────────────────────────────────────────────────────────┐
│                         Open WebUI                                  │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐  │
│  │ Jorvis-  │ │ Jorvis-  │ │ Jorvis-  │ │ Jorvis-  │ │ Jorvis-  │  │
│  │ Meteo    │ │ Wikipedia│ │ Wizly    │ │ Viorica  │ │ Help     │  │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘  │
│       │            │            │            │            │         │
└───────┼────────────┼────────────┼────────────┼────────────┼─────────┘
        │            │            │            │            │
        ▼            ▼            ▼            ▼            ▼
┌─────────────────────────────────────────────────────────────────────┐
│                    Jorvis API (OpenAI-Compat)                       │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                    Model Registry                            │   │
│  │  ┌─────────────────────────────────────────────────────────┐│   │
│  │  │ jorvis-meteo:     routing=tool-only, forcedTool=weather ││   │
│  │  │ jorvis-wikipedia: routing=tool-only, forcedTool=wikidata││   │
│  │  │ jorvis-wizly:     routing=sql-only,  brand=wizly        ││   │
│  │  │ jorvis-viorica:   routing=tool-only, forcedTool=sheets  ││   │
│  │  │ jorvis-help:      routing=doc-only                      ││   │
│  │  └─────────────────────────────────────────────────────────┘│   │
│  └─────────────────────────────────────────────────────────────┘   │
│                              │                                      │
│                              ▼                                      │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │              Graph Orchestrator (with Policy)                │   │
│  │                                                              │   │
│  │  ToolRouterNode ──► if forcedTool → skip routing            │   │
│  │  DataExecutionNode ──► if tool-only → skip SQL              │   │
│  │  ToolExecutionNode ──► check allowedTools whitelist         │   │
│  │  DocumentContextNode ──► for doc-only models                │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

3. Model Registry Schema

File: config/models/jorvis-models.yaml

version: "1.0"
models:
  # === TOOL-ONLY MODELS (API Data Sources) ===
  
  jorvis-meteo:
    displayName: "Jorvis-Meteo"
    description: "Weather forecasts and historical data from Open-Meteo"
    icon: "🌤️"
    routing: tool-only
    forcedTool: weather
    allowedTools: [weather]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis-Meteo, a weather assistant.
      You ONLY answer questions about weather using the Open-Meteo API.
      For non-weather questions, politely redirect users to other Jorvis models.
    
  jorvis-wikipedia:
    displayName: "Jorvis-Wikipedia"
    description: "Knowledge queries from WikiData"
    icon: "📚"
    routing: tool-only
    forcedTool: wikidata
    allowedTools: [wikidata]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis-Wikipedia, a knowledge assistant.
      You answer factual questions using WikiData.
      Cite sources when possible.
    
  jorvis-sports:
    displayName: "Jorvis-Sports"
    description: "Sports data, scores, and statistics"
    icon: "⚽"
    routing: tool-only
    forcedTool: sportsdb
    allowedTools: [sportsdb]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis-Sports, a sports data assistant.
      You provide information about sports events, teams, players, and scores.
    
  jorvis-movies:
    displayName: "Jorvis-Movies"
    description: "Movie and TV show information from TMDb"
    icon: "🎬"
    routing: tool-only
    forcedTool: tmdb
    allowedTools: [tmdb]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis-Movies, a movie and TV assistant.
      You provide information about films, actors, and TV shows using TMDb.
    
  jorvis-viorica:
    displayName: "Jorvis-Viorica"
    description: "Analytics for Viorica Google Spreadsheet"
    icon: "📊"
    routing: tool-only
    forcedTool: google-sheets
    allowedTools: [google-sheets]
    provider: gemini
    providerModel: gemini-2.5-flash
    config:
      sheetId: "1l5qrcke9u6h5EkeltP4gTIApO4uK5kl864gCWU09CEo"
      defaultGid: "2137851665"
    systemPrompt: |
      You are Jorvis-Viorica, an assistant for the Viorica spreadsheet.
      You can read and analyze data from this specific Google Sheet.
      Always confirm which sheet/tab the user wants to query.

  # === NOTE: EXCEL SUPPORT ===
  # "Jorvis-Auto" is NOT a separate model. Excel processing is provided via
  # the 'excel' tool available to 'jorvis-cloud' (routing: auto).

  # === SQL-ONLY MODELS (Database Sources) ===
  
  jorvis-wizly:
    displayName: "Jorvis-Wizly"
    description: "Business intelligence for Wizly database"
    icon: "📈"
    routing: sql-only
    brand: wizly
    allowedTools: [schema_explorer]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis-Wizly, a BI assistant for the Wizly database.
      You generate SQL queries to answer business questions.
      Always explain your SQL logic and validate results.
    
  jorvis-adventureworks:
    displayName: "Jorvis-AdventureWorks"
    description: "Demo analytics for AdventureWorks sample DB"
    icon: "�icing"
    routing: sql-only
    brand: adventureworks
    allowedTools: [schema_explorer]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis-AdventureWorks, an analytics assistant.
      You work with the AdventureWorks sample database (sales, products, customers).
    
  jorvis-chinook:
    displayName: "Jorvis-Chinook"
    description: "Demo analytics for Chinook music DB"
    icon: "🎵"
    routing: sql-only
    brand: chinook
    allowedTools: [schema_explorer]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis-Chinook, a music database assistant.
      You work with the Chinook database (artists, albums, tracks, invoices).

  # === DOC-ONLY MODELS (No Data Access) ===
  
  jorvis-help:
    displayName: "Jorvis-Help"
    description: "Documentation and help for Jorvis platform"
    icon: "❓"
    routing: doc-only
    allowedTools: []
    provider: gemini
    providerModel: gemini-2.5-flash
    knowledgeBase: jorvis-docs
    systemPrompt: |
      You are Jorvis-Help, the documentation assistant for the Jorvis platform.
      You know about:
      - Jorvis architecture and capabilities
      - Available models and their purposes
      - Configuration and deployment
      - Development workflows and ADRs
      
      You do NOT execute SQL or call external APIs.
      If users need data, direct them to the appropriate Jorvis model.

  # === GENERAL MODEL (Backward Compatibility) ===
  
  jorvis-cloud:
    displayName: "Jorvis Cloud"
    description: "General-purpose assistant with all capabilities"
    icon: "🧠"
    routing: auto
    allowedTools: [weather, wikidata, sportsdb, tmdb, google-sheets, schema_explorer, web_search, calculator]
    provider: gemini
    providerModel: gemini-2.5-flash
    systemPrompt: |
      You are Jorvis, a versatile AI assistant.
      You can query databases, call APIs, and answer general questions.

4. Implementation Plan

Phase T-1: Model Registry & listModels (2 days)

Files to create:

  • config/models/jorvis-models.yaml — Model definitions

Files to modify:

  • src/internal/openai-compat.service.ts:
    • Add ModelRegistryService injection
    • Update listModels() to return registry models
    • Update resolveProvider() to read from registry

New service:

// src/config/model-registry.service.ts
@Injectable()
export class ModelRegistryService {
  private models: Map<string, ModelConfig>;
  
  getModel(modelId: string): ModelConfig | undefined;
  listModels(): ModelConfig[];
  getRoutingPolicy(modelId: string): RoutingPolicy;
}

Acceptance Criteria:

  • Open WebUI shows all 10 models in dropdown
  • Each model has correct icon and description
  • GET /v1/models returns full list

Phase T-2: GraphTools Bridge (2 days)

Problem: MCP tools exist but aren't registered in Graph's DefaultToolRegistry.

Files to create:

  • src/ai/graph/tools/mcp-tool-adapter.ts — Adapter to wrap MCP tools as GraphTools

Files to modify:

  • src/ai/graph/tools/default-tool-registry.ts:
    • Register MCP tools via adapter
    • Add weather, wikidata, sportsdb, tmdb, google-sheets

Code pattern:

// MCP to Graph adapter
export class McpToolAdapter implements GraphTool {
  constructor(private mcpTool: McpTool) {}
  
  get name() { return this.mcpTool.name; }
  get description() { return this.mcpTool.description; }
  
  async execute(input: unknown) {
    return this.mcpTool.execute(input);
  }
}

Acceptance Criteria:

  • All MCP tools accessible via Graph ToolRouter
  • Tools return correct results
  • Unit tests pass

Phase T-3: Routing Policy Enforcement (3 days)

Files to modify:

  1. src/ai/graph/graph.types.ts:
export interface GraphState {
  // ... existing fields
  routingPolicy?: RoutingPolicy;
}

export interface RoutingPolicy {
  type: 'auto' | 'sql-only' | 'tool-only' | 'doc-only';
  forcedTool?: string;
  allowedTools?: string[];
  brand?: string;
}
  1. src/ai/graph/nodes/tool-router.node.ts:
async execute(state: GraphState) {
  // NEW: Check for forced tool
  if (state.routingPolicy?.forcedTool) {
    return {
      toolCall: {
        toolName: state.routingPolicy.forcedTool,
        input: { query: state.question },
      },
      nextNode: 'ToolExecutionNode',
    };
  }
  
  // Existing routing logic...
}
  1. src/ai/graph/nodes/data-execution.node.ts:
async execute(state: GraphState) {
  // NEW: Block SQL for tool-only/doc-only
  if (state.routingPolicy?.type === 'tool-only' || 
      state.routingPolicy?.type === 'doc-only') {
    this.logger.warn('SQL blocked by routing policy');
    return { nextNode: 'SynthesisNode' };
  }
  
  // Existing SQL execution...
}
  1. src/ai/graph/nodes/tool-execution.node.ts:
async execute(state: GraphState) {
  // NEW: Check tool whitelist
  const allowedTools = state.routingPolicy?.allowedTools;
  if (allowedTools && !allowedTools.includes(state.toolCall.toolName)) {
    this.logger.warn(`Tool ${state.toolCall.toolName} not in whitelist`);
    return { 
      error: `Tool not available for this model`,
      nextNode: 'SynthesisNode' 
    };
  }
  
  // Existing execution...
}
  1. src/question/conversation-question.service.ts:
// Pass routing policy to Graph
const state: GraphState = {
  question,
  routingPolicy: modelConfig.getRoutingPolicy(),
  // ...
};

Acceptance Criteria:

  • jorvis-meteo ONLY calls weather tool
  • jorvis-wizly ONLY executes SQL
  • jorvis-help returns doc-only responses
  • Cross-model tool calls blocked

Phase T-4: System Prompts & Knowledge Base (2 days)

Files to modify:

  • src/internal/openai-compat.service.ts:

    • Inject system prompt from model config
    • Pass to ConversationQuestionService
  • src/ai/graph/nodes/document-context.node.ts:

    • Filter by knowledgeBase field for doc-only models

For jorvis-help:

  • Index project documentation into vector store
  • Configure DocumentContextNode to use jorvis-docs collection

Acceptance Criteria:

  • Each model has correct system prompt
  • jorvis-help answers questions about Jorvis
  • System prompts visible in transparency trace

Phase T-5: Open WebUI Registration (1 day)

Files to modify:

  • scripts/register-openwebui-model.sh:
    • Read from config/models/jorvis-models.yaml
    • Register all models with correct metadata

Script enhancement:

#!/bin/bash
# Parse YAML and register each model
for model in $(yq '.models | keys | .[]' config/models/jorvis-models.yaml); do
  displayName=$(yq ".models.$model.displayName" config/models/jorvis-models.yaml)
  description=$(yq ".models.$model.description" config/models/jorvis-models.yaml)
  
  curl -X POST "$WEBUI_URL/api/models" \
    -H "Authorization: Bearer $WEBUI_TOKEN" \
    -d "{\"id\": \"$model\", \"name\": \"$displayName\", \"description\": \"$description\"}"
done

Acceptance Criteria:

  • All 10 models visible in Open WebUI
  • Icons and descriptions correct
  • Models selectable and functional

Phase T-6: Testing & Documentation (2 days)

Test scenarios:

ModelTest QueryExpected Behavior
jorvis-meteo"What's weather in Kyiv?"Calls weather tool ONLY
jorvis-meteo"SELECT * FROM users"Politely refuses, no SQL
jorvis-wikipedia"Who is Taras Shevchenko?"Calls wikidata tool
jorvis-wizly"Show top 10 customers"Executes SQL ONLY
jorvis-wizly"What's weather?"Politely refuses
jorvis-help"How to deploy Jorvis?"Returns docs, no API calls
jorvis-viorica"Show sales by month"Calls sheets with correct ID

Documentation:

  • Update docs/operations/LOCAL_RUNTIME.md
  • Create docs/guides/domain-specific-models.md
  • Update docs/architecture/CAPABILITIES_OVERVIEW.md

Acceptance Criteria:

  • All test scenarios pass
  • Documentation complete
  • E2E tests added

5. Task Breakdown for Executor

Task-T-1: Model Registry Service

Priority: P0 Estimate: 1 day Dependencies: None

Deliverables:

  1. Create config/models/jorvis-models.yaml
  2. Create src/config/model-registry.service.ts
  3. Create src/config/model-registry.module.ts
  4. Unit tests for registry service

Task-T-2: OpenAI-Compat Integration

Priority: P0 Estimate: 1 day Dependencies: Task-T-1

Deliverables:

  1. Update listModels() in openai-compat.service.ts
  2. Update resolveProvider() to use registry
  3. Pass routingPolicy to ConversationQuestionService
  4. Integration tests

Task-T-3: GraphTools Bridge (MCP Adapter)

Priority: P0 Estimate: 2 days Dependencies: None (parallel with T-1, T-2)

Deliverables:

  1. Create src/ai/graph/tools/mcp-tool-adapter.ts
  2. Update default-tool-registry.ts
  3. Register: weather, wikidata, sportsdb, tmdb, google-sheets
  4. Unit tests for each adapter

Task-T-4: Routing Policy Enforcement

Priority: P0 Estimate: 2 days Dependencies: Task-T-1, Task-T-2

Deliverables:

  1. Add routingPolicy to GraphState
  2. Update ToolRouterNode (forcedTool)
  3. Update DataExecutionNode (sql-only gate)
  4. Update ToolExecutionNode (allowedTools)
  5. Integration tests

Task-T-5: System Prompts Integration

Priority: P1 Estimate: 1 day Dependencies: Task-T-2

Deliverables:

  1. Inject system prompts from model config
  2. Pass to LLM adapter
  3. Verify in transparency trace

Task-T-6: Jorvis-Help Knowledge Base

Priority: P1 Estimate: 1 day Dependencies: Task-T-4

Deliverables:

  1. Index Jorvis docs into vector store
  2. Configure DocumentContextNode for doc-only
  3. Test jorvis-help responses

Task-T-7: Open WebUI Registration Script

Priority: P1 Estimate: 0.5 day Dependencies: Task-T-1

Deliverables:

  1. Update register-openwebui-model.sh
  2. Auto-register all models from YAML
  3. Manual verification

Task-T-8: E2E Testing & Documentation

Priority: P1 Estimate: 1.5 days Dependencies: All above

Deliverables:

  1. E2E test suite for all models
  2. User documentation
  3. Architecture docs update

6. Risks & Mitigations

RiskImpactMitigation
MCP tools not compatible with GraphHighCreate adapter pattern (Task-T-3)
System prompt injectionMediumSanitize in openai-compat service
Performance with many modelsLowLazy load model configs
Open WebUI cachingMediumAdd cache invalidation endpoint

7. Consequences

Positive

  • Clear separation of concerns per data source
  • Reduced hallucinations (tool filtering)
  • Better UX (users pick right model)
  • Easier debugging (model → single data source)

Negative

  • More models to maintain
  • User must know which model to pick
  • Increased config complexity

Mitigations

  • Clear naming convention (Jorvis-{DataSource})
  • Helpful descriptions in Open WebUI
  • jorvis-cloud as fallback for general queries

8. References

  • ADR-0018: Voice Pipeline Local Architecture
  • ADR-0019: Local LLM Strategy
  • ADR-0020: GraphRAG Implementation Strategy
  • analytics-platform/src/modules/mcp/tools/ — Existing MCP tools
  • analytics-platform/src/ai/graph/tools/ — Graph tool system

9. Approval

Status: Proposed — Awaiting Review

ReviewerStatusDate
Codex (Gatekeeper)⏳ Pending
George (User)⏳ Pending

This ADR was created by Rovo (Architect) for Phase T (Domain-Specific Models).