---
phase: 28-ollama-integration
plan: "01"
subsystem: server
tags: [ollama, model-catalog, service, routes, unit-tests]
dependency_graph:
  requires: []
  provides: [ollamaService, ollamaRoutes, ollama-model-catalog]
  affects: [server/src/app.ts, server/src/routes/index.ts]
tech_stack:
  added: []
  patterns: [AbortController-timeout, catalog-based-recommendation, assertCompanyAccess-authz]
key_files:
  created:
    - server/src/services/ollama.ts
    - server/src/data/ollama-model-catalog.json
    - server/src/__tests__/ollama-service.test.ts
    - server/src/routes/ollama.ts
  modified:
    - server/src/routes/index.ts
    - server/src/app.ts
decisions:
  - "Force-added server/src/data/ with git add -f because root .gitignore has data/ pattern — source catalog is not generated data"
  - "Used loadCatalog() with fs.readFileSync + fileURLToPath for reliable ESM-compatible JSON loading"
  - "getRecommendedModel picks highest quality-ranked variant within 75% RAM budget using QUALITY_RANK map"
  - "listOllamaModels includes its own AbortController timeout — guards against Ollama going down mid-request"
metrics:
  duration: "3 minutes"
  completed_date: "2026-04-02"
  tasks_completed: 2
  files_modified: 6
requirements_satisfied: [OLLA-01, OLLA-02, OLLA-04, OLLA-05]
---

# Phase 28 Plan 01: Ollama Service, Routes, and Model Catalog Summary

**One-liner:** Ollama detection + model listing service with AbortController timeouts, static 5-family model catalog for RAM-based recommendations, and Express routes at `/companies/:companyId/ollama/status` and `/models`.

## Tasks Completed

| Task | Name | Commit | Files |
|------|------|--------|-------|
| TDD RED | Add failing tests for ollama service | 2169a21e | server/src/__tests__/ollama-service.test.ts |
| TDD GREEN (Task 1) | ollamaService + model catalog | 4fce48e1 | server/src/services/ollama.ts, server/src/data/ollama-model-catalog.json |
| Task 2 | Ollama HTTP routes + app mount | e45a2578 | server/src/routes/ollama.ts, routes/index.ts, app.ts |

## What Was Built

### ollamaService (`server/src/services/ollama.ts`)

- `detectOllama()`: Probes `OLLAMA_BASE_URL/api/version` with a 3s AbortController timeout. Returns `{ installed: true, version }` on success, `{ installed: false, installUrl }` on any error or timeout.
- `listOllamaModels()`: Fetches `OLLAMA_BASE_URL/api/tags`, maps Ollama's native response (with `details.parameter_size`, `details.quantization_level`, `details.family`) to `OllamaModel[]`. Returns `[]` on any error.
- `getRecommendedModel(models, systemRamBytes)`: Reads the static catalog, computes usable RAM as 75% of total, ranks catalog variants by quality tier (best > reasoning > balanced > fast), and marks the single best-fitting model as `recommended: true` with a human-readable `recommendationReason`.
- Respects `process.env.OLLAMA_BASE_URL` override — never hard-codes `localhost:11434`.

### Model Catalog (`server/src/data/ollama-model-catalog.json`)

5 families with 11 total variants:
- **qwen2**: qwen2.5-coder 7b/14b/32b
- **llama**: llama3.2 3b, llama3.1 8b/70b
- **mistral**: mistral 7b/22b
- **phi**: phi4 14b
- **deepseek**: deepseek-r1 7b/32b

### Ollama Routes (`server/src/routes/ollama.ts`)

- `GET /companies/:companyId/ollama/status` — returns `OllamaStatus` JSON
- `GET /companies/:companyId/ollama/models` — returns `{ models: OllamaModel[], ramGb: number }`. Short-circuits to `{ models: [], ramGb: 0 }` if Ollama not installed.
- Both routes gated with `assertCompanyAccess(req, companyId)`.
- Mounted in `app.ts` as `api.use(ollamaRoutes())` after `agentRoutes`.

## Test Coverage

12 unit tests (all passing):
- `detectOllama`: success, ECONNREFUSED failure, AbortController timeout, non-ok response
- `listOllamaModels`: success with full OllamaTagsResponse shape, ECONNREFUSED, non-ok
- `getRecommendedModel`: 8GB → 7b, 32GB → 32b, unknown models → all false, empty input, RAM too low → no recommendation

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking] server/src/data/ gitignored by root .gitignore**
- **Found during:** Task 1 commit
- **Issue:** Root `.gitignore` has `data/` pattern; `server/src/data/ollama-model-catalog.json` was silently ignored
- **Fix:** Used `git add -f` to force-track the file. The catalog is source code (not generated data), so this is correct behavior.
- **Files modified:** `.gitignore` not modified — file force-added
- **Commit:** 4fce48e1

## Known Stubs

None — all functions return real data structures. Routes wire directly to service functions. No placeholder values in the response paths.

## Self-Check: PASSED

Files exist:
- server/src/services/ollama.ts: FOUND
- server/src/data/ollama-model-catalog.json: FOUND
- server/src/__tests__/ollama-service.test.ts: FOUND
- server/src/routes/ollama.ts: FOUND

Commits exist:
- 2169a21e: FOUND (test RED)
- 4fce48e1: FOUND (feat GREEN + catalog)
- e45a2578: FOUND (feat routes)
