3 phases, 6 plans, 16 requirements. Archives copied to milestones/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
118 lines
5.2 KiB
Markdown
118 lines
5.2 KiB
Markdown
---
|
|
phase: 28-ollama-integration
|
|
plan: 03
|
|
subsystem: dashboard
|
|
tags: [hermes, ollama, stateJson, jsonb, heartbeat, dashboard]
|
|
|
|
# Dependency graph
|
|
requires:
|
|
- phase: 28-01
|
|
provides: ollama.ts service with detectOllama, listOllamaModels, getRecommendedModel
|
|
|
|
provides:
|
|
- getOllamaMemoryUsage() in ollama.ts — queries /api/ps for active model VRAM usage
|
|
- Hermes stateJson merge in heartbeat.ts updateRuntimeState — stores hermesModel + hermesMemoryBytes after each run
|
|
- HermesRuntimeCard component in AgentDetail.tsx — displays model, native skill count, VRAM in AgentOverview
|
|
affects: [28-02, 29-default-provider]
|
|
|
|
# Tech tracking
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- COALESCE jsonb concat pattern for stateJson merge (no overwrite)
|
|
- Best-effort Ollama /api/ps probe (error caught, returns null gracefully)
|
|
- Native skill count derived from agentsApi.skills in UI (avoids cross-DB query in heartbeat)
|
|
|
|
key-files:
|
|
created: []
|
|
modified:
|
|
- server/src/services/ollama.ts
|
|
- server/src/services/heartbeat.ts
|
|
- ui/src/pages/AgentDetail.tsx
|
|
|
|
key-decisions:
|
|
- "hermesNativeSkillCount derived from agentsApi.skills in HermesRuntimeCard (not stored in stateJson) — avoids cross-DB query in heartbeat path"
|
|
- "COALESCE jsonb concat used for stateJson merge — prevents overwriting existing fields from other heartbeat writers"
|
|
- "getOllamaMemoryUsage catches all errors and returns null — Ollama absence or model-not-loaded both show Not loaded"
|
|
|
|
patterns-established:
|
|
- "Pattern: Use COALESCE(column, '{}'::jsonb) || patch::jsonb for safe jsonb merge in Drizzle"
|
|
- "Pattern: Hermes-specific stateJson written in updateRuntimeState conditional on adapterType === hermes_local"
|
|
|
|
requirements-completed: [HERM-06, HERM-07]
|
|
|
|
# Metrics
|
|
duration: 12min
|
|
completed: 2026-04-01
|
|
---
|
|
|
|
# Phase 28 Plan 03: Hermes Runtime Dashboard Summary
|
|
|
|
**Hermes heartbeat now persists model name + VRAM via jsonb merge, and AgentOverview renders a HermesRuntimeCard showing model, native skill count, and memory usage.**
|
|
|
|
## Tasks Completed
|
|
|
|
| Task | Description | Commit |
|
|
|------|-------------|--------|
|
|
| 1 | Add getOllamaMemoryUsage + stateJson merge in heartbeat | dbdc62aa |
|
|
| 2 | Add HermesRuntimeCard in AgentOverview | 7458753a |
|
|
|
|
## What Was Built
|
|
|
|
### Server: getOllamaMemoryUsage (ollama.ts)
|
|
|
|
New exported function queries Ollama `/api/ps` with a 3-second AbortController timeout. Finds the running model by `name` or `model` field, returns `size_vram` bytes. Returns `null` on any error (graceful degradation per Pitfall 5 from RESEARCH).
|
|
|
|
### Server: Hermes stateJson Merge (heartbeat.ts)
|
|
|
|
After the existing `db.update(agentRuntimeState)` cost tracking block in `updateRuntimeState`, a `hermes_local`-gated block merges `hermesModel` and `hermesMemoryBytes` into `stateJson` using Postgres jsonb concat:
|
|
|
|
```sql
|
|
COALESCE(stateJson, '{}'::jsonb) || '{"hermesModel": ..., "hermesMemoryBytes": ...}'::jsonb
|
|
```
|
|
|
|
This never overwrites other fields already stored in stateJson (Pitfall 3 from RESEARCH).
|
|
|
|
### UI: HermesRuntimeCard (AgentDetail.tsx)
|
|
|
|
New component defined before `AgentOverview`, rendered inside it gated by `agent.adapterType === "hermes_local" && runtimeState`. Shows:
|
|
- **Model**: `stateJson.hermesModel` or "Not set"
|
|
- **Native Skills**: count from `agentsApi.skills(agentId).entries` filtered by `originLabel === "Hermes skill"`
|
|
- **Memory (VRAM)**: formatted from `stateJson.hermesMemoryBytes` or "Not loaded"
|
|
|
|
## Deviations from Plan
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. [Rule 1 - Bug] Plan referenced `adapterEntries` but type has `entries`**
|
|
- **Found during:** Task 2
|
|
- **Issue:** The plan's action block referenced `skillsData.adapterEntries` but `AgentSkillSnapshot` has `entries: AgentSkillEntry[]`
|
|
- **Fix:** Used `skillsData.entries` in the implementation
|
|
- **Files modified:** ui/src/pages/AgentDetail.tsx
|
|
- **Commit:** 7458753a
|
|
|
|
**2. [Rule 1 - Bug] Removed unused `createRequire` import from ollama.ts**
|
|
- **Found during:** Task 1
|
|
- **Issue:** Plan 01 left a `createRequire` import in ollama.ts that was unused
|
|
- **Fix:** Removed the unused import when modifying the file
|
|
- **Files modified:** server/src/services/ollama.ts
|
|
- **Commit:** dbdc62aa
|
|
|
|
## Known Stubs
|
|
|
|
None — all data flows are wired (stateJson written by heartbeat, read by HermesRuntimeCard). Model name and memory bytes will be `null`/`undefined` until a Hermes run completes, which is correct behavior (displays "Not set" / "Not loaded").
|
|
|
|
## HERM-06 Verification (No Code Change)
|
|
|
|
Cost tracking for Hermes + Ollama runs correctly returns $0.00:
|
|
- `result.costUsd` is `undefined` for local Ollama runs
|
|
- `normalizeBilledCostCents(undefined, billingType)` returns `0`
|
|
- The `if (additionalCostCents > 0 || hasTokenUsage)` guard suppresses cost events when no token data emitted
|
|
- This is correct behavior per RESEARCH Pitfall 6
|
|
|
|
## Self-Check: PASSED
|
|
|
|
- `/opt/nexus/.claude/worktrees/agent-ad37cce3/server/src/services/ollama.ts` — exists with getOllamaMemoryUsage
|
|
- `/opt/nexus/.claude/worktrees/agent-ad37cce3/server/src/services/heartbeat.ts` — exists with hermes_local block
|
|
- `/opt/nexus/.claude/worktrees/agent-ad37cce3/ui/src/pages/AgentDetail.tsx` — exists with HermesRuntimeCard
|
|
- Commits dbdc62aa, 7458753a — confirmed in git log
|