# 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 │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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: │ └──► 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: ` | | `messages-proxy.service.ts` | messenger | `X-Client-Id: life-manager`, `X-Service-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.