From 9e7c2890d5a0f818fc7aadf1e207019751bcd55c Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Thu, 2 Apr 2026 16:50:58 +0000 Subject: [PATCH] docs(28): create phase plan --- .planning/ROADMAP.md | 8 +- .../28-ollama-integration/28-01-PLAN.md | 194 +++++++++++++ .../28-ollama-integration/28-02-PLAN.md | 198 ++++++++++++++ .../28-ollama-integration/28-03-PLAN.md | 258 ++++++++++++++++++ 4 files changed, 656 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/28-ollama-integration/28-01-PLAN.md create mode 100644 .planning/phases/28-ollama-integration/28-02-PLAN.md create mode 100644 .planning/phases/28-ollama-integration/28-03-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index f29ccd8a..aed323e6 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -43,7 +43,11 @@ Plans: 5. The agent config page shows Nexus-managed skills alongside Hermes native skills in a single unified list 6. The dashboard agent card for a Hermes agent shows model name, memory usage, and native skill count 7. Token usage and estimated model cost are recorded per heartbeat and surfaced in the cost tracking view -**Plans**: TBD +**Plans:** 3 plans +Plans: +- [ ] 28-01-PLAN.md — Ollama service, routes, model catalog, and unit tests +- [ ] 28-02-PLAN.md — UI model selector dropdown, install callout, Hermes skill badge +- [ ] 28-03-PLAN.md — Hermes stateJson runtime data and dashboard HermesRuntimeCard **UI hint**: yes ### Phase 29: Default Provider & End-to-End @@ -89,5 +93,5 @@ All 16 v1 requirements are mapped to exactly one phase. No orphans. | Phase | Milestone | Plans Complete | Status | Completed | |-------|-----------|----------------|--------|-----------| | 27. Hermes Adapter | v1.4 | 1/1 | Complete | 2026-04-02 | -| 28. Ollama Integration & Agent Surface | v1.4 | 0/? | Not started | - | +| 28. Ollama Integration & Agent Surface | v1.4 | 0/3 | Not started | - | | 29. Default Provider & End-to-End | v1.4 | 0/? | Not started | - | diff --git a/.planning/phases/28-ollama-integration/28-01-PLAN.md b/.planning/phases/28-ollama-integration/28-01-PLAN.md new file mode 100644 index 00000000..b7cf84a9 --- /dev/null +++ b/.planning/phases/28-ollama-integration/28-01-PLAN.md @@ -0,0 +1,194 @@ +--- +phase: 28-ollama-integration +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - server/src/services/ollama.ts + - server/src/routes/ollama.ts + - server/src/routes/index.ts + - server/src/app.ts + - server/src/data/ollama-model-catalog.json + - server/src/__tests__/ollama-service.test.ts +autonomous: true +requirements: [OLLA-01, OLLA-02, OLLA-04, OLLA-05] + +must_haves: + truths: + - "detectOllama() returns installed:true + version when Ollama responds at /api/version" + - "detectOllama() returns installed:false + installUrl when Ollama is absent or times out" + - "listOllamaModels() returns model list with parameterSize, family, quantization from /api/tags" + - "getRecommendedModel() returns highest-quality model that fits within 75% system RAM" + - "GET /companies/:companyId/ollama/status returns OllamaStatus JSON" + - "GET /companies/:companyId/ollama/models returns OllamaModel[] + ramGb" + artifacts: + - path: "server/src/services/ollama.ts" + provides: "Ollama detection, model listing, recommendation logic" + exports: ["detectOllama", "listOllamaModels", "OllamaStatus", "OllamaModel"] + - path: "server/src/routes/ollama.ts" + provides: "HTTP routes for Ollama status and model listing" + exports: ["ollamaRoutes"] + - path: "server/src/data/ollama-model-catalog.json" + provides: "Static model catalog for RAM-based recommendations" + contains: "qwen2.5-coder" + - path: "server/src/__tests__/ollama-service.test.ts" + provides: "Unit tests for ollamaService" + min_lines: 60 + key_links: + - from: "server/src/routes/ollama.ts" + to: "server/src/services/ollama.ts" + via: "import detectOllama, listOllamaModels" + pattern: "import.*from.*services/ollama" + - from: "server/src/app.ts" + to: "server/src/routes/ollama.ts" + via: "api.use(ollamaRoutes())" + pattern: "ollamaRoutes" +--- + + +Create the server-side Ollama integration service, HTTP routes, model catalog, and unit tests. + +Purpose: Provides the backend API surface that the UI (Plan 02) will consume to detect Ollama, list available models, and recommend a model based on system RAM. +Output: Working server routes at /companies/:companyId/ollama/status and /models, plus unit tests. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/28-ollama-integration/28-RESEARCH.md + +@server/src/app.ts +@server/src/routes/index.ts +@server/src/routes/agents.ts (for assertCompanyAccess pattern) +@ui/src/api/client.ts (for API client pattern reference) + + + + + + Task 1: Create ollamaService + model catalog + unit tests + server/src/services/ollama.ts, server/src/data/ollama-model-catalog.json, server/src/__tests__/ollama-service.test.ts + + - server/src/services/heartbeat.ts (lines 1-30 for import patterns) + - .planning/phases/28-ollama-integration/28-RESEARCH.md (Pattern 1, Pattern 5, Code Examples) + + + - detectOllama returns { installed: true, version: "0.5.x" } when fetch to /api/version succeeds + - detectOllama returns { installed: false, version: null, installUrl: "https://ollama.com/download" } when fetch rejects (ECONNREFUSED) + - detectOllama returns { installed: false, version: null, installUrl } when fetch times out (AbortController) + - listOllamaModels returns OllamaModel[] mapped from /api/tags response with name, parameterSize, quantization, sizeBytes, family + - listOllamaModels returns empty array when Ollama is absent + - getRecommendedModel marks the highest-quality model that fits within 75% of given RAM budget as recommended=true + - getRecommendedModel with 8GB RAM recommends a 7b model, not a 32b model + - Model catalog JSON contains at least qwen2, llama, mistral, phi, deepseek families + + + 1. Create `server/src/data/ollama-model-catalog.json` with the static catalog from RESEARCH Pattern 5. Include families: qwen2, llama, mistral, phi, deepseek. Each variant has name, ramGb, vramGb, quality fields. + + 2. Create `server/src/services/ollama.ts`: + - `const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434"` + - `const OLLAMA_TIMEOUT_MS = 3000` + - `const INSTALL_URL = "https://ollama.com/download"` + - Export interface `OllamaStatus { installed: boolean; version: string | null; installUrl: string }` + - Export interface `OllamaModel { name: string; parameterSize: string; quantization: string; sizeBytes: number; family: string; recommended: boolean; recommendationReason: string | null }` + - Export `async function detectOllama(): Promise` — fetch /api/version with AbortController 3s timeout. On success return installed:true + version. On any error return installed:false + installUrl. + - Export `async function listOllamaModels(): Promise` — fetch /api/tags, map response.models to OllamaModel[]. On error return []. + - Export `function getRecommendedModel(models: OllamaModel[], systemRamBytes: number): OllamaModel[]` — reads catalog JSON, computes usableRamGb = (systemRamBytes / 1024^3) * 0.75, for each model checks if a catalog entry matches by name and fits in RAM. Returns models with `recommended` field set. The highest-quality model within budget gets recommended=true + a recommendationReason string. All others get recommended=false. + - Anti-pattern: Do NOT poll in a loop. Do NOT hard-code localhost:11434 — always use OLLAMA_BASE_URL. + + 3. Create `server/src/__tests__/ollama-service.test.ts`: + - Mock global fetch using vi.stubGlobal("fetch", vi.fn()) + - Test detectOllama success case (mock returns { version: "0.5.1" }) + - Test detectOllama failure case (mock rejects with ECONNREFUSED) + - Test detectOllama timeout case (mock never resolves, verify AbortController fires) + - Test listOllamaModels success with mock /api/tags response matching OllamaTagsResponse shape from RESEARCH + - Test listOllamaModels returns [] on fetch error + - Test getRecommendedModel with 8GB RAM → recommends 7b-class model + - Test getRecommendedModel with 32GB RAM → recommends 32b-class model + - Test getRecommendedModel with models not in catalog → recommended=false for all + + + cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts + + + - grep -q "detectOllama" server/src/services/ollama.ts + - grep -q "listOllamaModels" server/src/services/ollama.ts + - grep -q "getRecommendedModel" server/src/services/ollama.ts + - grep -q "OLLAMA_BASE_URL" server/src/services/ollama.ts + - grep -q "qwen2.5-coder" server/src/data/ollama-model-catalog.json + - grep -q "detectOllama" server/src/__tests__/ollama-service.test.ts + - grep -q "getRecommendedModel" server/src/__tests__/ollama-service.test.ts + + All ollama service tests pass. detectOllama, listOllamaModels, and getRecommendedModel functions exported and tested. Model catalog JSON file exists with 5+ model families. + + + + Task 2: Create Ollama HTTP routes and mount in app + server/src/routes/ollama.ts, server/src/routes/index.ts, server/src/app.ts + + - server/src/routes/agents.ts (lines 1-60 for route pattern, assertCompanyAccess usage) + - server/src/app.ts (lines 134-170 for route mounting pattern) + - server/src/routes/index.ts + + + 1. Create `server/src/routes/ollama.ts`: + - Import Router from express, import assertCompanyAccess from "./authz.js", import detectOllama/listOllamaModels/getRecommendedModel from "../services/ollama.js" + - Import `os` for `os.totalmem()` + - Export function `ollamaRoutes()` returning Router + - `GET /companies/:companyId/ollama/status`: + - Call `assertCompanyAccess(req, companyId)` + - Call `detectOllama()`, return JSON response + - `GET /companies/:companyId/ollama/models`: + - Call `assertCompanyAccess(req, companyId)` + - Call `detectOllama()` first — if not installed, return `{ models: [], ramGb: 0 }` + - Call `listOllamaModels()`, then `getRecommendedModel(models, os.totalmem())` + - Return `{ models: enrichedModels, ramGb: Math.round(os.totalmem() / 1073741824) }` + - Wrap each handler in try/catch, return 500 on unexpected errors + + 2. Add `export { ollamaRoutes } from "./ollama.js"` to `server/src/routes/index.ts` + + 3. In `server/src/app.ts`: + - Add import: `import { ollamaRoutes } from "./routes/ollama.js"` + - Add `api.use(ollamaRoutes())` after the `api.use(agentRoutes(db))` line (around line 152) + + + cd /opt/nexus/server && npx tsc --noEmit 2>&1 | head -20 + + + - grep -q "ollamaRoutes" server/src/routes/ollama.ts + - grep -q "assertCompanyAccess" server/src/routes/ollama.ts + - grep -q "/companies/:companyId/ollama/status" server/src/routes/ollama.ts + - grep -q "/companies/:companyId/ollama/models" server/src/routes/ollama.ts + - grep -q "ollamaRoutes" server/src/routes/index.ts + - grep -q "ollamaRoutes" server/src/app.ts + - grep -q "os.totalmem" server/src/routes/ollama.ts + + Ollama routes mounted at /companies/:companyId/ollama/status and /models. TypeScript compiles without errors. Routes use assertCompanyAccess for auth and os.totalmem() for RAM detection. + + + + + +- `cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts` — all tests pass +- `cd /opt/nexus/server && npx tsc --noEmit` — no type errors +- Ollama service gracefully returns installed:false when Ollama is not running (no crashes) + + + +- Ollama detection service exists with detectOllama, listOllamaModels, getRecommendedModel +- Model catalog JSON ships with 5+ model families +- HTTP routes mounted and accessible at /companies/:companyId/ollama/status and /models +- Unit tests cover success, failure, timeout, and recommendation scenarios +- All code compiles without TypeScript errors + + + +After completion, create `.planning/phases/28-ollama-integration/28-01-SUMMARY.md` + diff --git a/.planning/phases/28-ollama-integration/28-02-PLAN.md b/.planning/phases/28-ollama-integration/28-02-PLAN.md new file mode 100644 index 00000000..57d88a73 --- /dev/null +++ b/.planning/phases/28-ollama-integration/28-02-PLAN.md @@ -0,0 +1,198 @@ +--- +phase: 28-ollama-integration +plan: 02 +type: execute +wave: 2 +depends_on: [28-01] +files_modified: + - ui/src/api/ollama.ts + - ui/src/adapters/hermes-local/config-fields.tsx + - ui/src/pages/AgentDetail.tsx +autonomous: true +requirements: [OLLA-02, OLLA-03, OLLA-05, HERM-05] + +must_haves: + truths: + - "When Ollama is installed, the Hermes agent config shows a model dropdown listing all locally pulled models" + - "When an Ollama model is selected, adapterConfig saves model + provider:custom + base_url:http://localhost:11434/v1" + - "When Ollama is absent, the config shows an install callout with a link to https://ollama.com/download" + - "Recommended models are visually highlighted in the dropdown" + - "Hermes native skills show an 'Hermes skill' badge in the Skills tab" + - "Manual model entry still works as fallback when Ollama is absent" + artifacts: + - path: "ui/src/api/ollama.ts" + provides: "API client for Ollama status and model listing" + exports: ["ollamaApi"] + - path: "ui/src/adapters/hermes-local/config-fields.tsx" + provides: "Ollama model dropdown, install callout, manual fallback" + contains: "ollamaApi" + - path: "ui/src/pages/AgentDetail.tsx" + provides: "Hermes skill badge in AgentSkillsTab" + contains: "Hermes skill" + key_links: + - from: "ui/src/adapters/hermes-local/config-fields.tsx" + to: "ui/src/api/ollama.ts" + via: "useQuery + ollamaApi.status / ollamaApi.models" + pattern: "ollamaApi" + - from: "ui/src/adapters/hermes-local/config-fields.tsx" + to: "server/src/routes/ollama.ts" + via: "fetch /companies/:companyId/ollama/*" + pattern: "ollama" +--- + + +Create the UI surface for Ollama model selection in Hermes agent config and improve Hermes skill visibility. + +Purpose: Users can discover, browse, and select local Ollama models when configuring a Hermes agent, with hardware-aware recommendations highlighted. When Ollama is absent, users see install instructions. Hermes native skills are clearly labeled in the Skills tab. +Output: Working model selector dropdown, install callout, and Hermes skill badges. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/28-ollama-integration/28-RESEARCH.md +@.planning/phases/28-ollama-integration/28-01-SUMMARY.md + +@ui/src/adapters/hermes-local/config-fields.tsx +@ui/src/adapters/types.ts +@ui/src/api/client.ts +@ui/src/pages/AgentDetail.tsx (AgentSkillsTab around line 2362, unmanagedSkillRows around 2566) + + + + +GET /api/companies/:companyId/ollama/status + Response: { installed: boolean; version: string | null; installUrl: string } + +GET /api/companies/:companyId/ollama/models + Response: { models: OllamaModel[]; ramGb: number } + Where OllamaModel = { + name: string; // e.g. "qwen2.5-coder:32b" + parameterSize: string; // e.g. "32.8B" + quantization: string; // e.g. "Q4_K_M" + sizeBytes: number; + family: string; // e.g. "qwen2" + recommended: boolean; + recommendationReason: string | null; + } + + +AdapterConfigFieldsProps: { mode, isCreate, adapterType, values, set, config, eff, mark, models, hideInstructionsFile } + + + + + + + Task 1: Create ollamaApi client and enhance HermesLocalConfigFields with model dropdown + ui/src/api/ollama.ts, ui/src/adapters/hermes-local/config-fields.tsx + + - ui/src/api/client.ts (full file — for api.get pattern) + - ui/src/api/health.ts (for simple API client pattern) + - ui/src/adapters/hermes-local/config-fields.tsx (full file — current state) + - ui/src/adapters/types.ts (AdapterConfigFieldsProps interface) + - .planning/phases/28-ollama-integration/28-RESEARCH.md (Pattern 3, Pattern 4, Pitfall 1, Pitfall 4) + + + 1. Create `ui/src/api/ollama.ts`: + - Import `api` from "./client" (the request helper) + - Define types: `OllamaStatus { installed: boolean; version: string | null; installUrl: string }`, `OllamaModel { name: string; parameterSize: string; quantization: string; sizeBytes: number; family: string; recommended: boolean; recommendationReason: string | null }`, `OllamaModelsResponse { models: OllamaModel[]; ramGb: number }` + - Export `ollamaApi` object with: + - `status(companyId: string): Promise` — GET `/companies/${companyId}/ollama/status` + - `models(companyId: string): Promise` — GET `/companies/${companyId}/ollama/models` + + 2. Rewrite `ui/src/adapters/hermes-local/config-fields.tsx`: + - Add imports: `useQuery` from `@tanstack/react-query`, `ollamaApi` from `../../api/ollama` + - Need companyId: extract from URL params using `useParams` from react-router-dom, or accept via a context. Check existing config-fields patterns for how companyId is obtained — look at the component's parent to find how it gets companyId. If not available via props, use `useParams<{ companyId?: string }>()` matching the route pattern `/companies/:companyId/agents/...`. + - Add two queries (only enabled when companyId is truthy): + ``` + const { data: ollamaStatus } = useQuery({ queryKey: ["ollama", "status", companyId], queryFn: () => ollamaApi.status(companyId!), enabled: Boolean(companyId), staleTime: 60_000 }) + const { data: ollamaModels } = useQuery({ queryKey: ["ollama", "models", companyId], queryFn: () => ollamaApi.models(companyId!), enabled: Boolean(companyId && ollamaStatus?.installed), staleTime: 60_000 }) + ``` + - Replace the current free-text Model `` with a hybrid control: + - **When ollamaStatus?.installed AND ollamaModels?.models.length > 0**: Render a `