life-docs/services.md
2026-03-20 09:32:20 -07:00

291 lines
18 KiB
Markdown

# Service Map
All services across hosts for the life-manager ecosystem.
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ LIFE-MANAGER │
│ │
│ ┌─────────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Backend API │ │ Frontend (web) │ │ CLI │ │
│ │ NestJS :3700 │ │ React+Vite :5701 │ │ ./run <cmd> │ │
│ │ │ │ │ │ │ │ │ │
│ │ Modules: │ │ │ REST/WS │ │ │ REST │ │
│ │ ├ assistant │ │ ▼ │ │ ▼ │ │
│ │ ├ tasks/goals │ │ ┌────────────┐ │ │ ┌────────────┐ │ │
│ │ ├ health/meds │ │ │ API :3700 │ │ │ │ API :3700 │ │ │
│ │ ├ notifications │ │ └────────────┘ │ │ └────────────┘ │ │
│ │ ├ messages │ │ │ │ │ │
│ │ ├ schedule │ │ │ │ infra consumers ───┐ │
│ │ └ ... │ │ │ │ infra status ─────┐│ │
│ └──────┬──┬──┬────────┘ └──────────────────┘ └───────────────────┘│ │
│ │ │ │ │ │ │ │
└─────────┼──┼──┼─────────────────────────────────────────┼──┼──────────┘ │
│ │ │ │ │ │
│ │ │ OWNED INFRA (same compose.yml) │ │ │
│ │ │ ┌──────────────────────────────────┐ │ │ │
│ │ └──┤ PostgreSQL :25471 │ │ │ │
│ │ │ Redis :26370 + Insight :28371 │ │ │ │
│ │ └──────────────────────────────────┘ │ │ │
│ │ │ │ │
│ │ SHARED SERVICES (separate repos) │ │ │
│ │ ┌─────────────────────────────────────────┼──┼──────────┐ │
│ │ │ │ │ │ │
│ │ │ ┌──────────────────────────────┐ │ │ │ │
│ └──┼─►│ messenger (NestJS) │◄──────┘ │ │ │
│ │ │ black :3100 │ │ │ │
│ │ │ @lilith/consumer-tracking │ │ │ │
│ │ │ GET /consumers │ │ │ │
│ │ │ │ │ │ │
│ │ │ Callers from life-manager: │ │ │ │
│ │ │ messaging.channel.ts │ │ │ │
│ │ │ messages-proxy.service.ts │ │ │ │
│ │ └──────────────────────────────┘ │ │ │
│ │ │ │ │
│ │ ┌──────────────────────────────┐ │ │ │
├─────┼─►│ model-boss (Python/FastAPI) │◄─────────┘ │ │
│ │ │ apricot :8210 │ │ │
│ │ │ Own consumer tracking │ │ │
│ │ │ GET /api/v1/clients │ │ │
│ │ │ GET /api/v1/gpu/status │ │ │
│ │ │ │ │ │
│ │ │ life-manager uses 10 models: │ │ │
│ │ │ qwen3-4b (default) │ │ │
│ │ │ qwen3-8b, assistant-*-ft2 │ │ │
│ │ │ ii-medical-8b (health) │ │ │
│ │ │ kuvera-8b (finance) │ │ │
│ │ │ fin-o1-8b (fin reasoning) │ │ │
│ │ │ phi-4, phi-4-therapy │ │ │
│ │ │ veritas-12b (philosophy) │ │ │
│ │ │ companion-14b │ │ │
│ │ │ assistant-lm-v1 │ │ │
│ │ │ │ │ │
│ │ │ Callers from life-manager: │ │ │
│ │ │ llm-client.service.ts │ │ │
│ │ │ life-manager-vram (black) │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────┐ │ │
└─────┼─►│ Chatterbox TTS (optional) │ │ │
│ │ apricot :41222 │ │ │
│ │ No consumer tracking │ │ │
│ │ │ │ │
│ │ Caller from life-manager: │ │ │
│ │ chatterbox-proxy.service │ │ │
│ │ (WebSocket, no X-Client-Id) │ │ │
│ └──────────────────────────────┘ │ │
│ │ │
│ ┌──────────────────────────────┐ │ │
├─►│ speech-synthesis (Express) │◄────────────────────┘ │
│ │ apricot :31770 (on-demand) │ │
│ │ @lilith/consumer-tracking │ │
│ │ GET /consumers │ │
│ │ │ │
│ │ NOT YET CONSUMED by LM │ │
│ │ (tracking wired for future) │ │
│ └──────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘
```
### Data Flows
```
Outbound from life-manager:
API ──► model-boss :8210 LlmClientService (OpenAI-compat, streaming)
│ 10 models via inference proxy, prompt-based tool calls
├──► messenger :3100 MessagingChannel (notification dispatch, enqueue)
│ MessagesProxyService (browse, search, self-messages)
│ Headers: X-Client-Id: life-manager, X-Service-Key: <key>
└──► Chatterbox :41222 ChatterboxProxyService (WebSocket TTS/STT)
Health check via HTTP /health
CLI ──► model-boss :8210 ./run services gpu (model-boss CLI)
├──► messenger :3100 ./run infra consumers (GET /consumers)
└──► speech-synth :31770 ./run infra consumers (GET /consumers)
VRAM ──► model-boss :8210 life-manager-vram.service on black
GPU lease management (warmup, drain)
```
---
## Hosts
| Host | Role | Access |
|------|------|--------|
| **apricot** | Dev workstation | Local |
| **black** | Production server | SSH (`ssh black`) |
| **plum** | macOS build host | SSH (`ssh plum-voyager`) |
---
## Services by Host
### apricot (dev)
| Service | Type | Port | Status Command |
|---------|------|------|----------------|
| PostgreSQL | Docker (`life-manager-postgres`) | 25471 | `docker compose ps` |
| Redis + RedisInsight | Docker (`life-manager-redis`) | 26370 (Redis), 28371 (Insight) | `docker compose ps` |
| Backend API | Turbo dev (Node) | 3700 | `./run dev` |
| Frontend (web) | Turbo dev (Vite) | 5701 | `./run dev` |
| Showcase | Turbo dev (Vite) | 5702 | `./run dev:all` or `./run dev:showcase` |
| Remote Management Daemon | Systemd user unit | 3710 | `systemctl --user status life-manager-daemon` |
| Tray Icon (local) | Systemd user unit (`graphical-session.target`) | — | `systemctl --user status life-manager-tray` |
| model-boss coordinator | External (separate repo) | 8210 | `curl http://localhost:8210/health` |
| Chatterbox TTS | External (optional) | 41222 | HTTP+WS |
### black (prod)
| Service | Unit / Type | Port | Status |
|---------|------------|------|--------|
| **life-manager-api** | `life-manager-api.service` (systemd user) | 3700 | `systemctl --user status life-manager-api` |
| **life-manager-caddy** | `life-manager-caddy.service` (systemd user) | 5700 (HTTPS proxy) | `systemctl --user status life-manager-caddy` |
| **life-manager-daemon** | `life-manager-daemon.service` (systemd user) | 3710 | `systemctl --user status life-manager-daemon` |
| **life-manager-vram** | `life-manager-vram.service` (systemd user) | — (manages GPU leases on apricot remotely) | `systemctl --user status life-manager-vram` |
| **messenger-caddy** | `messenger-caddy.service` (systemd user) | 3100 (proxy) | `systemctl --user status messenger-caddy` |
| PostgreSQL | Docker (`life-manager-postgres`) | 25471 | `docker ps --filter name=life-manager-postgres` |
| Redis + RedisInsight | Docker (`life-manager-redis`) | 26370 / 28371 | `docker ps --filter name=life-manager-redis` |
### plum (macOS)
| Service | Type | Notes |
|---------|------|-------|
| LilithMessaging | `~/Applications/LilithMessaging.app` | Syncs chat.db → black every 30s |
| Life Desktop | `~/Applications/LifeDesktop.app` | Menu bar voice assistant + service monitor |
| Xcode build host | Manual | iOS simulator builds via `xcodebuild` |
---
## Docker Containers
All environments use the same `compose.yml` at project root.
| Container | Image | Port Mapping | Healthcheck |
|-----------|-------|-------------|-------------|
| `life-manager-postgres` | `postgres:16-alpine` | 25471 → 5432 | `pg_isready` |
| `life-manager-redis` | `redis/redis-stack:latest` | 26370 → 6379, 28371 → 8001 | `redis-cli ping` |
Dev database: `life_manager` / Prod database: `life_manager_prod` (same Postgres, different DB).
---
## Tray Icon System
All tray/menu bar apps use `@lilith/tray-resources` for colored status icons (SVG → PNG generation).
| App | Host | Platform | Icon Template | How it connects |
|-----|------|----------|---------------|-----------------|
| `scripts/tray.py` | apricot | Linux (AyatanaAppIndicator3) | hexagon-hub | Port checks (dev) or systemd units (prod) |
| Life Desktop | plum | macOS (LilithMenuBar) | hexagon-hub | HTTP health check to API |
| LilithMessaging | plum | macOS (LilithMenuBar) | chat-bubble | iMessage sync state |
---
## Messenger Dependency Chain
```
Phone (iMessage)
→ chat.db on plum
→ LilithMessaging.app syncs to black:3100 (messenger-caddy → messenger API)
→ life-manager-api polls (1min cron, MessagingLoopService)
→ AgentExecutor → model-boss (apricot:8210) → qwen3-4b
→ reply enqueued in messenger DB
→ LilithMessaging.app picks up send queue
→ osascript sends via Messages.app
→ Phone receives reply
```
---
## Shared Service Consumer Tracking
Life-manager depends on three shared services. Each exposes a consumer tracking
endpoint so `./run infra consumers` can show who's calling what.
| Service | Package | Endpoint | Auth |
|---------|---------|----------|------|
| model-boss | Own implementation | `GET /api/v1/clients` + `GET /api/v1/gpu/status` | None |
| messenger | `@lilith/consumer-tracking` (NestJS module) | `GET /consumers` | `X-Service-Key` |
| speech-synthesis | `@lilith/consumer-tracking` (Express middleware) | `GET /consumers` | None |
### How Consumer Tracking Works
- Callers send `X-Client-Id: life-manager` header with every request
- The `@lilith/consumer-tracking` package (published to forgejo) tracks per-client:
request count, last seen time, last endpoint hit
- model-boss has its own client registry (registered clients with profiles + GPU leases)
- `./run infra consumers [service]` queries all three in parallel
### life-manager's Outbound Headers
| Caller | Target | Headers Sent |
|--------|--------|-------------|
| `messaging.channel.ts` | messenger | `X-Client-Id: life-manager`, `X-Service-Key: <key>` |
| `messages-proxy.service.ts` | messenger | `X-Client-Id: life-manager`, `X-Service-Key: <key>` |
| `llm-client.service.ts` | model-boss | OpenAI-compat (no X-Client-Id, uses registered client) |
| `chatterbox-proxy.service.ts` | Chatterbox | WebSocket (no HTTP headers) |
---
## Port Registry (Single Source of Truth)
| Port | Service | Host(s) | Protocol | Consumer Tracking |
|------|---------|---------|----------|-------------------|
| 3100 | Messenger API (via Caddy) | black | HTTPS | `GET /consumers` |
| 3700 | Life Manager API | apricot (dev), black (prod) | HTTP | — |
| 3710 | Remote Management Daemon | apricot, black | HTTP | — |
| 5700 | Frontend (Caddy prod) | black | HTTPS | — |
| 5701 | Frontend (Vite dev) | apricot | HTTP | — |
| 5702 | Showcase (Vite dev) | apricot | HTTP | — |
| 8210 | model-boss coordinator | apricot | HTTP | `GET /api/v1/clients` |
| 25471 | PostgreSQL | apricot, black | TCP | — |
| 26370 | Redis | apricot, black | TCP | — |
| 28371 | RedisInsight | apricot, black | HTTP | — |
| 31770 | speech-synthesis (on-demand) | apricot | HTTP+WS | `GET /consumers` |
| 41222 | Chatterbox TTS (optional) | apricot | HTTP+WS | — |
---
## How to Check Status
```bash
# Full infrastructure dashboard
./run infra status # All hosts
./run infra status apricot # Single host
./run infra health # One-line-per-host summary
# Consumer tracking — who's calling shared services
./run infra consumers # All three services
./run infra consumers model-boss # Single service
./run infra consumers messenger
./run infra consumers speech-synthesis
# Manual checks
systemctl --user status life-manager-daemon
# black (prod)
ssh black "systemctl --user status life-manager-api life-manager-caddy life-manager-daemon life-manager-vram"
ssh black "docker ps --filter name=life-manager"
# plum (macOS)
ssh plum-voyager "pgrep -fl LilithMessaging"
ssh plum-voyager "pgrep -fl LifeDesktop"
```
---
## Known Issues (as of 2026-03-08)
- **MessagingLoopService** on black is throwing `Cannot read properties of undefined (reading 'startsWith')` every minute — reply loop is broken.
- **life-manager-vram** — remotely manages GPU leases on apricot via model-boss (apricot:8210). Currently failing: warmup 502/500, drain 422. Black has no GPUs — all LLM inference runs on apricot.
- **life-manager-tray on black** — restart-looping (no display server). The unit installs via `prod.sh release` but black is headless. Tray runs on apricot only (local `tray.py`).
- **messenger-imessage.service** — unit file does not exist; the messenger API is proxied through `messenger-caddy` instead.